1use std::{
3 cell::RefCell,
4 cmp, env,
5 fmt::{self, Debug, Display, Formatter},
6 iter, thread,
7};
8
9use rand::{
10 self,
11 distributions::{uniform::SampleRange, Distribution, Standard},
12 CryptoRng, Error, Rng, RngCore, SeedableRng,
13};
14use rand_pcg::Pcg64Mcg;
15
16thread_local! {
17 static THIS_THREAD_HAS_RNG: RefCell<bool> = const { RefCell::new(false) };
18}
19
20const CL_TEST_SEED: &str = "CL_TEST_SEED";
21
22type Seed = <Pcg64Mcg as SeedableRng>::Seed; pub struct TestRng {
29 seed: Seed,
30 rng: Pcg64Mcg,
31}
32
33impl TestRng {
34 pub fn new() -> Self {
46 Self::set_flag_or_panic();
47
48 let mut seed = Seed::default();
49 match env::var(CL_TEST_SEED) {
50 Ok(seed_as_hex) => {
51 base16::decode_slice(&seed_as_hex, &mut seed).unwrap_or_else(|error| {
52 THIS_THREAD_HAS_RNG.with(|flag| {
53 *flag.borrow_mut() = false;
54 });
55 panic!("can't parse '{}' as a TestRng seed: {}", seed_as_hex, error)
56 });
57 }
58 Err(_) => {
59 rand::thread_rng().fill(&mut seed);
60 }
61 };
62
63 let rng = Pcg64Mcg::from_seed(seed);
64
65 TestRng { seed, rng }
66 }
67
68 pub fn from_seed(seed: Seed) -> Self {
77 Self::set_flag_or_panic();
78 let rng = Pcg64Mcg::from_seed(seed);
79 TestRng { seed, rng }
80 }
81
82 pub fn random_string<R: SampleRange<usize>>(&mut self, length_range: R) -> String {
84 let count = self.gen_range(length_range);
85 iter::repeat_with(|| self.gen::<char>())
86 .take(count)
87 .collect()
88 }
89
90 pub fn random_vec<R: SampleRange<usize>, T>(&mut self, length_range: R) -> Vec<T>
92 where
93 Standard: Distribution<T>,
94 {
95 let count = self.gen_range(length_range);
96 iter::repeat_with(|| self.gen::<T>()).take(count).collect()
97 }
98
99 fn set_flag_or_panic() {
100 THIS_THREAD_HAS_RNG.with(|flag| {
101 if *flag.borrow() {
102 panic!("cannot create multiple TestRngs on the same thread");
103 }
104 *flag.borrow_mut() = true;
105 });
106 }
107
108 pub fn create_child(&mut self) -> Self {
112 let seed = self.gen();
113 let rng = Pcg64Mcg::from_seed(seed);
114 TestRng { seed, rng }
115 }
116}
117
118impl Default for TestRng {
119 fn default() -> Self {
120 TestRng::new()
121 }
122}
123
124impl Display for TestRng {
125 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
126 write!(
127 formatter,
128 "TestRng seed: {}",
129 base16::encode_lower(&self.seed)
130 )
131 }
132}
133
134impl Debug for TestRng {
135 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
136 Display::fmt(self, formatter)
137 }
138}
139
140impl Drop for TestRng {
141 fn drop(&mut self) {
142 if thread::panicking() {
143 let line_1 = format!("Thread: {}", thread::current().name().unwrap_or("unnamed"));
144 let line_2 = "To reproduce failure, try running with env var:";
145 let line_3 = format!("{}={}", CL_TEST_SEED, base16::encode_lower(&self.seed));
146 let max_length = cmp::max(line_1.len(), line_2.len());
147 let border = "=".repeat(max_length);
148 println!(
149 "\n{}\n{}\n{}\n{}\n{}\n",
150 border, line_1, line_2, line_3, border
151 );
152 }
153 }
154}
155
156impl SeedableRng for TestRng {
157 type Seed = <Pcg64Mcg as SeedableRng>::Seed;
158
159 fn from_seed(seed: Self::Seed) -> Self {
160 Self::from_seed(seed)
161 }
162}
163
164impl RngCore for TestRng {
165 fn next_u32(&mut self) -> u32 {
166 self.rng.next_u32()
167 }
168
169 fn next_u64(&mut self) -> u64 {
170 self.rng.next_u64()
171 }
172
173 fn fill_bytes(&mut self, dest: &mut [u8]) {
174 self.rng.fill_bytes(dest)
175 }
176
177 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
178 self.rng.try_fill_bytes(dest)
179 }
180}
181
182impl CryptoRng for TestRng {}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 #[should_panic(expected = "cannot create multiple TestRngs on the same thread")]
190 fn second_test_rng_in_thread_should_panic() {
191 let _test_rng1 = TestRng::new();
192 let seed = [1; 16];
193 let _test_rng2 = TestRng::from_seed(seed);
194 }
195}