From 94507ec2f265ca9400d64d42036ca1e8722e27da Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 20 Aug 2026 09:50:58 +0900 Subject: fix(event-dispatcher): let a PHP script reach the Composer object graph A PHP script listener that walks the event it receives (Laravel's Illuminate\Foundation\ComposerScripts::postAutoloadDump asks for $event->getComposer()->getConfig()->get('vendor-dir')) aborted the run with `unknown Rust handle 2`. The event's getComposer/getIO answers register an entity in the R table and hand back its rhandle, but ScriptRpcDispatcher resolved only rhandle 0 and the one event handle of the call in flight, so it could not serve a method on a handle it had just minted itself. The R-table lookup PluginRpcDispatcher already does is now dispatch_r_table_method, shared by both. The stubs that graph hands out extend and implement the real Composer contracts (Composer\Package\PackageInterface and the rest), which live in the Composer PHP runtime and not among the generated stubs or guards, so execute_event_php_script loads that runtime the way the plugin and command-class paths do. Co-Authored-By: Claude Opus 5 (1M context) --- .../shirabe/tests/plugin/e2e_script_event_test.rs | 90 ++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 crates/shirabe/tests/plugin/e2e_script_event_test.rs (limited to 'crates/shirabe/tests/plugin/e2e_script_event_test.rs') diff --git a/crates/shirabe/tests/plugin/e2e_script_event_test.rs b/crates/shirabe/tests/plugin/e2e_script_event_test.rs new file mode 100644 index 00000000..5a0267d7 --- /dev/null +++ b/crates/shirabe/tests/plugin/e2e_script_event_test.rs @@ -0,0 +1,90 @@ +//! Script event E2E compatibility check: upstream Composer and Shirabe each run `dump-autoload` +//! in a fixture project whose `post-autoload-dump` script is a PHP static method, and the method +//! records what the `Composer\Script\Event` it receives exposes. Upstream has no test that reads +//! the Composer object graph out of an event from a script, so the whole fixture is +//! Shirabe-authored (`fixtures/e2e-script-event/`) and nothing has to be fetched; the test skips +//! only while the PHP runtime or the Composer checkout is missing. + +use crate::e2e_extension_installer_test::{copy_dir, upstream_composer_bin}; +use crate::php_worker::{lock_php_worker, php_runtime_available}; +use std::path::{Path, PathBuf}; +use tempfile::TempDir; + +fn fixture_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/plugin/fixtures/e2e-script-event") +} + +struct Run { + exit_code: i32, + trace: String, + stdout: String, +} + +/// Runs `dump-autoload` in a fresh copy of the fixture and returns the exit code with what the +/// script wrote to its trace file and through the event's IO. +fn dump_autoload(program: &str, prefix_args: &[&str]) -> Run { + let work = TempDir::new().unwrap(); + copy_dir(&fixture_dir(), work.path()); + let project = work.path().join("project"); + let output = std::process::Command::new(program) + .args(prefix_args) + .arg("dump-autoload") + .current_dir(&project) + .env("COMPOSER_HOME", work.path().join("home")) + .env("COMPOSER_CACHE_DIR", work.path().join("cache")) + .env("COMPOSER_NO_INTERACTION", "1") + .env("COLUMNS", "120") + .env("LINES", "30") + .output() + .unwrap(); + Run { + exit_code: output.status.code().unwrap_or(-1), + trace: std::fs::read_to_string(project.join("script-event-trace.txt")).unwrap_or_default(), + stdout: String::from_utf8_lossy(&output.stdout).into_owned(), + } +} + +#[test] +fn test_script_event_object_graph_matches_upstream_composer() { + if !php_runtime_available() { + return; + } + let Some(composer_bin) = upstream_composer_bin() else { + return; + }; + let _worker = lock_php_worker(); + let composer_bin = composer_bin.to_str().unwrap().to_string(); + + let upstream = dump_autoload("php", &[composer_bin.as_str()]); + let shirabe = dump_autoload(env!("CARGO_BIN_EXE_shirabe"), &[]); + + assert_eq!(0, upstream.exit_code, "upstream dump-autoload must succeed"); + assert_eq!(upstream.exit_code, shirabe.exit_code); + assert_eq!(upstream.trace, shirabe.trace); + + // Pinned as well as compared, so a run where neither side dispatches the script cannot pass. + assert_eq!( + "\ +event=post-autoload-dump +dev=0 +root=shirabe/e2e-script-event +vendor-dir=vendor +bin-dir=bin +", + upstream.trace + ); + + let io_line = |run: &Run| -> String { + run.stdout + .lines() + .find(|line| line.starts_with("script-event: ")) + .unwrap_or_default() + .to_string() + }; + assert_eq!(io_line(&upstream), io_line(&shirabe)); + assert_eq!( + "script-event: event=post-autoload-dump dev=0 root=shirabe/e2e-script-event \ + vendor-dir=vendor bin-dir=bin", + io_line(&upstream) + ); +} -- cgit v1.3.1-4-g156e