diff options
| -rw-r--r-- | crates/shirabe-php-shim/src/process.rs | 41 | ||||
| -rw-r--r-- | crates/shirabe/src/package/handle.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe/tests/installer/binary_installer_test.rs | 144 |
3 files changed, 169 insertions, 25 deletions
diff --git a/crates/shirabe-php-shim/src/process.rs b/crates/shirabe-php-shim/src/process.rs index b652bed..6999d99 100644 --- a/crates/shirabe-php-shim/src/process.rs +++ b/crates/shirabe-php-shim/src/process.rs @@ -371,9 +371,21 @@ pub fn pcntl_signal_get_handler(_signal: i64) -> PhpMixed { todo!() } +#[repr(C)] +struct Passwd { + pw_name: *const std::os::raw::c_char, + pw_passwd: *const std::os::raw::c_char, + pw_uid: u32, + pw_gid: u32, + pw_gecos: *const std::os::raw::c_char, + pw_dir: *const std::os::raw::c_char, + pw_shell: *const std::os::raw::c_char, +} + unsafe extern "C" { fn getuid() -> u32; fn geteuid() -> u32; + fn getpwuid(uid: u32) -> *const Passwd; } pub fn posix_getuid() -> i64 { @@ -386,9 +398,32 @@ pub fn posix_geteuid() -> i64 { (unsafe { geteuid() }) as i64 } -pub fn posix_getpwuid(_uid: i64) -> PhpMixed { - // TODO(phase-d): getpwuid(3) is not reachable without a libc/syscall crate. - todo!() +pub fn posix_getpwuid(uid: i64) -> PhpMixed { + // getpwuid(3) via libc (already linked); mirrors PHP posix_getpwuid returning an associative + // array of the passwd entry, or false when no entry matches the uid. + let pw = unsafe { getpwuid(uid as u32) }; + if pw.is_null() { + return PhpMixed::Bool(false); + } + let cstr = |p: *const std::os::raw::c_char| -> String { + if p.is_null() { + String::new() + } else { + unsafe { std::ffi::CStr::from_ptr(p) } + .to_string_lossy() + .into_owned() + } + }; + let pw = unsafe { &*pw }; + let mut entry = indexmap::IndexMap::new(); + entry.insert("name".to_string(), PhpMixed::String(cstr(pw.pw_name))); + entry.insert("passwd".to_string(), PhpMixed::String(cstr(pw.pw_passwd))); + entry.insert("uid".to_string(), PhpMixed::Int(pw.pw_uid as i64)); + entry.insert("gid".to_string(), PhpMixed::Int(pw.pw_gid as i64)); + entry.insert("gecos".to_string(), PhpMixed::String(cstr(pw.pw_gecos))); + entry.insert("dir".to_string(), PhpMixed::String(cstr(pw.pw_dir))); + entry.insert("shell".to_string(), PhpMixed::String(cstr(pw.pw_shell))); + PhpMixed::Array(entry) } pub fn posix_isatty(stream: PhpResource) -> bool { diff --git a/crates/shirabe/src/package/handle.rs b/crates/shirabe/src/package/handle.rs index 7ef7e98..83a5d47 100644 --- a/crates/shirabe/src/package/handle.rs +++ b/crates/shirabe/src/package/handle.rs @@ -1223,6 +1223,15 @@ macro_rules! impl_real_package_test_setters { .set_type(r#type); } + /// For testing only: mirrors PHP `Package::setBinaries`. + pub fn __set_binaries(&self, binaries: Vec<String>) { + self.0 + .borrow_mut() + .as_package_mut() + .expect("real package handle invariant") + .set_binaries(binaries); + } + /// For testing only: mirrors PHP `Package::setSourceType`. pub fn __set_source_type(&self, r#type: Option<String>) { self.0 diff --git a/crates/shirabe/tests/installer/binary_installer_test.rs b/crates/shirabe/tests/installer/binary_installer_test.rs index a3e3c24..48a6b6f 100644 --- a/crates/shirabe/tests/installer/binary_installer_test.rs +++ b/crates/shirabe/tests/installer/binary_installer_test.rs @@ -1,33 +1,133 @@ //! ref: composer/tests/Composer/Test/Installer/BinaryInstallerTest.php -/// Creates the root/vendor/bin temp directories and a mocked IO. The temp-dir -/// helpers (`getUniqueTmpDirectory`/`ensureDirectoryExistsAndClear`) and the IO -/// mock are not available here, so this remains a stub. -fn set_up() { - todo!() -} +use std::cell::RefCell; +use std::fs; +use std::rc::Rc; + +use base64::Engine; +use tempfile::TempDir; + +use shirabe::installer::BinaryInstaller; +use shirabe::io::IOInterface; +use shirabe::io::null_io::NullIO; +use shirabe::util::Filesystem; +use shirabe::util::ProcessExecutor; + +use crate::test_case::get_package; -/// Removes the root dir created by `set_up`, which is itself a stub. -fn tear_down() { - todo!() +/// Mirror of setUp(): builds temp root/vendor/bin dirs plus a mocked IO. PHP uses a +/// PHPUnit IOInterface mock with no expectations; a NullIO is the closest analogue. +struct SetUp { + root: TempDir, + vendor_dir: String, + bin_dir: String, + io: Rc<RefCell<dyn IOInterface>>, + fs: Filesystem, } -struct TearDown; +fn set_up() -> SetUp { + let fs = Filesystem::new(None); + + let root = TempDir::new().unwrap(); + let root_dir = fs::canonicalize(root.path()) + .unwrap() + .to_string_lossy() + .into_owned(); + + let vendor_dir = format!("{}/vendor", root_dir); + fs::create_dir_all(&vendor_dir).unwrap(); + + let bin_dir = format!("{}/bin", root_dir); + fs::create_dir_all(&bin_dir).unwrap(); -impl Drop for TearDown { - fn drop(&mut self) { - tear_down(); + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + + SetUp { + root, + vendor_dir, + bin_dir, + io, + fs, } } -// This installs a PHP binary (via NullIO + Package::__set_binaries + a real tempdir) and then -// executes it through ProcessExecutor, asserting its output. Setup and binary-proxy generation -// all work; the remaining blocker is real subprocess I/O: ProcessExecutor -> symfony Process -// drives its pipe reads through `stream_select`/`stream_set_blocking`, both of which are still -// `todo!()` in shirabe-php-shim (they require select(2)/fcntl(2) over the child pipe fds, which -// the shim does not yet expose). Un-ignore once that pipe-reading layer is implemented. +fn tear_down(setup: &mut SetUp) { + let root = setup.root.path().to_path_buf(); + setup.fs.remove_directory(&root).ok(); +} + +/// ref: BinaryInstallerTest::executableBinaryProvider +fn executable_binary_provider() -> Vec<(&'static str, Vec<u8>)> { + vec![ + ( + "simple php file", + b"<?php\n\necho 'success '.$_SERVER['argv'][1];".to_vec(), + ), + ( + "php file with shebang", + b"#!/usr/bin/env php\n<?php\n\necho 'success '.$_SERVER['argv'][1];".to_vec(), + ), + ( + "phar file", + base64::engine::general_purpose::STANDARD + .decode("IyEvdXNyL2Jpbi9lbnYgcGhwCjw/cGhwCgpQaGFyOjptYXBQaGFyKCd0ZXN0LnBoYXInKTsKCnJlcXVpcmUgJ3BoYXI6Ly90ZXN0LnBoYXIvcnVuLnBocCc7CgpfX0hBTFRfQ09NUElMRVIoKTsgPz4NCj4AAAABAAAAEQAAAAEACQAAAHRlc3QucGhhcgAAAAAHAAAAcnVuLnBocCoAAADb9n9hKgAAAMUDDWGkAQAAAAAAADw/cGhwIGVjaG8gInN1Y2Nlc3MgIi4kX1NFUlZFUlsiYXJndiJdWzFdO1SOC0IE3+UN0yzrHIwyspp9slhmAgAAAEdCTUI=") + .unwrap(), + ), + ( + "shebang with strict types declare", + b"#!/usr/bin/env php\n<?php declare(strict_types=1);\n\necho 'success '.$_SERVER['argv'][1];".to_vec(), + ), + ] +} + +/// ref: BinaryInstallerTest::testInstallAndExecBinaryWithFullCompat +fn run_install_and_exec_binary_with_full_compat(contents: &[u8]) { + let mut setup = set_up(); + + // PHP mocks Package::getBinaries() to return ['binary']; here a real package is + // configured via the __set_binaries test helper. + let package = get_package("foo/bar", "1.0.0"); + package.__set_binaries(vec!["binary".to_string()]); + + let pkg_dir = format!("{}/foo/bar", setup.vendor_dir); + fs::create_dir_all(&pkg_dir).unwrap(); + fs::write(format!("{}/binary", pkg_dir), contents).unwrap(); + + let mut installer = BinaryInstaller::new( + setup.io.clone(), + setup.bin_dir.clone(), + "full".to_string(), + Some(Rc::new(RefCell::new(Filesystem::new(None)))), + None, + ); + installer.install_binaries(package, &pkg_dir, true); + + let mut proc = ProcessExecutor::new(None); + let mut output = String::new(); + proc.execute(&format!("{}/binary arg", setup.bin_dir), &mut output, None) + .unwrap(); + assert_eq!("", proc.get_error_output()); + assert_eq!("success arg", output); + + tear_down(&mut setup); +} + +#[test] +fn test_install_and_exec_binary_with_full_compat_simple_php_file() { + run_install_and_exec_binary_with_full_compat(&executable_binary_provider()[0].1); +} + +#[test] +fn test_install_and_exec_binary_with_full_compat_php_file_with_shebang() { + run_install_and_exec_binary_with_full_compat(&executable_binary_provider()[1].1); +} + +#[test] +fn test_install_and_exec_binary_with_full_compat_phar_file() { + run_install_and_exec_binary_with_full_compat(&executable_binary_provider()[2].1); +} + #[test] -#[ignore = "ProcessExecutor cannot read a real child's output yet: shirabe_php_shim::{stream_select, stream_set_blocking} are todo!() (need select(2)/fcntl(2) over child pipe fds)"] -fn test_install_and_exec_binary_with_full_compat() { - todo!() +fn test_install_and_exec_binary_with_full_compat_shebang_with_strict_types_declare() { + run_install_and_exec_binary_with_full_compat(&executable_binary_provider()[3].1); } |
