1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
//! 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, 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::<u64>().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);
}
}
}
|