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