proptest/test_runner/
config.rs

1//-
2// Copyright 2017, 2018, 2019 The proptest developers
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use crate::std_facade::Box;
11use core::u32;
12
13use crate::test_runner::result_cache::{noop_result_cache, ResultCache};
14use crate::test_runner::rng::RngAlgorithm;
15use crate::test_runner::FailurePersistence;
16
17/// Override the config fields from environment variables, if any are set.
18/// Without the `std` feature this function returns config unchanged.
19#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
20pub fn contextualize_config(mut result: Config) -> Config {
21    use std::env;
22    use std::ffi::OsString;
23    use std::fmt;
24    use std::str::FromStr;
25
26    const CASES: &str = "PROPTEST_CASES";
27
28    const MAX_LOCAL_REJECTS: &str = "PROPTEST_MAX_LOCAL_REJECTS";
29    const MAX_GLOBAL_REJECTS: &str = "PROPTEST_MAX_GLOBAL_REJECTS";
30    const MAX_FLAT_MAP_REGENS: &str = "PROPTEST_MAX_FLAT_MAP_REGENS";
31    const MAX_SHRINK_TIME: &str = "PROPTEST_MAX_SHRINK_TIME";
32    const MAX_SHRINK_ITERS: &str = "PROPTEST_MAX_SHRINK_ITERS";
33    const MAX_DEFAULT_SIZE_RANGE: &str = "PROPTEST_MAX_DEFAULT_SIZE_RANGE";
34    #[cfg(feature = "fork")]
35    const FORK: &str = "PROPTEST_FORK";
36    #[cfg(feature = "timeout")]
37    const TIMEOUT: &str = "PROPTEST_TIMEOUT";
38    const VERBOSE: &str = "PROPTEST_VERBOSE";
39    const RNG_ALGORITHM: &str = "PROPTEST_RNG_ALGORITHM";
40    const DISABLE_FAILURE_PERSISTENCE: &str =
41        "PROPTEST_DISABLE_FAILURE_PERSISTENCE";
42
43    fn parse_or_warn<T: FromStr + fmt::Display>(
44        src: &OsString,
45        dst: &mut T,
46        typ: &str,
47        var: &str,
48    ) {
49        if let Some(src) = src.to_str() {
50            if let Ok(value) = src.parse() {
51                *dst = value;
52            } else {
53                eprintln!(
54                    "proptest: The env-var {}={} can't be parsed as {}, \
55                     using default of {}.",
56                    var, src, typ, *dst
57                );
58            }
59        } else {
60            eprintln!(
61                "proptest: The env-var {} is not valid, using \
62                 default of {}.",
63                var, *dst
64            );
65        }
66    }
67
68    for (var, value) in
69        env::vars_os().filter_map(|(k, v)| k.into_string().ok().map(|k| (k, v)))
70    {
71        let var = var.as_str();
72
73        #[cfg(feature = "fork")]
74        if var == FORK {
75            parse_or_warn(&value, &mut result.fork, "bool", FORK);
76            continue;
77        }
78
79        #[cfg(feature = "timeout")]
80        if var == TIMEOUT {
81            parse_or_warn(&value, &mut result.timeout, "timeout", TIMEOUT);
82            continue;
83        }
84
85        if var == CASES {
86            parse_or_warn(&value, &mut result.cases, "u32", CASES);
87        } else if var == MAX_LOCAL_REJECTS {
88            parse_or_warn(
89                &value,
90                &mut result.max_local_rejects,
91                "u32",
92                MAX_LOCAL_REJECTS,
93            );
94        } else if var == MAX_GLOBAL_REJECTS {
95            parse_or_warn(
96                &value,
97                &mut result.max_global_rejects,
98                "u32",
99                MAX_GLOBAL_REJECTS,
100            );
101        } else if var == MAX_FLAT_MAP_REGENS {
102            parse_or_warn(
103                &value,
104                &mut result.max_flat_map_regens,
105                "u32",
106                MAX_FLAT_MAP_REGENS,
107            );
108        } else if var == MAX_SHRINK_TIME {
109            parse_or_warn(
110                &value,
111                &mut result.max_shrink_time,
112                "u32",
113                MAX_SHRINK_TIME,
114            );
115        } else if var == MAX_SHRINK_ITERS {
116            parse_or_warn(
117                &value,
118                &mut result.max_shrink_iters,
119                "u32",
120                MAX_SHRINK_ITERS,
121            );
122        } else if var == MAX_DEFAULT_SIZE_RANGE {
123            parse_or_warn(
124                &value,
125                &mut result.max_default_size_range,
126                "usize",
127                MAX_DEFAULT_SIZE_RANGE,
128            );
129        } else if var == VERBOSE {
130            parse_or_warn(&value, &mut result.verbose, "u32", VERBOSE);
131        } else if var == RNG_ALGORITHM {
132            parse_or_warn(
133                &value,
134                &mut result.rng_algorithm,
135                "RngAlgorithm",
136                RNG_ALGORITHM,
137            );
138        } else if var == DISABLE_FAILURE_PERSISTENCE {
139            result.failure_persistence = None;
140        } else if var.starts_with("PROPTEST_") {
141            eprintln!("proptest: Ignoring unknown env-var {}.", var);
142        }
143    }
144
145    result
146}
147
148/// Without the `std` feature this function returns config unchanged.
149#[cfg(not(all(feature = "std", not(target_arch = "wasm32"))))]
150pub fn contextualize_config(result: Config) -> Config {
151    result
152}
153
154fn default_default_config() -> Config {
155    Config {
156        cases: 256,
157        max_local_rejects: 65_536,
158        max_global_rejects: 1024,
159        max_flat_map_regens: 1_000_000,
160        failure_persistence: None,
161        source_file: None,
162        test_name: None,
163        #[cfg(feature = "fork")]
164        fork: false,
165        #[cfg(feature = "timeout")]
166        timeout: 0,
167        #[cfg(feature = "std")]
168        max_shrink_time: 0,
169        max_shrink_iters: u32::MAX,
170        max_default_size_range: 100,
171        result_cache: noop_result_cache,
172        #[cfg(feature = "std")]
173        verbose: 0,
174        rng_algorithm: RngAlgorithm::default(),
175        _non_exhaustive: (),
176    }
177}
178
179// The default config, computed by combining environment variables and
180// defaults.
181#[cfg(feature = "std")]
182lazy_static! {
183    static ref DEFAULT_CONFIG: Config = {
184        let mut default_config = default_default_config();
185        default_config.failure_persistence = Some(Box::new(crate::test_runner::FileFailurePersistence::default()));
186        contextualize_config(default_config)
187    };
188}
189
190/// Configuration for how a proptest test should be run.
191#[derive(Clone, Debug, PartialEq)]
192pub struct Config {
193    /// The number of successful test cases that must execute for the test as a
194    /// whole to pass.
195    ///
196    /// This does not include implicitly-replayed persisted failing cases.
197    ///
198    /// The default is 256, which can be overridden by setting the
199    /// `PROPTEST_CASES` environment variable. (The variable is only considered
200    /// when the `std` feature is enabled, which it is by default.)
201    pub cases: u32,
202
203    /// The maximum number of individual inputs that may be rejected before the
204    /// test as a whole aborts.
205    ///
206    /// The default is 65536, which can be overridden by setting the
207    /// `PROPTEST_MAX_LOCAL_REJECTS` environment variable. (The variable is only
208    /// considered when the `std` feature is enabled, which it is by default.)
209    pub max_local_rejects: u32,
210
211    /// The maximum number of combined inputs that may be rejected before the
212    /// test as a whole aborts.
213    ///
214    /// The default is 1024, which can be overridden by setting the
215    /// `PROPTEST_MAX_GLOBAL_REJECTS` environment variable. (The variable is
216    /// only considered when the `std` feature is enabled, which it is by
217    /// default.)
218    pub max_global_rejects: u32,
219
220    /// The maximum number of times all `Flatten` combinators will attempt to
221    /// regenerate values. This puts a limit on the worst-case exponential
222    /// explosion that can happen with nested `Flatten`s.
223    ///
224    /// The default is 1_000_000, which can be overridden by setting the
225    /// `PROPTEST_MAX_FLAT_MAP_REGENS` environment variable. (The variable is
226    /// only considered when the `std` feature is enabled, which it is by
227    /// default.)
228    pub max_flat_map_regens: u32,
229
230    /// Indicates whether and how to persist failed test results.
231    ///
232    /// When compiling with "std" feature (i.e. the standard library is available), the default
233    /// is `Some(Box::new(FileFailurePersistence::SourceParallel("proptest-regressions")))`.
234    ///
235    /// Without the standard library, the default is `None`, and no persistence occurs.
236    ///
237    /// See the docs of [`FileFailurePersistence`](enum.FileFailurePersistence.html)
238    /// and [`MapFailurePersistence`](struct.MapFailurePersistence.html) for more information.
239    ///
240    /// You can disable failure persistence with the `PROPTEST_DISABLE_FAILURE_PERSISTENCE`
241    /// environment variable but its not currently possible to set the persistence file
242    /// with an environment variable. (The variable is
243    /// only considered when the `std` feature is enabled, which it is by
244    /// default.)
245    pub failure_persistence: Option<Box<dyn FailurePersistence>>,
246
247    /// File location of the current test, relevant for persistence
248    /// and debugging.
249    ///
250    /// Note the use of `&str` rather than `Path` to be compatible with
251    /// `#![no_std]` use cases where `Path` is unavailable.
252    ///
253    /// See the docs of [`FileFailurePersistence`](enum.FileFailurePersistence.html)
254    /// for more information on how it may be used for persistence.
255    pub source_file: Option<&'static str>,
256
257    /// The fully-qualified name of the test being run, as would be passed to
258    /// the test executable to run just that test.
259    ///
260    /// This must be set if `fork` is `true`. Otherwise, it is unused. It is
261    /// automatically set by `proptest!`.
262    ///
263    /// This must include the crate name at the beginning, as produced by
264    /// `module_path!()`.
265    pub test_name: Option<&'static str>,
266
267    /// If true, tests are run in a subprocess.
268    ///
269    /// Forking allows proptest to work with tests which may fail by aborting
270    /// the process, causing a segmentation fault, etc, but can be a lot slower
271    /// in certain environments or when running a very large number of tests.
272    ///
273    /// For forking to work correctly, both the `Strategy` and the content of
274    /// the test case itself must be deterministic.
275    ///
276    /// This requires the "fork" feature, enabled by default.
277    ///
278    /// The default is `false`, which can be overridden by setting the
279    /// `PROPTEST_FORK` environment variable. (The variable is
280    /// only considered when the `std` feature is enabled, which it is by
281    /// default.)
282    #[cfg(feature = "fork")]
283    #[cfg_attr(docsrs, doc(cfg(feature = "fork")))]
284    pub fork: bool,
285
286    /// If non-zero, tests are run in a subprocess and each generated case
287    /// fails if it takes longer than this number of milliseconds.
288    ///
289    /// This implicitly enables forking, even if the `fork` field is `false`.
290    ///
291    /// The type here is plain `u32` (rather than
292    /// `Option<std::time::Duration>`) for the sake of ergonomics.
293    ///
294    /// This requires the "timeout" feature, enabled by default.
295    ///
296    /// Setting a timeout to less than the time it takes the process to start
297    /// up and initialise the first test case will cause the whole test to be
298    /// aborted.
299    ///
300    /// The default is `0` (i.e., no timeout), which can be overridden by
301    /// setting the `PROPTEST_TIMEOUT` environment variable. (The variable is
302    /// only considered when the `std` feature is enabled, which it is by
303    /// default.)
304    #[cfg(feature = "timeout")]
305    #[cfg_attr(docsrs, doc(cfg(feature = "timeout")))]
306    pub timeout: u32,
307
308    /// If non-zero, give up the shrinking process after this many milliseconds
309    /// have elapsed since the start of the shrinking process.
310    ///
311    /// This will not cause currently running test cases to be interrupted.
312    ///
313    /// This configuration is only available when the `std` feature is enabled
314    /// (which it is by default).
315    ///
316    /// The default is `0` (i.e., no limit), which can be overridden by setting
317    /// the `PROPTEST_MAX_SHRINK_TIME` environment variable. (The variable is
318    /// only considered when the `std` feature is enabled, which it is by
319    /// default.)
320    #[cfg(feature = "std")]
321    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
322    pub max_shrink_time: u32,
323
324    /// Give up on shrinking if more than this number of iterations of the test
325    /// code are run.
326    ///
327    /// Setting this to `std::u32::MAX` causes the actual limit to be four
328    /// times the number of test cases.
329    ///
330    /// Setting this value to `0` disables shrinking altogether.
331    ///
332    /// Note that the type of this field will change in a future version of
333    /// proptest to better accommodate its special values.
334    ///
335    /// The default is `std::u32::MAX`, which can be overridden by setting the
336    /// `PROPTEST_MAX_SHRINK_ITERS` environment variable. (The variable is only
337    /// considered when the `std` feature is enabled, which it is by default.)
338    pub max_shrink_iters: u32,
339
340    /// The default maximum size to `proptest::collection::SizeRange`. The default
341    /// strategy for collections (like `Vec`) use collections in the range of
342    /// `0..max_default_size_range`.
343    ///
344    /// The default is `100` which can be overridden by setting the
345    /// `PROPTEST_MAX_DEFAULT_SIZE_RANGE` environment variable. (The variable
346    /// is only considered when the `std` feature is enabled, which it is by
347    /// default.)
348    pub max_default_size_range: usize,
349
350    /// A function to create new result caches.
351    ///
352    /// The default is to do no caching. The easiest way to enable caching is
353    /// to set this field to `basic_result_cache` (though that is currently
354    /// only available with the `std` feature).
355    ///
356    /// This is useful for strategies which have a tendency to produce
357    /// duplicate values, or for tests where shrinking can take a very long
358    /// time due to exploring the same output multiple times.
359    ///
360    /// When caching is enabled, generated values themselves are not stored, so
361    /// this does not pose a risk of memory exhaustion for large test inputs
362    /// unless using extraordinarily large test case counts.
363    ///
364    /// Caching incurs its own overhead, and may very well make your test run
365    /// more slowly.
366    pub result_cache: fn() -> Box<dyn ResultCache>,
367
368    /// Set to non-zero values to cause proptest to emit human-targeted
369    /// messages to stderr as it runs.
370    ///
371    /// Greater values cause greater amounts of logs to be emitted. The exact
372    /// meaning of certain levels other than 0 is subject to change.
373    ///
374    /// - 0: No extra output.
375    /// - 1: Log test failure messages. In state machine tests, this level is
376    ///   used to print transitions.
377    /// - 2: Trace low-level details.
378    ///
379    /// This is only available with the `std` feature (enabled by default)
380    /// since on nostd proptest has no way to produce output.
381    ///
382    /// The default is `0`, which can be overridden by setting the
383    /// `PROPTEST_VERBOSE` environment variable. (The variable is only considered
384    /// when the `std` feature is enabled, which it is by default.)
385    #[cfg(feature = "std")]
386    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
387    pub verbose: u32,
388
389    /// The RNG algorithm to use when not using a user-provided RNG.
390    ///
391    /// The default is `RngAlgorithm::default()`, which can be overridden by
392    /// setting the `PROPTEST_RNG_ALGORITHM` environment variable to one of the following:
393    ///
394    /// - `xs` — `RngAlgorithm::XorShift`
395    /// - `cc` — `RngAlgorithm::ChaCha`
396    ///
397    /// (The variable is only considered when the `std` feature is enabled,
398    /// which it is by default.)
399    pub rng_algorithm: RngAlgorithm,
400
401    // Needs to be public so FRU syntax can be used.
402    #[doc(hidden)]
403    pub _non_exhaustive: (),
404}
405
406impl Config {
407    /// Constructs a `Config` only differing from the `default()` in the
408    /// number of test cases required to pass the test successfully.
409    ///
410    /// This is simply a more concise alternative to using field-record update
411    /// syntax:
412    ///
413    /// ```
414    /// # use proptest::test_runner::Config;
415    /// assert_eq!(
416    ///     Config::with_cases(42),
417    ///     Config { cases: 42, .. Config::default() }
418    /// );
419    /// ```
420    pub fn with_cases(cases: u32) -> Self {
421        Self {
422            cases,
423            ..Config::default()
424        }
425    }
426
427    /// Constructs a `Config` only differing from the `default()` in the
428    /// source_file of the present test.
429    ///
430    /// This is simply a more concise alternative to using field-record update
431    /// syntax:
432    ///
433    /// ```
434    /// # use proptest::test_runner::Config;
435    /// assert_eq!(
436    ///     Config::with_source_file("computer/question"),
437    ///     Config { source_file: Some("computer/question"), .. Config::default() }
438    /// );
439    /// ```
440    pub fn with_source_file(source_file: &'static str) -> Self {
441        Self {
442            source_file: Some(source_file),
443            ..Config::default()
444        }
445    }
446
447    /// Constructs a `Config` only differing from the provided Config instance, `self`,
448    /// in the source_file of the present test.
449    ///
450    /// This is simply a more concise alternative to using field-record update
451    /// syntax:
452    ///
453    /// ```
454    /// # use proptest::test_runner::Config;
455    /// let a = Config::with_source_file("computer/question");
456    /// let b = a.clone_with_source_file("answer/42");
457    /// assert_eq!(
458    ///     a,
459    ///     Config { source_file: Some("computer/question"), .. Config::default() }
460    /// );
461    /// assert_eq!(
462    ///     b,
463    ///     Config { source_file: Some("answer/42"), .. Config::default() }
464    /// );
465    /// ```
466    pub fn clone_with_source_file(&self, source_file: &'static str) -> Self {
467        let mut result = self.clone();
468        result.source_file = Some(source_file);
469        result
470    }
471
472    /// Constructs a `Config` only differing from the `default()` in the
473    /// failure_persistence member.
474    ///
475    /// This is simply a more concise alternative to using field-record update
476    /// syntax:
477    ///
478    /// ```
479    /// # use proptest::test_runner::{Config, FileFailurePersistence};
480    /// assert_eq!(
481    ///     Config::with_failure_persistence(FileFailurePersistence::WithSource("regressions")),
482    ///     Config {
483    ///         failure_persistence: Some(Box::new(FileFailurePersistence::WithSource("regressions"))),
484    ///         .. Config::default()
485    ///     }
486    /// );
487    /// ```
488    pub fn with_failure_persistence<T>(failure_persistence: T) -> Self
489    where
490        T: FailurePersistence + 'static,
491    {
492        Self {
493            failure_persistence: Some(Box::new(failure_persistence)),
494            ..Default::default()
495        }
496    }
497
498    /// Return whether this configuration implies forking.
499    ///
500    /// This method exists even if the "fork" feature is disabled, in which
501    /// case it simply returns false.
502    pub fn fork(&self) -> bool {
503        self._fork() || self.timeout() > 0
504    }
505
506    #[cfg(feature = "fork")]
507    fn _fork(&self) -> bool {
508        self.fork
509    }
510
511    #[cfg(not(feature = "fork"))]
512    fn _fork(&self) -> bool {
513        false
514    }
515
516    /// Returns the configured timeout.
517    ///
518    /// This method exists even if the "timeout" feature is disabled, in which
519    /// case it simply returns 0.
520    #[cfg(feature = "timeout")]
521    pub fn timeout(&self) -> u32 {
522        self.timeout
523    }
524
525    /// Returns the configured timeout.
526    ///
527    /// This method exists even if the "timeout" feature is disabled, in which
528    /// case it simply returns 0.
529    #[cfg(not(feature = "timeout"))]
530    pub fn timeout(&self) -> u32 {
531        0
532    }
533
534    /// Returns the configured limit on shrinking iterations.
535    ///
536    /// This takes into account the special "automatic" behaviour.
537    pub fn max_shrink_iters(&self) -> u32 {
538        if u32::MAX == self.max_shrink_iters {
539            self.cases.saturating_mul(4)
540        } else {
541            self.max_shrink_iters
542        }
543    }
544
545    // Used by macros to force the config to be owned without depending on
546    // certain traits being `use`d.
547    #[allow(missing_docs)]
548    #[doc(hidden)]
549    pub fn __sugar_to_owned(&self) -> Self {
550        self.clone()
551    }
552}
553
554#[cfg(feature = "std")]
555impl Default for Config {
556    fn default() -> Self {
557        DEFAULT_CONFIG.clone()
558    }
559}
560
561#[cfg(not(feature = "std"))]
562impl Default for Config {
563    fn default() -> Self {
564        default_default_config()
565    }
566}