Skip to content
This repository was archived by the owner on Dec 29, 2021. It is now read-only.

Refactor output #87

Merged
merged 5 commits into from
Feb 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 57 additions & 62 deletions src/assert.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use environment::Environment;
use error_chain::ChainedError;
use errors::*;
use output::{OutputAssertion, OutputKind};
use output::{Content, Output, OutputKind, OutputPredicate};
use std::default;
use std::ffi::{OsStr, OsString};
use std::io::Write;
Expand All @@ -18,8 +18,8 @@ pub struct Assert {
current_dir: Option<PathBuf>,
expect_success: Option<bool>,
expect_exit_code: Option<i32>,
expect_output: Vec<OutputAssertion>,
stdin_contents: Option<String>,
expect_output: Vec<OutputPredicate>,
stdin_contents: Option<Vec<u8>>,
}

impl default::Default for Assert {
Expand Down Expand Up @@ -118,8 +118,8 @@ impl Assert {
/// .stdout().contains("42")
/// .unwrap();
/// ```
pub fn stdin(mut self, contents: &str) -> Self {
self.stdin_contents = Some(String::from(contents));
pub fn stdin<S: Into<Vec<u8>>>(mut self, contents: S) -> Self {
self.stdin_contents = Some(contents.into());
self
}

Expand Down Expand Up @@ -289,7 +289,6 @@ impl Assert {
OutputAssertionBuilder {
assertion: self,
kind: OutputKind::StdOut,
expected_result: true,
}
}

Expand All @@ -310,7 +309,6 @@ impl Assert {
OutputAssertionBuilder {
assertion: self,
kind: OutputKind::StdErr,
expected_result: true,
}
}

Expand All @@ -327,10 +325,10 @@ impl Assert {
/// assert!(test.is_ok());
/// ```
pub fn execute(self) -> Result<()> {
let cmd = &self.cmd[0];
let bin = &self.cmd[0];

let args: Vec<_> = self.cmd.iter().skip(1).collect();
let mut command = Command::new(cmd);
let mut command = Command::new(bin);
let command = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
Expand All @@ -353,38 +351,34 @@ impl Assert {
.stdin
.as_mut()
.expect("Couldn't get mut ref to command stdin")
.write_all(contents.as_bytes())?;
.write_all(contents)?;
}
let output = spawned.wait_with_output()?;

if let Some(expect_success) = self.expect_success {
if expect_success != output.status.success() {
let out = String::from_utf8_lossy(&output.stdout).to_string();
let err = String::from_utf8_lossy(&output.stderr).to_string();
bail!(ErrorKind::StatusMismatch(
self.cmd.clone(),
expect_success,
out,
err,
));
let err: Error = ErrorKind::StatusMismatch(expect_success, out, err).into();
bail!(err.chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone())));
}
}

if self.expect_exit_code.is_some() && self.expect_exit_code != output.status.code() {
let out = String::from_utf8_lossy(&output.stdout).to_string();
let err = String::from_utf8_lossy(&output.stderr).to_string();
bail!(ErrorKind::ExitCodeMismatch(
self.cmd.clone(),
self.expect_exit_code,
output.status.code(),
out,
err,
));
let err: Error =
ErrorKind::ExitCodeMismatch(self.expect_exit_code, output.status.code(), out, err)
.into();
bail!(err.chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone())));
}

self.expect_output
.iter()
.map(|a| a.execute(&output, &self.cmd))
.map(|a| {
a.verify(&output)
.chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone()))
})
.collect::<Result<Vec<()>>>()?;

