diff --git a/.github/actions/embed/Dockerfile b/.github/actions/embed/Dockerfile index a92ec5a28..13ee7c725 100644 --- a/.github/actions/embed/Dockerfile +++ b/.github/actions/embed/Dockerfile @@ -12,4 +12,4 @@ ENV PATH=/cargo/bin:/rust/bin:$PATH RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path -ENTRYPOINT [ "/cargo/bin/cargo", "test", "--lib", "--release", "--all-features" ] +ENTRYPOINT [ "/cargo/bin/cargo", "test", "--all", "--release", "--all-features", "--no-fail-fast" ] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e32486349..a68e4567e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,7 +92,7 @@ jobs: run: cargo build --release --features closure,anyhow --all # Test & lint - name: Test inline examples - run: cargo test --release --all --features closure,anyhow + run: cargo test --release --all --features closure,anyhow --no-fail-fast - name: Run rustfmt if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.2' run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index ac1d5bdd0..357778d20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,11 @@ closure = [] embed = [] [workspace] -members = ["crates/macros", "crates/cli"] +members = [ + "crates/macros", + "crates/cli", + "tests" +] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docs"] diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 9d09fb00b..c6a593808 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -231,6 +231,7 @@ bind! { _ZEND_TYPE_NULLABLE_BIT, ts_rsrc_id, _ZEND_TYPE_NAME_BIT, + _ZEND_TYPE_LITERAL_NAME_BIT, ZEND_INTERNAL_FUNCTION, ZEND_USER_FUNCTION, ZEND_EVAL_CODE, @@ -305,5 +306,6 @@ bind! { php_module_startup, php_module_shutdown, php_request_startup, - php_request_shutdown + php_request_shutdown, + instanceof_function_slow } diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 4a9ee7caf..aade607af 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1163,6 +1163,12 @@ extern "C" { extern "C" { pub fn zend_is_identical(op1: *const zval, op2: *const zval) -> bool; } +extern "C" { + pub fn instanceof_function_slow( + instance_ce: *const zend_class_entry, + ce: *const zend_class_entry, + ) -> bool; +} extern "C" { pub fn zend_is_true(op: *const zval) -> ::std::os::raw::c_int; } @@ -1986,7 +1992,7 @@ pub struct _php_stream { pub wrapperthis: *mut ::std::os::raw::c_void, pub wrapperdata: zval, pub _bitfield_align_1: [u8; 0], - pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, pub mode: [::std::os::raw::c_char; 16usize], pub flags: u32, pub res: *mut zend_resource, @@ -2005,122 +2011,105 @@ pub struct _php_stream { } impl _php_stream { #[inline] - pub fn is_persistent(&self) -> u16 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u16) } + pub fn is_persistent(&self) -> u8 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u8) } } #[inline] - pub fn set_is_persistent(&mut self, val: u16) { + pub fn set_is_persistent(&mut self, val: u8) { unsafe { - let val: u16 = ::std::mem::transmute(val); + let val: u8 = ::std::mem::transmute(val); self._bitfield_1.set(0usize, 1u8, val as u64) } } #[inline] - pub fn in_free(&self) -> u16 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 2u8) as u16) } + pub fn in_free(&self) -> u8 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 2u8) as u8) } } #[inline] - pub fn set_in_free(&mut self, val: u16) { + pub fn set_in_free(&mut self, val: u8) { unsafe { - let val: u16 = ::std::mem::transmute(val); + let val: u8 = ::std::mem::transmute(val); self._bitfield_1.set(1usize, 2u8, val as u64) } } #[inline] - pub fn eof(&self) -> u16 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u16) } + pub fn eof(&self) -> u8 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u8) } } #[inline] - pub fn set_eof(&mut self, val: u16) { + pub fn set_eof(&mut self, val: u8) { unsafe { - let val: u16 = ::std::mem::transmute(val); + let val: u8 = ::std::mem::transmute(val); self._bitfield_1.set(3usize, 1u8, val as u64) } } #[inline] - pub fn __exposed(&self) -> u16 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u16) } + pub fn __exposed(&self) -> u8 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u8) } } #[inline] - pub fn set___exposed(&mut self, val: u16) { + pub fn set___exposed(&mut self, val: u8) { unsafe { - let val: u16 = ::std::mem::transmute(val); + let val: u8 = ::std::mem::transmute(val); self._bitfield_1.set(4usize, 1u8, val as u64) } } #[inline] - pub fn fclose_stdiocast(&self) -> u16 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 2u8) as u16) } + pub fn fclose_stdiocast(&self) -> u8 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 2u8) as u8) } } #[inline] - pub fn set_fclose_stdiocast(&mut self, val: u16) { + pub fn set_fclose_stdiocast(&mut self, val: u8) { unsafe { - let val: u16 = ::std::mem::transmute(val); + let val: u8 = ::std::mem::transmute(val); self._bitfield_1.set(5usize, 2u8, val as u64) } } #[inline] - pub fn has_buffered_data(&self) -> u16 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u16) } + pub fn has_buffered_data(&self) -> u8 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u8) } } #[inline] - pub fn set_has_buffered_data(&mut self, val: u16) { + pub fn set_has_buffered_data(&mut self, val: u8) { unsafe { - let val: u16 = ::std::mem::transmute(val); + let val: u8 = ::std::mem::transmute(val); self._bitfield_1.set(7usize, 1u8, val as u64) } } #[inline] - pub fn fclose_stdiocast_flush_in_progress(&self) -> u16 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u16) } - } - #[inline] - pub fn set_fclose_stdiocast_flush_in_progress(&mut self, val: u16) { - unsafe { - let val: u16 = ::std::mem::transmute(val); - self._bitfield_1.set(8usize, 1u8, val as u64) - } - } - #[inline] pub fn new_bitfield_1( - is_persistent: u16, - in_free: u16, - eof: u16, - __exposed: u16, - fclose_stdiocast: u16, - has_buffered_data: u16, - fclose_stdiocast_flush_in_progress: u16, - ) -> __BindgenBitfieldUnit<[u8; 2usize]> { - let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + is_persistent: u8, + in_free: u8, + eof: u8, + __exposed: u8, + fclose_stdiocast: u8, + has_buffered_data: u8, + ) -> __BindgenBitfieldUnit<[u8; 1usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); __bindgen_bitfield_unit.set(0usize, 1u8, { - let is_persistent: u16 = unsafe { ::std::mem::transmute(is_persistent) }; + let is_persistent: u8 = unsafe { ::std::mem::transmute(is_persistent) }; is_persistent as u64 }); __bindgen_bitfield_unit.set(1usize, 2u8, { - let in_free: u16 = unsafe { ::std::mem::transmute(in_free) }; + let in_free: u8 = unsafe { ::std::mem::transmute(in_free) }; in_free as u64 }); __bindgen_bitfield_unit.set(3usize, 1u8, { - let eof: u16 = unsafe { ::std::mem::transmute(eof) }; + let eof: u8 = unsafe { ::std::mem::transmute(eof) }; eof as u64 }); __bindgen_bitfield_unit.set(4usize, 1u8, { - let __exposed: u16 = unsafe { ::std::mem::transmute(__exposed) }; + let __exposed: u8 = unsafe { ::std::mem::transmute(__exposed) }; __exposed as u64 }); __bindgen_bitfield_unit.set(5usize, 2u8, { - let fclose_stdiocast: u16 = unsafe { ::std::mem::transmute(fclose_stdiocast) }; + let fclose_stdiocast: u8 = unsafe { ::std::mem::transmute(fclose_stdiocast) }; fclose_stdiocast as u64 }); __bindgen_bitfield_unit.set(7usize, 1u8, { - let has_buffered_data: u16 = unsafe { ::std::mem::transmute(has_buffered_data) }; + let has_buffered_data: u8 = unsafe { ::std::mem::transmute(has_buffered_data) }; has_buffered_data as u64 }); - __bindgen_bitfield_unit.set(8usize, 1u8, { - let fclose_stdiocast_flush_in_progress: u16 = - unsafe { ::std::mem::transmute(fclose_stdiocast_flush_in_progress) }; - fclose_stdiocast_flush_in_progress as u64 - }); __bindgen_bitfield_unit } } @@ -2340,6 +2329,117 @@ extern "C" { module_number: ::std::os::raw::c_int, ); } +extern "C" { + pub fn php_info_print_table_header(num_cols: ::std::os::raw::c_int, ...); +} +extern "C" { + pub fn php_info_print_table_row(num_cols: ::std::os::raw::c_int, ...); +} +extern "C" { + pub fn php_info_print_table_start(); +} +extern "C" { + pub fn php_info_print_table_end(); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct hostent { + pub h_name: *mut ::std::os::raw::c_char, + pub h_aliases: *mut *mut ::std::os::raw::c_char, + pub h_addrtype: ::std::os::raw::c_int, + pub h_length: ::std::os::raw::c_int, + pub h_addr_list: *mut *mut ::std::os::raw::c_char, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct php_file_globals { + pub pclose_ret: ::std::os::raw::c_int, + pub def_chunk_size: usize, + pub auto_detect_line_endings: bool, + pub default_socket_timeout: zend_long, + pub user_agent: *mut ::std::os::raw::c_char, + pub from_address: *mut ::std::os::raw::c_char, + pub user_stream_current_filename: *const ::std::os::raw::c_char, + pub default_context: *mut php_stream_context, + pub stream_wrappers: *mut HashTable, + pub stream_filters: *mut HashTable, + pub wrapper_errors: *mut HashTable, + pub pclose_wait: ::std::os::raw::c_int, + pub tmp_host_info: hostent, + pub tmp_host_buf: *mut ::std::os::raw::c_char, + pub tmp_host_buf_len: usize, +} +extern "C" { + pub static mut file_globals: php_file_globals; +} +extern "C" { + pub static mut zend_ce_throwable: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_exception: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_error_exception: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_compile_error: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_parse_error: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_type_error: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_argument_count_error: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_value_error: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_arithmetic_error: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_division_by_zero_error: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_unhandled_match_error: *mut zend_class_entry; +} +extern "C" { + pub fn zend_throw_exception_ex( + exception_ce: *mut zend_class_entry, + code: zend_long, + format: *const ::std::os::raw::c_char, + ... + ) -> *mut zend_object; +} +extern "C" { + pub fn zend_throw_exception_object(exception: *mut zval); +} +extern "C" { + pub fn zend_do_implement_interface(ce: *mut zend_class_entry, iface: *mut zend_class_entry); +} +extern "C" { + pub static mut zend_ce_traversable: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_aggregate: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_iterator: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_arrayaccess: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_serializable: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_countable: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_stringable: *mut zend_class_entry; +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct sapi_header_struct { @@ -2534,132 +2634,3 @@ pub struct _sapi_post_entry { ), >, } -extern "C" { - pub fn php_request_startup() -> zend_result; -} -extern "C" { - pub fn php_request_shutdown(dummy: *mut ::std::os::raw::c_void); -} -extern "C" { - pub fn php_module_startup( - sf: *mut sapi_module_struct, - additional_module: *mut zend_module_entry, - ) -> zend_result; -} -extern "C" { - pub fn php_module_shutdown(); -} -extern "C" { - pub fn php_execute_script(primary_file: *mut zend_file_handle) -> bool; -} -extern "C" { - pub fn php_info_print_table_header(num_cols: ::std::os::raw::c_int, ...); -} -extern "C" { - pub fn php_info_print_table_row(num_cols: ::std::os::raw::c_int, ...); -} -extern "C" { - pub fn php_info_print_table_start(); -} -extern "C" { - pub fn php_info_print_table_end(); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct hostent { - pub h_name: *mut ::std::os::raw::c_char, - pub h_aliases: *mut *mut ::std::os::raw::c_char, - pub h_addrtype: ::std::os::raw::c_int, - pub h_length: ::std::os::raw::c_int, - pub h_addr_list: *mut *mut ::std::os::raw::c_char, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct php_file_globals { - pub pclose_ret: ::std::os::raw::c_int, - pub def_chunk_size: usize, - pub auto_detect_line_endings: bool, - pub default_socket_timeout: zend_long, - pub user_agent: *mut ::std::os::raw::c_char, - pub from_address: *mut ::std::os::raw::c_char, - pub user_stream_current_filename: *const ::std::os::raw::c_char, - pub default_context: *mut php_stream_context, - pub stream_wrappers: *mut HashTable, - pub stream_filters: *mut HashTable, - pub wrapper_errors: *mut HashTable, - pub pclose_wait: ::std::os::raw::c_int, - pub tmp_host_info: hostent, - pub tmp_host_buf: *mut ::std::os::raw::c_char, - pub tmp_host_buf_len: usize, -} -extern "C" { - pub static mut file_globals: php_file_globals; -} -extern "C" { - pub static mut zend_ce_throwable: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_exception: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_error_exception: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_compile_error: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_parse_error: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_type_error: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_argument_count_error: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_value_error: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_arithmetic_error: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_division_by_zero_error: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_unhandled_match_error: *mut zend_class_entry; -} -extern "C" { - pub fn zend_throw_exception_ex( - exception_ce: *mut zend_class_entry, - code: zend_long, - format: *const ::std::os::raw::c_char, - ... - ) -> *mut zend_object; -} -extern "C" { - pub fn zend_throw_exception_object(exception: *mut zval); -} -extern "C" { - pub fn zend_do_implement_interface(ce: *mut zend_class_entry, iface: *mut zend_class_entry); -} -extern "C" { - pub static mut zend_ce_traversable: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_aggregate: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_iterator: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_arrayaccess: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_serializable: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_countable: *mut zend_class_entry; -} -extern "C" { - pub static mut zend_ce_stringable: *mut zend_class_entry; -} diff --git a/guide/src/types/binary.md b/guide/src/types/binary.md index aa296b16c..74f9f9de3 100644 --- a/guide/src/types/binary.md +++ b/guide/src/types/binary.md @@ -45,7 +45,7 @@ pub fn test_binary(input: Binary) -> Binary { ```php 5, [1] => 4, [2] => 3, [3] => 2, [4] => 1 } ``` diff --git a/src/closure.rs b/src/closure.rs index 03e02653c..d9884d133 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -171,6 +171,7 @@ class_derives!(Closure); /// /// This trait is automatically implemented on functions with up to 8 /// parameters. +#[allow(clippy::missing_safety_doc)] pub unsafe trait PhpClosure { /// Invokes the closure. fn invoke<'a>(&'a mut self, parser: ArgParser<'a, '_>, ret: &mut Zval); diff --git a/src/zend/_type.rs b/src/zend/_type.rs index dc2ba4783..771851449 100644 --- a/src/zend/_type.rs +++ b/src/zend/_type.rs @@ -3,10 +3,9 @@ use std::{ffi::c_void, ptr}; use crate::{ ffi::{ zend_type, IS_MIXED, MAY_BE_ANY, MAY_BE_BOOL, _IS_BOOL, _ZEND_IS_VARIADIC_BIT, - _ZEND_SEND_MODE_SHIFT, _ZEND_TYPE_NAME_BIT, _ZEND_TYPE_NULLABLE_BIT, + _ZEND_SEND_MODE_SHIFT, _ZEND_TYPE_NULLABLE_BIT, }, flags::DataType, - types::ZendStr, }; /// Internal Zend type. @@ -79,15 +78,21 @@ impl ZendType { is_variadic: bool, allow_null: bool, ) -> Option { + let mut flags = Self::arg_info_flags(pass_by_ref, is_variadic); + if allow_null { + flags |= _ZEND_TYPE_NULLABLE_BIT + } + cfg_if::cfg_if! { + if #[cfg(php83)] { + flags |= crate::ffi::_ZEND_TYPE_LITERAL_NAME_BIT + } else { + flags |= crate::ffi::_ZEND_TYPE_NAME_BIT + } + } + Some(Self { - ptr: ZendStr::new(class_name, true).into_raw().as_ptr() as *mut c_void, - type_mask: _ZEND_TYPE_NAME_BIT - | (if allow_null { - _ZEND_TYPE_NULLABLE_BIT - } else { - 0 - }) - | Self::arg_info_flags(pass_by_ref, is_variadic), + ptr: std::ffi::CString::new(class_name).ok()?.into_raw() as *mut c_void, + type_mask: flags, }) } diff --git a/src/zend/class.rs b/src/zend/class.rs index d9765a3c5..03d3418c0 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,5 +1,6 @@ //! Builder and objects for creating classes in the PHP world. +use crate::ffi::instanceof_function_slow; use crate::types::{ZendIterator, Zval}; use crate::{ boxed::ZBox, @@ -61,13 +62,7 @@ impl ClassEntry { return true; } - if other.is_interface() { - return self - .interfaces() - .map_or(false, |mut it| it.any(|ce| ce == other)); - } - - std::iter::successors(self.parent(), |p| p.parent()).any(|ce| ce == other) + unsafe { instanceof_function_slow(self as _, other as _) } } /// Returns an iterator of all the interfaces that the class implements. diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 000000000..55aef2102 --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tests" +version = "0.0.0" +edition = "2021" +publish = false +license = "MIT OR Apache-2.0" + +[dependencies] +ext-php-rs = { path = "../", features = ["closure"] } + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/tests/src/integration/_utils.php b/tests/src/integration/_utils.php new file mode 100644 index 000000000..2201e1f4f --- /dev/null +++ b/tests/src/integration/_utils.php @@ -0,0 +1,11 @@ + '1', + 'b' => '2', + 'c' => '3' +]); + +assert(array_key_exists('a', $assoc)); +assert(array_key_exists('b', $assoc)); +assert(array_key_exists('c', $assoc)); +assert(in_array('1', $assoc)); +assert(in_array('2', $assoc)); +assert(in_array('3', $assoc)); diff --git a/tests/src/integration/array.rs b/tests/src/integration/array.rs new file mode 100644 index 000000000..447ece1bd --- /dev/null +++ b/tests/src/integration/array.rs @@ -0,0 +1,4 @@ +#[test] +fn binary_works() { + assert!(crate::integration::run_php("array.php")); +} diff --git a/tests/src/integration/binary.php b/tests/src/integration/binary.php new file mode 100644 index 000000000..30cbd429f --- /dev/null +++ b/tests/src/integration/binary.php @@ -0,0 +1,13 @@ + $a, 'test') === 'test'); diff --git a/tests/src/integration/callable.rs b/tests/src/integration/callable.rs new file mode 100644 index 000000000..1d4ac2d51 --- /dev/null +++ b/tests/src/integration/callable.rs @@ -0,0 +1,4 @@ +#[test] +fn callable_works() { + assert!(crate::integration::run_php("callable.php")); +} diff --git a/tests/src/integration/class.php b/tests/src/integration/class.php new file mode 100644 index 000000000..568cafbb1 --- /dev/null +++ b/tests/src/integration/class.php @@ -0,0 +1,21 @@ +getString() === 'lorem ipsum'); +$class->setString('dolor et'); +assert($class->getString() === 'dolor et'); + +assert($class->getNumber() === 2022); +$class->setNumber(2023); +assert($class->getNumber() === 2023); + +// Tests #prop decorator +assert($class->boolean); +$class->boolean = false; +assert($class->boolean === false); diff --git a/tests/src/integration/class.rs b/tests/src/integration/class.rs new file mode 100644 index 000000000..525220e85 --- /dev/null +++ b/tests/src/integration/class.rs @@ -0,0 +1,4 @@ +#[test] +fn class_works() { + assert!(crate::integration::run_php("class.php")); +} diff --git a/tests/src/integration/closure.php b/tests/src/integration/closure.php new file mode 100644 index 000000000..24137c74f --- /dev/null +++ b/tests/src/integration/closure.php @@ -0,0 +1,22 @@ +getMessage(), 'take(): Argument #1 ($rs) must be of type stdClass, RustClosure given, called in ')); +} \ No newline at end of file diff --git a/tests/src/integration/closure.rs b/tests/src/integration/closure.rs new file mode 100644 index 000000000..fad7e3c25 --- /dev/null +++ b/tests/src/integration/closure.rs @@ -0,0 +1,4 @@ +#[test] +fn closure_works() { + assert!(crate::integration::run_php("closure.php")); +} diff --git a/tests/src/integration/nullable.php b/tests/src/integration/nullable.php new file mode 100644 index 000000000..f3ba32681 --- /dev/null +++ b/tests/src/integration/nullable.php @@ -0,0 +1,6 @@ + test_number_unsigned(-12)); + +// Float +assert(round(test_number_float(-1.2), 2) === round(-1.2, 2)); +assert(round(test_number_float(0.0), 2) === round(0.0, 2)); +assert(round(test_number_float(1.2), 2) === round(1.2, 2)); diff --git a/tests/src/integration/number.rs b/tests/src/integration/number.rs new file mode 100644 index 000000000..94a518c4c --- /dev/null +++ b/tests/src/integration/number.rs @@ -0,0 +1,4 @@ +#[test] +fn number_works() { + assert!(crate::integration::run_php("number.php")); +} diff --git a/tests/src/integration/object.php b/tests/src/integration/object.php new file mode 100644 index 000000000..c0b4c3fac --- /dev/null +++ b/tests/src/integration/object.php @@ -0,0 +1,16 @@ +string = 'string'; +$obj->bool = true; +$obj->number = 2022; +$obj->array = [ + 1, 2, 3 +]; + +$test = test_object($obj); + +assert($test->string === 'string'); +assert($test->bool === true); +assert($test->number === 2022); +assert($test->array === [1, 2, 3]); diff --git a/tests/src/integration/object.rs b/tests/src/integration/object.rs new file mode 100644 index 000000000..11f70dbb7 --- /dev/null +++ b/tests/src/integration/object.rs @@ -0,0 +1,4 @@ +#[test] +fn object_works() { + assert!(crate::integration::run_php("object.php")); +} diff --git a/tests/src/integration/string.php b/tests/src/integration/string.php new file mode 100644 index 000000000..63e111f56 --- /dev/null +++ b/tests/src/integration/string.php @@ -0,0 +1,6 @@ + [['string'], 'string'], + 'test_string' => [['string'], 'string'], + 'test_bool' => [['bool'], 'bool'], + 'test_number_signed' => [['int'], 'int'], + 'test_number_unsigned' => [['int'], 'int'], + 'test_number_float' => [['float'], 'float'], + 'test_array' => [['array'], 'array'], + 'test_array' => [['array'], 'array'], + 'test_array_assoc' => [['array'], 'array'], + 'test_binary' => [['string'], 'string'], + 'test_nullable' => [['?string'], '?string'], + 'test_object' => [['object'], 'object'], + 'test_closure' => [[], 'RustClosure'], + 'test_closure_once' => [['string'], 'RustClosure'], + 'test_callable' => [['callable', 'string'], 'mixed'] +]; + +function toStr(ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $v): string { + if ($v === null) { + return ''; + } + return match (true) { + $v instanceof ReflectionNamedType => $v->allowsNull() && $v->getName() !== 'mixed' ? '?'.$v->getName() : $v->getName(), + $v instanceof ReflectionUnionType => $v->getName(), + $v instanceof ReflectionIntersectionType => $v->getName(), + }; +} + +foreach (TYPES as $func => [$args, $return]) { + $f = new ReflectionFunction($func); + $tReturn = toStr($f->getReturnType()); + assert($tReturn === $return, "Wrong return type of $func, expected $return, got $tReturn"); + foreach ($f->getParameters() as $idx => $param) { + $tParam = toStr($param->getType()); + assert($tParam === $args[$idx], "Wrong arg type $idx of $func, expected {$args[$idx]}, got $tParam"); + } +} \ No newline at end of file diff --git a/tests/src/integration/types.rs b/tests/src/integration/types.rs new file mode 100644 index 000000000..a1733d9a9 --- /dev/null +++ b/tests/src/integration/types.rs @@ -0,0 +1,4 @@ +#[test] +fn types_work() { + assert!(crate::integration::run_php("types.php")); +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs new file mode 100644 index 000000000..ec5c49d62 --- /dev/null +++ b/tests/src/lib.rs @@ -0,0 +1,187 @@ +#![cfg_attr(windows, feature(abi_vectorcall))] +use ext_php_rs::{binary::Binary, prelude::*, types::ZendObject, types::Zval}; +use std::collections::HashMap; + +#[php_function] +pub fn test_str(a: &str) -> &str { + a +} + +#[php_function] +pub fn test_string(a: String) -> String { + a +} + +#[php_function] +pub fn test_bool(a: bool) -> bool { + a +} + +#[php_function] +pub fn test_number_signed(a: i32) -> i32 { + a +} + +#[php_function] +pub fn test_number_unsigned(a: u32) -> u32 { + a +} + +#[php_function] +pub fn test_number_float(a: f32) -> f32 { + a +} + +#[php_function] +pub fn test_array(a: Vec) -> Vec { + a +} + +#[php_function] +pub fn test_array_assoc(a: HashMap) -> HashMap { + a +} + +#[php_function] +pub fn test_binary(a: Binary) -> Binary { + a +} + +#[php_function] +pub fn test_nullable(a: Option) -> Option { + a +} + +#[php_function] +pub fn test_object(a: &mut ZendObject) -> &mut ZendObject { + a +} + +#[php_function] +pub fn test_closure() -> Closure { + Closure::wrap(Box::new(|a| a) as Box String>) +} + +#[php_function] +pub fn test_closure_once(a: String) -> Closure { + Closure::wrap_once(Box::new(move || a) as Box String>) +} + +#[php_function] +pub fn test_callable(call: ZendCallable, a: String) -> Zval { + call.try_call(vec![&a]).expect("Failed to call function") +} + +#[php_class] +pub struct TestClass { + string: String, + number: i32, + #[prop] + boolean: bool, +} + +#[php_impl] +impl TestClass { + #[getter] + pub fn get_string(&self) -> String { + self.string.to_string() + } + + #[setter] + pub fn set_string(&mut self, string: String) { + self.string = string; + } + + #[getter] + pub fn get_number(&self) -> i32 { + self.number + } + + #[setter] + pub fn set_number(&mut self, number: i32) { + self.number = number; + } +} + +#[php_function] +pub fn test_class(string: String, number: i32) -> TestClass { + TestClass { + string, + number, + boolean: true, + } +} + +#[php_module] +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { + module +} + +#[cfg(test)] +mod integration { + use std::env; + use std::path::PathBuf; + use std::process::Command; + use std::sync::Once; + + static BUILD: Once = Once::new(); + + fn setup() { + BUILD.call_once(|| { + assert!(Command::new("cargo") + .arg("build") + .output() + .expect("failed to build extension") + .status + .success()); + }); + } + + pub fn run_php(file: &str) -> bool { + setup(); + let mut path = PathBuf::from(env::current_dir().expect("Could not get cwd")); + path.pop(); + path.push("target"); + path.push("debug"); + path.push(if std::env::consts::DLL_EXTENSION == "dll" { + "tests" + } else { + "libtests" + }); + path.set_extension(std::env::consts::DLL_EXTENSION); + let output = Command::new("php") + .arg(format!("-dextension={}", path.to_str().unwrap())) + .arg("-dassert.active=1") + .arg("-dassert.exception=1") + .arg("-dzend.assertions=1") + .arg(format!("src/integration/{}", file)) + .output() + .expect("failed to run php file"); + if output.status.success() { + true + } else { + panic!( + " + status: {} + stdout: {} + stderr: {} + ", + output.status, + String::from_utf8(output.stdout).unwrap(), + String::from_utf8(output.stderr).unwrap() + ); + } + } + + mod array; + mod binary; + mod bool; + mod callable; + mod class; + mod closure; + mod nullable; + mod number; + mod object; + mod string; + mod types; +}