rstest_bdd/macros.rs
1//! Public macro helpers exported by `rstest-bdd`.
2//!
3//! The macros live in a dedicated module to keep `lib.rs` small and focused on
4//! type exports. They remain available at the crate root via `#[macro_export]`.
5
6/// Skip the current scenario with an optional message.
7///
8/// Step or hook functions may invoke the macro to stop executing the remaining
9/// steps. When the [`config::fail_on_skipped`](crate::config::fail_on_skipped)
10/// flag is enabled, scenarios without the `@allow_skipped` tag panic after the
11/// last executed step instead of being recorded as skipped.
12#[macro_export]
13macro_rules! skip {
14 () => {{
15 $crate::__rstest_bdd_request_current_skip(None)
16 }};
17 ($msg:expr $(,)?) => {{
18 $crate::__rstest_bdd_request_current_skip(Some(Into::<String>::into($msg)))
19 }};
20 ($fmt:expr, $($arg:tt)*) => {{
21 $crate::__rstest_bdd_request_current_skip(Some(format!($fmt, $($arg)*)))
22 }};
23}
24
25/// Assert that a [`Result`] is `Ok` and unwrap it.
26///
27/// Panics with a message including the error when the value is an `Err`.
28///
29/// Note: Formatting the error in the panic message requires the error type to
30/// implement [`std::fmt::Display`].
31///
32/// # Examples
33/// ```
34/// use rstest_bdd::assert_step_ok;
35///
36/// let res: Result<(), &str> = Ok(());
37/// assert_step_ok!(res);
38/// ```
39#[macro_export]
40macro_rules! assert_step_ok {
41 ($expr:expr $(,)?) => {
42 match $expr {
43 Ok(value) => value,
44 Err(e) => $crate::panic_localized!("assert-step-ok-panic", error = e),
45 }
46 };
47}
48
49/// Assert that a [`Result`] is `Err` and unwrap the error.
50///
51/// Optionally asserts that the error's display contains a substring.
52///
53/// Note: The `(expr, "substring")` form requires the error type to
54/// implement [`std::fmt::Display`] so it can be converted to a string for
55/// matching.
56///
57/// # Examples
58/// ```
59/// use rstest_bdd::assert_step_err;
60///
61/// let err: Result<(), &str> = Err("boom");
62/// let e = assert_step_err!(err, "boom");
63/// assert_eq!(e, "boom");
64/// ```
65///
66/// Single-argument form:
67/// ```
68/// use rstest_bdd::assert_step_err;
69///
70/// let err: Result<(), &str> = Err("boom");
71/// let e = assert_step_err!(err);
72/// assert_eq!(e, "boom");
73/// ```
74#[macro_export]
75macro_rules! assert_step_err {
76 ($expr:expr $(,)?) => {
77 match $expr {
78 Ok(_) => $crate::panic_localized!("assert-step-err-success"),
79 Err(e) => e,
80 }
81 };
82 ($expr:expr, $msg:expr $(,)?) => {
83 match $expr {
84 Ok(_) => $crate::panic_localized!("assert-step-err-success"),
85 Err(e) => {
86 let __rstest_bdd_display = e.to_string();
87 let __rstest_bdd_msg: &str = $msg.as_ref();
88 assert!(
89 __rstest_bdd_display.contains(__rstest_bdd_msg),
90 "{}",
91 $crate::localization::message_with_args(
92 "assert-step-err-missing-substring",
93 |args| {
94 args.set("display", __rstest_bdd_display.clone());
95 args.set("expected", __rstest_bdd_msg.to_string());
96 },
97 )
98 );
99 e
100 }
101 }
102 };
103}
104
105#[doc(hidden)]
106#[macro_export]
107macro_rules! __rstest_bdd_assert_step_skipped_base {
108 ($expr:expr) => {
109 $crate::__rstest_bdd_unwrap_step_skipped($expr)
110 };
111}
112
113#[doc(hidden)]
114#[macro_export]
115macro_rules! __rstest_bdd_assert_scenario_skipped_base {
116 ($status:expr) => {{
117 match &$status {
118 $crate::reporting::ScenarioStatus::Skipped(details) => details.clone(),
119 _ => {
120 $crate::panic_localized!("assert-skip-not-skipped", target = "scenario status",)
121 }
122 }
123 }};
124}
125
126/// Assert that a [`StepExecution`](crate::StepExecution) represents a skipped
127/// outcome.
128///
129/// Returns the optional skip message so callers can inspect it further. Supply
130/// `message = "substring"` to assert that the reason contains a specific
131/// fragment, or `message_absent = true` to assert that no message was
132/// provided.
133///
134/// # Examples
135/// ```
136/// use rstest_bdd::{assert_step_skipped, StepExecution};
137///
138/// let message = assert_step_skipped!(
139/// StepExecution::skipped(Some("pending dependency".into())),
140/// message = "pending",
141/// );
142/// assert_eq!(message, Some("pending dependency".into()));
143/// ```
144#[macro_export]
145macro_rules! assert_step_skipped {
146 ($expr:expr $(,)?) => {
147 $crate::__rstest_bdd_assert_step_skipped_base!($expr)
148 };
149 ($expr:expr, message = $value:expr $(,)?) => {
150 $crate::__rstest_bdd_assert_step_skipped_message_contains($expr, $value)
151 };
152 ($expr:expr, message_absent = $value:expr $(,)?) => {
153 $crate::__rstest_bdd_assert_step_skipped_message_absent($expr, $value)
154 };
155 ($expr:expr, $($rest:tt)+) => {
156 compile_error!(
157 "unsupported assert_step_skipped! arguments; expected `message = ...` or `message_absent = ...`",
158 );
159 };
160}
161
162/// Assert that a [`ScenarioStatus`](crate::reporting::ScenarioStatus) recorded
163/// a skipped outcome.
164///
165/// Returns a cloned [`SkippedScenario`](crate::reporting::SkippedScenario)
166/// describing the skip. Provide optional filters to assert message contents,
167/// whether skipping was allowed, and whether it forced the run to fail.
168///
169/// # Examples
170/// ```
171/// use rstest_bdd::assert_scenario_skipped;
172/// use rstest_bdd::reporting::{ScenarioStatus, SkippedScenario};
173///
174/// let status = ScenarioStatus::Skipped(SkippedScenario::new(
175/// Some("pending upstream".into()),
176/// true,
177/// false,
178/// ));
179/// let details = assert_scenario_skipped!(
180/// status,
181/// message = "pending",
182/// allow_skipped = true,
183/// );
184/// assert!(details.allow_skipped());
185/// ```
186#[macro_export]
187macro_rules! assert_scenario_skipped {
188 ($status:expr $(, $key:ident = $value:expr )* $(,)?) => {{
189 let __rstest_bdd_details = $crate::__rstest_bdd_assert_scenario_skipped_base!($status);
190 $(
191 $crate::__rstest_bdd_assert_scenario_detail!(
192 __rstest_bdd_details,
193 $key,
194 $value
195 );
196 )*
197 __rstest_bdd_details
198 }};
199}
200
201#[doc(hidden)]
202#[macro_export]
203macro_rules! __rstest_bdd_assert_scenario_detail {
204 ($details:expr, message, None) => {
205 $crate::__rstest_bdd_assert_scenario_detail_message_absent(&$details, true)
206 };
207 ($details:expr, message, $value:expr) => {
208 $crate::__rstest_bdd_assert_scenario_detail_message_contains(&$details, $value)
209 };
210 ($details:expr, message_absent, $value:expr) => {
211 $crate::__rstest_bdd_assert_scenario_detail_message_absent(&$details, $value)
212 };
213 ($details:expr, allow_skipped, $value:expr) => {
214 $crate::__rstest_bdd_assert_scenario_detail_flag(
215 &$details,
216 "allow_skipped",
217 $details.allow_skipped(),
218 $value,
219 )
220 };
221 ($details:expr, forced_failure, $value:expr) => {
222 $crate::__rstest_bdd_assert_scenario_detail_flag(
223 &$details,
224 "forced_failure",
225 $details.forced_failure(),
226 $value,
227 )
228 };
229 ($details:expr, $other:ident, $value:expr) => {{
230 // purpose: force-evaluation of $value to avoid unused-variable warnings
231 let _ = &$value;
232 compile_error!(concat!(
233 "unsupported key for assert_scenario_skipped!: ",
234 stringify!($other),
235 "; supported keys: message, message_absent, allow_skipped, forced_failure",
236 ));
237 }};
238}