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

Refactor output assertions #24

Merged
merged 2 commits into from
Sep 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
pub fn render(&Changeset { ref diffs, .. }: &Changeset) -> Result<String, fmtError> {
let mut t = String::new();

for (i, diff) in diffs.iter().enumerate() {
Expand Down Expand Up @@ -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");
}
Expand Down
26 changes: 10 additions & 16 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ error_chain! {
StatusMismatch(cmd: Vec<String>, expected: bool) {
description("Wrong status")
display(
"{}: `(command `{}` expected to {})` (command {})",
"{}: (command `{}` expected to {}) (command {})",
ERROR_PREFIX,
cmd.join(" "),
expected = if *expected { "succeed" } else { "fail" },
Expand All @@ -19,33 +19,27 @@ error_chain! {
ExitCodeMismatch(cmd: Vec<String>, expected: Option<i32>, got: Option<i32>) {
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<String>, expected: String, got: String) {
StdoutMismatch(cmd: Vec<String>, 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<String>, diff: String) {
description("Output was not as expected")
StderrMismatch(cmd: Vec<String>, 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,
)
}

}
}
97 changes: 18 additions & 79 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,17 @@ 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::*;

#[macro_use] mod macros;
pub use macros::flatten_escaped_string;

mod output;
use output::{OutputAssertion, StdErr, StdOut};

mod diff;

/// Assertions for a specific command.
Expand All @@ -127,38 +127,8 @@ pub struct Assert {
cmd: Vec<String>,
expect_success: Option<bool>,
expect_exit_code: Option<i32>,
expect_stdout: Option<OutputAssertion>,
expect_stderr: Option<OutputAssertion>,
}

#[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<OutputAssertion<StdOut>>,
expect_stderr: Option<OutputAssertion<StdErr>>,
}

impl std::default::Default for Assert {
Expand Down Expand Up @@ -318,6 +288,7 @@ impl Assert {
self.expect_stdout = Some(OutputAssertion {
expect: output.into(),
fuzzy: true,
kind: StdOut,
});
self
}
Expand All @@ -337,6 +308,7 @@ impl Assert {
self.expect_stdout = Some(OutputAssertion {
expect: output.into(),
fuzzy: false,
kind: StdOut,
});
self
}
Expand All @@ -358,6 +330,7 @@ impl Assert {
self.expect_stderr = Some(OutputAssertion {
expect: output.into(),
fuzzy: true,
kind: StdErr,
});
self
}
Expand All @@ -379,6 +352,7 @@ impl Assert {
self.expect_stderr = Some(OutputAssertion {
expect: output.into(),
fuzzy: false,
kind: StdErr,
});
self
}
Expand Down Expand Up @@ -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<OutputAssertion> {
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.
Expand Down
102 changes: 102 additions & 0 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
pub expect: String,
pub fuzzy: bool,
pub kind: T,
}

impl<T: OutputType> OutputAssertion<T> {
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)
}
}
}
}