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}