| //! Traits related to testing. |
| //! |
| //! There are two main traits in this module: |
| //! |
| //! - `TupleCall`: implemented on tuples to allow calling them as function arguments. |
| //! - `CheckOutput`: implemented on anything that is an output type for validation against an |
| //! expected value. |
| |
| use std::panic::{RefUnwindSafe, UnwindSafe}; |
| use std::{fmt, panic}; |
| |
| use anyhow::{Context, anyhow, bail, ensure}; |
| use libm::support::Hexf; |
| |
| use crate::precision::CheckAction; |
| use crate::{ |
| CheckBasis, CheckCtx, Float, GeneratorKind, Int, MaybeOverride, SpecialCase, TestResult, |
| }; |
| |
| /// Trait for calling a function with a tuple as arguments. |
| /// |
| /// Implemented on the tuple with the function signature as the generic (so we can use the same |
| /// tuple for multiple signatures). |
| pub trait TupleCall<Func>: fmt::Debug { |
| type Output; |
| fn call(self, f: Func) -> Self::Output; |
| |
| /// Intercept panics and print the input to stderr before continuing. |
| fn call_intercept_panics(self, f: Func) -> Self::Output |
| where |
| Self: RefUnwindSafe + Copy, |
| Func: UnwindSafe, |
| { |
| let res = panic::catch_unwind(|| self.call(f)); |
| match res { |
| Ok(v) => v, |
| Err(e) => { |
| eprintln!("panic with the following input: {self:?}"); |
| panic::resume_unwind(e) |
| } |
| } |
| } |
| } |
| |
| /// A trait to implement on any output type so we can verify it in a generic way. |
| pub trait CheckOutput<Input>: Sized { |
| /// Validate `self` (actual) and `expected` are the same. |
| /// |
| /// `input` is only used here for error messages. |
| fn validate(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult; |
| } |
| |
| /// A helper trait to print something as hex with the correct number of nibbles, e.g. a `u32` |
| /// will always print with `0x` followed by 8 digits. |
| /// |
| /// This is only used for printing errors so allocating is okay. |
| pub trait Hex: Copy { |
| /// Hex integer syntax. |
| fn hex(self) -> String; |
| /// Hex float syntax. |
| fn hexf(self) -> String; |
| } |
| |
| /* implement `TupleCall` */ |
| |
| impl<T1, R> TupleCall<fn(T1) -> R> for (T1,) |
| where |
| T1: fmt::Debug, |
| { |
| type Output = R; |
| |
| fn call(self, f: fn(T1) -> R) -> Self::Output { |
| f(self.0) |
| } |
| } |
| |
| impl<T1, T2, R> TupleCall<fn(T1, T2) -> R> for (T1, T2) |
| where |
| T1: fmt::Debug, |
| T2: fmt::Debug, |
| { |
| type Output = R; |
| |
| fn call(self, f: fn(T1, T2) -> R) -> Self::Output { |
| f(self.0, self.1) |
| } |
| } |
| |
| impl<T1, T2, R> TupleCall<fn(T1, &mut T2) -> R> for (T1,) |
| where |
| T1: fmt::Debug, |
| T2: fmt::Debug + Default, |
| { |
| type Output = (R, T2); |
| |
| fn call(self, f: fn(T1, &mut T2) -> R) -> Self::Output { |
| let mut t2 = T2::default(); |
| (f(self.0, &mut t2), t2) |
| } |
| } |
| |
| impl<T1, T2, T3, R> TupleCall<fn(T1, T2, T3) -> R> for (T1, T2, T3) |
| where |
| T1: fmt::Debug, |
| T2: fmt::Debug, |
| T3: fmt::Debug, |
| { |
| type Output = R; |
| |
| fn call(self, f: fn(T1, T2, T3) -> R) -> Self::Output { |
| f(self.0, self.1, self.2) |
| } |
| } |
| |
| impl<T1, T2, T3, R> TupleCall<fn(T1, T2, &mut T3) -> R> for (T1, T2) |
| where |
| T1: fmt::Debug, |
| T2: fmt::Debug, |
| T3: fmt::Debug + Default, |
| { |
| type Output = (R, T3); |
| |
| fn call(self, f: fn(T1, T2, &mut T3) -> R) -> Self::Output { |
| let mut t3 = T3::default(); |
| (f(self.0, self.1, &mut t3), t3) |
| } |
| } |
| |
| impl<T1, T2, T3> TupleCall<for<'a> fn(T1, &'a mut T2, &'a mut T3)> for (T1,) |
| where |
| T1: fmt::Debug, |
| T2: fmt::Debug + Default, |
| T3: fmt::Debug + Default, |
| { |
| type Output = (T2, T3); |
| |
| fn call(self, f: for<'a> fn(T1, &'a mut T2, &'a mut T3)) -> Self::Output { |
| let mut t2 = T2::default(); |
| let mut t3 = T3::default(); |
| f(self.0, &mut t2, &mut t3); |
| (t2, t3) |
| } |
| } |
| |
| /* implement `Hex` */ |
| |
| impl<T1> Hex for (T1,) |
| where |
| T1: Hex, |
| { |
| fn hex(self) -> String { |
| format!("({},)", self.0.hex()) |
| } |
| |
| fn hexf(self) -> String { |
| format!("({},)", self.0.hexf()) |
| } |
| } |
| |
| impl<T1, T2> Hex for (T1, T2) |
| where |
| T1: Hex, |
| T2: Hex, |
| { |
| fn hex(self) -> String { |
| format!("({}, {})", self.0.hex(), self.1.hex()) |
| } |
| |
| fn hexf(self) -> String { |
| format!("({}, {})", self.0.hexf(), self.1.hexf()) |
| } |
| } |
| |
| impl<T1, T2, T3> Hex for (T1, T2, T3) |
| where |
| T1: Hex, |
| T2: Hex, |
| T3: Hex, |
| { |
| fn hex(self) -> String { |
| format!("({}, {}, {})", self.0.hex(), self.1.hex(), self.2.hex()) |
| } |
| |
| fn hexf(self) -> String { |
| format!("({}, {}, {})", self.0.hexf(), self.1.hexf(), self.2.hexf()) |
| } |
| } |
| |
| /* trait implementations for ints */ |
| |
| macro_rules! impl_int { |
| ($($ty:ty),*) => { |
| $( |
| impl Hex for $ty { |
| fn hex(self) -> String { |
| format!("{self:#0width$x}", width = ((Self::BITS / 4) + 2) as usize) |
| } |
| |
| fn hexf(self) -> String { |
| String::new() |
| } |
| } |
| |
| impl<Input> $crate::CheckOutput<Input> for $ty |
| where |
| Input: Hex + fmt::Debug, |
| SpecialCase: MaybeOverride<Input>, |
| { |
| fn validate<'a>( |
| self, |
| expected: Self, |
| input: Input, |
| ctx: &$crate::CheckCtx, |
| ) -> TestResult { |
| validate_int(self, expected, input, ctx) |
| } |
| } |
| )* |
| }; |
| } |
| |
| fn validate_int<I, Input>(actual: I, expected: I, input: Input, ctx: &CheckCtx) -> TestResult |
| where |
| I: Int + Hex, |
| Input: Hex + fmt::Debug, |
| SpecialCase: MaybeOverride<Input>, |
| { |
| let (result, xfail_msg) = match SpecialCase::check_int(input, actual, expected, ctx) { |
| // `require_biteq` forbids overrides. |
| _ if ctx.gen_kind == GeneratorKind::List => (actual == expected, None), |
| CheckAction::AssertSuccess => (actual == expected, None), |
| CheckAction::AssertFailure(msg) => (actual != expected, Some(msg)), |
| CheckAction::Custom(res) => return res, |
| CheckAction::Skip => return Ok(()), |
| CheckAction::AssertWithUlp(_) => panic!("ulp has no meaning for integer checks"), |
| }; |
| |
| let make_xfail_msg = || match xfail_msg { |
| Some(m) => format!( |
| "expected failure but test passed. Does an XFAIL need to be updated?\n\ |
| failed at: {m}", |
| ), |
| None => String::new(), |
| }; |
| |
| anyhow::ensure!( |
| result, |
| "\ |
| \n input: {input:?} {ibits}\ |
| \n expected: {expected:<22?} {expbits}\ |
| \n actual: {actual:<22?} {actbits}\ |
| \n {msg}\ |
| ", |
| actbits = actual.hex(), |
| expbits = expected.hex(), |
| ibits = input.hex(), |
| msg = make_xfail_msg() |
| ); |
| |
| Ok(()) |
| } |
| |
| impl_int!(u32, i32, u64, i64); |
| |
| /* trait implementations for floats */ |
| |
| macro_rules! impl_float { |
| ($($ty:ty),*) => { |
| $( |
| impl Hex for $ty { |
| fn hex(self) -> String { |
| format!( |
| "{:#0width$x}", |
| self.to_bits(), |
| width = ((Self::BITS / 4) + 2) as usize |
| ) |
| } |
| |
| fn hexf(self) -> String { |
| format!("{}", Hexf(self)) |
| } |
| } |
| |
| impl<Input> $crate::CheckOutput<Input> for $ty |
| where |
| Input: Hex + fmt::Debug, |
| SpecialCase: MaybeOverride<Input>, |
| { |
| fn validate<'a>( |
| self, |
| expected: Self, |
| input: Input, |
| ctx: &$crate::CheckCtx, |
| ) -> TestResult { |
| validate_float(self, expected, input, ctx) |
| } |
| } |
| )* |
| }; |
| } |
| |
| fn validate_float<F, Input>(actual: F, expected: F, input: Input, ctx: &CheckCtx) -> TestResult |
| where |
| F: Float + Hex, |
| Input: Hex + fmt::Debug, |
| u32: TryFrom<F::SignedInt, Error: fmt::Debug>, |
| SpecialCase: MaybeOverride<Input>, |
| { |
| let mut assert_failure_msg = None; |
| |
| // Create a wrapper function so we only need to `.with_context` once. |
| let mut inner = || -> TestResult { |
| let mut allowed_ulp = ctx.ulp; |
| |
| match SpecialCase::check_float(input, actual, expected, ctx) { |
| // Forbid overrides if the items came from an explicit list |
| _ if ctx.gen_kind == GeneratorKind::List => (), |
| CheckAction::AssertSuccess => (), |
| CheckAction::AssertFailure(msg) => assert_failure_msg = Some(msg), |
| CheckAction::Custom(res) => return res, |
| CheckAction::Skip => return Ok(()), |
| CheckAction::AssertWithUlp(ulp_override) => allowed_ulp = ulp_override, |
| }; |
| |
| // Check when both are NaNs |
| if actual.is_nan() && expected.is_nan() { |
| // Don't assert NaN bitwise equality if: |
| // |
| // * Testing against MPFR (there is a single NaN representation) |
| // * Testing against Musl except for explicit tests (Musl does some NaN quieting) |
| // |
| // In these cases, just the check that actual and expected are both NaNs is |
| // sufficient. |
| let skip_nan_biteq = ctx.basis == CheckBasis::Mpfr |
| || (ctx.basis == CheckBasis::Musl && ctx.gen_kind != GeneratorKind::List); |
| |
| if !skip_nan_biteq { |
| ensure!(actual.biteq(expected), "mismatched NaN bitpatterns"); |
| } |
| |
| // By default, NaNs have nothing special to check. |
| return Ok(()); |
| } else if actual.is_nan() || expected.is_nan() { |
| // Check when only one is a NaN |
| bail!("real value != NaN") |
| } |
| |
| // Make sure that the signs are the same before checing ULP to avoid wraparound |
| let act_sig = actual.signum(); |
| let exp_sig = expected.signum(); |
| ensure!( |
| act_sig == exp_sig, |
| "mismatched signs {act_sig:?} {exp_sig:?}" |
| ); |
| |
| if actual.is_infinite() ^ expected.is_infinite() { |
| bail!("mismatched infinities"); |
| } |
| |
| let act_bits = actual.to_bits().signed(); |
| let exp_bits = expected.to_bits().signed(); |
| |
| let ulp_diff = act_bits.checked_sub(exp_bits).unwrap().abs(); |
| |
| let ulp_u32 = u32::try_from(ulp_diff) |
| .map_err(|e| anyhow!("{e:?}: ulp of {ulp_diff} exceeds u32::MAX"))?; |
| |
| ensure!(ulp_u32 <= allowed_ulp, "ulp {ulp_diff} > {allowed_ulp}",); |
| |
| Ok(()) |
| }; |
| |
| let mut res = inner(); |
| |
| if let Some(msg) = assert_failure_msg { |
| // Invert `Ok` and `Err` if the test is an xfail. |
| if res.is_ok() { |
| let e = anyhow!( |
| "expected failure but test passed. Does an XFAIL need to be updated?\n\ |
| failed at: {msg}", |
| ); |
| res = Err(e) |
| } else { |
| res = Ok(()) |
| } |
| } |
| |
| res.with_context(|| { |
| format!( |
| "\ |
| \n input: {input:?}\ |
| \n as hex: {ihex}\ |
| \n as bits: {ibits}\ |
| \n expected: {expected:<22?} {exphex} {expbits}\ |
| \n actual: {actual:<22?} {acthex} {actbits}\ |
| ", |
| ihex = input.hexf(), |
| ibits = input.hex(), |
| exphex = expected.hexf(), |
| expbits = expected.hex(), |
| actbits = actual.hex(), |
| acthex = actual.hexf(), |
| ) |
| }) |
| } |
| |
| impl_float!(f32, f64); |
| |
| #[cfg(f16_enabled)] |
| impl_float!(f16); |
| |
| #[cfg(f128_enabled)] |
| impl_float!(f128); |
| |
| /* trait implementations for compound types */ |
| |
| /// Implement `CheckOutput` for combinations of types. |
| macro_rules! impl_tuples { |
| ($(($a:ty, $b:ty);)*) => { |
| $( |
| impl<Input> CheckOutput<Input> for ($a, $b) |
| where |
| Input: Hex + fmt::Debug, |
| SpecialCase: MaybeOverride<Input>, |
| { |
| fn validate<'a>( |
| self, |
| expected: Self, |
| input: Input, |
| ctx: &CheckCtx, |
| ) -> TestResult { |
| self.0.validate(expected.0, input, ctx) |
| .and_then(|()| self.1.validate(expected.1, input, ctx)) |
| .with_context(|| format!( |
| "full context:\ |
| \n input: {input:?} {ibits}\ |
| \n as hex: {ihex}\ |
| \n as bits: {ibits}\ |
| \n expected: {expected:?} {expbits}\ |
| \n actual: {self:?} {actbits}\ |
| ", |
| ihex = input.hexf(), |
| ibits = input.hex(), |
| expbits = expected.hex(), |
| actbits = self.hex(), |
| )) |
| } |
| } |
| )* |
| }; |
| } |
| |
| impl_tuples!( |
| (f32, i32); |
| (f64, i32); |
| (f32, f32); |
| (f64, f64); |
| ); |