proptest_ext/
lib.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use proptest::{
5    strategy::{Strategy, ValueTree},
6    test_runner::{Config, RngAlgorithm, TestRng, TestRunner},
7};
8use std::hash::{Hash, Hasher};
9use twox_hash::XxHash64;
10
11/// Context for generating single values out of strategies.
12///
13/// Proptest is designed to be built around "value trees", which represent a spectrum from complex
14/// values to simpler ones. But in some contexts, like benchmarking or generating corpuses, one just
15/// wants a single value. This is a convenience struct for that.
16#[derive(Debug, Default)]
17pub struct ValueGenerator {
18    runner: TestRunner,
19}
20
21impl ValueGenerator {
22    /// Creates a new value generator with the default RNG.
23    pub fn new() -> Self {
24        Self {
25            runner: TestRunner::new(Config::default()),
26        }
27    }
28
29    /// Creates a new value generator with a deterministic RNG.
30    ///
31    /// This generator has a hardcoded seed, so its results are predictable across test runs.
32    /// However, a new proptest version may change the seed.
33    pub fn deterministic() -> Self {
34        Self {
35            runner: TestRunner::deterministic(),
36        }
37    }
38
39    /// Creates a new value generator from the given seed.
40    ///
41    /// This generator is typically used with a hardcoded seed that is keyed on the input data
42    /// somehow. For example, for a test fixture it may be the name of the fixture.
43    pub fn from_seed(seed: impl Hash) -> Self {
44        // Convert the input seed into a 32-byte hash.
45        let mut rng_seed = [0u8; 32];
46        for hash_seed in 0usize..=3 {
47            let mut hasher = XxHash64::with_seed(hash_seed as u64);
48            seed.hash(&mut hasher);
49            rng_seed[hash_seed..(hash_seed + 8)].copy_from_slice(&hasher.finish().to_be_bytes());
50        }
51
52        Self {
53            runner: TestRunner::new_with_rng(
54                Config::default(),
55                TestRng::from_seed(RngAlgorithm::default(), &rng_seed),
56            ),
57        }
58    }
59
60    /// Does a "partial clone" of the `ValueGenerator`, creating a new independent but deterministic
61    /// RNG.
62    pub fn partial_clone(&mut self) -> Self {
63        Self {
64            runner: TestRunner::new_with_rng(Config::default(), self.runner.new_rng()),
65        }
66    }
67
68    /// Generates a single value for this strategy.
69    ///
70    /// Panics if generating the new value fails. The only situation in which this can happen is if
71    /// generating the value causes too many internal rejects.
72    pub fn generate<S: Strategy>(&mut self, strategy: S) -> S::Value {
73        strategy
74            .new_tree(&mut self.runner)
75            .expect("creating a new value should succeed")
76            .current()
77    }
78}