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

fix(output): Re-work API to work with rustfmt #74

Closed
wants to merge 1 commit into from
Closed
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -37,20 +37,20 @@ fn main() {
assert_cli::Assert::command(&["ls", "foo-bar-foo"])
.fails()
.and()
.stderr().contains("foo-bar-foo")
.stderr(assert_cli::Output::contains("foo-bar-foo"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably use assert_cli::{Assert, Output}; in most examples.

.unwrap();
}
```

If you want to match the program's output _exactly_, you can use
`stdout().is` (and shows the macro form of `command`):
`Output::is` (and shows the macro form of `command`):

```rust,should_panic
#[macro_use] extern crate assert_cli;

fn main() {
assert_cmd!(wc "README.md")
.stdout().is("1337 README.md")
.stdout(assert_cli::Output::is("1337 README.md"))
.unwrap();
}
```
231 changes: 58 additions & 173 deletions src/assert.rs

Large diffs are not rendered by default.

28 changes: 13 additions & 15 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -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);
@@ -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,
)
}

}
}
19 changes: 9 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -18,15 +18,15 @@
//!
//! ```rust
//! assert_cli::Assert::command(&["echo", "42"])
//! .stdout().contains("42")
//! .stdout(assert_cli::Output::contains("42"))
//! .unwrap();
//! ```
//!
//! And here is one that will fail:
//!
//! ```rust,should_panic
//! assert_cli::Assert::command(&["echo", "42"])
//! .stdout().is("1337")
//! .stdout(assert_cli::Output::is("1337"))
//! .unwrap();
//! ```
//!
@@ -45,7 +45,7 @@
//! ```rust
//! # #[macro_use] extern crate assert_cli;
//! # fn main() {
//! assert_cmd!(echo "42").stdout().contains("42").unwrap();
//! assert_cmd!(echo "42").stdout(assert_cli::Output::contains("42")).unwrap();
//! # }
//! ```
//!
@@ -88,9 +88,9 @@
//! # #[macro_use] extern crate assert_cli;
//! # fn main() {
//! assert_cmd!(echo "Hello world! The ansswer is 42.")
//! .stdout().contains("Hello world")
//! .stdout().contains("42")
//! .stderr().is("")
//! .stdout(assert_cli::Output::contains("Hello world"))
//! .stdout(assert_cli::Output::contains("42"))
//! .stderr(assert_cli::Output::is(""))
//! .unwrap();
//! # }
//! ```
@@ -110,7 +110,7 @@
//! ```rust
//! # #[macro_use] extern crate assert_cli;
//! # fn main() {
//! let x = assert_cmd!(echo "1337").stdout().is("42").execute();
//! let x = assert_cmd!(echo "1337").stdout(assert_cli::Output::is("42")).execute();
//! assert!(x.is_err());
//! # }
//! ```
@@ -130,13 +130,12 @@ mod macros;
pub use macros::flatten_escaped_string;

mod output;

mod diff;

mod assert;

pub use assert::Assert;
pub use assert::OutputAssertionBuilder;
/// Environment is a re-export of the Environment crate
///
/// It allow you to define/override environment variables for one or more assertions.
pub use environment::Environment;
pub use output::Output;
2 changes: 1 addition & 1 deletion src/macros.rs
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ use std::borrow::Cow;
/// # fn main() {
/// assert_cmd!(echo "Launch sequence initiated.\nNo errors whatsoever!\n")
/// .succeeds()
/// .stdout().contains("No errors whatsoever")
/// .stdout(assert_cli::Output::contains("No errors whatsoever"))
/// .unwrap();
/// # }
/// ```
205 changes: 174 additions & 31 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -2,67 +2,169 @@ use self::errors::*;
pub use self::errors::{Error, ErrorKind};
use diff;
use difference::Changeset;
use std::ffi::OsString;
use std::process::Output;
use std::process;

#[derive(Debug, Clone)]
pub struct OutputAssertion {

#[derive(Debug, Clone, PartialEq, Eq)]
struct IsPredicate {
pub expect: String,
pub fuzzy: bool,
pub expected_result: bool,
pub kind: OutputKind,
}

impl OutputAssertion {
fn matches_fuzzy(&self, got: &str) -> Result<()> {
let result = got.contains(&self.expect);
impl IsPredicate {
pub fn verify_str(&self, got: &str) -> Result<()> {
let differences = Changeset::new(self.expect.trim(), got.trim(), "\n");
let result = differences.distance == 0;

if result != self.expected_result {
if self.expected_result {
bail!(ErrorKind::OutputDoesntContain(
let nice_diff = diff::render(&differences)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to early-return this error? I think I'd .unwrap_or_else(|e| format!("(unable to render nice diff, error was: {:?})", e)) instead of ? here and give the user both the interesting error as well as the diff-error in one go

bail!(ErrorKind::OutputDoesntMatch(
self.expect.clone(),
got.into()
got.to_owned(),
nice_diff
));
} else {
bail!(ErrorKind::OutputContains(self.expect.clone(), got.into()));
bail!(ErrorKind::OutputMatches(got.to_owned()));
}
}

Ok(())
}
}

fn matches_exact(&self, got: &str) -> Result<()> {
let differences = Changeset::new(self.expect.trim(), got.trim(), "\n");
let result = differences.distance == 0;
#[derive(Debug, Clone, PartialEq, Eq)]
struct ContainsPredicate {
pub expect: String,
pub expected_result: bool,
}

impl ContainsPredicate {
pub fn verify_str(&self, got: &str) -> Result<()> {
let result = got.contains(&self.expect);
if result != self.expected_result {
if self.expected_result {
let nice_diff = diff::render(&differences)?;
bail!(ErrorKind::OutputDoesntMatch(
bail!(ErrorKind::OutputDoesntContain(
self.expect.clone(),
got.to_owned(),
nice_diff
got.into()
));
} else {
bail!(ErrorKind::OutputMatches(got.to_owned()));
bail!(ErrorKind::OutputContains(self.expect.clone(), got.into()));
}
}

Ok(())
}
}

#[derive(Debug, Clone)]
enum StrPredicate {
Is(IsPredicate),
Contains(ContainsPredicate),
}

impl StrPredicate {
pub fn verify_str(&self, got: &str) -> Result<()> {
match *self {
StrPredicate::Is(ref pred) => pred.verify_str(got),
StrPredicate::Contains(ref pred) => pred.verify_str(got),
}
}
}

pub fn execute(&self, output: &Output, cmd: &[OsString]) -> super::errors::Result<()> {
let observed = String::from_utf8_lossy(self.kind.select(output));
/// Assertions for command output.
#[derive(Debug, Clone)]
pub struct Output {
pred: StrPredicate,
}

let result = if self.fuzzy {
self.matches_fuzzy(&observed)
} else {
self.matches_exact(&observed)
impl Output {
/// Expect the command's output to **contain** `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo"])
/// .with_args(&["42"])
/// .stdout(assert_cli::Output::contains("42"))
/// .unwrap();
/// ```
pub fn contains<O: Into<String>>(output: O) -> Self {
let pred = ContainsPredicate {
expect: output.into(),
expected_result: true,
};
result.map_err(|e| {
super::errors::ErrorKind::OutputMismatch(cmd.to_vec(), e, self.kind)
})?;
Self::new(StrPredicate::Contains(pred))
}

Ok(())
/// Expect the command to output **exactly** this `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo"])
/// .with_args(&["42"])
/// .stdout(assert_cli::Output::is("42"))
/// .unwrap();
/// ```
pub fn is<O: Into<String>>(output: O) -> Self {
let pred = IsPredicate {
expect: output.into(),
expected_result: true,
};
Self::new(StrPredicate::Is(pred))
}

/// Expect the command's output to not **contain** `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo"])
/// .with_args(&["42"])
/// .stdout(assert_cli::Output::doesnt_contain("73"))
/// .unwrap();
/// ```
pub fn doesnt_contain<O: Into<String>>(output: O) -> Self {
let pred = ContainsPredicate {
expect: output.into(),
expected_result: false,
};
Self::new(StrPredicate::Contains(pred))
}

/// Expect the command to output to not be **exactly** this `output`.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["echo"])
/// .with_args(&["42"])
/// .stdout(assert_cli::Output::isnt("73"))
/// .unwrap();
/// ```
pub fn isnt<O: Into<String>>(output: O) -> Self {
let pred = IsPredicate {
expect: output.into(),
expected_result: false,
};
Self::new(StrPredicate::Is(pred))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading for of these I fell the need to write a macro for this… which would probably not make the code much clearer 😅


fn new(pred: StrPredicate) -> Self {
Self { pred }
}

pub(crate) fn verify_str(&self, got: &str) -> Result<()> {
self.pred.verify_str(got)
}
}

@@ -73,14 +175,48 @@ pub enum OutputKind {
}

impl OutputKind {
pub fn select(self, o: &Output) -> &[u8] {
pub fn select(self, o: &process::Output) -> &[u8] {
match self {
OutputKind::StdOut => &o.stdout,
OutputKind::StdErr => &o.stderr,
}
}
}

#[derive(Debug, Clone)]
pub struct OutputPredicate {
kind: OutputKind,
pred: Output,
}

impl OutputPredicate {
pub fn stdout(pred: Output) -> Self {
Self {
kind: OutputKind::StdOut,
pred: pred,
}
}

pub fn stderr(pred: Output) -> Self {
Self {
kind: OutputKind::StdErr,
pred: pred,
}
}

pub(crate) fn verify_str(&self, got: &str) -> Result<()> {
let kind = self.kind;
self.pred
.verify_str(got)
.chain_err(|| ErrorKind::OutputMismatch(kind))
}

pub(crate) fn verify_output(&self, got: &process::Output) -> Result<()> {
let got = String::from_utf8_lossy(self.kind.select(got));
self.verify_str(&got)
}
}

mod errors {
error_chain! {
foreign_links {
@@ -103,6 +239,13 @@ mod errors {
description("Output was not as expected")
display("expected to not match\noutput=```{}```", got)
}
OutputMismatch(kind: super::OutputKind) {
description("Output was not as expected")
display(
"Unexpected {:?}",
kind
)
}
}
}
}
12 changes: 4 additions & 8 deletions tests/cargo.rs
Original file line number Diff line number Diff line change
@@ -4,20 +4,16 @@ extern crate assert_cli;
fn main_binary() {
assert_cli::Assert::main_binary()
.with_env(assert_cli::Environment::inherit().insert("stdout", "42"))
.stdout()
.is("42")
.stderr()
.is("")
.stdout(assert_cli::Output::is("42"))
.stderr(assert_cli::Output::is(""))
.unwrap();
}

#[test]
fn cargo_binary() {
assert_cli::Assert::cargo_binary("assert_fixture")
.with_env(assert_cli::Environment::inherit().insert("stdout", "42"))
.stdout()
.is("42")
.stderr()
.is("")
.stdout(assert_cli::Output::is("42"))
.stderr(assert_cli::Output::is(""))
.unwrap();
}