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