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

Commit 02e986c

Browse files
committed
refactor: Migrate to failure
This takes the use of `Context` to a bit of an extreme, using a single error type and propogating any additional information along as individual pieces of `Context. Example: ``` command="wc" "README.md" with: stderr='''wc: README.md: No such file or directory ''' with: stdout='''''' with: expected to succeed with: CLI Assertion Error: Status mismatch. ``` BREAKING CHANGE: The error type of `Assert` is now `failure::Error`.
1 parent 0a2758e commit 02e986c

File tree

7 files changed

+316
-177
lines changed

7 files changed

+316
-177
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ name = "assert_fixture"
1818
[dependencies]
1919
colored = "1.5"
2020
difference = "2.0"
21-
error-chain = "0.11"
21+
failure = "0.1"
22+
failure_derive = "0.1"
2223
serde_json = "1.0"
2324
environment = "0.1"
2425

src/assert.rs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
use environment::Environment;
2-
use error_chain::ChainedError;
3-
use errors::*;
4-
use output::{Content, Output, OutputKind, OutputPredicate};
51
use std::default;
62
use std::ffi::{OsStr, OsString};
73
use std::io::Write;
84
use std::path::PathBuf;
95
use std::process::{Command, Stdio};
106
use std::vec::Vec;
117

8+
use environment::Environment;
9+
use failure;
10+
use failure::ResultExt;
11+
12+
use errors::*;
13+
use output::{Content, Output, OutputKind, OutputPredicate};
14+
1215
/// Assertions for a specific command.
1316
#[derive(Debug)]
1417
#[must_use]
@@ -342,9 +345,7 @@ impl Assert {
342345
None => command,
343346
};
344347

345-
let mut spawned = command
346-
.spawn()
347-
.chain_err(|| ErrorKind::SpawnFailed(self.cmd.clone()))?;
348+
let mut spawned = command.spawn().context(AssertionKind::Spawn)?;
348349

349350
if let Some(ref contents) = self.stdin_contents {
350351
spawned
@@ -359,25 +360,41 @@ impl Assert {
359360
if expect_success != output.status.success() {
360361
let out = String::from_utf8_lossy(&output.stdout).to_string();
361362
let err = String::from_utf8_lossy(&output.stderr).to_string();
362-
let err: Error = ErrorKind::StatusMismatch(expect_success, out, err).into();
363-
bail!(err.chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone())));
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)))?;
364372
}
365373
}
366374

367375
if self.expect_exit_code.is_some() && self.expect_exit_code != output.status.code() {
368376
let out = String::from_utf8_lossy(&output.stdout).to_string();
369377
let err = String::from_utf8_lossy(&output.stderr).to_string();
370-
let err: Error =
371-
ErrorKind::ExitCodeMismatch(self.expect_exit_code, output.status.code(), out, err)
372-
.into();
373-
bail!(err.chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone())));
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)))?;
374390
}
375391

376392
self.expect_output
377393
.iter()
378394
.map(|a| {
379395
a.verify(&output)
380-
.chain_err(|| ErrorKind::AssertionFailed(self.cmd.clone()))
396+
.with_context(|_| KeyValueDisplay::new("command", format!("{:?}", command)))
397+
.map_err(|e| failure::Error::from(e))
381398
})
382399
.collect::<Result<Vec<()>>>()?;
383400

@@ -397,8 +414,16 @@ impl Assert {
397414
/// ```
398415
pub fn unwrap(self) {
399416
if let Err(err) = self.execute() {
400-
panic!("{}", err.display_chain());
417+
panic!(Self::format_causes(err.causes()));
418+
}
419+
}
420+
421+
fn format_causes(mut causes: failure::Causes) -> String {
422+
let mut result = causes.next().expect("an error should exist").to_string();
423+
for cause in causes {
424+
result.push_str(&format!("\nwith: {}", cause));
401425
}
426+
result
402427
}
403428
}
404429

src/bin/assert_fixture.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
#[macro_use]
2-
extern crate error_chain;
1+
extern crate failure;
32

3+
use std::io;
4+
use std::io::Write;
45
use std::env;
56
use std::process;
67

7-
error_chain! {
8-
foreign_links {
9-
Env(env::VarError);
10-
ParseInt(std::num::ParseIntError);
11-
}
12-
}
8+
use failure::ResultExt;
139

