use std::cell::{Ref, RefCell};
use std::ffi::{OsStr, OsString};
use std::io;
use std::process;
use std::rc::Rc;
pub trait CommandRunner {
type Command: Command;
fn build<S: AsRef<OsStr>>(&self, program: S) -> Self::Command;
}
pub trait Command {
fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self;
fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
for arg in args {
self.arg(arg);
}
self
}
fn status(&mut self) -> io::Result<process::ExitStatus>;
}
pub struct OsCommandRunner;
impl CommandRunner for OsCommandRunner {
type Command = process::Command;
fn build<S: AsRef<OsStr>>(&self, program: S) -> Self::Command {
process::Command::new(program)
}
}
impl Command for process::Command {
fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
process::Command::arg(self, arg)
}
fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
process::Command::args(self, args)
}
fn status(&mut self) -> io::Result<process::ExitStatus> {
process::Command::status(self)
}
}
#[test]
fn os_command_runner_runs_commands() {
let runner = OsCommandRunner;
assert!(runner.build("true").status().unwrap().success());
assert!(!runner.build("false").status().unwrap().success());
}
pub struct TestCommandRunner {
cmds: Rc<RefCell<Vec<Vec<OsString>>>>,
}
impl TestCommandRunner {
pub fn new() -> TestCommandRunner {
TestCommandRunner { cmds: Rc::new(RefCell::new(vec!())) }
}
pub fn cmds(&self) -> Ref<Vec<Vec<OsString>>> {
self.cmds.borrow()
}
}
impl CommandRunner for TestCommandRunner {
type Command = TestCommand;
fn build<S: AsRef<OsStr>>(&self, program: S) -> Self::Command {
TestCommand {
cmd: vec!(program.as_ref().to_owned()),
cmds: self.cmds.clone(),
}
}
}
pub struct TestCommand {
cmd: Vec<OsString>,
cmds: Rc<RefCell<Vec<Vec<OsString>>>>,
}
impl TestCommand {
fn record_execution(&self) {
self.cmds.borrow_mut().push(self.cmd.clone());
}
}
impl Command for TestCommand {
fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
self.cmd.push(arg.as_ref().to_owned());
self
}
fn status(&mut self) -> io::Result<process::ExitStatus> {
self.record_execution();
process::Command::new("true").status()
}
}
macro_rules! assert_ran {
($runner:expr, { $( [ $($arg:expr),+ ] ),* }) => {
use std::ops::Deref;
fn coerce<S: AsRef<$crate::std::ffi::OsStr>>(s: S) ->
$crate::std::ffi::OsString
{
s.as_ref().to_owned()
}
let expected = vec!( $( vec!( $( coerce($arg) ),+ ) ),* );
assert_eq!($runner.cmds().deref(), &expected);
}
}
#[test]
pub fn test_command_runner_logs_commands() {
let runner = TestCommandRunner::new();
let exit_code = runner.build("git")
.args(&["clone", "https://github.com/torvalds/linux"])
.status().unwrap();
assert!(exit_code.success());
runner.build("echo").arg("a").arg("b").status().unwrap();
assert_ran!(runner, {
["git", "clone", "https://github.com/torvalds/linux"],
["echo", "a", "b"]
});
}