use std::cell::{Ref, RefCell};
use std::ffi::{OsStr, OsString};
use std::marker::PhantomData;
use std::path::Path;
use std::process;
use std::rc::Rc;
use crate::errors::*;
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 env<K, V>(&mut self, key: K, val: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>;
fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self;
fn status(&mut self) -> Result<process::ExitStatus>;
fn exec(&mut self) -> Result<()> {
let status = self.status()?;
if status.success() {
Ok(())
} else {
Err(self.command_failed_error().into())
}
}
fn command_failed_error(&self) -> ErrorKind;
}
#[derive(Debug, Default)]
#[allow(missing_copy_implementations)]
pub struct OsCommandRunner {
#[doc(hidden)]
pub _nonexhaustive: PhantomData<()>,
}
impl OsCommandRunner {
pub fn new() -> OsCommandRunner {
OsCommandRunner {
_nonexhaustive: PhantomData,
}
}
}
impl CommandRunner for OsCommandRunner {
type Command = OsCommand;
fn build<S: AsRef<OsStr>>(&self, program: S) -> Self::Command {
let program = program.as_ref();
OsCommand {
command: process::Command::new(program),
arg_log: vec![program.to_owned()],
}
}
}
#[derive(Debug)]
pub struct OsCommand {
command: process::Command,
arg_log: Vec<OsString>,
}
impl Command for OsCommand {
fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
let arg = arg.as_ref();
self.arg_log.push(arg.to_owned());
self.command.arg(arg);
self
}
fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
let args: Vec<_> = args.iter().map(|a| a.as_ref().to_owned()).collect();
self.arg_log.extend_from_slice(&args);
self.command.args(&args);
self
}
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.command.env(key, val);
self
}
fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
self.command.current_dir(dir);
self
}
fn status(&mut self) -> Result<process::ExitStatus> {
debug!("Running {:?}", &self.arg_log);
self.command
.status()
.chain_err(|| self.command_failed_error())
}
fn command_failed_error(&self) -> ErrorKind {
ErrorKind::CommandFailed(self.arg_log.clone())
}
}
#[test]
fn os_command_runner_runs_commands() {
let runner = OsCommandRunner::new();
assert!(runner.build("true").status().unwrap().success());
assert!(!runner.build("false").status().unwrap().success());
}
#[derive(Debug)]
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 Default for TestCommandRunner {
fn default() -> TestCommandRunner {
TestCommandRunner::new()
}
}
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(),
}
}
}
#[derive(Debug)]
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 env<K, V>(&mut self, _key: K, _val: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self
}
fn current_dir<P: AsRef<Path>>(&mut self, _dir: P) -> &mut Self {
self
}
fn status(&mut self) -> Result<process::ExitStatus> {
self.record_execution();
process::Command::new("true")
.status()
.chain_err(|| self.command_failed_error())
}
fn command_failed_error(&self) -> ErrorKind {
ErrorKind::CommandFailed(self.cmd.clone())
}
}
#[allow(unused_macros)]
macro_rules! assert_ran {
($runner:expr, { $( [ $($arg:expr),+ $(,)? ] ),* }) => {
use std::ops::Deref;
fn coerce<S: AsRef<::std::ffi::OsStr>>(s: S) ->
::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"]
});
}