Skip to main content

proptest/test_runner/
rng.rs

1//-
2// Copyright 2017, 2018, 2019, 2020, 2026 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 chacha20::ChaCha20Rng;
11use crate::std_facade::{Arc, String, ToOwned, Vec};
12use core::convert::{Infallible, TryInto};
13use core::result::Result;
14use core::{fmt, str, u8};
15use crate::test_runner::config;
16use rand::{Rng, RngExt, SeedableRng, TryRng};
17#[cfg(feature = "std")]
18use rand::rand_core::UnwrapErr;
19#[cfg(feature = "std")]
20use rand::rngs::SysRng;
21use rand_xorshift::XorShiftRng;
22
23/// Identifies a particular RNG algorithm supported by proptest.
24///
25/// Proptest supports dynamic configuration of algorithms to allow it to
26/// continue operating with persisted regression files and to allow the
27/// configuration to be expressed in the `Config` struct.
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum RngAlgorithm {
30    /// The [XorShift](https://rust-random.github.io/rand/rand_xorshift/struct.XorShiftRng.html)
31    /// algorithm. This was the default up through and including Proptest 0.9.0.
32    ///
33    /// It is faster than ChaCha but produces lower quality randomness and has
34    /// some pathological cases where it may fail to produce outputs that are
35    /// random even to casual observation.
36    ///
37    /// The seed must be exactly 16 bytes.
38    XorShift,
39    /// The [ChaCha](https://docs.rs/chacha20/latest/chacha20/struct.ChaCha20Rng.html)
40    /// algorithm. This became the default with Proptest 0.9.1.
41    ///
42    /// The seed must be exactly 32 bytes.
43    ChaCha,
44    /// This is not an actual RNG algorithm, but instead returns data directly
45    /// from its "seed".
46    ///
47    /// This is useful when Proptest is being driven from some other entropy
48    /// source, such as a fuzzer.
49    ///
50    /// If the seed is depleted, the RNG will return 0s forever.
51    ///
52    /// Note that in cases where a new RNG is to be derived from an existing
53    /// one, *the data is split evenly between them*, regardless of how much
54    /// entropy is actually needed. This means that combinators like
55    /// `prop_perturb` and `prop_flat_map` can require extremely large inputs.
56    PassThrough,
57    /// This is equivalent to the `ChaCha` RNG, with the addition that it
58    /// records the bytes used to create a value.
59    ///
60    /// This is useful when Proptest is used for fuzzing, and a corpus of
61    /// initial inputs need to be created. Note that in these cases, you need
62    /// to use the `TestRunner` API directly yourself instead of using the
63    /// `proptest!` macro, as otherwise there is no way to obtain the bytes
64    /// this captures.
65    Recorder,
66    #[allow(missing_docs)]
67    #[doc(hidden)]
68    _NonExhaustive,
69}
70
71impl Default for RngAlgorithm {
72    fn default() -> Self {
73        RngAlgorithm::ChaCha
74    }
75}
76
77impl RngAlgorithm {
78    pub(crate) fn persistence_key(self) -> &'static str {
79        match self {
80            RngAlgorithm::XorShift => "xs",
81            RngAlgorithm::ChaCha => "cc",
82            RngAlgorithm::PassThrough => "pt",
83            RngAlgorithm::Recorder => "rc",
84            RngAlgorithm::_NonExhaustive => unreachable!(),
85        }
86    }
87
88    pub(crate) fn from_persistence_key(k: &str) -> Option<Self> {
89        match k {
90            "xs" => Some(RngAlgorithm::XorShift),
91            "cc" => Some(RngAlgorithm::ChaCha),
92            "pt" => Some(RngAlgorithm::PassThrough),
93            "rc" => Some(RngAlgorithm::Recorder),
94            _ => None,
95        }
96    }
97}
98
99// These two are only used for parsing the environment variable
100// PROPTEST_RNG_ALGORITHM.
101impl str::FromStr for RngAlgorithm {
102    type Err = ();
103    fn from_str(s: &str) -> Result<Self, ()> {
104        RngAlgorithm::from_persistence_key(s).ok_or(())
105    }
106}
107impl fmt::Display for RngAlgorithm {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        write!(f, "{}", self.persistence_key())
110    }
111}
112
113/// Proptest's random number generator.
114#[derive(Clone, Debug)]
115pub struct TestRng {
116    rng: TestRngImpl,
117}
118
119#[derive(Debug)]
120enum TestRngImpl {
121    XorShift(XorShiftRng),
122    ChaCha(ChaCha20Rng),
123    PassThrough {
124        off: usize,
125        end: usize,
126        data: Arc<[u8]>,
127    },
128    Recorder {
129        rng: ChaCha20Rng,
130        record: Vec<u8>,
131    },
132}
133
134// ChaCha20Rng does not implement Clone, so we have to implement it manually
135impl Clone for TestRngImpl {
136    fn clone(&self) -> Self {
137        match self {
138            Self::XorShift(rng) => Self::XorShift(rng.clone()),
139            Self::ChaCha(rng) => Self::ChaCha(clone_chacha20_rng(rng)),
140            Self::PassThrough { off, end, data } => Self::PassThrough { off: off.clone(), end: end.clone(), data: data.clone() },
141            Self::Recorder { rng, record } => Self::Recorder { rng: clone_chacha20_rng(rng), record: record.clone() },
142        }
143    }
144}
145
146fn clone_chacha20_rng(rng: &ChaCha20Rng) -> ChaCha20Rng {
147    let seed = rng.get_seed();
148    let stream = rng.get_stream();
149    let word_offset = rng.get_word_pos();
150
151    let mut rng_clone = ChaCha20Rng::from_seed(seed);
152    rng_clone.set_stream(stream);
153    rng_clone.set_word_pos(word_offset);
154
155    rng_clone
156}
157
158#[cfg(feature = "std")]
159fn from_sys_rng<R: SeedableRng>() -> R {
160    let mut rng = UnwrapErr(SysRng);
161    R::from_rng(&mut rng)
162}
163
164impl TestRng {
165    fn next_u32_inner(&mut self) -> u32 {
166        match &mut self.rng {
167            TestRngImpl::XorShift(rng) => rng.next_u32(),
168            TestRngImpl::ChaCha(rng) => rng.next_u32(),
169            TestRngImpl::PassThrough { .. } => {
170                let mut buf = [0; 4];
171                self.fill_bytes_inner(&mut buf[..]);
172                u32::from_le_bytes(buf)
173            }
174            TestRngImpl::Recorder { rng, record } => {
175                let read = rng.next_u32();
176                record.extend_from_slice(&read.to_le_bytes());
177                read
178            }
179        }
180    }
181
182    fn next_u64_inner(&mut self) -> u64 {
183        match &mut self.rng {
184            TestRngImpl::XorShift(rng) => rng.next_u64(),
185            TestRngImpl::ChaCha(rng) => rng.next_u64(),
186            TestRngImpl::PassThrough { .. } => {
187                let mut buf = [0; 8];
188                self.fill_bytes_inner(&mut buf[..]);
189                u64::from_le_bytes(buf)
190            }
191            TestRngImpl::Recorder { rng, record } => {
192                let read = rng.next_u64();
193                record.extend_from_slice(&read.to_le_bytes());
194                read
195            }
196        }
197    }
198
199    fn fill_bytes_inner(&mut self, dest: &mut [u8]) {
200        match &mut self.rng {
201            TestRngImpl::XorShift(rng) => rng.fill_bytes(dest),
202            TestRngImpl::ChaCha(rng) => rng.fill_bytes(dest),
203            TestRngImpl::PassThrough { off, end, data } => {
204                let bytes_to_copy = dest.len().min(*end - *off);
205                dest[.. bytes_to_copy].copy_from_slice(&data[*off .. *off + bytes_to_copy]);
206                *off += bytes_to_copy;
207                for i in bytes_to_copy .. dest.len() {
208                    dest[i] = 0;
209                }
210            }
211            TestRngImpl::Recorder { rng, record } => {
212                rng.fill_bytes(dest);
213                record.extend_from_slice(dest);
214            }
215        }
216    }
217}
218
219impl TryRng for TestRng {
220    type Error = Infallible;
221
222    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
223        Ok(self.next_u32_inner())
224    }
225
226    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
227        Ok(self.next_u64_inner())
228    }
229
230    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
231        self.fill_bytes_inner(dest);
232        Ok(())
233    }
234}
235
236#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
237pub(crate) enum Seed {
238    XorShift([u8; 16]),
239    ChaCha([u8; 32]),
240    PassThrough(Option<(usize, usize)>, Arc<[u8]>),
241    Recorder([u8; 32]),
242}
243
244impl Seed {
245    pub(crate) fn from_bytes(algorithm: RngAlgorithm, seed: &[u8]) -> Self {
246        match algorithm {
247            RngAlgorithm::XorShift => {
248                assert_eq!(16, seed.len(), "XorShift requires a 16-byte seed");
249                let mut buf = [0; 16];
250                buf.copy_from_slice(seed);
251                Seed::XorShift(buf)
252            }
253
254            RngAlgorithm::ChaCha => {
255                assert_eq!(32, seed.len(), "ChaCha requires a 32-byte seed");
256                let mut buf = [0; 32];
257                buf.copy_from_slice(seed);
258                Seed::ChaCha(buf)
259            }
260
261            RngAlgorithm::PassThrough => Seed::PassThrough(None, seed.into()),
262
263            RngAlgorithm::Recorder => {
264                assert_eq!(32, seed.len(), "Recorder requires a 32-byte seed");
265                let mut buf = [0; 32];
266                buf.copy_from_slice(seed);
267                Seed::Recorder(buf)
268            }
269
270            RngAlgorithm::_NonExhaustive => unreachable!(),
271        }
272    }
273
274    pub(crate) fn from_persistence(string: &str) -> Option<Seed> {
275        fn from_base16(dst: &mut [u8], src: &str) -> Option<()> {
276            if dst.len() * 2 != src.len() {
277                return None;
278            }
279
280            for (dst_byte, src_pair) in
281                dst.into_iter().zip(src.as_bytes().chunks(2))
282            {
283                *dst_byte =
284                    u8::from_str_radix(str::from_utf8(src_pair).ok()?, 16)
285                        .ok()?;
286            }
287
288            Some(())
289        }
290
291        let parts =
292            string.trim().split(char::is_whitespace).collect::<Vec<_>>();
293        RngAlgorithm::from_persistence_key(&parts[0]).and_then(
294            |alg| match alg {
295                RngAlgorithm::XorShift => {
296                    if 5 != parts.len() {
297                        return None;
298                    }
299
300                    let mut dwords = [0u32; 4];
301                    for (dword, part) in
302                        (&mut dwords[..]).into_iter().zip(&parts[1..])
303                    {
304                        *dword = part.parse().ok()?;
305                    }
306
307                    let mut seed = [0u8; 16];
308                    for (chunk, dword) in seed.chunks_mut(4).zip(dwords) {
309                        chunk.copy_from_slice(&dword.to_le_bytes());
310                    }
311                    Some(Seed::XorShift(seed))
312                }
313
314                RngAlgorithm::ChaCha => {
315                    if 2 != parts.len() {
316                        return None;
317                    }
318
319                    let mut seed = [0u8; 32];
320                    from_base16(&mut seed, &parts[1])?;
321                    Some(Seed::ChaCha(seed))
322                }
323
324                RngAlgorithm::PassThrough => {
325                    if 1 == parts.len() {
326                        return Some(Seed::PassThrough(None, vec![].into()));
327                    }
328
329                    if 2 != parts.len() {
330                        return None;
331                    }
332
333                    let mut seed = vec![0u8; parts[1].len() / 2];
334                    from_base16(&mut seed, &parts[1])?;
335                    Some(Seed::PassThrough(None, seed.into()))
336                }
337
338                RngAlgorithm::Recorder => {
339                    if 2 != parts.len() {
340                        return None;
341                    }
342
343                    let mut seed = [0u8; 32];
344                    from_base16(&mut seed, &parts[1])?;
345                    Some(Seed::Recorder(seed))
346                }
347
348                RngAlgorithm::_NonExhaustive => unreachable!(),
349            },
350        )
351    }
352
353    pub(crate) fn to_persistence(&self) -> String {
354        fn to_base16(dst: &mut String, src: &[u8]) {
355            for byte in src {
356                dst.push_str(&format!("{:02x}", byte));
357            }
358        }
359
360        match *self {
361            Seed::XorShift(ref seed) => {
362                let dwords = [
363                    u32::from_le_bytes(seed[0..4].try_into().unwrap()),
364                    u32::from_le_bytes(seed[4..8].try_into().unwrap()),
365                    u32::from_le_bytes(seed[8..12].try_into().unwrap()),
366                    u32::from_le_bytes(seed[12..16].try_into().unwrap()),
367                ];
368                format!(
369                    "{} {} {} {} {}",
370                    RngAlgorithm::XorShift.persistence_key(),
371                    dwords[0],
372                    dwords[1],
373                    dwords[2],
374                    dwords[3]
375                )
376            }
377
378            Seed::ChaCha(ref seed) => {
379                let mut string =
380                    RngAlgorithm::ChaCha.persistence_key().to_owned();
381                string.push(' ');
382                to_base16(&mut string, seed);
383                string
384            }
385
386            Seed::PassThrough(bounds, ref data) => {
387                let data =
388                    bounds.map_or(&data[..], |(start, end)| &data[start..end]);
389                let mut string =
390                    RngAlgorithm::PassThrough.persistence_key().to_owned();
391                string.push(' ');
392                to_base16(&mut string, data);
393                string
394            }
395
396            Seed::Recorder(ref seed) => {
397                let mut string =
398                    RngAlgorithm::Recorder.persistence_key().to_owned();
399                string.push(' ');
400                to_base16(&mut string, seed);
401                string
402            }
403        }
404    }
405}
406
407impl TestRng {
408    /// Create a new RNG with the given algorithm and seed.
409    ///
410    /// Any RNG created with the same algorithm-seed pair will produce the same
411    /// sequence of values on all systems and all supporting versions of
412    /// proptest.
413    ///
414    /// ## Panics
415    ///
416    /// Panics if `seed` is not an appropriate length for `algorithm`.
417    pub fn from_seed(algorithm: RngAlgorithm, seed: &[u8]) -> Self {
418        TestRng::from_seed_internal(Seed::from_bytes(algorithm, seed))
419    }
420
421    /// Dumps the bytes obtained from the RNG so far (only works if the RNG is
422    /// set to `Recorder`).
423    ///
424    /// ## Panics
425    ///
426    /// Panics if this RNG does not capture generated data.
427    pub fn bytes_used(&self) -> Vec<u8> {
428        match self.rng {
429            TestRngImpl::Recorder { ref record, .. } => record.clone(),
430            _ => panic!("bytes_used() called on non-Recorder RNG"),
431        }
432    }
433
434    /// Construct a default TestRng from entropy.
435    pub(crate) fn default_rng(seed: config::RngSeed, algorithm: RngAlgorithm) -> Self {
436        #[cfg(feature = "std")]
437        {
438            Self {
439                rng: match algorithm {
440                    RngAlgorithm::XorShift => {
441                        let rng = match seed {
442                            config::RngSeed::Random => from_sys_rng::<XorShiftRng>(),
443                            config::RngSeed::Fixed(seed) => XorShiftRng::seed_from_u64(seed),
444                        };
445                        TestRngImpl::XorShift(rng)
446                    }
447                    RngAlgorithm::ChaCha => {
448                        let rng = match seed {
449                            config::RngSeed::Random => from_sys_rng::<ChaCha20Rng>(),
450                            config::RngSeed::Fixed(seed) => ChaCha20Rng::seed_from_u64(seed),
451                        };
452                        TestRngImpl::ChaCha(rng)
453                    }
454                    RngAlgorithm::PassThrough => {
455                        panic!("cannot create default instance of PassThrough")
456                    }
457                    RngAlgorithm::Recorder => {
458                        let rng =  match seed {
459                            config::RngSeed::Random => from_sys_rng::<ChaCha20Rng>(),
460                            config::RngSeed::Fixed(seed) => ChaCha20Rng::seed_from_u64(seed),
461                        };
462                        TestRngImpl::Recorder {rng, record: Vec::new()}
463                    },
464                    RngAlgorithm::_NonExhaustive => unreachable!(),
465                },
466            }
467        }
468        #[cfg(all(
469            not(feature = "std"),
470            any(target_arch = "x86", target_arch = "x86_64"),
471            feature = "hardware-rng"
472        ))]
473        {
474            return Self::hardware_rng(algorithm);
475        }
476        #[cfg(not(feature = "std"))]
477        {
478            return Self::deterministic_rng(algorithm);
479        }
480    }
481
482    const SEED_FOR_XOR_SHIFT: [u8; 16] = [
483        0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea,
484        0x99, 0x67, 0x2d, 0x6d,
485    ];
486
487    const SEED_FOR_CHA_CHA: [u8; 32] = [
488        0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea,
489        0x99, 0x67, 0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0,
490        0x59, 0x22, 0x6d, 0xc5, 0x46, 0x39, 0x1c, 0x4a,
491    ];
492
493    /// Returns a `TestRng` with a seed generated with the
494    /// RdRand instruction on x86 machines.
495    ///
496    /// This is useful in `no_std` scenarios on x86 where we don't
497    /// have a random number infrastructure but the `rdrand` instruction is
498    /// available.
499    #[cfg(all(
500        not(feature = "std"),
501        any(target_arch = "x86", target_arch = "x86_64"),
502        feature = "hardware-rng"
503    ))]
504    pub fn hardware_rng(algorithm: RngAlgorithm) -> Self {
505        use x86::random::{rdrand_slice, RdRand};
506        assert!(core_detect::is_x86_feature_detected!("rdrand"));
507
508        Self::from_seed_internal(match algorithm {
509            RngAlgorithm::XorShift => {
510                // Initialize to a sane seed just in case
511                let mut seed: [u8; 16] = TestRng::SEED_FOR_XOR_SHIFT;
512                // SAFETY: rdrand feature is asserted to be detected at the beginning of the function
513                unsafe {
514                    let r = rdrand_slice(&mut seed);
515                    assert!(r, "hardware_rng should only be called on machines with support for rdrand");
516                }
517                Seed::XorShift(seed)
518            }
519            RngAlgorithm::ChaCha => {
520                // Initialize to a sane seed just in case
521                let mut seed: [u8; 32] = TestRng::SEED_FOR_CHA_CHA;
522                // SAFETY: rdrand feature is asserted to be detected at the beginning of the function
523                unsafe {
524                    let r = rdrand_slice(&mut seed);
525                    assert!(r, "hardware_rng should only be called on machines with support for rdrand");
526                }
527                Seed::ChaCha(seed)
528            }
529            RngAlgorithm::PassThrough => {
530                panic!("deterministic RNG not available for PassThrough")
531            }
532            RngAlgorithm::Recorder => {
533                // Initialize to a sane seed just in case
534                let mut seed: [u8; 32] = TestRng::SEED_FOR_CHA_CHA;
535                // SAFETY: rdrand feature is asserted to be detected at the beginning of the function
536                unsafe {
537                    let r = rdrand_slice(&mut seed);
538                    assert!(r, "hardware_rng should only be called on machines with support for rdrand");
539                }
540                Seed::Recorder(seed)
541            }
542            RngAlgorithm::_NonExhaustive => unreachable!(),
543        })
544    }
545
546    /// Returns a `TestRng` with a particular hard-coded seed.
547    ///
548    /// The seed value will always be the same for a particular version of
549    /// Proptest and algorithm, but may change across releases.
550    ///
551    /// This is useful for testing things like strategy implementations without
552    /// risking getting "unlucky" RNGs which deviate from average behaviour
553    /// enough to cause spurious failures. For example, a strategy for `bool`
554    /// which is supposed to produce `true` 50% of the time might have a test
555    /// which checks that the distribution is "close enough" to 50%. If every
556    /// test run starts with a different RNG, occasionally there will be
557    /// spurious test failures when the RNG happens to produce a very skewed
558    /// distribution. Using this or `TestRunner::deterministic()` avoids such
559    /// issues.
560    pub fn deterministic_rng(algorithm: RngAlgorithm) -> Self {
561        Self::from_seed_internal(match algorithm {
562            RngAlgorithm::XorShift => {
563                Seed::XorShift(TestRng::SEED_FOR_XOR_SHIFT)
564            }
565            RngAlgorithm::ChaCha => Seed::ChaCha(TestRng::SEED_FOR_CHA_CHA),
566            RngAlgorithm::PassThrough => {
567                panic!("deterministic RNG not available for PassThrough")
568            }
569            RngAlgorithm::Recorder => Seed::Recorder(TestRng::SEED_FOR_CHA_CHA),
570            RngAlgorithm::_NonExhaustive => unreachable!(),
571        })
572    }
573
574    /// Construct a TestRng by the perturbed randomized seed
575    /// from an existing TestRng.
576    pub(crate) fn gen_rng(&mut self) -> Self {
577        Self::from_seed_internal(self.new_rng_seed())
578    }
579
580    /// Overwrite the given TestRng with the provided seed.
581    pub(crate) fn set_seed(&mut self, seed: Seed) {
582        *self = Self::from_seed_internal(seed);
583    }
584
585    /// Generate a new randomized seed, set it to this TestRng,
586    /// and return the seed.
587    pub(crate) fn gen_get_seed(&mut self) -> Seed {
588        let seed = self.new_rng_seed();
589        self.set_seed(seed.clone());
590        seed
591    }
592
593    /// Randomize a perturbed randomized seed from the given TestRng.
594    pub(crate) fn new_rng_seed(&mut self) -> Seed {
595        match self.rng {
596            TestRngImpl::XorShift(ref mut rng) => {
597                let mut seed = rng.random::<[u8; 16]>();
598
599                // Directly using XorShiftRng::from_seed() at this point would
600                // result in rng and the returned value being exactly the same.
601                // Perturb the seed with some arbitrary values to prevent this.
602                for word in seed.chunks_mut(4) {
603                    word[3] ^= 0xde;
604                    word[2] ^= 0xad;
605                    word[1] ^= 0xbe;
606                    word[0] ^= 0xef;
607                }
608
609                Seed::XorShift(seed)
610            }
611
612            TestRngImpl::ChaCha(ref mut rng) => Seed::ChaCha(rng.random()),
613
614            TestRngImpl::PassThrough {
615                ref mut off,
616                ref mut end,
617                ref data,
618            } => {
619                let len = *end - *off;
620                let child_start = *off + len / 2;
621                let child_end = *off + len;
622                *end = child_start;
623                Seed::PassThrough(
624                    Some((child_start, child_end)),
625                    Arc::clone(data),
626                )
627            }
628
629            TestRngImpl::Recorder { ref mut rng, .. } => {
630                Seed::Recorder(rng.random())
631            }
632        }
633    }
634
635    /// Construct a TestRng from a given seed.
636    fn from_seed_internal(seed: Seed) -> Self {
637        Self {
638            rng: match seed {
639                Seed::XorShift(seed) => {
640                    TestRngImpl::XorShift(XorShiftRng::from_seed(seed))
641                }
642
643                Seed::ChaCha(seed) => {
644                    TestRngImpl::ChaCha(ChaCha20Rng::from_seed(seed))
645                }
646
647                Seed::PassThrough(bounds, data) => {
648                    let (start, end) = bounds.unwrap_or((0, data.len()));
649                    TestRngImpl::PassThrough {
650                        off: start,
651                        end,
652                        data,
653                    }
654                }
655
656                Seed::Recorder(seed) => TestRngImpl::Recorder {
657                    rng: ChaCha20Rng::from_seed(seed),
658                    record: Vec::new(),
659                },
660            },
661        }
662    }
663}
664
665#[cfg(test)]
666mod test {
667    use crate::std_facade::Vec;
668
669    use rand::{Rng, RngExt};
670
671    use super::{RngAlgorithm, Seed, TestRng};
672    use crate::arbitrary::any;
673    use crate::strategy::*;
674
675    proptest! {
676        #[test]
677        fn gen_parse_seeds(
678            seed in prop_oneof![
679                any::<[u8;16]>().prop_map(Seed::XorShift),
680                any::<[u8;32]>().prop_map(Seed::ChaCha),
681                any::<Vec<u8>>().prop_map(|data| Seed::PassThrough(None, data.into())),
682                any::<[u8;32]>().prop_map(Seed::Recorder),
683            ])
684        {
685            assert_eq!(seed, Seed::from_persistence(&seed.to_persistence()).unwrap());
686        }
687
688        #[test]
689        fn rngs_dont_clone_self_on_genrng(
690            seed in prop_oneof![
691                any::<[u8;16]>().prop_map(Seed::XorShift),
692                any::<[u8;32]>().prop_map(Seed::ChaCha),
693                Just(()).prop_perturb(|_, mut rng| {
694                    let mut buf = vec![0u8; 2048];
695                    rng.fill_bytes(&mut buf);
696                    Seed::PassThrough(None, buf.into())
697                }),
698                any::<[u8;32]>().prop_map(Seed::Recorder),
699            ])
700        {
701            type Value = [u8;32];
702            let orig = TestRng::from_seed_internal(seed);
703
704            {
705                let mut rng1 = orig.clone();
706                let mut rng2 = rng1.gen_rng();
707                assert_ne!(rng1.random::<Value>(), rng2.random::<Value>());
708            }
709
710            {
711                let mut rng1 = orig.clone();
712                let mut rng2 = rng1.gen_rng();
713                let mut rng3 = rng1.gen_rng();
714                let mut rng4 = rng2.gen_rng();
715                let a = rng1.random::<Value>();
716                let b = rng2.random::<Value>();
717                let c = rng3.random::<Value>();
718                let d = rng4.random::<Value>();
719                assert_ne!(a, b);
720                assert_ne!(a, c);
721                assert_ne!(a, d);
722                assert_ne!(b, c);
723                assert_ne!(b, d);
724                assert_ne!(c, d);
725            }
726        }
727
728        #[test]
729        fn rngs_clone(
730            seed in prop_oneof![
731                any::<[u8;16]>().prop_map(Seed::XorShift),
732                any::<[u8;32]>().prop_map(Seed::ChaCha),
733                Just(()).prop_perturb(|_, mut rng| {
734                    let mut buf = vec![0u8; 2048];
735                    rng.fill_bytes(&mut buf);
736                    Seed::PassThrough(None, buf.into())
737                }),
738                any::<[u8;32]>().prop_map(Seed::Recorder),
739            ])
740        {
741            type Value = [u8;32];
742            let orig = TestRng::from_seed_internal(seed);
743
744            {
745                let mut rng1 = orig.clone();
746                rng1.random::<Value>();
747                let mut rng2 = rng1.clone();
748                assert_eq!(rng1.random::<Value>(), rng2.random::<Value>());
749            }
750
751            {
752                let mut rng1 = orig.clone();
753                rng1.random::<Value>();
754                rng1.random::<Value>();
755                let mut rng2 = rng1.clone();
756                let mut rng3 = rng1.clone();
757                let mut rng4 = rng2.clone();
758                let a = rng1.random::<Value>();
759                let b = rng2.random::<Value>();
760                let c = rng3.random::<Value>();
761                let d = rng4.random::<Value>();
762                assert_eq!(a, b);
763                assert_eq!(a, c);
764                assert_eq!(a, d);
765            }
766        }
767    }
768
769    #[test]
770    fn passthrough_rng_behaves_properly() {
771        let mut rng = TestRng::from_seed(
772            RngAlgorithm::PassThrough,
773            &[
774                0xDE, 0xC0, 0x12, 0x34, 0x56, 0x78, 0xFE, 0xCA, 0xEF, 0xBE,
775                0xAD, 0xDE, 0x01, 0x02, 0x03,
776            ],
777        );
778
779        assert_eq!(0x3412C0DE, rng.next_u32());
780        assert_eq!(0xDEADBEEFCAFE7856, rng.next_u64());
781
782        let mut buf = [0u8; 4];
783        rng.fill_bytes(&mut buf[0..4]);
784        assert_eq!([1, 2, 3, 0], buf);
785        rng.fill_bytes(&mut buf[0..4]);
786        assert_eq!([0, 0, 0, 0], buf);
787    }
788
789    #[test]
790    fn seeded_xorshift_output_is_stable() {
791        let seed = [
792            0x00, 0x01, 0x02, 0x03,
793            0x04, 0x05, 0x06, 0x07,
794            0x08, 0x09, 0x0a, 0x0b,
795            0x0c, 0x0d, 0x0e, 0x0f,
796        ];
797        let mut rng_u32 = TestRng::from_seed(RngAlgorithm::XorShift, &seed);
798        let mut rng_u64 = TestRng::from_seed(RngAlgorithm::XorShift, &seed);
799        let mut rng_fill = TestRng::from_seed(RngAlgorithm::XorShift, &seed);
800
801        assert_eq!(
802            [
803                471271404,
804                722341711,
805                1880555887,
806                252576780,
807            ],
808            [
809                rng_u32.next_u32(),
810                rng_u32.next_u32(),
811                rng_u32.next_u32(),
812                rng_u32.next_u32(),
813            ]
814        );
815        assert_eq!(
816            [
817                3102434025752954860,
818                1084809011709542767,
819                17342619095589341798,
820                5127465042768897837,
821            ],
822            [
823                rng_u64.next_u64(),
824                rng_u64.next_u64(),
825                rng_u64.next_u64(),
826                rng_u64.next_u64(),
827            ]
828        );
829
830        let mut fill = [0u8; 16];
831        rng_fill.fill_bytes(&mut fill);
832        assert_eq!(
833            [236, 7, 23, 28, 79, 15, 14, 43, 111, 1, 23, 112, 12, 4, 14, 15],
834            fill
835        );
836    }
837
838    #[test]
839    fn seeded_chacha_output_is_stable() {
840        let seed = [
841            0x00, 0x01, 0x02, 0x03,
842            0x04, 0x05, 0x06, 0x07,
843            0x08, 0x09, 0x0a, 0x0b,
844            0x0c, 0x0d, 0x0e, 0x0f,
845            0x10, 0x11, 0x12, 0x13,
846            0x14, 0x15, 0x16, 0x17,
847            0x18, 0x19, 0x1a, 0x1b,
848            0x1c, 0x1d, 0x1e, 0x1f,
849        ];
850        let mut rng_u32 = TestRng::from_seed(RngAlgorithm::ChaCha, &seed);
851        let mut rng_u64 = TestRng::from_seed(RngAlgorithm::ChaCha, &seed);
852        let mut rng_fill = TestRng::from_seed(RngAlgorithm::ChaCha, &seed);
853
854        assert_eq!(
855            [
856                2100034873,
857                1780073945,
858                1996733837,
859                1229642936,
860            ],
861            [
862                rng_u32.next_u32(),
863                rng_u32.next_u32(),
864                rng_u32.next_u32(),
865                rng_u32.next_u32(),
866            ]
867        );
868        assert_eq!(
869            [
870                7645359380336737593,
871                5281276197874154893,
872                14729830432180286858,
873                10530800043416210610,
874            ],
875            [
876                rng_u64.next_u64(),
877                rng_u64.next_u64(),
878                rng_u64.next_u64(),
879                rng_u64.next_u64(),
880            ]
881        );
882
883        let mut fill = [0u8; 16];
884        rng_fill.fill_bytes(&mut fill);
885        assert_eq!(
886            [57, 253, 43, 125, 217, 197, 25, 106, 141, 189, 3, 119, 184, 220, 74, 73],
887            fill
888        );
889    }
890
891    #[test]
892    fn derived_child_rng_output_is_stable() {
893        let seed = [
894            0x00, 0x01, 0x02, 0x03,
895            0x04, 0x05, 0x06, 0x07,
896            0x08, 0x09, 0x0a, 0x0b,
897            0x0c, 0x0d, 0x0e, 0x0f,
898            0x10, 0x11, 0x12, 0x13,
899            0x14, 0x15, 0x16, 0x17,
900            0x18, 0x19, 0x1a, 0x1b,
901            0x1c, 0x1d, 0x1e, 0x1f,
902        ];
903        let mut parent = TestRng::from_seed(RngAlgorithm::ChaCha, &seed);
904        let mut child = parent.gen_rng();
905
906        assert_eq!(
907            [
908                357635273,
909                1295757006,
910                1334659017,
911                3423482104,
912            ],
913            [
914                child.next_u32(),
915                child.next_u32(),
916                child.next_u32(),
917                child.next_u32(),
918            ],
919        );
920    }
921
922    #[test]
923    fn recorder_bytes_used_matches_emitted_bytes() {
924        let seed = [
925            0x00, 0x01, 0x02, 0x03,
926            0x04, 0x05, 0x06, 0x07,
927            0x08, 0x09, 0x0a, 0x0b,
928            0x0c, 0x0d, 0x0e, 0x0f,
929            0x10, 0x11, 0x12, 0x13,
930            0x14, 0x15, 0x16, 0x17,
931            0x18, 0x19, 0x1a, 0x1b,
932            0x1c, 0x1d, 0x1e, 0x1f,
933        ];
934        let mut rng = TestRng::from_seed(RngAlgorithm::Recorder, &seed);
935        let first = rng.next_u32();
936        let second = rng.next_u64();
937        let mut fill = [0u8; 16];
938        rng.fill_bytes(&mut fill);
939
940        let mut expected = Vec::new();
941        expected.extend_from_slice(&first.to_le_bytes());
942        expected.extend_from_slice(&second.to_le_bytes());
943        expected.extend_from_slice(&fill);
944
945        assert_eq!(expected, rng.bytes_used());
946    }
947
948    #[test]
949    fn recorder_clone_preserves_history() {
950        let mut original = TestRng::from_seed(RngAlgorithm::Recorder, &[42; 32]);
951        original.next_u32();
952        let history = original.bytes_used();
953
954        let mut cloned = original.clone();
955        assert_eq!(history, cloned.bytes_used());
956
957        // Advancing the original must not change the clone's history.
958        let next = original.next_u32();
959        assert_eq!(history, cloned.bytes_used());
960
961        // The clone resumes at the same position and records the same bytes.
962        assert_eq!(next, cloned.next_u32());
963        assert_eq!(original.bytes_used(), cloned.bytes_used());
964    }
965
966}