assert_panic/
lib.rs

1//! Assert that a panic happens, and optionally what (kind of) panic happens.
2
3#![doc(html_root_url = "https://docs.rs/assert-panic/1.0.1")]
4#![doc(test(no_crate_inject))]
5#![warn(
6    clippy::as_conversions,
7    clippy::cargo,
8    clippy::clone_on_ref_ptr,
9    clippy::missing_docs_in_private_items,
10    clippy::pedantic
11)]
12// Debug cleanup. Uncomment before committing.
13#![forbid(
14    clippy::dbg_macro,
15    clippy::print_stdout,
16    clippy::todo,
17    clippy::unimplemented
18)]
19
20/// Asserts that `$stmt` panics.  
21///
22/// - Only this base form with a single expression returns the panic.
23///
24/// Optionally asserts the type of the panic.
25///
26/// Optionally asserts the downcast panic `contains`, `starts with` or equals a given expression `$expr`.
27///
28/// # Panics
29///
30/// - if `$stmt` doesn't panic.
31/// - optionally if the type of the panic doesn't match.
32/// - optionally if the panic has the wrong value.
33///
34/// # Example
35///
36/// ```rust
37/// # use std::any::Any;
38/// use assert_panic::assert_panic;
39///
40/// let _: Box<dyn Any + Send + 'static> =
41///     assert_panic!(panic!("at the Disco"));
42///
43/// # let _: () =
44/// assert_panic!(panic!("at the Disco"), &str);
45///
46/// # let _: () =
47/// assert_panic!(
48///     { assert_panic!({}); },
49///     String,
50///     starts with "assert_panic! argument did not panic:",
51/// );
52///
53/// # let _: () =
54/// assert_panic!(
55///     assert_panic!(panic!("at the Disco"), String),
56///     String,
57///     starts with "Expected a `String` panic but found one with TypeId { t: ",
58/// );
59///
60/// # let _: () =
61/// assert_panic!(
62///     assert_panic!(panic!("found"), &str, contains "expected"),
63///     String,
64///     "Expected a panic containing \"expected\" but found \"found\"",
65/// );
66///
67/// # let _: () =
68/// assert_panic!(
69///     assert_panic!(panic!("found"), &str, starts with "expected"),
70///     String,
71///     "Expected a panic starting with \"expected\" but found \"found\"",
72/// );
73///
74/// # let _: () =
75/// assert_panic!(
76///     assert_panic!(panic!(1_usize), usize, 2_usize),
77///     String,
78///     "Expected a panic equal to 2 but found 1",
79/// );
80/// ```
81///
82/// # Details
83///
84/// All arguments are evaluated at most once, but `$expr` must be [`Copy`](https://doc.rust-lang.org/stable/std/marker/trait.Copy.html).
85///
86/// `$expr` is only evaluated if `$stmt` panics.
87///
88/// Type assertions use [`Any::downcast_ref::<$ty>()`](https://doc.rust-lang.org/stable/std/any/trait.Any.html#method.downcast_ref-1).
89///
90/// The value is examined by reference `panic` only after downcasting it to `$ty`:
91///
92/// - `contains` uses `panic.contains(expr)`.  
93/// - `starts with` uses `panic.starts_with(expr)`.  
94/// - Equality comparison is done with `*panic == expr`.
95///
96/// All of this is duck-typed, so the respective forms only require that matching methods / the matching operator are present.
97#[macro_export]
98macro_rules! assert_panic {
99    ($stmt:stmt$(,)?) => {
100        ::std::panic::catch_unwind(|| -> () { $stmt })
101            .expect_err("assert_panic! argument did not panic")
102    };
103
104    ($stmt:stmt, $ty:ty$(,)?) => {{
105        let panic = $crate::assert_panic!($stmt);
106        panic.downcast_ref::<$ty>().unwrap_or_else(|| {
107            panic!(
108                "Expected a `{}` panic but found one with {:?}",
109                stringify!($ty),
110                panic.type_id()
111            )
112        });
113    }};
114
115    ($stmt:stmt, $ty:ty, contains $expr:expr$(,)?) => {{
116        let panic = $crate::assert_panic!($stmt);
117        let expr = $expr;
118        let panic = panic.downcast_ref::<$ty>().unwrap_or_else(|| {
119            panic!(
120                "Expected a `{}` panic containing {:?} but found one with {:?}",
121                stringify!($ty),
122                expr,
123                panic.type_id()
124            )
125        });
126        assert!(
127            panic.contains(expr),
128            "Expected a panic containing {:?} but found {:?}",
129            expr,
130            panic
131        );
132    }};
133
134    ($stmt:stmt, $ty:ty, starts with $expr:expr$(,)?) => {{
135        let panic = $crate::assert_panic!($stmt);
136        let expr = $expr;
137        let panic = panic.downcast_ref::<$ty>().unwrap_or_else(|| {
138            panic!(
139                "Expected a `{}` panic starting with {:?} but found one with {:?}",
140                stringify!($ty),
141                expr,
142                panic.type_id()
143            )
144        });
145        assert!(
146            panic.starts_with(expr),
147            "Expected a panic starting with {:?} but found {:?}",
148            expr,
149            panic
150        );
151    }};
152
153    ($stmt:stmt, $ty:ty, $expr:expr$(,)?) => {{
154        let panic = $crate::assert_panic!($stmt);
155        let expr = $expr;
156        let panic = panic.downcast_ref::<$ty>().unwrap_or_else(|| {
157            panic!(
158                "Expected a `{}` panic equal to {:?} but found one with {:?}",
159                stringify!($ty),
160                expr,
161                panic.type_id()
162            )
163        });
164        assert!(
165            *panic == expr,
166            "Expected a panic equal to {:?} but found {:?}",
167            expr,
168            panic
169        );
170    }};
171}