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);
}
}