aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe/tests/common/php_worker.rs55
-rw-r--r--crates/shirabe/tests/installed_versions_test.rs512
-rw-r--r--crates/shirabe/tests/repository/filesystem_repository_test.rs104
-rw-r--r--crates/shirabe/tests/repository/main.rs2
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"]