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}