casper_types/
testing.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! An RNG for testing purposes.
use std::{
    cell::RefCell,
    cmp, env,
    fmt::{self, Debug, Display, Formatter},
    iter, thread,
};

use rand::{
    self,
    distributions::{uniform::SampleRange, Distribution, Standard},
    CryptoRng, Error, Rng, RngCore, SeedableRng,
};
use rand_pcg::Pcg64Mcg;

thread_local! {
    static THIS_THREAD_HAS_RNG: RefCell<bool> = const { RefCell::new(false) };
}

const CL_TEST_SEED: &str = "CL_TEST_SEED";

type Seed = <Pcg64Mcg as SeedableRng>::Seed; // [u8; 16]

/// A fast, seedable pseudorandom number generator for use in tests which prints the seed if the
/// thread in which it is created panics.
///
/// Only one `TestRng` is permitted per thread.
pub struct TestRng {
    seed: Seed,
    rng: Pcg64Mcg,
}

impl TestRng {
    /// Constructs a new `TestRng` using a seed generated from the env var `CL_TEST_SEED` if set or
    /// from cryptographically secure random data if not.
    ///
    /// Note that `new()` or `default()` should only be called once per test.  If a test needs to
    /// spawn multiple threads each with their own `TestRng`, then use `new()` to create a single,
    /// master `TestRng`, then use it to create a seed per child thread.  The child `TestRng`s can
    /// then be constructed in their own threads via `from_seed()`.
    ///
    /// # Panics
    ///
    /// Panics if a `TestRng` has already been created on this thread.
    pub fn new() -> Self {
        Self::set_flag_or_panic();

        let mut seed = Seed::default();
        match env::var(CL_TEST_SEED) {
            Ok(seed_as_hex) => {
                base16::decode_slice(&seed_as_hex, &mut seed).unwrap_or_else(|error| {
                    THIS_THREAD_HAS_RNG.with(|flag| {
                        *flag.borrow_mut() = false;
                    });
                    panic!("can't parse '{}' as a TestRng seed: {}", seed_as_hex, error)
                });
            }
            Err(_) => {
                rand::thread_rng().fill(&mut seed);
            }
        };

        let rng = Pcg64Mcg::from_seed(seed);

        TestRng { seed, rng }
    }

    /// Constructs a new `TestRng` using `seed`.  This should be used in cases where a test needs to
    /// spawn multiple threads each with their own `TestRng`.  A single, master `TestRng` should be
    /// constructed before any child threads are spawned, and that one should be used to create
    /// seeds for the child threads' `TestRng`s.
    ///
    /// # Panics
    ///
    /// Panics if a `TestRng` has already been created on this thread.
    pub fn from_seed(seed: Seed) -> Self {
        Self::set_flag_or_panic();
        let rng = Pcg64Mcg::from_seed(seed);
        TestRng { seed, rng }
    }

    /// Returns a random `String` of length within the range specified by `length_range`.
    pub fn random_string<R: SampleRange<usize>>(&mut self, length_range: R) -> String {
        let count = self.gen_range(length_range);
        iter::repeat_with(|| self.gen::<char>())
            .take(count)
            .collect()
    }

    /// Returns a random `Vec` of length within the range specified by `length_range`.
    pub fn random_vec<R: SampleRange<usize>, T>(&mut self, length_range: R) -> Vec<T>
    where
        Standard: Distribution<T>,
    {
        let count = self.gen_range(length_range);
        iter::repeat_with(|| self.gen::<T>()).take(count).collect()
    }

    fn set_flag_or_panic() {
        THIS_THREAD_HAS_RNG.with(|flag| {
            if *flag.borrow() {
                panic!("cannot create multiple TestRngs on the same thread");
            }
            *flag.borrow_mut() = true;
        });
    }

    /// Creates a child RNG.
    ///
    /// The resulting RNG is seeded from `self` deterministically.
    pub fn create_child(&mut self) -> Self {
        let seed = self.gen();
        let rng = Pcg64Mcg::from_seed(seed);
        TestRng { seed, rng }
    }
}

impl Default for TestRng {
    fn default() -> Self {
        TestRng::new()
    }
}

impl Display for TestRng {
    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
        write!(
            formatter,
            "TestRng seed: {}",
            base16::encode_lower(&self.seed)
        )
    }
}

impl Debug for TestRng {
    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
        Display::fmt(self, formatter)
    }
}

impl Drop for TestRng {
    fn drop(&mut self) {
        if thread::panicking() {
            let line_1 = format!("Thread: {}", thread::current().name().unwrap_or("unnamed"));
            let line_2 = "To reproduce failure, try running with env var:";
            let line_3 = format!("{}={}", CL_TEST_SEED, base16::encode_lower(&self.seed));
            let max_length = cmp::max(line_1.len(), line_2.len());
            let border = "=".repeat(max_length);
            println!(
                "\n{}\n{}\n{}\n{}\n{}\n",
                border, line_1, line_2, line_3, border
            );
        }
    }
}

impl SeedableRng for TestRng {
    type Seed = <Pcg64Mcg as SeedableRng>::Seed;

    fn from_seed(seed: Self::Seed) -> Self {
        Self::from_seed(seed)
    }
}

impl RngCore for TestRng {
    fn next_u32(&mut self) -> u32 {
        self.rng.next_u32()
    }

    fn next_u64(&mut self) -> u64 {
        self.rng.next_u64()
    }

    fn fill_bytes(&mut self, dest: &mut [u8]) {
        self.rng.fill_bytes(dest)
    }

    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
        self.rng.try_fill_bytes(dest)
    }
}

impl CryptoRng for TestRng {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic(expected = "cannot create multiple TestRngs on the same thread")]
    fn second_test_rng_in_thread_should_panic() {
        let _test_rng1 = TestRng::new();
        let seed = [1; 16];
        let _test_rng2 = TestRng::from_seed(seed);
    }
}