use std::ffi::OsStr;
use std::io::Write;
use std::path::Path;
use std::process::{Command as StdCommand, ExitStatus, Output, Stdio};
use std::{ffi, panic};
use build_helper::drop_bomb::DropBomb;
use crate::util::handle_failed_output;
use crate::{
assert_contains, assert_contains_regex, assert_equals, assert_not_contains,
assert_not_contains_regex,
};
#[derive(Debug)]
pub struct Command {
cmd: StdCommand,
stdin: Option<Box<[u8]>>,
drop_bomb: DropBomb,
}
impl Command {
#[track_caller]
pub fn new<P: AsRef<OsStr>>(program: P) -> Self {
let program = program.as_ref();
Self { cmd: StdCommand::new(program), stdin: None, drop_bomb: DropBomb::arm(program) }
}
pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.stdin = Some(input.as_ref().to_vec().into_boxed_slice());
self
}
pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: AsRef<ffi::OsStr>,
V: AsRef<ffi::OsStr>,
{
self.cmd.env(key, value);
self
}
pub fn env_remove<K>(&mut self, key: K) -> &mut Self
where
K: AsRef<ffi::OsStr>,
{
self.cmd.env_remove(key);
self
}
pub fn arg<S>(&mut self, arg: S) -> &mut Self
where
S: AsRef<ffi::OsStr>,
{
self.cmd.arg(arg);
self
}
pub fn args<S, V>(&mut self, args: V) -> &mut Self
where
S: AsRef<ffi::OsStr>,
V: AsRef<[S]>,
{
self.cmd.args(args.as_ref());
self
}
pub fn inspect<I>(&mut self, inspector: I) -> &mut Self
where
I: FnOnce(&StdCommand),
{
inspector(&self.cmd);
self
}
pub fn current_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.cmd.current_dir(path);
self
}
#[track_caller]
pub fn run(&mut self) -> CompletedProcess {
let output = self.command_output();
if !output.status().success() {
handle_failed_output(&self, output, panic::Location::caller().line());
}
output
}
#[track_caller]
pub fn run_fail(&mut self) -> CompletedProcess {
let output = self.command_output();
if output.status().success() {
handle_failed_output(&self, output, panic::Location::caller().line());
}
output
}
#[track_caller]
pub fn run_unchecked(&mut self) -> CompletedProcess {
self.command_output()
}
#[track_caller]
fn command_output(&mut self) -> CompletedProcess {
self.drop_bomb.defuse();
self.cmd.stdin(Stdio::piped());
self.cmd.stdout(Stdio::piped());
self.cmd.stderr(Stdio::piped());
let output = if let Some(input) = &self.stdin {
let mut child = self.cmd.spawn().unwrap();
{
let mut stdin = child.stdin.take().unwrap();
stdin.write_all(input.as_ref()).unwrap();
}
child.wait_with_output().expect("failed to get output of finished process")
} else {
self.cmd.output().expect("failed to get output of finished process")
};
output.into()
}
}
pub struct CompletedProcess {
output: Output,
}
impl CompletedProcess {
#[must_use]
#[track_caller]
pub fn stdout_utf8(&self) -> String {
String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
}
#[must_use]
#[track_caller]
pub fn invalid_stdout_utf8(&self) -> String {
String::from_utf8_lossy(&self.output.stdout.clone()).to_string()
}
#[must_use]
#[track_caller]
pub fn stderr_utf8(&self) -> String {
String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
}
#[must_use]
pub fn status(&self) -> ExitStatus {
self.output.status
}
#[track_caller]
pub fn assert_stdout_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
assert_equals(self.stdout_utf8().trim(), expected.as_ref().trim());
self
}
#[track_caller]
pub fn assert_stdout_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
assert_not_contains(&self.stdout_utf8(), unexpected);
self
}
#[track_caller]
pub fn assert_stdout_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
assert_not_contains_regex(&self.stdout_utf8(), unexpected);
self
}
#[track_caller]
pub fn assert_stdout_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
assert_contains(&self.stdout_utf8(), expected);
self
}
#[track_caller]
pub fn assert_stdout_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
assert_contains_regex(&self.stdout_utf8(), expected);
self
}
#[track_caller]
pub fn assert_stderr_equals<S: AsRef<str>>(&self, expected: S) -> &Self {
assert_equals(self.stderr_utf8().trim(), expected.as_ref().trim());
self
}
#[track_caller]
pub fn assert_stderr_contains<S: AsRef<str>>(&self, expected: S) -> &Self {
assert_contains(&self.stderr_utf8(), expected);
self
}
#[track_caller]
pub fn assert_stderr_contains_regex<S: AsRef<str>>(&self, expected: S) -> &Self {
assert_contains_regex(&self.stderr_utf8(), expected);
self
}
#[track_caller]
pub fn assert_stderr_not_contains<S: AsRef<str>>(&self, unexpected: S) -> &Self {
assert_not_contains(&self.stderr_utf8(), unexpected);
self
}
#[track_caller]
pub fn assert_stderr_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
assert_not_contains_regex(&self.stdout_utf8(), unexpected);
self
}
#[track_caller]
pub fn assert_exit_code(&self, code: i32) -> &Self {
assert!(self.output.status.code() == Some(code));
self
}
}
impl From<Output> for CompletedProcess {
fn from(output: Output) -> Self {
Self { output }
}
}