diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-07-12 01:28:57 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-07-16 01:02:47 +0900 |
| commit | 754c41c474194a00ed34190ba228ac36dac609a5 (patch) | |
| tree | 325a04486e0800c51c0c8aa4d1658aac5d597036 /crates/shirabe/tests/plugin/plugin_installer_test.rs | |
| parent | 6f55caac4e6e55661305311174e31ec2b0e375b6 (diff) | |
| download | php-shirabe-754c41c474194a00ed34190ba228ac36dac609a5.tar.gz php-shirabe-754c41c474194a00ed34190ba228ac36dac609a5.tar.zst php-shirabe-754c41c474194a00ed34190ba228ac36dac609a5.zip | |
test(plugin): port capability-query tests, record remaining TODO(phase-d) reasons
Implement test_incapable_plugin_is_correctly_detected and
test_querying_non_provided_capability_returns_null_safely against a
real PluginManager, using hand-written PluginInterface/Capable stubs
in place of PHPUnit's ad-hoc mocks. Wire the shared config_stub test
helper into the plugin test binary, and derive Debug on the SetUp
struct per this project's convention.
The remaining plugin_installer_test.rs cases stay #[ignore]d:
install/update/uninstall wiring in PluginInstaller and class
instantiation in PluginManager::register_package are still
TODO(plugin) stubs, so no plugin ever actually gets registered. Each
now records its blocking TODO(plugin) site via // TODO(phase-d).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/plugin/plugin_installer_test.rs')
| -rw-r--r-- | crates/shirabe/tests/plugin/plugin_installer_test.rs | 271 |
1 files changed, 253 insertions, 18 deletions
diff --git a/crates/shirabe/tests/plugin/plugin_installer_test.rs b/crates/shirabe/tests/plugin/plugin_installer_test.rs index 6f67c680..ca2dd879 100644 --- a/crates/shirabe/tests/plugin/plugin_installer_test.rs +++ b/crates/shirabe/tests/plugin/plugin_installer_test.rs @@ -1,18 +1,108 @@ //! ref: composer/tests/Composer/Test/Plugin/PluginInstallerTest.php -/// Builds the Composer instance from mocked DownloadManager/RepositoryManager/ -/// InstallationManager/EventDispatcher and an InstalledRepository mock, plus the -/// plugin fixtures temp directory. The mocks and the plugin machinery are not -/// available here, so this remains a stub. -fn set_up() { - todo!() +use crate::config_stub::ConfigStubBuilder; +use indexmap::IndexMap; +use shirabe::composer::{Composer, ComposerHandle, PartialOrFullComposer}; +use shirabe::config::Config; +use shirabe::factory::DisablePlugins; +use shirabe::installer::InstallationManager; +use shirabe::io::IOInterface; +use shirabe::io::buffer_io::BufferIO; +use shirabe::json::JsonFile; +use shirabe::package::{Locker, LockerInterface}; +use shirabe::plugin::plugin_interface::PluginInterface; +use shirabe::plugin::{Capable, PluginManager}; +use shirabe::util::Platform; +use shirabe::util::http_downloader::HttpDownloader; +use shirabe::util::r#loop::Loop; +use shirabe::util::process_executor::ProcessExecutor; +use shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL; +use shirabe_php_shim::PhpMixed; + +/// Equivalent to PHP setUp()'s `new InstallationManager(...)`, used only to satisfy +/// `Locker::new`'s constructor argument; it is never exercised by the currently-portable +/// tests below. +fn installation_manager( + io: &std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, +) -> std::rc::Rc<std::cell::RefCell<InstallationManager>> { + let config = std::rc::Rc::new(std::cell::RefCell::new(Config::new(false, None))); + let http_downloader = std::rc::Rc::new(std::cell::RefCell::new(HttpDownloader::new( + io.clone(), + config, + IndexMap::new(), + true, + ))); + let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new(http_downloader, None))); + std::rc::Rc::new(std::cell::RefCell::new(InstallationManager::new( + r#loop, + io.clone(), + None, + ))) } -/// Removes the fixtures directory created by `set_up`, which is itself a stub. -fn tear_down() { - todo!() +#[derive(Debug)] +struct SetUp { + #[allow(dead_code)] + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + pm: std::rc::Rc<std::cell::RefCell<PluginManager>>, + // Keeps the Composer alive; PluginManager only holds a weak back-reference to it. + #[allow(dead_code)] + composer: ComposerHandle, +} + +/// Builds a `Composer` the way PHP's setUp() does (config with `allow-plugins => true` +/// and a `Locker` backed by /dev/null) and constructs a `PluginManager` from it. +/// +/// PHP's setUp() additionally mocks DownloadManager/RepositoryManager/InstallationManager/ +/// EventDispatcher, loads 8 plugin-vN fixture packages, and creates a temp fixtures +/// directory. None of that is reproduced here: every test that would exercise it depends on +/// `PluginManager::register_package` actually instantiating a plugin class, which is an +/// unported runtime concern (`TODO(plugin)` in `plugin/plugin_manager.rs`) — those tests stay +/// `#[ignore]` below. Only the tests that call `PluginManager::get_plugin_capability` directly +/// with a hand-built plugin object are portable, and they need nothing more than `pm` itself. +fn set_up() -> SetUp { + let io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = std::rc::Rc::new( + std::cell::RefCell::new(BufferIO::new(String::new(), VERBOSITY_NORMAL, None).unwrap()), + ); + + let config = ConfigStubBuilder::new() + .with("allow-plugins", PhpMixed::Bool(true)) + .build_shared(); + + let json_file = JsonFile::new(Platform::get_dev_null(), None, Some(io.clone())).unwrap(); + let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some( + io.clone(), + )))); + let locker: std::rc::Rc<std::cell::RefCell<dyn LockerInterface>> = + std::rc::Rc::new(std::cell::RefCell::new(Locker::new( + io.clone(), + json_file, + installation_manager(&io), + "{}", + process, + ))); + + let mut composer = Composer::new(); + composer.set_config(config); + composer.set_locker(locker); + + let composer = ComposerHandle::from_rc_unchecked(std::rc::Rc::new(std::cell::RefCell::new( + PartialOrFullComposer::Full(composer), + ))); + + let pm = PluginManager::new(io.clone(), composer.downgrade(), None, DisablePlugins::None); + + SetUp { + io, + pm: std::rc::Rc::new(std::cell::RefCell::new(pm)), + composer, + } } +/// PHP's tearDown() removes the temp fixtures directory created by setUp(); `set_up` above +/// creates no such directory, so there is nothing to clean up. +fn tear_down() {} + struct TearDown; impl Drop for TearDown { @@ -21,100 +111,245 @@ impl Drop for TearDown { } } -// The plugin system requires the PHP runtime to load and instantiate plugin classes; the -// PluginManager/PluginInstaller is intentionally not implemented yet (TODO(plugin)). +// The plugin system requires the PHP runtime to load and instantiate plugin classes. +// `PluginInstaller::install`/`update` never call `PluginManager::register_package` (the calls +// are commented out in installer/plugin_installer.rs, TODO(plugin)), and `register_package` +// itself never instantiates a plugin class or calls `add_plugin` (TODO(plugin) in +// plugin/plugin_manager.rs). So `PluginManager::get_plugins()` can never contain the plugin +// instances these tests assert on. #[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes (plugin-v1) are not implemented (TODO(plugin))"] #[test] fn test_install_new_plugin() { + // TODO(phase-d): PluginInstaller and runtime loading of fixture plugin PHP classes (plugin-v1) + // are not implemented (TODO(plugin)). todo!() } #[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes are not implemented (TODO(plugin))"] #[test] fn test_install_plugin_with_root_package_having_files_autoload() { + // TODO(phase-d): PluginInstaller and runtime loading of fixture plugin PHP classes are not + // implemented (TODO(plugin)). todo!() } #[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes (plugin-v4) are not implemented (TODO(plugin))"] #[test] fn test_install_multiple_plugins() { + // TODO(phase-d): PluginInstaller and runtime loading of fixture plugin PHP classes (plugin-v4) + // are not implemented (TODO(plugin)). todo!() } #[ignore = "PluginInstaller.update and runtime plugin class loading/deactivation are not implemented (TODO(plugin))"] #[test] fn test_upgrade_with_new_class_name() { + // TODO(phase-d): PluginInstaller.update and runtime plugin class loading/deactivation are not + // implemented (TODO(plugin)). todo!() } #[ignore = "PluginInstaller.uninstall and runtime plugin class loading/uninstall hook are not implemented (TODO(plugin))"] #[test] fn test_uninstall() { + // TODO(phase-d): PluginInstaller.uninstall and runtime plugin class loading/uninstall hook are + // not implemented (TODO(plugin)). todo!() } #[ignore = "PluginInstaller.update and runtime plugin class loading/deactivation are not implemented (TODO(plugin))"] #[test] fn test_upgrade_with_same_class_name() { + // TODO(phase-d): PluginInstaller.update and runtime plugin class loading/deactivation are not + // implemented (TODO(plugin)). todo!() } #[ignore = "PluginInstaller and runtime loading of fixture plugin PHP classes are not implemented (TODO(plugin))"] #[test] fn test_register_plugin_only_one_time() { + // TODO(phase-d): PluginInstaller and runtime loading of fixture plugin PHP classes are not + // implemented (TODO(plugin)). todo!() } +// PluginManager::register_package's version-constraint check against composer-plugin-api is +// fully ported and does gate loading correctly, but `getPluginApiVersion()` 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, and +// even a matching version can never produce a registered plugin (register_package's +// instantiate-and-add_plugin step is an unported TODO(plugin) stub). Both blockers must be +// resolved together; a partial port (e.g. only the count==0 branches) would drop assertions the +// test relies on, which is disallowed. #[ignore = "Requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP classes; not implemented (TODO(plugin))"] #[test] fn test_star_plugin_version_works_with_any_api_version() { + // TODO(phase-d): requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP + // classes; not implemented (TODO(plugin)). todo!() } #[ignore = "Requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP classes; not implemented (TODO(plugin))"] #[test] fn test_plugin_constraint_works_only_with_certain_api_version() { + // TODO(phase-d): requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP + // classes; not implemented (TODO(plugin)). todo!() } #[ignore = "Requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP classes; not implemented (TODO(plugin))"] #[test] fn test_plugin_range_constraints_work_only_with_certain_api_version() { + // TODO(phase-d): requires mocking getPluginApiVersion and runtime loading of fixture plugin PHP + // classes; not implemented (TODO(plugin)). todo!() } -#[ignore = "get_plugin_capabilities and runtime loading of fixture plugin/capability PHP classes (plugin-v8) are not implemented (TODO(plugin))"] +#[ignore = "get_plugin_capabilities requires a registered plugin, which register_package never produces (TODO(plugin) in plugin/plugin_manager.rs); Capability::CommandProvider/BaseCommand runtime instantiation is also unported"] #[test] fn test_command_provider_capability() { + // TODO(phase-d): get_plugin_capabilities requires a registered plugin, which register_package + // never produces (TODO(plugin) in plugin/plugin_manager.rs); Capability::CommandProvider/ + // BaseCommand runtime instantiation is also unported. todo!() } -#[ignore = "Requires a PHP-runtime mock of PluginInterface and get_plugin_capability; not implemented (TODO(plugin))"] +// A hand-written stub is used in place of PHPUnit's +// `getMockBuilder('Composer\Plugin\PluginInterface')->getMock()`: PluginInterface's +// `as_capable()` default already returns None, exactly matching a bare (non-Capable) plugin +// mock. +#[derive(Debug)] +struct NoopPlugin; + +impl PluginInterface for NoopPlugin { + fn activate( + &mut self, + _composer: &ComposerHandle, + _io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + ) { + } + + fn deactivate( + &mut self, + _composer: &ComposerHandle, + _io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + ) { + } + + fn uninstall( + &mut self, + _composer: &ComposerHandle, + _io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + ) { + } +} + #[test] fn test_incapable_plugin_is_correctly_detected() { - todo!() + let set_up = set_up(); + let _tear_down = TearDown; + + let plugin = NoopPlugin; + let result = set_up + .pm + .borrow() + .get_plugin_capability(&plugin, "Fake\\Ability", IndexMap::new()) + .unwrap(); + assert!(result.is_none()); } #[ignore = "Requires runtime instantiation of Mock\\Capability via get_plugin_capability; not implemented (TODO(plugin))"] #[test] fn test_capability_implements_composer_plugin_api_class_and_is_constructed_with_args() { + // TODO(phase-d): requires runtime instantiation of Mock\Capability via + // get_plugin_capability; not implemented (TODO(plugin)). todo!() } -#[ignore = "Requires runtime get_plugin_capability with a mocked Capable plugin and PHP-class-name capability lookup; not implemented (TODO(plugin))"] +// PluginManager::get_capability_implementation_class_name (via Capable::get_capabilities) +// resolves capability class names through an IndexMap<String, String>, so most of PHP's +// invalidImplementationClassNames data provider (null, 0, 1000, [1], [], stdClass) cannot be +// represented at all in the ported type — only the string entries ("", " ") could be +// constructed. Per the phase-d rule against porting a subset of a data provider, this whole +// test must stay unported rather than dropping the non-string cases. +#[ignore = "Capable::get_capabilities is typed IndexMap<String, String>; most of the invalidImplementationClassNames data provider (null, 0, 1000, [1], [], stdClass) is not representable, and porting only the string cases would drop data-provider entries (TODO(phase-d))"] #[test] fn test_querying_with_invalid_capability_class_name_throws() { + // TODO(phase-d): Capable::get_capabilities is typed IndexMap<String, String>; most of the + // invalidImplementationClassNames data provider (null, 0, 1000, [1], [], stdClass) is not + // representable, and porting only the string cases would drop data-provider entries. todo!() } -#[ignore = "Requires runtime get_plugin_capability with a mocked Capable plugin; not implemented (TODO(plugin))"] +// A hand-written stub plays the role of PHPUnit's +// `getMockBuilder('Composer\Test\Plugin\Mock\CapablePluginInterface')->getMock()`, i.e. a +// PluginInterface that is also Capable. `->expects($this->once())->method('getCapabilities')` +// is reproduced with a call counter asserted after the call. +#[derive(Debug)] +struct CapablePlugin { + get_capabilities_calls: std::cell::RefCell<i64>, +} + +impl PluginInterface for CapablePlugin { + fn activate( + &mut self, + _composer: &ComposerHandle, + _io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + ) { + } + + fn deactivate( + &mut self, + _composer: &ComposerHandle, + _io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + ) { + } + + fn uninstall( + &mut self, + _composer: &ComposerHandle, + _io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + ) { + } + + fn as_capable(&self) -> Option<&dyn Capable> { + Some(self) + } +} + +impl Capable for CapablePlugin { + fn get_capabilities(&self) -> IndexMap<String, String> { + *self.get_capabilities_calls.borrow_mut() += 1; + IndexMap::new() + } +} + #[test] fn test_querying_non_provided_capability_returns_null_safely() { - todo!() + let set_up = set_up(); + let _tear_down = TearDown; + + let plugin = CapablePlugin { + get_capabilities_calls: std::cell::RefCell::new(0), + }; + + let result = set_up + .pm + .borrow() + .get_plugin_capability( + &plugin, + "Composer\\Plugin\\Capability\\MadeUpCapability", + IndexMap::new(), + ) + .unwrap(); + assert!(result.is_none()); + assert_eq!(1, *plugin.get_capabilities_calls.borrow()); } -#[ignore = "Requires runtime get_plugin_capability with PHP-class-name capability lookup; not implemented (TODO(plugin))"] +#[ignore = "Requires runtime get_plugin_capability with PHP-class-name capability lookup (class_exists/instanceof checks are unported TODO(plugin)); not implemented"] #[test] fn test_querying_with_non_existing_or_wrong_capability_class_types_throws() { + // TODO(phase-d): requires runtime get_plugin_capability with PHP-class-name capability lookup + // (class_exists/instanceof checks are unported TODO(plugin)); not implemented. todo!() } |
