use std::process::{Command, Stdio};
pub trait CommandRunner: Send + Sync {
fn run(&self, bin: &str, args: &[&str]) -> Option<String>;
}
pub struct SystemCommandRunner;
impl CommandRunner for SystemCommandRunner {
fn run(&self, bin: &str, args: &[&str]) -> Option<String> {
let output = Command::new(bin)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
if stdout.is_empty() {
None
} else {
Some(stdout)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(unix)]
#[test]
fn system_runner_returns_stdout_for_real_binary() {
let runner = SystemCommandRunner;
let out = runner.run("echo", &["hello"]).expect("echo should succeed");
assert_eq!(out, "hello");
}
#[test]
fn system_runner_returns_none_on_spawn_failure() {
let runner = SystemCommandRunner;
let out = runner.run("definitely-not-a-real-binary-1234567", &[]);
assert_eq!(out, None);
}
#[cfg(unix)]
#[test]
fn system_runner_returns_none_on_non_zero_exit() {
let runner = SystemCommandRunner;
let out = runner.run("false", &[]);
assert_eq!(out, None);
}
#[cfg(unix)]
#[test]
fn system_runner_returns_none_on_empty_stdout_despite_success() {
let runner = SystemCommandRunner;
let out = runner.run("true", &[]);
assert_eq!(out, None);
}
#[cfg(unix)]
#[test]
fn system_runner_treats_whitespace_only_stdout_as_empty() {
let runner = SystemCommandRunner;
let out = runner.run("sh", &["-c", "printf ' \\t\\n '"]);
assert_eq!(out, None);
}
#[cfg(unix)]
#[test]
fn system_runner_trims_surrounding_whitespace() {
let runner = SystemCommandRunner;
let out = runner.run("sh", &["-c", "printf ' /some/path \\n'"]);
assert_eq!(out.as_deref(), Some("/some/path"));
}
#[cfg(unix)]
#[test]
fn system_runner_ignores_stderr_when_stdout_empty() {
let runner = SystemCommandRunner;
let out = runner.run("sh", &["-c", "printf 'diagnostic' >&2"]);
assert_eq!(out, None);
}
#[cfg(unix)]
#[test]
fn system_runner_returns_only_stdout_when_both_streams_used() {
let runner = SystemCommandRunner;
let out = runner.run("sh", &["-c", "printf 'good\\n'; printf 'bad\\n' >&2"]);
assert_eq!(out.as_deref(), Some("good"));
}
#[cfg(unix)]
#[test]
fn system_runner_forwards_all_args_in_order() {
let runner = SystemCommandRunner;
let out = runner.run("sh", &["-c", "printf '%s' \"$1\"", "sh", "forwarded"]);
assert_eq!(out.as_deref(), Some("forwarded"));
}
}