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