Skip to main content

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}