From f7a7f64100fb439cef67e58c450725d260f5fa50 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 28 Oct 2017 09:11:35 -0600 Subject: [PATCH 1/5] refactor(output): Make implementation more expanable. Move from a `bool` to track the type of output predicate to a stateful enum. --- src/assert.rs | 83 +++++++-------------- src/errors.rs | 28 ++++--- src/lib.rs | 5 +- src/output.rs | 198 ++++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 207 insertions(+), 107 deletions(-) diff --git a/src/assert.rs b/src/assert.rs index 8d0ba0e..604440f 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, OutputKind, 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, } @@ -289,7 +289,6 @@ impl Assert { OutputAssertionBuilder { assertion: self, kind: OutputKind::StdOut, - expected_result: true, } } @@ -310,7 +309,6 @@ impl Assert { OutputAssertionBuilder { assertion: self, kind: OutputKind::StdErr, - expected_result: true, } } @@ -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()) @@ -361,30 +359,23 @@ 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(()) @@ -414,28 +405,9 @@ impl Assert { 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 @@ -448,12 +420,8 @@ impl OutputAssertionBuilder { /// .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, - }); + let pred = OutputPredicate::new(self.kind, Output::contains(output)); + self.assertion.expect_output.push(pred); self.assertion } @@ -469,12 +437,8 @@ impl OutputAssertionBuilder { /// .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, - }); + let pred = OutputPredicate::new(self.kind, Output::is(output)); + self.assertion.expect_output.push(pred); self.assertion } @@ -489,8 +453,10 @@ impl OutputAssertionBuilder { /// .stdout().doesnt_contain("73") /// .unwrap(); /// ``` - pub fn doesnt_contain>(self, output: O) -> Assert { - self.not().contains(output) + pub fn doesnt_contain>(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 to output to not be **exactly** this `output`. @@ -504,8 +470,10 @@ impl OutputAssertionBuilder { /// .stdout().isnt("73") /// .unwrap(); /// ``` - pub fn isnt>(self, output: O) -> Assert { - self.not().is(output) + pub fn isnt>(mut self, output: O) -> Assert { + let pred = OutputPredicate::new(self.kind, Output::isnt(output)); + self.assertion.expect_output.push(pred); + self.assertion } } @@ -522,7 +490,7 @@ 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] @@ -564,8 +532,7 @@ mod test { command() .with_env(y) .stdout() - .not() - .contains("key=value") + .doesnt_contain("key=value") .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..12def0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/output.rs b/src/output.rs index 207cec6..8f2ce9f 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(()) } +} - pub fn execute(&self, output: &Output, cmd: &[OsString]) -> super::errors::Result<()> { - let observed = String::from_utf8_lossy(self.kind.select(output)); +#[derive(Debug, Clone)] +enum StrPredicate { + Is(IsPredicate), + Contains(ContainsPredicate), +} - let result = if self.fuzzy { - self.matches_fuzzy(&observed) - } else { - self.matches_exact(&observed) +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), + } + } +} + +/// Assertions for command output. +#[derive(Debug, Clone)] +pub struct Output { + pred: StrPredicate, +} + +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().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().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().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().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,33 @@ impl OutputKind { } } +#[derive(Debug, Clone)] +pub struct OutputPredicate { + kind: OutputKind, + pred: Output, +} + +impl OutputPredicate { + pub fn new(kind: OutputKind, pred: Output) -> Self { + Self { + kind, + 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 +232,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 + ) + } } } } From abe260634138a43820507443509345ab24950891 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 3 Feb 2018 10:18:51 -0700 Subject: [PATCH 2/5] feat(output): Generic predicate support This accepts a message with it. This should hit the 90% case of a `satisfies_ok` (or whatever it'd be called). I'm also assuming that it'll be a best practice to document the custom predicates, so its acceptable to force it on everyone. If a `satisfies_ok` is found to be needed, I'm assuming its because the user wants to tie into existing machinery that has error reporting. This means we'll probably need to accept an `Fn` that `Box`es the error to preserve it. Fixes #55 --- src/assert.rs | 20 +++++++++++++++++ src/output.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/assert.rs b/src/assert.rs index 604440f..1892ab5 100644 --- a/src/assert.rs +++ b/src/assert.rs @@ -475,6 +475,26 @@ impl OutputAssertionBuilder { self.assertion.expect_output.push(pred); self.assertion } + + /// Expect the command output to satisfy the given predicate. + /// + /// # Examples + /// + /// ```rust + /// extern crate assert_cli; + /// + /// assert_cli::Assert::command(&["echo", "-n", "42"]) + /// .stdout().satisfies(|x| x.len() == 2, "bad length") + /// .unwrap(); + /// ``` + pub fn satisfies(mut self, pred: F, msg: M) -> Assert + where F: 'static + Fn(&str) -> bool, + M: Into + { + let pred = OutputPredicate::new(self.kind, Output::satisfies(pred, msg)); + self.assertion.expect_output.push(pred); + self.assertion + } } #[cfg(test)] diff --git a/src/output.rs b/src/output.rs index 8f2ce9f..f692f92 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,8 +1,12 @@ +use std::fmt; +use std::process; +use std::rc; + +use difference::Changeset; + +use diff; use self::errors::*; pub use self::errors::{Error, ErrorKind}; -use diff; -use difference::Changeset; -use std::process; #[derive(Debug, Clone, PartialEq, Eq)] @@ -57,10 +61,34 @@ impl ContainsPredicate { } } +#[derive(Clone)] +struct FnPredicate { + pub pred: rc::Rc bool>, + pub msg: String, +} + +impl FnPredicate { + pub fn verify_str(&self, got: &str) -> Result<()> { + let pred = &self.pred; + if ! pred(got) { + bail!(ErrorKind::PredicateFailed(got.into(), self.msg.clone())); + } + + Ok(()) + } +} + +impl fmt::Debug for FnPredicate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.msg) + } +} + #[derive(Debug, Clone)] enum StrPredicate { Is(IsPredicate), Contains(ContainsPredicate), + Fn(FnPredicate), } impl StrPredicate { @@ -68,6 +96,7 @@ impl StrPredicate { match *self { StrPredicate::Is(ref pred) => pred.verify_str(got), StrPredicate::Contains(ref pred) => pred.verify_str(got), + StrPredicate::Fn(ref pred) => pred.verify_str(got), } } } @@ -159,6 +188,28 @@ impl Output { Self::new(StrPredicate::Is(pred)) } + /// Expect the command output to satisfy the given predicate. + /// + /// # Examples + /// + /// ```rust + /// extern crate assert_cli; + /// + /// assert_cli::Assert::command(&["echo", "-n", "42"]) + /// .stdout().satisfies(|x| x.len() == 2, "bad length") + /// .unwrap(); + /// ``` + pub fn satisfies(pred: F, msg: M) -> Self + where F: 'static + Fn(&str) -> bool, + M: Into + { + let pred = FnPredicate { + pred: rc::Rc::new(pred), + msg: msg.into(), + }; + Self::new(StrPredicate::Fn(pred)) + } + fn new(pred: StrPredicate) -> Self { Self { pred } } @@ -232,6 +283,10 @@ mod errors { description("Output was not as expected") display("expected to not match\noutput=```{}```", got) } + PredicateFailed(got: String, msg: String) { + description("Output predicate failed") + display("{}\noutput=```{}```", msg, got) + } OutputMismatch(kind: super::OutputKind) { description("Output was not as expected") display( From 67d2f6db3801c3219c5490af8d2e862ece9bcf8e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 3 Feb 2018 10:30:48 -0700 Subject: [PATCH 3/5] feat(stdin): Support binary data `.stdin()` can now accept anything convertable to bytes, including `str`, `OsStr`, and `[u8]`. --- src/assert.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assert.rs b/src/assert.rs index 1892ab5..a669e38 100644 --- a/src/assert.rs +++ b/src/assert.rs @@ -19,7 +19,7 @@ pub struct Assert { expect_success: Option, expect_exit_code: Option, expect_output: Vec, - stdin_contents: Option, + stdin_contents: Option>, } impl default::Default for Assert { @@ -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>>(mut self, contents: S) -> Self { + self.stdin_contents = Some(contents.into()); self } @@ -351,7 +351,7 @@ 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()?; From 230ecac49a6d0a8006fe2fc58a156bd03f4cd9a9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 3 Feb 2018 12:58:19 -0700 Subject: [PATCH 4/5] feat(output): Support byte predicates If you pass a `&str` into the predicates, the programs output is assumed to be UTF-8 and will be converted accordingly. If you pass `&[u8]` into the predicates, the program output is treated as raw binary. Fixes #80 --- src/assert.rs | 20 +++--- src/output.rs | 194 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 157 insertions(+), 57 deletions(-) diff --git a/src/assert.rs b/src/assert.rs index a669e38..e8a6285 100644 --- a/src/assert.rs +++ b/src/assert.rs @@ -1,7 +1,3 @@ -use environment::Environment; -use error_chain::ChainedError; -use errors::*; -use output::{Output, OutputKind, OutputPredicate}; use std::default; use std::ffi::{OsStr, OsString}; use std::io::Write; @@ -9,6 +5,12 @@ use std::path::PathBuf; use std::process::{Command, Stdio}; use std::vec::Vec; +use environment::Environment; +use error_chain::ChainedError; + +use errors::*; +use output::{Content, Output, OutputKind, OutputPredicate}; + /// Assertions for a specific command. #[derive(Debug)] #[must_use] @@ -375,7 +377,7 @@ impl Assert { self.expect_output .iter() - .map(|a| a.verify_output(&output).chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone()))) + .map(|a| a.verify(&output).chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone()))) .collect::>>()?; Ok(()) @@ -419,7 +421,7 @@ impl OutputAssertionBuilder { /// .stdout().contains("42") /// .unwrap(); /// ``` - pub fn contains>(mut self, output: O) -> Assert { + pub fn contains>(mut self, output: O) -> Assert { let pred = OutputPredicate::new(self.kind, Output::contains(output)); self.assertion.expect_output.push(pred); self.assertion @@ -436,7 +438,7 @@ impl OutputAssertionBuilder { /// .stdout().is("42") /// .unwrap(); /// ``` - pub fn is>(mut self, output: O) -> Assert { + pub fn is>(mut self, output: O) -> Assert { let pred = OutputPredicate::new(self.kind, Output::is(output)); self.assertion.expect_output.push(pred); self.assertion @@ -453,7 +455,7 @@ impl OutputAssertionBuilder { /// .stdout().doesnt_contain("73") /// .unwrap(); /// ``` - pub fn doesnt_contain>(mut self, output: O) -> Assert { + pub fn doesnt_contain>(mut self, output: O) -> Assert { let pred = OutputPredicate::new(self.kind, Output::doesnt_contain(output)); self.assertion.expect_output.push(pred); self.assertion @@ -470,7 +472,7 @@ impl OutputAssertionBuilder { /// .stdout().isnt("73") /// .unwrap(); /// ``` - pub fn isnt>(mut self, output: O) -> Assert { + pub fn isnt>(mut self, output: O) -> Assert { let pred = OutputPredicate::new(self.kind, Output::isnt(output)); self.assertion.expect_output.push(pred); self.assertion diff --git a/src/output.rs b/src/output.rs index f692f92..b57ef13 100644 --- a/src/output.rs +++ b/src/output.rs @@ -9,27 +9,80 @@ use self::errors::*; pub use self::errors::{Error, ErrorKind}; +#[derive(Clone, PartialEq, Eq)] +pub enum Content { + Str(String), + Bytes(Vec), +} + +impl fmt::Debug for Content { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Content::Str(ref data) => write!(f, "{}", data), + Content::Bytes(ref data) => write!(f, "{:?}", data), + } + } +} + +impl<'a> From<&'a str> for Content +{ + fn from(data: &'a str) -> Self { + Content::Str(data.into()) + } +} + +impl<'a> From<&'a [u8]> for Content +{ + fn from(data: &'a [u8]) -> Self { + Content::Bytes(data.into()) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] struct IsPredicate { - pub expect: String, + pub expect: Content, pub expected_result: bool, } impl IsPredicate { - pub fn verify_str(&self, got: &str) -> Result<()> { - let differences = Changeset::new(self.expect.trim(), got.trim(), "\n"); + pub fn verify(&self, got: &[u8]) -> Result<()> { + match self.expect { + Content::Str(ref expect) => self.verify_str(expect, String::from_utf8_lossy(got).as_ref()), + Content::Bytes(ref expect) => self.verify_bytes(expect, got), + } + } + + fn verify_bytes(&self, expect: &[u8], got: &[u8]) -> Result<()> { + let result = expect == got; + + if result != self.expected_result { + if self.expected_result { + bail!(ErrorKind::BytesDoesntMatch( + expect.to_owned(), + got.to_owned(), + )); + } else { + bail!(ErrorKind::BytesMatches(got.to_owned())); + } + } + + Ok(()) + } + + fn verify_str(&self, expect: &str, got: &str) -> Result<()> { + let differences = Changeset::new(expect.trim(), got.trim(), "\n"); let result = differences.distance == 0; if result != self.expected_result { if self.expected_result { let nice_diff = diff::render(&differences)?; - bail!(ErrorKind::OutputDoesntMatch( - self.expect.clone(), + bail!(ErrorKind::StrDoesntMatch( + expect.to_owned(), got.to_owned(), nice_diff )); } else { - bail!(ErrorKind::OutputMatches(got.to_owned())); + bail!(ErrorKind::StrMatches(got.to_owned())); } } @@ -39,21 +92,54 @@ impl IsPredicate { #[derive(Debug, Clone, PartialEq, Eq)] struct ContainsPredicate { - pub expect: String, + pub expect: Content, pub expected_result: bool, } +fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option { + haystack.windows(needle.len()).position(|window| window == needle) +} + +#[test] +fn test_find_subsequence() { + assert_eq!(find_subsequence(b"qwertyuiop", b"tyu"), Some(4)); + assert_eq!(find_subsequence(b"qwertyuiop", b"asd"), None); +} + impl ContainsPredicate { - pub fn verify_str(&self, got: &str) -> Result<()> { - let result = got.contains(&self.expect); + pub fn verify(&self, got: &[u8]) -> Result<()> { + match self.expect { + Content::Str(ref expect) => self.verify_str(expect, String::from_utf8_lossy(got).as_ref()), + Content::Bytes(ref expect) => self.verify_bytes(expect, got), + } + } + + pub fn verify_bytes(&self, expect: &[u8], got: &[u8]) -> Result<()> { + let result = find_subsequence(got, expect).is_some(); + if result != self.expected_result { + if self.expected_result { + bail!(ErrorKind::BytesDoesntContain( + expect.to_owned(), + got.to_owned() + )); + } else { + bail!(ErrorKind::BytesContains(expect.to_owned(), got.to_owned())); + } + } + + Ok(()) + } + + pub fn verify_str(&self, expect: &str, got: &str) -> Result<()> { + let result = got.contains(expect); if result != self.expected_result { if self.expected_result { - bail!(ErrorKind::OutputDoesntContain( - self.expect.clone(), - got.into() + bail!(ErrorKind::StrDoesntContain( + expect.to_owned(), + got.to_owned() )); } else { - bail!(ErrorKind::OutputContains(self.expect.clone(), got.into())); + bail!(ErrorKind::StrContains(expect.to_owned(), got.to_owned())); } } @@ -68,10 +154,11 @@ struct FnPredicate { } impl FnPredicate { - pub fn verify_str(&self, got: &str) -> Result<()> { + pub fn verify(&self, got: &[u8]) -> Result<()> { + let got = String::from_utf8_lossy(got); let pred = &self.pred; - if ! pred(got) { - bail!(ErrorKind::PredicateFailed(got.into(), self.msg.clone())); + if ! pred(&got) { + bail!(ErrorKind::PredicateFailed(got.into_owned(), self.msg.clone())); } Ok(()) @@ -85,18 +172,18 @@ impl fmt::Debug for FnPredicate { } #[derive(Debug, Clone)] -enum StrPredicate { +enum ContentPredicate { Is(IsPredicate), Contains(ContainsPredicate), Fn(FnPredicate), } -impl StrPredicate { - pub fn verify_str(&self, got: &str) -> Result<()> { +impl ContentPredicate { + pub fn verify(&self, got: &[u8]) -> Result<()> { match *self { - StrPredicate::Is(ref pred) => pred.verify_str(got), - StrPredicate::Contains(ref pred) => pred.verify_str(got), - StrPredicate::Fn(ref pred) => pred.verify_str(got), + ContentPredicate::Is(ref pred) => pred.verify(got), + ContentPredicate::Contains(ref pred) => pred.verify(got), + ContentPredicate::Fn(ref pred) => pred.verify(got), } } } @@ -104,7 +191,7 @@ impl StrPredicate { /// Assertions for command output. #[derive(Debug, Clone)] pub struct Output { - pred: StrPredicate, + pred: ContentPredicate, } impl Output { @@ -120,12 +207,12 @@ impl Output { /// .stdout().contains("42") /// .unwrap(); /// ``` - pub fn contains>(output: O) -> Self { + pub fn contains>(output: O) -> Self { let pred = ContainsPredicate { expect: output.into(), expected_result: true, }; - Self::new(StrPredicate::Contains(pred)) + Self::new(ContentPredicate::Contains(pred)) } /// Expect the command to output **exactly** this `output`. @@ -140,12 +227,12 @@ impl Output { /// .stdout().is("42") /// .unwrap(); /// ``` - pub fn is>(output: O) -> Self { + pub fn is>(output: O) -> Self { let pred = IsPredicate { expect: output.into(), expected_result: true, }; - Self::new(StrPredicate::Is(pred)) + Self::new(ContentPredicate::Is(pred)) } /// Expect the command's output to not **contain** `output`. @@ -160,12 +247,12 @@ impl Output { /// .stdout().doesnt_contain("73") /// .unwrap(); /// ``` - pub fn doesnt_contain>(output: O) -> Self { + pub fn doesnt_contain>(output: O) -> Self { let pred = ContainsPredicate { expect: output.into(), expected_result: false, }; - Self::new(StrPredicate::Contains(pred)) + Self::new(ContentPredicate::Contains(pred)) } /// Expect the command to output to not be **exactly** this `output`. @@ -180,12 +267,12 @@ impl Output { /// .stdout().isnt("73") /// .unwrap(); /// ``` - pub fn isnt>(output: O) -> Self { + pub fn isnt>(output: O) -> Self { let pred = IsPredicate { expect: output.into(), expected_result: false, }; - Self::new(StrPredicate::Is(pred)) + Self::new(ContentPredicate::Is(pred)) } /// Expect the command output to satisfy the given predicate. @@ -207,15 +294,15 @@ impl Output { pred: rc::Rc::new(pred), msg: msg.into(), }; - Self::new(StrPredicate::Fn(pred)) + Self::new(ContentPredicate::Fn(pred)) } - fn new(pred: StrPredicate) -> Self { + fn new(pred: ContentPredicate) -> Self { Self { pred } } - pub(crate) fn verify_str(&self, got: &str) -> Result<()> { - self.pred.verify_str(got) + pub(crate) fn verify(&self, got: &[u8]) -> Result<()> { + self.pred.verify(got) } } @@ -248,16 +335,11 @@ impl OutputPredicate { } } - pub(crate) fn verify_str(&self, got: &str) -> Result<()> { - let kind = self.kind; + pub(crate) fn verify(&self, got: &process::Output) -> Result<()> { + let got = self.kind.select(got); 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) + .verify(got) + .chain_err(|| ErrorKind::OutputMismatch(self.kind)) } } @@ -267,22 +349,38 @@ mod errors { Fmt(::std::fmt::Error); } errors { - OutputDoesntContain(expected: String, got: String) { + StrDoesntContain(expected: String, got: String) { description("Output was not as expected") display("expected to contain {:?}\noutput=```{}```", expected, got) } - OutputContains(expected: String, got: String) { + BytesDoesntContain(expected: Vec, got: Vec) { + description("Output was not as expected") + display("expected to contain {:?}\noutput=```{:?}```", expected, got) + } + StrContains(expected: String, got: String) { description("Output was not as expected") display("expected to not contain {:?}\noutput=```{}```", expected, got) } - OutputDoesntMatch(expected: String, got: String, diff: String) { + BytesContains(expected: Vec, got: Vec) { + description("Output was not as expected") + display("expected to not contain {:?}\noutput=```{:?}```", expected, got) + } + StrDoesntMatch(expected: String, got: String, diff: String) { description("Output was not as expected") display("diff:\n{}", diff) } - OutputMatches(got: String) { + BytesDoesntMatch(expected: Vec, got: Vec) { + description("Output was not as expected") + display("expected=```{:?}```\noutput=```{:?}```", expected, got) + } + StrMatches(got: String) { description("Output was not as expected") display("expected to not match\noutput=```{}```", got) } + BytesMatches(got: Vec) { + description("Output was not as expected") + display("expected to not match\noutput=```{:?}```", got) + } PredicateFailed(got: String, msg: String) { description("Output predicate failed") display("{}\noutput=```{}```", msg, got) From f0145fb40409294bf8167ddc21ba590f972002c8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 3 Feb 2018 20:11:54 -0700 Subject: [PATCH 5/5] style: Adjust for rustfmt --- src/assert.rs | 26 ++++++++++++++++---------- src/output.rs | 45 +++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/assert.rs b/src/assert.rs index e8a6285..bccc54e 100644 --- a/src/assert.rs +++ b/src/assert.rs @@ -1,3 +1,7 @@ +use environment::Environment; +use error_chain::ChainedError; +use errors::*; +use output::{Content, Output, OutputKind, OutputPredicate}; use std::default; use std::ffi::{OsStr, OsString}; use std::io::Write; @@ -5,12 +9,6 @@ use std::path::PathBuf; use std::process::{Command, Stdio}; use std::vec::Vec; -use environment::Environment; -use error_chain::ChainedError; - -use errors::*; -use output::{Content, Output, OutputKind, OutputPredicate}; - /// Assertions for a specific command. #[derive(Debug)] #[must_use] @@ -377,7 +375,10 @@ impl Assert { self.expect_output .iter() - .map(|a| a.verify(&output).chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone()))) + .map(|a| { + a.verify(&output) + .chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone())) + }) .collect::>>()?; Ok(()) @@ -490,8 +491,9 @@ impl OutputAssertionBuilder { /// .unwrap(); /// ``` pub fn satisfies(mut self, pred: F, msg: M) -> Assert - where F: 'static + Fn(&str) -> bool, - M: Into + where + F: 'static + Fn(&str) -> bool, + M: Into, { let pred = OutputPredicate::new(self.kind, Output::satisfies(pred, msg)); self.assertion.expect_output.push(pred); @@ -512,7 +514,11 @@ mod test { fn take_ownership() { let x = Environment::inherit(); - command().with_env(x.clone()).with_env(&x).with_env(x).unwrap(); + command() + .with_env(x.clone()) + .with_env(&x) + .with_env(x) + .unwrap(); } #[test] diff --git a/src/output.rs b/src/output.rs index b57ef13..3395f94 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,13 +1,11 @@ +use self::errors::*; +pub use self::errors::{Error, ErrorKind}; +use diff; +use difference::Changeset; use std::fmt; use std::process; use std::rc; -use difference::Changeset; - -use diff; -use self::errors::*; -pub use self::errors::{Error, ErrorKind}; - #[derive(Clone, PartialEq, Eq)] pub enum Content { @@ -24,15 +22,13 @@ impl fmt::Debug for Content { } } -impl<'a> From<&'a str> for Content -{ +impl<'a> From<&'a str> for Content { fn from(data: &'a str) -> Self { Content::Str(data.into()) } } -impl<'a> From<&'a [u8]> for Content -{ +impl<'a> From<&'a [u8]> for Content { fn from(data: &'a [u8]) -> Self { Content::Bytes(data.into()) } @@ -47,7 +43,9 @@ struct IsPredicate { impl IsPredicate { pub fn verify(&self, got: &[u8]) -> Result<()> { match self.expect { - Content::Str(ref expect) => self.verify_str(expect, String::from_utf8_lossy(got).as_ref()), + Content::Str(ref expect) => { + self.verify_str(expect, String::from_utf8_lossy(got).as_ref()) + } Content::Bytes(ref expect) => self.verify_bytes(expect, got), } } @@ -97,7 +95,9 @@ struct ContainsPredicate { } fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option { - haystack.windows(needle.len()).position(|window| window == needle) + haystack + .windows(needle.len()) + .position(|window| window == needle) } #[test] @@ -109,7 +109,9 @@ fn test_find_subsequence() { impl ContainsPredicate { pub fn verify(&self, got: &[u8]) -> Result<()> { match self.expect { - Content::Str(ref expect) => self.verify_str(expect, String::from_utf8_lossy(got).as_ref()), + Content::Str(ref expect) => { + self.verify_str(expect, String::from_utf8_lossy(got).as_ref()) + } Content::Bytes(ref expect) => self.verify_bytes(expect, got), } } @@ -157,8 +159,9 @@ impl FnPredicate { pub fn verify(&self, got: &[u8]) -> Result<()> { let got = String::from_utf8_lossy(got); let pred = &self.pred; - if ! pred(&got) { - bail!(ErrorKind::PredicateFailed(got.into_owned(), self.msg.clone())); + if !pred(&got) { + let err: Error = ErrorKind::PredicateFailed(got.into_owned(), self.msg.clone()).into(); + bail!(err); } Ok(()) @@ -175,7 +178,7 @@ impl fmt::Debug for FnPredicate { enum ContentPredicate { Is(IsPredicate), Contains(ContainsPredicate), - Fn(FnPredicate), + Fn(FnPredicate), } impl ContentPredicate { @@ -287,8 +290,9 @@ impl Output { /// .unwrap(); /// ``` pub fn satisfies(pred: F, msg: M) -> Self - where F: 'static + Fn(&str) -> bool, - M: Into + where + F: 'static + Fn(&str) -> bool, + M: Into, { let pred = FnPredicate { pred: rc::Rc::new(pred), @@ -329,10 +333,7 @@ pub struct OutputPredicate { impl OutputPredicate { pub fn new(kind: OutputKind, pred: Output) -> Self { - Self { - kind, - pred, - } + Self { kind, pred } } pub(crate) fn verify(&self, got: &process::Output) -> Result<()> {