Ok(())
Expand Down Expand Up @@ -414,98 +408,96 @@ impl Assert {
pub struct OutputAssertionBuilder {
assertion: Assert,
kind: OutputKind,
expected_result: bool,
}

impl OutputAssertionBuilder {
/// Negate the assertion predicate
/// Expect the command's output to **contain** `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo", "42"])
/// .stdout().not().contains("73")
/// .stdout().contains("42")
/// .unwrap();
/// ```
// No clippy, we don't want to implement std::ops::Not :)
#[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))]
pub fn not(mut self) -> Self {
self.expected_result = !self.expected_result;
self
pub fn contains<O: Into<Content>>(mut self, output: O) -> Assert {
let pred = OutputPredicate::new(self.kind, Output::contains(output));
self.assertion.expect_output.push(pred);
self.assertion
}

/// Expect the command's output to **contain** `output`.
/// Expect the command to output **exactly** this `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo", "42"])
/// .stdout().contains("42")
/// .stdout().is("42")
/// .unwrap();
/// ```
pub fn contains<O: Into<String>>(mut self, output: O) -> Assert {
self.assertion.expect_output.push(OutputAssertion {
expect: output.into(),
fuzzy: true,
expected_result: self.expected_result,
kind: self.kind,
});
pub fn is<O: Into<Content>>(mut self, output: O) -> Assert {
let pred = OutputPredicate::new(self.kind, Output::is(output));
self.assertion.expect_output.push(pred);
self.assertion
}

/// Expect the command to output **exactly** this `output`.
/// Expect the command's output to not **contain** `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo", "42"])
/// .stdout().is("42")
/// .stdout().doesnt_contain("73")
/// .unwrap();
/// ```
pub fn is<O: Into<String>>(mut self, output: O) -> Assert {
self.assertion.expect_output.push(OutputAssertion {
expect: output.into(),
fuzzy: false,
expected_result: self.expected_result,
kind: self.kind,
});
pub fn doesnt_contain<O: Into<Content>>(mut self, output: O) -> Assert {
let pred = OutputPredicate::new(self.kind, Output::doesnt_contain(output));
self.assertion.expect_output.push(pred);
self.assertion
}

/// Expect the command's output to not **contain** `output`.
/// Expect the command to output to not be **exactly** this `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo", "42"])
/// .stdout().doesnt_contain("73")
/// .stdout().isnt("73")
/// .unwrap();
/// ```
pub fn doesnt_contain<O: Into<String>>(self, output: O) -> Assert {
self.not().contains(output)
pub fn isnt<O: Into<Content>>(mut self, output: O) -> Assert {
let pred = OutputPredicate::new(self.kind, Output::isnt(output));
self.assertion.expect_output.push(pred);
self.assertion
}

/// Expect the command to output to not be **exactly** this `output`.
/// Expect the command output to satisfy the given predicate.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo", "42"])
/// .stdout().isnt("73")
/// assert_cli::Assert::command(&["echo", "-n", "42"])
/// .stdout().satisfies(|x| x.len() == 2, "bad length")
/// .unwrap();
/// ```
pub fn isnt<O: Into<String>>(self, output: O) -> Assert {
self.not().is(output)
pub fn satisfies<F, M>(mut self, pred: F, msg: M) -> Assert
where
F: 'static + Fn(&str) -> bool,
M: Into<String>,
{
let pred = OutputPredicate::new(self.kind, Output::satisfies(pred, msg));
self.assertion.expect_output.push(pred);
self.assertion
}
}

Expand All @@ -522,7 +514,11 @@ mod test {
fn take_ownership() {
let x = Environment::inherit();

command().with_env(x.clone()).with_env(&x).with_env(x);
command()
.with_env(x.clone())
.with_env(&x)
.with_env(x)
.unwrap();
}

#[test]
Expand Down Expand Up @@ -564,8 +560,7 @@ mod test {
command()
.with_env(y)
.stdout()
.not()
.contains("key=value")
.doesnt_contain("key=value")
.execute()
.unwrap();
}
Expand Down
28 changes: 13 additions & 15 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ fn format_cmd(cmd: &[OsString]) -> String {
}

error_chain! {
links {
Output(output::Error, output::ErrorKind);
}
foreign_links {
Io(::std::io::Error);
Fmt(::std::fmt::Error);
Expand All @@ -24,46 +27,41 @@ error_chain! {
format_cmd(cmd),
)
}
StatusMismatch(cmd: Vec<OsString>, expected: bool, out: String, err: String) {
description("Wrong status")
AssertionFailed(cmd: Vec<OsString>) {
description("Assertion failed")
display(
"{}: (command `{}` expected to {})\nstatus={}\nstdout=```{}```\nstderr=```{}```",
"{}: (command `{}` failed)",
ERROR_PREFIX,
format_cmd(cmd),
)
}
StatusMismatch(expected: bool, out: String, err: String) {
description("Wrong status")
display(
"Expected to {}\nstatus={}\nstdout=```{}```\nstderr=```{}```",
expected = if *expected { "succeed" } else { "fail" },
got = if *expected { "failed" } else { "succeeded" },
out = out,
err = err,
)
}
ExitCodeMismatch(
cmd: Vec<OsString>,
expected: Option<i32>,
got: Option<i32>,
out: String,
err: String
) {
description("Wrong exit code")
display(
"{prefix}: (exit code of `{cmd}` expected to be `{expected:?}`)\n\
"Expected exit code to be `{expected:?}`)\n\
exit code=`{code:?}`\n\
stdout=```{stdout}```\n\
stderr=```{stderr}```",
prefix=ERROR_PREFIX,
cmd=format_cmd(cmd),
expected=expected,
code=got,
stdout=out,
stderr=err,
)
}
OutputMismatch(cmd: Vec<OsString>, output_err: output::Error, kind: output::OutputKind) {
description("Output was not as expected")
display(
"{}: `{}` {:?} mismatch: {}",
ERROR_PREFIX, format_cmd(cmd), kind, output_err,
)
}

}
}
5 changes: 2 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,10 @@ mod errors;
mod macros;
pub use macros::flatten_escaped_string;

mod output;

mod assert;
mod diff;
mod output;

mod assert;
pub use assert::Assert;
pub use assert::OutputAssertionBuilder;
/// Environment is a re-export of the Environment crate
Expand Down
Loading