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