diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-06 01:50:34 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-06 01:50:34 +0900 |
| commit | 4de018826e9dce90fd5cb78d468641478327ec99 (patch) | |
| tree | 0843f7e3e8c50886c8470ace17fcb6fed937be4f /crates/shirabe/tests | |
| parent | da602f1cb1d555c7826fa3d026df66b82061cda4 (diff) | |
| download | php-shirabe-4de018826e9dce90fd5cb78d468641478327ec99.tar.gz php-shirabe-4de018826e9dce90fd5cb78d468641478327ec99.tar.zst php-shirabe-4de018826e9dce90fd5cb78d468641478327ec99.zip | |
feat(plugin): run plugin-provided installers through the RPC worker
A plugin can now hand an InstallerInterface implementation to
InstallationManager::addInstaller across the wire, and a legacy
composer-installer package is loaded as one; both are backed by a
PhpInstallerProxy forwarding the whole installer contract to the entity in
the PHP worker. An installer returning a real promise is an explicit error
until promises can cross the boundary.
InstallationManager takes installers as shared handles instead of boxes, so
the object identity removeInstaller and PluginManager's registeredPlugins
compare against survives registration, and holds them in a RefCell:
Installer::run keeps a shared borrow of the manager for the whole run, and a
plugin activated inside it registers its installer from there. The type
cache keys on the installer itself, like upstream, so re-entrant
registration cannot leave a stale index behind. InstallerInterface::supports
is fallible for the same reason getCapabilities and getCommands are: it
answers over RPC.
Cloning a proxy stub clones the Rust-side entity and rebinds the copy to the
fresh handle. Previously only the classes declaring __clone got a throwing
body, and the rest let two stubs share (and twice release) one handle.
Package entities answer with AnyPackage::dup, which already carries
BasePackage::__clone and the RootAliasPackage override; the others are an
explicit error.
The package proxy covers the whole PackageInterface surface; only the link
maps and the release date still lack a wire image for their value objects.
PluginManager gains a test-only seam for the reported Plugin API version,
and the three PluginInstallerTest cases that need it are ported.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests')
5 files changed, 135 insertions, 49 deletions
diff --git a/crates/shirabe/tests/autoload/autoload_generator_test.rs b/crates/shirabe/tests/autoload/autoload_generator_test.rs index 85e98d81..81f508cc 100644 --- a/crates/shirabe/tests/autoload/autoload_generator_test.rs +++ b/crates/shirabe/tests/autoload/autoload_generator_test.rs @@ -33,8 +33,8 @@ struct InstallPathStubInstaller { #[async_trait::async_trait(?Send)] impl InstallerInterface for InstallPathStubInstaller { - fn supports(&self, _package_type: &str) -> bool { - true + fn supports(&self, _package_type: &str) -> anyhow::Result<bool> { + Ok(true) } fn is_installed( @@ -155,8 +155,8 @@ fn make_installation_manager( ))); let loop_ = std::rc::Rc::new(std::cell::RefCell::new(Loop::new(http_downloader, None))); - let mut im = InstallationManager::new(loop_, io, None); - im.add_installer(Box::new(InstallPathStubInstaller { + let im = InstallationManager::new(loop_, io, None); + im.add_installer(std::rc::Rc::new(InstallPathStubInstaller { vendor_dir: vendor_dir.to_string(), })); im @@ -491,7 +491,7 @@ fn test_vendor_dir_same_as_working_dir() { let mut s = set_up(); s.vendor_dir = s.working_dir.clone(); // Re-register the install-path stub so getInstallPath uses the new vendor dir. - s.im.add_installer(Box::new(InstallPathStubInstaller { + s.im.add_installer(std::rc::Rc::new(InstallPathStubInstaller { vendor_dir: s.vendor_dir.clone(), })); @@ -551,7 +551,7 @@ fn test_root_package_autoloading_alternative_vendor_dir() { ])); s.vendor_dir = format!("{}/subdir", s.vendor_dir); - s.im.add_installer(Box::new(InstallPathStubInstaller { + s.im.add_installer(std::rc::Rc::new(InstallPathStubInstaller { vendor_dir: s.vendor_dir.clone(), })); diff --git a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs index 5a6c93e5..674c4a80 100644 --- a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs +++ b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs @@ -421,8 +421,8 @@ mockall::mock! { #[derive(Debug)] pub InstallationManager {} impl InstallationManagerInterface for InstallationManager { - fn add_installer(&mut self, installer: Box<dyn InstallerInterface>); - fn remove_installer(&mut self, installer: &dyn InstallerInterface); + fn add_installer(&self, installer: std::rc::Rc<dyn InstallerInterface>); + fn remove_installer(&self, installer: &dyn InstallerInterface); fn disable_plugins(&mut self); fn is_package_installed( &mut self, diff --git a/crates/shirabe/tests/installer/installation_manager_test.rs b/crates/shirabe/tests/installer/installation_manager_test.rs index 8f2d7d4f..c511703e 100644 --- a/crates/shirabe/tests/installer/installation_manager_test.rs +++ b/crates/shirabe/tests/installer/installation_manager_test.rs @@ -67,8 +67,8 @@ mockall::mock! { #[async_trait::async_trait(?Send)] impl InstallerInterface for MockInstaller { - fn supports(&self, package_type: &str) -> bool { - MockInstaller::supports(self, package_type) + fn supports(&self, package_type: &str) -> anyhow::Result<bool> { + Ok(MockInstaller::supports(self, package_type)) } fn is_installed( @@ -163,12 +163,12 @@ impl BinaryInstaller { #[async_trait::async_trait(?Send)] impl InstallerInterface for BinaryInstaller { - fn supports(&self, package_type: &str) -> bool { + fn supports(&self, package_type: &str) -> anyhow::Result<bool> { self.calls .borrow_mut() .supports_args .push(package_type.to_string()); - package_type == "library" + Ok(package_type == "library") } fn is_installed( @@ -272,10 +272,10 @@ fn test_add_get_installer() { .times(2) .returning(|arg| arg == "vendor"); - let mut manager = + let manager = shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None); - manager.add_installer(Box::new(installer)); + manager.add_installer(std::rc::Rc::new(installer)); assert!(manager.get_installer("vendor").is_ok()); assert!(manager.get_installer("unregistered").is_err()); @@ -290,7 +290,7 @@ fn test_add_remove_installer() { .times(2) .returning(|arg| arg == "vendor"); // The manager stores installers as Rc, so the PHP object-identity semantics (assertSame, - // removeInstaller) map to Rc::ptr_eq on a handle registered via __add_installer. + // removeInstaller) map to Rc::ptr_eq on the handle the caller keeps. let installer: std::rc::Rc<dyn InstallerInterface> = std::rc::Rc::new(installer); let mut installer2 = MockInstaller::new(); @@ -300,15 +300,15 @@ fn test_add_remove_installer() { .returning(|arg| arg == "vendor"); let installer2: std::rc::Rc<dyn InstallerInterface> = std::rc::Rc::new(installer2); - let mut manager = + let manager = shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None); - manager.__add_installer(installer.clone()); + manager.add_installer(installer.clone()); assert!(std::rc::Rc::ptr_eq( &installer, &manager.get_installer("vendor").unwrap() )); - manager.__add_installer(installer2.clone()); + manager.add_installer(installer2.clone()); assert!(std::rc::Rc::ptr_eq( &installer2, &manager.get_installer("vendor").unwrap() @@ -352,9 +352,9 @@ fn test_install() { .withf_st(move |package| same_handle(package, &expected)) .returning(|_| Ok(None)); - let mut manager = + let manager = shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None); - manager.add_installer(Box::new(installer)); + manager.add_installer(std::rc::Rc::new(installer)); let operation = InstallOperation::new(package); @@ -385,9 +385,9 @@ fn test_update_with_equal_types() { }) .returning(|_, _| Ok(None)); - let mut manager = + let manager = shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None); - manager.add_installer(Box::new(installer)); + manager.add_installer(std::rc::Rc::new(installer)); let operation = UpdateOperation::new(initial, target); @@ -427,10 +427,10 @@ fn test_update_with_not_equal_types() { .withf_st(move |package| same_handle(package, &expected_target)) .returning(|_| Ok(None)); - let mut manager = + let manager = shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None); - manager.add_installer(Box::new(lib_installer)); - manager.add_installer(Box::new(bundle_installer)); + manager.add_installer(std::rc::Rc::new(lib_installer)); + manager.add_installer(std::rc::Rc::new(bundle_installer)); let operation = UpdateOperation::new(initial, target); @@ -457,9 +457,9 @@ fn test_uninstall() { .withf_st(move |package| same_handle(package, &expected)) .returning(|_| Ok(None)); - let mut manager = + let manager = shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None); - manager.add_installer(Box::new(installer)); + manager.add_installer(std::rc::Rc::new(installer)); let operation = UninstallOperation::new(package); @@ -472,9 +472,9 @@ fn test_uninstall() { fn test_install_binary() { let set_up = set_up(); let (installer, calls) = BinaryInstaller::new(); - let mut manager = + let manager = shirabe::installer::InstallationManager::new(set_up.loop_.clone(), set_up.io.clone(), None); - manager.add_installer(Box::new(installer)); + manager.add_installer(std::rc::Rc::new(installer)); let package = get_package("test/pkg", "1.0.0"); manager.ensure_binaries_presence(package.clone()); diff --git a/crates/shirabe/tests/plugin/plugin_installer_test.rs b/crates/shirabe/tests/plugin/plugin_installer_test.rs index 35088c8d..06cd2ce9 100644 --- a/crates/shirabe/tests/plugin/plugin_installer_test.rs +++ b/crates/shirabe/tests/plugin/plugin_installer_test.rs @@ -16,7 +16,9 @@ use shirabe::io::IOInterface; use shirabe::io::buffer_io::BufferIO; use shirabe::json::JsonFile; use shirabe::package::loader::{ArrayLoader, JsonLoader, JsonLoaderInput}; -use shirabe::package::{Locker, LockerInterface, PackageInterfaceHandle, RootPackageHandle}; +use shirabe::package::{ + CompletePackageHandle, Locker, LockerInterface, PackageInterfaceHandle, RootPackageHandle, +}; use shirabe::plugin::plugin_interface::PluginInterface; use shirabe::plugin::{Capable, PluginManager, composer_handle_value, io_handle_value}; use shirabe::repository::{ @@ -30,6 +32,7 @@ use shirabe::util::process_executor::ProcessExecutor; use shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL; use shirabe_external_packages::symfony::process::PhpExecutableFinder; use shirabe_php_shim::PhpMixed; +use shirabe_semver::VersionParser; use tempfile::TempDir; /// The register/activate flow runs the plugin in the real PHP worker; without a PHP binary the @@ -166,9 +169,9 @@ impl RepositoryManagerInterface for MockRepositoryManager { struct MockInstallationManager; impl InstallationManagerInterface for MockInstallationManager { - fn add_installer(&mut self, _installer: Box<dyn InstallerInterface>) {} + fn add_installer(&self, _installer: std::rc::Rc<dyn InstallerInterface>) {} - fn remove_installer(&mut self, _installer: &dyn InstallerInterface) {} + fn remove_installer(&self, _installer: &dyn InstallerInterface) {} fn disable_plugins(&mut self) {} @@ -606,31 +609,114 @@ fn test_register_plugin_only_one_time() { assert_eq!("activate v1\n", set_up.io.borrow().get_output()); } -// PluginManager::get_plugin_api_version returns a hardcoded constant -// (plugin_interface::PLUGIN_API_VERSION) with no seam to override it per-test the way PHP's -// `getMockBuilder(PluginManager::class)->onlyMethods(['getPluginApiVersion'])` does. -#[ignore = "Requires mocking getPluginApiVersion; PluginManager has no such seam (TODO(plugin))"] +/// PHP `setPluginApiVersionWithPlugins`: swaps in a plugin manager reporting +/// `new_plugin_api_version` (PHP mocks `getPluginApiVersion`; the Rust seam is +/// `__set_plugin_api_version`) and a local repository holding the internal composer-plugin-api +/// package plus `plugins` (PHP mocks `getPackages`), then loads the installed plugins. +fn set_plugin_api_version_with_plugins( + set_up: &SetUp, + new_plugin_api_version: &str, + plugins: Vec<PackageInterfaceHandle>, +) -> std::rc::Rc<std::cell::RefCell<PluginManager>> { + let pm = std::rc::Rc::new(std::cell::RefCell::new(PluginManager::new( + set_up.io_dyn.clone(), + set_up.composer.downgrade(), + None, + DisablePlugins::None, + ))); + pm.borrow_mut() + .__set_plugin_api_version(new_plugin_api_version); + set_up.composer.borrow_mut().set_plugin_manager(pm.clone()); + + let plug_api_internal_package: PackageInterfaceHandle = CompletePackageHandle::new( + "composer-plugin-api".to_string(), + VersionParser + .normalize(new_plugin_api_version, None) + .unwrap(), + new_plugin_api_version.to_string(), + ) + .into(); + let repository = + InstalledRepositoryInterfaceHandle::new(InstalledArrayRepository::new().unwrap()); + repository + .borrow_mut() + .add_package(plug_api_internal_package) + .unwrap(); + for plugin in plugins { + repository.borrow_mut().add_package(plugin).unwrap(); + } + set_up + .composer + .borrow() + .get_repository_manager() + .borrow_mut() + .set_local_repository(repository.as_repository_handle()); + + pm.borrow_mut().load_installed_plugins().unwrap(); + pm +} + #[test] fn test_star_plugin_version_works_with_any_api_version() { - // TODO(phase-d): requires mocking getPluginApiVersion; PluginManager has no such seam - // (TODO(plugin)). - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + let star_version_plugin = || vec![PackageInterfaceHandle::dup(&set_up.packages[4])]; + + let pm = set_plugin_api_version_with_plugins(&set_up, "1.0.0", star_version_plugin()); + assert_eq!(1, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "1.9.9", star_version_plugin()); + assert_eq!(1, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "2.0.0-dev", star_version_plugin()); + assert_eq!(1, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "100.0.0-stable", star_version_plugin()); + assert_eq!(1, pm.borrow().get_plugins().len()); } -#[ignore = "Requires mocking getPluginApiVersion; PluginManager has no such seam (TODO(plugin))"] #[test] fn test_plugin_constraint_works_only_with_certain_api_version() { - // TODO(phase-d): requires mocking getPluginApiVersion; PluginManager has no such seam - // (TODO(plugin)). - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + let plugin_with_api_constraint = || vec![PackageInterfaceHandle::dup(&set_up.packages[5])]; + + let pm = set_plugin_api_version_with_plugins(&set_up, "1.0.0", plugin_with_api_constraint()); + assert_eq!(0, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "1.1.9", plugin_with_api_constraint()); + assert_eq!(0, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "1.2.0", plugin_with_api_constraint()); + assert_eq!(1, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "1.9.9", plugin_with_api_constraint()); + assert_eq!(1, pm.borrow().get_plugins().len()); } -#[ignore = "Requires mocking getPluginApiVersion; PluginManager has no such seam (TODO(plugin))"] #[test] fn test_plugin_range_constraints_work_only_with_certain_api_version() { - // TODO(phase-d): requires mocking getPluginApiVersion; PluginManager has no such seam - // (TODO(plugin)). - todo!() + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + let plugin_with_api_constraint = || vec![PackageInterfaceHandle::dup(&set_up.packages[6])]; + + let pm = set_plugin_api_version_with_plugins(&set_up, "1.0.0", plugin_with_api_constraint()); + assert_eq!(0, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "3.0.0", plugin_with_api_constraint()); + assert_eq!(1, pm.borrow().get_plugins().len()); + + let pm = set_plugin_api_version_with_plugins(&set_up, "5.5.0", plugin_with_api_constraint()); + assert_eq!(0, pm.borrow().get_plugins().len()); } #[test] diff --git a/crates/shirabe/tests/repository/filesystem_repository_test.rs b/crates/shirabe/tests/repository/filesystem_repository_test.rs index bcdb766e..3279f29e 100644 --- a/crates/shirabe/tests/repository/filesystem_repository_test.rs +++ b/crates/shirabe/tests/repository/filesystem_repository_test.rs @@ -93,8 +93,8 @@ mockall::mock! { #[derive(Debug)] pub InstallationManager {} impl InstallationManagerInterface for InstallationManager { - fn add_installer(&mut self, installer: Box<dyn InstallerInterface>); - fn remove_installer(&mut self, installer: &dyn InstallerInterface); + fn add_installer(&self, installer: std::rc::Rc<dyn InstallerInterface>); + fn remove_installer(&self, installer: &dyn InstallerInterface); fn disable_plugins(&mut self); fn is_package_installed( &mut self, |
