From 063b3ce0bfaad34f64f5be8c402a5361f075042d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 28 Oct 2017 09:11:35 -0600 Subject: [PATCH 1/2] fix(output): Re-work API to work with rustfmt rustfmt will split long builers across lines, even when that breaks logical grouping. For example ```rust assert_cli::Assert::command(&["ls", "foo-bar-foo"]) .fails() .and() .stderr().contains("foo-bar-foo") .unwrap(); ``` will be turned into ```rust assert_cli::Assert::command(&["ls", "foo-bar-foo"]) .fails() .and() .stderr() .contains("foo-bar-foo") .unwrap(); ``` which obscures intent. Normally, I don't like working around tools but this one seems sufficient to do so. ```rust assert_cli::Assert::command(&["ls", "foo-bar-foo"]) .fails() .and() .stderr(assert_cli::Output::contains("foo-bar-foo")) .unwrap(); ``` Pros - More consistent with `with_env` - Can add support for accepting arrays - Still auto-complete / docs friendly - Still expandable to additional assertions without much duplication or losing out on good error reporting Cons - More verbose if you don't `use assert_cli::{Assert, Environment, Output}` Alternatives - Accept distinct predicates - e.g. `.stderr(assert_cli::Is::text("foo-bar-foo"))` - e.g. `.stderr(assert_cli::Is::not("foo-bar-foo"))` - Strange `text` function - More structs to `use` - Less auto-complete / docs friendly (lacks contextual discovery or whatever the UX term is) Fixes #70 BREAKING CHANGE: `.stdout().contains(text)` is now `.stdout(assert_cli::Output::contains(text)`, etc. --- README.md | 6 +- src/assert.rs | 231 +++++++++++++------------------------------------ src/errors.rs | 28 +++--- src/lib.rs | 19 ++-- src/macros.rs | 2 +- src/output.rs | 205 ++++++++++++++++++++++++++++++++++++------- tests/cargo.rs | 12 +-- 7 files changed, 262 insertions(+), 241 deletions(-) diff --git a/README.md b/README.md index de618bf..c6cb292 100644 --- a/README.md +++ b/README.md @@ -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")) .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(); } ``` diff --git a/src/assert.rs b/src/assert.rs index 8d0ba0e..5451726 100644 --- a/src/assert.rs +++ b/src/assert.rs @@ -1,7 +1,7 @@ use environment::Environment; use error_chain::ChainedError; use errors::*; -use output::{OutputAssertion, OutputKind}; +use output::{Output, OutputPredicate}; use std::default; use std::ffi::{OsStr, OsString}; use std::io::Write; @@ -18,7 +18,7 @@ pub struct Assert { current_dir: Option, expect_success: Option, expect_exit_code: Option, - expect_output: Vec, + expect_output: Vec, stdin_contents: Option, } @@ -97,7 +97,7 @@ impl Assert { /// /// assert_cli::Assert::command(&["echo"]) /// .with_args(&["42"]) - /// .stdout().contains("42") + /// .stdout(assert_cli::Output::contains("42")) /// .unwrap(); /// /// ``` @@ -115,7 +115,7 @@ impl Assert { /// /// assert_cli::Assert::command(&["cat"]) /// .stdin("42") - /// .stdout().contains("42") + /// .stdout(assert_cli::Output::contains("42")) /// .unwrap(); /// ``` pub fn stdin(mut self, contents: &str) -> Self { @@ -132,7 +132,7 @@ impl Assert { /// /// assert_cli::Assert::command(&["wc", "lib.rs"]) /// .current_dir(std::path::Path::new("src")) - /// .stdout().contains("lib.rs") + /// .stdout(assert_cli::Output::contains("lib.rs")) /// .execute() /// .unwrap(); /// ``` @@ -150,7 +150,7 @@ impl Assert { /// /// assert_cli::Assert::command(&["printenv"]) /// .with_env(&[("TEST_ENV", "OK")]) - /// .stdout().contains("TEST_ENV=OK") + /// .stdout(assert_cli::Output::is("TEST_ENV=OK")) /// .execute() /// .unwrap(); /// @@ -159,14 +159,14 @@ impl Assert { /// /// assert_cli::Assert::command(&["printenv"]) /// .with_env(&env) - /// .stdout().is("FOO=BAR") + /// .stdout(assert_cli::Output::is("FOO=BAR")) /// .execute() /// .unwrap(); /// /// ::std::env::set_var("BAZ", "BAR"); /// /// assert_cli::Assert::command(&["printenv"]) - /// .stdout().contains("BAZ=BAR") + /// .stdout(assert_cli::Output::contains("BAZ=BAR")) /// .execute() /// .unwrap(); /// ``` @@ -186,7 +186,7 @@ impl Assert { /// assert_cli::Assert::command(&["cat", "non-existing-file"]) /// .fails() /// .and() - /// .stderr().contains("non-existing-file") + /// .stderr(assert_cli::Output::contains("non-existing-file")) /// .unwrap(); /// ``` pub fn and(self) -> Self { @@ -223,7 +223,7 @@ impl Assert { /// assert_cli::Assert::command(&["cat", "non-existing-file"]) /// .fails() /// .and() - /// .stderr().contains("non-existing-file") + /// .stderr(assert_cli::Output::contains("non-existing-file")) /// .unwrap(); /// ``` pub fn fails(mut self) -> Self { @@ -241,7 +241,7 @@ impl Assert { /// assert_cli::Assert::command(&["cat", "non-existing-file"]) /// .fails_with(1) /// .and() - /// .stderr().is("cat: non-existing-file: No such file or directory") + /// .stderr(assert_cli::Output::contains("non-existing-file")) /// .unwrap(); /// ``` pub fn fails_with(mut self, expect_exit_code: i32) -> Self { @@ -263,7 +263,7 @@ impl Assert { /// assert_cli::Assert::command(&["cat", "non-existing-file"]) /// .ignore_status() /// .and() - /// .stderr().is("cat: non-existing-file: No such file or directory") + /// .stderr(assert_cli::Output::contains("non-existing-file")) /// .unwrap(); /// ``` /// @@ -282,15 +282,12 @@ impl Assert { /// extern crate assert_cli; /// /// assert_cli::Assert::command(&["echo", "42"]) - /// .stdout().contains("42") + /// .stdout(assert_cli::Output::contains("42")) /// .unwrap(); /// ``` - pub fn stdout(self) -> OutputAssertionBuilder { - OutputAssertionBuilder { - assertion: self, - kind: OutputKind::StdOut, - expected_result: true, - } + pub fn stdout(mut self, pred: Output) -> Self { + self.expect_output.push(OutputPredicate::stdout(pred)); + self } /// Create an assertion for stdout's contents @@ -303,15 +300,12 @@ impl Assert { /// assert_cli::Assert::command(&["cat", "non-existing-file"]) /// .fails_with(1) /// .and() - /// .stderr().is("cat: non-existing-file: No such file or directory") + /// .stderr(assert_cli::Output::contains("non-existing-file")) /// .unwrap(); /// ``` - pub fn stderr(self) -> OutputAssertionBuilder { - OutputAssertionBuilder { - assertion: self, - kind: OutputKind::StdErr, - expected_result: true, - } + pub fn stderr(mut self, pred: Output) -> Self { + self.expect_output.push(OutputPredicate::stderr(pred)); + self } /// Execute the command and check the assertions. @@ -322,15 +316,15 @@ impl Assert { /// extern crate assert_cli; /// /// let test = assert_cli::Assert::command(&["echo", "42"]) - /// .stdout().contains("42") + /// .stdout(assert_cli::Output::contains("42")) /// .execute(); /// 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()) @@ -361,30 +355,26 @@ impl Assert { 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(&output) + .chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone())) + }) .collect::>>()?; Ok(()) @@ -408,107 +398,6 @@ impl Assert { } } -/// Assertions for command output. -#[derive(Debug)] -#[must_use] -pub struct OutputAssertionBuilder { - assertion: Assert, - kind: OutputKind, - expected_result: bool, -} - -impl OutputAssertionBuilder { - /// Negate the assertion predicate - /// - /// # Examples - /// - /// ```rust - /// extern crate assert_cli; - /// - /// assert_cli::Assert::command(&["echo", "42"]) - /// .stdout().not().contains("73") - /// .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 - } - - /// Expect the command's output to **contain** `output`. - /// - /// # Examples - /// - /// ```rust - /// extern crate assert_cli; - /// - /// assert_cli::Assert::command(&["echo", "42"]) - /// .stdout().contains("42") - /// .unwrap(); - /// ``` - pub fn contains>(mut self, output: O) -> Assert { - self.assertion.expect_output.push(OutputAssertion { - expect: output.into(), - fuzzy: true, - expected_result: self.expected_result, - kind: self.kind, - }); - self.assertion - } - - /// Expect the command to output **exactly** this `output`. - /// - /// # Examples - /// - /// ```rust - /// extern crate assert_cli; - /// - /// assert_cli::Assert::command(&["echo", "42"]) - /// .stdout().is("42") - /// .unwrap(); - /// ``` - pub fn is>(mut self, output: O) -> Assert { - self.assertion.expect_output.push(OutputAssertion { - expect: output.into(), - fuzzy: false, - expected_result: self.expected_result, - kind: self.kind, - }); - self.assertion - } - - /// Expect the command's output to not **contain** `output`. - /// - /// # Examples - /// - /// ```rust - /// extern crate assert_cli; - /// - /// assert_cli::Assert::command(&["echo", "42"]) - /// .stdout().doesnt_contain("73") - /// .unwrap(); - /// ``` - pub fn doesnt_contain>(self, output: O) -> Assert { - self.not().contains(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().isnt("73") - /// .unwrap(); - /// ``` - pub fn isnt>(self, output: O) -> Assert { - self.not().is(output) - } -} - #[cfg(test)] mod test { use super::*; @@ -522,7 +411,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] @@ -543,8 +436,7 @@ mod test { command() .with_env(&x.insert("key", "value").insert("key", "vv")) - .stdout() - .contains("key=vv") + .stdout(Output::contains("key=vv")) .execute() .unwrap(); // Granted, `insert` moved `x`, so we can no longer reference it, even @@ -563,9 +455,7 @@ mod test { command() .with_env(y) - .stdout() - .not() - .contains("key=value") + .stdout(Output::doesnt_contain("key=value")) .execute() .unwrap(); } @@ -575,7 +465,13 @@ mod test { // In-place modification while allowing later accesses to the `Environment` let y = Environment::empty(); - assert!(command().with_env(y).stdout().is("").execute().is_ok()); + assert!( + command() + .with_env(y) + .stdout(Output::is("")) + .execute() + .is_ok() + ); } #[test] fn take_vec() { @@ -583,22 +479,19 @@ mod test { command() .with_env(&vec![("bar", "baz")]) - .stdout() - .contains("bar=baz") + .stdout(Output::contains("bar=baz")) .execute() .unwrap(); command() .with_env(&v) - .stdout() - .contains("bar=baz") + .stdout(Output::contains("bar=baz")) .execute() .unwrap(); command() .with_env(&vec![("bar", "baz")]) - .stdout() - .isnt("") + .stdout(Output::isnt("")) .execute() .unwrap(); } @@ -607,22 +500,19 @@ mod test { fn take_slice_of_strs() { command() .with_env(&[("bar", "BAZ")]) - .stdout() - .contains("bar=BAZ") + .stdout(Output::contains("bar=BAZ")) .execute() .unwrap(); command() .with_env(&[("bar", "BAZ")][..]) - .stdout() - .contains("bar=BAZ") + .stdout(Output::contains("bar=BAZ")) .execute() .unwrap(); command() .with_env([("bar", "BAZ")].as_ref()) - .stdout() - .contains("bar=BAZ") + .stdout(Output::contains("bar=BAZ")) .execute() .unwrap(); } @@ -633,15 +523,13 @@ mod test { command() .with_env(&[("bar".to_string(), "BAZ".to_string())]) - .stdout() - .contains("bar=BAZ") + .stdout(Output::contains("bar=BAZ")) .execute() .unwrap(); command() .with_env(&[("bar".to_string(), "BAZ".to_string())][..]) - .stdout() - .contains("bar=BAZ") + .stdout(Output::contains("bar=BAZ")) .execute() .unwrap(); } @@ -650,15 +538,13 @@ mod test { fn take_slice() { command() .with_env(&[("hey", "ho")]) - .stdout() - .contains("hey=ho") + .stdout(Output::contains("hey=ho")) .execute() .unwrap(); command() .with_env(&[("hey", "ho".to_string())]) - .stdout() - .contains("hey=ho") + .stdout(Output::contains("hey=ho")) .execute() .unwrap(); } @@ -667,8 +553,7 @@ mod test { fn take_string_i32() { command() .with_env(&[("bar", 3 as i32)]) - .stdout() - .contains("bar=3") + .stdout(Output::contains("bar=3")) .execute() .unwrap(); } diff --git a/src/errors.rs b/src/errors.rs index d809f91..8f3172e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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,12 +27,18 @@ error_chain! { format_cmd(cmd), ) } - StatusMismatch(cmd: Vec, expected: bool, out: String, err: String) { - description("Wrong status") + AssertionFailed(cmd: Vec) { + 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, @@ -37,7 +46,6 @@ error_chain! { ) } ExitCodeMismatch( - cmd: Vec, expected: Option, got: Option, out: String, @@ -45,25 +53,15 @@ error_chain! { ) { 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, output_err: output::Error, kind: output::OutputKind) { - description("Output was not as expected") - display( - "{}: `{}` {:?} mismatch: {}", - ERROR_PREFIX, format_cmd(cmd), kind, output_err, - ) - } - } } diff --git a/src/lib.rs b/src/lib.rs index 8d56f1d..007822a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ //! //! ```rust //! assert_cli::Assert::command(&["echo", "42"]) -//! .stdout().contains("42") +//! .stdout(assert_cli::Output::contains("42")) //! .unwrap(); //! ``` //! @@ -26,7 +26,7 @@ //! //! ```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; diff --git a/src/macros.rs b/src/macros.rs index 9b5350d..386eae6 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -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(); /// # } /// ``` diff --git a/src/output.rs b/src/output.rs index 207cec6..daf568d 100644 --- a/src/output.rs +++ b/src/output.rs @@ -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)?; + 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>(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>(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>(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>(output: O) -> Self { + let pred = IsPredicate { + expect: output.into(), + expected_result: false, + }; + Self::new(StrPredicate::Is(pred)) + } + + fn new(pred: StrPredicate) -> Self { + Self { pred } + } + + pub(crate) fn verify_str(&self, got: &str) -> Result<()> { + self.pred.verify_str(got) } } @@ -73,7 +175,7 @@ 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, @@ -81,6 +183,40 @@ impl OutputKind { } } +#[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 + ) + } } } } diff --git a/tests/cargo.rs b/tests/cargo.rs index f476ee1..a7f185a 100644 --- a/tests/cargo.rs +++ b/tests/cargo.rs @@ -4,10 +4,8 @@ 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(); } @@ -15,9 +13,7 @@ fn main_binary() { 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(); } From 491d6482c5b034ed0ec80988247b1f6c13ba2009 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sun, 29 Oct 2017 12:30:22 +0100 Subject: [PATCH 2/2] Free fns for Output constructors and add prelude Originally based on #74 --- README.md | 8 ++-- src/assert.rs | 105 ++++++++++++++++++++++++++++--------------------- src/lib.rs | 14 +++++++ src/output.rs | 43 +++++++++++--------- tests/cargo.rs | 17 ++++---- 5 files changed, 112 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index c6cb292..679dbd9 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,13 @@ And here is one that will fail (and demonstrates running arbitrary commands): ```rust extern crate assert_cli; +use assert_cli::prelude::*; fn main() { - assert_cli::Assert::command(&["ls", "foo-bar-foo"]) + Assert::command(&["ls", "foo-bar-foo"]) .fails() .and() - .stderr(assert_cli::Output::contains("foo-bar-foo")) + .stderr(contains("foo-bar-foo")) .unwrap(); } ``` @@ -47,10 +48,11 @@ If you want to match the program's output _exactly_, you can use ```rust,should_panic #[macro_use] extern crate assert_cli; +use assert_cli::prelude::*; fn main() { assert_cmd!(wc "README.md") - .stdout(assert_cli::Output::is("1337 README.md")) + .stdout(is("1337 README.md")) .unwrap(); } ``` diff --git a/src/assert.rs b/src/assert.rs index 5451726..95e2205 100644 --- a/src/assert.rs +++ b/src/assert.rs @@ -77,8 +77,9 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["echo", "1337"]) + /// Assert::command(&["echo", "1337"]) /// .unwrap(); /// ``` pub fn command>(cmd: &[S]) -> Self { @@ -94,12 +95,12 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["echo"]) + /// Assert::command(&["echo"]) /// .with_args(&["42"]) - /// .stdout(assert_cli::Output::contains("42")) + /// .stdout(contains("42")) /// .unwrap(); - /// /// ``` pub fn with_args>(mut self, args: &[S]) -> Self { self.cmd.extend(args.into_iter().map(OsString::from)); @@ -112,10 +113,11 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["cat"]) + /// Assert::command(&["cat"]) /// .stdin("42") - /// .stdout(assert_cli::Output::contains("42")) + /// .stdout(contains("42")) /// .unwrap(); /// ``` pub fn stdin(mut self, contents: &str) -> Self { @@ -129,10 +131,11 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["wc", "lib.rs"]) + /// Assert::command(&["wc", "lib.rs"]) /// .current_dir(std::path::Path::new("src")) - /// .stdout(assert_cli::Output::contains("lib.rs")) + /// .stdout(contains("lib.rs")) /// .execute() /// .unwrap(); /// ``` @@ -147,26 +150,28 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["printenv"]) + /// Assert::command(&["printenv"]) /// .with_env(&[("TEST_ENV", "OK")]) - /// .stdout(assert_cli::Output::is("TEST_ENV=OK")) + /// .stdout(is("TEST_ENV=OK")) /// .execute() /// .unwrap(); /// /// let env = assert_cli::Environment::empty() /// .insert("FOO", "BAR"); /// - /// assert_cli::Assert::command(&["printenv"]) + /// Assert::command(&["printenv"]) /// .with_env(&env) - /// .stdout(assert_cli::Output::is("FOO=BAR")) + /// .stdout(is("FOO=BAR")) /// .execute() /// .unwrap(); /// /// ::std::env::set_var("BAZ", "BAR"); /// - /// assert_cli::Assert::command(&["printenv"]) - /// .stdout(assert_cli::Output::contains("BAZ=BAR")) + /// Assert::command(&["printenv"]) + /// .stdout(contains("BAZ=BAR")) /// .execute() /// .unwrap(); /// ``` @@ -182,11 +187,12 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["cat", "non-existing-file"]) + /// Assert::command(&["cat", "non-existing-file"]) /// .fails() /// .and() - /// .stderr(assert_cli::Output::contains("non-existing-file")) + /// .stderr(contains("non-existing-file")) /// .unwrap(); /// ``` pub fn and(self) -> Self { @@ -199,8 +205,9 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["echo", "42"]) + /// Assert::command(&["echo", "42"]) /// .succeeds() /// .unwrap(); /// ``` @@ -219,11 +226,12 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["cat", "non-existing-file"]) + /// Assert::command(&["cat", "non-existing-file"]) /// .fails() /// .and() - /// .stderr(assert_cli::Output::contains("non-existing-file")) + /// .stderr(contains("non-existing-file")) /// .unwrap(); /// ``` pub fn fails(mut self) -> Self { @@ -237,11 +245,12 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["cat", "non-existing-file"]) + /// Assert::command(&["cat", "non-existing-file"]) /// .fails_with(1) /// .and() - /// .stderr(assert_cli::Output::contains("non-existing-file")) + /// .stderr(contains("non-existing-file")) /// .unwrap(); /// ``` pub fn fails_with(mut self, expect_exit_code: i32) -> Self { @@ -259,11 +268,12 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["cat", "non-existing-file"]) + /// Assert::command(&["cat", "non-existing-file"]) /// .ignore_status() /// .and() - /// .stderr(assert_cli::Output::contains("non-existing-file")) + /// .stderr(contains("non-existing-file")) /// .unwrap(); /// ``` /// @@ -280,9 +290,10 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["echo", "42"]) - /// .stdout(assert_cli::Output::contains("42")) + /// Assert::command(&["echo", "42"]) + /// .stdout(contains("42")) /// .unwrap(); /// ``` pub fn stdout(mut self, pred: Output) -> Self { @@ -296,11 +307,12 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["cat", "non-existing-file"]) + /// Assert::command(&["cat", "non-existing-file"]) /// .fails_with(1) /// .and() - /// .stderr(assert_cli::Output::contains("non-existing-file")) + /// .stderr(contains("non-existing-file")) /// .unwrap(); /// ``` pub fn stderr(mut self, pred: Output) -> Self { @@ -314,9 +326,10 @@ impl Assert { /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// let test = assert_cli::Assert::command(&["echo", "42"]) - /// .stdout(assert_cli::Output::contains("42")) + /// let test = Assert::command(&["echo", "42"]) + /// .stdout(contains("42")) /// .execute(); /// assert!(test.is_ok()); /// ``` @@ -386,8 +399,9 @@ impl Assert { /// /// ```rust,should_panic="Assert CLI failure" /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["echo", "42"]) + /// Assert::command(&["echo", "42"]) /// .fails() /// .unwrap(); // panics /// ``` @@ -400,8 +414,9 @@ impl Assert { #[cfg(test)] mod test { - use super::*; use std::ffi::OsString; + use super::*; + use output::predicates::*; fn command() -> Assert { Assert::command(&["printenv"]) @@ -436,7 +451,7 @@ mod test { command() .with_env(&x.insert("key", "value").insert("key", "vv")) - .stdout(Output::contains("key=vv")) + .stdout(contains("key=vv")) .execute() .unwrap(); // Granted, `insert` moved `x`, so we can no longer reference it, even @@ -455,7 +470,7 @@ mod test { command() .with_env(y) - .stdout(Output::doesnt_contain("key=value")) + .stdout(doesnt_contain("key=value")) .execute() .unwrap(); } @@ -468,7 +483,7 @@ mod test { assert!( command() .with_env(y) - .stdout(Output::is("")) + .stdout(is("")) .execute() .is_ok() ); @@ -479,19 +494,19 @@ mod test { command() .with_env(&vec![("bar", "baz")]) - .stdout(Output::contains("bar=baz")) + .stdout(contains("bar=baz")) .execute() .unwrap(); command() .with_env(&v) - .stdout(Output::contains("bar=baz")) + .stdout(contains("bar=baz")) .execute() .unwrap(); command() .with_env(&vec![("bar", "baz")]) - .stdout(Output::isnt("")) + .stdout(isnt("")) .execute() .unwrap(); } @@ -500,19 +515,19 @@ mod test { fn take_slice_of_strs() { command() .with_env(&[("bar", "BAZ")]) - .stdout(Output::contains("bar=BAZ")) + .stdout(contains("bar=BAZ")) .execute() .unwrap(); command() .with_env(&[("bar", "BAZ")][..]) - .stdout(Output::contains("bar=BAZ")) + .stdout(contains("bar=BAZ")) .execute() .unwrap(); command() .with_env([("bar", "BAZ")].as_ref()) - .stdout(Output::contains("bar=BAZ")) + .stdout(contains("bar=BAZ")) .execute() .unwrap(); } @@ -523,13 +538,13 @@ mod test { command() .with_env(&[("bar".to_string(), "BAZ".to_string())]) - .stdout(Output::contains("bar=BAZ")) + .stdout(contains("bar=BAZ")) .execute() .unwrap(); command() .with_env(&[("bar".to_string(), "BAZ".to_string())][..]) - .stdout(Output::contains("bar=BAZ")) + .stdout(contains("bar=BAZ")) .execute() .unwrap(); } @@ -538,13 +553,13 @@ mod test { fn take_slice() { command() .with_env(&[("hey", "ho")]) - .stdout(Output::contains("hey=ho")) + .stdout(contains("hey=ho")) .execute() .unwrap(); command() .with_env(&[("hey", "ho".to_string())]) - .stdout(Output::contains("hey=ho")) + .stdout(contains("hey=ho")) .execute() .unwrap(); } @@ -553,7 +568,7 @@ mod test { fn take_string_i32() { command() .with_env(&[("bar", 3 as i32)]) - .stdout(Output::contains("bar=3")) + .stdout(contains("bar=3")) .execute() .unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index 007822a..34fdcf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,3 +139,17 @@ pub use assert::Assert; /// It allow you to define/override environment variables for one or more assertions. pub use environment::Environment; pub use output::Output; + +/// Convenience module to get all the good stuff +/// +/// Glob import this module like this to quickly get all the important parts of +/// this crate: +/// +/// ```rust +/// use assert_cli::prelude::*; +/// ``` +pub mod prelude { + pub use assert::Assert; + pub use environment::Environment; + pub use output::predicates::*; +} diff --git a/src/output.rs b/src/output.rs index daf568d..1d18f1a 100644 --- a/src/output.rs +++ b/src/output.rs @@ -4,7 +4,6 @@ use diff; use difference::Changeset; use std::process; - #[derive(Debug, Clone, PartialEq, Eq)] struct IsPredicate { pub expect: String, @@ -79,24 +78,38 @@ pub struct Output { } impl Output { + fn new(pred: StrPredicate) -> Self { + Self { pred } + } + + pub(crate) fn verify_str(&self, got: &str) -> Result<()> { + self.pred.verify_str(got) + } +} + +/// Predicate helpers to match against outputs +pub mod predicates { + use super::*; + /// Expect the command's output to **contain** `output`. /// /// # Examples /// /// ```rust /// extern crate assert_cli; + /// use assert_cli::prelude::*; /// - /// assert_cli::Assert::command(&["echo"]) + /// Assert::command(&["echo"]) /// .with_args(&["42"]) - /// .stdout(assert_cli::Output::contains("42")) + /// .stdout(contains("42")) /// .unwrap(); /// ``` - pub fn contains>(output: O) -> Self { + pub fn contains>(output: O) -> Output { let pred = ContainsPredicate { expect: output.into(), expected_result: true, }; - Self::new(StrPredicate::Contains(pred)) + Output::new(StrPredicate::Contains(pred)) } /// Expect the command to output **exactly** this `output`. @@ -111,12 +124,12 @@ impl Output { /// .stdout(assert_cli::Output::is("42")) /// .unwrap(); /// ``` - pub fn is>(output: O) -> Self { + pub fn is>(output: O) -> Output { let pred = IsPredicate { expect: output.into(), expected_result: true, }; - Self::new(StrPredicate::Is(pred)) + Output::new(StrPredicate::Is(pred)) } /// Expect the command's output to not **contain** `output`. @@ -131,12 +144,12 @@ impl Output { /// .stdout(assert_cli::Output::doesnt_contain("73")) /// .unwrap(); /// ``` - pub fn doesnt_contain>(output: O) -> Self { + pub fn doesnt_contain>(output: O) -> Output { let pred = ContainsPredicate { expect: output.into(), expected_result: false, }; - Self::new(StrPredicate::Contains(pred)) + Output::new(StrPredicate::Contains(pred)) } /// Expect the command to output to not be **exactly** this `output`. @@ -151,20 +164,12 @@ impl Output { /// .stdout(assert_cli::Output::isnt("73")) /// .unwrap(); /// ``` - pub fn isnt>(output: O) -> Self { + pub fn isnt>(output: O) -> Output { let pred = IsPredicate { expect: output.into(), expected_result: false, }; - Self::new(StrPredicate::Is(pred)) - } - - fn new(pred: StrPredicate) -> Self { - Self { pred } - } - - pub(crate) fn verify_str(&self, got: &str) -> Result<()> { - self.pred.verify_str(got) + Output::new(StrPredicate::Is(pred)) } } diff --git a/tests/cargo.rs b/tests/cargo.rs index a7f185a..dc64e7a 100644 --- a/tests/cargo.rs +++ b/tests/cargo.rs @@ -1,19 +1,20 @@ extern crate assert_cli; +use assert_cli::prelude::*; #[test] fn main_binary() { - assert_cli::Assert::main_binary() - .with_env(assert_cli::Environment::inherit().insert("stdout", "42")) - .stdout(assert_cli::Output::is("42")) - .stderr(assert_cli::Output::is("")) + Assert::main_binary() + .with_env(Environment::inherit().insert("stdout", "42")) + .stdout(is("42")) + .stderr(is("")) .unwrap(); } #[test] fn cargo_binary() { - assert_cli::Assert::cargo_binary("assert_fixture") - .with_env(assert_cli::Environment::inherit().insert("stdout", "42")) - .stdout(assert_cli::Output::is("42")) - .stderr(assert_cli::Output::is("")) + Assert::cargo_binary("assert_fixture") + .with_env(Environment::inherit().insert("stdout", "42")) + .stdout(is("42")) + .stderr(is("")) .unwrap(); }