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}