aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/common
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-25 14:46:55 +0900
committernsfisis <nsfisis@gmail.com>2026-06-26 00:19:42 +0900
commit9c6f614aa6ddb5d91340e34b0136be6a14712d63 (patch)
tree35973a35532ada33fe05805995b2e5b5e7bee1bc /crates/shirabe/tests/common
parentd0336078c5b63b174e7313d54d973a1832228928 (diff)
downloadphp-shirabe-9c6f614aa6ddb5d91340e34b0136be6a14712d63.tar.gz
php-shirabe-9c6f614aa6ddb5d91340e34b0136be6a14712d63.tar.zst
php-shirabe-9c6f614aa6ddb5d91340e34b0136be6a14712d63.zip
feat(util): add ProcessExecutorMock test infra; port SymfonyStyle message methods
Add an internal mock hook to ProcessExecutor (None in production) so tests can stub command execution without spawning processes, mirroring Composer's ProcessExecutorMock subclass. Add get_process_executor_mock helper and two verification tests. Implement SymfonyStyle's message-handling methods. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/common')
-rw-r--r--crates/shirabe/tests/common/process_executor_mock.rs101
1 files changed, 101 insertions, 0 deletions
diff --git a/crates/shirabe/tests/common/process_executor_mock.rs b/crates/shirabe/tests/common/process_executor_mock.rs
new file mode 100644
index 0000000..b81c8bb
--- /dev/null
+++ b/crates/shirabe/tests/common/process_executor_mock.rs
@@ -0,0 +1,101 @@
+//! ref: composer/tests/Composer/Test/Mock/ProcessExecutorMock.php
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use shirabe::util::process_executor::{MockExpectation, MockHandler, ProcessExecutor};
+use shirabe_php_shim::PhpMixed;
+
+// A command expectation as written in the PHP tests: either a bare command
+// (`'git command'` / `['git', '--version']`) or the full
+// `{cmd, return?, stdout?, stderr?}` associative form.
+pub fn cmd(command: impl IntoMockCmd) -> MockExpectation {
+ MockExpectation::from_cmd(command.into_mock_cmd())
+}
+
+pub fn cmd_full(
+ command: impl IntoMockCmd,
+ r#return: i64,
+ stdout: impl Into<String>,
+ stderr: impl Into<String>,
+) -> MockExpectation {
+ MockExpectation {
+ cmd: command.into_mock_cmd(),
+ r#return,
+ stdout: stdout.into(),
+ stderr: stderr.into(),
+ callback: None,
+ }
+}
+
+// Accepts the same command shapes the tests use: a string command or a list of
+// string args. Comparison against the executed command is exact (PHP `===`), so
+// the form here must match the form the code under test passes to `execute`.
+pub trait IntoMockCmd {
+ fn into_mock_cmd(self) -> PhpMixed;
+}
+
+impl IntoMockCmd for &str {
+ fn into_mock_cmd(self) -> PhpMixed {
+ PhpMixed::String(self.to_string())
+ }
+}
+
+impl IntoMockCmd for String {
+ fn into_mock_cmd(self) -> PhpMixed {
+ PhpMixed::String(self)
+ }
+}
+
+impl IntoMockCmd for Vec<&str> {
+ fn into_mock_cmd(self) -> PhpMixed {
+ PhpMixed::List(
+ self.into_iter()
+ .map(|s| PhpMixed::String(s.to_string()))
+ .collect(),
+ )
+ }
+}
+
+impl IntoMockCmd for Vec<String> {
+ fn into_mock_cmd(self) -> PhpMixed {
+ PhpMixed::List(self.into_iter().map(PhpMixed::String).collect())
+ }
+}
+
+impl<const N: usize> IntoMockCmd for [&str; N] {
+ fn into_mock_cmd(self) -> PhpMixed {
+ PhpMixed::List(
+ self.iter()
+ .map(|s| PhpMixed::String(s.to_string()))
+ .collect(),
+ )
+ }
+}
+
+pub struct ProcessExecutorMockGuard(Rc<RefCell<ProcessExecutor>>);
+
+impl Drop for ProcessExecutorMockGuard {
+ fn drop(&mut self) {
+ // Avoid aborting on a double panic when a test assertion is already unwinding.
+ if std::thread::panicking() {
+ return;
+ }
+ self.0.borrow().__assert_complete();
+ }
+}
+
+// For testing only. Mirrors TestCase::getProcessExecutorMock: returns a shared
+// ProcessExecutor handle configured with the given expectations, plus a guard
+// that runs `__assert_complete` when it drops at the end of the test scope.
+pub fn get_process_executor_mock(
+ expectations: Vec<MockExpectation>,
+ strict: bool,
+ default_handler: MockHandler,
+) -> (Rc<RefCell<ProcessExecutor>>, ProcessExecutorMockGuard) {
+ let process = Rc::new(RefCell::new(ProcessExecutor::new(None)));
+ process
+ .borrow_mut()
+ .__expects(expectations, strict, default_handler);
+ (process.clone(), ProcessExecutorMockGuard(process))
+}