Skip to content

Commit 32a2893

Browse files
committed
test-float-parse: Use an overrideable random seed
Currently `test-float-parse` is mostly random tests which take a fixed seed, meaning the same set of values get tested for each invocation. This isn't ideal because we don't get the true benefit of randomness, which is to have better coverage over time. Improve this by using a randomly generated seed, which can also be set via env. The seed is printed at the beginning of each run so it is easy to reproduce failures using the same test set, if needed. Typically it isn't great to have fuzzing randomness in tests that get run in CI because it can lead to spurious failures. However, this is a test for which failure should never happen becasue the algorithms are reasonably well proven, so if one does occur it represents a very unexpected bug that needs to be addressed. The error message is updated to strongly recommend reporting before retrying and to include details on how to reproduce.
1 parent cdcab4c commit 32a2893

4 files changed

Lines changed: 55 additions & 8 deletions

File tree

src/tools/test-float-parse/src/gen_/fuzz.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ where
6262
}
6363

6464
fn new() -> Self {
65-
let rng = ChaCha8Rng::from_seed(SEED);
65+
let rng = ChaCha8Rng::from_seed(SEED.0);
6666

6767
Self { iter: 0..Self::total_tests(), rng, marker: PhantomData }
6868
}

src/tools/test-float-parse/src/gen_/many_digits.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ impl<F: Float> Generator<F> for RandDigits<F> {
3939
}
4040

4141
fn new() -> Self {
42-
let rng = ChaCha8Rng::from_seed(SEED);
42+
let rng = ChaCha8Rng::from_seed(SEED.0);
4343
let range = Uniform::try_from(0..10).unwrap();
4444

4545
Self { rng, iter: 0..ITERATIONS, uniform: range, marker: PhantomData }

src/tools/test-float-parse/src/lib.rs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ use std::any::type_name;
1010
use std::cmp::min;
1111
use std::ops::RangeInclusive;
1212
use std::process::ExitCode;
13-
use std::sync::OnceLock;
1413
use std::sync::atomic::{AtomicU64, Ordering};
15-
use std::{fmt, time};
14+
use std::sync::{LazyLock, OnceLock};
15+
use std::{env, fmt, time};
1616

17+
use rand::TryRng;
1718
use rand::distr::{Distribution, StandardUniform};
19+
use rand::rngs::SysRng;
1820
use rayon::prelude::*;
1921
use time::{Duration, Instant};
2022
use traits::{Float, Generator, Int};
@@ -44,8 +46,41 @@ const MAX_BITS_FOR_EXHAUUSTIVE: u32 = 32;
4446
/// `--skip-huge`.
4547
const HUGE_TEST_CUTOFF: u64 = 5_000_000;
4648

47-
/// Seed for tests that use a deterministic RNG.
48-
const SEED: [u8; 32] = *b"3.141592653589793238462643383279";
49+
const SEED_ENV: &str = "TEST_FLOAT_PARSE_SEED";
50+
51+
/// Seed for tests that use a deterministic RNG, and its b64 representation for printing. Taken
52+
/// from env if provided,
53+
static SEED: LazyLock<([u8; 32], Box<str>)> = LazyLock::new(|| {
54+
let seed = match env::var(SEED_ENV) {
55+
Ok(s) => {
56+
seed_from_str(&s).unwrap_or_else(|| panic!("{SEED_ENV} must be 32 bytes, hex encoded"))
57+
}
58+
Err(_) => {
59+
let mut seed = [0u8; 32];
60+
SysRng.try_fill_bytes(&mut seed).unwrap();
61+
seed
62+
}
63+
};
64+
65+
let lo = u128::from_le_bytes(seed[..16].try_into().unwrap());
66+
let hi = u128::from_le_bytes(seed[16..].try_into().unwrap());
67+
let encoded = format!("{hi:032x}{lo:032x}").into_boxed_str();
68+
69+
(seed, encoded)
70+
});
71+
72+
fn seed_from_str(s: &str) -> Option<[u8; 32]> {
73+
let hi = s.get(..32)?;
74+
let hi = u128::from_str_radix(hi, 16).ok()?;
75+
let lo = s.get(32..)?;
76+
let lo = u128::from_str_radix(lo, 16).ok()?;
77+
78+
let mut out = [0u8; 32];
79+
out[..16].copy_from_slice(&lo.to_le_bytes());
80+
out[16..].copy_from_slice(&hi.to_le_bytes());
81+
82+
Some(out)
83+
}
4984

5085
/// Global configuration.
5186
#[derive(Debug)]
@@ -78,8 +113,10 @@ pub fn run(cfg: Config, include: &[String], exclude: &[String]) -> ExitCode {
78113
let threads = std::thread::available_parallelism().map(Into::into).unwrap_or(0) * 3 / 2;
79114
rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap();
80115

116+
println!("Starting test runner");
117+
println!("Using {SEED_ENV}={}`", SEED.1);
81118
let mut tests = register_tests(&cfg);
82-
println!("registered");
119+
println!("Tests registered");
83120
let initial_tests: Vec<_> = tests.iter().map(|t| t.name.clone()).collect();
84121

85122
let unmatched: Vec<_> = include

src/tools/test-float-parse/src/ui.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::time::Duration;
88

99
use indicatif::{ProgressBar, ProgressStyle};
1010

11-
use crate::{Completed, Config, EarlyExit, FinishedAll, TestInfo};
11+
use crate::{Completed, Config, EarlyExit, FinishedAll, SEED, SEED_ENV, TestInfo};
1212

1313
/// Templates for progress bars.
1414
const PB_TEMPLATE: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME \
@@ -135,6 +135,16 @@ pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) ->
135135
);
136136

137137
if failed_generators > 0 || stopped_generators > 0 {
138+
println!();
139+
println!(
140+
"ERROR: Some float parsing/printing tests failed.\n\
141+
\n\
142+
If you ever encounter this failure when not expected, PLEASE OPEN AN ISSUE!! The \
143+
failure will likely go away on the next run, but may represent a real bug.\n\
144+
\n\
145+
To reproduce, rerun with the same seed: {}={}",
146+
SEED_ENV, SEED.1
147+
);
138148
ExitCode::FAILURE
139149
} else {
140150
ExitCode::SUCCESS

0 commit comments

Comments
 (0)