#[cfg(unix)]
use libc;
use std::boxed::Box;
use std::ffi::OsStr;
use std::fmt;
use std::io::{
self,
Read,
Write,
};
use std::path::Path;
use std::process::{
Child,
ChildStderr,
ChildStdin,
ChildStdout,
Command,
ExitStatus,
Output,
Stdio,
};
use std::sync::{Arc,Mutex};
pub trait CommandChild {
type I: Write + Sync + Send + 'static;
type O: Read + Sync + Send + 'static;
type E: Read + Sync + Send + 'static;
fn take_stdin(&mut self) -> Option<Self::I>;
fn take_stdout(&mut self) -> Option<Self::O>;
fn take_stderr(&mut self) -> Option<Self::E>;
fn wait(&mut self) -> io::Result<ExitStatus>;
fn wait_with_output(self) -> io::Result<Output>;
}
pub trait RunCommand : fmt::Debug {
type C: CommandChild + 'static;
fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self;
fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self;
fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self;
fn stdin(&mut self, cfg: Stdio) -> &mut Self;
fn stdout(&mut self, cfg: Stdio) -> &mut Self;
fn stderr(&mut self, cfg: Stdio) -> &mut Self;
fn spawn(&mut self) -> io::Result<Self::C>;
}
pub trait CommandCreator : Send {
type Cmd: RunCommand;
fn new() -> Self;
fn new_command<S: AsRef<OsStr>>(&mut self, program: S) -> Self::Cmd;
}
pub trait CommandCreatorSync : Clone + Send {
type Cmd: RunCommand;
fn new() -> Self;
fn new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> Self::Cmd;
}
impl CommandChild for Child {
type I = ChildStdin;
type O = ChildStdout;
type E = ChildStderr;
fn take_stdin(&mut self) -> Option<ChildStdin> { self.stdin.take() }
fn take_stdout(&mut self) -> Option<ChildStdout> { self.stdout.take() }
fn take_stderr(&mut self) -> Option<ChildStderr> { self.stderr.take() }
fn wait(&mut self) -> io::Result<ExitStatus> {
self.wait()
}
fn wait_with_output(self) -> io::Result<Output> {
self.wait_with_output()
}
}
impl RunCommand for Command {
type C = Child;
fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
self.arg(arg)
}
fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Command {
self.args(args)
}
fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Command {
self.current_dir(dir)
}
fn stdin(&mut self, cfg: Stdio) -> &mut Command {
self.stdin(cfg)
}
fn stdout(&mut self, cfg: Stdio) -> &mut Command {
self.stdout(cfg)
}
fn stderr(&mut self, cfg: Stdio) -> &mut Command {
self.stderr(cfg)
}
fn spawn(&mut self) -> io::Result<Child> {
self.spawn()
}
}
#[derive(Clone)]
pub struct ProcessCommandCreator;
impl CommandCreator for ProcessCommandCreator {
type Cmd = Command;
fn new() -> ProcessCommandCreator {
ProcessCommandCreator
}
fn new_command<S: AsRef<OsStr>>(&mut self, program: S) -> Command {
Command::new(program)
}
}
impl CommandCreatorSync for ProcessCommandCreator {
type Cmd = Command;
fn new() -> ProcessCommandCreator {
ProcessCommandCreator
}
fn new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> Command {
Command::new(program)
}
}
#[cfg(unix)]
pub type ExitStatusValue = libc::c_int;
#[cfg(windows)]
pub type ExitStatusValue = u32;
#[allow(dead_code)]
struct InnerExitStatus(ExitStatusValue);
#[allow(dead_code)]
pub fn exit_status(v : ExitStatusValue) -> ExitStatus {
use std::mem::transmute;
unsafe { transmute(InnerExitStatus(v)) }
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct MockChild {
pub stdin: Option<io::Cursor<Vec<u8>>>,
pub stdout: Option<io::Cursor<Vec<u8>>>,
pub stderr: Option<io::Cursor<Vec<u8>>>,
pub wait_result: Option<io::Result<ExitStatus>>,
}
impl MockChild {
#[allow(dead_code)]
pub fn new<T: AsRef<[u8]>>(status: ExitStatus, stdout: T, stderr: T) -> MockChild {
MockChild {
stdin: Some(io::Cursor::new(vec!())),
stdout: Some(io::Cursor::new(stdout.as_ref().to_vec())),
stderr: Some(io::Cursor::new(stderr.as_ref().to_vec())),
wait_result: Some(Ok(status)),
}
}
#[allow(dead_code)]
pub fn with_error(err: io::Error) -> MockChild {
MockChild {
stdin: None,
stdout: None,
stderr: None,
wait_result: Some(Err(err)),
}
}
}
impl CommandChild for MockChild {
type I = io::Cursor<Vec<u8>>;
type O = io::Cursor<Vec<u8>>;
type E = io::Cursor<Vec<u8>>;
fn take_stdin(&mut self) -> Option<io::Cursor<Vec<u8>>> { self.stdin.take() }
fn take_stdout(&mut self) -> Option<io::Cursor<Vec<u8>>> { self.stdout.take() }
fn take_stderr(&mut self) -> Option<io::Cursor<Vec<u8>>> { self.stderr.take() }
fn wait(&mut self) -> io::Result<ExitStatus> {
self.wait_result.take().unwrap()
}
fn wait_with_output(self) -> io::Result<Output> {
let MockChild { stdout, stderr, wait_result, .. } = self;
wait_result.unwrap().and_then(|status| {
Ok(Output {
status: status,
stdout: stdout.map(|c| c.into_inner()).unwrap_or(vec!()),
stderr: stderr.map(|c| c.into_inner()).unwrap_or(vec!()),
})
})
}
}
pub enum ChildOrCall {
Child(io::Result<MockChild>),
Call(Box<Fn() -> io::Result<MockChild> + Send>),
}
impl fmt::Debug for ChildOrCall {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ChildOrCall::Child(ref r) => write!(f, "ChildOrCall::Child({:?}", r),
ChildOrCall::Call(_) => write!(f, "ChildOrCall::Call(...)"),
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct MockCommand {
pub child : Option<ChildOrCall>,
}
impl RunCommand for MockCommand {
type C = MockChild;
fn arg<S: AsRef<OsStr>>(&mut self, _arg: S) -> &mut MockCommand {
self
}
fn args<S: AsRef<OsStr>>(&mut self, _args: &[S]) -> &mut MockCommand {
self
}
fn current_dir<P: AsRef<Path>>(&mut self, _dir: P) -> &mut MockCommand {
self
}
fn stdin(&mut self, _cfg: Stdio) -> &mut MockCommand {
self
}
fn stdout(&mut self, _cfg: Stdio) -> &mut MockCommand {
self
}
fn stderr(&mut self, _cfg: Stdio) -> &mut MockCommand {
self
}
fn spawn(&mut self) -> io::Result<MockChild> {
match self.child.take().unwrap() {
ChildOrCall::Child(c) => c,
ChildOrCall::Call(f) => f(),
}
}
}
#[allow(dead_code)]
pub struct MockCommandCreator {
pub children : Vec<ChildOrCall>,
}
impl MockCommandCreator {
#[allow(dead_code)]
pub fn next_command_spawns(&mut self, child: io::Result<MockChild>) {
self.children.push(ChildOrCall::Child(child));
}
#[allow(dead_code)]
pub fn next_command_calls<C: Fn() -> io::Result<MockChild> + Send + 'static>(&mut self, call: C) {
self.children.push(ChildOrCall::Call(Box::new(call)));
}
}
impl CommandCreator for MockCommandCreator {
type Cmd = MockCommand;
fn new() -> MockCommandCreator {
MockCommandCreator {
children: vec!(),
}
}
fn new_command<S: AsRef<OsStr>>(&mut self, _program: S) -> MockCommand {
assert!(self.children.len() > 0, "Too many calls to MockCommandCreator::new_command, or not enough to MockCommandCreator::new_command_spawns!");
MockCommand {
child: Some(self.children.remove(0)),
}
}
}
impl<T : CommandCreator> CommandCreatorSync for Arc<Mutex<T>> {
type Cmd = T::Cmd;
fn new() -> Arc<Mutex<T>> {
Arc::new(Mutex::new(T::new()))
}
fn new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> T::Cmd {
self.lock().unwrap().new_command(program)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::error::Error;
use std::ffi::OsStr;
use std::io;
use std::process::{
ExitStatus,
Output,
};
use std::sync::{Arc,Mutex};
use std::thread;
use test::utils::*;
fn spawn_command<T : CommandCreator, S: AsRef<OsStr>>(creator : &mut T, program: S) -> io::Result<<<T as CommandCreator>::Cmd as RunCommand>::C> {
creator.new_command(program).spawn()
}
fn spawn_wait_command<T : CommandCreator, S: AsRef<OsStr>>(creator : &mut T, program: S) -> io::Result<ExitStatus> {
spawn_command(creator, program).and_then(|mut c| c.wait())
}
fn spawn_output_command<T : CommandCreator, S: AsRef<OsStr>>(creator : &mut T, program: S) -> io::Result<Output> {
spawn_command(creator, program).and_then(|c| c.wait_with_output())
}
fn spawn_on_thread<T : CommandCreatorSync + Send + 'static>(mut t : T, really : bool) -> ExitStatus {
thread::spawn(move || {
if really {
t.new_command_sync("foo").spawn().and_then(|mut c| c.wait()).unwrap()
} else {
exit_status(1)
}
}).join().unwrap()
}
#[test]
fn test_mock_command_wait() {
let mut creator = MockCommandCreator::new();
creator.next_command_spawns(Ok(MockChild::new(exit_status(0), "hello", "error")));
assert_eq!(0, spawn_wait_command(&mut creator, "foo").unwrap().code().unwrap());
}
#[test]
#[should_panic]
fn test_unexpected_new_command() {
let mut creator = MockCommandCreator::new();
creator.new_command("foo").spawn().unwrap();
}
#[test]
fn test_mock_command_output() {
let mut creator = MockCommandCreator::new();
creator.next_command_spawns(Ok(MockChild::new(exit_status(0), "hello", "error")));
let output = spawn_output_command(&mut creator, "foo").unwrap();
assert_eq!(0, output.status.code().unwrap());
assert_eq!("hello".as_bytes().to_vec(), output.stdout);
assert_eq!("error".as_bytes().to_vec(), output.stderr);
}
#[test]
fn test_mock_command_calls() {
let mut creator = MockCommandCreator::new();
creator.next_command_calls(|| {
Ok(MockChild::new(exit_status(0), "hello", "error"))
});
let output = spawn_output_command(&mut creator, "foo").unwrap();
assert_eq!(0, output.status.code().unwrap());
assert_eq!("hello".as_bytes().to_vec(), output.stdout);
assert_eq!("error".as_bytes().to_vec(), output.stderr);
}
#[test]
fn test_mock_spawn_error() {
let mut creator = MockCommandCreator::new();
creator.next_command_spawns(Err(io::Error::new(io::ErrorKind::Other, "error")));
let e = spawn_command(&mut creator, "foo").err().unwrap();
assert_eq!(io::ErrorKind::Other, e.kind());
assert_eq!("error", e.description());
}
#[test]
fn test_mock_wait_error() {
let mut creator = MockCommandCreator::new();
creator.next_command_spawns(Ok(MockChild::with_error(io::Error::new(io::ErrorKind::Other, "error"))));
let e = spawn_wait_command(&mut creator, "foo").err().unwrap();
assert_eq!(io::ErrorKind::Other, e.kind());
assert_eq!("error", e.description());
}
#[test]
fn test_mock_command_sync() {
let creator = Arc::new(Mutex::new(MockCommandCreator::new()));
next_command(&creator, Ok(MockChild::new(exit_status(0), "hello", "error")));
assert_eq!(exit_status(0), spawn_on_thread(creator.clone(), true));
}
#[test]
fn test_real_command_sync() {
let creator = ProcessCommandCreator;
assert_eq!(exit_status(1), spawn_on_thread(creator.clone(), false));
}
}