Skip to content

Commit

Permalink
Add comments for rich assertions.
Browse files Browse the repository at this point in the history
  • Loading branch information
wsx-antithesis committed Sep 30, 2024
1 parent d2030ad commit 7a740eb
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 0 deletions.
18 changes: 18 additions & 0 deletions lib/src/assert/guidance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ use crate::internal;

use super::AntithesisLocationInfo;

// Types and traits that model the SDK filtering of numerical guidance reporting.
// For assertions like "always (x < y)", we would like to only report the most extreme
// violations seen so far, which is implemented by having a `Guard` that keep a maximizing
// watermark on the difference (x - y).
// The `AtomicMinMax` trait requirement allows multiple concurrent update to the watermark.

// NOTE: The structures setup in this modules allow `Guard` to be generic over the numeric
// type (or even any partially ordered type).
// But due to some limitation of stable Rust, we are only instanciating `Guard<f64>` by
// converting the result of all `x - y` into `f64`.
// See the impl `numeric_guidance_helper` for more details on the limitation.
// Once that is lifted, some implementations of `Diff` can be changed to truly take advantage
// of the zero-cost polymorphism that `Guard` provides.

pub struct Guard<const MAX: bool, T: AtomicMinMax> {
mark: T::Atomic,
}
Expand Down Expand Up @@ -71,6 +85,8 @@ macro_rules! impl_extremal_atomic {

impl_extremal_atomic! { (AtomicUsize, usize) (AtomicU8, u8) (AtomicU16, u16) (AtomicU32, u32) (AtomicU64, u64) (AtomicIsize, isize) (AtomicI8, i8) (AtomicI16, i16) (AtomicI32, i32) (AtomicI64, i64) }

// For atomic floats, their minimal/maximal elements are `-inf` and `+inf` respectively.

impl Extremal for AtomicF32 {
const MIN: Self = AtomicF32(AtomicU32::new(0xff800000));
const MAX: Self = AtomicF32(AtomicU32::new(0x7f800000));
Expand Down Expand Up @@ -106,6 +122,8 @@ macro_rules! impl_atomic_min_max_float {
impl AtomicMinMax for $t {
type Atomic = $atomic_t;

// TODO: Check the atomic orderings are used properly in general.
// Right now we are always passing SeqCst, which should be fine.
fn fetch_min(current: &Self::Atomic, other: Self, ordering: atomic::Ordering) -> Self {
<$t>::from_bits(current.0.fetch_update(ordering, ordering, |x| Some(<$t>::from_bits(x).min(other).to_bits())).unwrap())
}
Expand Down
29 changes: 29 additions & 0 deletions lib/src/assert/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,19 @@ macro_rules! numeric_guidance_helper {
"left": left,
"right": right,
});
// TODO: Right now it seems to be impossible for this macro to use the returned
// type of `diff` to instanciate the `T` in `Guard<T>`, which has to be
// explicitly provided for the static variable `GUARD`.
// Instead, we currently fix `T` to be `f64`, and ensure all implementations of `Diff` returns `f64`.
// Here are some related language limitations:
// - Although `typeof` is a reserved keyword in Rust, it is never implemented. See <https://stackoverflow.com/questions/64890774>.
// - Rust does not, and explicitly would not (see https://doc.rust-lang.org/reference/items/static-items.html#statics--generics), support generic static variable.
// - Type inference is not performed for static variable, i.e. `Guard<_>` is not allowed.
// - Some form of existential type can help, but that's only available in nightly Rust under feature `type_alias_impl_trait`.
//
// Other approaches I can think of either requires dynamic type tagging that has
// runtime overhead, or requires the user of the macro to explicitly provide the type,
// which is really not ergonomic and deviate from the APIs from other SDKs.
let diff = $crate::assert::guidance::Diff::diff(&left, right);
type Guard<T> = $crate::assert::guidance::Guard<$maximize, T>;
// TODO: Waiting for [type_alias_impl_trait](https://github.com/rust-lang/rust/issues/63063) to stabilize...
Expand Down Expand Up @@ -345,69 +358,85 @@ macro_rules! boolean_guidance_helper {
}};
}

/// `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}`.
#[macro_export]
macro_rules! assert_always_greater_than {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_always, >, false, $left, $right, $message, $details)
};
}

/// `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}`.
#[macro_export]
macro_rules! assert_always_greater_than_or_equal_to {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_always, >=, false, $left, $right, $message, $details)
};
}

/// `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}`.
#[macro_export]
macro_rules! assert_always_less_than {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_always, <, true, $left, $right, $message, $details)
};
}

/// `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}`.
#[macro_export]
macro_rules! assert_always_less_than_or_equal_to {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_always, <=, true, $left, $right, $message, $details)
};
}

/// `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}`.
#[macro_export]
macro_rules! assert_sometimes_greater_than {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_sometimes, >, true, $left, $right, $message, $details)
};
}

/// `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}`.
#[macro_export]
macro_rules! assert_sometimes_greater_than_or_equal_to {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_sometimes, >=, true, $left, $right, $message, $details)
};
}

/// `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}`.
#[macro_export]
macro_rules! assert_sometimes_less_than {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_sometimes, <, false, $left, $right, $message, $details)
};
}

/// `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}`.
#[macro_export]
macro_rules! assert_sometimes_less_than_or_equal_to {
($left:expr, $right:expr, $message:literal, $details:expr) => {
$crate::numeric_guidance_helper!($crate::assert_sometimes, <=, false, $left, $right, $message, $details)
};
}

/// `assert_always_some({a: x, b: y, ...})` is similar to `assert_always(x || y || ...)`, except:
/// - Antithesis has more visibility to the individual propositions.
/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
#[macro_export]
macro_rules! assert_always_some {
({$($name:ident: $cond:expr),*}, $message:literal, $details:expr) => {
$crate::boolean_guidance_helper!($crate::assert_always, false, {$($name: $cond),*}, $message, $details);
}
}

/// `assert_sometimes_all({a: x, b: y, ...})` is similar to `assert_sometimes(x && y && ...)`, except:
/// - Antithesis has more visibility to the individual propositions.
/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
#[macro_export]
macro_rules! assert_sometimes_all {
({$($name:ident: $cond:expr),*}, $message:literal, $details:expr) => {
Expand Down

0 comments on commit 7a740eb

Please sign in to comment.