diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-07 07:01:50 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-07 07:01:50 +0900 |
| commit | 759b2980e70dfb8960238f75d68bb6dddce25414 (patch) | |
| tree | e37a0af6423f8ae8dd26da309b278d49d45b3451 /crates/shirabe/tests/event_dispatcher | |
| parent | 9a393adc0ace86cac788723b524e83c63dfc91c1 (diff) | |
| download | php-shirabe-759b2980e70dfb8960238f75d68bb6dddce25414.tar.gz php-shirabe-759b2980e70dfb8960238f75d68bb6dddce25414.tar.zst php-shirabe-759b2980e70dfb8960238f75d68bb6dddce25414.zip | |
test: port the tests left as todo!() stubs
Replace the todo!() bodies with real ports. Four autoload-generator
tests now run for real; the rest stay #[ignore]d, but each ignore reason
now names the concrete missing symbol instead of a vague subsystem.
Production additions the ports need: the deprecated
AuthHelper::addAuthenticationHeader wrapper,
EventDispatcher::__set_dispatch_script_override as the seam for PHPUnit
onlyMethods(['dispatchScript']), and a define() stub in the shim.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/event_dispatcher')
| -rw-r--r-- | crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs | 261 | ||||
| -rw-r--r-- | crates/shirabe/tests/event_dispatcher/main.rs | 2 |
2 files changed, 242 insertions, 21 deletions
diff --git a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs index 674c4a80..d97c14f0 100644 --- a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs +++ b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs @@ -1,5 +1,6 @@ //! ref: composer/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +use crate::io_mock::{Expectation, get_io_mock}; use crate::process_executor_mock::{ProcessExecutorMockGuard, cmd, get_process_executor_mock}; use indexmap::IndexMap; use serial_test::serial; @@ -13,6 +14,7 @@ use shirabe::filter::PlatformRequirementFilterInterface; use shirabe::installer::{InstallationManagerInterface, InstallerEvents, InstallerInterface}; use shirabe::io::IOInterface; use shirabe::io::buffer_io::BufferIO; +use shirabe::io::io_interface; use shirabe::package::{ LockerInterface, PackageInterfaceHandle, RootPackageHandle, RootPackageInterfaceHandle, }; @@ -111,6 +113,18 @@ fn dispatcher_with_listeners( dispatcher } +/// ref: EventDispatcherTest::getDispatcherStubForListenersTest — same mocked `getListeners`, but +/// constructed without a ProcessExecutor. +fn dispatcher_stub_for_listeners_test( + composer: &ComposerHandle, + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + listeners: Vec<&str>, +) -> EventDispatcher { + let mut dispatcher = EventDispatcher::new(composer.upcast().downgrade(), io, None); + dispatcher.__set_get_listeners_override(listeners_const(listeners)); + dispatcher +} + fn listeners_const(listeners: Vec<&str>) -> Box<dyn Fn(&dyn EventInterface) -> Vec<Callable>> { let listeners: Vec<String> = listeners.into_iter().map(|s| s.to_string()).collect(); Box::new(move |_event| listeners.iter().cloned().map(Callable::String).collect()) @@ -342,13 +356,47 @@ fn test_dispatcher_doesnt_return_skipped_scripts() { // unilaterally under the no-test-alteration rule. #[test] +#[serial] #[ignore = "listener `EventDispatcherTest::call` is a static method of the PHPUnit test class itself; the PHP worker cannot load it (extends PHPUnit\\Framework\\TestCase, phpunit absent from composer/vendor) — see the note above the ignored block"] fn test_listener_exceptions_are_caught() { let _tear_down = TearDown; - // TODO(phase-d): the listener is a static method of the PHPUnit test class itself, which - // the PHP worker cannot load (phpunit is absent from composer/vendor); pending a decision on - // providing the listener methods to the child process. - todo!() + + let (io_mock, _io_guard) = get_io_mock(io_interface::NORMAL).unwrap(); + let io_dyn: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = io_mock.clone(); + + let composer = create_composer_instance(); + let mut dispatcher = dispatcher_stub_for_listeners_test( + &composer, + io_dyn, + vec!["Composer\\Test\\EventDispatcher\\EventDispatcherTest::call"], + ); + + io_mock + .borrow_mut() + .expects( + vec![ + Expectation::text("> Composer\\Test\\EventDispatcher\\EventDispatcherTest::call"), + Expectation::text( + "Script Composer\\Test\\EventDispatcher\\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception", + ), + ], + true, + ) + .unwrap(); + + let result = dispatcher.dispatch_script( + ScriptEvents::POST_INSTALL_CMD, + false, + vec![], + IndexMap::new(), + ); + + let e = result.expect_err("expected RuntimeException"); + assert!( + e.downcast_ref::<shirabe_php_shim::RuntimeException>() + .is_some(), + "got: {e:?}" + ); } // PHP mocks `Composer\Autoload\AutoloadGenerator` with onlyMethods(['buildPackageMap', @@ -535,43 +583,214 @@ fn test_dispatcher_pass_dev_mode_to_autoload_generator_for_script_events() { } #[test] -#[ignore = "listeners are object-method array callables ([\\$this, 'someMethod']) invoked + removed by object identity; the array-callable invocation path is an unimplemented plugin-runtime stub"] +#[serial] +#[ignore = "the object-method listeners are methods of the PHPUnit test class itself; invoking them sends a CallMethod for a phandle that has no counterpart in the worker, which cannot load that class (extends PHPUnit\\Framework\\TestCase, phpunit absent from composer/vendor) — see the note above the ignored block"] fn test_dispatcher_remove_listener() { let _tear_down = TearDown; - // TODO(phase-d): listeners are object-method array callables ([$this, 'someMethod']) invoked - // and removed by object identity; the array-callable invocation path is an unimplemented - // plugin-runtime stub - todo!() + + let composer = create_composer_instance(); + + let mut repository_manager = MockRepositoryManager::new(); + repository_manager + .expect_get_local_repository() + .returning(|| RepositoryInterfaceHandle::new(InstalledArrayRepository::new().unwrap())); + composer + .borrow_mut() + .set_repository_manager(std::rc::Rc::new(std::cell::RefCell::new( + repository_manager, + ))); + composer + .borrow_mut() + .set_installation_manager(std::rc::Rc::new(std::cell::RefCell::new( + MockInstallationManager::new(), + ))); + + let (process, _process_guard) = + get_process_executor_mock(vec![], false, MockHandler::default()); + let io = buffer_io_verbose(); + let io_dyn: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = io.clone(); + let mut dispatcher = EventDispatcher::new(composer.upcast().downgrade(), io_dyn, Some(process)); + + // PHP's `[$this, 'someMethod']` is an array callable whose object half is the test instance. + // `Callable::PhpMethod` is the port's shape for an object-half callable: it carries the + // cross-RPC identity `remove_listener` compares, and echoes `Class->method` like PHP does. + let this = shirabe_php_rpc::PhpObjHandle { + phandle: 1, + class: "Composer\\Test\\EventDispatcher\\EventDispatcherTest".to_string(), + implements: vec![], + }; + let listener = Callable::PhpMethod(this.clone(), "someMethod".to_string()); + let listener2 = Callable::PhpMethod(this.clone(), "someMethod2".to_string()); + let listener3 = Callable::String( + "Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod".to_string(), + ); + + dispatcher.add_listener("ev1", listener.clone(), 0); + dispatcher.add_listener("ev1", listener.clone(), 1); + dispatcher.add_listener("ev1", listener2, 1); + dispatcher.add_listener("ev1", listener3.clone(), 0); + dispatcher.add_listener("ev2", listener3, 0); + dispatcher.add_listener("ev2", listener, 0); + dispatcher.dispatch(Some("ev1"), None).unwrap(); + dispatcher.dispatch(Some("ev2"), None).unwrap(); + + let mut expected = format!( + "> ev1: Composer\\Test\\EventDispatcher\\EventDispatcherTest->someMethod{eol}\ + > ev1: Composer\\Test\\EventDispatcher\\EventDispatcherTest->someMethod2{eol}\ + > ev1: Composer\\Test\\EventDispatcher\\EventDispatcherTest->someMethod{eol}\ + > ev1: Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod{eol}\ + > ev2: Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod{eol}\ + > ev2: Composer\\Test\\EventDispatcher\\EventDispatcherTest->someMethod{eol}", + eol = PHP_EOL + ); + assert_eq!(expected, io.borrow().get_output()); + + dispatcher.remove_listener(&this); + dispatcher.dispatch(Some("ev1"), None).unwrap(); + dispatcher.dispatch(Some("ev2"), None).unwrap(); + + expected += &format!( + "> ev1: Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod{eol}\ + > ev2: Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod{eol}", + eol = PHP_EOL + ); + assert_eq!(expected, io.borrow().get_output()); } #[test] +#[serial] #[ignore = "listener `EventDispatcherTest::someMethod` is a static method of the PHPUnit test class itself; the PHP worker cannot load it — see the note above the ignored block"] fn test_dispatcher_can_execute_cli_and_php_in_same_event_script_stack() { let _tear_down = TearDown; - // TODO(phase-d): the PHP-script listener is a static method of the PHPUnit test class - // itself, which the PHP worker cannot load; pending a decision on providing the listener - // methods to the child process. - todo!() + + let (process, _process_guard) = get_process_executor_mock( + vec![cmd("echo -n foo"), cmd("echo -n bar")], + true, + MockHandler::default(), + ); + + let composer = create_composer_instance(); + let io = buffer_io_verbose(); + let io_dyn: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = io.clone(); + + let mut dispatcher = dispatcher_with_listeners( + &composer, + io_dyn, + process, + listeners_const(vec![ + "echo -n foo", + "Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod", + "echo -n bar", + ]), + ); + + dispatcher + .dispatch_script( + ScriptEvents::POST_INSTALL_CMD, + false, + vec![], + IndexMap::new(), + ) + .unwrap(); + + let expected = format!( + "> post-install-cmd: echo -n foo{eol}> post-install-cmd: Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod{eol}> post-install-cmd: echo -n bar{eol}", + eol = PHP_EOL + ); + assert_eq!(expected, io.borrow().get_output()); } #[test] +#[serial] #[ignore = "listener `EventDispatcherTest::getTestEnv` is a static method of the PHPUnit test class itself; the PHP worker cannot load it — see the note above the ignored block"] fn test_dispatcher_can_put_env() { let _tear_down = TearDown; - // TODO(phase-d): the second listener is a static method of the PHPUnit test class itself, - // which the PHP worker cannot load; pending a decision on providing the listener methods to - // the child process. - todo!() + + let (process, _process_guard) = + get_process_executor_mock(vec![], false, MockHandler::default()); + + let composer = create_composer_instance(); + let io = buffer_io_verbose(); + let io_dyn: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = io.clone(); + + let mut dispatcher = dispatcher_with_listeners( + &composer, + io_dyn, + process, + listeners_const(vec![ + "@putenv ABC=123", + "Composer\\Test\\EventDispatcher\\EventDispatcherTest::getTestEnv", + ]), + ); + + dispatcher + .dispatch_script( + ScriptEvents::POST_INSTALL_CMD, + false, + vec![], + IndexMap::new(), + ) + .unwrap(); + + let expected = format!( + "> post-install-cmd: @putenv ABC=123{eol}> post-install-cmd: Composer\\Test\\EventDispatcher\\EventDispatcherTest::getTestEnv{eol}", + eol = PHP_EOL + ); + assert_eq!(expected, io.borrow().get_output()); } #[test] +#[serial] #[ignore = "listeners (createsVendorBinFolderChecksEnv*) are static methods of the PHPUnit test class itself; the PHP worker cannot load them — see the note above the ignored block"] fn test_dispatcher_appends_dir_bin_on_path_for_every_listener() { let _tear_down = TearDown; - // TODO(phase-d): the listeners are static methods of the PHPUnit test class itself, which - // the PHP worker cannot load; pending a decision on providing the listener methods to the - // child process. - todo!() + + let current_directory_bkp = Platform::get_cwd(false).unwrap(); + let composer_bin_dir_bkp = Platform::get_env("COMPOSER_BIN_DIR"); + // ref: __DIR__ of EventDispatcherTest.php, where the listeners create `vendor/bin`. + let php_test_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../composer/tests/Composer/Test/EventDispatcher") + .canonicalize() + .unwrap(); + std::env::set_current_dir(&php_test_dir).unwrap(); + Platform::put_env( + "COMPOSER_BIN_DIR", + &format!("{}/vendor/bin", php_test_dir.display()), + ); + + let (process, _process_guard) = + get_process_executor_mock(vec![], false, MockHandler::default()); + + let composer = create_composer_instance(); + let io = buffer_io_verbose(); + let io_dyn: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = io.clone(); + + let mut dispatcher = dispatcher_with_listeners( + &composer, + io_dyn, + process, + listeners_const(vec![ + "Composer\\Test\\EventDispatcher\\EventDispatcherTest::createsVendorBinFolderChecksEnvDoesNotContainsBin", + "Composer\\Test\\EventDispatcher\\EventDispatcherTest::createsVendorBinFolderChecksEnvContainsBin", + ]), + ); + + dispatcher + .dispatch_script( + ScriptEvents::POST_INSTALL_CMD, + false, + vec![], + IndexMap::new(), + ) + .unwrap(); + std::fs::remove_dir(php_test_dir.join("vendor/bin")).unwrap(); + std::fs::remove_dir(php_test_dir.join("vendor")).unwrap(); + + std::env::set_current_dir(¤t_directory_bkp).unwrap(); + match composer_bin_dir_bkp { + Some(dir) if !dir.is_empty() => Platform::put_env("COMPOSER_BIN_DIR", &dir), + _ => Platform::clear_env("COMPOSER_BIN_DIR"), + } } #[test] diff --git a/crates/shirabe/tests/event_dispatcher/main.rs b/crates/shirabe/tests/event_dispatcher/main.rs index eac717d7..f51b0f2d 100644 --- a/crates/shirabe/tests/event_dispatcher/main.rs +++ b/crates/shirabe/tests/event_dispatcher/main.rs @@ -1,3 +1,5 @@ +#[path = "../common/io_mock.rs"] +mod io_mock; #[path = "../common/io_stub.rs"] mod io_stub; #[path = "../common/process_executor_mock.rs"] |
