diff options
| author | tv <tv@krebsco.de> | 2026-10-25 23:19:56 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-25 23:19:56 +0100 |
| commit | 1a2bdeb40920bdf89d3eda30265b42f377972454 (patch) | |
| tree | 7ceb85c3b2fe2a12929328d5a6fce89518dacb1d /src/testrand.rs | |
| parent | d86d554ceff4209e7e8bc80aea9cc04f7697e704 (diff) | |
add random swarms test
Diffstat (limited to 'src/testrand.rs')
| -rw-r--r-- | src/testrand.rs | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/src/testrand.rs b/src/testrand.rs new file mode 100644 index 0000000..dbb2148 --- /dev/null +++ b/src/testrand.rs @@ -0,0 +1,82 @@ +//! 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<T>(&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 in `0..cases`, each with its own generator. 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)) { + for seed in 0..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); + } + } +} |
