diff options
Diffstat (limited to 'crates/shirabe/tests/plugin/e2e_script_command_test.rs')
| -rw-r--r-- | crates/shirabe/tests/plugin/e2e_script_command_test.rs | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/crates/shirabe/tests/plugin/e2e_script_command_test.rs b/crates/shirabe/tests/plugin/e2e_script_command_test.rs new file mode 100644 index 00000000..019c830d --- /dev/null +++ b/crates/shirabe/tests/plugin/e2e_script_command_test.rs @@ -0,0 +1,157 @@ +//! Script-provided command E2E compatibility check: a `composer.json` script naming a +//! `Symfony\Component\Console\Command\Command` subclass is imported as an application command, +//! and `list`, `help`, executions and the mismatched-name warning are compared between upstream +//! Composer and Shirabe. +//! +//! The whole fixture is Shirabe-authored (`fixtures/e2e-script-command/`), so 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::plugin_installer_test::{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-command") +} + +struct CommandRun { + exit_code: i32, + stdout: String, + stderr: String, +} + +/// One composer-CLI invocation inside the prepared project. +fn run_command(work: &Path, program: &str, prefix_args: &[&str], args: &[&str]) -> CommandRun { + let output = std::process::Command::new(program) + .args(prefix_args) + .args(args) + .current_dir(work) + .env("COMPOSER_HOME", work.join("home")) + .env("COMPOSER_CACHE_DIR", work.join("cache")) + .env("COMPOSER_NO_INTERACTION", "1") + // Rendering width must not depend on the invoking terminal. + .env("COLUMNS", "120") + .env("LINES", "30") + .output() + .unwrap(); + CommandRun { + exit_code: output.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&output.stdout).into_owned(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + } +} + +fn lines_starting_with<'a>(text: &'a str, prefix: &str) -> Vec<&'a str> { + text.lines() + .map(str::trim_end) + .filter(|line| line.trim_start().starts_with(prefix)) + .collect() +} + +#[test] +fn test_script_command_class_import_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 implementations: [(&str, Vec<&str>); 2] = [ + ("php", vec![composer_bin.as_str()]), + (env!("CARGO_BIN_EXE_shirabe"), vec![]), + ]; + + let mut results: Vec<[CommandRun; 4]> = Vec::new(); + for (program, prefix) in &implementations { + let work = TempDir::new().unwrap(); + copy_dir(&fixture_dir(), work.path()); + let install = run_command(work.path(), program, prefix, &["install"]); + assert_eq!(0, install.exit_code, "{program}: install must succeed"); + + let greet = run_command(work.path(), program, prefix, &["greet", "World", "--shout"]); + let renamed = run_command(work.path(), program, prefix, &["renamed"]); + let help = run_command(work.path(), program, prefix, &["help", "greet"]); + let list = run_command(work.path(), program, prefix, &["list"]); + results.push([greet, renamed, help, list]); + } + + let [upstream, shirabe] = <[_; 2]>::try_from(results).ok().unwrap(); + let [u_greet, u_renamed, u_help, u_list] = upstream; + let [s_greet, s_renamed, s_help, s_list] = shirabe; + + // The imported command runs with its own definition bound: the argument, the shorthand + // option, the name the constructor took from composer.json and the hosting application. + assert_eq!(0, u_greet.exit_code, "upstream greet must succeed"); + assert_eq!(u_greet.exit_code, s_greet.exit_code); + assert_eq!( + lines_starting_with(&u_greet.stdout, "greet:"), + lines_starting_with(&s_greet.stdout, "greet:") + ); + assert_eq!( + vec![ + "greet: HELLO WORLD", + "greet: name=greet", + "greet: app=Composer\\Console\\Application", + ], + lines_starting_with(&s_greet.stdout, "greet:") + ); + + // A class whose configure() sets a different name is renamed to the script name, and an + // empty description is filled in from scripts-descriptions. + assert_eq!(0, u_renamed.exit_code, "upstream renamed must succeed"); + assert_eq!(u_renamed.exit_code, s_renamed.exit_code); + assert_eq!( + lines_starting_with(&u_renamed.stdout, "renamed:"), + lines_starting_with(&s_renamed.stdout, "renamed:") + ); + assert_eq!( + vec![ + "renamed: name=renamed", + "renamed: description=Description taken from composer.json", + ], + lines_starting_with(&s_renamed.stdout, "renamed:") + ); + let mismatch_warning = + "The script named renamed in composer.json has a mismatched name in its class definition."; + assert!( + u_renamed.stderr.contains(mismatch_warning), + "upstream must warn about the mismatched name: {}", + u_renamed.stderr + ); + assert!( + s_renamed.stderr.contains(mismatch_warning), + "shirabe must warn about the mismatched name: {}", + s_renamed.stderr + ); + + // A class extending SingleCommandApplication is still imported, with a warning. + let single_warning = "The script named single extends SingleCommandApplication which is not compatible with Composer 2.9+"; + assert!( + u_list.stderr.contains(single_warning), + "upstream must warn about SingleCommandApplication: {}", + u_list.stderr + ); + assert!( + s_list.stderr.contains(single_warning), + "shirabe must warn about SingleCommandApplication: {}", + s_list.stderr + ); + + assert_eq!(0, u_help.exit_code, "upstream help greet must succeed"); + assert_eq!(u_help.exit_code, s_help.exit_code); + assert_eq!(u_help.stdout, s_help.stdout, "help greet output differs"); + + // The imported commands are listed with the descriptions the class and composer.json give + // them, next to the plain shell script that stays a ScriptAliasCommand. + assert_eq!(0, u_list.exit_code); + assert_eq!(u_list.exit_code, s_list.exit_code); + assert_eq!(u_list.stdout, s_list.stdout, "list output differs"); + assert_eq!( + vec![" greet Greets someone from a script-provided command."], + lines_starting_with(&s_list.stdout, "greet") + ); +} |
