109 lines
2.5 KiB
Rust
109 lines
2.5 KiB
Rust
//! Test-only utilities for deterministic behavior in unit tests.
|
|
//!
|
|
//! This crate is intended to be used as a dev-dependency only. It provides:
|
|
//! - Seeded RNG helpers for reproducible randomness in tests.
|
|
//! - A tiny deterministic clock abstraction for tests that need to reason about time.
|
|
|
|
use rand::{RngCore, SeedableRng, rngs::StdRng};
|
|
|
|
/// Builds a `StdRng` from a u64 seed in a deterministic way.
|
|
///
|
|
/// This expands the u64 seed into a 32-byte array (little-endian repeated)
|
|
/// to initialize `StdRng`.
|
|
pub fn rng_from_seed(seed: u64) -> StdRng {
|
|
let le = seed.to_le_bytes();
|
|
let mut buf = [0u8; 32];
|
|
// Repeat the 8-byte seed 4 times to fill 32 bytes
|
|
for i in 0..4 {
|
|
buf[i * 8..(i + 1) * 8].copy_from_slice(&le);
|
|
}
|
|
StdRng::from_seed(buf)
|
|
}
|
|
|
|
/// Convenience helper that returns a RNG with a fixed well-known seed.
|
|
pub fn deterministic_rng() -> StdRng {
|
|
rng_from_seed(0xC0FFEE_5EED)
|
|
}
|
|
|
|
/// Returns the next u64 from the provided RNG.
|
|
pub fn next_u64(rng: &mut StdRng) -> u64 {
|
|
rng.next_u64()
|
|
}
|
|
|
|
/// Collects `n` u64 values from the RNG.
|
|
pub fn take_n_u64(rng: &mut StdRng, n: usize) -> Vec<u64> {
|
|
let mut out = Vec::with_capacity(n);
|
|
for _ in 0..n {
|
|
out.push(rng.next_u64());
|
|
}
|
|
out
|
|
}
|
|
|
|
/// Simple deterministic clock abstraction for tests.
|
|
pub trait Clock {
|
|
fn now_millis(&self) -> u64;
|
|
}
|
|
|
|
/// A clock that always returns a fixed instant.
|
|
pub struct FixedClock {
|
|
now: u64,
|
|
}
|
|
|
|
impl FixedClock {
|
|
pub fn new(now: u64) -> Self {
|
|
Self { now }
|
|
}
|
|
}
|
|
|
|
impl Clock for FixedClock {
|
|
fn now_millis(&self) -> u64 {
|
|
self.now
|
|
}
|
|
}
|
|
|
|
/// A clock that advances by a constant step each tick.
|
|
pub struct TickingClock {
|
|
now: u64,
|
|
step: u64,
|
|
}
|
|
|
|
impl TickingClock {
|
|
pub fn new(start: u64, step: u64) -> Self {
|
|
Self { now: start, step }
|
|
}
|
|
pub fn tick(&mut self) {
|
|
self.now = self.now.saturating_add(self.step);
|
|
}
|
|
}
|
|
|
|
impl Clock for TickingClock {
|
|
fn now_millis(&self) -> u64 {
|
|
self.now
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn rng_reproducible_sequences() {
|
|
let mut a = rng_from_seed(42);
|
|
let mut b = rng_from_seed(42);
|
|
|
|
for _ in 0..10 {
|
|
assert_eq!(a.next_u64(), b.next_u64());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ticking_clock_advances_deterministically() {
|
|
let mut c = TickingClock::new(1000, 16);
|
|
assert_eq!(c.now_millis(), 1000);
|
|
c.tick();
|
|
assert_eq!(c.now_millis(), 1016);
|
|
c.tick();
|
|
assert_eq!(c.now_millis(), 1032);
|
|
}
|
|
}
|