14-
fn run() -> Result<()> {
10+
fn run() -> Result<(), failure::Error> {
1511
if let Ok(text) = env::var("stdout") {
1612
println!("{}", text);
1713
}
@@ -23,9 +19,18 @@ fn run() -> Result<()> {
2319
.ok()
2420
.map(|v| v.parse::<i32>())
2521
.map_or(Ok(None), |r| r.map(Some))
26-
.chain_err(|| "Invalid exit code")?
22+
.context("Invalid exit code")?
2723
.unwrap_or(0);
2824
process::exit(code);
2925
}
3026

31-
quick_main!(run);
27+
fn main() {
28+
let code = match run() {
29+
Ok(_) => 0,
30+
Err(ref e) => {
31+
write!(&mut io::stderr(), "{}", e).expect("writing to stderr won't fail");
32+
1
33+
}
34+
};
35+
process::exit(code);
36+
}

src/diff.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
extern crate colored;
2-
use self::colored::Colorize;
3-
use difference::{Changeset, Difference};
41
use std::fmt::{Error as fmtError, Write};
52

3+
use colored::Colorize;
4+
use difference::{Changeset, Difference};
5+
66
pub fn render(&Changeset { ref diffs, .. }: &Changeset) -> Result<String, fmtError> {
77
let mut t = String::new();
88

src/errors.rs

Lines changed: 151 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,154 @@
1-
use output;
2-
use std::ffi::OsString;
3-
4-
const ERROR_PREFIX: &'static str = "CLI assertion failed";
5-
6-
fn format_cmd(cmd: &[OsString]) -> String {
7-
let result: Vec<String> = cmd.iter()
8-
.map(|s| s.to_string_lossy().into_owned())
9-
.collect();
10-
result.join(" ")
11-
}
12-
13-
error_chain! {
14-
links {
15-
Output(output::Error, output::ErrorKind);
16-
}
17-
foreign_links {
18-
Io(::std::io::Error);
19-
Fmt(::std::fmt::Error);
20-
}
21-
errors {
22-
SpawnFailed(cmd: Vec<OsString>) {
23-
description("Spawn failed")
24-
display(
25-
"{}: (command `{}` failed to run)",
26-
ERROR_PREFIX,
27-
format_cmd(cmd),
28-
)
29-
}
30-
AssertionFailed(cmd: Vec<OsString>) {
31-
description("Assertion failed")
32-
display(
33-
"{}: (command `{}` failed)",
34-
ERROR_PREFIX,
35-
format_cmd(cmd),
36-
)
37-
}
38-
StatusMismatch(expected: bool, out: String, err: String) {
39-
description("Wrong status")
40-
display(
41-
"Expected to {}\nstatus={}\nstdout=```{}```\nstderr=```{}```",
42-
expected = if *expected { "succeed" } else { "fail" },
43-
got = if *expected { "failed" } else { "succeeded" },
44-
out = out,
45-
err = err,
46-
)
47-
}
48-
ExitCodeMismatch(
49-
expected: Option<i32>,
50-
got: Option<i32>,
51-
out: String,
52-
err: String
53-
) {
54-
description("Wrong exit code")
55-
display(
56-
"Expected exit code to be `{expected:?}`)\n\
57-
exit code=`{code:?}`\n\
58-
stdout=```{stdout}```\n\
59-
stderr=```{stderr}```",
60-
expected=expected,
61-
code=got,
62-
stdout=out,
63-
stderr=err,
64-
)
1+
use std::fmt;
2+
use std::result;
3+
4+
use failure;
5+
6+
pub type Result<T> = result::Result<T, failure::Error>;
7+
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,
14+
}
15+
16+
#[derive(Debug)]
17+
pub struct AssertionError {
18+
inner: failure::Context<AssertionKind>,
19+
}
20+
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()
38+
}
39+
}
40+
41+
impl fmt::Display for AssertionError {
42+
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),
6551
}
6652
}
6753
}
54+
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,
68+
}
69+
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
84+
}
85+
}
86+
87+
impl<D> fmt::Display for KeyValueDisplay<D>
88+
where
89+
D: fmt::Display + Send + Sync + 'static,
90+
{
91+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92+
write!(f, "{}={}", self.key, self.context)
93+
}
94+
}
95+
96+
#[derive(Debug)]
97+
pub struct DebugDisplay<D>
98+
where
99+
D: fmt::Debug + Send + Sync + 'static,
100+
{
101+
context: D,
102+
}
103+
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
114+
}
115+
}
116+
117+
impl<D> fmt::Display for DebugDisplay<D>
118+
where
119+
D: fmt::Debug + Send + Sync + 'static,
120+
{
121+
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
144+
}
145+
}
146+
147+
impl<D> fmt::Display for QuotedDisplay<D>
148+
where
149+
D: fmt::Display + Send + Sync + 'static,
150+
{
151+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152+
write!(f, "```{}```", self.context)
153+
}
154+
}

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,12 @@
117117
118118
#![deny(missing_docs)]
119119

120+
extern crate colored;
120121
extern crate difference;
121122
extern crate environment;
123+
extern crate failure;
122124
#[macro_use]
123-
extern crate error_chain;
125+
extern crate failure_derive;
124126
extern crate serde_json;
125127

126128
mod errors;

0 commit comments

Comments
 (0)