soft_test_failures/lib.rs
1//! This crate trackes assertions during the execution of a test, delaying panicks until the end of execution.
2//! This ensures that if two assertions were to fail, the first does not clobber the second. This is most useful
3//! when writing large tests with many assertions that may begin to fail simultaneously, as you can pinpoint exactly
4//! which ones failed.
5//!
6//! # Usage
7//!
8//! Replace your normal `assert!()` assertions with `expect!()` from this crate. At the end of your test, call `let_fail!()`
9//!
10//! ```rust
11//! #[test]
12//! fn expect_failures() {
13//! let x = 4;
14//! let y = "is not";
15//! let z = 5;
16//! expect!(2 + 2 == 5, "{} surely {} {}", x, y, z);
17//! expect!(1 + 1 == 2);
18//! expect!(3 - 7 == -4);
19//! expect!(3 - 7 == -3);
20//! let_fail!();
21//! }
22//! ```
23
24use std::cell::RefCell;
25use std::thread_local;
26
27thread_local! {
28 /// Implementation detail; don't use directly
29 #[doc(hidden)]
30 pub static FAILURES: RefCell<Vec<String>> = RefCell::new(Vec::new());
31}
32
33/// Tracks a failed condition like an assertion, but allows test execution to continue.
34#[macro_export]
35macro_rules! expect {
36 ($cond:expr) => {{
37 expect!($cond, "Expected {}", stringify!($cond));
38 }};
39 ($cond:expr, $($arg:tt)+) => {{
40 if !($cond) {
41 $crate::FAILURES.with(|cell| cell.borrow_mut().push(format!($($arg)*)));
42 }
43 }};
44}
45
46/// Panics if at least one `expect!()` failed, thus failing the test.
47#[macro_export]
48macro_rules! let_fail {
49 () => {
50 use std::fmt::Write;
51 $crate::FAILURES.with(|cell| {
52 let f = cell.borrow_mut();
53 if f.len() > 0 {
54 let mut msg = format!("`expect` test failed with {} failed assertions:\n", f.len());
55 f.iter()
56 .enumerate()
57 .for_each(|x| writeln!(msg, "{}: {}", x.0 + 1, x.1).unwrap()); // <String as Write> never panics
58 panic!(msg);
59 } else {
60 println!("`expect` test passed.");
61 }
62 });
63 };
64}
65
66#[test]
67#[should_panic]
68fn expect_failures() {
69 let x = 4;
70 let y = "is not";
71 let z = 5;
72 expect!(2 + 2 == 5, "{} surely {} {}", x, y, z);
73 expect!(1 + 1 == 2);
74 expect!(3 - 7 == -4);
75 expect!(3 - 7 == -2);
76 let_fail!();
77}
78
79#[test]
80fn expect_pass() {
81 let x = 4;
82 let y = "is not";
83 let z = 5;
84 expect!(2 + 2 == 4, "{} surely {} {}", x, y, z);
85 expect!(1 + 1 == 2);
86 expect!(3 - 7 == -4);
87 expect!(3 - 7 == -4);
88 let_fail!();
89}