assert2/lib.rs
1#![cfg_attr(feature = "doc-cfg", feature(doc_cfg))]
2#![allow(clippy::needless_lifetimes)]
3
4//! All-purpose [`assert!(...)`](macro.assert.html) and [`check!(...)`](macro.check.html) macros, inspired by [Catch2](https://github.com/catchorg/Catch2).
5//! There is also a [`debug_assert!(...)`](macro.debug_assert.html) macro that is disabled on optimized builds by default.
6//! As cherry on top there is a [`let_assert!(...)`](macro.let_assert.html) macro that lets you test a pattern while capturing parts of it.
7//!
8//! # Why these macros?
9//!
10//! These macros offer some benefits over the assertions from the standard library:
11//! * The macros parse your expression to detect comparisons and adjust the error message accordingly.
12//! No more `assert_eq!(a, b)` or `assert_ne!(c, d)`, just write `assert!(1 + 1 == 2)`, or even `assert!(1 + 1 > 1)`!
13//! * You can test for pattern matches: `assert!(let Err(_) = File::open("/non/existing/file"))`.
14//! * You can capture parts of the pattern for further testing by using the `let_assert!(...)` macro.
15//! * The `check` macro can be used to perform multiple checks before panicking.
16//! * The macros provide more information than the standard `std::assert!()` when the assertion fails.
17//! * Colored failure messages with diffs!
18//!
19//! The macros also accept additional arguments for a custom message, so it is fully compatible with `std::assert`.
20//! This means that you can import the macro as a drop in replacement:
21//! ```
22//! use assert2::assert;
23//! ```
24//!
25//! # Examples
26//!
27//! ```should_panic
28//! # use assert2::check;
29//! check!(6 + 1 <= 2 * 3);
30//! ```
31//!
32//! 
33//!
34//! ----------
35//!
36//! ```should_panic
37//! # use assert2::check;
38//! # use assert2::let_assert;
39//! # use std::fs::File;
40//! # use std::io::ErrorKind;
41//! # #[derive(Debug, Eq, PartialEq)]
42//! # struct Pet {
43//! # name: String,
44//! # age: u32,
45//! # kind: String,
46//! # shaved: bool,
47//! # }
48//! # let scrappy = Pet {
49//! # name: "Scrappy".into(),
50//! # age: 7,
51//! # kind: "Bearded Collie".into(),
52//! # shaved: false,
53//! # };
54//! # let coco = Pet {
55//! # name: "Coco".into(),
56//! # age: 7,
57//! # kind: "Bearded Collie".into(),
58//! # shaved: true,
59//! # };
60//! check!(scrappy == coco);
61//! ```
62//!
63//! 
64//!
65//! ----------
66//!
67//! ```should_panic
68//! # use assert2::check;
69//! check!((3, Some(4)) == [1, 2, 3].iter().size_hint());
70//! ```
71//!
72//! 
73//!
74//! ----------
75//!
76//! ```should_panic
77//! # use assert2::check;
78//! # use std::fs::File;
79//! check!(let Ok(_) = File::open("/non/existing/file"));
80//! ```
81//!
82//! 
83//!
84//! ----------
85//!
86//! ```should_panic
87//! # use assert2::check;
88//! # use assert2::let_assert;
89//! # use std::fs::File;
90//! # use std::io::ErrorKind;
91//! let_assert!(Err(e) = File::open("/non/existing/file"));
92//! check!(e.kind() == ErrorKind::PermissionDenied);
93//! ```
94//!
95//! 
96//!
97//! # `assert` vs `check`
98//! The crate provides two macros: `check!(...)` and `assert!(...)`.
99//! The main difference is that `check` is really intended for test cases and doesn't immediately panic.
100//! Instead, it will print the assertion error and fail the test.
101//! This allows you to run multiple checks and can help to determine the reason of a test failure more easily.
102//! The `assert` macro on the other hand simply prints the error and panics,
103//! and can be used outside of tests just as well.
104//!
105//! Currently, `check` uses a scope guard to delay the panic until the current scope ends.
106//! Ideally, `check` doesn't panic at all, but only signals that a test case has failed.
107//! If this becomes possible in the future, the `check` macro will change, so **you should not rely on `check` to panic**.
108//!
109//! # Difference between stable and nightly.
110//! If available, the crate uses the `proc_macro_span` feature to get the original source code.
111//! On stable and beta, it falls back to stringifying the expression.
112//! This makes the output a bit more readable on nightly.
113//!
114//! # The `let_assert!()` macro
115//! You can also use the [`let_assert!(...)`](macro.let_assert.html).
116//! It is very similar to `assert!(let ...)`,
117//! but all placeholders will be made available as variables in the calling scope.
118//!
119//! This allows you to run additional checks on the captured variables.
120//!
121//! For example:
122//!
123//! ```
124//! # fn main() {
125//! # use assert2::let_assert;
126//! # use assert2::check;
127//! # struct Foo {
128//! # name: &'static str,
129//! # }
130//! # enum Error {
131//! # InvalidName(InvalidNameError),
132//! # }
133//! # struct InvalidNameError {
134//! # name: &'static str,
135//! # }
136//! # impl Foo {
137//! # fn try_new(name: &'static str) -> Result<Self, Error> {
138//! # if name == "bar" {
139//! # Ok(Self { name })
140//! # } else {
141//! # Err(Error::InvalidName(InvalidNameError { name }))
142//! # }
143//! # }
144//! # fn name(&self) -> &'static str {
145//! # self.name
146//! # }
147//! # }
148//! # impl InvalidNameError {
149//! # fn name(&self) -> &'static str {
150//! # self.name
151//! # }
152//! # fn to_string(&self) -> String {
153//! # format!("invalid name: {}", self.name)
154//! # }
155//! # }
156//! let_assert!(Ok(foo) = Foo::try_new("bar"));
157//! check!(foo.name() == "bar");
158//!
159//! let_assert!(Err(Error::InvalidName(e)) = Foo::try_new("bogus name"));
160//! check!(e.name() == "bogus name");
161//! check!(e.to_string() == "invalid name: bogus name");
162//! # }
163//! ```
164//!
165//! # Controlling the output format.
166//!
167//! As an end-user, you can influence the way that `assert2` formats failed assertions by changing the `ASSERT2` environment variable.
168//! You can specify any combination of options, separated by a comma.
169//! The supported options are:
170//! * `auto`: Automatically select the compact or pretty `Debug` format for an assertion based on the length (default).
171//! * `pretty`: Always use the pretty `Debug` format for assertion messages (`{:#?}`).
172//! * `compact`: Always use the compact `Debug` format for assertion messages (`{:?}`).
173//! * `no-color`: Disable colored output, even when the output is going to a terminal.
174//! * `color`: Enable colored output, even when the output is not going to a terminal.
175//!
176//! For example, you can run the following command to force the use of the compact `Debug` format with colored output:
177//! ```shell
178//! ASSERT2=compact,color cargo test
179//! ```
180//!
181//! If neither the `color` or the `no-color` options are set,
182//! then `assert2` follows the [clicolors specification](https://bixense.com/clicolors/):
183//!
184//! * `NO_COLOR != 0` or `CLICOLOR == 0`: Write plain output without color codes.
185//! * `CLICOLOR != 0`: Write colored output when the output is going to a terminal.
186//! * `CLICOLOR_FORCE != 0`: Write colored output even when it is not going to a terminal.
187
188#[doc(hidden)]
189pub mod __assert2_impl;
190
191/// Assert that an expression evaluates to true or matches a pattern.
192///
193/// Use a `let` expression to test an expression against a pattern: `assert!(let pattern = expr)`.
194/// For other tests, just give a boolean expression to the macro: `assert!(1 + 2 == 2)`.
195///
196/// If the expression evaluates to false or if the pattern doesn't match,
197/// an assertion failure is printed and the macro panics instantly.
198///
199/// Use [`check!`](macro.check.html) if you still want further checks to be executed.
200///
201/// # Custom messages
202/// You can pass additional arguments to the macro.
203/// These will be used to print a custom message in addition to the normal message.
204///
205/// ```
206/// # use assert2::assert;
207/// assert!(3 * 4 == 12, "Oh no, math is broken! 1 + 1 == {}", 1 + 1);
208/// ```
209#[macro_export]
210macro_rules! assert {
211 ($($tokens:tt)*) => {
212 if let Err(()) = $crate::__assert2_impl::check_impl!($crate, "assert", $($tokens)*) {
213 panic!("assertion failed");
214 }
215 }
216}
217
218/// Check if an expression evaluates to true or matches a pattern.
219///
220/// Use a `let` expression to test an expression against a pattern: `check!(let pattern = expr)`.
221/// For other tests, just give a boolean expression to the macro: `check!(1 + 2 == 2)`.
222///
223/// If the expression evaluates to false or if the pattern doesn't match,
224/// an assertion failure is printed but the macro does not panic immediately.
225/// The check macro will cause the running test to fail eventually.
226///
227/// Use [`assert!`](macro.assert.html) if you want the test to panic instantly.
228///
229/// Currently, this macro uses a scope guard to delay the panic.
230/// However, this may change in the future if there is a way to signal a test failure without panicking.
231/// **Do not rely on `check!()` to panic**.
232///
233/// # Custom messages
234/// You can pass additional arguments to the macro.
235/// These will be used to print a custom message in addition to the normal message.
236///
237/// ```
238/// # use assert2::check;
239/// check!(3 * 4 == 12, "Oh no, math is broken! 1 + 1 == {}", 1 + 1);
240/// ```
241#[macro_export]
242macro_rules! check {
243 ($($tokens:tt)*) => {
244 let _guard = match $crate::__assert2_impl::check_impl!($crate, "check", $($tokens)*) {
245 Ok(_) => None,
246 Err(_) => {
247 Some($crate::__assert2_impl::FailGuard(|| panic!("check failed")))
248 },
249 };
250 }
251}
252
253/// Assert that an expression evaluates to true or matches a pattern.
254///
255/// This macro supports the same checks as [`assert`](macro.assert.html), but they are only executed if debug assertions are enabled.
256///
257/// As with [`std::debug_assert`](https://doc.rust-lang.org/stable/std/macro.debug_assert.html),
258/// the expression is still type checked if debug assertions are disabled.
259///
260#[macro_export]
261macro_rules! debug_assert {
262 ($($tokens:tt)*) => {
263 if ::core::cfg!(debug_assertions) {
264 if let Err(()) = $crate::__assert2_impl::check_impl!($crate, "debug_assert", $($tokens)*) {
265 panic!("assertion failed");
266 }
267 }
268 }
269}
270
271/// Assert that an expression matches a pattern.
272///
273/// This is very similar to `assert!(let pattern = expression)`,
274/// except that this macro makes all placeholders available in the calling scope.
275/// This can be used to assert a pattern match,
276/// and then run more checks on the captured variables.
277///
278/// For example:
279/// ```
280/// # use assert2::let_assert;
281/// # use assert2::check;
282/// # fn main() {
283/// # struct Foo {
284/// # name: &'static str,
285/// # }
286/// # enum Error {
287/// # InvalidName(InvalidNameError),
288/// # }
289/// # struct InvalidNameError {
290/// # name: &'static str,
291/// # }
292/// # impl Foo {
293/// # fn try_new(name: &'static str) -> Result<Self, Error> {
294/// # if name == "bar" {
295/// # Ok(Self { name })
296/// # } else {
297/// # Err(Error::InvalidName(InvalidNameError { name }))
298/// # }
299/// # }
300/// # fn name(&self) -> &'static str {
301/// # self.name
302/// # }
303/// # }
304/// # impl InvalidNameError {
305/// # fn name(&self) -> &'static str {
306/// # self.name
307/// # }
308/// # fn to_string(&self) -> String {
309/// # format!("invalid name: {}", self.name)
310/// # }
311/// # }
312/// let_assert!(Ok(foo) = Foo::try_new("bar"));
313/// check!(foo.name() == "bar");
314///
315/// let_assert!(Err(Error::InvalidName(e)) = Foo::try_new("bogus name"));
316/// check!(e.name() == "bogus name");
317/// check!(e.to_string() == "invalid name: bogus name");
318/// # }
319/// ```
320#[macro_export]
321macro_rules! let_assert {
322 ($($tokens:tt)*) => {
323 $crate::__assert2_impl::let_assert_impl!($crate, "let_assert", $($tokens)*);
324 }
325}
326
327#[doc(hidden)]
328#[macro_export]
329macro_rules! __assert2_stringify {
330 ($e:expr) => {
331 // Stringifying as an expression gives nicer output
332 // than stringifying a raw list of token trees.
333 $crate::__assert2_core_stringify!($e)
334 };
335 ($($t:tt)*) => {
336 $crate::__assert2_core_stringify!($($t)*)
337 };
338}
339
340#[doc(hidden)]
341pub use core::stringify as __assert2_core_stringify;