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