diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-25 14:46:55 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-26 00:19:42 +0900 |
| commit | 9c6f614aa6ddb5d91340e34b0136be6a14712d63 (patch) | |
| tree | 35973a35532ada33fe05805995b2e5b5e7bee1bc /crates/shirabe/tests/common/process_executor_mock.rs | |
| parent | d0336078c5b63b174e7313d54d973a1832228928 (diff) | |
| download | php-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/process_executor_mock.rs')
| -rw-r--r-- | crates/shirabe/tests/common/process_executor_mock.rs | 101 |
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)) +} |
