//! 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 { 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); } }