From 4bf116f146b9f3d918e9d8be74616eb044fd5ead Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 6 Feb 2025 04:03:11 +0000 Subject: [PATCH] Add an integration test that verifies a list of cases We need someplace to collect known failures, previous regressions, edge cases that are difficult to construct from generics, and similar. Introduce this here. --- .../libm/crates/libm-test/src/gen.rs | 1 + .../crates/libm-test/src/gen/case_list.rs | 686 ++++++++++++++++++ .../libm/crates/libm-test/src/run_cfg.rs | 8 +- .../libm-test/tests/compare_built_musl.rs | 11 +- .../crates/libm-test/tests/multiprecision.rs | 11 +- .../libm/crates/libm-test/tests/standalone.rs | 38 + 6 files changed, 750 insertions(+), 5 deletions(-) create mode 100644 library/compiler-builtins/libm/crates/libm-test/src/gen/case_list.rs create mode 100644 library/compiler-builtins/libm/crates/libm-test/tests/standalone.rs diff --git a/library/compiler-builtins/libm/crates/libm-test/src/gen.rs b/library/compiler-builtins/libm/crates/libm-test/src/gen.rs index e0a7f5766805..89ca09a7a0b8 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/gen.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/gen.rs @@ -1,5 +1,6 @@ //! Different generators that can create random or systematic bit patterns. +pub mod case_list; pub mod edge_cases; pub mod random; pub mod spaced; diff --git a/library/compiler-builtins/libm/crates/libm-test/src/gen/case_list.rs b/library/compiler-builtins/libm/crates/libm-test/src/gen/case_list.rs new file mode 100644 index 000000000000..9720f68e9612 --- /dev/null +++ b/library/compiler-builtins/libm/crates/libm-test/src/gen/case_list.rs @@ -0,0 +1,686 @@ +//! Test cases to verify specific values. +//! +//! Each routine can have a set of inputs and, optinoally, outputs. If an output is provided, it +//! will be used to check against. If only inputs are provided, the case will be checked against +//! a basis. +//! +//! This is useful for adding regression tests or expected failures. + +use crate::{CheckBasis, CheckCtx, GeneratorKind, MathOp, op}; + +pub struct TestCase { + pub input: Op::RustArgs, + pub output: Option, +} + +impl TestCase { + #[expect(dead_code)] + fn append_inputs(v: &mut Vec, l: &[Op::RustArgs]) { + v.extend(l.iter().copied().map(|input| Self { input, output: None })); + } + + fn append_pairs(v: &mut Vec, l: &[(Op::RustArgs, Option)]) + where + Op::RustRet: Copy, + { + v.extend(l.iter().copied().map(|(input, output)| Self { input, output })); + } +} + +fn acos_cases() -> Vec> { + vec![] +} + +fn acosf_cases() -> Vec> { + vec![] +} + +fn acosh_cases() -> Vec> { + vec![] +} + +fn acoshf_cases() -> Vec> { + vec![] +} + +fn asin_cases() -> Vec> { + vec![] +} + +fn asinf_cases() -> Vec> { + vec![] +} + +fn asinh_cases() -> Vec> { + vec![] +} + +fn asinhf_cases() -> Vec> { + vec![] +} + +fn atan_cases() -> Vec> { + vec![] +} + +fn atan2_cases() -> Vec> { + vec![] +} + +fn atan2f_cases() -> Vec> { + vec![] +} + +fn atanf_cases() -> Vec> { + vec![] +} + +fn atanh_cases() -> Vec> { + vec![] +} + +fn atanhf_cases() -> Vec> { + vec![] +} + +fn cbrt_cases() -> Vec> { + vec![] +} + +fn cbrtf_cases() -> Vec> { + vec![] +} + +fn ceil_cases() -> Vec> { + vec![] +} + +fn ceilf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn ceilf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn ceilf16_cases() -> Vec> { + vec![] +} + +fn copysign_cases() -> Vec> { + vec![] +} + +fn copysignf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn copysignf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn copysignf16_cases() -> Vec> { + vec![] +} + +fn cos_cases() -> Vec> { + vec![] +} + +fn cosf_cases() -> Vec> { + vec![] +} + +fn cosh_cases() -> Vec> { + vec![] +} + +fn coshf_cases() -> Vec> { + vec![] +} + +fn erf_cases() -> Vec> { + vec![] +} + +fn erfc_cases() -> Vec> { + vec![] +} + +fn erfcf_cases() -> Vec> { + vec![] +} + +fn erff_cases() -> Vec> { + vec![] +} + +fn exp_cases() -> Vec> { + vec![] +} + +fn exp10_cases() -> Vec> { + vec![] +} + +fn exp10f_cases() -> Vec> { + vec![] +} + +fn exp2_cases() -> Vec> { + vec![] +} + +fn exp2f_cases() -> Vec> { + vec![] +} + +fn expf_cases() -> Vec> { + vec![] +} + +fn expm1_cases() -> Vec> { + vec![] +} + +fn expm1f_cases() -> Vec> { + vec![] +} + +fn fabs_cases() -> Vec> { + vec![] +} + +fn fabsf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn fabsf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn fabsf16_cases() -> Vec> { + vec![] +} + +fn fdim_cases() -> Vec> { + vec![] +} + +fn fdimf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn fdimf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn fdimf16_cases() -> Vec> { + vec![] +} + +fn floor_cases() -> Vec> { + vec![] +} + +fn floorf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn floorf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn floorf16_cases() -> Vec> { + vec![] +} + +fn fma_cases() -> Vec> { + let mut v = vec![]; + TestCase::append_pairs( + &mut v, + &[ + // Previously failure with incorrect sign + ((5e-324, -5e-324, 0.0), Some(-0.0)), + ], + ); + v +} + +fn fmaf_cases() -> Vec> { + vec![] +} + +fn fmax_cases() -> Vec> { + vec![] +} + +fn fmaxf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn fmaxf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn fmaxf16_cases() -> Vec> { + vec![] +} + +fn fmin_cases() -> Vec> { + vec![] +} + +fn fminf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn fminf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn fminf16_cases() -> Vec> { + vec![] +} + +fn fmod_cases() -> Vec> { + vec![] +} + +fn fmodf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn fmodf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn fmodf16_cases() -> Vec> { + vec![] +} + +fn frexp_cases() -> Vec> { + vec![] +} + +fn frexpf_cases() -> Vec> { + vec![] +} + +fn hypot_cases() -> Vec> { + vec![] +} + +fn hypotf_cases() -> Vec> { + vec![] +} + +fn ilogb_cases() -> Vec> { + vec![] +} + +fn ilogbf_cases() -> Vec> { + vec![] +} + +fn j0_cases() -> Vec> { + vec![] +} + +fn j0f_cases() -> Vec> { + vec![] +} + +fn j1_cases() -> Vec> { + vec![] +} + +fn j1f_cases() -> Vec> { + vec![] +} + +fn jn_cases() -> Vec> { + vec![] +} + +fn jnf_cases() -> Vec> { + vec![] +} + +fn ldexp_cases() -> Vec> { + vec![] +} + +fn ldexpf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn ldexpf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn ldexpf16_cases() -> Vec> { + vec![] +} + +fn lgamma_cases() -> Vec> { + vec![] +} + +fn lgamma_r_cases() -> Vec> { + vec![] +} + +fn lgammaf_cases() -> Vec> { + vec![] +} + +fn lgammaf_r_cases() -> Vec> { + vec![] +} + +fn log_cases() -> Vec> { + vec![] +} + +fn log10_cases() -> Vec> { + vec![] +} + +fn log10f_cases() -> Vec> { + vec![] +} + +fn log1p_cases() -> Vec> { + vec![] +} + +fn log1pf_cases() -> Vec> { + vec![] +} + +fn log2_cases() -> Vec> { + vec![] +} + +fn log2f_cases() -> Vec> { + vec![] +} + +fn logf_cases() -> Vec> { + vec![] +} + +fn modf_cases() -> Vec> { + vec![] +} + +fn modff_cases() -> Vec> { + vec![] +} + +fn nextafter_cases() -> Vec> { + vec![] +} + +fn nextafterf_cases() -> Vec> { + vec![] +} + +fn pow_cases() -> Vec> { + vec![] +} + +fn powf_cases() -> Vec> { + vec![] +} + +fn remainder_cases() -> Vec> { + vec![] +} + +fn remainderf_cases() -> Vec> { + vec![] +} + +fn remquo_cases() -> Vec> { + vec![] +} + +fn remquof_cases() -> Vec> { + vec![] +} + +fn rint_cases() -> Vec> { + vec![] +} + +fn rintf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn rintf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn rintf16_cases() -> Vec> { + vec![] +} + +fn round_cases() -> Vec> { + vec![] +} + +fn roundf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn roundf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn roundf16_cases() -> Vec> { + vec![] +} + +fn scalbn_cases() -> Vec> { + vec![] +} + +fn scalbnf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn scalbnf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn scalbnf16_cases() -> Vec> { + vec![] +} + +fn sin_cases() -> Vec> { + vec![] +} + +fn sincos_cases() -> Vec> { + vec![] +} + +fn sincosf_cases() -> Vec> { + vec![] +} + +fn sinf_cases() -> Vec> { + vec![] +} + +fn sinh_cases() -> Vec> { + vec![] +} + +fn sinhf_cases() -> Vec> { + vec![] +} + +fn sqrt_cases() -> Vec> { + vec![] +} + +fn sqrtf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn sqrtf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn sqrtf16_cases() -> Vec> { + vec![] +} + +fn tan_cases() -> Vec> { + vec![] +} + +fn tanf_cases() -> Vec> { + vec![] +} + +fn tanh_cases() -> Vec> { + vec![] +} + +fn tanhf_cases() -> Vec> { + vec![] +} + +fn tgamma_cases() -> Vec> { + vec![] +} + +fn tgammaf_cases() -> Vec> { + vec![] +} + +fn trunc_cases() -> Vec> { + vec![] +} + +fn truncf_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn truncf128_cases() -> Vec> { + vec![] +} + +#[cfg(f16_enabled)] +fn truncf16_cases() -> Vec> { + vec![] +} + +fn y0_cases() -> Vec> { + vec![] +} + +fn y0f_cases() -> Vec> { + vec![] +} + +fn y1_cases() -> Vec> { + vec![] +} + +fn y1f_cases() -> Vec> { + vec![] +} + +fn yn_cases() -> Vec> { + vec![] +} + +fn ynf_cases() -> Vec> { + vec![] +} + +pub trait CaseListInput: MathOp + Sized { + fn get_cases() -> Vec>; +} + +macro_rules! impl_case_list { + ( + fn_name: $fn_name:ident, + attrs: [$($attr:meta),*], + ) => { + paste::paste! { + $(#[$attr])* + impl CaseListInput for crate::op::$fn_name::Routine { + fn get_cases() -> Vec> { + [< $fn_name _cases >]() + } + } + } + }; +} + +libm_macros::for_each_function! { + callback: impl_case_list, +} + +/// This is the test generator for standalone tests, i.e. those with no basis. For this, it +/// only extracts tests with a known output. +pub fn get_test_cases_standalone( + ctx: &CheckCtx, +) -> impl Iterator + use<'_, Op> +where + Op: MathOp + CaseListInput, +{ + assert_eq!(ctx.basis, CheckBasis::None); + assert_eq!(ctx.gen_kind, GeneratorKind::List); + Op::get_cases().into_iter().filter_map(|x| x.output.map(|o| (x.input, o))) +} + +/// Opposite of the above; extract only test cases that don't have a known output, to be run +/// against a basis. +pub fn get_test_cases_basis( + ctx: &CheckCtx, +) -> (impl Iterator + use<'_, Op>, u64) +where + Op: MathOp + CaseListInput, +{ + assert_ne!(ctx.basis, CheckBasis::None); + assert_eq!(ctx.gen_kind, GeneratorKind::List); + + let cases = Op::get_cases(); + let count: u64 = cases.iter().filter(|case| case.output.is_none()).count().try_into().unwrap(); + + (cases.into_iter().filter(|x| x.output.is_none()).map(|x| x.input), count) +} diff --git a/library/compiler-builtins/libm/crates/libm-test/src/run_cfg.rs b/library/compiler-builtins/libm/crates/libm-test/src/run_cfg.rs index 783142e37836..5728c3b2e5b0 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/run_cfg.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/run_cfg.rs @@ -102,6 +102,7 @@ pub enum GeneratorKind { Extensive, QuickSpaced, Random, + List, } /// A list of all functions that should get extensive tests. @@ -219,8 +220,8 @@ pub fn iteration_count(ctx: &CheckCtx, argnum: usize) -> u64 { GeneratorKind::QuickSpaced => domain_iter_count, GeneratorKind::Random => random_iter_count, GeneratorKind::Extensive => extensive_max_iterations(), - GeneratorKind::EdgeCases => { - unimplemented!("edge case tests shoudn't need `iteration_count`") + GeneratorKind::EdgeCases | GeneratorKind::List => { + unimplemented!("shoudn't need `iteration_count` for {:?}", ctx.gen_kind) } }; @@ -269,7 +270,7 @@ pub fn iteration_count(ctx: &CheckCtx, argnum: usize) -> u64 { GeneratorKind::Random => { format!(" using `{SEED_ENV}={}`", str::from_utf8(SEED.as_slice()).unwrap()) } - GeneratorKind::EdgeCases => unreachable!(), + GeneratorKind::EdgeCases | GeneratorKind::List => unimplemented!(), }; test_log(&format!( @@ -310,6 +311,7 @@ pub fn int_range(ctx: &CheckCtx, argnum: usize) -> RangeInclusive { GeneratorKind::Extensive => extensive_range, GeneratorKind::QuickSpaced | GeneratorKind::Random => non_extensive_range, GeneratorKind::EdgeCases => extensive_range, + GeneratorKind::List => unimplemented!("shoudn't need range for {:?}", ctx.gen_kind), } } diff --git a/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs b/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs index c8beaffc3af2..927cb25afc47 100644 --- a/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs +++ b/library/compiler-builtins/libm/crates/libm-test/tests/compare_built_musl.rs @@ -9,7 +9,7 @@ // There are some targets we can't build musl for #![cfg(feature = "build-musl")] -use libm_test::gen::{edge_cases, random, spaced}; +use libm_test::gen::{case_list, edge_cases, random, spaced}; use libm_test::{CheckBasis, CheckCtx, CheckOutput, GeneratorKind, MathOp, TupleCall}; const BASIS: CheckBasis = CheckBasis::Musl; @@ -34,6 +34,15 @@ macro_rules! musl_tests { attrs: [$($attr:meta),*], ) => { paste::paste! { + #[test] + $(#[$attr])* + fn [< musl_case_list_ $fn_name >]() { + type Op = libm_test::op::$fn_name::Routine; + let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::List); + let cases = case_list::get_test_cases_basis::(&ctx).0; + musl_runner::(&ctx, cases, musl_math_sys::$fn_name); + } + #[test] $(#[$attr])* fn [< musl_random_ $fn_name >]() { diff --git a/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs b/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs index 0d5c5e60c20c..fd1f11610f52 100644 --- a/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs +++ b/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs @@ -2,7 +2,7 @@ #![cfg(feature = "build-mpfr")] -use libm_test::gen::{edge_cases, random, spaced}; +use libm_test::gen::{case_list, edge_cases, random, spaced}; use libm_test::mpfloat::MpOp; use libm_test::{CheckBasis, CheckCtx, CheckOutput, GeneratorKind, MathOp, TupleCall}; @@ -24,6 +24,15 @@ macro_rules! mp_tests { attrs: [$($attr:meta),*], ) => { paste::paste! { + #[test] + $(#[$attr])* + fn [< mp_case_list_ $fn_name >]() { + type Op = libm_test::op::$fn_name::Routine; + let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::List); + let cases = case_list::get_test_cases_basis::(&ctx).0; + mp_runner::(&ctx, cases); + } + #[test] $(#[$attr])* fn [< mp_random_ $fn_name >]() { diff --git a/library/compiler-builtins/libm/crates/libm-test/tests/standalone.rs b/library/compiler-builtins/libm/crates/libm-test/tests/standalone.rs new file mode 100644 index 000000000000..d6417acac2bd --- /dev/null +++ b/library/compiler-builtins/libm/crates/libm-test/tests/standalone.rs @@ -0,0 +1,38 @@ +//! Test cases that have both an input and an output, so do not require a basis. + +use libm_test::gen::case_list; +use libm_test::{CheckBasis, CheckCtx, CheckOutput, GeneratorKind, MathOp, TupleCall}; + +const BASIS: CheckBasis = CheckBasis::None; + +fn standalone_runner( + ctx: &CheckCtx, + cases: impl Iterator, +) { + for (input, expected) in cases { + let crate_res = input.call(Op::ROUTINE); + crate_res.validate(expected, input, ctx).unwrap(); + } +} + +macro_rules! mp_tests { + ( + fn_name: $fn_name:ident, + attrs: [$($attr:meta),*], + ) => { + paste::paste! { + #[test] + $(#[$attr])* + fn [< standalone_ $fn_name >]() { + type Op = libm_test::op::$fn_name::Routine; + let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::List); + let cases = case_list::get_test_cases_standalone::(&ctx); + standalone_runner::(&ctx, cases); + } + } + }; +} + +libm_macros::for_each_function! { + callback: mp_tests, +}