casper_types/
testing.rs

1//! An RNG for testing purposes.
2use 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; // [u8; 16]
23
24/// A fast, seedable pseudorandom number generator for use in tests which prints the seed if the
25/// thread in which it is created panics.
26///
27/// Only one `TestRng` is permitted per thread.
28pub struct TestRng {
29    seed: Seed,
30    rng: Pcg64Mcg,
31}
32
33impl TestRng {
34    /// Constructs a new `TestRng` using a seed generated from the env var `CL_TEST_SEED` if set or
35    /// from cryptographically secure random data if not.
36    ///
37    /// Note that `new()` or `default()` should only be called once per test.  If a test needs to
38    /// spawn multiple threads each with their own `TestRng`, then use `new()` to create a single,
39    /// master `TestRng`, then use it to create a seed per child thread.  The child `TestRng`s can
40    /// then be constructed in their own threads via `from_seed()`.
41    ///
42    /// # Panics
43    ///
44    /// Panics if a `TestRng` has already been created on this thread.
45    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    /// Constructs a new `TestRng` using `seed`.  This should be used in cases where a test needs to
69    /// spawn multiple threads each with their own `TestRng`.  A single, master `TestRng` should be
70    /// constructed before any child threads are spawned, and that one should be used to create
71    /// seeds for the child threads' `TestRng`s.
72    ///
73    /// # Panics
74    ///
75    /// Panics if a `TestRng` has already been created on this thread.
76    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    /// Returns a random `String` of length within the range specified by `length_range`.
83    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    /// Returns a random `Vec` of length within the range specified by `length_range`.
91    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    /// Creates a child RNG.
109    ///
110    /// The resulting RNG is seeded from `self` deterministically.
111    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}