#![deny(warnings, missing_docs)]
extern crate difference;
#[macro_use] extern crate error_chain;
use std::process::Command;
use difference::Changeset;
mod errors;
use errors::*;
mod diff;
#[derive(Debug)]
pub struct Assert {
cmd: Vec<String>,
expect_success: bool,
expect_exit_code: Option<i32>,
expect_output: Option<String>,
fuzzy_output: bool,
expect_error_output: Option<String>,
fuzzy_error_output: bool,
}
impl std::default::Default for Assert {
fn default() -> Self {
Assert {
cmd: vec!["cargo", "run", "--"]
.into_iter().map(String::from).collect(),
expect_success: true,
expect_exit_code: None,
expect_output: None,
fuzzy_output: false,
expect_error_output: None,
fuzzy_error_output: false,
}
}
}
impl Assert {
pub fn main_binary() -> Self {
Assert::default()
}
pub fn cargo_binary(name: &str) -> Self {
Assert {
cmd: vec!["cargo", "run", "--bin", name, "--"]
.into_iter().map(String::from).collect(),
..Self::default()
}
}
pub fn command(cmd: &[&str]) -> Self {
Assert {
cmd: cmd.into_iter().cloned().map(String::from).collect(),
..Self::default()
}
}
pub fn with_args(mut self, args: &[&str]) -> Self {
self.cmd.extend(args.into_iter().cloned().map(String::from));
self
}
pub fn and(self) -> Self {
self
}
pub fn succeeds(mut self) -> Self {
self.expect_success = true;
self
}
pub fn fails(mut self) -> Self {
self.expect_success = false;
self
}
pub fn fails_with(mut self, expect_exit_code: i32) -> Self {
self.expect_success = false;
self.expect_exit_code = Some(expect_exit_code);
self
}
pub fn prints<O: Into<String>>(mut self, output: O) -> Self {
self.expect_output = Some(output.into());
self.fuzzy_output = true;
self
}
pub fn prints_exactly<O: Into<String>>(mut self, output: O) -> Self {
self.expect_output = Some(output.into());
self.fuzzy_output = false;
self
}
pub fn prints_error<O: Into<String>>(mut self, output: O) -> Self {
self.expect_error_output = Some(output.into());
self.fuzzy_error_output = true;
self
}
pub fn prints_error_exactly<O: Into<String>>(mut self, output: O) -> Self {
self.expect_error_output = Some(output.into());
self.fuzzy_error_output = false;
self
}
pub fn execute(self) -> Result<()> {
let cmd = &self.cmd[0];
let args: Vec<_> = self.cmd.iter().skip(1).collect();
let mut command = Command::new(cmd);
let command = command.args(&args);
let output = command.output()?;
if self.expect_success != output.status.success() {
bail!(ErrorKind::StatusMismatch(
self.cmd.clone(),
self.expect_success.clone(),
));
}
if self.expect_exit_code.is_some() &&
self.expect_exit_code != output.status.code() {
bail!(ErrorKind::ExitCodeMismatch(
self.cmd.clone(),
self.expect_exit_code,
output.status.code(),
));
}
let stdout = String::from_utf8_lossy(&output.stdout);
match (self.expect_output, self.fuzzy_output) {
(Some(ref expected_output), true) if !stdout.contains(expected_output) => {
bail!(ErrorKind::OutputMismatch(
expected_output.clone(),
stdout.into(),
));
},
(Some(ref expected_output), false) => {
let differences = Changeset::new(expected_output.trim(), stdout.trim(), "\n");
if differences.distance > 0 {
let nice_diff = diff::render(&differences)?;
bail!(ErrorKind::ExactOutputMismatch(nice_diff));
}
},
_ => {},
}
let stderr = String::from_utf8_lossy(&output.stderr);
match (self.expect_error_output, self.fuzzy_error_output) {
(Some(ref expected_output), true) if !stderr.contains(expected_output) => {
bail!(ErrorKind::ErrorOutputMismatch(
expected_output.clone(),
stderr.into(),
));
},
(Some(ref expected_output), false) => {
let differences = Changeset::new(expected_output.trim(), stderr.trim(), "\n");
if differences.distance > 0 {
let nice_diff = diff::render(&differences)?;
bail!(ErrorKind::ExactErrorOutputMismatch(nice_diff));
}
},
_ => {},
}
Ok(())
}
pub fn unwrap(self) {
if let Err(err) = self.execute() {
panic!("Assert CLI failure:\n{}", err);
}
}
}
#[macro_export]
macro_rules! assert_cmd {
($($x:tt)+) => {{
$crate::Assert::command(
&[$(stringify!($x)),*]
)
}}
}