aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-19 23:00:38 +0900
committernsfisis <nsfisis@gmail.com>2026-06-19 23:00:38 +0900
commitadfa7c6b295521a6f8fdf4084993a80a8030e49b (patch)
tree36d2bc1f717286d129bc7f15fba2d06e6887ad5f
parentfd733a87364b877d5e66b4973bd61b5379ec4d61 (diff)
downloadphp-shirabe-adfa7c6b295521a6f8fdf4084993a80a8030e49b.tar.gz
php-shirabe-adfa7c6b295521a6f8fdf4084993a80a8030e49b.tar.zst
php-shirabe-adfa7c6b295521a6f8fdf4084993a80a8030e49b.zip
test(command): add basic smoke tests
-rw-r--r--crates/shirabe-php-shim/src/lib.rs2
-rw-r--r--crates/shirabe/src/console/application.rs45
-rw-r--r--crates/shirabe/src/lib.rs20
-rw-r--r--crates/shirabe/src/main.rs29
-rw-r--r--crates/shirabe/tests/cli.rs82
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:?}");
+}