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

Commit 285aee5

Browse files
committed
refactor: Migrate to AssertionError
In doing so, this caught a couple places we weren't providing additional context to an error. This does not address the errors returned by the internal string predicates. Example: ``` Assertion failed for 'wc README.md' with: Unexpected return status: failure stdout='''''' stderr='''wc: README.md: No such file or directory ''' ``` BREAKING CHANGE: The error type has changed from `failure::Error` to `assert_cli::AssertionError`.
1 parent 7ad76af commit 285aee5

File tree

4 files changed

+199
-52
lines changed

4 files changed

+199
-52
lines changed

src/assert.rs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::vec::Vec;
88
use environment::Environment;
99
use failure;
1010
use failure::Fail;
11-
use failure::ResultExt;
1211

1312
use errors::*;
1413
use output::{Content, Output, OutputKind, OutputPredicate};
@@ -328,7 +327,7 @@ impl Assert {
328327
/// .execute();
329328
/// assert!(test.is_ok());
330329
/// ```
331-
pub fn execute(self) -> Result<()> {
330+
pub fn execute(self) -> Result<(), AssertionError> {
332331
let bin = &self.cmd[0];
333332

334333
let args: Vec<_> = self.cmd.iter().skip(1).collect();
@@ -348,45 +347,51 @@ impl Assert {
348347

349348
let mut spawned = command
350349
.spawn()
351-
.with_context(|_| SpawnError::new(self.cmd.clone()))?;
350+
.chain_with(|| AssertionError::new(self.cmd.clone()))?;
352351

353352
if let Some(ref contents) = self.stdin_contents {
354353
spawned
355354
.stdin
356355
.as_mut()
357356
.expect("Couldn't get mut ref to command stdin")
358-
.write_all(contents)?;
357+
.write_all(contents)
358+
.chain_with(|| AssertionError::new(self.cmd.clone()))?;
359359
}
360-
let output = spawned.wait_with_output()?;
360+
let output = spawned
361+
.wait_with_output()
362+
.chain_with(|| AssertionError::new(self.cmd.clone()))?;
361363

362364
if let Some(expect_success) = self.expect_success {
363365
let actual_success = output.status.success();
364366
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())))?;
367+
return Err(
368+
AssertionError::new(self.cmd.clone()).chain(StatusError::new(
369+
actual_success,
370+
output.stdout.clone(),
371+
output.stderr.clone(),
372+
)),
373+
)?;
370374
}
371375
}
372376

373377
if self.expect_exit_code.is_some() && self.expect_exit_code != output.status.code() {
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())))?;
378+
return Err(
379+
AssertionError::new(self.cmd.clone()).chain(ExitCodeError::new(
380+
self.expect_exit_code,
381+
output.status.code(),
382+
output.stdout.clone(),
383+
output.stderr.clone(),
384+
)),
385+
);
380386
}
381387

382388
self.expect_output
383389
.iter()
384390
.map(|a| {
385391
a.verify(&output)
386-
.with_context(|_| AssertionError::new(self.cmd.clone()))
387-
.map_err(|e| failure::Error::from(e))
392+
.chain_with(|| AssertionError::new(self.cmd.clone()))
388393
})
389-
.collect::<Result<Vec<()>>>()?;
394+
.collect::<Result<Vec<()>, AssertionError>>()?;
390395

391396
Ok(())
392397
}

src/errors.rs

Lines changed: 116 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,97 @@
11
use std::ffi;
22
use std::fmt;
3-
use std::result;
3+
use std::io;
44

55
use failure;
66

7-
pub type Result<T> = result::Result<T, failure::Error>;
8-
97
fn format_cmd(cmd: &[ffi::OsString]) -> String {
108
let result: Vec<String> = cmd.iter()
119
.map(|s| s.to_string_lossy().into_owned())
1210
.collect();
1311
result.join(" ")
1412
}
1513

16-
#[derive(Fail, Debug)]
17-
pub struct SpawnError {
18-
cmd: Vec<ffi::OsString>,
14+
pub trait ChainFail {
15+
fn chain<E>(self, cause: E) -> Self
16+
where
17+
E: Into<failure::Error>;
18+
}
19+
20+
pub trait ResultChainExt<T> {
21+
fn chain<C>(self, chainable: C) -> Result<T, C>
22+
where
23+
C: ChainFail;
24+
25+
fn chain_with<F, C>(self, chainable: F) -> Result<T, C>
26+
where
27+
F: FnOnce() -> C,
28+
C: ChainFail;
1929
}
2030

21-
impl SpawnError {
22-
pub fn new(cmd: Vec<ffi::OsString>) -> Self {
23-
Self { cmd }
31+
impl<T> ResultChainExt<T> for Result<T, failure::Error> {
32+
fn chain<C>(self, chainable: C) -> Result<T, C>
33+
where
34+
C: ChainFail,
35+
{
36+
self.map_err(|e| chainable.chain(e))
37+
}
38+
39+
fn chain_with<F, C>(self, chainable: F) -> Result<T, C>
40+
where
41+
F: FnOnce() -> C,
42+
C: ChainFail,
43+
{
44+
self.map_err(|e| chainable().chain(e))
2445
}
2546
}
2647

27-
impl fmt::Display for SpawnError {
28-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29-
write!(f, "Failed to run `{}`", format_cmd(&self.cmd))
48+
impl<T> ResultChainExt<T> for Result<T, io::Error> {
49+
fn chain<C>(self, chainable: C) -> Result<T, C>
50+
where
51+
C: ChainFail,
52+
{
53+
self.map_err(|e| chainable.chain(e))
54+
}
55+
56+
fn chain_with<F, C>(self, chainable: F) -> Result<T, C>
57+
where
58+
F: FnOnce() -> C,
59+
C: ChainFail,
60+
{
61+
self.map_err(|e| chainable().chain(e))
3062
}
3163
}
3264

33-
#[derive(Fail, Debug)]
65+
/// Failure when processing assertions.
66+
#[derive(Debug)]
3467
pub struct AssertionError {
3568
cmd: Vec<ffi::OsString>,
69+
cause: Option<failure::Error>,
3670
}
3771

3872
impl AssertionError {
39-
pub fn new(cmd: Vec<ffi::OsString>) -> Self {
40-
Self { cmd }
73+
pub(crate) fn new(cmd: Vec<ffi::OsString>) -> Self {
74+
Self { cmd, cause: None }
75+
}
76+
}
77+
78+
impl failure::Fail for AssertionError {
79+
fn cause(&self) -> Option<&failure::Fail> {
80+
self.cause.as_ref().map(failure::Error::cause)
81+
}
82+
83+
fn backtrace(&self) -> Option<&failure::Backtrace> {
84+
None
85+
}
86+
}
87+
88+
impl ChainFail for AssertionError {
89+
fn chain<E>(mut self, error: E) -> Self
90+
where
91+
E: Into<failure::Error>,
92+
{
93+
self.cause = Some(error.into());
94+
self
4195
}
4296
}
4397

@@ -47,11 +101,12 @@ impl fmt::Display for AssertionError {
47101
}
48102
}
49103

50-
#[derive(Fail, Debug)]
104+
#[derive(Debug)]
51105
pub struct StatusError {
52106
unexpected: bool,
53107
stdout: Vec<u8>,
54108
stderr: Vec<u8>,
109+
cause: Option<failure::Error>,
55110
}
56111

57112
impl StatusError {
@@ -60,17 +115,38 @@ impl StatusError {
60115
unexpected,
61116
stdout,
62117
stderr,
118+
cause: None,
63119
}
64120
}
65121
}
66122

123+
impl failure::Fail for StatusError {
124+
fn cause(&self) -> Option<&failure::Fail> {
125+
self.cause.as_ref().map(failure::Error::cause)
126+
}
127+
128+
fn backtrace(&self) -> Option<&failure::Backtrace> {
129+
None
130+
}
131+
}
132+
133+
impl ChainFail for StatusError {
134+
fn chain<E>(mut self, error: E) -> Self
135+
where
136+
E: Into<failure::Error>,
137+
{
138+
self.cause = Some(error.into());
139+
self
140+
}
141+
}
142+
67143
impl fmt::Display for StatusError {
68144
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69145
let out = String::from_utf8_lossy(&self.stdout);
70146
let err = String::from_utf8_lossy(&self.stderr);
71147
writeln!(
72148
f,
73-
"Unexpected {} return status",
149+
"Unexpected return status: {}",
74150
if self.unexpected {
75151
"success"
76152
} else {
@@ -82,12 +158,13 @@ impl fmt::Display for StatusError {
82158
}
83159
}
84160

85-
#[derive(Fail, Debug)]
161+
#[derive(Debug)]
86162
pub struct ExitCodeError {
87163
expected: Option<i32>,
88164
got: Option<i32>,
89165
stdout: Vec<u8>,
90166
stderr: Vec<u8>,
167+
cause: Option<failure::Error>,
91168
}
92169

93170
impl ExitCodeError {
@@ -97,10 +174,31 @@ impl ExitCodeError {
97174
got,
98175
stdout,
99176
stderr,
177+
cause: None,
100178
}
101179
}
102180
}
103181

182+
impl failure::Fail for ExitCodeError {
183+
fn cause(&self) -> Option<&failure::Fail> {
184+
self.cause.as_ref().map(failure::Error::cause)
185+
}
186+
187+
fn backtrace(&self) -> Option<&failure::Backtrace> {
188+
None
189+
}
190+
}
191+
192+
impl ChainFail for ExitCodeError {
193+
fn chain<E>(mut self, error: E) -> Self
194+
where
195+
E: Into<failure::Error>,
196+
{
197+
self.cause = Some(error.into());
198+
self
199+
}
200+
}
201+
104202
impl fmt::Display for ExitCodeError {
105203
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106204
let out = String::from_utf8_lossy(&self.stdout);

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ extern crate failure_derive;
127127
extern crate serde_json;
128128

129129
mod errors;
130+
pub use errors::AssertionError;
130131

131132
#[macro_use]
132133
mod macros;

0 commit comments

Comments
 (0)