aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs139
-rw-r--r--crates/shirabe/tests/event_dispatcher/fixtures/phpunit_test_case.php36
-rw-r--r--crates/shirabe/tests/event_dispatcher/main.rs2
3 files changed, 134 insertions, 43 deletions
diff --git a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs
index a343d056..7a98adc8 100644
--- a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs
+++ b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs
@@ -1,10 +1,13 @@
//! ref: composer/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
use crate::io_mock::{Expectation, get_io_mock};
+use crate::php_worker::{
+ load_composer_php_runtime, php_eval, php_runtime_available, php_single_quote,
+};
use crate::process_executor_mock::{ProcessExecutorMockGuard, cmd, get_process_executor_mock};
use indexmap::IndexMap;
use serial_test::serial;
-use shirabe::autoload::{AutoloadGeneratorInterface, ClassLoader};
+use shirabe::autoload::{AutoloadGenerator, AutoloadGeneratorInterface, ClassLoader};
use shirabe::composer::{ComposerHandle, PartialOrFullComposer};
use shirabe::config::Config;
use shirabe::dependency_resolver::Transaction;
@@ -45,10 +48,8 @@ impl Drop for TearDown {
/// ref: EventDispatcherTest::createComposerInstance.
///
-/// The PHP helper wires a RepositoryManager / AutoloadGenerator / InstallationManager so that the
-/// autoloader-rebuild path (only reached for PHP-script and array callables) works. The tests ported
-/// here drive only command-line / composer-script listeners, which never touch those collaborators,
-/// so a minimal full Composer carrying a Config and a RootPackage is sufficient.
+/// The RepositoryManager / AutoloadGenerator / InstallationManager are what the autoloader-rebuild
+/// path reaches for, which happens for PHP-script and array callables.
fn create_composer_instance() -> ComposerHandle {
let composer = ComposerHandle::from_rc_unchecked(std::rc::Rc::new(std::cell::RefCell::new(
PartialOrFullComposer::new_full(),
@@ -62,9 +63,65 @@ fn create_composer_instance() -> ComposerHandle {
)
.into();
composer.borrow_mut().set_package(package);
+
+ 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 generator = AutoloadGenerator::new(
+ std::rc::Rc::new(std::cell::RefCell::new(EventDispatcher::new(
+ composer.upcast().downgrade(),
+ null_io(),
+ None,
+ ))),
+ None,
+ );
+ composer
+ .borrow_mut()
+ .set_autoload_generator(std::rc::Rc::new(std::cell::RefCell::new(generator)));
+
composer
}
+fn composer_test_dir() -> std::path::PathBuf {
+ std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("../../composer/tests/Composer/Test")
+ .canonicalize()
+ .unwrap()
+}
+
+/// Makes `Composer\Test\EventDispatcher\EventDispatcherTest` — the class whose static and object
+/// methods the listeners below name — loadable in the worker, by requiring the upstream file from
+/// the Composer checkout. Its base class chain ends at `PHPUnit\Framework\TestCase`, which the
+/// Composer PHP runtime bundle does not carry, so the stand-in under `fixtures/` declares that
+/// class first. In PHP the listeners resolve because the class is the running test class itself.
+fn load_php_listener_class() {
+ static LOADED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
+ LOADED.get_or_init(|| {
+ load_composer_php_runtime();
+ let stub = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("tests/event_dispatcher/fixtures/phpunit_test_case.php");
+ for file in [
+ stub,
+ composer_test_dir().join("TestCase.php"),
+ composer_test_dir().join("EventDispatcher/EventDispatcherTest.php"),
+ ] {
+ let path = file.to_str().expect("the fixture paths are UTF-8");
+ php_eval(&format!("require {};", php_single_quote(path)));
+ }
+ });
+}
+
fn null_io() -> std::rc::Rc<std::cell::RefCell<dyn IOInterface>> {
std::rc::Rc::new(std::cell::RefCell::new(shirabe::io::null_io::NullIO::new()))
}
@@ -345,20 +402,14 @@ fn test_dispatcher_doesnt_return_skipped_scripts() {
let _ = &mut event;
}
-// The remaining ignored tests use, as their listeners, static methods of the PHPUnit test class
-// `Composer\Test\EventDispatcher\EventDispatcherTest` itself (or object-identity array
-// callables). The PHP-script invocation path is implemented (execute_event_php_script sends a
-// CallStaticMethod over the RPC channel), but the worker child process cannot load that test
-// class: it extends PHPUnit\Framework\TestCase and phpunit is not part of composer/vendor.
-// Making these pass needs a decision on how to provide the listener methods to the child (e.g. a
-// stand-in fixture class with the same FQCN and method bodies), which is not a call to make
-// 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;
+ if !php_runtime_available() {
+ return;
+ }
+ load_php_listener_class();
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();
@@ -582,27 +633,15 @@ fn test_dispatcher_pass_dev_mode_to_autoload_generator_for_script_events() {
#[test]
#[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;
+ if !php_runtime_available() {
+ return;
+ }
+ load_php_listener_class();
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();
@@ -612,11 +651,11 @@ fn test_dispatcher_remove_listener() {
// 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 this =
+ match php_eval("return new \\Composer\\Test\\EventDispatcher\\EventDispatcherTest();") {
+ shirabe_php_rpc::PluginValue::PhpHandle(handle) => handle,
+ other => panic!("the worker returned no object handle for the listener: {other:?}"),
+ };
let listener = Callable::PhpMethod(this.clone(), "someMethod".to_string());
let listener2 = Callable::PhpMethod(this.clone(), "someMethod2".to_string());
let listener3 = Callable::String(
@@ -657,9 +696,12 @@ fn test_dispatcher_remove_listener() {
#[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;
+ if !ensure_php_binary() || !php_runtime_available() {
+ return;
+ }
+ load_php_listener_class();
let (process, _process_guard) = get_process_executor_mock(
vec![cmd("echo -n foo"), cmd("echo -n bar")],
@@ -698,11 +740,18 @@ fn test_dispatcher_can_execute_cli_and_php_in_same_event_script_stack() {
assert_eq!(expected, io.borrow().get_output());
}
+// TODO(php-runtime): `@putenv` writes the environment of the Shirabe process, while the listener
+// reads `getenv()` inside the PHP worker — a long-lived child that keeps the environment it
+// inherited when it was spawned. Nothing propagates the write across the boundary.
#[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"]
+#[ignore = "`@putenv ABC=123` does not reach the PHP worker the listener runs in"]
fn test_dispatcher_can_put_env() {
let _tear_down = TearDown;
+ if !php_runtime_available() {
+ return;
+ }
+ load_php_listener_class();
let (process, _process_guard) =
get_process_executor_mock(vec![], false, MockHandler::default());
@@ -737,19 +786,23 @@ fn test_dispatcher_can_put_env() {
assert_eq!(expected, io.borrow().get_output());
}
+// TODO(php-runtime): the bin dir is appended to the PATH of the Shirabe process, while the
+// listeners read `getenv('PATH')` inside the PHP worker — a long-lived child that keeps the
+// environment it inherited when it was spawned. Nothing propagates the append across the boundary.
#[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"]
+#[ignore = "the bin dir the dispatcher appends to PATH does not reach the PHP worker the listeners run in"]
fn test_dispatcher_appends_dir_bin_on_path_for_every_listener() {
let _tear_down = TearDown;
+ if !php_runtime_available() {
+ return;
+ }
+ load_php_listener_class();
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();
+ let php_test_dir = composer_test_dir().join("EventDispatcher");
std::env::set_current_dir(&php_test_dir).unwrap();
Platform::put_env(
"COMPOSER_BIN_DIR",
diff --git a/crates/shirabe/tests/event_dispatcher/fixtures/phpunit_test_case.php b/crates/shirabe/tests/event_dispatcher/fixtures/phpunit_test_case.php
new file mode 100644
index 00000000..72446369
--- /dev/null
+++ b/crates/shirabe/tests/event_dispatcher/fixtures/phpunit_test_case.php
@@ -0,0 +1,36 @@
+<?php declare(strict_types=1);
+
+namespace PHPUnit\Framework;
+
+/**
+ * Stands in for the base class of `Composer\Test\TestCase`, which the Composer PHP runtime
+ * bundle does not carry. Declaring it is all the worker needs to require the upstream
+ * `EventDispatcherTest` from the Composer checkout and call its listener methods.
+ *
+ * The two assertions are the only PHPUnit members those listeners use, and they report failure
+ * the way PHPUnit does, by throwing.
+ */
+class TestCase
+{
+ public static function assertStringContainsString(string $needle, string $haystack, string $message = ''): void
+ {
+ if (!str_contains($haystack, $needle)) {
+ throw new \RuntimeException($message !== '' ? $message : sprintf(
+ 'Failed asserting that %s contains %s.',
+ var_export($haystack, true),
+ var_export($needle, true)
+ ));
+ }
+ }
+
+ public static function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void
+ {
+ if (str_contains($haystack, $needle)) {
+ throw new \RuntimeException($message !== '' ? $message : sprintf(
+ 'Failed asserting that %s does not contain %s.',
+ var_export($haystack, true),
+ var_export($needle, true)
+ ));
+ }
+ }
+}
diff --git a/crates/shirabe/tests/event_dispatcher/main.rs b/crates/shirabe/tests/event_dispatcher/main.rs
index f51b0f2d..a17270ca 100644
--- a/crates/shirabe/tests/event_dispatcher/main.rs
+++ b/crates/shirabe/tests/event_dispatcher/main.rs
@@ -2,6 +2,8 @@
mod io_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;