summaryrefslogtreecommitdiffstats
path: root/vendor/proptest/src/test_runner
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
commit9835e2ae736235810b4ea1c162ca5e65c547e770 (patch)
tree3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/proptest/src/test_runner
parentReleasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff)
downloadrustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz
rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/proptest/src/test_runner')
-rw-r--r--vendor/proptest/src/test_runner/config.rs516
-rw-r--r--vendor/proptest/src/test_runner/errors.rs135
-rw-r--r--vendor/proptest/src/test_runner/failure_persistence/file.rs536
-rw-r--r--vendor/proptest/src/test_runner/failure_persistence/map.rs99
-rw-r--r--vendor/proptest/src/test_runner/failure_persistence/mod.rs156
-rw-r--r--vendor/proptest/src/test_runner/failure_persistence/noop.rs76
-rw-r--r--vendor/proptest/src/test_runner/mod.rs31
-rw-r--r--vendor/proptest/src/test_runner/reason.rs54
-rw-r--r--vendor/proptest/src/test_runner/replay.rs189
-rw-r--r--vendor/proptest/src/test_runner/result_cache.rs120
-rw-r--r--vendor/proptest/src/test_runner/rng.rs724
-rw-r--r--vendor/proptest/src/test_runner/runner.rs1538
12 files changed, 4174 insertions, 0 deletions
diff --git a/vendor/proptest/src/test_runner/config.rs b/vendor/proptest/src/test_runner/config.rs
new file mode 100644
index 000000000..a3d30846a
--- /dev/null
+++ b/vendor/proptest/src/test_runner/config.rs
@@ -0,0 +1,516 @@
+//-
+// Copyright 2017, 2018, 2019 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::Box;
+use core::u32;
+
+#[cfg(feature = "std")]
+use std::env;
+#[cfg(feature = "std")]
+use std::ffi::OsString;
+#[cfg(feature = "std")]
+use std::fmt;
+#[cfg(feature = "std")]
+use std::str::FromStr;
+
+use crate::test_runner::result_cache::{noop_result_cache, ResultCache};
+use crate::test_runner::rng::RngAlgorithm;
+use crate::test_runner::FailurePersistence;
+#[cfg(feature = "std")]
+use crate::test_runner::FileFailurePersistence;
+
+#[cfg(feature = "std")]
+const CASES: &str = "PROPTEST_CASES";
+#[cfg(feature = "std")]
+const MAX_LOCAL_REJECTS: &str = "PROPTEST_MAX_LOCAL_REJECTS";
+#[cfg(feature = "std")]
+const MAX_GLOBAL_REJECTS: &str = "PROPTEST_MAX_GLOBAL_REJECTS";
+#[cfg(feature = "std")]
+const MAX_FLAT_MAP_REGENS: &str = "PROPTEST_MAX_FLAT_MAP_REGENS";
+#[cfg(feature = "std")]
+const MAX_SHRINK_TIME: &str = "PROPTEST_MAX_SHRINK_TIME";
+#[cfg(feature = "std")]
+const MAX_SHRINK_ITERS: &str = "PROPTEST_MAX_SHRINK_ITERS";
+#[cfg(feature = "fork")]
+const FORK: &str = "PROPTEST_FORK";
+#[cfg(feature = "timeout")]
+const TIMEOUT: &str = "PROPTEST_TIMEOUT";
+#[cfg(feature = "std")]
+const VERBOSE: &str = "PROPTEST_VERBOSE";
+#[cfg(feature = "std")]
+const RNG_ALGORITHM: &str = "PROPTEST_RNG_ALGORITHM";
+#[cfg(feature = "std")]
+const DISABLE_FAILURE_PERSISTENCE: &str =
+ "PROPTEST_DISABLE_FAILURE_PERSISTENCE";
+
+#[cfg(feature = "std")]
+fn contextualize_config(mut result: Config) -> Config {
+ fn parse_or_warn<T: FromStr + fmt::Display>(
+ src: &OsString,
+ dst: &mut T,
+ typ: &str,
+ var: &str,
+ ) {
+ if let Some(src) = src.to_str() {
+ if let Ok(value) = src.parse() {
+ *dst = value;
+ } else {
+ eprintln!(
+ "proptest: The env-var {}={} can't be parsed as {}, \
+ using default of {}.",
+ var, src, typ, *dst
+ );
+ }
+ } else {
+ eprintln!(
+ "proptest: The env-var {} is not valid, using \
+ default of {}.",
+ var, *dst
+ );
+ }
+ }
+
+ result.failure_persistence =
+ Some(Box::new(FileFailurePersistence::default()));
+ for (var, value) in
+ env::vars_os().filter_map(|(k, v)| k.into_string().ok().map(|k| (k, v)))
+ {
+ match var.as_str() {
+ CASES => parse_or_warn(&value, &mut result.cases, "u32", CASES),
+ MAX_LOCAL_REJECTS => parse_or_warn(
+ &value,
+ &mut result.max_local_rejects,
+ "u32",
+ MAX_LOCAL_REJECTS,
+ ),
+ MAX_GLOBAL_REJECTS => parse_or_warn(
+ &value,
+ &mut result.max_global_rejects,
+ "u32",
+ MAX_GLOBAL_REJECTS,
+ ),
+ MAX_FLAT_MAP_REGENS => parse_or_warn(
+ &value,
+ &mut result.max_flat_map_regens,
+ "u32",
+ MAX_FLAT_MAP_REGENS,
+ ),
+ #[cfg(feature = "fork")]
+ FORK => parse_or_warn(&value, &mut result.fork, "bool", FORK),
+ #[cfg(feature = "timeout")]
+ TIMEOUT => {
+ parse_or_warn(&value, &mut result.timeout, "timeout", TIMEOUT)
+ }
+ MAX_SHRINK_TIME => parse_or_warn(
+ &value,
+ &mut result.max_shrink_time,
+ "u32",
+ MAX_SHRINK_TIME,
+ ),
+ MAX_SHRINK_ITERS => parse_or_warn(
+ &value,
+ &mut result.max_shrink_iters,
+ "u32",
+ MAX_SHRINK_ITERS,
+ ),
+ VERBOSE => {
+ parse_or_warn(&value, &mut result.verbose, "u32", VERBOSE)
+ }
+ RNG_ALGORITHM => parse_or_warn(
+ &value,
+ &mut result.rng_algorithm,
+ "RngAlgorithm",
+ RNG_ALGORITHM,
+ ),
+ DISABLE_FAILURE_PERSISTENCE => result.failure_persistence = None,
+
+ _ => {
+ if var.starts_with("PROPTEST_") {
+ eprintln!("proptest: Ignoring unknown env-var {}.", var);
+ }
+ }
+ }
+ }
+
+ result
+}
+
+#[cfg(not(feature = "std"))]
+fn contextualize_config(result: Config) -> Config {
+ result
+}
+
+fn default_default_config() -> Config {
+ Config {
+ cases: 256,
+ max_local_rejects: 65_536,
+ max_global_rejects: 1024,
+ max_flat_map_regens: 1_000_000,
+ failure_persistence: None,
+ source_file: None,
+ test_name: None,
+ #[cfg(feature = "fork")]
+ fork: false,
+ #[cfg(feature = "timeout")]
+ timeout: 0,
+ #[cfg(feature = "std")]
+ max_shrink_time: 0,
+ max_shrink_iters: u32::MAX,
+ result_cache: noop_result_cache,
+ #[cfg(feature = "std")]
+ verbose: 0,
+ rng_algorithm: RngAlgorithm::default(),
+ _non_exhaustive: (),
+ }
+}
+
+// The default config, computed by combining environment variables and
+// defaults.
+#[cfg(feature = "std")]
+lazy_static! {
+ static ref DEFAULT_CONFIG: Config =
+ contextualize_config(default_default_config());
+}
+
+/// Configuration for how a proptest test should be run.
+#[derive(Clone, Debug, PartialEq)]
+pub struct Config {
+ /// The number of successful test cases that must execute for the test as a
+ /// whole to pass.
+ ///
+ /// This does not include implicitly-replayed persisted failing cases.
+ ///
+ /// The default is 256, which can be overridden by setting the
+ /// `PROPTEST_CASES` environment variable. (The variable is only considered
+ /// when the `std` feature is enabled, which it is by default.)
+ pub cases: u32,
+
+ /// The maximum number of individual inputs that may be rejected before the
+ /// test as a whole aborts.
+ ///
+ /// The default is 65536, which can be overridden by setting the
+ /// `PROPTEST_MAX_LOCAL_REJECTS` environment variable. (The variable is only
+ /// considered when the `std` feature is enabled, which it is by default.)
+ pub max_local_rejects: u32,
+
+ /// The maximum number of combined inputs that may be rejected before the
+ /// test as a whole aborts.
+ ///
+ /// The default is 1024, which can be overridden by setting the
+ /// `PROPTEST_MAX_GLOBAL_REJECTS` environment variable. (The variable is
+ /// only considered when the `std` feature is enabled, which it is by
+ /// default.)
+ pub max_global_rejects: u32,
+
+ /// The maximum number of times all `Flatten` combinators will attempt to
+ /// regenerate values. This puts a limit on the worst-case exponential
+ /// explosion that can happen with nested `Flatten`s.
+ ///
+ /// The default is 1_000_000, which can be overridden by setting the
+ /// `PROPTEST_MAX_FLAT_MAP_REGENS` environment variable. (The variable is
+ /// only considered when the `std` feature is enabled, which it is by
+ /// default.)
+ pub max_flat_map_regens: u32,
+
+ /// Indicates whether and how to persist failed test results.
+ ///
+ /// When compiling with "std" feature (i.e. the standard library is available), the default
+ /// is `Some(Box::new(FileFailurePersistence::SourceParallel("proptest-regressions")))`.
+ ///
+ /// Without the standard library, the default is `None`, and no persistence occurs.
+ ///
+ /// See the docs of [`FileFailurePersistence`](enum.FileFailurePersistence.html)
+ /// and [`MapFailurePersistence`](struct.MapFailurePersistence.html) for more information.
+ ///
+ /// You can disable failure persistence with the `PROPTEST_DISABLE_FAILURE_PERSISTENCE`
+ /// environment variable but its not currently possible to set the persistence file
+ /// with an environment variable. (The variable is
+ /// only considered when the `std` feature is enabled, which it is by
+ /// default.)
+ pub failure_persistence: Option<Box<dyn FailurePersistence>>,
+
+ /// File location of the current test, relevant for persistence
+ /// and debugging.
+ ///
+ /// Note the use of `&str` rather than `Path` to be compatible with
+ /// `#![no_std]` use cases where `Path` is unavailable.
+ ///
+ /// See the docs of [`FileFailurePersistence`](enum.FileFailurePersistence.html)
+ /// for more information on how it may be used for persistence.
+ pub source_file: Option<&'static str>,
+
+ /// The fully-qualified name of the test being run, as would be passed to
+ /// the test executable to run just that test.
+ ///
+ /// This must be set if `fork` is `true`. Otherwise, it is unused. It is
+ /// automatically set by `proptest!`.
+ ///
+ /// This must include the crate name at the beginning, as produced by
+ /// `module_path!()`.
+ pub test_name: Option<&'static str>,
+
+ /// If true, tests are run in a subprocess.
+ ///
+ /// Forking allows proptest to work with tests which may fail by aborting
+ /// the process, causing a segmentation fault, etc, but can be a lot slower
+ /// in certain environments or when running a very large number of tests.
+ ///
+ /// For forking to work correctly, both the `Strategy` and the content of
+ /// the test case itself must be deterministic.
+ ///
+ /// This requires the "fork" feature, enabled by default.
+ ///
+ /// The default is `false`, which can be overridden by setting the
+ /// `PROPTEST_FORK` environment variable. (The variable is
+ /// only considered when the `std` feature is enabled, which it is by
+ /// default.)
+ #[cfg(feature = "fork")]
+ pub fork: bool,
+
+ /// If non-zero, tests are run in a subprocess and each generated case
+ /// fails if it takes longer than this number of milliseconds.
+ ///
+ /// This implicitly enables forking, even if the `fork` field is `false`.
+ ///
+ /// The type here is plain `u32` (rather than
+ /// `Option<std::time::Duration>`) for the sake of ergonomics.
+ ///
+ /// This requires the "timeout" feature, enabled by default.
+ ///
+ /// Setting a timeout to less than the time it takes the process to start
+ /// up and initialise the first test case will cause the whole test to be
+ /// aborted.
+ ///
+ /// The default is `0` (i.e., no timeout), which can be overridden by
+ /// setting the `PROPTEST_TIMEOUT` environment variable. (The variable is
+ /// only considered when the `std` feature is enabled, which it is by
+ /// default.)
+ #[cfg(feature = "timeout")]
+ pub timeout: u32,
+
+ /// If non-zero, give up the shrinking process after this many milliseconds
+ /// have elapsed since the start of the shrinking process.
+ ///
+ /// This will not cause currently running test cases to be interrupted.
+ ///
+ /// This configuration is only available when the `std` feature is enabled
+ /// (which it is by default).
+ ///
+ /// The default is `0` (i.e., no limit), which can be overridden by setting
+ /// the `PROPTEST_MAX_SHRINK_TIME` environment variable. (The variable is
+ /// only considered when the `std` feature is enabled, which it is by
+ /// default.)
+ #[cfg(feature = "std")]
+ pub max_shrink_time: u32,
+
+ /// Give up on shrinking if more than this number of iterations of the test
+ /// code are run.
+ ///
+ /// Setting this to `std::u32::MAX` causes the actual limit to be four
+ /// times the number of test cases.
+ ///
+ /// Setting this value to `0` disables shrinking altogether.
+ ///
+ /// Note that the type of this field will change in a future version of
+ /// proptest to better accommodate its special values.
+ ///
+ /// The default is `std::u32::MAX`, which can be overridden by setting the
+ /// `PROPTEST_MAX_SHRINK_ITERS` environment variable. (The variable is only
+ /// considered when the `std` feature is enabled, which it is by default.)
+ pub max_shrink_iters: u32,
+
+ /// A function to create new result caches.
+ ///
+ /// The default is to do no caching. The easiest way to enable caching is
+ /// to set this field to `basic_result_cache` (though that is currently
+ /// only available with the `std` feature).
+ ///
+ /// This is useful for strategies which have a tendency to produce
+ /// duplicate values, or for tests where shrinking can take a very long
+ /// time due to exploring the same output multiple times.
+ ///
+ /// When caching is enabled, generated values themselves are not stored, so
+ /// this does not pose a risk of memory exhaustion for large test inputs
+ /// unless using extraordinarily large test case counts.
+ ///
+ /// Caching incurs its own overhead, and may very well make your test run
+ /// more slowly.
+ pub result_cache: fn() -> Box<dyn ResultCache>,
+
+ /// Set to non-zero values to cause proptest to emit human-targeted
+ /// messages to stderr as it runs.
+ ///
+ /// Greater values cause greater amounts of logs to be emitted. The exact
+ /// meaning of certain levels other than 0 is subject to change.
+ ///
+ /// - 0: No extra output.
+ /// - 1: Log test failure messages.
+ /// - 2: Trace low-level details.
+ ///
+ /// This is only available with the `std` feature (enabled by default)
+ /// since on nostd proptest has no way to produce output.
+ ///
+ /// The default is `0`, which can be overridden by setting the
+ /// `PROPTEST_VERBOSE` environment variable. (The variable is only considered
+ /// when the `std` feature is enabled, which it is by default.)
+ #[cfg(feature = "std")]
+ pub verbose: u32,
+
+ /// The RNG algorithm to use when not using a user-provided RNG.
+ ///
+ /// The default is `RngAlgorithm::default()`, which can be overridden by
+ /// setting the `PROPTEST_RNG_ALGORITHM` environment variable to one of the following:
+ ///
+ /// - `xs` — `RngAlgorithm::XorShift`
+ /// - `cc` — `RngAlgorithm::ChaCha`
+ ///
+ /// (The variable is only considered when the `std` feature is enabled,
+ /// which it is by default.)
+ pub rng_algorithm: RngAlgorithm,
+
+ // Needs to be public so FRU syntax can be used.
+ #[doc(hidden)]
+ pub _non_exhaustive: (),
+}
+
+impl Config {
+ /// Constructs a `Config` only differing from the `default()` in the
+ /// number of test cases required to pass the test successfully.
+ ///
+ /// This is simply a more concise alternative to using field-record update
+ /// syntax:
+ ///
+ /// ```
+ /// # use proptest::test_runner::Config;
+ /// assert_eq!(
+ /// Config::with_cases(42),
+ /// Config { cases: 42, .. Config::default() }
+ /// );
+ /// ```
+ pub fn with_cases(cases: u32) -> Self {
+ Self {
+ cases,
+ ..Config::default()
+ }
+ }
+
+ /// Constructs a `Config` only differing from the `default()` in the
+ /// source_file of the present test.
+ ///
+ /// This is simply a more concise alternative to using field-record update
+ /// syntax:
+ ///
+ /// ```
+ /// # use proptest::test_runner::Config;
+ /// assert_eq!(
+ /// Config::with_source_file("computer/question"),
+ /// Config { source_file: Some("computer/question"), .. Config::default() }
+ /// );
+ /// ```
+ pub fn with_source_file(source_file: &'static str) -> Self {
+ Self {
+ source_file: Some(source_file),
+ ..Config::default()
+ }
+ }
+
+ /// Constructs a `Config` only differing from the provided Config instance, `self`,
+ /// in the source_file of the present test.
+ ///
+ /// This is simply a more concise alternative to using field-record update
+ /// syntax:
+ ///
+ /// ```
+ /// # use proptest::test_runner::Config;
+ /// let a = Config::with_source_file("computer/question");
+ /// let b = a.clone_with_source_file("answer/42");
+ /// assert_eq!(
+ /// a,
+ /// Config { source_file: Some("computer/question"), .. Config::default() }
+ /// );
+ /// assert_eq!(
+ /// b,
+ /// Config { source_file: Some("answer/42"), .. Config::default() }
+ /// );
+ /// ```
+ pub fn clone_with_source_file(&self, source_file: &'static str) -> Self {
+ let mut result = self.clone();
+ result.source_file = Some(source_file);
+ result
+ }
+
+ /// Return whether this configuration implies forking.
+ ///
+ /// This method exists even if the "fork" feature is disabled, in which
+ /// case it simply returns false.
+ pub fn fork(&self) -> bool {
+ self._fork() || self.timeout() > 0
+ }
+
+ #[cfg(feature = "fork")]
+ fn _fork(&self) -> bool {
+ self.fork
+ }
+
+ #[cfg(not(feature = "fork"))]
+ fn _fork(&self) -> bool {
+ false
+ }
+
+ /// Returns the configured timeout.
+ ///
+ /// This method exists even if the "timeout" feature is disabled, in which
+ /// case it simply returns 0.
+ #[cfg(feature = "timeout")]
+ pub fn timeout(&self) -> u32 {
+ self.timeout
+ }
+
+ /// Returns the configured timeout.
+ ///
+ /// This method exists even if the "timeout" feature is disabled, in which
+ /// case it simply returns 0.
+ #[cfg(not(feature = "timeout"))]
+ pub fn timeout(&self) -> u32 {
+ 0
+ }
+
+ /// Returns the configured limit on shrinking iterations.
+ ///
+ /// This takes into account the special "automatic" behaviour.
+ pub fn max_shrink_iters(&self) -> u32 {
+ if u32::MAX == self.max_shrink_iters {
+ self.cases.saturating_mul(4)
+ } else {
+ self.max_shrink_iters
+ }
+ }
+
+ // Used by macros to force the config to be owned without depending on
+ // certain traits being `use`d.
+ #[allow(missing_docs)]
+ #[doc(hidden)]
+ pub fn __sugar_to_owned(&self) -> Self {
+ self.clone()
+ }
+}
+
+#[cfg(feature = "std")]
+impl Default for Config {
+ fn default() -> Self {
+ DEFAULT_CONFIG.clone()
+ }
+}
+
+#[cfg(not(feature = "std"))]
+impl Default for Config {
+ fn default() -> Self {
+ default_default_config()
+ }
+}
diff --git a/vendor/proptest/src/test_runner/errors.rs b/vendor/proptest/src/test_runner/errors.rs
new file mode 100644
index 000000000..dfa4a4852
--- /dev/null
+++ b/vendor/proptest/src/test_runner/errors.rs
@@ -0,0 +1,135 @@
+//-
+// Copyright 2017, 2018 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::fmt;
+
+#[cfg(feature = "std")]
+use std::string::ToString;
+
+use crate::test_runner::Reason;
+
+/// Errors which can be returned from test cases to indicate non-successful
+/// completion.
+///
+/// Note that in spite of the name, `TestCaseError` is currently *not* an
+/// instance of `Error`, since otherwise `impl<E : Error> From<E>` could not be
+/// provided.
+///
+/// Any `Error` can be converted to a `TestCaseError`, which places
+/// `Error::display()` into the `Fail` case.
+#[derive(Debug, Clone)]
+pub enum TestCaseError {
+ /// The input was not valid for the test case. This does not count as a
+ /// test failure (nor a success); rather, it simply signals to generate
+ /// a new input and try again.
+ Reject(Reason),
+ /// The code under test failed the test.
+ Fail(Reason),
+}
+
+/// Indicates the type of test that ran successfully.
+///
+/// This is used for managing whether or not a success is counted against
+/// configured `PROPTEST_CASES`; only `NewCases` shall be counted.
+///
+/// TODO-v2: Ideally `TestCaseResult = Result<TestCaseOk, TestCaseError>`
+/// however this breaks source compability in version 1.x.x because
+/// `TestCaseResult` is public.
+#[derive(Debug, Clone)]
+pub(crate) enum TestCaseOk {
+ NewCaseSuccess,
+ PersistedCaseSuccess,
+ ReplayFromForkSuccess,
+ CacheHitSuccess,
+ Reject,
+}
+
+/// Convenience for the type returned by test cases.
+pub type TestCaseResult = Result<(), TestCaseError>;
+
+/// Intended to replace `TestCaseResult` in v2.
+///
+/// TODO-v2: Ideally `TestCaseResult = Result<TestCaseOk, TestCaseError>`
+/// however this breaks source compability in version 1.x.x because
+/// `TestCaseResult` is public.
+pub(crate) type TestCaseResultV2 = Result<TestCaseOk, TestCaseError>;
+
+impl TestCaseError {
+ /// Rejects the generated test input as invalid for this test case. This
+ /// does not count as a test failure (nor a success); rather, it simply
+ /// signals to generate a new input and try again.
+ ///
+ /// The string gives the location and context of the rejection, and
+ /// should be suitable for formatting like `Foo did X at {whence}`.
+ pub fn reject(reason: impl Into<Reason>) -> Self {
+ TestCaseError::Reject(reason.into())
+ }
+
+ /// The code under test failed the test.
+ ///
+ /// The string should indicate the location of the failure, but may
+ /// generally be any string.
+ pub fn fail(reason: impl Into<Reason>) -> Self {
+ TestCaseError::Fail(reason.into())
+ }
+}
+
+impl fmt::Display for TestCaseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ TestCaseError::Reject(ref whence) => {
+ write!(f, "Input rejected at {}", whence)
+ }
+ TestCaseError::Fail(ref why) => write!(f, "Case failed: {}", why),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<E: ::std::error::Error> From<E> for TestCaseError {
+ fn from(cause: E) -> Self {
+ TestCaseError::fail(cause.to_string())
+ }
+}
+
+/// A failure state from running test cases for a single test.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum TestError<T> {
+ /// The test was aborted for the given reason, for example, due to too many
+ /// inputs having been rejected.
+ Abort(Reason),
+ /// A failing test case was found. The string indicates where and/or why
+ /// the test failed. The `T` is the minimal input found to reproduce the
+ /// failure.
+ Fail(Reason, T),
+}
+
+impl<T: fmt::Debug> fmt::Display for TestError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ TestError::Abort(ref why) => write!(f, "Test aborted: {}", why),
+ TestError::Fail(ref why, ref what) => write!(
+ f,
+ "Test failed: {}; minimal failing input: {:?}",
+ why, what
+ ),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+#[allow(deprecated)] // description()
+impl<T: fmt::Debug> ::std::error::Error for TestError<T> {
+ fn description(&self) -> &str {
+ match *self {
+ TestError::Abort(..) => "Abort",
+ TestError::Fail(..) => "Fail",
+ }
+ }
+}
diff --git a/vendor/proptest/src/test_runner/failure_persistence/file.rs b/vendor/proptest/src/test_runner/failure_persistence/file.rs
new file mode 100644
index 000000000..61d7dcf6a
--- /dev/null
+++ b/vendor/proptest/src/test_runner/failure_persistence/file.rs
@@ -0,0 +1,536 @@
+//-
+// Copyright 2017, 2018, 2019 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use core::any::Any;
+use core::fmt::Debug;
+use std::borrow::{Cow, ToOwned};
+use std::boxed::Box;
+use std::env;
+use std::fs;
+use std::io::{self, BufRead, Write};
+use std::path::{Path, PathBuf};
+use std::string::{String, ToString};
+use std::sync::RwLock;
+use std::vec::Vec;
+
+use self::FileFailurePersistence::*;
+use crate::test_runner::failure_persistence::{
+ FailurePersistence, PersistedSeed,
+};
+
+/// Describes how failing test cases are persisted.
+///
+/// Note that file names in this enum are `&str` rather than `&Path` since
+/// constant functions are not yet in Rust stable as of 2017-12-16.
+///
+/// In all cases, if a derived path references a directory which does not yet
+/// exist, proptest will attempt to create all necessary parent directories.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum FileFailurePersistence {
+ /// Completely disables persistence of failing test cases.
+ ///
+ /// This is semantically equivalent to `Direct("/dev/null")` on Unix and
+ /// `Direct("NUL")` on Windows (though it is internally handled by simply
+ /// not doing any I/O).
+ Off,
+ /// The path given to `TestRunner::set_source_file()` is parsed. The path
+ /// is traversed up the directory tree until a directory containing a file
+ /// named `lib.rs` or `main.rs` is found. A sibling to that directory with
+ /// the name given by the string in this configuration is created, and a
+ /// file with the same name and path relative to the source directory, but
+ /// with the extension changed to `.txt`, is used.
+ ///
+ /// For example, given a source path of
+ /// `/home/jsmith/code/project/src/foo/bar.rs` and a configuration of
+ /// `SourceParallel("proptest-regressions")` (the default), assuming the
+ /// `src` directory has a `lib.rs` or `main.rs`, the resulting file would
+ /// be `/home/jsmith/code/project/proptest-regressions/foo/bar.txt`.
+ ///
+ /// If no `lib.rs` or `main.rs` can be found, a warning is printed and this
+ /// behaves like `WithSource`.
+ ///
+ /// If no source file has been configured, a warning is printed and this
+ /// behaves like `Off`.
+ SourceParallel(&'static str),
+ /// The path given to `TestRunner::set_source_file()` is parsed. The
+ /// extension of the path is changed to the string given in this
+ /// configuration, and that filename is used.
+ ///
+ /// For example, given a source path of
+ /// `/home/jsmith/code/project/src/foo/bar.rs` and a configuration of
+ /// `WithSource("regressions")`, the resulting path would be
+ /// `/home/jsmith/code/project/src/foo/bar.regressions`.
+ WithSource(&'static str),
+ /// The string given in this option is directly used as a file path without
+ /// any further processing.
+ Direct(&'static str),
+ #[doc(hidden)]
+ #[allow(missing_docs)]
+ _NonExhaustive,
+}
+
+impl Default for FileFailurePersistence {
+ fn default() -> Self {
+ SourceParallel("proptest-regressions")
+ }
+}
+
+impl FailurePersistence for FileFailurePersistence {
+ fn load_persisted_failures2(
+ &self,
+ source_file: Option<&'static str>,
+ ) -> Vec<PersistedSeed> {
+ let p = self.resolve(
+ source_file
+ .and_then(|s| absolutize_source_file(Path::new(s)))
+ .as_ref()
+ .map(|cow| &**cow),
+ );
+
+ let path: Option<&PathBuf> = p.as_ref();
+ let result: io::Result<Vec<PersistedSeed>> = path.map_or_else(
+ || Ok(vec![]),
+ |path| {
+ // .ok() instead of .unwrap() so we don't propagate panics here
+ let _lock = PERSISTENCE_LOCK.read().ok();
+ io::BufReader::new(fs::File::open(path)?)
+ .lines()
+ .enumerate()
+ .filter_map(|(lineno, line)| match line {
+ Err(err) => Some(Err(err)),
+ Ok(line) => parse_seed_line(line, path, lineno).map(Ok),
+ })
+ .collect()
+ },
+ );
+
+ unwrap_or!(result, err => {
+ if io::ErrorKind::NotFound != err.kind() {
+ eprintln!(
+ "proptest: failed to open {}: {}",
+ &path.map(|x| &**x)
+ .unwrap_or_else(|| Path::new("??"))
+ .display(),
+ err
+ );
+ }
+ vec![]
+ })
+ }
+
+ fn save_persisted_failure2(
+ &mut self,
+ source_file: Option<&'static str>,
+ seed: PersistedSeed,
+ shrunken_value: &dyn Debug,
+ ) {
+ let path = self.resolve(source_file.map(Path::new));
+ if let Some(path) = path {
+ // .ok() instead of .unwrap() so we don't propagate panics here
+ let _lock = PERSISTENCE_LOCK.write().ok();
+ let is_new = !path.is_file();
+
+ let mut to_write = Vec::<u8>::new();
+ if is_new {
+ write_header(&mut to_write)
+ .expect("proptest: couldn't write header.");
+ }
+
+ write_seed_line(&mut to_write, &seed, shrunken_value)
+ .expect("proptest: couldn't write seed line.");
+
+ if let Err(e) = write_seed_data_to_file(&path, &to_write) {
+ eprintln!(
+ "proptest: failed to append to {}: {}",
+ path.display(),
+ e
+ );
+ } else if is_new {
+ eprintln!(
+ "proptest: Saving this and future failures in {}\n\
+ proptest: If this test was run on a CI system, you may \
+ wish to add the following line to your copy of the file.{}\n\
+ {}",
+ path.display(),
+ if is_new { " (You may need to create it.)" } else { "" },
+ seed);
+ }
+ }
+ }
+
+ fn box_clone(&self) -> Box<dyn FailurePersistence> {
+ Box::new(*self)
+ }
+
+ fn eq(&self, other: &dyn FailurePersistence) -> bool {
+ other
+ .as_any()
+ .downcast_ref::<Self>()
+ .map_or(false, |x| x == self)
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+/// Ensure that the source file to use for resolving the location of the persisted
+/// failing cases file is absolute.
+///
+/// The source location can only be used if it is absolute. If `source` is
+/// not an absolute path, an attempt will be made to determine the absolute
+/// path based on the current working directory and its parents. If no
+/// absolute path can be determined, a warning will be printed and proptest
+/// will continue as if this function had never been called.
+///
+/// See [`FileFailurePersistence`](enum.FileFailurePersistence.html) for details on
+/// how this value is used once it is made absolute.
+///
+/// This is normally called automatically by the `proptest!` macro, which
+/// passes `file!()`.
+///
+fn absolutize_source_file<'a>(source: &'a Path) -> Option<Cow<'a, Path>> {
+ absolutize_source_file_with_cwd(env::current_dir, source)
+}
+
+fn absolutize_source_file_with_cwd<'a>(
+ getcwd: impl FnOnce() -> io::Result<PathBuf>,
+ source: &'a Path,
+) -> Option<Cow<'a, Path>> {
+ if source.is_absolute() {
+ // On Unix, `file!()` is absolute. In these cases, we can use
+ // that path directly.
+ Some(Cow::Borrowed(source))
+ } else {
+ // On Windows, `file!()` is relative to the crate root, but the
+ // test is not generally run with the crate root as the working
+ // directory, so the path is not directly usable. However, the
+ // working directory is almost always a subdirectory of the crate
+ // root, so pop directories off until pushing the source onto the
+ // directory results in a path that refers to an existing file.
+ // Once we find such a path, we can use that.
+ //
+ // If we can't figure out an absolute path, print a warning and act
+ // as if no source had been given.
+ match getcwd() {
+ Ok(mut cwd) => loop {
+ let joined = cwd.join(source);
+ if joined.is_file() {
+ break Some(Cow::Owned(joined));
+ }
+
+ if !cwd.pop() {
+ eprintln!(
+ "proptest: Failed to find absolute path of \
+ source file '{:?}'. Ensure the test is \
+ being run from somewhere within the crate \
+ directory hierarchy.",
+ source
+ );
+ break None;
+ }
+ },
+
+ Err(e) => {
+ eprintln!(
+ "proptest: Failed to determine current \
+ directory, so the relative source path \
+ '{:?}' cannot be resolved: {}",
+ source, e
+ );
+ None
+ }
+ }
+ }
+}
+
+fn parse_seed_line(
+ mut line: String,
+ path: &Path,
+ lineno: usize,
+) -> Option<PersistedSeed> {
+ // Remove anything after and including '#':
+ if let Some(comment_start) = line.find('#') {
+ line.truncate(comment_start);
+ }
+
+ if line.len() > 0 {
+ let ret = line.parse::<PersistedSeed>().ok();
+ if !ret.is_some() {
+ eprintln!(
+ "proptest: {}:{}: unparsable line, ignoring",
+ path.display(),
+ lineno + 1
+ );
+ }
+ return ret;
+ }
+
+ None
+}
+
+fn write_seed_line(
+ buf: &mut Vec<u8>,
+ seed: &PersistedSeed,
+ shrunken_value: &dyn Debug,
+) -> io::Result<()> {
+ // Write the seed itself
+ write!(buf, "{}", seed.to_string())?;
+
+ // Write out comment:
+ let debug_start = buf.len();
+ write!(buf, " # shrinks to {:?}", shrunken_value)?;
+
+ // Ensure there are no newlines in the debug output
+ for byte in &mut buf[debug_start..] {
+ if b'\n' == *byte || b'\r' == *byte {
+ *byte = b' ';
+ }
+ }
+
+ buf.push(b'\n');
+
+ Ok(())
+}
+
+fn write_header(buf: &mut Vec<u8>) -> io::Result<()> {
+ writeln!(
+ buf,
+ "\
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases."
+ )
+}
+
+fn write_seed_data_to_file(dst: &Path, data: &[u8]) -> io::Result<()> {
+ if let Some(parent) = dst.parent() {
+ fs::create_dir_all(parent)?;
+ }
+
+ let mut options = fs::OpenOptions::new();
+ options.append(true).create(true);
+ let mut out = options.open(dst)?;
+ out.write_all(data)?;
+
+ Ok(())
+}
+
+impl FileFailurePersistence {
+ /// Given the nominal source path, determine the location of the failure
+ /// persistence file, if any.
+ pub(super) fn resolve(&self, source: Option<&Path>) -> Option<PathBuf> {
+ let source = source.and_then(absolutize_source_file);
+
+ match *self {
+ Off => None,
+
+ SourceParallel(sibling) => match source {
+ Some(source_path) => {
+ let mut dir = Cow::into_owned(source_path.clone());
+ let mut found = false;
+ while dir.pop() {
+ if dir.join("lib.rs").is_file()
+ || dir.join("main.rs").is_file()
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if !found {
+ eprintln!(
+ "proptest: FileFailurePersistence::SourceParallel set, \
+ but failed to find lib.rs or main.rs"
+ );
+ WithSource(sibling).resolve(Some(&*source_path))
+ } else {
+ let suffix = source_path
+ .strip_prefix(&dir)
+ .expect("parent of source is not a prefix of it?")
+ .to_owned();
+ let mut result = dir;
+ // If we've somehow reached the root, or someone gave
+ // us a relative path that we've exhausted, just accept
+ // creating a subdirectory instead.
+ let _ = result.pop();
+ result.push(sibling);
+ result.push(&suffix);
+ result.set_extension("txt");
+ Some(result)
+ }
+ }
+ None => {
+ eprintln!(
+ "proptest: FileFailurePersistence::SourceParallel set, \
+ but no source file known"
+ );
+ None
+ }
+ },
+
+ WithSource(extension) => match source {
+ Some(source_path) => {
+ let mut result = Cow::into_owned(source_path);
+ result.set_extension(extension);
+ Some(result)
+ }
+
+ None => {
+ eprintln!(
+ "proptest: FileFailurePersistence::WithSource set, \
+ but no source file known"
+ );
+ None
+ }
+ },
+
+ Direct(path) => Some(Path::new(path).to_owned()),
+
+ _NonExhaustive => {
+ panic!("FailurePersistence set to _NonExhaustive")
+ }
+ }
+ }
+}
+
+lazy_static! {
+ /// Used to guard access to the persistence file(s) so that a single
+ /// process will not step on its own toes.
+ ///
+ /// We don't have much protecting us should two separate process try to
+ /// write to the same file at once (depending on how atomic append mode is
+ /// on the OS), but this should be extremely rare.
+ static ref PERSISTENCE_LOCK: RwLock<()> = RwLock::new(());
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ struct TestPaths {
+ crate_root: &'static Path,
+ src_file: PathBuf,
+ subdir_file: PathBuf,
+ misplaced_file: PathBuf,
+ }
+
+ lazy_static! {
+ static ref TEST_PATHS: TestPaths = {
+ let crate_root = Path::new(env!("CARGO_MANIFEST_DIR"));
+ let lib_root = crate_root.join("src");
+ let src_subdir = lib_root.join("strategy");
+ let src_file = lib_root.join("foo.rs");
+ let subdir_file = src_subdir.join("foo.rs");
+ let misplaced_file = crate_root.join("foo.rs");
+ TestPaths {
+ crate_root,
+ src_file,
+ subdir_file,
+ misplaced_file,
+ }
+ };
+ }
+
+ #[test]
+ fn persistence_file_location_resolved_correctly() {
+ // If off, there is never a file
+ assert_eq!(None, Off.resolve(None));
+ assert_eq!(None, Off.resolve(Some(&TEST_PATHS.subdir_file)));
+
+ // For direct, we don't care about the source file, and instead always
+ // use whatever is in the config.
+ assert_eq!(
+ Some(Path::new("bar.txt").to_owned()),
+ Direct("bar.txt").resolve(None)
+ );
+ assert_eq!(
+ Some(Path::new("bar.txt").to_owned()),
+ Direct("bar.txt").resolve(Some(&TEST_PATHS.subdir_file))
+ );
+
+ // For WithSource, only the extension changes, but we get nothing if no
+ // source file was configured.
+ // Accounting for the way absolute paths work on Windows would be more
+ // complex, so for now don't test that case.
+ #[cfg(unix)]
+ fn absolute_path_case() {
+ assert_eq!(
+ Some(Path::new("/foo/bar.ext").to_owned()),
+ WithSource("ext").resolve(Some(Path::new("/foo/bar.rs")))
+ );
+ }
+ #[cfg(not(unix))]
+ fn absolute_path_case() {}
+ absolute_path_case();
+ assert_eq!(None, WithSource("ext").resolve(None));
+
+ // For SourceParallel, we make a sibling directory tree and change the
+ // extensions to .txt ...
+ assert_eq!(
+ Some(TEST_PATHS.crate_root.join("sib").join("foo.txt")),
+ SourceParallel("sib").resolve(Some(&TEST_PATHS.src_file))
+ );
+ assert_eq!(
+ Some(
+ TEST_PATHS
+ .crate_root
+ .join("sib")
+ .join("strategy")
+ .join("foo.txt")
+ ),
+ SourceParallel("sib").resolve(Some(&TEST_PATHS.subdir_file))
+ );
+ // ... but if we can't find lib.rs / main.rs, give up and set the
+ // extension instead ...
+ assert_eq!(
+ Some(TEST_PATHS.crate_root.join("foo.sib")),
+ SourceParallel("sib").resolve(Some(&TEST_PATHS.misplaced_file))
+ );
+ // ... and if no source is configured, we do nothing
+ assert_eq!(None, SourceParallel("ext").resolve(None));
+ }
+
+ #[test]
+ fn relative_source_files_absolutified() {
+ const TEST_RUNNER_PATH: &[&str] = &["src", "test_runner", "mod.rs"];
+ lazy_static! {
+ static ref TEST_RUNNER_RELATIVE: PathBuf =
+ TEST_RUNNER_PATH.iter().collect();
+ }
+ const CARGO_DIR: &str = env!("CARGO_MANIFEST_DIR");
+
+ let expected = ::std::iter::once(CARGO_DIR)
+ .chain(TEST_RUNNER_PATH.iter().map(|s| *s))
+ .collect::<PathBuf>();
+
+ // Running from crate root
+ assert_eq!(
+ &*expected,
+ absolutize_source_file_with_cwd(
+ || Ok(Path::new(CARGO_DIR).to_owned()),
+ &TEST_RUNNER_RELATIVE
+ )
+ .unwrap()
+ );
+
+ // Running from test subdirectory
+ assert_eq!(
+ &*expected,
+ absolutize_source_file_with_cwd(
+ || Ok(Path::new(CARGO_DIR).join("target")),
+ &TEST_RUNNER_RELATIVE
+ )
+ .unwrap()
+ );
+ }
+}
diff --git a/vendor/proptest/src/test_runner/failure_persistence/map.rs b/vendor/proptest/src/test_runner/failure_persistence/map.rs
new file mode 100644
index 000000000..322e554c6
--- /dev/null
+++ b/vendor/proptest/src/test_runner/failure_persistence/map.rs
@@ -0,0 +1,99 @@
+//-
+// Copyright 2017, 2018 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::{fmt, BTreeMap, BTreeSet, Box, Vec};
+use core::any::Any;
+
+use crate::test_runner::failure_persistence::FailurePersistence;
+use crate::test_runner::failure_persistence::PersistedSeed;
+
+/// Failure persistence option that loads and saves seeds in memory
+/// on the heap. This may be useful when accumulating test failures
+/// across multiple `TestRunner` instances for external reporting
+/// or batched persistence.
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct MapFailurePersistence {
+ /// Backing map, keyed by source_file.
+ pub map: BTreeMap<&'static str, BTreeSet<PersistedSeed>>,
+}
+
+impl FailurePersistence for MapFailurePersistence {
+ fn load_persisted_failures2(
+ &self,
+ source_file: Option<&'static str>,
+ ) -> Vec<PersistedSeed> {
+ source_file
+ .and_then(|source| self.map.get(source))
+ .map(|seeds| seeds.iter().cloned().collect::<Vec<_>>())
+ .unwrap_or_default()
+ }
+
+ fn save_persisted_failure2(
+ &mut self,
+ source_file: Option<&'static str>,
+ seed: PersistedSeed,
+ _shrunken_value: &dyn fmt::Debug,
+ ) {
+ let s = match source_file {
+ Some(sf) => sf,
+ None => return,
+ };
+ let set = self.map.entry(s).or_insert_with(BTreeSet::new);
+ set.insert(seed);
+ }
+
+ fn box_clone(&self) -> Box<dyn FailurePersistence> {
+ Box::new(self.clone())
+ }
+
+ fn eq(&self, other: &dyn FailurePersistence) -> bool {
+ other
+ .as_any()
+ .downcast_ref::<Self>()
+ .map_or(false, |x| x == self)
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_runner::failure_persistence::tests::*;
+
+ #[test]
+ fn initial_map_is_empty() {
+ assert!(MapFailurePersistence::default()
+ .load_persisted_failures2(HI_PATH)
+ .is_empty())
+ }
+
+ #[test]
+ fn seeds_recoverable() {
+ let mut p = MapFailurePersistence::default();
+ p.save_persisted_failure2(HI_PATH, INC_SEED, &"");
+ let restored = p.load_persisted_failures2(HI_PATH);
+ assert_eq!(1, restored.len());
+ assert_eq!(INC_SEED, *restored.first().unwrap());
+
+ assert!(p.load_persisted_failures2(None).is_empty());
+ assert!(p.load_persisted_failures2(UNREL_PATH).is_empty());
+ }
+
+ #[test]
+ fn seeds_deduplicated() {
+ let mut p = MapFailurePersistence::default();
+ p.save_persisted_failure2(HI_PATH, INC_SEED, &"");
+ p.save_persisted_failure2(HI_PATH, INC_SEED, &"");
+ let restored = p.load_persisted_failures2(HI_PATH);
+ assert_eq!(1, restored.len());
+ }
+}
diff --git a/vendor/proptest/src/test_runner/failure_persistence/mod.rs b/vendor/proptest/src/test_runner/failure_persistence/mod.rs
new file mode 100644
index 000000000..6d21c4002
--- /dev/null
+++ b/vendor/proptest/src/test_runner/failure_persistence/mod.rs
@@ -0,0 +1,156 @@
+//-
+// Copyright 2017, 2018, 2019 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::{fmt, Box, Vec};
+use core::any::Any;
+use core::fmt::Display;
+use core::result::Result;
+use core::str::FromStr;
+
+#[cfg(feature = "std")]
+mod file;
+mod map;
+mod noop;
+
+#[cfg(feature = "std")]
+pub use self::file::*;
+pub use self::map::*;
+pub use self::noop::*;
+
+use crate::test_runner::Seed;
+
+/// Opaque struct representing a seed which can be persisted.
+///
+/// The `Display` and `FromStr` implementations go to and from the format
+/// Proptest uses for its persistence file.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct PersistedSeed(pub(crate) Seed);
+
+impl Display for PersistedSeed {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.0.to_persistence())
+ }
+}
+
+impl FromStr for PersistedSeed {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, ()> {
+ Seed::from_persistence(s).map(PersistedSeed).ok_or(())
+ }
+}
+
+/// Provides external persistence for historical test failures by storing seeds.
+///
+/// **Note**: Implementing `load_persisted_failures` and
+/// `save_persisted_failures` is **deprecated** and these methods will be
+/// removed in proptest 0.10.0. Instead, implement `load_persisted_failures2`
+/// and `save_persisted_failures2`.
+pub trait FailurePersistence: Send + Sync + fmt::Debug {
+ /// Supply seeds associated with the given `source_file` that may be used
+ /// by a `TestRunner`'s random number generator in order to consistently
+ /// recreate a previously-failing `Strategy`-provided value.
+ ///
+ /// The default implementation is **for backwards compatibility**. It
+ /// delegates to `load_persisted_failures` and converts the results into
+ /// XorShift seeds.
+ #[allow(deprecated)]
+ fn load_persisted_failures2(
+ &self,
+ source_file: Option<&'static str>,
+ ) -> Vec<PersistedSeed> {
+ self.load_persisted_failures(source_file)
+ .into_iter()
+ .map(|seed| PersistedSeed(Seed::XorShift(seed)))
+ .collect()
+ }
+
+ /// Use `load_persisted_failures2` instead.
+ ///
+ /// This function inadvertently exposes the implementation of seeds prior
+ /// to Proptest 0.9.1 and only works with XorShift seeds.
+ #[deprecated]
+ #[allow(unused_variables)]
+ fn load_persisted_failures(
+ &self,
+ source_file: Option<&'static str>,
+ ) -> Vec<[u8; 16]> {
+ panic!("load_persisted_failures2 not implemented");
+ }
+
+ /// Store a new failure-generating seed associated with the given `source_file`.
+ ///
+ /// The default implementation is **for backwards compatibility**. It
+ /// delegates to `save_persisted_failure` if `seed` is a XorShift seed.
+ #[allow(deprecated)]
+ fn save_persisted_failure2(
+ &mut self,
+ source_file: Option<&'static str>,
+ seed: PersistedSeed,
+ shrunken_value: &dyn fmt::Debug,
+ ) {
+ match seed.0 {
+ Seed::XorShift(seed) => {
+ self.save_persisted_failure(source_file, seed, shrunken_value)
+ }
+ _ => (),
+ }
+ }
+
+ /// Use `save_persisted_failures2` instead.
+ ///
+ /// This function inadvertently exposes the implementation of seeds prior
+ /// to Proptest 0.9.1 and only works with XorShift seeds.
+ #[deprecated]
+ #[allow(unused_variables)]
+ fn save_persisted_failure(
+ &mut self,
+ source_file: Option<&'static str>,
+ seed: [u8; 16],
+ shrunken_value: &dyn fmt::Debug,
+ ) {
+ panic!("save_persisted_failure2 not implemented");
+ }
+
+ /// Delegate method for producing a trait object usable with `Clone`
+ fn box_clone(&self) -> Box<dyn FailurePersistence>;
+
+ /// Equality testing delegate required due to constraints of trait objects.
+ fn eq(&self, other: &dyn FailurePersistence) -> bool;
+
+ /// Assistant method for trait object comparison.
+ fn as_any(&self) -> &dyn Any;
+}
+
+impl<'a, 'b> PartialEq<dyn FailurePersistence + 'b>
+ for dyn FailurePersistence + 'a
+{
+ fn eq(&self, other: &(dyn FailurePersistence + 'b)) -> bool {
+ FailurePersistence::eq(self, other)
+ }
+}
+
+impl Clone for Box<dyn FailurePersistence> {
+ fn clone(&self) -> Box<dyn FailurePersistence> {
+ self.box_clone()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::PersistedSeed;
+ use crate::test_runner::rng::Seed;
+
+ pub const INC_SEED: PersistedSeed = PersistedSeed(Seed::XorShift([
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ ]));
+
+ pub const HI_PATH: Option<&str> = Some("hi");
+ pub const UNREL_PATH: Option<&str> = Some("unrelated");
+}
diff --git a/vendor/proptest/src/test_runner/failure_persistence/noop.rs b/vendor/proptest/src/test_runner/failure_persistence/noop.rs
new file mode 100644
index 000000000..59538392a
--- /dev/null
+++ b/vendor/proptest/src/test_runner/failure_persistence/noop.rs
@@ -0,0 +1,76 @@
+//-
+// Copyright 2017, 2018 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::{fmt, Box, Vec};
+use core::any::Any;
+
+use crate::test_runner::failure_persistence::{
+ FailurePersistence, PersistedSeed,
+};
+
+/// Failure persistence option that loads and saves nothing at all.
+#[derive(Debug, Default, PartialEq)]
+struct NoopFailurePersistence;
+
+impl FailurePersistence for NoopFailurePersistence {
+ fn load_persisted_failures2(
+ &self,
+ _source_file: Option<&'static str>,
+ ) -> Vec<PersistedSeed> {
+ Vec::new()
+ }
+
+ fn save_persisted_failure2(
+ &mut self,
+ _source_file: Option<&'static str>,
+ _seed: PersistedSeed,
+ _shrunken_value: &dyn fmt::Debug,
+ ) {
+ }
+
+ fn box_clone(&self) -> Box<dyn FailurePersistence> {
+ Box::new(NoopFailurePersistence)
+ }
+
+ fn eq(&self, other: &dyn FailurePersistence) -> bool {
+ other
+ .as_any()
+ .downcast_ref::<Self>()
+ .map_or(false, |x| x == self)
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_runner::failure_persistence::tests::*;
+
+ #[test]
+ fn default_load_is_empty() {
+ assert!(NoopFailurePersistence::default()
+ .load_persisted_failures2(None)
+ .is_empty());
+ assert!(NoopFailurePersistence::default()
+ .load_persisted_failures2(HI_PATH)
+ .is_empty());
+ }
+
+ #[test]
+ fn seeds_not_recoverable() {
+ let mut p = NoopFailurePersistence::default();
+ p.save_persisted_failure2(HI_PATH, INC_SEED, &"");
+ assert!(p.load_persisted_failures2(HI_PATH).is_empty());
+ assert!(p.load_persisted_failures2(None).is_empty());
+ assert!(p.load_persisted_failures2(UNREL_PATH).is_empty());
+ }
+}
diff --git a/vendor/proptest/src/test_runner/mod.rs b/vendor/proptest/src/test_runner/mod.rs
new file mode 100644
index 000000000..d7516ab96
--- /dev/null
+++ b/vendor/proptest/src/test_runner/mod.rs
@@ -0,0 +1,31 @@
+//-
+// Copyright 2017, 2018 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! State and functions for running proptest tests.
+//!
+//! You do not normally need to access things in this module directly except
+//! when implementing new low-level strategies.
+
+mod config;
+mod errors;
+mod failure_persistence;
+mod reason;
+#[cfg(feature = "fork")]
+mod replay;
+mod result_cache;
+mod rng;
+mod runner;
+
+pub use self::config::*;
+pub use self::errors::*;
+pub use self::failure_persistence::*;
+pub use self::reason::*;
+pub use self::result_cache::*;
+pub use self::rng::*;
+pub use self::runner::*;
diff --git a/vendor/proptest/src/test_runner/reason.rs b/vendor/proptest/src/test_runner/reason.rs
new file mode 100644
index 000000000..38cc7e322
--- /dev/null
+++ b/vendor/proptest/src/test_runner/reason.rs
@@ -0,0 +1,54 @@
+//-
+// Copyright 2017, 2018 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::{fmt, Box, Cow, String};
+
+/// The reason for why something, such as a generated value, was rejected.
+///
+/// Currently this is merely a wrapper around a message, but more properties
+/// may be added in the future.
+///
+/// This is constructed via `.into()` on a `String`, `&'static str`, or
+/// `Box<str>`.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Reason(Cow<'static, str>);
+
+impl Reason {
+ /// Return the message for this `Reason`.
+ ///
+ /// The message is intended for human consumption, and is not guaranteed to
+ /// have any format in particular.
+ pub fn message(&self) -> &str {
+ &*self.0
+ }
+}
+
+impl From<&'static str> for Reason {
+ fn from(s: &'static str) -> Self {
+ Reason(s.into())
+ }
+}
+
+impl From<String> for Reason {
+ fn from(s: String) -> Self {
+ Reason(s.into())
+ }
+}
+
+impl From<Box<str>> for Reason {
+ fn from(s: Box<str>) -> Self {
+ Reason(String::from(s).into())
+ }
+}
+
+impl fmt::Display for Reason {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(self.message(), f)
+ }
+}
diff --git a/vendor/proptest/src/test_runner/replay.rs b/vendor/proptest/src/test_runner/replay.rs
new file mode 100644
index 000000000..4365d5538
--- /dev/null
+++ b/vendor/proptest/src/test_runner/replay.rs
@@ -0,0 +1,189 @@
+//-
+// Copyright 2018 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(dead_code)]
+
+use std::fs;
+use std::io::{self, BufRead, Read, Seek, Write};
+use std::path::Path;
+use std::string::String;
+use std::vec::Vec;
+
+use crate::test_runner::{Seed, TestCaseError, TestCaseResult};
+
+const SENTINEL: &'static str = "proptest-forkfile";
+
+/// A "replay" of a `TestRunner` invocation.
+///
+/// The replay mechanism is used to support forking. When a child process
+/// exits, the parent can read the replay to reproduce the state the child had;
+/// similarly, if a child crashes, a new one can be started and given a replay
+/// which steps it one complication past the input that caused the crash.
+///
+/// The replay system is tightly coupled to the `TestRunner` itself. It does
+/// not carry enough information to be used in different builds of the same
+/// application, or even two different runs of the test process since changes
+/// to the persistence file will perturb the replay.
+///
+/// `Replay` has a special string format for being stored in files. It starts
+/// with a line just containing the text in `SENTINEL`, then 16 lines
+/// containing the values of `seed`, then an unterminated line consisting of
+/// `+`, `-`, and `!` characters to indicate test case passes/failures/rejects,
+/// `.` to indicate termination of the test run, or ` ` as a dummy "I'm alive"
+/// signal. This format makes it easy for the child process to blindly append
+/// to the file without having to worry about the possibility of appends being
+/// non-atomic.
+#[derive(Clone, Debug)]
+pub(crate) struct Replay {
+ /// The seed of the RNG used to start running the test cases.
+ pub(crate) seed: Seed,
+ /// A log of whether certain test cases passed or failed. The runner will
+ /// assume the same results occur without actually running the test cases.
+ pub(crate) steps: Vec<TestCaseResult>,
+}
+
+impl Replay {
+ /// If `other` is longer than `self`, add the extra elements to `self`.
+ pub fn merge(&mut self, other: &Replay) {
+ if other.steps.len() > self.steps.len() {
+ let sl = self.steps.len();
+ self.steps.extend_from_slice(&other.steps[sl..]);
+ }
+ }
+}
+
+/// Result of loading a replay file.
+#[derive(Clone, Debug)]
+pub(crate) enum ReplayFileStatus {
+ /// The file is valid and represents a currently-in-progress test.
+ InProgress(Replay),
+ /// The file is valid, but indicates that all testing has completed.
+ Terminated(Replay),
+ /// The file is not parsable.
+ Corrupt,
+}
+
+/// Open the file in the usual read+append+create mode.
+pub(crate) fn open_file(path: impl AsRef<Path>) -> io::Result<fs::File> {
+ fs::OpenOptions::new()
+ .read(true)
+ .append(true)
+ .create(true)
+ .truncate(false)
+ .open(path)
+}
+
+fn step_to_char(step: &TestCaseResult) -> char {
+ match *step {
+ Ok(_) => '+',
+ Err(TestCaseError::Reject(_)) => '!',
+ Err(TestCaseError::Fail(_)) => '-',
+ }
+}
+
+/// Append the given step to the given output.
+pub(crate) fn append(
+ mut file: impl Write,
+ step: &TestCaseResult,
+) -> io::Result<()> {
+ write!(file, "{}", step_to_char(step))
+}
+
+/// Append a no-op step to the given output.
+pub(crate) fn ping(mut file: impl Write) -> io::Result<()> {
+ write!(file, " ")
+}
+
+/// Append a termination mark to the given output.
+pub(crate) fn terminate(mut file: impl Write) -> io::Result<()> {
+ write!(file, ".")
+}
+
+impl Replay {
+ /// Write the full state of this `Replay` to the given output.
+ pub fn init_file(&self, mut file: impl Write) -> io::Result<()> {
+ writeln!(file, "{}", SENTINEL)?;
+ writeln!(file, "{}", self.seed.to_persistence())?;
+
+ let mut step_data = Vec::<u8>::new();
+ for step in &self.steps {
+ step_data.push(step_to_char(step) as u8);
+ }
+
+ file.write_all(&step_data)?;
+
+ Ok(())
+ }
+
+ /// Mark the replay as complete in the file.
+ pub fn complete(mut file: impl Write) -> io::Result<()> {
+ write!(file, ".")
+ }
+
+ /// Parse a `Replay` out of the given file.
+ ///
+ /// The reader is implicitly seeked to the beginning before reading.
+ pub fn parse_from(
+ mut file: impl Read + Seek,
+ ) -> io::Result<ReplayFileStatus> {
+ file.seek(io::SeekFrom::Start(0))?;
+
+ let mut reader = io::BufReader::new(&mut file);
+ let mut line = String::new();
+
+ // Ensure it starts with the sentinel. We do this since we rely on a
+ // named temporary file which could be in a location where another
+ // actor could replace it with, eg, a symlink to a location they don't
+ // control but we do. By rejecting a read from a file missing the
+ // sentinel, and not doing any writes if we can't read the file, we
+ // won't risk overwriting another file since the prospective attacker
+ // would need to be able to change the file to start with the sentinel
+ // themselves.
+ //
+ // There are still some possible symlink attacks that can work by
+ // tricking us into reading, but those are non-destructive things like
+ // interfering with a FIFO or Unix socket.
+ reader.read_line(&mut line)?;
+ if SENTINEL != line.trim() {
+ return Ok(ReplayFileStatus::Corrupt);
+ }
+
+ line.clear();
+ reader.read_line(&mut line)?;
+ let seed = match Seed::from_persistence(&line) {
+ Some(seed) => seed,
+ None => return Ok(ReplayFileStatus::Corrupt),
+ };
+
+ line.clear();
+ reader.read_line(&mut line)?;
+
+ let mut steps = Vec::new();
+ for ch in line.chars() {
+ match ch {
+ '+' => steps.push(Ok(())),
+ '-' => steps
+ .push(Err(TestCaseError::fail("failed in other process"))),
+ '!' => steps.push(Err(TestCaseError::reject(
+ "rejected in other process",
+ ))),
+ '.' => {
+ return Ok(ReplayFileStatus::Terminated(Replay {
+ seed,
+ steps,
+ }))
+ }
+ ' ' => (),
+ _ => return Ok(ReplayFileStatus::Corrupt),
+ }
+ }
+
+ Ok(ReplayFileStatus::InProgress(Replay { seed, steps }))
+ }
+}
diff --git a/vendor/proptest/src/test_runner/result_cache.rs b/vendor/proptest/src/test_runner/result_cache.rs
new file mode 100644
index 000000000..c5bc68c3a
--- /dev/null
+++ b/vendor/proptest/src/test_runner/result_cache.rs
@@ -0,0 +1,120 @@
+//-
+// Copyright 2018 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::fmt;
+use crate::std_facade::Box;
+#[cfg(feature = "std")]
+use std::collections::HashMap;
+
+use crate::test_runner::errors::TestCaseResult;
+
+/// A key used for the result cache.
+///
+/// The capabilities of this structure are currently quite limited; all one can
+/// do with safe code is get the `&dyn Debug` of the test input value. This may
+/// improve in the future, particularly at such a time that specialisation
+/// becomes stable.
+#[derive(Debug)]
+pub struct ResultCacheKey<'a> {
+ value: &'a dyn fmt::Debug,
+}
+
+impl<'a> ResultCacheKey<'a> {
+ pub(crate) fn new(value: &'a dyn fmt::Debug) -> Self {
+ Self { value }
+ }
+
+ /// Return the test input value as an `&dyn Debug`.
+ pub fn value_debug(&self) -> &dyn fmt::Debug {
+ self.value
+ }
+}
+
+/// An object which can cache the outcomes of tests.
+pub trait ResultCache {
+ /// Convert the given cache key into a `u64` representing that value. The
+ /// u64 is used as the key below.
+ ///
+ /// This is a separate step so that ownership of the key value can be
+ /// handed off to user code without needing to be able to clone it.
+ fn key(&self, key: &ResultCacheKey) -> u64;
+ /// Save `result` as the outcome associated with the test input in `key`.
+ ///
+ /// `result` is passed as a reference so that the decision to clone depends
+ /// on whether the cache actually plans on storing it.
+ fn put(&mut self, key: u64, result: &TestCaseResult);
+ /// If `put()` has been called with a semantically equivalent `key`, return
+ /// the saved result. Otherwise, return `None`.
+ fn get(&self, key: u64) -> Option<&TestCaseResult>;
+}
+
+#[cfg(feature = "std")]
+#[derive(Debug, Default, Clone)]
+struct BasicResultCache {
+ entries: HashMap<u64, TestCaseResult>,
+}
+
+#[cfg(feature = "std")]
+impl ResultCache for BasicResultCache {
+ fn key(&self, val: &ResultCacheKey) -> u64 {
+ use std::collections::hash_map::DefaultHasher;
+ use std::hash::Hasher;
+ use std::io::{self, Write};
+
+ struct HashWriter(DefaultHasher);
+ impl io::Write for HashWriter {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.0.write(buf);
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+
+ let mut hash = HashWriter(DefaultHasher::default());
+ write!(hash, "{:?}", val).expect("Debug format returned Err");
+ hash.0.finish()
+ }
+
+ fn put(&mut self, key: u64, result: &TestCaseResult) {
+ self.entries.insert(key, result.clone());
+ }
+
+ fn get(&self, key: u64) -> Option<&TestCaseResult> {
+ self.entries.get(&key)
+ }
+}
+
+/// A basic result cache.
+///
+/// Values are identified by their `Debug` string representation.
+#[cfg(feature = "std")]
+pub fn basic_result_cache() -> Box<dyn ResultCache> {
+ Box::new(BasicResultCache::default())
+}
+
+pub(crate) struct NoOpResultCache;
+impl ResultCache for NoOpResultCache {
+ fn key(&self, _: &ResultCacheKey) -> u64 {
+ 0
+ }
+ fn put(&mut self, _: u64, _: &TestCaseResult) {}
+ fn get(&self, _: u64) -> Option<&TestCaseResult> {
+ None
+ }
+}
+
+/// A result cache that does nothing.
+///
+/// This is the default value of `ProptestConfig.result_cache`.
+pub fn noop_result_cache() -> Box<dyn ResultCache> {
+ Box::new(NoOpResultCache)
+}
diff --git a/vendor/proptest/src/test_runner/rng.rs b/vendor/proptest/src/test_runner/rng.rs
new file mode 100644
index 000000000..78c8d7a75
--- /dev/null
+++ b/vendor/proptest/src/test_runner/rng.rs
@@ -0,0 +1,724 @@
+//-
+// Copyright 2017, 2018, 2019, 2020 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::{Arc, String, ToOwned, Vec};
+use core::result::Result;
+use core::{fmt, str, u8};
+
+use byteorder::{ByteOrder, LittleEndian};
+use rand::{self, Rng, RngCore, SeedableRng};
+use rand_chacha::ChaChaRng;
+use rand_xorshift::XorShiftRng;
+
+/// Identifies a particular RNG algorithm supported by proptest.
+///
+/// Proptest supports dynamic configuration of algorithms to allow it to
+/// continue operating with persisted regression files and to allow the
+/// configuration to be expressed in the `Config` struct.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RngAlgorithm {
+ /// The [XorShift](https://rust-random.github.io/rand/rand_xorshift/struct.XorShiftRng.html)
+ /// algorithm. This was the default up through and including Proptest 0.9.0.
+ ///
+ /// It is faster than ChaCha but produces lower quality randomness and has
+ /// some pathological cases where it may fail to produce outputs that are
+ /// random even to casual observation.
+ ///
+ /// The seed must be exactly 16 bytes.
+ XorShift,
+ /// The [ChaCha](https://rust-random.github.io/rand/rand_chacha/struct.ChaChaRng.html)
+ /// algorithm. This became the default with Proptest 0.9.1.
+ ///
+ /// The seed must be exactly 32 bytes.
+ ChaCha,
+ /// This is not an actual RNG algorithm, but instead returns data directly
+ /// from its "seed".
+ ///
+ /// This is useful when Proptest is being driven from some other entropy
+ /// source, such as a fuzzer.
+ ///
+ /// If the seed is depleted, the RNG will return 0s forever.
+ ///
+ /// Note that in cases where a new RNG is to be derived from an existing
+ /// one, *the data is split evenly between them*, regardless of how much
+ /// entropy is actually needed. This means that combinators like
+ /// `prop_perturb` and `prop_flat_map` can require extremely large inputs.
+ PassThrough,
+ /// This is equivalent to the `ChaCha` RNG, with the addition that it
+ /// records the bytes used to create a value.
+ ///
+ /// This is useful when Proptest is used for fuzzing, and a corpus of
+ /// initial inputs need to be created. Note that in these cases, you need
+ /// to use the `TestRunner` API directly yourself instead of using the
+ /// `proptest!` macro, as otherwise there is no way to obtain the bytes
+ /// this captures.
+ Recorder,
+ #[allow(missing_docs)]
+ #[doc(hidden)]
+ _NonExhaustive,
+}
+
+impl Default for RngAlgorithm {
+ fn default() -> Self {
+ RngAlgorithm::ChaCha
+ }
+}
+
+impl RngAlgorithm {
+ pub(crate) fn persistence_key(self) -> &'static str {
+ match self {
+ RngAlgorithm::XorShift => "xs",
+ RngAlgorithm::ChaCha => "cc",
+ RngAlgorithm::PassThrough => "pt",
+ RngAlgorithm::Recorder => "rc",
+ RngAlgorithm::_NonExhaustive => unreachable!(),
+ }
+ }
+
+ pub(crate) fn from_persistence_key(k: &str) -> Option<Self> {
+ match k {
+ "xs" => Some(RngAlgorithm::XorShift),
+ "cc" => Some(RngAlgorithm::ChaCha),
+ "pt" => Some(RngAlgorithm::PassThrough),
+ "rc" => Some(RngAlgorithm::Recorder),
+ _ => None,
+ }
+ }
+}
+
+// These two are only used for parsing the environment variable
+// PROPTEST_RNG_ALGORITHM.
+impl str::FromStr for RngAlgorithm {
+ type Err = ();
+ fn from_str(s: &str) -> Result<Self, ()> {
+ RngAlgorithm::from_persistence_key(s).ok_or(())
+ }
+}
+impl fmt::Display for RngAlgorithm {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.persistence_key())
+ }
+}
+
+/// Proptest's random number generator.
+#[derive(Clone, Debug)]
+pub struct TestRng {
+ rng: TestRngImpl,
+}
+
+#[derive(Clone, Debug)]
+enum TestRngImpl {
+ XorShift(XorShiftRng),
+ ChaCha(ChaChaRng),
+ PassThrough {
+ off: usize,
+ end: usize,
+ data: Arc<[u8]>,
+ },
+ Recorder {
+ rng: ChaChaRng,
+ record: Vec<u8>,
+ },
+}
+
+impl RngCore for TestRng {
+ fn next_u32(&mut self) -> u32 {
+ match &mut self.rng {
+ &mut TestRngImpl::XorShift(ref mut rng) => rng.next_u32(),
+
+ &mut TestRngImpl::ChaCha(ref mut rng) => rng.next_u32(),
+
+ &mut TestRngImpl::PassThrough { .. } => {
+ let mut buf = [0; 4];
+ self.fill_bytes(&mut buf[..]);
+ LittleEndian::read_u32(&buf[..])
+ }
+
+ &mut TestRngImpl::Recorder {
+ ref mut rng,
+ ref mut record,
+ } => {
+ let read = rng.next_u32();
+ record.extend_from_slice(&read.to_le_bytes());
+ read
+ }
+ }
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ match &mut self.rng {
+ &mut TestRngImpl::XorShift(ref mut rng) => rng.next_u64(),
+
+ &mut TestRngImpl::ChaCha(ref mut rng) => rng.next_u64(),
+
+ &mut TestRngImpl::PassThrough { .. } => {
+ let mut buf = [0; 8];
+ self.fill_bytes(&mut buf[..]);
+ LittleEndian::read_u64(&buf[..])
+ }
+
+ &mut TestRngImpl::Recorder {
+ ref mut rng,
+ ref mut record,
+ } => {
+ let read = rng.next_u64();
+ record.extend_from_slice(&read.to_le_bytes());
+ read
+ }
+ }
+ }
+
+ fn fill_bytes(&mut self, dest: &mut [u8]) {
+ match &mut self.rng {
+ &mut TestRngImpl::XorShift(ref mut rng) => rng.fill_bytes(dest),
+
+ &mut TestRngImpl::ChaCha(ref mut rng) => rng.fill_bytes(dest),
+
+ &mut TestRngImpl::PassThrough {
+ ref mut off,
+ end,
+ ref data,
+ } => {
+ let bytes_to_copy = dest.len().min(end - *off);
+ dest[..bytes_to_copy]
+ .copy_from_slice(&data[*off..*off + bytes_to_copy]);
+ *off += bytes_to_copy;
+ for i in bytes_to_copy..dest.len() {
+ dest[i] = 0;
+ }
+ }
+
+ &mut TestRngImpl::Recorder {
+ ref mut rng,
+ ref mut record,
+ } => {
+ let res = rng.fill_bytes(dest);
+ record.extend_from_slice(&dest);
+ res
+ }
+ }
+ }
+
+ fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
+ match self.rng {
+ TestRngImpl::XorShift(ref mut rng) => rng.try_fill_bytes(dest),
+
+ TestRngImpl::ChaCha(ref mut rng) => rng.try_fill_bytes(dest),
+
+ TestRngImpl::PassThrough { .. } => {
+ self.fill_bytes(dest);
+ Ok(())
+ }
+
+ TestRngImpl::Recorder {
+ ref mut rng,
+ ref mut record,
+ } => {
+ let res = rng.try_fill_bytes(dest);
+ if res.is_ok() {
+ record.extend_from_slice(&dest);
+ }
+ res
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub(crate) enum Seed {
+ XorShift([u8; 16]),
+ ChaCha([u8; 32]),
+ PassThrough(Option<(usize, usize)>, Arc<[u8]>),
+ Recorder([u8; 32]),
+}
+
+impl Seed {
+ pub(crate) fn from_bytes(algorithm: RngAlgorithm, seed: &[u8]) -> Self {
+ match algorithm {
+ RngAlgorithm::XorShift => {
+ assert_eq!(16, seed.len(), "XorShift requires a 16-byte seed");
+ let mut buf = [0; 16];
+ buf.copy_from_slice(seed);
+ Seed::XorShift(buf)
+ }
+
+ RngAlgorithm::ChaCha => {
+ assert_eq!(32, seed.len(), "ChaCha requires a 32-byte seed");
+ let mut buf = [0; 32];
+ buf.copy_from_slice(seed);
+ Seed::ChaCha(buf)
+ }
+
+ RngAlgorithm::PassThrough => Seed::PassThrough(None, seed.into()),
+
+ RngAlgorithm::Recorder => {
+ assert_eq!(32, seed.len(), "Recorder requires a 32-byte seed");
+ let mut buf = [0; 32];
+ buf.copy_from_slice(seed);
+ Seed::Recorder(buf)
+ }
+
+ RngAlgorithm::_NonExhaustive => unreachable!(),
+ }
+ }
+
+ pub(crate) fn from_persistence(string: &str) -> Option<Seed> {
+ fn from_base16(dst: &mut [u8], src: &str) -> Option<()> {
+ if dst.len() * 2 != src.len() {
+ return None;
+ }
+
+ for (dst_byte, src_pair) in
+ dst.into_iter().zip(src.as_bytes().chunks(2))
+ {
+ *dst_byte =
+ u8::from_str_radix(str::from_utf8(src_pair).ok()?, 16)
+ .ok()?;
+ }
+
+ Some(())
+ }
+
+ let parts =
+ string.trim().split(char::is_whitespace).collect::<Vec<_>>();
+ RngAlgorithm::from_persistence_key(&parts[0]).and_then(
+ |alg| match alg {
+ RngAlgorithm::XorShift => {
+ if 5 != parts.len() {
+ return None;
+ }
+
+ let mut dwords = [0u32; 4];
+ for (dword, part) in
+ (&mut dwords[..]).into_iter().zip(&parts[1..])
+ {
+ *dword = part.parse().ok()?;
+ }
+
+ let mut seed = [0u8; 16];
+ LittleEndian::write_u32_into(&dwords[..], &mut seed[..]);
+ Some(Seed::XorShift(seed))
+ }
+
+ RngAlgorithm::ChaCha => {
+ if 2 != parts.len() {
+ return None;
+ }
+
+ let mut seed = [0u8; 32];
+ from_base16(&mut seed, &parts[1])?;
+ Some(Seed::ChaCha(seed))
+ }
+
+ RngAlgorithm::PassThrough => {
+ if 1 == parts.len() {
+ return Some(Seed::PassThrough(None, vec![].into()));
+ }
+
+ if 2 != parts.len() {
+ return None;
+ }
+
+ let mut seed = vec![0u8; parts[1].len() / 2];
+ from_base16(&mut seed, &parts[1])?;
+ Some(Seed::PassThrough(None, seed.into()))
+ }
+
+ RngAlgorithm::Recorder => {
+ if 2 != parts.len() {
+ return None;
+ }
+
+ let mut seed = [0u8; 32];
+ from_base16(&mut seed, &parts[1])?;
+ Some(Seed::Recorder(seed))
+ }
+
+ RngAlgorithm::_NonExhaustive => unreachable!(),
+ },
+ )
+ }
+
+ pub(crate) fn to_persistence(&self) -> String {
+ fn to_base16(dst: &mut String, src: &[u8]) {
+ for byte in src {
+ dst.push_str(&format!("{:02x}", byte));
+ }
+ }
+
+ match *self {
+ Seed::XorShift(ref seed) => {
+ let mut dwords = [0u32; 4];
+ LittleEndian::read_u32_into(seed, &mut dwords[..]);
+ format!(
+ "{} {} {} {} {}",
+ RngAlgorithm::XorShift.persistence_key(),
+ dwords[0],
+ dwords[1],
+ dwords[2],
+ dwords[3]
+ )
+ }
+
+ Seed::ChaCha(ref seed) => {
+ let mut string =
+ RngAlgorithm::ChaCha.persistence_key().to_owned();
+ string.push(' ');
+ to_base16(&mut string, seed);
+ string
+ }
+
+ Seed::PassThrough(bounds, ref data) => {
+ let data =
+ bounds.map_or(&data[..], |(start, end)| &data[start..end]);
+ let mut string =
+ RngAlgorithm::PassThrough.persistence_key().to_owned();
+ string.push(' ');
+ to_base16(&mut string, data);
+ string
+ }
+
+ Seed::Recorder(ref seed) => {
+ let mut string =
+ RngAlgorithm::Recorder.persistence_key().to_owned();
+ string.push(' ');
+ to_base16(&mut string, seed);
+ string
+ }
+ }
+ }
+}
+
+impl TestRng {
+ /// Create a new RNG with the given algorithm and seed.
+ ///
+ /// Any RNG created with the same algorithm-seed pair will produce the same
+ /// sequence of values on all systems and all supporting versions of
+ /// proptest.
+ ///
+ /// ## Panics
+ ///
+ /// Panics if `seed` is not an appropriate length for `algorithm`.
+ pub fn from_seed(algorithm: RngAlgorithm, seed: &[u8]) -> Self {
+ TestRng::from_seed_internal(Seed::from_bytes(algorithm, seed))
+ }
+
+ /// Dumps the bytes obtained from the RNG so far (only works if the RNG is
+ /// set to `Recorder`).
+ ///
+ /// ## Panics
+ ///
+ /// Panics if this RNG does not capture generated data.
+ pub fn bytes_used(&self) -> Vec<u8> {
+ match self.rng {
+ TestRngImpl::Recorder { ref record, .. } => record.clone(),
+ _ => panic!("bytes_used() called on non-Recorder RNG"),
+ }
+ }
+
+ /// Construct a default TestRng from entropy.
+ pub(crate) fn default_rng(algorithm: RngAlgorithm) -> Self {
+ #[cfg(feature = "std")]
+ {
+ Self {
+ rng: match algorithm {
+ RngAlgorithm::XorShift => {
+ TestRngImpl::XorShift(XorShiftRng::from_entropy())
+ }
+ RngAlgorithm::ChaCha => {
+ TestRngImpl::ChaCha(ChaChaRng::from_entropy())
+ }
+ RngAlgorithm::PassThrough => {
+ panic!("cannot create default instance of PassThrough")
+ }
+ RngAlgorithm::Recorder => TestRngImpl::Recorder {
+ rng: ChaChaRng::from_entropy(),
+ record: Vec::new(),
+ },
+ RngAlgorithm::_NonExhaustive => unreachable!(),
+ },
+ }
+ }
+ #[cfg(all(
+ not(feature = "std"),
+ any(target_arch = "x86", target_arch = "x86_64"),
+ feature = "hardware-rng"
+ ))]
+ {
+ return Self::hardware_rng(algorithm);
+ }
+ #[cfg(not(feature = "std"))]
+ {
+ return Self::deterministic_rng(algorithm);
+ }
+ }
+
+ const SEED_FOR_XOR_SHIFT: [u8; 16] = [
+ 0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea,
+ 0x99, 0x67, 0x2d, 0x6d,
+ ];
+
+ const SEED_FOR_CHA_CHA: [u8; 32] = [
+ 0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea,
+ 0x99, 0x67, 0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0,
+ 0x59, 0x22, 0x6d, 0xc5, 0x46, 0x39, 0x1c, 0x4a,
+ ];
+
+ /// Returns a `TestRng` with a seed generated with the
+ /// RdRand instruction on x86 machines.
+ ///
+ /// This is useful in `no_std` scenarios on x86 where we don't
+ /// have a random number infrastructure but the `rdrand` instruction is
+ /// available.
+ #[cfg(all(
+ not(feature = "std"),
+ any(target_arch = "x86", target_arch = "x86_64"),
+ feature = "hardware-rng"
+ ))]
+ pub fn hardware_rng(algorithm: RngAlgorithm) -> Self {
+ use x86::random::{rdrand_slice, RdRand};
+
+ Self::from_seed_internal(match algorithm {
+ RngAlgorithm::XorShift => {
+ // Initialize to a sane seed just in case
+ let mut seed: [u8; 16] = TestRng::SEED_FOR_XOR_SHIFT;
+ unsafe {
+ let r = rdrand_slice(&mut seed);
+ debug_assert!(r, "hardware_rng should only be called on machines with support for rdrand");
+ }
+ Seed::XorShift(seed)
+ }
+ RngAlgorithm::ChaCha => {
+ // Initialize to a sane seed just in case
+ let mut seed: [u8; 32] = TestRng::SEED_FOR_CHA_CHA;
+ unsafe {
+ let r = rdrand_slice(&mut seed);
+ debug_assert!(r, "hardware_rng should only be called on machines with support for rdrand");
+ }
+ Seed::ChaCha(seed)
+ }
+ RngAlgorithm::PassThrough => {
+ panic!("deterministic RNG not available for PassThrough")
+ }
+ RngAlgorithm::Recorder => {
+ // Initialize to a sane seed just in case
+ let mut seed: [u8; 32] = TestRng::SEED_FOR_CHA_CHA;
+ unsafe {
+ let r = rdrand_slice(&mut seed);
+ debug_assert!(r, "hardware_rng should only be called on machines with support for rdrand");
+ }
+ Seed::Recorder(seed)
+ }
+ RngAlgorithm::_NonExhaustive => unreachable!(),
+ })
+ }
+
+ /// Returns a `TestRng` with a particular hard-coded seed.
+ ///
+ /// The seed value will always be the same for a particular version of
+ /// Proptest and algorithm, but may change across releases.
+ ///
+ /// This is useful for testing things like strategy implementations without
+ /// risking getting "unlucky" RNGs which deviate from average behaviour
+ /// enough to cause spurious failures. For example, a strategy for `bool`
+ /// which is supposed to produce `true` 50% of the time might have a test
+ /// which checks that the distribution is "close enough" to 50%. If every
+ /// test run starts with a different RNG, occasionally there will be
+ /// spurious test failures when the RNG happens to produce a very skewed
+ /// distribution. Using this or `TestRunner::deterministic()` avoids such
+ /// issues.
+ pub fn deterministic_rng(algorithm: RngAlgorithm) -> Self {
+ Self::from_seed_internal(match algorithm {
+ RngAlgorithm::XorShift => {
+ Seed::XorShift(TestRng::SEED_FOR_XOR_SHIFT)
+ }
+ RngAlgorithm::ChaCha => Seed::ChaCha(TestRng::SEED_FOR_CHA_CHA),
+ RngAlgorithm::PassThrough => {
+ panic!("deterministic RNG not available for PassThrough")
+ }
+ RngAlgorithm::Recorder => Seed::Recorder(TestRng::SEED_FOR_CHA_CHA),
+ RngAlgorithm::_NonExhaustive => unreachable!(),
+ })
+ }
+
+ /// Construct a TestRng by the perturbed randomized seed
+ /// from an existing TestRng.
+ pub(crate) fn gen_rng(&mut self) -> Self {
+ Self::from_seed_internal(self.new_rng_seed())
+ }
+
+ /// Overwrite the given TestRng with the provided seed.
+ pub(crate) fn set_seed(&mut self, seed: Seed) {
+ *self = Self::from_seed_internal(seed);
+ }
+
+ /// Generate a new randomized seed, set it to this TestRng,
+ /// and return the seed.
+ pub(crate) fn gen_get_seed(&mut self) -> Seed {
+ let seed = self.new_rng_seed();
+ self.set_seed(seed.clone());
+ seed
+ }
+
+ /// Randomize a perturbed randomized seed from the given TestRng.
+ pub(crate) fn new_rng_seed(&mut self) -> Seed {
+ match self.rng {
+ TestRngImpl::XorShift(ref mut rng) => {
+ let mut seed = rng.gen::<[u8; 16]>();
+
+ // Directly using XorShiftRng::from_seed() at this point would
+ // result in rng and the returned value being exactly the same.
+ // Perturb the seed with some arbitrary values to prevent this.
+ for word in seed.chunks_mut(4) {
+ word[3] ^= 0xde;
+ word[2] ^= 0xad;
+ word[1] ^= 0xbe;
+ word[0] ^= 0xef;
+ }
+
+ Seed::XorShift(seed)
+ }
+
+ TestRngImpl::ChaCha(ref mut rng) => Seed::ChaCha(rng.gen()),
+
+ TestRngImpl::PassThrough {
+ ref mut off,
+ ref mut end,
+ ref data,
+ } => {
+ let len = *end - *off;
+ let child_start = *off + len / 2;
+ let child_end = *off + len;
+ *end = child_start;
+ Seed::PassThrough(
+ Some((child_start, child_end)),
+ Arc::clone(data),
+ )
+ }
+
+ TestRngImpl::Recorder { ref mut rng, .. } => {
+ Seed::Recorder(rng.gen())
+ }
+ }
+ }
+
+ /// Construct a TestRng from a given seed.
+ fn from_seed_internal(seed: Seed) -> Self {
+ Self {
+ rng: match seed {
+ Seed::XorShift(seed) => {
+ TestRngImpl::XorShift(XorShiftRng::from_seed(seed))
+ }
+
+ Seed::ChaCha(seed) => {
+ TestRngImpl::ChaCha(ChaChaRng::from_seed(seed))
+ }
+
+ Seed::PassThrough(bounds, data) => {
+ let (start, end) = bounds.unwrap_or((0, data.len()));
+ TestRngImpl::PassThrough {
+ off: start,
+ end,
+ data,
+ }
+ }
+
+ Seed::Recorder(seed) => TestRngImpl::Recorder {
+ rng: ChaChaRng::from_seed(seed),
+ record: Vec::new(),
+ },
+ },
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::std_facade::Vec;
+
+ use rand::{Rng, RngCore};
+
+ use super::{RngAlgorithm, Seed, TestRng};
+ use crate::arbitrary::any;
+ use crate::strategy::*;
+
+ proptest! {
+ #[test]
+ fn gen_parse_seeds(
+ seed in prop_oneof![
+ any::<[u8;16]>().prop_map(Seed::XorShift),
+ any::<[u8;32]>().prop_map(Seed::ChaCha),
+ any::<Vec<u8>>().prop_map(|data| Seed::PassThrough(None, data.into())),
+ any::<[u8;32]>().prop_map(Seed::Recorder),
+ ])
+ {
+ assert_eq!(seed, Seed::from_persistence(&seed.to_persistence()).unwrap());
+ }
+
+ #[test]
+ fn rngs_dont_clone_self_on_genrng(
+ seed in prop_oneof![
+ any::<[u8;16]>().prop_map(Seed::XorShift),
+ any::<[u8;32]>().prop_map(Seed::ChaCha),
+ Just(()).prop_perturb(|_, mut rng| {
+ let mut buf = vec![0u8; 2048];
+ rng.fill_bytes(&mut buf);
+ Seed::PassThrough(None, buf.into())
+ }),
+ any::<[u8;32]>().prop_map(Seed::Recorder),
+ ])
+ {
+ type Value = [u8;32];
+ let orig = TestRng::from_seed_internal(seed);
+
+ {
+ let mut rng1 = orig.clone();
+ let mut rng2 = rng1.gen_rng();
+ assert_ne!(rng1.gen::<Value>(), rng2.gen::<Value>());
+ }
+
+ {
+ let mut rng1 = orig.clone();
+ let mut rng2 = rng1.gen_rng();
+ let mut rng3 = rng1.gen_rng();
+ let mut rng4 = rng2.gen_rng();
+ let a = rng1.gen::<Value>();
+ let b = rng2.gen::<Value>();
+ let c = rng3.gen::<Value>();
+ let d = rng4.gen::<Value>();
+ assert_ne!(a, b);
+ assert_ne!(a, c);
+ assert_ne!(a, d);
+ assert_ne!(b, c);
+ assert_ne!(b, d);
+ assert_ne!(c, d);
+ }
+ }
+ }
+
+ #[test]
+ fn passthrough_rng_behaves_properly() {
+ let mut rng = TestRng::from_seed(
+ RngAlgorithm::PassThrough,
+ &[
+ 0xDE, 0xC0, 0x12, 0x34, 0x56, 0x78, 0xFE, 0xCA, 0xEF, 0xBE,
+ 0xAD, 0xDE, 0x01, 0x02, 0x03,
+ ],
+ );
+
+ assert_eq!(0x3412C0DE, rng.next_u32());
+ assert_eq!(0xDEADBEEFCAFE7856, rng.next_u64());
+
+ let mut buf = [0u8; 4];
+ rng.try_fill_bytes(&mut buf[0..4]).unwrap();
+ assert_eq!([1, 2, 3, 0], buf);
+ rng.try_fill_bytes(&mut buf[0..4]).unwrap();
+ assert_eq!([0, 0, 0, 0], buf);
+ }
+}
diff --git a/vendor/proptest/src/test_runner/runner.rs b/vendor/proptest/src/test_runner/runner.rs
new file mode 100644
index 000000000..ce540499e
--- /dev/null
+++ b/vendor/proptest/src/test_runner/runner.rs
@@ -0,0 +1,1538 @@
+//-
+// Copyright 2017, 2018, 2019 The proptest developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::std_facade::{Arc, BTreeMap, Box, String, Vec};
+use core::sync::atomic::AtomicUsize;
+use core::sync::atomic::Ordering::SeqCst;
+use core::{fmt, iter};
+#[cfg(feature = "std")]
+use std::panic::{self, AssertUnwindSafe};
+
+#[cfg(feature = "fork")]
+use rusty_fork;
+#[cfg(feature = "fork")]
+use std::cell::{Cell, RefCell};
+#[cfg(feature = "fork")]
+use std::env;
+#[cfg(feature = "fork")]
+use std::fs;
+#[cfg(feature = "fork")]
+use tempfile;
+
+use crate::strategy::*;
+use crate::test_runner::config::*;
+use crate::test_runner::errors::*;
+use crate::test_runner::failure_persistence::PersistedSeed;
+use crate::test_runner::reason::*;
+#[cfg(feature = "fork")]
+use crate::test_runner::replay;
+use crate::test_runner::result_cache::*;
+use crate::test_runner::rng::TestRng;
+
+#[cfg(feature = "fork")]
+const ENV_FORK_FILE: &'static str = "_PROPTEST_FORKFILE";
+
+const ALWAYS: u32 = 0;
+const SHOW_FALURES: u32 = 1;
+const TRACE: u32 = 2;
+
+#[cfg(feature = "std")]
+macro_rules! verbose_message {
+ ($runner:expr, $level:expr, $fmt:tt $($arg:tt)*) => { {
+ #[allow(unused_comparisons)]
+ {
+ if $runner.config.verbose >= $level {
+ eprintln!(concat!("proptest: ", $fmt) $($arg)*);
+ }
+ };
+ ()
+ } }
+}
+
+#[cfg(not(feature = "std"))]
+macro_rules! verbose_message {
+ ($runner:expr, $level:expr, $fmt:tt $($arg:tt)*) => {
+ let _ = $level;
+ };
+}
+
+type RejectionDetail = BTreeMap<Reason, u32>;
+
+/// State used when running a proptest test.
+#[derive(Clone)]
+pub struct TestRunner {
+ config: Config,
+ successes: u32,
+ local_rejects: u32,
+ global_rejects: u32,
+ rng: TestRng,
+ flat_map_regens: Arc<AtomicUsize>,
+
+ local_reject_detail: RejectionDetail,
+ global_reject_detail: RejectionDetail,
+}
+
+impl fmt::Debug for TestRunner {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("TestRunner")
+ .field("config", &self.config)
+ .field("successes", &self.successes)
+ .field("local_rejects", &self.local_rejects)
+ .field("global_rejects", &self.global_rejects)
+ .field("rng", &"<TestRng>")
+ .field("flat_map_regens", &self.flat_map_regens)
+ .field("local_reject_detail", &self.local_reject_detail)
+ .field("global_reject_detail", &self.global_reject_detail)
+ .finish()
+ }
+}
+
+impl fmt::Display for TestRunner {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "\tsuccesses: {}\n\
+ \tlocal rejects: {}\n",
+ self.successes, self.local_rejects
+ )?;
+ for (whence, count) in &self.local_reject_detail {
+ writeln!(f, "\t\t{} times at {}", count, whence)?;
+ }
+ writeln!(f, "\tglobal rejects: {}", self.global_rejects)?;
+ for (whence, count) in &self.global_reject_detail {
+ writeln!(f, "\t\t{} times at {}", count, whence)?;
+ }
+
+ Ok(())
+ }
+}
+
+/// Equivalent to: `TestRunner::new(Config::default())`.
+impl Default for TestRunner {
+ fn default() -> Self {
+ Self::new(Config::default())
+ }
+}
+
+#[cfg(feature = "fork")]
+#[derive(Debug)]
+struct ForkOutput {
+ file: Option<fs::File>,
+}
+
+#[cfg(feature = "fork")]
+impl ForkOutput {
+ fn append(&mut self, result: &TestCaseResult) {
+ if let Some(ref mut file) = self.file {
+ replay::append(file, result)
+ .expect("Failed to append to replay file");
+ }
+ }
+
+ fn ping(&mut self) {
+ if let Some(ref mut file) = self.file {
+ replay::ping(file).expect("Failed to append to replay file");
+ }
+ }
+
+ fn terminate(&mut self) {
+ if let Some(ref mut file) = self.file {
+ replay::terminate(file).expect("Failed to append to replay file");
+ }
+ }
+
+ fn empty() -> Self {
+ ForkOutput { file: None }
+ }
+
+ fn is_in_fork(&self) -> bool {
+ self.file.is_some()
+ }
+}
+
+#[cfg(not(feature = "fork"))]
+#[derive(Debug)]
+struct ForkOutput;
+
+#[cfg(not(feature = "fork"))]
+impl ForkOutput {
+ fn append(&mut self, _result: &TestCaseResult) {}
+ fn ping(&mut self) {}
+ fn terminate(&mut self) {}
+ fn empty() -> Self {
+ ForkOutput
+ }
+ fn is_in_fork(&self) -> bool {
+ false
+ }
+}
+
+#[cfg(not(feature = "std"))]
+fn call_test<V, F, R>(
+ _runner: &mut TestRunner,
+ case: V,
+ test: &F,
+ replay_from_fork: &mut R,
+ result_cache: &mut dyn ResultCache,
+ _: &mut ForkOutput,
+ is_from_persisted_seed: bool,
+) -> TestCaseResultV2
+where
+ V: fmt::Debug,
+ F: Fn(V) -> TestCaseResult,
+ R: Iterator<Item = TestCaseResult>,
+{
+ if let Some(result) = replay_from_fork.next() {
+ return result.map(|_| TestCaseOk::ReplayFromForkSuccess);
+ }
+
+ let cache_key = result_cache.key(&ResultCacheKey::new(&case));
+ if let Some(result) = result_cache.get(cache_key) {
+ return result.clone().map(|_| TestCaseOk::CacheHitSuccess);
+ }
+
+ let result = test(case);
+ result_cache.put(cache_key, &result);
+ result.map(|_| {
+ if is_from_persisted_seed {
+ TestCaseOk::PersistedCaseSuccess
+ } else {
+ TestCaseOk::NewCaseSuccess
+ }
+ })
+}
+
+#[cfg(feature = "std")]
+fn call_test<V, F, R>(
+ runner: &mut TestRunner,
+ case: V,
+ test: &F,
+ replay_from_fork: &mut R,
+ result_cache: &mut dyn ResultCache,
+ fork_output: &mut ForkOutput,
+ is_from_persisted_seed: bool,
+) -> TestCaseResultV2
+where
+ V: fmt::Debug,
+ F: Fn(V) -> TestCaseResult,
+ R: Iterator<Item = TestCaseResult>,
+{
+ use std::time;
+
+ let timeout = runner.config.timeout();
+
+ if let Some(result) = replay_from_fork.next() {
+ return result.map(|_| TestCaseOk::ReplayFromForkSuccess);
+ }
+
+ // Now that we're about to start a new test (as far as the replay system is
+ // concerned), ping the replay file so the parent process can determine
+ // that we made it this far.
+ fork_output.ping();
+
+ verbose_message!(runner, TRACE, "Next test input: {:?}", case);
+
+ let cache_key = result_cache.key(&ResultCacheKey::new(&case));
+ if let Some(result) = result_cache.get(cache_key) {
+ verbose_message!(
+ runner,
+ TRACE,
+ "Test input hit cache, skipping execution"
+ );
+ return result.clone().map(|_| TestCaseOk::CacheHitSuccess);
+ }
+
+ let time_start = time::Instant::now();
+
+ let mut result = unwrap_or!(
+ panic::catch_unwind(AssertUnwindSafe(|| test(case))),
+ what => Err(TestCaseError::Fail(
+ what.downcast::<&'static str>().map(|s| (*s).into())
+ .or_else(|what| what.downcast::<String>().map(|b| (*b).into()))
+ .or_else(|what| what.downcast::<Box<str>>().map(|b| (*b).into()))
+ .unwrap_or_else(|_| "<unknown panic value>".into()))));
+
+ // If there is a timeout and we exceeded it, fail the test here so we get
+ // consistent behaviour. (The parent process cannot precisely time the test
+ // cases itself.)
+ if timeout > 0 && result.is_ok() {
+ let elapsed = time_start.elapsed();
+ let elapsed_millis = elapsed.as_secs() as u32 * 1000
+ + elapsed.subsec_nanos() / 1_000_000;
+
+ if elapsed_millis > timeout {
+ result = Err(TestCaseError::fail(format!(
+ "Timeout of {} ms exceeded: test took {} ms",
+ timeout, elapsed_millis
+ )));
+ }
+ }
+
+ result_cache.put(cache_key, &result);
+ fork_output.append(&result);
+
+ match result {
+ Ok(()) => verbose_message!(runner, TRACE, "Test case passed"),
+ Err(TestCaseError::Reject(ref reason)) => verbose_message!(
+ runner,
+ SHOW_FALURES,
+ "Test case rejected: {}",
+ reason
+ ),
+ Err(TestCaseError::Fail(ref reason)) => verbose_message!(
+ runner,
+ SHOW_FALURES,
+ "Test case failed: {}",
+ reason
+ ),
+ }
+
+ result.map(|_| {
+ if is_from_persisted_seed {
+ TestCaseOk::PersistedCaseSuccess
+ } else {
+ TestCaseOk::NewCaseSuccess
+ }
+ })
+}
+
+type TestRunResult<S> = Result<(), TestError<<S as Strategy>::Value>>;
+
+impl TestRunner {
+ /// Create a fresh `TestRunner` with the given configuration.
+ ///
+ /// The runner will use an RNG with a generated seed and the default
+ /// algorithm.
+ ///
+ /// In `no_std` environments, every `TestRunner` will use the same
+ /// hard-coded seed. This seed is not contractually guaranteed and may be
+ /// changed between releases without notice.
+ pub fn new(config: Config) -> Self {
+ let algorithm = config.rng_algorithm;
+ TestRunner::new_with_rng(config, TestRng::default_rng(algorithm))
+ }
+
+ /// Create a fresh `TestRunner` with the standard deterministic RNG.
+ ///
+ /// This is sugar for the following:
+ ///
+ /// ```rust
+ /// # use proptest::test_runner::*;
+ /// let config = Config::default();
+ /// let algorithm = config.rng_algorithm;
+ /// TestRunner::new_with_rng(
+ /// config,
+ /// TestRng::deterministic_rng(algorithm));
+ /// ```
+ ///
+ /// Refer to `TestRng::deterministic_rng()` for more information on the
+ /// properties of the RNG used here.
+ pub fn deterministic() -> Self {
+ let config = Config::default();
+ let algorithm = config.rng_algorithm;
+ TestRunner::new_with_rng(config, TestRng::deterministic_rng(algorithm))
+ }
+
+ /// Create a fresh `TestRunner` with the given configuration and RNG.
+ pub fn new_with_rng(config: Config, rng: TestRng) -> Self {
+ TestRunner {
+ config: config,
+ successes: 0,
+ local_rejects: 0,
+ global_rejects: 0,
+ rng: rng,
+ flat_map_regens: Arc::new(AtomicUsize::new(0)),
+ local_reject_detail: BTreeMap::new(),
+ global_reject_detail: BTreeMap::new(),
+ }
+ }
+
+ /// Create a fresh `TestRunner` with the same config and global counters as
+ /// this one, but with local state reset and an independent `Rng` (but
+ /// deterministic).
+ pub(crate) fn partial_clone(&mut self) -> Self {
+ TestRunner {
+ config: self.config.clone(),
+ successes: 0,
+ local_rejects: 0,
+ global_rejects: 0,
+ rng: self.new_rng(),
+ flat_map_regens: Arc::clone(&self.flat_map_regens),
+ local_reject_detail: BTreeMap::new(),
+ global_reject_detail: BTreeMap::new(),
+ }
+ }
+
+ /// Returns the RNG for this test run.
+ pub fn rng(&mut self) -> &mut TestRng {
+ &mut self.rng
+ }
+
+ /// Create a new, independent but deterministic RNG from the RNG in this
+ /// runner.
+ pub fn new_rng(&mut self) -> TestRng {
+ self.rng.gen_rng()
+ }
+
+ /// Returns the configuration of this runner.
+ pub fn config(&self) -> &Config {
+ &self.config
+ }
+
+ /// Dumps the bytes obtained from the RNG so far (only works if the RNG is
+ /// set to `Recorder`).
+ ///
+ /// ## Panics
+ ///
+ /// Panics if the RNG does not capture generated data.
+ pub fn bytes_used(&self) -> Vec<u8> {
+ self.rng.bytes_used()
+ }
+
+ /// Run test cases against `f`, choosing inputs via `strategy`.
+ ///
+ /// If any failure cases occur, try to find a minimal failure case and
+ /// report that. If invoking `f` panics, the panic is turned into a
+ /// `TestCaseError::Fail`.
+ ///
+ /// If failure persistence is enabled, all persisted failing cases are
+ /// tested first. If a later non-persisted case fails, its seed is
+ /// persisted before returning failure.
+ ///
+ /// Returns success or failure indicating why the test as a whole failed.
+ pub fn run<S: Strategy>(
+ &mut self,
+ strategy: &S,
+ test: impl Fn(S::Value) -> TestCaseResult,
+ ) -> TestRunResult<S> {
+ if self.config.fork() {
+ self.run_in_fork(strategy, test)
+ } else {
+ self.run_in_process(strategy, test)
+ }
+ }
+
+ #[cfg(not(feature = "fork"))]
+ fn run_in_fork<S: Strategy>(
+ &mut self,
+ _: &S,
+ _: impl Fn(S::Value) -> TestCaseResult,
+ ) -> TestRunResult<S> {
+ unreachable!()
+ }
+
+ #[cfg(feature = "fork")]
+ fn run_in_fork<S: Strategy>(
+ &mut self,
+ strategy: &S,
+ test: impl Fn(S::Value) -> TestCaseResult,
+ ) -> TestRunResult<S> {
+ let mut test = Some(test);
+
+ let test_name = rusty_fork::fork_test::fix_module_path(
+ self.config
+ .test_name
+ .expect("Must supply test_name when forking enabled"),
+ );
+ let forkfile: RefCell<Option<tempfile::NamedTempFile>> =
+ RefCell::new(None);
+ let init_forkfile_size = Cell::new(0u64);
+ let seed = self.rng.new_rng_seed();
+ let mut replay = replay::Replay {
+ seed,
+ steps: vec![],
+ };
+ let mut child_count = 0;
+ let timeout = self.config.timeout();
+
+ fn forkfile_size(forkfile: &Option<tempfile::NamedTempFile>) -> u64 {
+ forkfile.as_ref().map_or(0, |ff| {
+ ff.as_file().metadata().map(|md| md.len()).unwrap_or(0)
+ })
+ }
+
+ loop {
+ let (child_error, last_fork_file_len) = rusty_fork::fork(
+ test_name,
+ rusty_fork_id!(),
+ |cmd| {
+ let mut forkfile = forkfile.borrow_mut();
+ if forkfile.is_none() {
+ *forkfile =
+ Some(tempfile::NamedTempFile::new().expect(
+ "Failed to create temporary file for fork",
+ ));
+ replay.init_file(forkfile.as_mut().unwrap()).expect(
+ "Failed to initialise temporary file for fork",
+ );
+ }
+
+ init_forkfile_size.set(forkfile_size(&forkfile));
+
+ cmd.env(ENV_FORK_FILE, forkfile.as_ref().unwrap().path());
+ },
+ |child, _| {
+ await_child(
+ child,
+ &mut forkfile.borrow_mut().as_mut().unwrap(),
+ timeout,
+ )
+ },
+ || match self.run_in_process(strategy, test.take().unwrap()) {
+ Ok(_) => (),
+ Err(e) => panic!(
+ "Test failed normally in child process.\n{}\n{}",
+ e, self
+ ),
+ },
+ )
+ .expect("Fork failed");
+
+ let parsed = replay::Replay::parse_from(
+ &mut forkfile.borrow_mut().as_mut().unwrap(),
+ )
+ .expect("Failed to re-read fork file");
+ match parsed {
+ replay::ReplayFileStatus::InProgress(new_replay) => {
+ replay = new_replay
+ }
+ replay::ReplayFileStatus::Terminated(new_replay) => {
+ replay = new_replay;
+ break;
+ }
+ replay::ReplayFileStatus::Corrupt => {
+ panic!("Child process corrupted replay file")
+ }
+ }
+
+ let curr_forkfile_size = forkfile_size(&forkfile.borrow());
+
+ // If the child failed to append *anything* to the forkfile, it
+ // crashed or timed out before starting even one test case, so
+ // bail.
+ if curr_forkfile_size == init_forkfile_size.get() {
+ return Err(TestError::Abort(
+ "Child process crashed or timed out before the first test \
+ started running; giving up."
+ .into(),
+ ));
+ }
+
+ // The child only terminates early if it outright crashes or we
+ // kill it due to timeout, so add a synthetic failure to the
+ // output. But only do this if the length of the fork file is the
+ // same as when we last saw it, or if the child was not killed due
+ // to timeout. (This is because the child could have appended
+ // something to the file after we gave up waiting for it but before
+ // we were able to kill it).
+ if last_fork_file_len.map_or(true, |last_fork_file_len| {
+ last_fork_file_len == curr_forkfile_size
+ }) {
+ let error = Err(child_error.unwrap_or(TestCaseError::fail(
+ "Child process was terminated abruptly \
+ but with successful status",
+ )));
+ replay::append(forkfile.borrow_mut().as_mut().unwrap(), &error)
+ .expect("Failed to append to replay file");
+ replay.steps.push(error);
+ }
+
+ // Bail if we've gone through too many processes in case the
+ // shrinking process itself is crashing.
+ child_count += 1;
+ if child_count >= 10000 {
+ return Err(TestError::Abort(
+ "Giving up after 10000 child processes crashed".into(),
+ ));
+ }
+ }
+
+ // Run through the steps in-process (without ever running the actual
+ // tests) to produce the shrunken value and update the persistence
+ // file.
+ self.rng.set_seed(replay.seed);
+ self.run_in_process_with_replay(
+ strategy,
+ |_| panic!("Ran past the end of the replay"),
+ replay.steps.into_iter(),
+ ForkOutput::empty(),
+ )
+ }
+
+ fn run_in_process<S: Strategy>(
+ &mut self,
+ strategy: &S,
+ test: impl Fn(S::Value) -> TestCaseResult,
+ ) -> TestRunResult<S> {
+ let (replay_steps, fork_output) = init_replay(&mut self.rng);
+ self.run_in_process_with_replay(
+ strategy,
+ test,
+ replay_steps.into_iter(),
+ fork_output,
+ )
+ }
+
+ fn run_in_process_with_replay<S: Strategy>(
+ &mut self,
+ strategy: &S,
+ test: impl Fn(S::Value) -> TestCaseResult,
+ mut replay_from_fork: impl Iterator<Item = TestCaseResult>,
+ mut fork_output: ForkOutput,
+ ) -> TestRunResult<S> {
+ let old_rng = self.rng.clone();
+
+ let persisted_failure_seeds: Vec<PersistedSeed> = self
+ .config
+ .failure_persistence
+ .as_ref()
+ .map(|f| f.load_persisted_failures2(self.config.source_file))
+ .unwrap_or_default();
+
+ let mut result_cache = self.new_cache();
+
+ for PersistedSeed(persisted_seed) in persisted_failure_seeds {
+ self.rng.set_seed(persisted_seed);
+ self.gen_and_run_case(
+ strategy,
+ &test,
+ &mut replay_from_fork,
+ &mut *result_cache,
+ &mut fork_output,
+ true,
+ )?;
+ }
+ self.rng = old_rng;
+
+ while self.successes < self.config.cases {
+ // Generate a new seed and make an RNG from that so that we know
+ // what seed to persist if this case fails.
+ let seed = self.rng.gen_get_seed();
+ let result = self.gen_and_run_case(
+ strategy,
+ &test,
+ &mut replay_from_fork,
+ &mut *result_cache,
+ &mut fork_output,
+ false,
+ );
+ if let Err(TestError::Fail(_, ref value)) = result {
+ if let Some(ref mut failure_persistence) =
+ self.config.failure_persistence
+ {
+ let source_file = &self.config.source_file;
+
+ // Don't update the persistence file if we're a child
+ // process. The parent relies on it remaining consistent
+ // and will take care of updating it itself.
+ if !fork_output.is_in_fork() {
+ failure_persistence.save_persisted_failure2(
+ *source_file,
+ PersistedSeed(seed),
+ value,
+ );
+ }
+ }
+ }
+
+ if let Err(e) = result {
+ fork_output.terminate();
+ return Err(e.into());
+ }
+ }
+
+ fork_output.terminate();
+ Ok(())
+ }
+
+ fn gen_and_run_case<S: Strategy>(
+ &mut self,
+ strategy: &S,
+ f: &impl Fn(S::Value) -> TestCaseResult,
+ replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
+ result_cache: &mut dyn ResultCache,
+ fork_output: &mut ForkOutput,
+ is_from_persisted_seed: bool,
+ ) -> TestRunResult<S> {
+ let case = unwrap_or!(strategy.new_tree(self), msg =>
+ return Err(TestError::Abort(msg)));
+
+ // We only count new cases to our set of successful runs against
+ // `PROPTEST_CASES` config.
+ let ok_type = self.run_one_with_replay(
+ case,
+ f,
+ replay_from_fork,
+ result_cache,
+ fork_output,
+ is_from_persisted_seed,
+ )?;
+ match ok_type {
+ TestCaseOk::NewCaseSuccess | TestCaseOk::ReplayFromForkSuccess => {
+ self.successes += 1
+ }
+ TestCaseOk::PersistedCaseSuccess
+ | TestCaseOk::CacheHitSuccess
+ | TestCaseOk::Reject => (),
+ }
+
+ Ok(())
+ }
+
+ /// Run one specific test case against this runner.
+ ///
+ /// If the test fails, finds the minimal failing test case. If the test
+ /// does not fail, returns whether it succeeded or was filtered out.
+ ///
+ /// This does not honour the `fork` config, and will not be able to
+ /// terminate the run if it runs for longer than `timeout`. However, if the
+ /// test function returns but took longer than `timeout`, the test case
+ /// will fail.
+ pub fn run_one<V: ValueTree>(
+ &mut self,
+ case: V,
+ test: impl Fn(V::Value) -> TestCaseResult,
+ ) -> Result<bool, TestError<V::Value>> {
+ let mut result_cache = self.new_cache();
+ self.run_one_with_replay(
+ case,
+ test,
+ &mut iter::empty::<TestCaseResult>().fuse(),
+ &mut *result_cache,
+ &mut ForkOutput::empty(),
+ false,
+ )
+ .map(|ok_type| match ok_type {
+ TestCaseOk::Reject => false,
+ _ => true,
+ })
+ }
+
+ fn run_one_with_replay<V: ValueTree>(
+ &mut self,
+ mut case: V,
+ test: impl Fn(V::Value) -> TestCaseResult,
+ replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
+ result_cache: &mut dyn ResultCache,
+ fork_output: &mut ForkOutput,
+ is_from_persisted_seed: bool,
+ ) -> Result<TestCaseOk, TestError<V::Value>> {
+ let result = call_test(
+ self,
+ case.current(),
+ &test,
+ replay_from_fork,
+ result_cache,
+ fork_output,
+ is_from_persisted_seed,
+ );
+
+ match result {
+ Ok(success_type) => Ok(success_type),
+ Err(TestCaseError::Fail(why)) => {
+ let why = self
+ .shrink(
+ &mut case,
+ test,
+ replay_from_fork,
+ result_cache,
+ fork_output,
+ is_from_persisted_seed,
+ )
+ .unwrap_or(why);
+ Err(TestError::Fail(why, case.current()))
+ }
+ Err(TestCaseError::Reject(whence)) => {
+ self.reject_global(whence)?;
+ Ok(TestCaseOk::Reject)
+ }
+ }
+ }
+
+ fn shrink<V: ValueTree>(
+ &mut self,
+ case: &mut V,
+ test: impl Fn(V::Value) -> TestCaseResult,
+ replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
+ result_cache: &mut dyn ResultCache,
+ fork_output: &mut ForkOutput,
+ is_from_persisted_seed: bool,
+ ) -> Option<Reason> {
+ #[cfg(feature = "std")]
+ use std::time;
+
+ let mut last_failure = None;
+ let mut iterations = 0;
+ #[cfg(feature = "std")]
+ let start_time = time::Instant::now();
+
+ if case.simplify() {
+ loop {
+ #[cfg(feature = "std")]
+ let timed_out = if self.config.max_shrink_time > 0 {
+ let elapsed = start_time.elapsed();
+ let elapsed_ms = elapsed
+ .as_secs()
+ .saturating_mul(1000)
+ .saturating_add(elapsed.subsec_millis().into());
+ if elapsed_ms > self.config.max_shrink_time as u64 {
+ Some(elapsed_ms)
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+ #[cfg(not(feature = "std"))]
+ let timed_out: Option<u64> = None;
+
+ let bail = if iterations >= self.config.max_shrink_iters() {
+ #[cfg(feature = "std")]
+ const CONTROLLER: &str =
+ "the PROPTEST_MAX_SHRINK_ITERS environment \
+ variable or ProptestConfig.max_shrink_iters";
+ #[cfg(not(feature = "std"))]
+ const CONTROLLER: &str = "ProptestConfig.max_shrink_iters";
+ verbose_message!(
+ self,
+ ALWAYS,
+ "Aborting shrinking after {} iterations (set {} \
+ to a large(r) value to shrink more; current \
+ configuration: {} iterations)",
+ CONTROLLER,
+ self.config.max_shrink_iters(),
+ iterations
+ );
+ true
+ } else if let Some(ms) = timed_out {
+ #[cfg(feature = "std")]
+ const CONTROLLER: &str =
+ "the PROPTEST_MAX_SHRINK_TIME environment \
+ variable or ProptestConfig.max_shrink_time";
+ #[cfg(feature = "std")]
+ let current = self.config.max_shrink_time;
+ #[cfg(not(feature = "std"))]
+ const CONTROLLER: &str = "(not configurable in no_std)";
+ #[cfg(not(feature = "std"))]
+ let current = 0;
+ verbose_message!(
+ self,
+ ALWAYS,
+ "Aborting shrinking after taking too long: {} ms \
+ (set {} to a large(r) value to shrink more; current \
+ configuration: {} ms)",
+ ms,
+ CONTROLLER,
+ current
+ );
+ true
+ } else {
+ false
+ };
+
+ if bail {
+ // Move back to the most recent failing case
+ while case.complicate() {
+ fork_output.append(&Ok(()));
+ }
+ break;
+ }
+
+ iterations += 1;
+
+ let result = call_test(
+ self,
+ case.current(),
+ &test,
+ replay_from_fork,
+ result_cache,
+ fork_output,
+ is_from_persisted_seed,
+ );
+
+ match result {
+ // Rejections are effectively a pass here,
+ // since they indicate that any behaviour of
+ // the function under test is acceptable.
+ Ok(_) | Err(TestCaseError::Reject(..)) => {
+ if !case.complicate() {
+ break;
+ }
+ }
+ Err(TestCaseError::Fail(why)) => {
+ last_failure = Some(why);
+ if !case.simplify() {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ last_failure
+ }
+
+ /// Update the state to account for a local rejection from `whence`, and
+ /// return `Ok` if the caller should keep going or `Err` to abort.
+ pub fn reject_local(
+ &mut self,
+ whence: impl Into<Reason>,
+ ) -> Result<(), Reason> {
+ if self.local_rejects >= self.config.max_local_rejects {
+ Err("Too many local rejects".into())
+ } else {
+ self.local_rejects += 1;
+ Self::insert_or_increment(
+ &mut self.local_reject_detail,
+ whence.into(),
+ );
+ Ok(())
+ }
+ }
+
+ /// Update the state to account for a global rejection from `whence`, and
+ /// return `Ok` if the caller should keep going or `Err` to abort.
+ fn reject_global<T>(&mut self, whence: Reason) -> Result<(), TestError<T>> {
+ if self.global_rejects >= self.config.max_global_rejects {
+ Err(TestError::Abort("Too many global rejects".into()))
+ } else {
+ self.global_rejects += 1;
+ Self::insert_or_increment(&mut self.global_reject_detail, whence);
+ Ok(())
+ }
+ }
+
+ /// Insert 1 or increment the rejection detail at key for whence.
+ fn insert_or_increment(into: &mut RejectionDetail, whence: Reason) {
+ into.entry(whence)
+ .and_modify(|count| *count += 1)
+ .or_insert(1);
+ }
+
+ /// Increment the counter of flat map regenerations and return whether it
+ /// is still under the configured limit.
+ pub fn flat_map_regen(&self) -> bool {
+ self.flat_map_regens.fetch_add(1, SeqCst)
+ < self.config.max_flat_map_regens as usize
+ }
+
+ fn new_cache(&self) -> Box<dyn ResultCache> {
+ (self.config.result_cache)()
+ }
+}
+
+#[cfg(feature = "fork")]
+fn init_replay(rng: &mut TestRng) -> (Vec<TestCaseResult>, ForkOutput) {
+ use crate::test_runner::replay::{open_file, Replay, ReplayFileStatus::*};
+
+ if let Some(path) = env::var_os(ENV_FORK_FILE) {
+ let mut file = open_file(&path).expect("Failed to open replay file");
+ let loaded =
+ Replay::parse_from(&mut file).expect("Failed to read replay file");
+ match loaded {
+ InProgress(replay) => {
+ rng.set_seed(replay.seed);
+ (replay.steps, ForkOutput { file: Some(file) })
+ }
+
+ Terminated(_) => {
+ panic!("Replay file for child process is terminated?")
+ }
+
+ Corrupt => panic!("Replay file for child process is corrupt"),
+ }
+ } else {
+ (vec![], ForkOutput::empty())
+ }
+}
+
+#[cfg(not(feature = "fork"))]
+fn init_replay(
+ _rng: &mut TestRng,
+) -> (iter::Empty<TestCaseResult>, ForkOutput) {
+ (iter::empty(), ForkOutput::empty())
+}
+
+#[cfg(feature = "fork")]
+fn await_child_without_timeout(
+ child: &mut rusty_fork::ChildWrapper,
+) -> (Option<TestCaseError>, Option<u64>) {
+ let status = child.wait().expect("Failed to wait for child process");
+
+ if status.success() {
+ (None, None)
+ } else {
+ (
+ Some(TestCaseError::fail(format!(
+ "Child process exited with {}",
+ status
+ ))),
+ None,
+ )
+ }
+}
+
+#[cfg(all(feature = "fork", not(feature = "timeout")))]
+fn await_child(
+ child: &mut rusty_fork::ChildWrapper,
+ _: &mut tempfile::NamedTempFile,
+ _timeout: u32,
+) -> (Option<TestCaseError>, Option<u64>) {
+ await_child_without_timeout(child)
+}
+
+#[cfg(all(feature = "fork", feature = "timeout"))]
+fn await_child(
+ child: &mut rusty_fork::ChildWrapper,
+ forkfile: &mut tempfile::NamedTempFile,
+ timeout: u32,
+) -> (Option<TestCaseError>, Option<u64>) {
+ use std::time::Duration;
+
+ if 0 == timeout {
+ return await_child_without_timeout(child);
+ }
+
+ // The child can run for longer than the timeout since it may run
+ // multiple tests. Each time the timeout expires, we check whether the
+ // file has grown larger. If it has, we allow the child to keep running
+ // until the next timeout.
+ let mut last_forkfile_len = forkfile
+ .as_file()
+ .metadata()
+ .map(|md| md.len())
+ .unwrap_or(0);
+
+ loop {
+ if let Some(status) = child
+ .wait_timeout(Duration::from_millis(timeout.into()))
+ .expect("Failed to wait for child process")
+ {
+ if status.success() {
+ return (None, None);
+ } else {
+ return (
+ Some(TestCaseError::fail(format!(
+ "Child process exited with {}",
+ status
+ ))),
+ None,
+ );
+ }
+ }
+
+ let current_len = forkfile
+ .as_file()
+ .metadata()
+ .map(|md| md.len())
+ .unwrap_or(0);
+ // If we've gone a full timeout period without the file growing,
+ // fail the test and kill the child.
+ if current_len <= last_forkfile_len {
+ return (
+ Some(TestCaseError::fail(format!(
+ "Timed out waiting for child process"
+ ))),
+ Some(current_len),
+ );
+ } else {
+ last_forkfile_len = current_len;
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::cell::Cell;
+ use std::fs;
+
+ use super::*;
+ use crate::strategy::Strategy;
+ use crate::test_runner::{FileFailurePersistence, RngAlgorithm, TestRng};
+
+ #[test]
+ fn gives_up_after_too_many_rejections() {
+ let config = Config::default();
+ let mut runner = TestRunner::new(config.clone());
+ let runs = Cell::new(0);
+ let result = runner.run(&(0u32..), |_| {
+ runs.set(runs.get() + 1);
+ Err(TestCaseError::reject("reject"))
+ });
+ match result {
+ Err(TestError::Abort(_)) => (),
+ e => panic!("Unexpected result: {:?}", e),
+ }
+ assert_eq!(config.max_global_rejects + 1, runs.get());
+ }
+
+ #[test]
+ fn test_pass() {
+ let mut runner = TestRunner::default();
+ let result = runner.run(&(1u32..), |v| {
+ assert!(v > 0);
+ Ok(())
+ });
+ assert_eq!(Ok(()), result);
+ }
+
+ #[test]
+ fn test_fail_via_result() {
+ let mut runner = TestRunner::new(Config {
+ failure_persistence: None,
+ ..Config::default()
+ });
+ let result = runner.run(&(0u32..10u32), |v| {
+ if v < 5 {
+ Ok(())
+ } else {
+ Err(TestCaseError::fail("not less than 5"))
+ }
+ });
+
+ assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
+ }
+
+ #[test]
+ fn test_fail_via_panic() {
+ let mut runner = TestRunner::new(Config {
+ failure_persistence: None,
+ ..Config::default()
+ });
+ let result = runner.run(&(0u32..10u32), |v| {
+ assert!(v < 5, "not less than 5");
+ Ok(())
+ });
+ assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
+ }
+
+ #[test]
+ fn persisted_cases_do_not_count_towards_total_cases() {
+ const FILE: &'static str = "persistence-test.txt";
+ let _ = fs::remove_file(FILE);
+
+ let config = Config {
+ failure_persistence: Some(Box::new(
+ FileFailurePersistence::Direct(FILE),
+ )),
+ cases: 1,
+ ..Config::default()
+ };
+
+ let max = 10_000_000i32;
+ {
+ TestRunner::new(config.clone())
+ .run(&(0i32..max), |_v| {
+ Err(TestCaseError::Fail("persist a failure".into()))
+ })
+ .expect_err("didn't fail?");
+ }
+
+ let run_count = RefCell::new(0);
+ TestRunner::new(config.clone())
+ .run(&(0i32..max), |_v| {
+ *run_count.borrow_mut() += 1;
+ Ok(())
+ })
+ .expect("should succeed");
+
+ // Persisted ran, and a new case ran, and only new case counts
+ // against `cases: 1`.
+ assert_eq!(run_count.into_inner(), 2);
+ }
+
+ #[derive(Clone, Copy, PartialEq)]
+ struct PoorlyBehavedDebug(i32);
+ impl fmt::Debug for PoorlyBehavedDebug {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "\r\n{:?}\r\n", self.0)
+ }
+ }
+
+ #[test]
+ fn failing_cases_persisted_and_reloaded() {
+ const FILE: &'static str = "persistence-test.txt";
+ let _ = fs::remove_file(FILE);
+
+ let max = 10_000_000i32;
+ let input = (0i32..max).prop_map(PoorlyBehavedDebug);
+ let config = Config {
+ failure_persistence: Some(Box::new(
+ FileFailurePersistence::Direct(FILE),
+ )),
+ ..Config::default()
+ };
+
+ // First test with cases that fail above half max, and then below half
+ // max, to ensure we can correctly parse both lines of the persistence
+ // file.
+ let first_sub_failure = {
+ TestRunner::new(config.clone())
+ .run(&input, |v| {
+ if v.0 < max / 2 {
+ Ok(())
+ } else {
+ Err(TestCaseError::Fail("too big".into()))
+ }
+ })
+ .expect_err("didn't fail?")
+ };
+ let first_super_failure = {
+ TestRunner::new(config.clone())
+ .run(&input, |v| {
+ if v.0 >= max / 2 {
+ Ok(())
+ } else {
+ Err(TestCaseError::Fail("too small".into()))
+ }
+ })
+ .expect_err("didn't fail?")
+ };
+ let second_sub_failure = {
+ TestRunner::new(config.clone())
+ .run(&input, |v| {
+ if v.0 < max / 2 {
+ Ok(())
+ } else {
+ Err(TestCaseError::Fail("too big".into()))
+ }
+ })
+ .expect_err("didn't fail?")
+ };
+ let second_super_failure = {
+ TestRunner::new(config.clone())
+ .run(&input, |v| {
+ if v.0 >= max / 2 {
+ Ok(())
+ } else {
+ Err(TestCaseError::Fail("too small".into()))
+ }
+ })
+ .expect_err("didn't fail?")
+ };
+
+ assert_eq!(first_sub_failure, second_sub_failure);
+ assert_eq!(first_super_failure, second_super_failure);
+ }
+
+ #[test]
+ fn new_rng_makes_separate_rng() {
+ use rand::Rng;
+ let mut runner = TestRunner::default();
+ let from_1 = runner.new_rng().gen::<[u8; 16]>();
+ let from_2 = runner.rng().gen::<[u8; 16]>();
+ assert_ne!(from_1, from_2);
+ }
+
+ #[test]
+ fn record_rng_use() {
+ use rand::Rng;
+
+ // create value with recorder rng
+ let default_config = Config::default();
+ let recorder_rng = TestRng::default_rng(RngAlgorithm::Recorder);
+ let mut runner =
+ TestRunner::new_with_rng(default_config.clone(), recorder_rng);
+ let random_byte_array1 = runner.rng().gen::<[u8; 16]>();
+ let bytes_used = runner.bytes_used();
+ assert!(bytes_used.len() >= 16); // could use more bytes for some reason
+
+ // re-create value with pass-through rng
+ let passthrough_rng =
+ TestRng::from_seed(RngAlgorithm::PassThrough, &bytes_used);
+ let mut runner =
+ TestRunner::new_with_rng(default_config, passthrough_rng);
+ let random_byte_array2 = runner.rng().gen::<[u8; 16]>();
+
+ // make sure the same value was created
+ assert_eq!(random_byte_array1, random_byte_array2);
+ }
+
+ #[cfg(feature = "fork")]
+ #[test]
+ fn run_successful_test_in_fork() {
+ let mut runner = TestRunner::new(Config {
+ fork: true,
+ test_name: Some(concat!(
+ module_path!(),
+ "::run_successful_test_in_fork"
+ )),
+ ..Config::default()
+ });
+
+ assert!(runner.run(&(0u32..1000), |_| Ok(())).is_ok());
+ }
+
+ #[cfg(feature = "fork")]
+ #[test]
+ fn normal_failure_in_fork_results_in_correct_failure() {
+ let mut runner = TestRunner::new(Config {
+ fork: true,
+ test_name: Some(concat!(
+ module_path!(),
+ "::normal_failure_in_fork_results_in_correct_failure"
+ )),
+ ..Config::default()
+ });
+
+ let failure = runner
+ .run(&(0u32..1000), |v| {
+ prop_assert!(v < 500);
+ Ok(())
+ })
+ .err()
+ .unwrap();
+
+ match failure {
+ TestError::Fail(_, value) => assert_eq!(500, value),
+ failure => panic!("Unexpected failure: {:?}", failure),
+ }
+ }
+
+ #[cfg(feature = "fork")]
+ #[test]
+ fn nonsuccessful_exit_finds_correct_failure() {
+ let mut runner = TestRunner::new(Config {
+ fork: true,
+ test_name: Some(concat!(
+ module_path!(),
+ "::nonsuccessful_exit_finds_correct_failure"
+ )),
+ ..Config::default()
+ });
+
+ let failure = runner
+ .run(&(0u32..1000), |v| {
+ if v >= 500 {
+ ::std::process::exit(1);
+ }
+ Ok(())
+ })
+ .err()
+ .unwrap();
+
+ match failure {
+ TestError::Fail(_, value) => assert_eq!(500, value),
+ failure => panic!("Unexpected failure: {:?}", failure),
+ }
+ }
+
+ #[cfg(feature = "fork")]
+ #[test]
+ fn spurious_exit_finds_correct_failure() {
+ let mut runner = TestRunner::new(Config {
+ fork: true,
+ test_name: Some(concat!(
+ module_path!(),
+ "::spurious_exit_finds_correct_failure"
+ )),
+ ..Config::default()
+ });
+
+ let failure = runner
+ .run(&(0u32..1000), |v| {
+ if v >= 500 {
+ ::std::process::exit(0);
+ }
+ Ok(())
+ })
+ .err()
+ .unwrap();
+
+ match failure {
+ TestError::Fail(_, value) => assert_eq!(500, value),
+ failure => panic!("Unexpected failure: {:?}", failure),
+ }
+ }
+
+ #[cfg(feature = "timeout")]
+ #[test]
+ fn long_sleep_timeout_finds_correct_failure() {
+ let mut runner = TestRunner::new(Config {
+ fork: true,
+ timeout: 500,
+ test_name: Some(concat!(
+ module_path!(),
+ "::long_sleep_timeout_finds_correct_failure"
+ )),
+ ..Config::default()
+ });
+
+ let failure = runner
+ .run(&(0u32..1000), |v| {
+ if v >= 500 {
+ ::std::thread::sleep(::std::time::Duration::from_millis(
+ 10_000,
+ ));
+ }
+ Ok(())
+ })
+ .err()
+ .unwrap();
+
+ match failure {
+ TestError::Fail(_, value) => assert_eq!(500, value),
+ failure => panic!("Unexpected failure: {:?}", failure),
+ }
+ }
+
+ #[cfg(feature = "timeout")]
+ #[test]
+ fn mid_sleep_timeout_finds_correct_failure() {
+ let mut runner = TestRunner::new(Config {
+ fork: true,
+ timeout: 500,
+ test_name: Some(concat!(
+ module_path!(),
+ "::mid_sleep_timeout_finds_correct_failure"
+ )),
+ ..Config::default()
+ });
+
+ let failure = runner
+ .run(&(0u32..1000), |v| {
+ if v >= 500 {
+ // Sleep a little longer than the timeout. This means that
+ // sometimes the test case itself will return before the parent
+ // process has noticed the child is timing out, so it's up to
+ // the child to mark it as a failure.
+ ::std::thread::sleep(::std::time::Duration::from_millis(
+ 600,
+ ));
+ } else {
+ // Sleep a bit so that the parent and child timing don't stay
+ // in sync.
+ ::std::thread::sleep(::std::time::Duration::from_millis(
+ 100,
+ ))
+ }
+ Ok(())
+ })
+ .err()
+ .unwrap();
+
+ match failure {
+ TestError::Fail(_, value) => assert_eq!(500, value),
+ failure => panic!("Unexpected failure: {:?}", failure),
+ }
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn duplicate_tests_not_run_with_basic_result_cache() {
+ use std::cell::{Cell, RefCell};
+ use std::collections::HashSet;
+ use std::rc::Rc;
+
+ for _ in 0..256 {
+ let mut runner = TestRunner::new(Config {
+ failure_persistence: None,
+ result_cache:
+ crate::test_runner::result_cache::basic_result_cache,
+ ..Config::default()
+ });
+ let pass = Rc::new(Cell::new(true));
+ let seen = Rc::new(RefCell::new(HashSet::new()));
+ let result =
+ runner.run(&(0u32..65536u32).prop_map(|v| v % 10), |val| {
+ if !seen.borrow_mut().insert(val) {
+ println!("Value {} seen more than once", val);
+ pass.set(false);
+ }
+
+ prop_assert!(val <= 5);
+ Ok(())
+ });
+
+ assert!(pass.get());
+ if let Err(TestError::Fail(_, val)) = result {
+ assert_eq!(6, val);
+ } else {
+ panic!("Incorrect result: {:?}", result);
+ }
+ }
+ }
+}
+
+#[cfg(all(feature = "fork", feature = "timeout", test))]
+mod timeout_tests {
+ use core::u32;
+ use std::thread;
+ use std::time::Duration;
+
+ use super::*;
+
+ rusty_fork_test! {
+ #![rusty_fork(timeout_ms = 4_000)]
+
+ #[test]
+ fn max_shrink_iters_works() {
+ test_shrink_bail(Config {
+ max_shrink_iters: 5,
+ .. Config::default()
+ });
+ }
+
+ #[test]
+ fn max_shrink_time_works() {
+ test_shrink_bail(Config {
+ max_shrink_time: 1000,
+ .. Config::default()
+ });
+ }
+
+ #[test]
+ fn max_shrink_iters_works_with_forking() {
+ test_shrink_bail(Config {
+ fork: true,
+ test_name: Some(
+ concat!(module_path!(),
+ "::max_shrink_iters_works_with_forking")),
+ max_shrink_time: 1000,
+ .. Config::default()
+ });
+ }
+
+ #[test]
+ fn detects_child_failure_to_start() {
+ let mut runner = TestRunner::new(Config {
+ timeout: 100,
+ test_name: Some(
+ concat!(module_path!(),
+ "::detects_child_failure_to_start")),
+ .. Config::default()
+ });
+ let result = runner.run(&Just(()).prop_map(|()| {
+ thread::sleep(Duration::from_millis(200))
+ }), Ok);
+
+ if let Err(TestError::Abort(_)) = result {
+ // OK
+ } else {
+ panic!("Unexpected result: {:?}", result);
+ }
+ }
+ }
+
+ fn test_shrink_bail(config: Config) {
+ let mut runner = TestRunner::new(config);
+ let result = runner.run(&crate::num::u64::ANY, |v| {
+ thread::sleep(Duration::from_millis(250));
+ prop_assert!(v <= u32::MAX as u64);
+ Ok(())
+ });
+
+ if let Err(TestError::Fail(_, value)) = result {
+ // Ensure the final value was in fact a failing case.
+ assert!(value > u32::MAX as u64);
+ } else {
+ panic!("Unexpected result: {:?}", result);
+ }
+ }
+}