proptest/test_runner/
rng.rs

1//-
2// Copyright 2017, 2018, 2019, 2020 The proptest developers
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use crate::std_facade::{Arc, String, ToOwned, Vec};
11use core::result::Result;
12use core::{fmt, str, u8};
13
14use byteorder::{ByteOrder, LittleEndian};
15use rand::{self, Rng, RngCore, SeedableRng};
16use rand_chacha::ChaChaRng;
17use rand_xorshift::XorShiftRng;
18
19/// Identifies a particular RNG algorithm supported by proptest.
20///
21/// Proptest supports dynamic configuration of algorithms to allow it to
22/// continue operating with persisted regression files and to allow the
23/// configuration to be expressed in the `Config` struct.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum RngAlgorithm {
26    /// The [XorShift](https://rust-random.github.io/rand/rand_xorshift/struct.XorShiftRng.html)
27    /// algorithm. This was the default up through and including Proptest 0.9.0.
28    ///
29    /// It is faster than ChaCha but produces lower quality randomness and has
30    /// some pathological cases where it may fail to produce outputs that are
31    /// random even to casual observation.
32    ///
33    /// The seed must be exactly 16 bytes.
34    XorShift,
35    /// The [ChaCha](https://rust-random.github.io/rand/rand_chacha/struct.ChaChaRng.html)
36    /// algorithm. This became the default with Proptest 0.9.1.
37    ///
38    /// The seed must be exactly 32 bytes.
39    ChaCha,
40    /// This is not an actual RNG algorithm, but instead returns data directly
41    /// from its "seed".
42    ///
43    /// This is useful when Proptest is being driven from some other entropy
44    /// source, such as a fuzzer.
45    ///
46    /// If the seed is depleted, the RNG will return 0s forever.
47    ///
48    /// Note that in cases where a new RNG is to be derived from an existing
49    /// one, *the data is split evenly between them*, regardless of how much
50    /// entropy is actually needed. This means that combinators like
51    /// `prop_perturb` and `prop_flat_map` can require extremely large inputs.
52    PassThrough,
53    /// This is equivalent to the `ChaCha` RNG, with the addition that it
54    /// records the bytes used to create a value.
55    ///
56    /// This is useful when Proptest is used for fuzzing, and a corpus of
57    /// initial inputs need to be created. Note that in these cases, you need
58    /// to use the `TestRunner` API directly yourself instead of using the
59    /// `proptest!` macro, as otherwise there is no way to obtain the bytes
60    /// this captures.
61    Recorder,
62    #[allow(missing_docs)]
63    #[doc(hidden)]
64    _NonExhaustive,
65}
66
67impl Default for RngAlgorithm {
68    fn default() -> Self {
69        RngAlgorithm::ChaCha
70    }
71}
72
73impl RngAlgorithm {
74    pub(crate) fn persistence_key(self) -> &'static str {
75        match self {
76            RngAlgorithm::XorShift => "xs",
77            RngAlgorithm::ChaCha => "cc",
78            RngAlgorithm::PassThrough => "pt",
79            RngAlgorithm::Recorder => "rc",
80            RngAlgorithm::_NonExhaustive => unreachable!(),
81        }
82    }
83
84    pub(crate) fn from_persistence_key(k: &str) -> Option<Self> {
85        match k {
86            "xs" => Some(RngAlgorithm::XorShift),
87            "cc" => Some(RngAlgorithm::ChaCha),
88            "pt" => Some(RngAlgorithm::PassThrough),
89            "rc" => Some(RngAlgorithm::Recorder),
90            _ => None,
91        }
92    }
93}
94
95// These two are only used for parsing the environment variable
96// PROPTEST_RNG_ALGORITHM.
97impl str::FromStr for RngAlgorithm {
98    type Err = ();
99    fn from_str(s: &str) -> Result<Self, ()> {
100        RngAlgorithm::from_persistence_key(s).ok_or(())
101    }
102}
103impl fmt::Display for RngAlgorithm {
104    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105        write!(f, "{}", self.persistence_key())
106    }
107}
108
109/// Proptest's random number generator.
110#[derive(Clone, Debug)]
111pub struct TestRng {
112    rng: TestRngImpl,
113}
114
115#[derive(Clone, Debug)]
116enum TestRngImpl {
117    XorShift(XorShiftRng),
118    ChaCha(ChaChaRng),
119    PassThrough {
120        off: usize,
121        end: usize,
122        data: Arc<[u8]>,
123    },
124    Recorder {
125        rng: ChaChaRng,
126        record: Vec<u8>,
127    },
128}
129
130impl RngCore for TestRng {
131    fn next_u32(&mut self) -> u32 {
132        match &mut self.rng {
133            &mut TestRngImpl::XorShift(ref mut rng) => rng.next_u32(),
134
135            &mut TestRngImpl::ChaCha(ref mut rng) => rng.next_u32(),
136
137            &mut TestRngImpl::PassThrough { .. } => {
138                let mut buf = [0; 4];
139                self.fill_bytes(&mut buf[..]);
140                LittleEndian::read_u32(&buf[..])
141            }
142
143            &mut TestRngImpl::Recorder {
144                ref mut rng,
145                ref mut record,
146            } => {
147                let read = rng.next_u32();
148                record.extend_from_slice(&read.to_le_bytes());
149                read
150            }
151        }
152    }
153
154    fn next_u64(&mut self) -> u64 {
155        match &mut self.rng {
156            &mut TestRngImpl::XorShift(ref mut rng) => rng.next_u64(),
157
158            &mut TestRngImpl::ChaCha(ref mut rng) => rng.next_u64(),
159
160            &mut TestRngImpl::PassThrough { .. } => {
161                let mut buf = [0; 8];
162                self.fill_bytes(&mut buf[..]);
163                LittleEndian::read_u64(&buf[..])
164            }
165
166            &mut TestRngImpl::Recorder {
167                ref mut rng,
168                ref mut record,
169            } => {
170                let read = rng.next_u64();
171                record.extend_from_slice(&read.to_le_bytes());
172                read
173            }
174        }
175    }
176
177    fn fill_bytes(&mut self, dest: &mut [u8]) {
178        match &mut self.rng {
179            &mut TestRngImpl::XorShift(ref mut rng) => rng.fill_bytes(dest),
180
181            &mut TestRngImpl::ChaCha(ref mut rng) => rng.fill_bytes(dest),
182
183            &mut TestRngImpl::PassThrough {
184                ref mut off,
185                end,
186                ref data,
187            } => {
188                let bytes_to_copy = dest.len().min(end - *off);
189                dest[..bytes_to_copy]
190                    .copy_from_slice(&data[*off..*off + bytes_to_copy]);
191                *off += bytes_to_copy;
192                for i in bytes_to_copy..dest.len() {
193                    dest[i] = 0;
194                }
195            }
196
197            &mut TestRngImpl::Recorder {
198                ref mut rng,
199                ref mut record,
200            } => {
201                let res = rng.fill_bytes(dest);
202                record.extend_from_slice(&dest);
203                res
204            }
205        }
206    }
207
208    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
209        match self.rng {
210            TestRngImpl::XorShift(ref mut rng) => rng.try_fill_bytes(dest),
211
212            TestRngImpl::ChaCha(ref mut rng) => rng.try_fill_bytes(dest),
213
214            TestRngImpl::PassThrough { .. } => {
215                self.fill_bytes(dest);
216                Ok(())
217            }
218
219            TestRngImpl::Recorder {
220                ref mut rng,
221                ref mut record,
222            } => {
223                let res = rng.try_fill_bytes(dest);
224                if res.is_ok() {
225                    record.extend_from_slice(&dest);
226                }
227                res
228            }
229        }
230    }
231}
232
233#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
234pub(crate) enum Seed {
235    XorShift([u8; 16]),
236    ChaCha([u8; 32]),
237    PassThrough(Option<(usize, usize)>, Arc<[u8]>),
238    Recorder([u8; 32]),
239}
240
241impl Seed {
242    pub(crate) fn from_bytes(algorithm: RngAlgorithm, seed: &[u8]) -> Self {
243        match algorithm {
244            RngAlgorithm::XorShift => {
245                assert_eq!(16, seed.len(), "XorShift requires a 16-byte seed");
246                let mut buf = [0; 16];
247                buf.copy_from_slice(seed);
248                Seed::XorShift(buf)
249            }
250
251            RngAlgorithm::ChaCha => {
252                assert_eq!(32, seed.len(), "ChaCha requires a 32-byte seed");
253                let mut buf = [0; 32];
254                buf.copy_from_slice(seed);
255                Seed::ChaCha(buf)
256            }
257
258            RngAlgorithm::PassThrough => Seed::PassThrough(None, seed.into()),
259
260            RngAlgorithm::Recorder => {
261                assert_eq!(32, seed.len(), "Recorder requires a 32-byte seed");
262                let mut buf = [0; 32];
263                buf.copy_from_slice(seed);
264                Seed::Recorder(buf)
265            }
266
267            RngAlgorithm::_NonExhaustive => unreachable!(),
268        }
269    }
270
271    pub(crate) fn from_persistence(string: &str) -> Option<Seed> {
272        fn from_base16(dst: &mut [u8], src: &str) -> Option<()> {
273            if dst.len() * 2 != src.len() {
274                return None;
275            }
276
277            for (dst_byte, src_pair) in
278                dst.into_iter().zip(src.as_bytes().chunks(2))
279            {
280                *dst_byte =
281                    u8::from_str_radix(str::from_utf8(src_pair).ok()?, 16)
282                        .ok()?;
283            }
284
285            Some(())
286        }
287
288        let parts =
289            string.trim().split(char::is_whitespace).collect::<Vec<_>>();
290        RngAlgorithm::from_persistence_key(&parts[0]).and_then(
291            |alg| match alg {
292                RngAlgorithm::XorShift => {
293                    if 5 != parts.len() {
294                        return None;
295                    }
296
297                    let mut dwords = [0u32; 4];
298                    for (dword, part) in
299                        (&mut dwords[..]).into_iter().zip(&parts[1..])
300                    {
301                        *dword = part.parse().ok()?;
302                    }
303
304                    let mut seed = [0u8; 16];
305                    LittleEndian::write_u32_into(&dwords[..], &mut seed[..]);
306                    Some(Seed::XorShift(seed))
307                }
308
309                RngAlgorithm::ChaCha => {
310                    if 2 != parts.len() {
311                        return None;
312                    }
313
314                    let mut seed = [0u8; 32];
315                    from_base16(&mut seed, &parts[1])?;
316                    Some(Seed::ChaCha(seed))
317                }
318
319                RngAlgorithm::PassThrough => {
320                    if 1 == parts.len() {
321                        return Some(Seed::PassThrough(None, vec![].into()));
322                    }
323
324                    if 2 != parts.len() {
325                        return None;
326                    }
327
328                    let mut seed = vec![0u8; parts[1].len() / 2];
329                    from_base16(&mut seed, &parts[1])?;
330                    Some(Seed::PassThrough(None, seed.into()))
331                }
332
333                RngAlgorithm::Recorder => {
334                    if 2 != parts.len() {
335                        return None;
336                    }
337
338                    let mut seed = [0u8; 32];
339                    from_base16(&mut seed, &parts[1])?;
340                    Some(Seed::Recorder(seed))
341                }
342
343                RngAlgorithm::_NonExhaustive => unreachable!(),
344            },
345        )
346    }
347
348    pub(crate) fn to_persistence(&self) -> String {
349        fn to_base16(dst: &mut String, src: &[u8]) {
350            for byte in src {
351                dst.push_str(&format!("{:02x}", byte));
352            }
353        }
354
355        match *self {
356            Seed::XorShift(ref seed) => {
357                let mut dwords = [0u32; 4];
358                LittleEndian::read_u32_into(seed, &mut dwords[..]);
359                format!(
360                    "{} {} {} {} {}",
361                    RngAlgorithm::XorShift.persistence_key(),
362                    dwords[0],
363                    dwords[1],
364                    dwords[2],
365                    dwords[3]
366                )
367            }
368
369            Seed::ChaCha(ref seed) => {
370                let mut string =
371                    RngAlgorithm::ChaCha.persistence_key().to_owned();
372                string.push(' ');
373                to_base16(&mut string, seed);
374                string
375            }
376
377            Seed::PassThrough(bounds, ref data) => {
378                let data =
379                    bounds.map_or(&data[..], |(start, end)| &data[start..end]);
380                let mut string =
381                    RngAlgorithm::PassThrough.persistence_key().to_owned();
382                string.push(' ');
383                to_base16(&mut string, data);
384                string
385            }
386
387            Seed::Recorder(ref seed) => {
388                let mut string =
389                    RngAlgorithm::Recorder.persistence_key().to_owned();
390                string.push(' ');
391                to_base16(&mut string, seed);
392                string
393            }
394        }
395    }
396}
397
398impl TestRng {
399    /// Create a new RNG with the given algorithm and seed.
400    ///
401    /// Any RNG created with the same algorithm-seed pair will produce the same
402    /// sequence of values on all systems and all supporting versions of
403    /// proptest.
404    ///
405    /// ## Panics
406    ///
407    /// Panics if `seed` is not an appropriate length for `algorithm`.
408    pub fn from_seed(algorithm: RngAlgorithm, seed: &[u8]) -> Self {
409        TestRng::from_seed_internal(Seed::from_bytes(algorithm, seed))
410    }
411
412    /// Dumps the bytes obtained from the RNG so far (only works if the RNG is
413    /// set to `Recorder`).
414    ///
415    /// ## Panics
416    ///
417    /// Panics if this RNG does not capture generated data.
418    pub fn bytes_used(&self) -> Vec<u8> {
419        match self.rng {
420            TestRngImpl::Recorder { ref record, .. } => record.clone(),
421            _ => panic!("bytes_used() called on non-Recorder RNG"),
422        }
423    }
424
425    /// Construct a default TestRng from entropy.
426    pub(crate) fn default_rng(algorithm: RngAlgorithm) -> Self {
427        #[cfg(feature = "std")]
428        {
429            Self {
430                rng: match algorithm {
431                    RngAlgorithm::XorShift => {
432                        TestRngImpl::XorShift(XorShiftRng::from_entropy())
433                    }
434                    RngAlgorithm::ChaCha => {
435                        TestRngImpl::ChaCha(ChaChaRng::from_entropy())
436                    }
437                    RngAlgorithm::PassThrough => {
438                        panic!("cannot create default instance of PassThrough")
439                    }
440                    RngAlgorithm::Recorder => TestRngImpl::Recorder {
441                        rng: ChaChaRng::from_entropy(),
442                        record: Vec::new(),
443                    },
444                    RngAlgorithm::_NonExhaustive => unreachable!(),
445                },
446            }
447        }
448        #[cfg(all(
449            not(feature = "std"),
450            any(target_arch = "x86", target_arch = "x86_64"),
451            feature = "hardware-rng"
452        ))]
453        {
454            return Self::hardware_rng(algorithm);
455        }
456        #[cfg(not(feature = "std"))]
457        {
458            return Self::deterministic_rng(algorithm);
459        }
460    }
461
462    const SEED_FOR_XOR_SHIFT: [u8; 16] = [
463        0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea,
464        0x99, 0x67, 0x2d, 0x6d,
465    ];
466
467    const SEED_FOR_CHA_CHA: [u8; 32] = [
468        0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea,
469        0x99, 0x67, 0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0,
470        0x59, 0x22, 0x6d, 0xc5, 0x46, 0x39, 0x1c, 0x4a,
471    ];
472
473    /// Returns a `TestRng` with a seed generated with the
474    /// RdRand instruction on x86 machines.
475    ///
476    /// This is useful in `no_std` scenarios on x86 where we don't
477    /// have a random number infrastructure but the `rdrand` instruction is
478    /// available.
479    #[cfg(all(
480        not(feature = "std"),
481        any(target_arch = "x86", target_arch = "x86_64"),
482        feature = "hardware-rng"
483    ))]
484    pub fn hardware_rng(algorithm: RngAlgorithm) -> Self {
485        use x86::random::{rdrand_slice, RdRand};
486
487        Self::from_seed_internal(match algorithm {
488            RngAlgorithm::XorShift => {
489                // Initialize to a sane seed just in case
490                let mut seed: [u8; 16] = TestRng::SEED_FOR_XOR_SHIFT;
491                unsafe {
492                    let r = rdrand_slice(&mut seed);
493                    debug_assert!(r, "hardware_rng should only be called on machines with support for rdrand");
494                }
495                Seed::XorShift(seed)
496            }
497            RngAlgorithm::ChaCha => {
498                // Initialize to a sane seed just in case
499                let mut seed: [u8; 32] = TestRng::SEED_FOR_CHA_CHA;
500                unsafe {
501                    let r = rdrand_slice(&mut seed);
502                    debug_assert!(r, "hardware_rng should only be called on machines with support for rdrand");
503                }
504                Seed::ChaCha(seed)
505            }
506            RngAlgorithm::PassThrough => {
507                panic!("deterministic RNG not available for PassThrough")
508            }
509            RngAlgorithm::Recorder => {
510                // Initialize to a sane seed just in case
511                let mut seed: [u8; 32] = TestRng::SEED_FOR_CHA_CHA;
512                unsafe {
513                    let r = rdrand_slice(&mut seed);
514                    debug_assert!(r, "hardware_rng should only be called on machines with support for rdrand");
515                }
516                Seed::Recorder(seed)
517            }
518            RngAlgorithm::_NonExhaustive => unreachable!(),
519        })
520    }
521
522    /// Returns a `TestRng` with a particular hard-coded seed.
523    ///
524    /// The seed value will always be the same for a particular version of
525    /// Proptest and algorithm, but may change across releases.
526    ///
527    /// This is useful for testing things like strategy implementations without
528    /// risking getting "unlucky" RNGs which deviate from average behaviour
529    /// enough to cause spurious failures. For example, a strategy for `bool`
530    /// which is supposed to produce `true` 50% of the time might have a test
531    /// which checks that the distribution is "close enough" to 50%. If every
532    /// test run starts with a different RNG, occasionally there will be
533    /// spurious test failures when the RNG happens to produce a very skewed
534    /// distribution. Using this or `TestRunner::deterministic()` avoids such
535    /// issues.
536    pub fn deterministic_rng(algorithm: RngAlgorithm) -> Self {
537        Self::from_seed_internal(match algorithm {
538            RngAlgorithm::XorShift => {
539                Seed::XorShift(TestRng::SEED_FOR_XOR_SHIFT)
540            }
541            RngAlgorithm::ChaCha => Seed::ChaCha(TestRng::SEED_FOR_CHA_CHA),
542            RngAlgorithm::PassThrough => {
543                panic!("deterministic RNG not available for PassThrough")
544            }
545            RngAlgorithm::Recorder => Seed::Recorder(TestRng::SEED_FOR_CHA_CHA),
546            RngAlgorithm::_NonExhaustive => unreachable!(),
547        })
548    }
549
550    /// Construct a TestRng by the perturbed randomized seed
551    /// from an existing TestRng.
552    pub(crate) fn gen_rng(&mut self) -> Self {
553        Self::from_seed_internal(self.new_rng_seed())
554    }
555
556    /// Overwrite the given TestRng with the provided seed.
557    pub(crate) fn set_seed(&mut self, seed: Seed) {
558        *self = Self::from_seed_internal(seed);
559    }
560
561    /// Generate a new randomized seed, set it to this TestRng,
562    /// and return the seed.
563    pub(crate) fn gen_get_seed(&mut self) -> Seed {
564        let seed = self.new_rng_seed();
565        self.set_seed(seed.clone());
566        seed
567    }
568
569    /// Randomize a perturbed randomized seed from the given TestRng.
570    pub(crate) fn new_rng_seed(&mut self) -> Seed {
571        match self.rng {
572            TestRngImpl::XorShift(ref mut rng) => {
573                let mut seed = rng.gen::<[u8; 16]>();
574
575                // Directly using XorShiftRng::from_seed() at this point would
576                // result in rng and the returned value being exactly the same.
577                // Perturb the seed with some arbitrary values to prevent this.
578                for word in seed.chunks_mut(4) {
579                    word[3] ^= 0xde;
580                    word[2] ^= 0xad;
581                    word[1] ^= 0xbe;
582                    word[0] ^= 0xef;
583                }
584
585                Seed::XorShift(seed)
586            }
587
588            TestRngImpl::ChaCha(ref mut rng) => Seed::ChaCha(rng.gen()),
589
590            TestRngImpl::PassThrough {
591                ref mut off,
592                ref mut end,
593                ref data,
594            } => {
595                let len = *end - *off;
596                let child_start = *off + len / 2;
597                let child_end = *off + len;
598                *end = child_start;
599                Seed::PassThrough(
600                    Some((child_start, child_end)),
601                    Arc::clone(data),
602                )
603            }
604
605            TestRngImpl::Recorder { ref mut rng, .. } => {
606                Seed::Recorder(rng.gen())
607            }
608        }
609    }
610
611    /// Construct a TestRng from a given seed.
612    fn from_seed_internal(seed: Seed) -> Self {
613        Self {
614            rng: match seed {
615                Seed::XorShift(seed) => {
616                    TestRngImpl::XorShift(XorShiftRng::from_seed(seed))
617                }
618
619                Seed::ChaCha(seed) => {
620                    TestRngImpl::ChaCha(ChaChaRng::from_seed(seed))
621                }
622
623                Seed::PassThrough(bounds, data) => {
624                    let (start, end) = bounds.unwrap_or((0, data.len()));
625                    TestRngImpl::PassThrough {
626                        off: start,
627                        end,
628                        data,
629                    }
630                }
631
632                Seed::Recorder(seed) => TestRngImpl::Recorder {
633                    rng: ChaChaRng::from_seed(seed),
634                    record: Vec::new(),
635                },
636            },
637        }
638    }
639}
640
641#[cfg(test)]
642mod test {
643    use crate::std_facade::Vec;
644
645    use rand::{Rng, RngCore};
646
647    use super::{RngAlgorithm, Seed, TestRng};
648    use crate::arbitrary::any;
649    use crate::strategy::*;
650
651    proptest! {
652        #[test]
653        fn gen_parse_seeds(
654            seed in prop_oneof![
655                any::<[u8;16]>().prop_map(Seed::XorShift),
656                any::<[u8;32]>().prop_map(Seed::ChaCha),
657                any::<Vec<u8>>().prop_map(|data| Seed::PassThrough(None, data.into())),
658                any::<[u8;32]>().prop_map(Seed::Recorder),
659            ])
660        {
661            assert_eq!(seed, Seed::from_persistence(&seed.to_persistence()).unwrap());
662        }
663
664        #[test]
665        fn rngs_dont_clone_self_on_genrng(
666            seed in prop_oneof![
667                any::<[u8;16]>().prop_map(Seed::XorShift),
668                any::<[u8;32]>().prop_map(Seed::ChaCha),
669                Just(()).prop_perturb(|_, mut rng| {
670                    let mut buf = vec![0u8; 2048];
671                    rng.fill_bytes(&mut buf);
672                    Seed::PassThrough(None, buf.into())
673                }),
674                any::<[u8;32]>().prop_map(Seed::Recorder),
675            ])
676        {
677            type Value = [u8;32];
678            let orig = TestRng::from_seed_internal(seed);
679
680            {
681                let mut rng1 = orig.clone();
682                let mut rng2 = rng1.gen_rng();
683                assert_ne!(rng1.gen::<Value>(), rng2.gen::<Value>());
684            }
685
686            {
687                let mut rng1 = orig.clone();
688                let mut rng2 = rng1.gen_rng();
689                let mut rng3 = rng1.gen_rng();
690                let mut rng4 = rng2.gen_rng();
691                let a = rng1.gen::<Value>();
692                let b = rng2.gen::<Value>();
693                let c = rng3.gen::<Value>();
694                let d = rng4.gen::<Value>();
695                assert_ne!(a, b);
696                assert_ne!(a, c);
697                assert_ne!(a, d);
698                assert_ne!(b, c);
699                assert_ne!(b, d);
700                assert_ne!(c, d);
701            }
702        }
703    }
704
705    #[test]
706    fn passthrough_rng_behaves_properly() {
707        let mut rng = TestRng::from_seed(
708            RngAlgorithm::PassThrough,
709            &[
710                0xDE, 0xC0, 0x12, 0x34, 0x56, 0x78, 0xFE, 0xCA, 0xEF, 0xBE,
711                0xAD, 0xDE, 0x01, 0x02, 0x03,
712            ],
713        );
714
715        assert_eq!(0x3412C0DE, rng.next_u32());
716        assert_eq!(0xDEADBEEFCAFE7856, rng.next_u64());
717
718        let mut buf = [0u8; 4];
719        rng.try_fill_bytes(&mut buf[0..4]).unwrap();
720        assert_eq!([1, 2, 3, 0], buf);
721        rng.try_fill_bytes(&mut buf[0..4]).unwrap();
722        assert_eq!([0, 0, 0, 0], buf);
723    }
724}