diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-12 02:05:03 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-12 02:05:03 +0900 |
| commit | ed8694f89eb7702eb7e617af29f8f444f32b8d3c (patch) | |
| tree | eb140dc918445f325c92618cfabb8094b53ccd55 /crates/shirabe/tests | |
| parent | 3e2613f70ebdb441269556d1080a69a1f21a4f7d (diff) | |
| download | php-shirabe-ed8694f89eb7702eb7e617af29f8f444f32b8d3c.tar.gz php-shirabe-ed8694f89eb7702eb7e617af29f8f444f32b8d3c.tar.zst php-shirabe-ed8694f89eb7702eb7e617af29f8f444f32b8d3c.zip | |
test(installed-versions): verify the PHP class the worker loads
InstalledVersions has no Rust port, so the skeletons left behind asserted
nothing about compatibility. The tests now require the Composer checkout's
vendor/autoload.php into the PHP worker and drive the real class there, which
puts $selfDir, the registered ClassLoader and Composer\Semver\VersionParser in
the same state as the upstream PHPUnit run. The upstream setUp reflection and
the installed_relative.php require stay in PHP; the expected values are Rust.
The class Composer's own vendor directory autoloads and the one
FilesystemRepository::write dumps from include_str! are separate files, so an
added test asserts they hold the same bytes.
FilesystemRepositoryTest::testSafelyLoadInstalledVersions moves to the worker
too, against the php/stubs FilesystemRepository, whose safelyLoadInstalledVersions
runs the PCRE recursive grammar natively.
The shared worker helpers live in tests/common/php_worker.rs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests')
| -rw-r--r-- | crates/shirabe/tests/common/php_worker.rs | 55 | ||||
| -rw-r--r-- | crates/shirabe/tests/installed_versions_test.rs | 512 | ||||
| -rw-r--r-- | crates/shirabe/tests/repository/filesystem_repository_test.rs | 104 | ||||
| -rw-r--r-- | crates/shirabe/tests/repository/main.rs | 2 |
4 files changed, 626 insertions, 47 deletions
diff --git a/crates/shirabe/tests/common/php_worker.rs b/crates/shirabe/tests/common/php_worker.rs new file mode 100644 index 00000000..4be064e4 --- /dev/null +++ b/crates/shirabe/tests/common/php_worker.rs @@ -0,0 +1,55 @@ +//! Shared access to the PHP worker for integration tests. +//! +//! Included into an integration-test binary via +//! `#[path = "../common/php_worker.rs"] mod php_worker;`. +#![allow(dead_code)] + +use shirabe_php_rpc::PluginValue; +use shirabe_symfony_process::PhpExecutableFinder; + +/// Whether a PHP binary is available. Without one the worker cannot start, so tests that need it +/// return early instead of failing. +pub fn php_runtime_available() -> bool { + PhpExecutableFinder::new().find(false).is_some() +} + +/// All tests in one binary share the single PHP worker, whose loaded-class table and class statics +/// persist across tests just like PHPUnit's single-process runs. Interleaving two tests would let +/// one test's state race the other's, so the worker-touching tests run serialized. +static PHP_WORKER_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +pub fn lock_php_worker() -> std::sync::MutexGuard<'static, ()> { + PHP_WORKER_TESTS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) +} + +/// Requires the Composer PHP runtime (`composer/vendor/autoload.php`) into the worker, which is +/// what makes the real `Composer\` classes autoloadable there. +pub fn load_composer_php_runtime() { + shirabe::event_dispatcher::EventDispatcher::__ensure_composer_php_runtime().unwrap(); +} + +/// Calls a static method in the worker, panicking on either failure lane. +pub fn php_call_static(class: &str, method: &str, args: Vec<PluginValue>) -> PluginValue { + shirabe_php_rpc::call_static_method(class, method, args, None) + .unwrap_or_else(|error| panic!("{class}::{method} request failed: {error}")) + .unwrap_or_else(|throw| { + panic!( + "{class}::{method} threw {}: {}", + throw.exception_class, throw.message + ) + }) +} + +/// Runs a PHP snippet in the worker and returns its `return` value. +pub fn php_eval(code: &str) -> PluginValue { + shirabe_php_rpc::call_function("__shirabe_eval", vec![PluginValue::string(code)]) + .expect("eval request failed") + .expect("eval threw") +} + +/// Quotes a string as a PHP single-quoted literal for a generated snippet. +pub fn php_single_quote(value: &str) -> String { + format!("'{}'", value.replace('\\', "\\\\").replace('\'', "\\'")) +} diff --git a/crates/shirabe/tests/installed_versions_test.rs b/crates/shirabe/tests/installed_versions_test.rs index 49007a81..9b81c3c8 100644 --- a/crates/shirabe/tests/installed_versions_test.rs +++ b/crates/shirabe/tests/installed_versions_test.rs @@ -1,97 +1,523 @@ //! ref: composer/tests/Composer/Test/InstalledVersionsTest.php //! -//! Every test here needs the lookup APIs of `InstalledVersions`, which the Rust port does not -//! have. The PHP setUp reflects into `ClassLoader::registeredLoaders` to make it seem like no -//! class loaders are registered, then loads the installed_relative.php fixture via `require`. +//! `InstalledVersions` has no Rust port: its readers are plugins and project code, which run in +//! the PHP worker against the copy `FilesystemRepository::write` dumps. These tests drive the real +//! PHP class there, autoloaded from the Composer checkout's vendor directory just as PHPUnit +//! autoloads it upstream, so `$selfDir` and the registered `ClassLoader` match the upstream run. + +#[path = "common/php_worker.rs"] +mod php_worker; + +use indexmap::IndexMap; +use php_worker::{ + load_composer_php_runtime, lock_php_worker, php_call_static, php_eval, php_runtime_available, + php_single_quote, +}; +use shirabe_php_rpc::PluginValue; +use shirabe_php_shim::{PhpMixed, realpath}; +use tempfile::TempDir; + +const INSTALLED_VERSIONS: &str = "Composer\\InstalledVersions"; + +/// `$this->root` of the upstream test class, kept alive for the duration of one test. +struct SetUp { + root: TempDir, +} + +impl SetUp { + fn root(&self) -> &str { + self.root.path().to_str().unwrap() + } +} + +fn set_up() -> SetUp { + load_composer_php_runtime(); + + // setUpBeforeClass: disable the multiple-ClassLoader-based checks of InstalledVersions by + // making it seem like no class loaders are registered. A ClassLoader cannot cross the wire, + // so the snapshot the upstream tearDownAfterClass restores stays in the worker. + php_eval( + r"$prop = new \ReflectionProperty('Composer\Autoload\ClassLoader', 'registeredLoaders'); + (\PHP_VERSION_ID < 80100) and $prop->setAccessible(true); + if (!array_key_exists('__shirabe_previous_registered_loaders', $GLOBALS)) { + $GLOBALS['__shirabe_previous_registered_loaders'] = $prop->getValue(); + } + $prop->setValue(null, []); + return true;", + ); + + let root = TempDir::new().unwrap(); + let data = fixture_data(root.path().to_str().unwrap()); + call_static("reload", vec![data]); + SetUp { root } +} + +/// `require __DIR__.'/Repository/Fixtures/installed_relative.php'` with `$dir` bound, evaluated in +/// the worker so the upstream fixture file is used as-is. +fn fixture_data(dir: &str) -> PluginValue { + php_eval(&format!( + "$dir = {};\nreturn require {};", + php_single_quote(dir), + php_single_quote(&fixture_path("installed_relative.php")), + )) +} + +fn fixture_path(name: &str) -> String { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../composer/tests/Composer/Test/Repository/Fixtures") + .join(name) + .canonicalize() + .expect("the Composer checkout must provide the repository fixtures") + .to_str() + .unwrap() + .to_string() +} + +fn call_static(method: &str, args: Vec<PluginValue>) -> PluginValue { + php_call_static(INSTALLED_VERSIONS, method, args) +} + +fn string_of(value: &PluginValue) -> String { + match value { + PluginValue::String(bytes) => String::from_utf8(bytes.clone()).unwrap(), + other => panic!("expected a string, got {other:?}"), + } +} + +fn string_list(names: &[&str]) -> PluginValue { + PluginValue::List( + names + .iter() + .map(|name| PluginValue::string(*name)) + .collect(), + ) +} #[test] -#[ignore = "InstalledVersions::getInstalledPackages has no Rust counterpart"] fn test_get_installed_packages() { - // TODO(port): needs InstalledVersions::get_installed_packages. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + let names = [ + "__root__", + "a/provider", + "a/provider2", + "b/replacer", + "c/c", + "foo/impl", + "foo/impl2", + "foo/replaced", + "meta/package", + ]; + assert_eq!( + string_list(&names), + call_static("getInstalledPackages", vec![]) + ); } #[test] -#[ignore = "InstalledVersions::isInstalled has no Rust counterpart"] fn test_is_installed() { - // TODO(port): needs InstalledVersions::is_installed. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + for (expected, name, include_dev_requirements) in is_installed_provider() { + assert_eq!( + PluginValue::Bool(expected), + call_static( + "isInstalled", + vec![ + PluginValue::string(name), + PluginValue::Bool(include_dev_requirements) + ] + ), + "isInstalled({name}, {include_dev_requirements})", + ); + } +} + +fn is_installed_provider() -> Vec<(bool, &'static str, bool)> { + vec![ + (true, "foo/impl", true), + (true, "foo/replaced", true), + (true, "c/c", true), + (false, "c/c", false), + (true, "__root__", true), + (true, "b/replacer", true), + (false, "not/there", true), + (true, "meta/package", true), + ] } #[test] -#[ignore = "InstalledVersions::satisfies has no Rust counterpart"] fn test_satisfies() { - // TODO(port): needs InstalledVersions::satisfies. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + for (expected, name, constraint) in satisfies_provider() { + assert_eq!( + PluginValue::Bool(expected), + call_static( + "satisfies", + vec![ + new_version_parser(), + PluginValue::string(name), + PluginValue::string(constraint) + ] + ), + "satisfies({name}, {constraint})", + ); + } +} + +fn new_version_parser() -> PluginValue { + shirabe_php_rpc::new_object("Composer\\Semver\\VersionParser", vec![], None) + .expect("VersionParser request failed") + .expect("VersionParser threw") +} + +fn satisfies_provider() -> Vec<(bool, &'static str, &'static str)> { + vec![ + (true, "foo/impl", "1.5"), + (true, "foo/impl", "1.2"), + (true, "foo/impl", "^1.0"), + (true, "foo/impl", "^3 || ^2"), + (false, "foo/impl", "^3"), + (true, "foo/replaced", "3.5"), + (true, "foo/replaced", "^3.2"), + (false, "foo/replaced", "4.0"), + (true, "c/c", "3.0.0"), + (true, "c/c", "^3"), + (false, "c/c", "^3.1"), + (true, "__root__", "dev-master"), + (true, "__root__", "^1.10"), + (false, "__root__", "^2"), + (true, "b/replacer", "^2.1"), + (false, "b/replacer", "^2.3"), + (true, "a/provider2", "^1.2"), + (true, "a/provider2", "^1.4"), + (false, "a/provider2", "^1.5"), + ] } #[test] -#[ignore = "InstalledVersions::getVersionRanges has no Rust counterpart"] fn test_get_version_ranges() { - // TODO(port): needs InstalledVersions::get_version_ranges. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + for (expected, name) in get_version_ranges_provider() { + assert_eq!( + PluginValue::string(expected), + call_static("getVersionRanges", vec![PluginValue::string(name)]), + "getVersionRanges({name})", + ); + } +} + +fn get_version_ranges_provider() -> Vec<(&'static str, &'static str)> { + vec![ + ("dev-master || 1.10.x-dev", "__root__"), + ("^1.1 || 1.2 || 1.4 || 2.0", "foo/impl"), + ("2.2 || 2.0", "foo/impl2"), + ("^3.0", "foo/replaced"), + ("1.1", "a/provider"), + ("1.2 || 1.4", "a/provider2"), + ("2.2", "b/replacer"), + ("3.0", "c/c"), + ] } #[test] -#[ignore = "InstalledVersions::getVersion has no Rust counterpart"] fn test_get_version() { - // TODO(port): needs InstalledVersions::get_version. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + for (expected, name) in get_version_provider() { + assert_eq!( + optional_string(expected), + call_static("getVersion", vec![PluginValue::string(name)]), + "getVersion({name})", + ); + } +} + +fn get_version_provider() -> Vec<(Option<&'static str>, &'static str)> { + vec![ + (Some("dev-master"), "__root__"), + (None, "foo/impl"), + (None, "foo/impl2"), + (None, "foo/replaced"), + (Some("1.1.0.0"), "a/provider"), + (Some("1.2.0.0"), "a/provider2"), + (Some("2.2.0.0"), "b/replacer"), + (Some("3.0.0.0"), "c/c"), + ] } #[test] -#[ignore = "InstalledVersions::getPrettyVersion has no Rust counterpart"] fn test_get_pretty_version() { - // TODO(port): needs InstalledVersions::get_pretty_version. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + for (expected, name) in get_pretty_version_provider() { + assert_eq!( + optional_string(expected), + call_static("getPrettyVersion", vec![PluginValue::string(name)]), + "getPrettyVersion({name})", + ); + } +} + +fn get_pretty_version_provider() -> Vec<(Option<&'static str>, &'static str)> { + vec![ + (Some("dev-master"), "__root__"), + (None, "foo/impl"), + (None, "foo/impl2"), + (None, "foo/replaced"), + (Some("1.1"), "a/provider"), + (Some("1.2"), "a/provider2"), + (Some("2.2"), "b/replacer"), + (Some("3.0"), "c/c"), + ] +} + +fn optional_string(value: Option<&str>) -> PluginValue { + match value { + Some(value) => PluginValue::string(value), + None => PluginValue::Null, + } } #[test] -#[ignore = "InstalledVersions::getVersion has no Rust counterpart"] fn test_get_version_out_of_bounds() { - // TODO(port): needs InstalledVersions::get_version. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + let throw = shirabe_php_rpc::call_static_method( + INSTALLED_VERSIONS, + "getVersion", + vec![PluginValue::string("not/installed")], + None, + ) + .expect("getVersion request failed") + .expect_err("getVersion must throw for a package that is not installed"); + assert_eq!("OutOfBoundsException", throw.exception_class); } #[test] -#[ignore = "InstalledVersions::getRootPackage has no Rust counterpart"] fn test_get_root_package() { - // TODO(port): needs InstalledVersions::get_root_package. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + + let expected = PhpMixed::Array(IndexMap::from([ + ("name".to_string(), PhpMixed::String("__root__".to_string())), + ( + "pretty_version".to_string(), + PhpMixed::String("dev-master".to_string()), + ), + ( + "version".to_string(), + PhpMixed::String("dev-master".to_string()), + ), + ( + "reference".to_string(), + PhpMixed::String("sourceref-by-default".to_string()), + ), + ("type".to_string(), PhpMixed::String("library".to_string())), + ( + "install_path".to_string(), + PhpMixed::String(format!("{}/./", set_up.root())), + ), + ( + "aliases".to_string(), + PhpMixed::List(vec![PhpMixed::String("1.10.x-dev".to_string())]), + ), + ("dev".to_string(), PhpMixed::Bool(true)), + ])); + + assert_eq!( + PluginValue::from_php_mixed(&expected), + call_static("getRootPackage", vec![]) + ); } #[test] -#[ignore = "InstalledVersions::getRawData has no Rust counterpart"] fn test_get_raw_data() { - // TODO(port): needs InstalledVersions::get_raw_data. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + + assert_eq!( + fixture_data(set_up.root()), + call_static("getRawData", vec![]) + ); } #[test] -#[ignore = "InstalledVersions::getReference has no Rust counterpart"] fn test_get_reference() { - // TODO(port): needs InstalledVersions::get_reference. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + for (expected, name) in get_reference_provider() { + assert_eq!( + optional_string(expected), + call_static("getReference", vec![PluginValue::string(name)]), + "getReference({name})", + ); + } +} + +fn get_reference_provider() -> Vec<(Option<&'static str>, &'static str)> { + vec![ + (Some("sourceref-by-default"), "__root__"), + (None, "foo/impl"), + (None, "foo/impl2"), + (None, "foo/replaced"), + (Some("distref-as-no-source"), "a/provider"), + (Some("distref-as-installed-from-dist"), "a/provider2"), + (None, "b/replacer"), + (None, "c/c"), + ] } #[test] -#[ignore = "InstalledVersions::getInstalledPackagesByType has no Rust counterpart"] fn test_get_installed_packages_by_type() { - // TODO(port): needs InstalledVersions::get_installed_packages_by_type. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + let names = ["__root__", "a/provider", "a/provider2", "b/replacer", "c/c"]; + assert_eq!( + string_list(&names), + call_static( + "getInstalledPackagesByType", + vec![PluginValue::string("library")] + ) + ); } #[test] -#[ignore = "InstalledVersions::getInstallPath has no Rust counterpart"] fn test_get_install_path() { - // TODO(port): needs InstalledVersions::get_install_path. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + + let root_install_path = string_of(&call_static( + "getInstallPath", + vec![PluginValue::string("__root__")], + )); + assert_eq!(realpath(set_up.root()), realpath(root_install_path)); + assert_eq!( + PluginValue::string("/foo/bar/vendor/c/c"), + call_static("getInstallPath", vec![PluginValue::string("c/c")]) + ); + assert_eq!( + PluginValue::Null, + call_static("getInstallPath", vec![PluginValue::string("foo/impl")]) + ); } #[test] -#[ignore = "InstalledVersions::isInstalled and getRootPackage have no Rust counterpart"] fn test_with_class_loader_loaded() { - // TODO(port): needs InstalledVersions::is_installed and - // InstalledVersions::get_root_package. - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let _set_up = set_up(); + + // disable multiple-ClassLoader-based checks of InstalledVersions by making it seem like no + // class loaders are registered + php_eval( + r"$prop = new \ReflectionProperty(\Composer\Autoload\ClassLoader::class, 'registeredLoaders'); + (\PHP_VERSION_ID < 80100) and $prop->setAccessible(true); + $prop->setValue(null, array_slice($GLOBALS['__shirabe_previous_registered_loaders'], 0, 1, true)); + + $prop2 = new \ReflectionProperty(\Composer\InstalledVersions::class, 'installedIsLocalDir'); + (\PHP_VERSION_ID < 80100) and $prop2->setAccessible(true); + $prop2->setValue(null, true); + return true;", + ); + + assert_eq!( + PluginValue::Bool(false), + call_static("isInstalled", vec![PluginValue::string("foo/bar")]) + ); + let reloaded = PluginValue::Array(IndexMap::from([ + (b"root".to_vec(), call_static("getRootPackage", vec![])), + ( + b"versions".to_vec(), + PluginValue::Array(IndexMap::from([( + b"foo/bar".to_vec(), + PluginValue::from_php_mixed(&PhpMixed::Array(IndexMap::from([ + ("version".to_string(), PhpMixed::String("1.0.0".to_string())), + ("dev_requirement".to_string(), PhpMixed::Bool(false)), + ]))), + )])), + ), + ])); + call_static("reload", vec![reloaded]); + assert_eq!( + PluginValue::Bool(true), + call_static("isInstalled", vec![PluginValue::string("foo/bar")]) + ); + + php_eval( + r"$prop = new \ReflectionProperty(\Composer\Autoload\ClassLoader::class, 'registeredLoaders'); + (\PHP_VERSION_ID < 80100) and $prop->setAccessible(true); + $prop->setValue(null, []); + return true;", + ); +} + +/// Not an upstream test: the class exercised above is the one Composer's own vendor directory +/// autoloads, while `FilesystemRepository::write` dumps the `include_str!`ed source file. The two +/// have to be the same bytes for the tests above to say anything about what Shirabe ships. +#[test] +fn test_worker_loads_the_installed_versions_file_shirabe_dumps() { + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + load_composer_php_runtime(); + + let loaded = string_of(&php_eval( + r"return (new \ReflectionClass(\Composer\InstalledVersions::class))->getFileName();", + )); + assert_eq!( + include_str!("../../../composer/src/Composer/InstalledVersions.php"), + std::fs::read_to_string(&loaded).unwrap(), + "the worker autoloads {loaded}, which must match the file Shirabe dumps", + ); } diff --git a/crates/shirabe/tests/repository/filesystem_repository_test.rs b/crates/shirabe/tests/repository/filesystem_repository_test.rs index 59901072..4fc968cc 100644 --- a/crates/shirabe/tests/repository/filesystem_repository_test.rs +++ b/crates/shirabe/tests/repository/filesystem_repository_test.rs @@ -1,5 +1,6 @@ //! ref: composer/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +use crate::php_worker::{load_composer_php_runtime, php_call_static, php_runtime_available}; use crate::test_case::{get_alias_package, get_package}; use indexmap::IndexMap; use serial_test::serial; @@ -12,6 +13,7 @@ use shirabe::package::{Link, PackageInterfaceHandle, RootAliasPackageHandle, Roo use shirabe::repository::RepositoryInterface; use shirabe::repository::filesystem_repository::FilesystemRepository; use shirabe::util::filesystem::Filesystem; +use shirabe_php_rpc::PluginValue; use shirabe_php_shim::Catch as _; use shirabe_php_shim::PhpMixed; use shirabe_semver::VersionParser; @@ -325,10 +327,104 @@ fn test_repository_writes_installed_php() { assert_eq!(expected, actual); } -#[ignore = "safely_load_installed_versions's pattern uses a PCRE (?(DEFINE)...) recursive grammar the regex crate cannot compile, and InstalledVersions::getAllRawData has no Rust counterpart"] +/// The Rust `FilesystemRepository::safely_load_installed_versions` is a no-op stub, and +/// `InstalledVersions` has no Rust port at all; both live in the PHP worker, which is where the +/// upstream assertions are checked. See `crates/shirabe/tests/installed_versions_test.rs`. #[test] +// Serialized because test_repository_writes_installed_php pushes InstalledVersions::reload into +// the same worker, which would replace the state asserted here. +#[serial] fn test_safely_load_installed_versions() { - // TODO(pcre): needs a regex-crate expression equivalent to the PCRE recursive grammar, and - // InstalledVersions::get_all_raw_data. - todo!() + if !php_runtime_available() { + return; + } + load_composer_php_runtime(); + + let fixtures_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../composer/tests/Composer/Test/Repository/Fixtures") + .canonicalize() + .expect("the Composer checkout must provide the repository fixtures") + .to_str() + .unwrap() + .to_string(); + + let result = php_call_static( + "Composer\\Repository\\FilesystemRepository", + "safelyLoadInstalledVersions", + vec![PluginValue::string(format!( + "{}/installed_complex.php", + fixtures_dir + ))], + ); + assert_eq!( + PluginValue::Bool(true), + result, + "The file should be considered valid" + ); + + let raw_data = php_call_static("Composer\\InstalledVersions", "getAllRawData", vec![]); + let PluginValue::List(datasets) = raw_data else { + panic!("getAllRawData must return a list, got {raw_data:?}") + }; + let raw_data = datasets.last().cloned().unwrap(); + + let root = PhpMixed::Array(IndexMap::from([ + ( + "install_path".to_string(), + PhpMixed::String(format!("{}/./", fixtures_dir)), + ), + ( + "aliases".to_string(), + PhpMixed::List(vec![ + PhpMixed::String("1.10.x-dev".to_string()), + PhpMixed::String("2.10.x-dev".to_string()), + ]), + ), + ("name".to_string(), PhpMixed::String("__root__".to_string())), + ("true".to_string(), PhpMixed::Bool(true)), + ("false".to_string(), PhpMixed::Bool(false)), + ("null".to_string(), PhpMixed::Null), + ])); + + let a_provider = PhpMixed::Array(IndexMap::from([ + ( + "foo".to_string(), + PhpMixed::String("simple string/no backslash".to_string()), + ), + ( + "install_path".to_string(), + PhpMixed::String(format!( + "{}/vendor/{{${{passthru('bash -i')}}}}", + fixtures_dir + )), + ), + ("empty array".to_string(), PhpMixed::List(vec![])), + ])); + + let c_c = PhpMixed::Array(IndexMap::from([ + ( + "install_path".to_string(), + PhpMixed::String("/foo/bar/ven/do{}r/c/c${}".to_string()), + ), + ("aliases".to_string(), PhpMixed::List(vec![])), + ( + "reference".to_string(), + PhpMixed::String( + "{${passthru('bash -i')}} Foo\\Bar\n\ttab\u{0b}verticaltab\0".to_string(), + ), + ), + ])); + + let expected = PhpMixed::Array(IndexMap::from([ + ("root".to_string(), root), + ( + "versions".to_string(), + PhpMixed::Array(IndexMap::from([ + ("a/provider".to_string(), a_provider), + ("c/c".to_string(), c_c), + ])), + ), + ])); + + assert_eq!(PluginValue::from_php_mixed(&expected), raw_data); } diff --git a/crates/shirabe/tests/repository/main.rs b/crates/shirabe/tests/repository/main.rs index e86772e7..cf15d27b 100644 --- a/crates/shirabe/tests/repository/main.rs +++ b/crates/shirabe/tests/repository/main.rs @@ -6,6 +6,8 @@ mod config_stub; mod http_downloader_mock; #[path = "../common/io_stub.rs"] mod io_stub; +#[path = "../common/php_worker.rs"] +mod php_worker; #[path = "../common/process_executor_mock.rs"] mod process_executor_mock; #[path = "../common/test_case.rs"] |
