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

Commit 7ad76af

Browse files
committed
refactor: Migrate from Context to Fail
This replaces adding a `Context` per piece of information to bundling the information up into `Fail`s. The individual `Fail`s are chained using `Context` / `Error` rather than writing my own chaining logic. The information is now reported in a more sane order. Example: ``` Assertion failed for 'wc README.md' with: Unexpected failure return status stdout='''''' stderr='''wc: README.md: No such file or directory ''' ```
1 parent 02e986c commit 7ad76af

File tree

4 files changed

+300
-231
lines changed

4 files changed

+300
-231
lines changed

src/assert.rs

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::vec::Vec;
77

88
use environment::Environment;
99
use failure;
10+
use failure::Fail;
1011
use failure::ResultExt;
1112

1213
use errors::*;
@@ -345,7 +346,9 @@ impl Assert {
345346
None => command,
346347
};
347348

348-
let mut spawned = command.spawn().context(AssertionKind::Spawn)?;
349+
let mut spawned = command
350+
.spawn()
351+
.with_context(|_| SpawnError::new(self.cmd.clone()))?;
349352

350353
if let Some(ref contents) = self.stdin_contents {
351354
spawned
@@ -357,43 +360,30 @@ impl Assert {
357360
let output = spawned.wait_with_output()?;
358361

359362
if let Some(expect_success) = self.expect_success {
360-
if expect_success != output.status.success() {
361-
let out = String::from_utf8_lossy(&output.stdout).to_string();
362-
let err = String::from_utf8_lossy(&output.stderr).to_string();
363-
Err(AssertionError::new(AssertionKind::StatusMismatch))
364-
.context(if expect_success {
365-
"expected to succeed"
366-
} else {
367-
"expected to fail"
368-
})
369-
.context(KeyValueDisplay::new("stdout", QuotedDisplay::new(out)))
370-
.context(KeyValueDisplay::new("stderr", QuotedDisplay::new(err)))
371-
.with_context(|_| KeyValueDisplay::new("command", format!("{:?}", command)))?;
363+
let actual_success = output.status.success();
364+
if expect_success != actual_success {
365+
Err(failure::Context::new(StatusError::new(
366+
actual_success,
367+
output.stdout.clone(),
368+
output.stderr.clone(),
369+
)).context(AssertionError::new(self.cmd.clone())))?;
372370
}
373371
}
374372

375373
if self.expect_exit_code.is_some() && self.expect_exit_code != output.status.code() {
376-
let out = String::from_utf8_lossy(&output.stdout).to_string();
377-
let err = String::from_utf8_lossy(&output.stderr).to_string();
378-
Err(AssertionError::new(AssertionKind::ExitCodeMismatch))
379-
.context(KeyValueDisplay::new(
380-
"expected",
381-
self.expect_exit_code.expect("is_some already called"),
382-
))
383-
.context(KeyValueDisplay::new(
384-
"got",
385-
output.status.code().unwrap_or_default(),
386-
))
387-
.context(KeyValueDisplay::new("stdout", QuotedDisplay::new(out)))
388-
.context(KeyValueDisplay::new("stderr", QuotedDisplay::new(err)))
389-
.with_context(|_| KeyValueDisplay::new("command", format!("{:?}", command)))?;
374+
Err(failure::Context::new(ExitCodeError::new(
375+
self.expect_exit_code,
376+
output.status.code(),
377+
output.stdout.clone(),
378+
output.stderr.clone(),
379+
)).context(AssertionError::new(self.cmd.clone())))?;
390380
}
391381

392382
self.expect_output
393383
.iter()
394384
.map(|a| {
395385
a.verify(&output)
396-
.with_context(|_| KeyValueDisplay::new("command", format!("{:?}", command)))
386+
.with_context(|_| AssertionError::new(self.cmd.clone()))
397387
.map_err(|e| failure::Error::from(e))
398388
})
399389
.collect::<Result<Vec<()>>>()?;

src/errors.rs

Lines changed: 74 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,113 @@
1+
use std::ffi;
12
use std::fmt;
23
use std::result;
34

45
use failure;
56

67
pub type Result<T> = result::Result<T, failure::Error>;
78

8-
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
9-
pub enum AssertionKind {
10-
#[fail(display = "Spawn failed.")] Spawn,
11-
#[fail(display = "Status mismatch.")] StatusMismatch,
12-
#[fail(display = "Exit code mismatch.")] ExitCodeMismatch,
13-
#[fail(display = "Output mismatch.")] OutputMismatch,
9+
fn format_cmd(cmd: &[ffi::OsString]) -> String {
10+
let result: Vec<String> = cmd.iter()
11+
.map(|s| s.to_string_lossy().into_owned())
12+
.collect();
13+
result.join(" ")
1414
}
1515

16-
#[derive(Debug)]
17-
pub struct AssertionError {
18-
inner: failure::Context<AssertionKind>,
16+
#[derive(Fail, Debug)]
17+
pub struct SpawnError {
18+
cmd: Vec<ffi::OsString>,
1919
}
2020

21-
impl AssertionError {
22-
pub fn new(kind: AssertionKind) -> Self {
23-
Self { inner: kind.into() }
24-
}
25-
26-
pub fn kind(&self) -> AssertionKind {
27-
*self.inner.get_context()
28-
}
29-
}
30-
31-
impl failure::Fail for AssertionError {
32-
fn cause(&self) -> Option<&failure::Fail> {
33-
self.inner.cause()
34-
}
35-
36-
fn backtrace(&self) -> Option<&failure::Backtrace> {
37-
self.inner.backtrace()
21+
impl SpawnError {
22+
pub fn new(cmd: Vec<ffi::OsString>) -> Self {
23+
Self { cmd }
3824
}
3925
}
4026

41-
impl fmt::Display for AssertionError {
27+
impl fmt::Display for SpawnError {
4228
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43-
writeln!(f, "CLI Assertion Error: {}", self.inner)
44-
}
45-
}
46-
47-
impl From<AssertionKind> for AssertionError {
48-
fn from(kind: AssertionKind) -> AssertionError {
49-
AssertionError {
50-
inner: failure::Context::new(kind),
51-
}
29+
write!(f, "Failed to run `{}`", format_cmd(&self.cmd))
5230
}
5331
}
5432

55-
impl From<failure::Context<AssertionKind>> for AssertionError {
56-
fn from(inner: failure::Context<AssertionKind>) -> AssertionError {
57-
AssertionError { inner: inner }
58-
}
59-
}
60-
61-
#[derive(Debug)]
62-
pub struct KeyValueDisplay<D>
63-
where
64-
D: fmt::Display + Send + Sync + 'static,
65-
{
66-
key: &'static str,
67-
context: D,
33+
#[derive(Fail, Debug)]
34+
pub struct AssertionError {
35+
cmd: Vec<ffi::OsString>,
6836
}
6937

70-
impl<D> KeyValueDisplay<D>
71-
where
72-
D: fmt::Display + Send + Sync + 'static,
73-
{
74-
pub fn new(key: &'static str, context: D) -> Self {
75-
Self { key, context }
76-
}
77-
78-
pub fn key(&self) -> &str {
79-
self.key
80-
}
81-
82-
pub fn context(&self) -> &D {
83-
&self.context
38+
impl AssertionError {
39+
pub fn new(cmd: Vec<ffi::OsString>) -> Self {
40+
Self { cmd }
8441
}
8542
}
8643

87-
impl<D> fmt::Display for KeyValueDisplay<D>
88-
where
89-
D: fmt::Display + Send + Sync + 'static,
90-
{
44+
impl fmt::Display for AssertionError {
9145
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92-
write!(f, "{}={}", self.key, self.context)
46+
write!(f, "Assertion failed for `{}`", format_cmd(&self.cmd))
9347
}
9448
}
9549

96-
#[derive(Debug)]
97-
pub struct DebugDisplay<D>
98-
where
99-
D: fmt::Debug + Send + Sync + 'static,
100-
{
101-
context: D,
50+
#[derive(Fail, Debug)]
51+
pub struct StatusError {
52+
unexpected: bool,
53+
stdout: Vec<u8>,
54+
stderr: Vec<u8>,
10255
}
10356

104-
impl<D> DebugDisplay<D>
105-
where
106-
D: fmt::Debug + Send + Sync + 'static,
107-
{
108-
pub fn new(context: D) -> Self {
109-
Self { context }
110-
}
111-
112-
pub fn context(&self) -> &D {
113-
&self.context
57+
impl StatusError {
58+
pub fn new(unexpected: bool, stdout: Vec<u8>, stderr: Vec<u8>) -> Self {
59+
Self {
60+
unexpected,
61+
stdout,
62+
stderr,
63+
}
11464
}
11565
}
11666

117-
impl<D> fmt::Display for DebugDisplay<D>
118-
where
119-
D: fmt::Debug + Send + Sync + 'static,
120-
{
67+
impl fmt::Display for StatusError {
12168
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122-
write!(f, "{:?}", self.context)
123-
}
124-
}
125-
126-
#[derive(Debug)]
127-
pub struct QuotedDisplay<D>
128-
where
129-
D: fmt::Display + Send + Sync + 'static,
130-
{
131-
context: D,
132-
}
133-
134-
impl<D> QuotedDisplay<D>
135-
where
136-
D: fmt::Display + Send + Sync + 'static,
137-
{
138-
pub fn new(context: D) -> Self {
139-
Self { context }
140-
}
141-
142-
pub fn context(&self) -> &D {
143-
&self.context
69+
let out = String::from_utf8_lossy(&self.stdout);
70+
let err = String::from_utf8_lossy(&self.stderr);
71+
writeln!(
72+
f,
73+
"Unexpected {} return status",
74+
if self.unexpected {
75+
"success"
76+
} else {
77+
"failure"
78+
}
79+
)?;
80+
writeln!(f, "stdout=```{}```", out)?;
81+
write!(f, "stderr=```{}```", err)
82+
}
83+
}
84+
85+
#[derive(Fail, Debug)]
86+
pub struct ExitCodeError {
87+
expected: Option<i32>,
88+
got: Option<i32>,
89+
stdout: Vec<u8>,
90+
stderr: Vec<u8>,
91+
}
92+
93+
impl ExitCodeError {
94+
pub fn new(expected: Option<i32>, got: Option<i32>, stdout: Vec<u8>, stderr: Vec<u8>) -> Self {
95+
Self {
96+
expected,
97+
got,
98+
stdout,
99+
stderr,
100+
}
144101
}
145102
}
146103

147-
impl<D> fmt::Display for QuotedDisplay<D>
148-
where
149-
D: fmt::Display + Send + Sync + 'static,
150-
{
104+
impl fmt::Display for ExitCodeError {
151105
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152-
write!(f, "```{}```", self.context)
106+
let out = String::from_utf8_lossy(&self.stdout);
107+
let err = String::from_utf8_lossy(&self.stderr);
108+
writeln!(f, "expected={:?}", self.expected)?;
109+
writeln!(f, "got={:?}", self.got)?;
110+
writeln!(f, "stdout=```{}```", out)?;
111+
write!(f, "stderr=```{}```", err)
153112
}
154113
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
extern crate colored;
121121
extern crate difference;
122122
extern crate environment;
123+
#[macro_use]
123124
extern crate failure;
124125
#[macro_use]
125126
extern crate failure_derive;

0 commit comments

Comments
 (0)