Skip to content

Commit

Permalink
Add PROPTEST_RNG_SEED to take initial TestRunner seed from environment (
Browse files Browse the repository at this point in the history
#549)

* Use env var override system to set rng seed

* only allow u64 parsing

* remove std usage for FromStr and Display
  • Loading branch information
martyall authored Feb 24, 2025
1 parent 924e1c7 commit b9eb3e4
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 10 deletions.
40 changes: 39 additions & 1 deletion proptest/src/test_runner/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// except according to those terms.

use crate::std_facade::Box;
use core::u32;
use core::{fmt, str, u32};

use crate::test_runner::result_cache::{noop_result_cache, ResultCache};
use crate::test_runner::rng::RngAlgorithm;
Expand Down Expand Up @@ -37,6 +37,7 @@ pub fn contextualize_config(mut result: Config) -> Config {
const TIMEOUT: &str = "PROPTEST_TIMEOUT";
const VERBOSE: &str = "PROPTEST_VERBOSE";
const RNG_ALGORITHM: &str = "PROPTEST_RNG_ALGORITHM";
const RNG_SEED: &str = "PROPTEST_RNG_SEED";
const DISABLE_FAILURE_PERSISTENCE: &str =
"PROPTEST_DISABLE_FAILURE_PERSISTENCE";

Expand Down Expand Up @@ -135,6 +136,13 @@ pub fn contextualize_config(mut result: Config) -> Config {
"RngAlgorithm",
RNG_ALGORITHM,
);
} else if var == RNG_SEED {
parse_or_warn(
&value,
&mut result.rng_seed,
"u64",
RNG_SEED,
);
} else if var == DISABLE_FAILURE_PERSISTENCE {
result.failure_persistence = None;
} else if var.starts_with("PROPTEST_") {
Expand Down Expand Up @@ -172,6 +180,7 @@ fn default_default_config() -> Config {
#[cfg(feature = "std")]
verbose: 0,
rng_algorithm: RngAlgorithm::default(),
rng_seed: RngSeed::Random,
_non_exhaustive: (),
}
}
Expand All @@ -187,6 +196,31 @@ lazy_static! {
};
}

/// The seed for the RNG, can either be random or specified as a u64.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RngSeed {
/// Default case, use a random value
Random,
/// Use a specific value to generate a seed
Fixed(u64)
}

impl str::FromStr for RngSeed {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u64>().map(RngSeed::Fixed).map_err(|_| ())
}
}

impl fmt::Display for RngSeed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RngSeed::Random => write!(f, "random"),
RngSeed::Fixed(n) => write!(f, "{}", n),
}
}
}

/// Configuration for how a proptest test should be run.
#[derive(Clone, Debug, PartialEq)]
pub struct Config {
Expand Down Expand Up @@ -398,6 +432,10 @@ pub struct Config {
/// which it is by default.)
pub rng_algorithm: RngAlgorithm,

/// Seed used for the RNG. Set by using the PROPTEST_RNG_SEED environment variable
/// If the environment variable is undefined, a random seed is generated (this is the default option).
pub rng_seed: RngSeed,

// Needs to be public so FRU syntax can be used.
#[doc(hidden)]
pub _non_exhaustive: (),
Expand Down
25 changes: 18 additions & 7 deletions proptest/src/test_runner/rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use crate::std_facade::{Arc, String, ToOwned, Vec};
use core::result::Result;
use core::{fmt, str, u8, convert::TryInto};

use crate::test_runner::{config, RngSeed};
use rand::{self, Rng, RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use rand_xorshift::XorShiftRng;
Expand Down Expand Up @@ -428,23 +428,34 @@ impl TestRng {
}

/// Construct a default TestRng from entropy.
pub(crate) fn default_rng(algorithm: RngAlgorithm) -> Self {
pub(crate) fn default_rng(seed: config::RngSeed, algorithm: RngAlgorithm) -> Self {
#[cfg(feature = "std")]
{
Self {
rng: match algorithm {
RngAlgorithm::XorShift => {
TestRngImpl::XorShift(XorShiftRng::from_entropy())
let rng = match seed {
RngSeed::Random => XorShiftRng::from_entropy(),
RngSeed::Fixed(seed) => XorShiftRng::seed_from_u64(seed),
};
TestRngImpl::XorShift(rng)
}
RngAlgorithm::ChaCha => {
TestRngImpl::ChaCha(ChaChaRng::from_entropy())
let rng = match seed {
RngSeed::Random => ChaChaRng::from_entropy(),
RngSeed::Fixed(seed) => ChaChaRng::seed_from_u64(seed),
};
TestRngImpl::ChaCha(rng)
}
RngAlgorithm::PassThrough => {
panic!("cannot create default instance of PassThrough")
}
RngAlgorithm::Recorder => TestRngImpl::Recorder {
rng: ChaChaRng::from_entropy(),
record: Vec::new(),
RngAlgorithm::Recorder => {
let rng = match seed {
RngSeed::Random => ChaChaRng::from_entropy(),
RngSeed::Fixed(seed) => ChaChaRng::seed_from_u64(seed),
};
TestRngImpl::Recorder {rng, record: Vec::new()}
},
RngAlgorithm::_NonExhaustive => unreachable!(),
},
Expand Down
5 changes: 3 additions & 2 deletions proptest/src/test_runner/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,9 @@ impl TestRunner {
/// hard-coded seed. This seed is not contractually guaranteed and may be
/// changed between releases without notice.
pub fn new(config: Config) -> Self {
let seed = config.rng_seed;
let algorithm = config.rng_algorithm;
TestRunner::new_with_rng(config, TestRng::default_rng(algorithm))
TestRunner::new_with_rng(config, TestRng::default_rng(seed, algorithm))
}

/// Create a fresh `TestRunner` with the standard deterministic RNG.
Expand Down Expand Up @@ -1254,7 +1255,7 @@ mod test {

// create value with recorder rng
let default_config = Config::default();
let recorder_rng = TestRng::default_rng(RngAlgorithm::Recorder);
let recorder_rng = TestRng::default_rng(RngSeed::Random, RngAlgorithm::Recorder);
let mut runner =
TestRunner::new_with_rng(default_config.clone(), recorder_rng);
let random_byte_array1 = runner.rng().gen::<[u8; 16]>();
Expand Down

0 comments on commit b9eb3e4

Please sign in to comment.