//! 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: 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: 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 TupleCall R> for (T1,) where T1: fmt::Debug, { type Output = R; fn call(self, f: fn(T1) -> R) -> Self::Output { f(self.0) } } impl TupleCall 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 TupleCall 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 TupleCall 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 TupleCall 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 TupleCall 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 Hex for (T1,) where T1: Hex, { fn hex(self) -> String { format!("({},)", self.0.hex()) } fn hexf(self) -> String { format!("({},)", self.0.hexf()) } } impl 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 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 $crate::CheckOutput for $ty where Input: Hex + fmt::Debug, SpecialCase: MaybeOverride, { fn validate<'a>( self, expected: Self, input: Input, ctx: &$crate::CheckCtx, ) -> TestResult { validate_int(self, expected, input, ctx) } } )* }; } fn validate_int(actual: I, expected: I, input: Input, ctx: &CheckCtx) -> TestResult where I: Int + Hex, Input: Hex + fmt::Debug, SpecialCase: MaybeOverride, { 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 $crate::CheckOutput for $ty where Input: Hex + fmt::Debug, SpecialCase: MaybeOverride, { fn validate<'a>( self, expected: Self, input: Input, ctx: &$crate::CheckCtx, ) -> TestResult { validate_float(self, expected, input, ctx) } } )* }; } fn validate_float(actual: F, expected: F, input: Input, ctx: &CheckCtx) -> TestResult where F: Float + Hex, Input: Hex + fmt::Debug, u32: TryFrom, SpecialCase: MaybeOverride, { 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 CheckOutput for ($a, $b) where Input: Hex + fmt::Debug, SpecialCase: MaybeOverride, { 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); );