#![allow(unused)]
extern crate wait_timeout;
use std::ffi::{OsString, OsStr};
use std::collections::VecDeque;
use std::env::{split_paths, join_paths};
use std::path::PathBuf;
use wait_timeout::ChildExt;
use std::process::{Child, ExitStatus};
use std::time::Duration;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::fmt::Debug;
#[macro_export]
macro_rules! exec {
(
// Input Configuration
$program:expr // the program name, without a key and required
$(, args: $args:expr)? // the arguments to pass to the program
$(, cwd: $cwd:expr)? // the desired current working directory
$(, clear_env: $clear_env:expr)? // a boolean setting whether the environment should be cleared
$(, env: { $( $key:ident : $value:expr ),* } )? $(, modify_path: $modify_path:expr)? $(, stdin: $stdin:expr)? $(, timeout: $timeout:expr)? $(, log: $log:expr)?
$(, code: $code:expr)? $(, stdout: $stdout:expr)? $(, stderr: $stderr:expr)? $(, signal: $signal:expr)? ) => {{
use $crate::*;
use std::process::{Command, Stdio, Output};
use std::env::var_os;
use std::io::{self, Write};
use std::time::Duration;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::mem;
use std::str;
use std::fmt::Debug;
let mut command = Command::new($program);
command.stdin(Stdio::null());
command.stdout(Stdio::piped());
command.stderr(Stdio::piped());
$(
command.args($args.into_iter());
)?
$( command.current_dir($cwd);
)?
$( if $clear_env {
command.env_clear();
};
)?
let mut modify_path = true;
let mut custom_path = false;
$(
modify_path = $modify_path;
)?
$( $(
let key = stringify!($key);
if key == "PATH" && modify_path {
command.env("PATH", alter_path($value, env!("CARGO_MANIFEST_DIR")));
custom_path = true;
}else {
command.env(key, $value);
};
)*
)?
if !custom_path && modify_path {
let path = alter_path(&var_os("PATH").unwrap_or_default(), env!("CARGO_MANIFEST_DIR"));
command.env("PATH", path);
};
$( let _ = $stdin; command.stdin(Stdio::piped());
)?
let mut child = command.spawn().expect("Failed to spawn child process");
$( let stdin = &$stdin;
let a = AsRef::<[u8]>::as_ref(&stdin); child.stdin.as_mut()
.map(|buf| buf.write_all(a).expect("Failed to write to stdin"));
)?
let mut duration = None;
$( duration = Some(Duration::from_millis($timeout as u64));
)?
let status = wait(&mut child, duration);
let mut code = 0;
$( code = $code;
)?
assert(&code, &get_code(status), "Unexpected exit code");
$( #[cfg(unix)] {
assert_eq!(Some($signal), status.signal(), "Unexpected signal");
};
)?
let mut stdout = Vec::with_capacity(0xff);
let mut child_stdout = mem::replace(&mut child.stdout, None).unwrap();
io::copy(&mut child_stdout, &mut stdout).unwrap();
let mut stderr = Vec::with_capacity(0xf);
let mut child_stderr = mem::replace(&mut child.stderr, None).unwrap();
io::copy(&mut child_stderr, &mut stderr).unwrap();
$( if $log {
println!("{} returned\n code: {}\n stdout: {:?}\n stderr: {:?}",
stringify!($program),
code,
match str::from_utf8(&stdout) {
Ok(ref string) => string as &Debug,
Err(_) => &stdout
},
match str::from_utf8(&stderr) {
Ok(ref string) => string as &Debug,
Err(_) => &stderr
}
);
};
)?
$( let expected_stdout = &$stdout;
let a = AsRef::<[u8]>::as_ref(expected_stdout);
assert(&a, &stdout.as_slice(), "Unexpected value of stdout");
)?
$( let expected_stderr = &$stderr;
let a = AsRef::<[u8]>::as_ref(expected_stderr);
assert(&a, &stderr.as_slice(), "Unexpected value of stderr");
)?
let output = Output {
status,
stdout,
stderr
};
output
}};
($program:expr,) => {
exec!($program)
}
}
#[doc(hidden)]
pub fn alter_path<T: AsRef<OsStr> + ?Sized>(path: &T, current_path: &'static str) -> OsString {
let mut paths: VecDeque<_> = split_paths(path).collect();
let mut bins = PathBuf::from(current_path);
bins.push("target/debug");
paths.push_front(bins.clone());
bins.pop();
bins.push("release");
paths.push_front(bins);
join_paths(paths).expect("Invalid characters in path")
}
#[doc(hidden)]
pub fn wait(child: &mut Child, duration: Option<Duration>) -> ExitStatus {
if let Some(duration) = duration {
child.wait_timeout(duration).expect("Failed to wait for child process")
.unwrap_or_else(|| {
child.kill().expect("Failed to kill child process");
println!("Killed child process");
child.wait().unwrap()
})
} else {
child.wait().expect("Failed to wait for child process")
}
}
#[cfg(unix)]
#[doc(hidden)]
#[inline]
pub fn get_code(status: ExitStatus) -> i32 {
status.code()
.or_else(|| status.signal())
.unwrap()
}
#[cfg(not(unix))]
#[doc(hidden)]
#[inline]
pub fn get_code(status: ExitStatus) -> i32 {
status.code().unwrap()
}
#[doc(hidden)]
pub fn assert<T, U>(a: &T, b: &U, message: &str) where
T: Debug + PartialEq<U>,
U: Debug {
if a != b {
panic!("assertion failed: {}\nexpected `{:?}`\nfound `{:?}`", message, a, b);
};
}
#[cold]
fn possible_input() {
exec! {
"",
stdin: b"a",
stdout: b"b",
stderr: b"c"
};
let buf = vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144];
exec! {
"",
stdin: buf,
stdout: vec![0x13],
stderr: Vec::new()
};
let a = buf.as_slice();
exec! {
"",
stdin: a,
stdout: []
};
struct Arbitrary<'a>(&'a [u8]);
impl<'a> AsRef<[u8]> for Arbitrary<'a> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
let a = Arbitrary(&buf);
exec! {
"",
stdin: a,
stdout: a,
stderr: a
};
}