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