xerv_core/testing/providers/
rng.rs

1//! Random number generator provider for deterministic testing.
2//!
3//! Allows tests to use a seeded RNG for reproducible behavior.
4
5use parking_lot::Mutex;
6use rand::rngs::StdRng;
7use rand::{Rng, SeedableRng};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10/// Provider trait for random number generation.
11pub trait RngProvider: Send + Sync {
12    /// Generate a random u64.
13    fn next_u64(&self) -> u64;
14
15    /// Generate a random f64 in the range [0, 1).
16    fn next_f64(&self) -> f64;
17
18    /// Fill a byte slice with random data.
19    fn fill_bytes(&self, dest: &mut [u8]);
20
21    /// Generate a random boolean with the given probability of being true.
22    fn gen_bool(&self, probability: f64) -> bool {
23        self.next_f64() < probability
24    }
25
26    /// Generate a random value in the given range.
27    fn gen_range(&self, low: u64, high: u64) -> u64 {
28        low + (self.next_u64() % (high - low))
29    }
30
31    /// Check if this is a mock provider.
32    fn is_mock(&self) -> bool;
33}
34
35/// Real RNG that uses the system's entropy source.
36///
37/// Uses StdRng seeded from system time and entropy for thread-safety.
38pub struct RealRng {
39    rng: Mutex<StdRng>,
40}
41
42impl RealRng {
43    /// Create a new real RNG.
44    pub fn new() -> Self {
45        // Seed from system time + entropy
46        let seed = SystemTime::now()
47            .duration_since(UNIX_EPOCH)
48            .expect("System time before UNIX epoch")
49            .as_nanos() as u64;
50
51        Self {
52            rng: Mutex::new(StdRng::seed_from_u64(seed)),
53        }
54    }
55}
56
57impl Default for RealRng {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl RngProvider for RealRng {
64    fn next_u64(&self) -> u64 {
65        self.rng.lock().r#gen()
66    }
67
68    fn next_f64(&self) -> f64 {
69        self.rng.lock().r#gen()
70    }
71
72    fn fill_bytes(&self, dest: &mut [u8]) {
73        self.rng.lock().fill(dest);
74    }
75
76    fn is_mock(&self) -> bool {
77        false
78    }
79}
80
81/// Mock RNG with a fixed seed for deterministic behavior.
82///
83/// # Example
84///
85/// ```
86/// use xerv_core::testing::MockRng;
87/// use xerv_core::testing::RngProvider;
88///
89/// let rng = MockRng::seeded(42);
90/// let first = rng.next_u64();
91/// let second = rng.next_u64();
92///
93/// // Same seed produces same sequence
94/// let rng2 = MockRng::seeded(42);
95/// assert_eq!(rng2.next_u64(), first);
96/// assert_eq!(rng2.next_u64(), second);
97/// ```
98pub struct MockRng {
99    rng: Mutex<StdRng>,
100    seed: u64,
101}
102
103impl MockRng {
104    /// Create a new mock RNG with the given seed.
105    pub fn seeded(seed: u64) -> Self {
106        Self {
107            rng: Mutex::new(StdRng::seed_from_u64(seed)),
108            seed,
109        }
110    }
111
112    /// Get the seed used to create this RNG.
113    pub fn seed(&self) -> u64 {
114        self.seed
115    }
116
117    /// Reset the RNG to its initial state.
118    pub fn reset(&self) {
119        *self.rng.lock() = StdRng::seed_from_u64(self.seed);
120    }
121}
122
123impl RngProvider for MockRng {
124    fn next_u64(&self) -> u64 {
125        self.rng.lock().r#gen()
126    }
127
128    fn next_f64(&self) -> f64 {
129        self.rng.lock().r#gen()
130    }
131
132    fn fill_bytes(&self, dest: &mut [u8]) {
133        self.rng.lock().fill(dest);
134    }
135
136    fn is_mock(&self) -> bool {
137        true
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn mock_rng_is_deterministic() {
147        let rng1 = MockRng::seeded(12345);
148        let rng2 = MockRng::seeded(12345);
149
150        let values1: Vec<u64> = (0..10).map(|_| rng1.next_u64()).collect();
151        let values2: Vec<u64> = (0..10).map(|_| rng2.next_u64()).collect();
152
153        assert_eq!(values1, values2);
154    }
155
156    #[test]
157    fn mock_rng_reset() {
158        let rng = MockRng::seeded(42);
159        let first_run: Vec<u64> = (0..5).map(|_| rng.next_u64()).collect();
160
161        rng.reset();
162        let second_run: Vec<u64> = (0..5).map(|_| rng.next_u64()).collect();
163
164        assert_eq!(first_run, second_run);
165    }
166
167    #[test]
168    fn mock_rng_different_seeds_different_values() {
169        let rng1 = MockRng::seeded(1);
170        let rng2 = MockRng::seeded(2);
171
172        let v1 = rng1.next_u64();
173        let v2 = rng2.next_u64();
174
175        assert_ne!(v1, v2);
176    }
177
178    #[test]
179    fn mock_rng_gen_range() {
180        let rng = MockRng::seeded(42);
181
182        for _ in 0..100 {
183            let v = rng.gen_range(10, 20);
184            assert!(v >= 10 && v < 20);
185        }
186    }
187
188    #[test]
189    fn mock_rng_fill_bytes() {
190        let rng = MockRng::seeded(42);
191
192        let mut buf1 = [0u8; 16];
193        let mut buf2 = [0u8; 16];
194
195        rng.fill_bytes(&mut buf1);
196        rng.reset();
197        rng.fill_bytes(&mut buf2);
198
199        assert_eq!(buf1, buf2);
200    }
201}