Skip to main content

antithesis_sdk/assert/
macros.rs

1#[cfg(feature = "full")]
2#[doc(hidden)]
3#[macro_export]
4macro_rules! function {
5    ($static:ident) => {
6        // Define a do-nothing function `'_f()'` within the context of
7        // the function invoking an assertion.  Then the ``type_name`` of
8        // this do-nothing will be something like:
9        //
10        //     bincrate::binmod::do_stuff::_f
11        //
12        // After trimming off the last three chars ``::_f`` what remains is
13        // the full path to the name of the function invoking the assertion
14        //
15        // The result will be stored as a lazily initialized statics in
16        // `$static`, so that it can be available at
17        // assertion catalog registration time.
18        use $crate::once_cell::sync::Lazy;
19        fn _f() {}
20        static $static: $crate::once_cell::sync::Lazy<&'static str> =
21            $crate::once_cell::sync::Lazy::new(|| {
22                fn type_name_of<T>(_: T) -> &'static str {
23                    ::std::any::type_name::<T>()
24                }
25                let name = type_name_of(_f);
26                &name[..name.len() - 4]
27            });
28    };
29}
30
31/// Common handling used by all the assertion-related macros
32#[cfg(feature = "full")]
33#[doc(hidden)]
34#[macro_export]
35macro_rules! assert_helper {
36    // The handling of this pattern-arm of assert_helper
37    // is wrapped in a block {} to avoid name collisions
38    (condition = $condition:expr, $message:expr, $(details = $details:expr)?, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{
39        // `$message` must be const evaluable
40        const _: &str = $message;
41
42        // Force evaluation of expressions.
43        let condition = $condition;
44        let details = &$crate::serde_json::json!({});
45        $(let details = $details;)?
46
47        $crate::function!(FUN_NAME);
48
49        use $crate::assert::AssertionCatalogInfo;
50        #[$crate::linkme::distributed_slice($crate::assert::ANTITHESIS_CATALOG)]
51        #[linkme(crate = $crate::linkme)] // Refer to our re-exported linkme.
52        static ALWAYS_CATALOG_ITEM: AssertionCatalogInfo = AssertionCatalogInfo {
53            assert_type: $assert_type,
54            display_type: $display_type,
55            condition: false,
56            message: $message,
57            class: ::std::module_path!(),
58            function: &FUN_NAME, /* function: &Lazy<&str> */
59            file: ::std::file!(),
60            begin_line: ::std::line!(),
61            begin_column: ::std::column!(),
62            must_hit: $must_hit,
63            id: $message,
64        };
65
66        let ptr_function = Lazy::force(&FUN_NAME);
67
68        static TRACKER: $crate::assert::TrackingInfo = $crate::assert::TrackingInfo::new();
69
70        $crate::assert::assert_impl(
71            $assert_type,                     /* assert_type */
72            $display_type,                    /* display_type */
73            condition,                        /* condition */
74            $message,                         /* message */
75            ::std::module_path!(),            /* class */
76            *ptr_function,                    /* function */
77            ::std::file!(),                   /* file */
78            ::std::line!(),                   /* line */
79            ::std::column!(),                 /* column */
80            true,                             /* hit */
81            $must_hit,                        /* must-hit */
82            $message,                         /* id */
83            details,                          /* details */
84            Some(&TRACKER),                   /* tracker */
85        )
86    }}; // end pattern-arm block
87}
88
89#[cfg(not(feature = "full"))]
90#[doc(hidden)]
91#[macro_export]
92macro_rules! assert_helper {
93    (condition = $condition:expr, $message:expr, $(details = $details:expr)?, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{
94        // `$message` must be const evaluable
95        const _: &str = $message;
96
97        // Force evaluation of expressions, ensuring that
98        // any side effects of these expressions will always be
99        // evaluated at runtime - even if the assertion itself
100        // is supressed by the `no-antithesis-sdk` feature
101        let condition = $condition;
102        $(let details = $details;)?
103    }};
104}
105
106/// Assert that ``condition`` is true every time this function is called, **and** that it is
107/// called at least once. The corresponding test property will be viewable in the ``Antithesis SDK: Always`` group of your triage report.
108///
109/// # Example
110///
111/// ```
112/// use serde::Serialize;
113/// use antithesis_sdk::{assert_always, random};
114///
115/// // Details can be any Serialize type, and are only serialized when the
116/// // assertion actually emits — on a sparse sample of passing and failing
117/// // evaluations. A plain struct of values and references like this
118/// // therefore costs nothing on the hot path.
119/// #[derive(Serialize)]
120/// struct Details<'a> {
121///     max_allowed: u64,
122///     actual: u64,
123///     source: &'a str,
124/// }
125///
126/// const MAX_ALLOWED: u64 = 100;
127/// let actual = random::get_random() % 100u64;
128/// let details = Details { max_allowed: MAX_ALLOWED, actual, source: "demo" };
129/// antithesis_sdk::assert_always!(actual < MAX_ALLOWED, "Value in range", &details);
130/// ```
131///
132/// Ensure that non-const-evaluable messages are rejected.
133///
134/// ```
135/// use serde_json::json;
136/// const MESSAGE: &str = concat!("Value", " in range");
137/// antithesis_sdk::assert_always!(true, MESSAGE, &json!({}));
138/// ```
139///
140/// A message computed at runtime is rejected at compile time:
141///
142/// ```compile_fail
143/// use serde_json::json;
144/// let message = String::from("Value in range");
145/// antithesis_sdk::assert_always!(true, message, &json!({}));
146/// ```
147///
148/// ```compile_fail
149/// use serde_json::json;
150/// antithesis_sdk::assert_always!(true, format!("{}", "Value in range"), &json!({}));
151/// ```
152#[macro_export]
153macro_rules! assert_always {
154    ($condition:expr, $message:expr$(, $details:expr)?) => {
155        $crate::assert_helper!(
156            condition = $condition,
157            $message,
158            $(details = $details)?,
159            $crate::assert::AssertType::Always,
160            "Always",
161            must_hit = true
162        )
163    };
164    ($($rest:tt)*) => {
165        ::std::compile_error!(
166r#"Invalid syntax when calling macro `assert_always`.
167Example usage:
168    `assert_always!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
169"#
170        );
171    };
172}
173
174/// Assert that ``condition`` is true every time this function is called. The corresponding test property will pass even if the assertion is never encountered.
175/// This test property will be viewable in the ``Antithesis SDK: Always`` group of your triage report.
176///
177/// # Example
178///
179/// ```
180/// use serde::Serialize;
181/// use antithesis_sdk::{assert_always_or_unreachable, random};
182///
183/// // Only serialized when the assertion emits; see assert_always.
184/// #[derive(Serialize)]
185/// struct Details { max_allowed: u64, actual: u64 }
186///
187/// const MAX_ALLOWED: u64 = 100;
188/// let actual = random::get_random() % 100u64;
189/// let details = Details { max_allowed: MAX_ALLOWED, actual };
190/// antithesis_sdk::assert_always_or_unreachable!(actual < MAX_ALLOWED, "Value in range", &details);
191/// ```
192#[macro_export]
193macro_rules! assert_always_or_unreachable {
194    ($condition:expr, $message:expr$(, $details:expr)?) => {
195        $crate::assert_helper!(
196            condition = $condition,
197            $message,
198            $(details = $details)?,
199            $crate::assert::AssertType::Always,
200            "AlwaysOrUnreachable",
201            must_hit = false
202        )
203    };
204    ($($rest:tt)*) => {
205        ::std::compile_error!(
206r#"Invalid syntax when calling macro `assert_always_or_unreachable`.
207Example usage:
208    `assert_always_or_unreachable!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
209"#
210        );
211    };
212}
213
214/// Assert that ``condition`` is true at least one time that this function was called.
215/// (If the assertion is never encountered, the test property will therefore fail.)
216/// This test property will be viewable in the ``Antithesis SDK: Sometimes`` group.
217///
218/// # Example
219///
220/// ```
221/// use serde::Serialize;
222/// use antithesis_sdk::{assert_sometimes, random};
223///
224/// // Only serialized when the assertion emits; see assert_always.
225/// #[derive(Serialize)]
226/// struct Details { max_allowed: u64, actual: u64 }
227///
228/// const MAX_ALLOWED: u64 = 100;
229/// let actual = random::get_random() % 120u64;
230/// let details = Details { max_allowed: MAX_ALLOWED, actual };
231/// antithesis_sdk::assert_sometimes!(actual > MAX_ALLOWED, "Value in range", &details);
232/// ```
233#[macro_export]
234macro_rules! assert_sometimes {
235    ($condition:expr, $message:expr$(, $details:expr)?) => {
236        $crate::assert_helper!(
237            condition = $condition,
238            $message,
239            $(details = $details)?,
240            $crate::assert::AssertType::Sometimes,
241            "Sometimes",
242            must_hit = true
243        )
244    };
245    ($($rest:tt)*) => {
246        ::std::compile_error!(
247r#"Invalid syntax when calling macro `assert_sometimes`.
248Example usage:
249    `assert_sometimes!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
250"#
251        );
252    };
253}
254
255/// Assert that a line of code is reached at least once.
256/// The corresponding test property will pass if this macro is ever called. (If it is never called the test property will therefore fail.)
257/// This test property will be viewable in the ``Antithesis SDK: Reachablity assertions`` group.
258///
259/// # Example
260///
261/// ```
262/// use serde::Serialize;
263/// use antithesis_sdk::{assert_reachable, random};
264///
265/// // Only serialized when the assertion emits; see assert_always.
266/// #[derive(Serialize)]
267/// struct Details { max_allowed: u64, actual: u64 }
268///
269/// const MAX_ALLOWED: u64 = 100;
270/// let actual = random::get_random() % 120u64;
271/// let details = Details { max_allowed: MAX_ALLOWED, actual };
272/// if (actual > MAX_ALLOWED) {
273///     antithesis_sdk::assert_reachable!("Value in range", &details);
274/// }
275/// ```
276#[macro_export]
277macro_rules! assert_reachable {
278    ($message:expr$(, $details:expr)?) => {
279        $crate::assert_helper!(
280            condition = true,
281            $message,
282            $(details = $details)?,
283            $crate::assert::AssertType::Reachability,
284            "Reachable",
285            must_hit = true
286        )
287    };
288    ($($rest:tt)*) => {
289        ::std::compile_error!(
290r#"Invalid syntax when calling macro `assert_reachable`.
291Example usage:
292    `assert_reachable!("assertion message (const &'static str)", &details_json_value_expr)`
293"#
294        );
295    };
296}
297
298/// Assert that a line of code is never reached.
299/// The corresponding test property will fail if this macro is ever called.
300/// (If it is never called the test property will therefore pass.)
301/// This test property will be viewable in the ``Antithesis SDK: Reachablity assertions`` group.
302///
303/// # Example
304///
305/// ```
306/// use serde::Serialize;
307/// use antithesis_sdk::{assert_unreachable, random};
308///
309/// // Only serialized when the assertion emits; see assert_always.
310/// #[derive(Serialize)]
311/// struct Details { max_allowed: u64, actual: u64 }
312///
313/// const MAX_ALLOWED: u64 = 100;
314/// let actual = random::get_random() % 120u64;
315/// let details = Details { max_allowed: MAX_ALLOWED, actual };
316/// if (actual > 120u64) {
317///     antithesis_sdk::assert_unreachable!("Value is above range", &details);
318/// }
319/// ```
320#[macro_export]
321macro_rules! assert_unreachable {
322    ($message:expr$(, $details:expr)?) => {
323        $crate::assert_helper!(
324            condition = false,
325            $message,
326            $(details = $details)?,
327            $crate::assert::AssertType::Reachability,
328            "Unreachable",
329            must_hit = false
330        )
331    };
332    ($($rest:tt)*) => {
333        ::std::compile_error!(
334r#"Invalid syntax when calling macro `assert_unreachable`.
335Example usage:
336    `assert_unreachable!("assertion message (const &'static str)", &details_json_value_expr)`
337"#
338        );
339    };
340}
341
342#[cfg(feature = "full")]
343#[doc(hidden)]
344#[macro_export]
345macro_rules! guidance_helper {
346    ($guidance_type:expr, $message:expr, $maximize:literal, $guidance_data:expr) => {
347        // `$message` must be const evaluable
348        const _: &str = $message;
349
350        $crate::function!(FUN_NAME);
351
352        use $crate::assert::guidance::{GuidanceCatalogInfo, GuidanceType};
353        #[$crate::linkme::distributed_slice($crate::assert::ANTITHESIS_GUIDANCE_CATALOG)]
354        #[linkme(crate = $crate::linkme)] // Refer to our re-exported linkme.
355        static GUIDANCE_CATALOG_ITEM: GuidanceCatalogInfo = GuidanceCatalogInfo {
356            guidance_type: $guidance_type,
357            message: $message,
358            id: $message,
359            class: ::std::module_path!(),
360            function: &FUN_NAME,
361            file: ::std::file!(),
362            begin_line: ::std::line!(),
363            begin_column: ::std::column!(),
364            maximize: $maximize,
365        };
366
367        $crate::assert::guidance::guidance_impl(
368            $guidance_type,
369            $message,
370            $message,
371            ::std::module_path!(),
372            *Lazy::force(&FUN_NAME),
373            ::std::file!(),
374            ::std::line!(),
375            ::std::column!(),
376            $maximize,
377            $guidance_data,
378            true,
379        )
380    };
381}
382
383#[cfg(feature = "full")]
384#[doc(hidden)]
385#[macro_export]
386macro_rules! numeric_guidance_helper {
387    ($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:expr$(, $details:expr)?) => {{
388        let left = $left;
389        let right = $right;
390        let details = &$crate::serde_json::json!({});
391        $(let details = $details;)?
392        let mut details = details.clone();
393        details["left"] = left.into();
394        details["right"] = right.into();
395        $assert!(left $op right, $message, &details);
396
397        // TODO: Right now it seems to be impossible for this macro to use the returned
398        // type of `diff` to instanciate the `T` in `Guard<T>`, which has to be
399        // explicitly provided for the static variable `GUARD`.
400        // Instead, we currently fix `T` to be `f64`, and ensure all implementations of `Diff` returns `f64`.
401        // Here are some related language limitations:
402        // - Although `typeof` is a reserved keyword in Rust, it is never implemented. See <https://stackoverflow.com/questions/64890774>.
403        // - Rust does not, and explicitly would not (see https://doc.rust-lang.org/reference/items/static-items.html#statics--generics), support generic static variable.
404        // - Type inference is not performed for static variable, i.e. `Guard<_>` is not allowed.
405        // - Some form of existential type can help, but that's only available in nightly Rust under feature `type_alias_impl_trait`.
406        //
407        // Other approaches I can think of either requires dynamic type tagging that has
408        // runtime overhead, or requires the user of the macro to explicitly provide the type,
409        // which is really not ergonomic and deviate from the APIs from other SDKs.
410        let diff = $crate::assert::guidance::Diff::diff(&left, right);
411        type Guard<T> = $crate::assert::guidance::Guard<$maximize, T>;
412        // TODO: Waiting for [type_alias_impl_trait](https://github.com/rust-lang/rust/issues/63063) to stabilize...
413        // type Distance = impl Minimal;
414        type Distance = f64;
415        static GUARD: Guard<Distance> = Guard::init();
416        if GUARD.should_emit(diff) {
417            let guidance_data = $crate::serde_json::json!({
418                "left": left,
419                "right": right,
420            });
421            $crate::guidance_helper!($crate::assert::guidance::GuidanceType::Numeric, $message, $maximize, guidance_data);
422        }
423    }};
424}
425
426#[cfg(not(feature = "full"))]
427#[doc(hidden)]
428#[macro_export]
429macro_rules! numeric_guidance_helper {
430    ($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:expr$(, $details:expr)?) => {{
431        // `$message` must be const evaluable
432        const _: &str = $message;
433        $assert!($left $op $right, $message$(, $details)?);
434    }};
435}
436
437#[cfg(feature = "full")]
438#[doc(hidden)]
439#[macro_export]
440macro_rules! boolean_guidance_helper {
441    ($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:expr$(, $details:expr)?) => {{
442        let details = &$crate::serde_json::json!({});
443        $(let details = $details;)?
444        let mut details = details.clone();
445        let (cond, guidance_data) = {
446            $(let $name = $cond;)*
447            $(details[::std::stringify!($name)] = $name.into();)*
448            (
449                if $all { true $(&& $name)* } else { false $(|| $name)* },
450                $crate::serde_json::json!({$(::std::stringify!($name): $name),*})
451            )
452        };
453        $assert!(cond, $message, &details);
454        $crate::guidance_helper!($crate::assert::guidance::GuidanceType::Boolean, $message, $all, guidance_data);
455    }};
456}
457
458#[cfg(not(feature = "full"))]
459#[doc(hidden)]
460#[macro_export]
461macro_rules! boolean_guidance_helper {
462    ($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:expr$(, $details:expr)?) => {{
463        let cond = {
464            $(let $name = $cond;)*
465            if $all { true $(&& $name)* } else { false $(|| $name)* }
466        };
467        $assert!(cond, $message$(, $details)?);
468    }};
469}
470
471/// `assert_always_greater_than(x, y, ...)` is mostly equivalent to `assert_always!(x > y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
472///
473/// Ensure that non-const-evaluable messages are rejected.
474///
475/// ```
476/// use serde_json::json;
477/// const MESSAGE: &str = concat!("x", " over y");
478/// antithesis_sdk::assert_always_greater_than!(2, 1, MESSAGE, &json!({}));
479/// ```
480///
481/// ```compile_fail
482/// use serde_json::json;
483/// antithesis_sdk::assert_always_greater_than!(2, 1, format!("{}", "x over y"), &json!({}));
484/// ```
485#[macro_export]
486macro_rules! assert_always_greater_than {
487    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
488        $crate::numeric_guidance_helper!($crate::assert_always, >, false, $left, $right, $message$(, $details)?)
489    };
490    ($($rest:tt)*) => {
491        ::std::compile_error!(
492r#"Invalid syntax when calling macro `assert_always_greater_than`.
493Example usage:
494    `assert_always_greater_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
495"#
496        );
497    };
498}
499
500/// `assert_always_greater_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_always!(x >= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
501#[macro_export]
502macro_rules! assert_always_greater_than_or_equal_to {
503    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
504        $crate::numeric_guidance_helper!($crate::assert_always, >=, false, $left, $right, $message$(, $details)?)
505    };
506    ($($rest:tt)*) => {
507        ::std::compile_error!(
508r#"Invalid syntax when calling macro `assert_always_greater_than_or_equal_to`.
509Example usage:
510    `assert_always_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
511"#
512        );
513    };
514}
515
516/// `assert_always_less_than(x, y, ...)` is mostly equivalent to `assert_always!(x < y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
517#[macro_export]
518macro_rules! assert_always_less_than {
519    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
520        $crate::numeric_guidance_helper!($crate::assert_always, <, true, $left, $right, $message$(, $details)?)
521    };
522    ($($rest:tt)*) => {
523        ::std::compile_error!(
524r#"Invalid syntax when calling macro `assert_always_less_than`.
525Example usage:
526    `assert_always_less_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
527"#
528        );
529    };
530}
531
532/// `assert_always_less_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_always!(x <= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
533#[macro_export]
534macro_rules! assert_always_less_than_or_equal_to {
535    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
536        $crate::numeric_guidance_helper!($crate::assert_always, <=, true, $left, $right, $message$(, $details)?)
537    };
538    ($($rest:tt)*) => {
539        ::std::compile_error!(
540r#"Invalid syntax when calling macro `assert_always_less_than_or_equal_to`.
541Example usage:
542    `assert_always_less_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
543"#
544        );
545    };
546}
547
548/// `assert_sometimes_greater_than(x, y, ...)` is mostly equivalent to `assert_sometimes!(x > y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
549#[macro_export]
550macro_rules! assert_sometimes_greater_than {
551    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
552        $crate::numeric_guidance_helper!($crate::assert_sometimes, >, true, $left, $right, $message$(, $details)?)
553    };
554    ($($rest:tt)*) => {
555        ::std::compile_error!(
556r#"Invalid syntax when calling macro `assert_sometimes_greater_than`.
557Example usage:
558    `assert_sometimes_greater_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
559"#
560        );
561    };
562}
563
564/// `assert_sometimes_greater_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_sometimes!(x >= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
565#[macro_export]
566macro_rules! assert_sometimes_greater_than_or_equal_to {
567    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
568        $crate::numeric_guidance_helper!($crate::assert_sometimes, >=, true, $left, $right, $message$(, $details)?)
569    };
570    ($($rest:tt)*) => {
571        ::std::compile_error!(
572r#"Invalid syntax when calling macro `assert_sometimes_greater_than_or_equal_to`.
573Example usage:
574    `assert_sometimes_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
575"#
576        );
577    };
578}
579
580/// `assert_sometimes_less_than(x, y, ...)` is mostly equivalent to `assert_sometimes!(x < y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
581#[macro_export]
582macro_rules! assert_sometimes_less_than {
583    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
584        $crate::numeric_guidance_helper!($crate::assert_sometimes, <, false, $left, $right, $message$(, $details)?)
585    };
586    ($($rest:tt)*) => {
587        ::std::compile_error!(
588r#"Invalid syntax when calling macro `assert_sometimes_less_than`.
589Example usage:
590    `assert_sometimes_less_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
591"#
592        );
593    };
594}
595
596/// `assert_sometimes_less_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_sometimes!(x <= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
597#[macro_export]
598macro_rules! assert_sometimes_less_than_or_equal_to {
599    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
600        $crate::numeric_guidance_helper!($crate::assert_sometimes, <=, false, $left, $right, $message$(, $details)?)
601    };
602    ($($rest:tt)*) => {
603        ::std::compile_error!(
604r#"Invalid syntax when calling macro `assert_sometimes_less_than_or_equal_to`.
605Example usage:
606    `assert_sometimes_less_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
607"#
608        );
609    };
610}
611
612/// `assert_always_some({a: x, b: y, ...})` is similar to `assert_always(x || y || ...)`, except:
613/// - Antithesis has more visibility to the individual propositions.
614/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
615/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
616///
617/// Ensure that non-const-evaluable messages are rejected.
618///
619/// ```
620/// use serde_json::json;
621/// const MESSAGE: &str = concat!("at least ", "one");
622/// antithesis_sdk::assert_always_some!({a: true, b: false}, MESSAGE, &json!({}));
623/// ```
624///
625/// ```compile_fail
626/// use serde_json::json;
627/// antithesis_sdk::assert_always_some!({a: true, b: false}, format!("{}", "at least one"), &json!({}));
628/// ```
629#[macro_export]
630macro_rules! assert_always_some {
631    ({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:expr$(, $details:expr)?) => {
632        $crate::boolean_guidance_helper!($crate::assert_always, false, {$($($name: $cond),+)?}, $message$(, $details)?);
633    };
634    ($($rest:tt)*) => {
635        ::std::compile_error!(
636r#"Invalid syntax when calling macro `assert_always_some`.
637Example usage:
638    `assert_always_some!({field1: cond1, field2: cond2, ...}, "assertion message (const &'static str)", &details_json_value_expr)`
639"#
640        );
641    };
642}
643
644/// `assert_sometimes_all({a: x, b: y, ...})` is similar to `assert_sometimes(x && y && ...)`, except:
645/// - Antithesis has more visibility to the individual propositions.
646/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
647/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
648#[macro_export]
649macro_rules! assert_sometimes_all {
650    ({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:expr$(, $details:expr)?) => {
651        $crate::boolean_guidance_helper!($crate::assert_sometimes, true, {$($($name: $cond),+)?}, $message$(, $details)?);
652    };
653    ($($rest:tt)*) => {
654        ::std::compile_error!(
655r#"Invalid syntax when calling macro `assert_sometimes_all`.
656Example usage:
657    `assert_sometimes_all!({field1: cond1, field2: cond2, ...}, "assertion message (const &'static str)", &details_json_value_expr)`
658"#
659        );
660    };
661}