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//! ![Output](https://raw.githubusercontent.com/de-vri-es/assert2-rs/ba98984a32d6381e6710e34eb1fb83e65e851236/binary-operator.png)
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//! ![Output](https://raw.githubusercontent.com/de-vri-es/assert2-rs/54ee3141e9b23a0d9038697d34f29f25ef7fe810/multiline-diff.png)
63//!
64//! ----------
65//!
66//! ```should_panic
67//! # use assert2::check;
68//! check!((3, Some(4)) == [1, 2, 3].iter().size_hint());
69//! ```
70//!
71//! ![Output](https://raw.githubusercontent.com/de-vri-es/assert2-rs/54ee3141e9b23a0d9038697d34f29f25ef7fe810/single-line-diff.png)
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//! ![Output](https://raw.githubusercontent.com/de-vri-es/assert2-rs/54ee3141e9b23a0d9038697d34f29f25ef7fe810/pattern-match.png)
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//! ![Output](https://github.com/de-vri-es/assert2-rs/blob/54ee3141e9b23a0d9038697d34f29f25ef7fe810/let-assert.png?raw=true)
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;