//! A small, seedable random generator for tests (SplitMix64), and a runner for //! property tests over many seeds. No dependency; reproducible: a failing case names //! its seed. use std::time::Duration; pub(crate) struct Rng(u64); impl Rng { pub(crate) fn new(seed: u64) -> Self { Self(seed) } pub(crate) fn next_u64(&mut self) -> u64 { self.0 = self.0.wrapping_add(0x9e37_79b9_7f4a_7c15); let mut z = self.0; z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); z ^ (z >> 31) } /// Uniform in [0, 1). pub(crate) fn next_f64(&mut self) -> f64 { (self.next_u64() >> 11) as f64 / (1_u64 << 53) as f64 } pub(crate) fn chance(&mut self, probability: f64) -> bool { probability > 0.0 && self.next_f64() < probability } /// Uniform in [0, n); 0 if `n` is 0. pub(crate) fn below(&mut self, n: usize) -> usize { if n == 0 { return 0; } (self.next_u64() % n as u64) as usize } /// Uniform in [min, max]. pub(crate) fn between(&mut self, min: usize, max: usize) -> usize { min + self.below(max - min + 1) } pub(crate) fn pick<'a, T>(&mut self, items: &'a [T]) -> &'a T { &items[self.below(items.len())] } pub(crate) fn bytes32(&mut self) -> [u8; 32] { let mut bytes = [0; 32]; for chunk in bytes.chunks_mut(8) { chunk.copy_from_slice(&self.next_u64().to_le_bytes()); } bytes } pub(crate) fn shuffle(&mut self, items: &mut [T]) { for i in (1..items.len()).rev() { items.swap(i, self.below(i + 1)); } } /// Uniform in the range (`min` if it is empty). pub(crate) fn duration_in(&mut self, (min, max): (Duration, Duration)) -> Duration { if max <= min { return min; } min + (max - min).mul_f64(self.next_f64()) } } /// Runs `check` once per seed, each with its own generator: seeds `0..cases` by default, /// so every run checks the same cases. To explore further, `CONTINUUM_TEST_SEED` moves /// the first seed and `CONTINUUM_TEST_CASES` multiplies the number of cases. A failing /// case says its seed, to rerun it alone with `Rng::new(seed)`. pub(crate) fn for_seeds(cases: u64, check: impl Fn(&mut Rng)) { let env = |name: &str| { std::env::var(name) .ok() .and_then(|value| value.parse::().ok()) }; let first = env("CONTINUUM_TEST_SEED").unwrap_or(0); let cases = cases.saturating_mul(env("CONTINUUM_TEST_CASES").unwrap_or(1)); for seed in first..first.saturating_add(cases) { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| check(&mut Rng::new(seed)))); if let Err(panic) = result { eprintln!("property failed for seed {seed}"); std::panic::resume_unwind(panic); } } }