diff --git a/src/diff.rs b/src/diff.rs index 32ba178..4ec1eb9 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -2,11 +2,9 @@ extern crate colored; use self::colored::Colorize; use difference::{Difference, Changeset}; -use std::fmt::Write; +use std::fmt::{Write, Error as fmtError}; -use errors::*; - -pub fn render(&Changeset { ref diffs, .. }: &Changeset) -> Result { +pub fn render(&Changeset { ref diffs, .. }: &Changeset) -> Result { let mut t = String::new(); for (i, diff) in diffs.iter().enumerate() { @@ -66,11 +64,11 @@ mod tests { sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor **incididunt** ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", - "\n"); + "\n"); println!("{}", render(&diff).unwrap()); assert_eq!(render(&diff).unwrap(), " Lorem ipsum dolor sit amet, consectetur adipisicing elit,\n\u{1b}[31m-sed do eiusmod tempor incididunt ut labore et dolore magna\u{1b}[0m\n\u{1b}[32m+\u{1b}[0m\u{1b}[32msed do eiusmod tempor\u{1b}[0m \u{1b}[7;32m**incididunt**\u{1b}[0m \u{1b}[32mut labore et dolore magna\u{1b}[0m \n aliqua. Ut enim ad minim veniam, quis nostrud exercitation\nullamco laboris nisi ut aliquip ex ea commodo consequat.\n"); } diff --git a/src/errors.rs b/src/errors.rs index e4b7ae8..3499e18 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -9,7 +9,7 @@ error_chain! { StatusMismatch(cmd: Vec, expected: bool) { description("Wrong status") display( - "{}: `(command `{}` expected to {})` (command {})", + "{}: (command `{}` expected to {}) (command {})", ERROR_PREFIX, cmd.join(" "), expected = if *expected { "succeed" } else { "fail" }, @@ -19,33 +19,27 @@ error_chain! { ExitCodeMismatch(cmd: Vec, expected: Option, got: Option) { description("Wrong exit code") display( - "{}: `(exit code of `{}` expected to be `{:?}`)` (exit code was: `{:?}`)", + "{}: (exit code of `{}` expected to be `{:?}`) (exit code was: `{:?}`)", ERROR_PREFIX, cmd.join(" "), expected, got, ) } - OutputMismatch(output_name: String, cmd: Vec, expected: String, got: String) { + StdoutMismatch(cmd: Vec, output_err: ::output::Error) { description("Output was not as expected") display( - "{}: `({} of `{}` expected to contain `{:?}`)` (output was: `{:?}`)", - ERROR_PREFIX, - output_name, - cmd.join(" "), - expected, - got, + "{}: `{}` stdout mismatch: `{}`)", + ERROR_PREFIX, cmd.join(" "), output_err, ) } - ExactOutputMismatch(output_name: String, cmd: Vec, diff: String) { - description("Output was not as expected") + StderrMismatch(cmd: Vec, output_err: ::output::Error) { + description("Error output was not as expected") display( - "{}: `({} of `{}` was not as expected)`\n{}\n", - ERROR_PREFIX, - output_name, - cmd.join(" "), - diff.trim() + "{}: `{}` stderr mismatch: `{}`)", + ERROR_PREFIX, cmd.join(" "), output_err, ) } + } } diff --git a/src/lib.rs b/src/lib.rs index 80e28bc..6e97fc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,10 +108,7 @@ extern crate difference; #[macro_use] extern crate error_chain; extern crate rustc_serialize; -use std::process::{Command, Output}; -use std::fmt; - -use difference::Changeset; +use std::process::Command; mod errors; use errors::*; @@ -119,6 +116,9 @@ use errors::*; #[macro_use] mod macros; pub use macros::flatten_escaped_string; +mod output; +use output::{OutputAssertion, StdErr, StdOut}; + mod diff; /// Assertions for a specific command. @@ -127,38 +127,8 @@ pub struct Assert { cmd: Vec, expect_success: Option, expect_exit_code: Option, - expect_stdout: Option, - expect_stderr: Option, -} - -#[derive(Debug)] -struct OutputAssertion { - expect: String, - fuzzy: bool, -} - -#[derive(Debug, Copy, Clone)] -enum OutputType { - StdOut, - StdErr, -} - -impl OutputType { - fn select<'a>(&self, o: &'a Output) -> &'a [u8] { - match *self { - OutputType::StdOut => &o.stdout, - OutputType::StdErr => &o.stderr, - } - } -} - -impl fmt::Display for OutputType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - OutputType::StdOut => write!(f, "stdout"), - OutputType::StdErr => write!(f, "stderr"), - } - } + expect_stdout: Option>, + expect_stderr: Option>, } impl std::default::Default for Assert { @@ -318,6 +288,7 @@ impl Assert { self.expect_stdout = Some(OutputAssertion { expect: output.into(), fuzzy: true, + kind: StdOut, }); self } @@ -337,6 +308,7 @@ impl Assert { self.expect_stdout = Some(OutputAssertion { expect: output.into(), fuzzy: false, + kind: StdOut, }); self } @@ -358,6 +330,7 @@ impl Assert { self.expect_stderr = Some(OutputAssertion { expect: output.into(), fuzzy: true, + kind: StdErr, }); self } @@ -379,6 +352,7 @@ impl Assert { self.expect_stderr = Some(OutputAssertion { expect: output.into(), fuzzy: false, + kind: StdErr, }); self } @@ -421,52 +395,17 @@ impl Assert { )); } - self.assert_output(OutputType::StdOut, &output)?; - self.assert_output(OutputType::StdErr, &output)?; - - Ok(()) - } - - /// Perform the appropriate output assertion. - fn assert_output(&self, output_type: OutputType, output: &Output) -> Result<()> { - let observed = String::from_utf8_lossy(output_type.select(output)); - match *self.expect_output(output_type) { - Some(OutputAssertion { - expect: ref expected_output, - fuzzy: true, - }) if !observed.contains(expected_output) => { - bail!(ErrorKind::OutputMismatch( - output_type.to_string(), - self.cmd.clone(), - expected_output.clone(), - observed.into(), - )); - }, - Some(OutputAssertion { - expect: ref expected_output, - fuzzy: false, - }) => { - let differences = Changeset::new(expected_output.trim(), observed.trim(), "\n"); - if differences.distance > 0 { - let nice_diff = diff::render(&differences)?; - bail!(ErrorKind::ExactOutputMismatch( - output_type.to_string(), - self.cmd.clone(), - nice_diff - )); - } - }, - _ => {}, + if let Some(ref ouput_assertion) = self.expect_stdout { + ouput_assertion.execute(&output) + .map_err(|e| ErrorKind::StdoutMismatch(self.cmd.clone(), e))?; } - Ok(()) - } - /// Return a reference to the appropriate output assertion. - fn expect_output(&self, output_type: OutputType) -> &Option { - match output_type { - OutputType::StdOut => &self.expect_stdout, - OutputType::StdErr => &self.expect_stderr, + if let Some(ref ouput_assertion) = self.expect_stderr { + ouput_assertion.execute(&output) + .map_err(|e| ErrorKind::StderrMismatch(self.cmd.clone(), e))?; } + + Ok(()) } /// Execute the command, check the assertions, and panic when they fail. diff --git a/src/output.rs b/src/output.rs new file mode 100644 index 0000000..2dc9586 --- /dev/null +++ b/src/output.rs @@ -0,0 +1,102 @@ +use std::fmt; +use std::process::Output; + +use difference::Changeset; + +use self::errors::*; +pub use self::errors::{Error, ErrorKind}; +use diff; + +#[derive(Debug, Clone)] +pub struct OutputAssertion { + pub expect: String, + pub fuzzy: bool, + pub kind: T, +} + +impl OutputAssertion { + fn matches_fuzzy(&self, got: &str) -> Result<()> { + if !got.contains(&self.expect) { + bail!(ErrorKind::OutputMismatch(self.expect.clone(), got.into())); + } + + Ok(()) + } + + fn matches_exact(&self, got: &str) -> Result<()> { + let differences = Changeset::new(self.expect.trim(), got.trim(), "\n"); + + if differences.distance > 0 { + let nice_diff = diff::render(&differences)?; + bail!(ErrorKind::ExactOutputMismatch(nice_diff)); + } + + Ok(()) + } + + pub fn execute(&self, output: &Output) -> Result<()> { + let observed = String::from_utf8_lossy(self.kind.select(output)); + + if self.fuzzy { + self.matches_fuzzy(&observed) + } else { + self.matches_exact(&observed) + } + } +} + + +pub trait OutputType: fmt::Display { + fn select<'a>(&self, o: &'a Output) -> &'a [u8]; +} + + +#[derive(Debug, Clone, Copy)] +pub struct StdOut; + +impl fmt::Display for StdOut { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "stdout") + } +} + +impl OutputType for StdOut { + fn select<'a>(&self, o: &'a Output) -> &'a [u8] { + &o.stdout + } +} + + +#[derive(Debug, Clone, Copy)] +pub struct StdErr; + +impl fmt::Display for StdErr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "stderr") + } +} + +impl OutputType for StdErr { + fn select<'a>(&self, o: &'a Output) -> &'a [u8] { + &o.stderr + } +} + +mod errors { + error_chain! { + foreign_links { + // Io(::std::io::Error); + Fmt(::std::fmt::Error); + } + errors { + OutputMismatch(expected: String, got: String) { + description("Output was not as expected") + display("expected {:?}, got {:?}", expected, got) + } + ExactOutputMismatch(diff: String) { + description("Output was not as expected") + display("{}", diff) + } + } + } +}