diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-19 23:00:38 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-19 23:00:38 +0900 |
| commit | adfa7c6b295521a6f8fdf4084993a80a8030e49b (patch) | |
| tree | 36d2bc1f717286d129bc7f15fba2d06e6887ad5f /crates | |
| parent | fd733a87364b877d5e66b4973bd61b5379ec4d61 (diff) | |
| download | php-shirabe-adfa7c6b295521a6f8fdf4084993a80a8030e49b.tar.gz php-shirabe-adfa7c6b295521a6f8fdf4084993a80a8030e49b.tar.zst php-shirabe-adfa7c6b295521a6f8fdf4084993a80a8030e49b.zip | |
test(command): add basic smoke tests
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/shirabe-php-shim/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe/src/console/application.rs | 45 | ||||
| -rw-r--r-- | crates/shirabe/src/lib.rs | 20 | ||||
| -rw-r--r-- | crates/shirabe/src/main.rs | 29 | ||||
| -rw-r--r-- | crates/shirabe/tests/cli.rs | 82 |
5 files changed, 126 insertions, 52 deletions
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs index c716fcc..6b8a229 100644 --- a/crates/shirabe-php-shim/src/lib.rs +++ b/crates/shirabe-php-shim/src/lib.rs @@ -3178,7 +3178,7 @@ pub fn str_replace_arr(_search: &[&str], _replace: &str, _subject: &str) -> Stri todo!() } -pub fn php_exception_get_code(_error: &anyhow::Error) -> i64 { +pub fn php_exception_get_code(_error: &anyhow::Error) -> i32 { todo!() } pub fn sscanf(_subject: &str, _format: &str, _a: &mut i64, _b: &mut i64) -> i64 { diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index 591114a..82aa03d 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -127,7 +127,6 @@ pub struct Application { version: String, command_loader: Option<Box<dyn CommandLoaderInterface>>, catch_exceptions: bool, - auto_exit: bool, definition: Option<std::rc::Rc<std::cell::RefCell<InputDefinition>>>, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>, terminal: Terminal, @@ -201,7 +200,6 @@ impl Application { version, command_loader: None, catch_exceptions: true, - auto_exit: true, definition: None, helper_set: None, terminal: Terminal::new(), @@ -318,7 +316,7 @@ impl Application { application: &std::rc::Rc<std::cell::RefCell<Application>>, input: Option<std::rc::Rc<std::cell::RefCell<dyn InputInterface>>>, output: Option<std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>>, - ) -> anyhow::Result<i64> { + ) -> anyhow::Result<i32> { let output = match output { Some(output) => Some(output), None => Some( @@ -334,7 +332,7 @@ impl Application { application: &std::rc::Rc<std::cell::RefCell<Application>>, input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - ) -> anyhow::Result<i64> { + ) -> anyhow::Result<i32> { application.borrow_mut().disable_plugins_by_default = input .borrow() .has_parameter_option(PhpMixed::from(vec!["--no-plugins"]), false); @@ -907,7 +905,7 @@ impl Application { } let mut start_time: Option<f64> = None; - let result_outcome: anyhow::Result<i64> = (|| -> anyhow::Result<i64> { + let result_outcome: anyhow::Result<i32> = (|| -> anyhow::Result<i32> { if input .borrow() .has_parameter_option(PhpMixed::from(vec!["--profile"]), false) @@ -921,7 +919,7 @@ impl Application { let _ = start_time.unwrap(); } - let result: i64 = Application::base_do_run(application, input.clone(), output.clone())?; + let result = Application::base_do_run(application, input.clone(), output.clone())?; if input .borrow() @@ -975,7 +973,7 @@ impl Application { ); } - Ok(see.get_code()) + Ok(see.get_code() as i32) } else { let mut ghe = GithubActionError::new(io.clone()); ghe.emit(&e.to_string(), None, None); @@ -1443,7 +1441,7 @@ impl Application { application: &std::rc::Rc<std::cell::RefCell<Application>>, input: Option<std::rc::Rc<std::cell::RefCell<dyn InputInterface>>>, output: Option<std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>>, - ) -> anyhow::Result<i64> { + ) -> anyhow::Result<i32> { if shirabe_php_shim::function_exists("putenv") { let (height, width) = { let app = application.borrow(); @@ -1479,7 +1477,7 @@ impl Application { this.render_throwable(e, output.clone()); }; - let result = (|| -> anyhow::Result<i64> { + let result = (|| -> anyhow::Result<i32> { application.borrow_mut().configure_io(&input, &output)?; let exit_code = Application::do_run(application, input.clone(), output.clone())?; @@ -1487,7 +1485,7 @@ impl Application { Ok(exit_code) })(); - let mut exit_code = match result { + let exit_code = match result { Ok(exit_code) => exit_code, Err(e) => { if !application.borrow().catch_exceptions { @@ -1512,14 +1510,6 @@ impl Application { // finally: handler restore. See TODO above; no-op here. - if application.borrow().auto_exit { - if exit_code > 255 { - exit_code = 255; - } - - shirabe_php_shim::exit(exit_code); - } - Ok(exit_code) } @@ -1528,7 +1518,7 @@ impl Application { application: &std::rc::Rc<std::cell::RefCell<Application>>, input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - ) -> anyhow::Result<i64> { + ) -> anyhow::Result<i32> { if input.borrow().has_parameter_option( PhpMixed::from(vec![ PhpMixed::from("--version".to_string()), @@ -1801,16 +1791,6 @@ impl Application { self.catch_exceptions = boolean; } - /// Gets whether to automatically exit after a command execution or not. - pub fn is_auto_exit_enabled(&self) -> bool { - self.auto_exit - } - - /// Sets whether to automatically exit after a command execution or not. - pub fn set_auto_exit(&mut self, boolean: bool) { - self.auto_exit = boolean; - } - /// Gets the name of the application. pub fn get_name(&self) -> String { self.name.clone() @@ -2545,7 +2525,7 @@ impl Application { command: std::rc::Rc<std::cell::RefCell<dyn SymfonyCommand>>, input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - ) -> anyhow::Result<i64> { + ) -> anyhow::Result<i32> { if let Some(helper_set) = command.borrow().get_helper_set() { for (_alias, helper) in helper_set.borrow().get_iterator() { // if ($helper instanceof InputAwareInterface) $helper->setInput($input); @@ -2588,7 +2568,10 @@ impl Application { } } - command.borrow_mut().run(input.clone(), output.clone()) + command + .borrow_mut() + .run(input.clone(), output.clone()) + .map(|c| c as i32) } /// Gets the name of the command based on input. diff --git a/crates/shirabe/src/lib.rs b/crates/shirabe/src/lib.rs index 1e17b72..4bd52c2 100644 --- a/crates/shirabe/src/lib.rs +++ b/crates/shirabe/src/lib.rs @@ -24,3 +24,23 @@ pub mod repository; pub mod script; pub mod self_update; pub mod util; + +pub fn run(argv: Vec<String>) -> anyhow::Result<i32> { + use crate::console::Application; + use crate::util::Platform; + use shirabe_external_packages::symfony::console::input::argv_input::ArgvInput; + use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; + + // TODO(php-runtime): the full initialization process in composer/bin/composer should be ported + // somewhere else that communicates with the real PHP runtime. + Platform::put_env( + "COMPOSER_BINARY", + &shirabe_php_shim::realpath(argv.first().map(String::as_str).unwrap_or_default()) + .unwrap_or_default(), + ); + + let application = Application::new_shared("Composer".to_string(), String::new())?; + let input = std::rc::Rc::new(std::cell::RefCell::new(ArgvInput::new(Some(argv), None)?)) + as std::rc::Rc<std::cell::RefCell<dyn InputInterface>>; + Application::run(&application, Some(input), None) +} diff --git a/crates/shirabe/src/main.rs b/crates/shirabe/src/main.rs index f179c52..3e3623d 100644 --- a/crates/shirabe/src/main.rs +++ b/crates/shirabe/src/main.rs @@ -1,30 +1,19 @@ //! ref: composer/bin/composer -use shirabe::console::Application; -use shirabe::util::Platform; -use shirabe_php_shim::{realpath, run_shutdown_functions}; +use shirabe_php_shim::run_shutdown_functions; fn main() { - // TODO(php-runtime): the full initialization process in composer/bin/composer should be ported - // somewhere else that communicates with the real PHP runtime. - - Platform::put_env( - "COMPOSER_BINARY", - &realpath(&std::env::args().next().unwrap_or_default()).unwrap_or_default(), - ); - - // run the command application - let application = match Application::new_shared("Composer".to_string(), String::new()) { - Ok(application) => application, + let result = shirabe::run(std::env::args().collect()); + run_shutdown_functions(); + let mut exit_code = match result { + Ok(exit_code) => exit_code, Err(e) => { eprintln!("{}", e); - std::process::exit(1); + 1 } }; - let result = Application::run(&application, None, None); - run_shutdown_functions(); - if let Err(e) = result { - eprintln!("{}", e); - std::process::exit(1); + if exit_code > 255 { + exit_code = 255; } + std::process::exit(exit_code); } diff --git a/crates/shirabe/tests/cli.rs b/crates/shirabe/tests/cli.rs new file mode 100644 index 0000000..09b9746 --- /dev/null +++ b/crates/shirabe/tests/cli.rs @@ -0,0 +1,82 @@ +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::sync::{Mutex, Once}; + +const COMMANDS: &[&str] = &[ + "about", + "archive", + "audit", + "browse", + "bump", + "check-platform-reqs", + "clear-cache", + "config", + "create-project", + "depends", + "diagnose", + "dump-autoload", + "exec", + "fund", + "global", + "init", + "install", + "licenses", + "outdated", + "prohibits", + "reinstall", + "remove", + "repository", + "require", + "run-script", + "search", + "self-update", + "show", + "status", + "suggests", + "update", + "validate", +]; + +static QUIET_PANIC: Once = Once::new(); + +/// `shirabe::run` reads/writes process-global env, so concurrent invocations race; +/// serialize them since the default test harness runs tests on many threads. +static SERIAL: Mutex<()> = Mutex::new(()); + +/// Runs the CLI with `args`. Returns true on clean exit, false on any panic / error / non-zero +/// exit. +fn run(args: &[&str]) -> bool { + QUIET_PANIC.call_once(|| std::panic::set_hook(Box::new(|_| {}))); + let _guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner()); + + // Each invocation must look like a fresh process. + // + // SAFETY: all environment access in these tests happens through `shirabe::run` while holding + // `SERIAL`, so no other thread reads or writes the environment concurrently with these calls. + unsafe { + std::env::remove_var("COLUMNS"); + std::env::remove_var("LINES"); + } + + let mut argv = vec!["composer".to_string()]; + argv.extend(args.iter().map(|s| s.to_string())); + matches!( + catch_unwind(AssertUnwindSafe(|| shirabe::run(argv))), + Ok(Ok(0)) + ) +} + +#[test] +fn version_flag() { + assert!(run(&["--version"])); +} + +#[test] +fn help_flag() { + assert!(run(&["--help"])); +} + +#[test] +fn each_command_help() { + let failed: Vec<&&str> = COMMANDS.iter().filter(|c| !run(&[c, "--help"])).collect(); + assert!(failed.is_empty(), "`<cmd> --help` failed for: {failed:?}"); +} |
