|
1 | 1 | //! Configuration for how tests get run.
|
2 | 2 |
|
3 |
| -#![allow(unused)] |
4 |
| - |
5 |
| -use std::collections::BTreeMap; |
6 | 3 | use std::env;
|
| 4 | +use std::ops::RangeInclusive; |
7 | 5 | use std::sync::LazyLock;
|
8 | 6 |
|
9 |
| -use crate::{BaseName, FloatTy, Identifier, op}; |
| 7 | +use crate::{BaseName, FloatTy, Identifier, test_log}; |
10 | 8 |
|
| 9 | +/// The environment variable indicating which extensive tests should be run. |
11 | 10 | pub const EXTENSIVE_ENV: &str = "LIBM_EXTENSIVE_TESTS";
|
12 | 11 |
|
13 | 12 | /// Context passed to [`CheckOutput`].
|
@@ -49,3 +48,199 @@ pub enum CheckBasis {
|
49 | 48 | /// Check against infinite precision (MPFR).
|
50 | 49 | Mpfr,
|
51 | 50 | }
|
| 51 | + |
| 52 | +/// The different kinds of generators that provide test input. |
| 53 | +#[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 54 | +pub enum GeneratorKind { |
| 55 | + Domain, |
| 56 | + Random, |
| 57 | +} |
| 58 | + |
| 59 | +/// Configuration for how a test should be run. |
| 60 | +#[derive(Clone, Debug, PartialEq, Eq)] |
| 61 | +pub enum TestAction { |
| 62 | + /// Run the test with no specific configuration. |
| 63 | + Run, |
| 64 | + /// Run the test with a certain number of iterations. |
| 65 | + Iterations(u64), |
| 66 | + /// Skip the test. |
| 67 | + Skip, |
| 68 | +} |
| 69 | + |
| 70 | +/// A list of all functions that should get extensive tests. |
| 71 | +static EXTENSIVE: LazyLock<Vec<Identifier>> = LazyLock::new(|| { |
| 72 | + let var = env::var(EXTENSIVE_ENV).unwrap_or_default(); |
| 73 | + let list = var.split(",").filter(|s| !s.is_empty()).collect::<Vec<_>>(); |
| 74 | + let mut ret = Vec::new(); |
| 75 | + |
| 76 | + for item in list { |
| 77 | + match item { |
| 78 | + "all" => ret = Identifier::ALL.to_owned(), |
| 79 | + "all_f32" => ret.extend( |
| 80 | + Identifier::ALL |
| 81 | + .iter() |
| 82 | + .filter(|id| matches!(id.math_op().float_ty, FloatTy::F32)) |
| 83 | + .copied(), |
| 84 | + ), |
| 85 | + "all_f64" => ret.extend( |
| 86 | + Identifier::ALL |
| 87 | + .iter() |
| 88 | + .filter(|id| matches!(id.math_op().float_ty, FloatTy::F64)) |
| 89 | + .copied(), |
| 90 | + ), |
| 91 | + s => ret.push( |
| 92 | + Identifier::from_str(s).unwrap_or_else(|| panic!("unrecognized test name `{s}`")), |
| 93 | + ), |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + ret |
| 98 | +}); |
| 99 | + |
| 100 | +/// Information about the function to be tested. |
| 101 | +#[derive(Debug)] |
| 102 | +struct TestEnv { |
| 103 | + /// Tests should be reduced because the platform is slow. E.g. 32-bit or emulated. |
| 104 | + slow_platform: bool, |
| 105 | + /// The float cannot be tested exhaustively, `f64` or `f128`. |
| 106 | + large_float_ty: bool, |
| 107 | + /// Env indicates that an extensive test should be run. |
| 108 | + should_run_extensive: bool, |
| 109 | + /// Multiprecision tests will be run. |
| 110 | + mp_tests_enabled: bool, |
| 111 | + /// The number of inputs to the function. |
| 112 | + input_count: usize, |
| 113 | +} |
| 114 | + |
| 115 | +impl TestEnv { |
| 116 | + fn from_env(ctx: &CheckCtx) -> Self { |
| 117 | + let id = ctx.fn_ident; |
| 118 | + let op = id.math_op(); |
| 119 | + |
| 120 | + let will_run_mp = cfg!(feature = "test-multiprecision"); |
| 121 | + |
| 122 | + // Tests are pretty slow on non-64-bit targets, x86 MacOS, and targets that run in QEMU. Start |
| 123 | + // with a reduced number on these platforms. |
| 124 | + let slow_on_ci = crate::emulated() |
| 125 | + || usize::BITS < 64 |
| 126 | + || cfg!(all(target_arch = "x86_64", target_vendor = "apple")); |
| 127 | + let slow_platform = slow_on_ci && crate::ci(); |
| 128 | + |
| 129 | + let large_float_ty = match op.float_ty { |
| 130 | + FloatTy::F16 | FloatTy::F32 => false, |
| 131 | + FloatTy::F64 | FloatTy::F128 => true, |
| 132 | + }; |
| 133 | + |
| 134 | + let will_run_extensive = EXTENSIVE.contains(&id); |
| 135 | + |
| 136 | + let input_count = op.rust_sig.args.len(); |
| 137 | + |
| 138 | + Self { |
| 139 | + slow_platform, |
| 140 | + large_float_ty, |
| 141 | + should_run_extensive: will_run_extensive, |
| 142 | + mp_tests_enabled: will_run_mp, |
| 143 | + input_count, |
| 144 | + } |
| 145 | + } |
| 146 | +} |
| 147 | + |
| 148 | +/// The number of iterations to run for a given test. |
| 149 | +pub fn iteration_count(ctx: &CheckCtx, gen_kind: GeneratorKind, argnum: usize) -> u64 { |
| 150 | + let t_env = TestEnv::from_env(ctx); |
| 151 | + |
| 152 | + // Ideally run 5M tests |
| 153 | + let mut domain_iter_count: u64 = 4_000_000; |
| 154 | + |
| 155 | + // Start with a reduced number of tests on slow platforms. |
| 156 | + if t_env.slow_platform { |
| 157 | + domain_iter_count = 100_000; |
| 158 | + } |
| 159 | + |
| 160 | + // Larger float types get more iterations. |
| 161 | + if t_env.large_float_ty { |
| 162 | + domain_iter_count *= 4; |
| 163 | + } |
| 164 | + |
| 165 | + // Functions with more arguments get more iterations. |
| 166 | + let arg_multiplier = 1 << (t_env.input_count - 1); |
| 167 | + domain_iter_count *= arg_multiplier; |
| 168 | + |
| 169 | + // If we will be running tests against MPFR, we don't need to test as much against musl. |
| 170 | + // However, there are some platforms where we have to test against musl since MPFR can't be |
| 171 | + // built. |
| 172 | + if t_env.mp_tests_enabled && ctx.basis == CheckBasis::Musl { |
| 173 | + domain_iter_count /= 100; |
| 174 | + } |
| 175 | + |
| 176 | + // Run fewer random tests than domain tests. |
| 177 | + let random_iter_count = domain_iter_count / 100; |
| 178 | + |
| 179 | + let mut total_iterations = match gen_kind { |
| 180 | + GeneratorKind::Domain => domain_iter_count, |
| 181 | + GeneratorKind::Random => random_iter_count, |
| 182 | + }; |
| 183 | + |
| 184 | + if cfg!(optimizations_enabled) { |
| 185 | + // Always run at least 10,000 tests |
| 186 | + total_iterations = total_iterations.max(10_000); |
| 187 | + } else { |
| 188 | + // Without optimizations just run a quick check |
| 189 | + total_iterations = 800; |
| 190 | + } |
| 191 | + |
| 192 | + // Adjust for the number of inputs |
| 193 | + let ntests = match t_env.input_count { |
| 194 | + 1 => total_iterations, |
| 195 | + 2 => (total_iterations as f64).sqrt().ceil() as u64, |
| 196 | + 3 => (total_iterations as f64).cbrt().ceil() as u64, |
| 197 | + _ => panic!("test has more than three arguments"), |
| 198 | + }; |
| 199 | + let total = ntests.pow(t_env.input_count.try_into().unwrap()); |
| 200 | + |
| 201 | + test_log(&format!( |
| 202 | + "{gen_kind:?} {basis:?} {fn_ident} arg {arg}/{args}: {ntests} iterations \ |
| 203 | + ({total} total)", |
| 204 | + basis = ctx.basis, |
| 205 | + fn_ident = ctx.fn_ident, |
| 206 | + arg = argnum + 1, |
| 207 | + args = t_env.input_count, |
| 208 | + )); |
| 209 | + |
| 210 | + ntests |
| 211 | +} |
| 212 | + |
| 213 | +pub fn int_range(ctx: &CheckCtx, argnum: usize) -> RangeInclusive<i32> { |
| 214 | + let t_env = TestEnv::from_env(ctx); |
| 215 | + |
| 216 | + if ctx.base_name != BaseName::Jn { |
| 217 | + return i32::MIN..=i32::MAX; |
| 218 | + } |
| 219 | + |
| 220 | + assert_eq!(argnum, 0, "For `jn`, only the first argument takes an integer"); |
| 221 | + |
| 222 | + if t_env.slow_platform || !cfg!(optimizations_enabled) { |
| 223 | + (-0xf)..=0xff |
| 224 | + } else { |
| 225 | + (-0xff)..=0xffff |
| 226 | + } |
| 227 | +} |
| 228 | + |
| 229 | +/// For domain tests, limit how many asymptotes or specified check points we test. |
| 230 | +pub fn check_point_count(ctx: &CheckCtx) -> usize { |
| 231 | + let t_env = TestEnv::from_env(ctx); |
| 232 | + if t_env.slow_platform || !cfg!(optimizations_enabled) { 4 } else { 10 } |
| 233 | +} |
| 234 | + |
| 235 | +/// When validating points of interest (e.g. asymptotes, inflection points, extremes), also check |
| 236 | +/// this many surrounding values. |
| 237 | +pub fn check_near_count(_ctx: &CheckCtx) -> u64 { |
| 238 | + if cfg!(optimizations_enabled) { 100 } else { 10 } |
| 239 | +} |
| 240 | + |
| 241 | +/// Check whether extensive actions should be run or skipped. |
| 242 | +#[expect(dead_code, reason = "extensive tests have not yet been added")] |
| 243 | +pub fn skip_extensive_test(ctx: &CheckCtx) -> bool { |
| 244 | + let t_env = TestEnv::from_env(ctx); |
| 245 | + !t_env.should_run_extensive |
| 246 | +} |
0 commit comments