Skip to main content

proptest/test_runner/
runner.rs

1//-
2// Copyright 2017, 2018, 2019, 2024, 2026 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::{Arc, BTreeMap, Box, String, Vec};
11use core::sync::atomic::AtomicUsize;
12use core::sync::atomic::Ordering::SeqCst;
13use core::{fmt, iter};
14#[cfg(feature = "std")]
15use std::panic::{self, AssertUnwindSafe};
16
17#[cfg(feature = "fork")]
18use rusty_fork;
19#[cfg(feature = "fork")]
20use std::cell::{Cell, RefCell};
21#[cfg(feature = "fork")]
22use std::env;
23#[cfg(feature = "fork")]
24use std::fs;
25#[cfg(feature = "fork")]
26use tempfile;
27
28use crate::strategy::*;
29use crate::test_runner::config::*;
30use crate::test_runner::errors::*;
31use crate::test_runner::failure_persistence::PersistedSeed;
32use crate::test_runner::reason::*;
33#[cfg(feature = "fork")]
34use crate::test_runner::replay;
35use crate::test_runner::result_cache::*;
36use crate::test_runner::rng::TestRng;
37
38#[cfg(feature = "fork")]
39const ENV_FORK_FILE: &'static str = "_PROPTEST_FORKFILE";
40
41const ALWAYS: u32 = 0;
42/// Verbose level 1 to show failures. In state machine tests this level is used
43/// to print transitions.
44pub const INFO_LOG: u32 = 1;
45const TRACE: u32 = 2;
46
47#[cfg(feature = "std")]
48macro_rules! verbose_message {
49    ($runner:expr, $level:expr, $fmt:tt $($arg:tt)*) => { {
50        #[allow(unused_comparisons)]
51        {
52            if $runner.config.verbose >= $level {
53                eprintln!(concat!("proptest: ", $fmt) $($arg)*);
54            }
55        };
56        ()
57    } }
58}
59
60#[cfg(not(feature = "std"))]
61macro_rules! verbose_message {
62    ($runner:expr, $level:expr, $fmt:tt $($arg:tt)*) => {
63        let _ = $level;
64    };
65}
66
67type RejectionDetail = BTreeMap<Reason, u32>;
68
69/// State used when running a proptest test.
70#[derive(Clone)]
71pub struct TestRunner {
72    config: Config,
73    successes: u32,
74    local_rejects: u32,
75    global_rejects: u32,
76    rng: TestRng,
77    flat_map_regens: Arc<AtomicUsize>,
78
79    local_reject_detail: RejectionDetail,
80    global_reject_detail: RejectionDetail,
81}
82
83impl fmt::Debug for TestRunner {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        f.debug_struct("TestRunner")
86            .field("config", &self.config)
87            .field("successes", &self.successes)
88            .field("local_rejects", &self.local_rejects)
89            .field("global_rejects", &self.global_rejects)
90            .field("rng", &"<TestRng>")
91            .field("flat_map_regens", &self.flat_map_regens)
92            .field("local_reject_detail", &self.local_reject_detail)
93            .field("global_reject_detail", &self.global_reject_detail)
94            .finish()
95    }
96}
97
98impl fmt::Display for TestRunner {
99    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100        write!(
101            f,
102            "\tsuccesses: {}\n\
103             \tlocal rejects: {}\n",
104            self.successes, self.local_rejects
105        )?;
106        for (whence, count) in &self.local_reject_detail {
107            writeln!(f, "\t\t{} times at {}", count, whence)?;
108        }
109        writeln!(f, "\tglobal rejects: {}", self.global_rejects)?;
110        for (whence, count) in &self.global_reject_detail {
111            writeln!(f, "\t\t{} times at {}", count, whence)?;
112        }
113
114        Ok(())
115    }
116}
117
118/// Equivalent to: `TestRunner::new(Config::default())`.
119impl Default for TestRunner {
120    fn default() -> Self {
121        Self::new(Config::default())
122    }
123}
124
125#[cfg(feature = "fork")]
126#[derive(Debug)]
127struct ForkOutput {
128    file: Option<fs::File>,
129}
130
131#[cfg(feature = "fork")]
132impl ForkOutput {
133    fn append(&mut self, result: &TestCaseResult) {
134        if let Some(ref mut file) = self.file {
135            replay::append(file, result)
136                .expect("Failed to append to replay file");
137        }
138    }
139
140    fn ping(&mut self) {
141        if let Some(ref mut file) = self.file {
142            replay::ping(file).expect("Failed to append to replay file");
143        }
144    }
145
146    fn terminate(&mut self) {
147        if let Some(ref mut file) = self.file {
148            replay::terminate(file).expect("Failed to append to replay file");
149        }
150    }
151
152    fn empty() -> Self {
153        ForkOutput { file: None }
154    }
155
156    fn is_in_fork(&self) -> bool {
157        self.file.is_some()
158    }
159}
160
161#[cfg(not(feature = "fork"))]
162#[derive(Debug)]
163struct ForkOutput;
164
165#[cfg(not(feature = "fork"))]
166impl ForkOutput {
167    fn append(&mut self, _result: &TestCaseResult) {}
168    fn ping(&mut self) {}
169    fn terminate(&mut self) {}
170    fn empty() -> Self {
171        ForkOutput
172    }
173    fn is_in_fork(&self) -> bool {
174        false
175    }
176}
177
178#[cfg(not(feature = "std"))]
179fn call_test<V, F, R>(
180    _runner: &mut TestRunner,
181    case: V,
182    test: &F,
183    replay_from_fork: &mut R,
184    result_cache: &mut dyn ResultCache,
185    _: &mut ForkOutput,
186    is_from_persisted_seed: bool,
187) -> TestCaseResultV2
188where
189    V: fmt::Debug,
190    F: Fn(V) -> TestCaseResult,
191    R: Iterator<Item = TestCaseResult>,
192{
193    if let Some(result) = replay_from_fork.next() {
194        return result.map(|_| TestCaseOk::ReplayFromForkSuccess);
195    }
196
197    let cache_key = result_cache.key(&ResultCacheKey::new(&case));
198    if let Some(result) = result_cache.get(cache_key) {
199        return result.clone().map(|_| TestCaseOk::CacheHitSuccess);
200    }
201
202    let result = test(case);
203    result_cache.put(cache_key, &result);
204    result.map(|_| {
205        if is_from_persisted_seed {
206            TestCaseOk::PersistedCaseSuccess
207        } else {
208            TestCaseOk::NewCaseSuccess
209        }
210    })
211}
212
213#[cfg(feature = "std")]
214fn call_test<V, F, R>(
215    runner: &mut TestRunner,
216    case: V,
217    test: &F,
218    replay_from_fork: &mut R,
219    result_cache: &mut dyn ResultCache,
220    fork_output: &mut ForkOutput,
221    is_from_persisted_seed: bool,
222) -> TestCaseResultV2
223where
224    V: fmt::Debug,
225    F: Fn(V) -> TestCaseResult,
226    R: Iterator<Item = TestCaseResult>,
227{
228    #[cfg(feature = "timeout")]
229    let timeout = runner.config.timeout();
230
231    if let Some(result) = replay_from_fork.next() {
232        return result.map(|_| TestCaseOk::ReplayFromForkSuccess);
233    }
234
235    // Now that we're about to start a new test (as far as the replay system is
236    // concerned), ping the replay file so the parent process can determine
237    // that we made it this far.
238    fork_output.ping();
239
240    verbose_message!(runner, TRACE, "Next test input: {:?}", case);
241
242    let cache_key = result_cache.key(&ResultCacheKey::new(&case));
243    if let Some(result) = result_cache.get(cache_key) {
244        verbose_message!(
245            runner,
246            TRACE,
247            "Test input hit cache, skipping execution"
248        );
249        return result.clone().map(|_| TestCaseOk::CacheHitSuccess);
250    }
251
252    #[cfg(feature = "timeout")]
253    let time_start = std::time::Instant::now();
254
255    let mut result = unwrap_or!(
256        super::scoped_panic_hook::with_hook(
257            |_| { /* Silence out panic backtrace */ },
258            || panic::catch_unwind(AssertUnwindSafe(|| test(case)))
259        ),
260        what => Err(TestCaseError::Fail(
261            what.downcast::<&'static str>().map(|s| (*s).into())
262                .or_else(|what| what.downcast::<String>().map(|b| (*b).into()))
263                .or_else(|what| what.downcast::<Box<str>>().map(|b| (*b).into()))
264                .unwrap_or_else(|_| "<unknown panic value>".into()))));
265
266    // If there is a timeout and we exceeded it, fail the test here so we get
267    // consistent behaviour. (The parent process cannot precisely time the test
268    // cases itself.)
269    #[cfg(feature = "timeout")]
270    if timeout > 0 && result.is_ok() {
271        let elapsed = time_start.elapsed();
272        let elapsed_millis = elapsed.as_secs() as u32 * 1000
273            + elapsed.subsec_nanos() / 1_000_000;
274
275        if elapsed_millis > timeout {
276            result = Err(TestCaseError::fail(format!(
277                "Timeout of {} ms exceeded: test took {} ms",
278                timeout, elapsed_millis
279            )));
280        }
281    }
282
283    result_cache.put(cache_key, &result);
284    fork_output.append(&result);
285
286    match result {
287        Ok(()) => verbose_message!(runner, TRACE, "Test case passed"),
288        Err(TestCaseError::Reject(ref reason)) => {
289            verbose_message!(runner, INFO_LOG, "Test case rejected: {}", reason)
290        }
291        Err(TestCaseError::Fail(ref reason)) => {
292            verbose_message!(runner, INFO_LOG, "Test case failed: {}", reason)
293        }
294    }
295
296    result.map(|_| {
297        if is_from_persisted_seed {
298            TestCaseOk::PersistedCaseSuccess
299        } else {
300            TestCaseOk::NewCaseSuccess
301        }
302    })
303}
304
305type TestRunResult<S> = Result<(), TestError<<S as Strategy>::Value>>;
306
307impl TestRunner {
308    /// Create a fresh `TestRunner` with the given configuration.
309    ///
310    /// The runner will use an RNG with a generated seed and the default
311    /// algorithm.
312    ///
313    /// In `no_std` environments, every `TestRunner` will use the same
314    /// hard-coded seed. This seed is not contractually guaranteed and may be
315    /// changed between releases without notice.
316    pub fn new(config: Config) -> Self {
317        let seed = config.rng_seed;
318        let algorithm = config.rng_algorithm;
319        TestRunner::new_with_rng(config, TestRng::default_rng(seed, algorithm))
320    }
321
322    /// Create a fresh `TestRunner` with the standard deterministic RNG.
323    ///
324    /// This is sugar for the following:
325    ///
326    /// ```rust
327    /// # use proptest::test_runner::*;
328    /// let config = Config::default();
329    /// let algorithm = config.rng_algorithm;
330    /// TestRunner::new_with_rng(
331    ///     config,
332    ///     TestRng::deterministic_rng(algorithm));
333    /// ```
334    ///
335    /// Refer to `TestRng::deterministic_rng()` for more information on the
336    /// properties of the RNG used here.
337    pub fn deterministic() -> Self {
338        let config = Config::default();
339        let algorithm = config.rng_algorithm;
340        TestRunner::new_with_rng(config, TestRng::deterministic_rng(algorithm))
341    }
342
343    /// Create a fresh `TestRunner` with the given configuration and RNG.
344    pub fn new_with_rng(config: Config, rng: TestRng) -> Self {
345        TestRunner {
346            config: config,
347            successes: 0,
348            local_rejects: 0,
349            global_rejects: 0,
350            rng: rng,
351            flat_map_regens: Arc::new(AtomicUsize::new(0)),
352            local_reject_detail: BTreeMap::new(),
353            global_reject_detail: BTreeMap::new(),
354        }
355    }
356
357    /// Create a fresh `TestRunner` with the same config and global counters as
358    /// this one, but with local state reset and an independent `Rng` (but
359    /// deterministic).
360    pub(crate) fn partial_clone(&mut self) -> Self {
361        TestRunner {
362            config: self.config.clone(),
363            successes: 0,
364            local_rejects: 0,
365            global_rejects: 0,
366            rng: self.new_rng(),
367            flat_map_regens: Arc::clone(&self.flat_map_regens),
368            local_reject_detail: BTreeMap::new(),
369            global_reject_detail: BTreeMap::new(),
370        }
371    }
372
373    /// Returns the RNG for this test run.
374    pub fn rng(&mut self) -> &mut TestRng {
375        &mut self.rng
376    }
377
378    /// Create a new, independent but deterministic RNG from the RNG in this
379    /// runner.
380    pub fn new_rng(&mut self) -> TestRng {
381        self.rng.gen_rng()
382    }
383
384    /// Returns the configuration of this runner.
385    pub fn config(&self) -> &Config {
386        &self.config
387    }
388
389    /// Dumps the bytes obtained from the RNG so far (only works if the RNG is
390    /// set to `Recorder`).
391    ///
392    /// ## Panics
393    ///
394    /// Panics if the RNG does not capture generated data.
395    pub fn bytes_used(&self) -> Vec<u8> {
396        self.rng.bytes_used()
397    }
398
399    /// Run test cases against `f`, choosing inputs via `strategy`.
400    ///
401    /// If any failure cases occur, try to find a minimal failure case and
402    /// report that. If invoking `f` panics, the panic is turned into a
403    /// `TestCaseError::Fail`.
404    ///
405    /// If failure persistence is enabled, all persisted failing cases are
406    /// tested first. If a later non-persisted case fails, its seed is
407    /// persisted before returning failure.
408    ///
409    /// Returns success or failure indicating why the test as a whole failed.
410    pub fn run<S: Strategy>(
411        &mut self,
412        strategy: &S,
413        test: impl Fn(S::Value) -> TestCaseResult,
414    ) -> TestRunResult<S> {
415        if self.config.fork() {
416            self.run_in_fork(strategy, test)
417        } else {
418            self.run_in_process(strategy, test)
419        }
420    }
421
422    #[cfg(not(feature = "fork"))]
423    fn run_in_fork<S: Strategy>(
424        &mut self,
425        _: &S,
426        _: impl Fn(S::Value) -> TestCaseResult,
427    ) -> TestRunResult<S> {
428        unreachable!()
429    }
430
431    #[cfg(feature = "fork")]
432    fn run_in_fork<S: Strategy>(
433        &mut self,
434        strategy: &S,
435        test: impl Fn(S::Value) -> TestCaseResult,
436    ) -> TestRunResult<S> {
437        let mut test = Some(test);
438
439        let test_name = rusty_fork::fork_test::fix_module_path(
440            self.config
441                .test_name
442                .expect("Must supply test_name when forking enabled"),
443        );
444        let forkfile: RefCell<Option<tempfile::NamedTempFile>> =
445            RefCell::new(None);
446        let init_forkfile_size = Cell::new(0u64);
447        let seed = self.rng.new_rng_seed();
448        let mut replay = replay::Replay {
449            seed,
450            steps: vec![],
451        };
452        let mut child_count = 0;
453        let timeout = self.config.timeout();
454
455        fn forkfile_size(forkfile: &Option<tempfile::NamedTempFile>) -> u64 {
456            forkfile.as_ref().map_or(0, |ff| {
457                ff.as_file().metadata().map(|md| md.len()).unwrap_or(0)
458            })
459        }
460
461        loop {
462            let (child_error, last_fork_file_len) = rusty_fork::fork(
463                test_name,
464                rusty_fork_id!(),
465                |cmd| {
466                    let mut forkfile = forkfile.borrow_mut();
467                    if forkfile.is_none() {
468                        *forkfile =
469                            Some(tempfile::NamedTempFile::new().expect(
470                                "Failed to create temporary file for fork",
471                            ));
472                        replay.init_file(forkfile.as_mut().unwrap()).expect(
473                            "Failed to initialise temporary file for fork",
474                        );
475                    }
476
477                    init_forkfile_size.set(forkfile_size(&forkfile));
478
479                    cmd.env(ENV_FORK_FILE, forkfile.as_ref().unwrap().path());
480                },
481                |child, _| {
482                    await_child(
483                        child,
484                        &mut forkfile.borrow_mut().as_mut().unwrap(),
485                        timeout,
486                    )
487                },
488                || match self.run_in_process(strategy, test.take().unwrap()) {
489                    Ok(_) => (),
490                    Err(e) => panic!(
491                        "Test failed normally in child process.\n{}\n{}",
492                        e, self
493                    ),
494                },
495            )
496            .expect("Fork failed");
497
498            let parsed = replay::Replay::parse_from(
499                &mut forkfile.borrow_mut().as_mut().unwrap(),
500            )
501            .expect("Failed to re-read fork file");
502            match parsed {
503                replay::ReplayFileStatus::InProgress(new_replay) => {
504                    replay = new_replay
505                }
506                replay::ReplayFileStatus::Terminated(new_replay) => {
507                    replay = new_replay;
508                    break;
509                }
510                replay::ReplayFileStatus::Corrupt => {
511                    panic!("Child process corrupted replay file")
512                }
513            }
514
515            let curr_forkfile_size = forkfile_size(&forkfile.borrow());
516
517            // If the child failed to append *anything* to the forkfile, it
518            // crashed or timed out before starting even one test case, so
519            // bail.
520            if curr_forkfile_size == init_forkfile_size.get() {
521                return Err(TestError::Abort(
522                    "Child process crashed or timed out before the first test \
523                     started running; giving up."
524                        .into(),
525                ));
526            }
527
528            // The child only terminates early if it outright crashes or we
529            // kill it due to timeout, so add a synthetic failure to the
530            // output. But only do this if the length of the fork file is the
531            // same as when we last saw it, or if the child was not killed due
532            // to timeout. (This is because the child could have appended
533            // something to the file after we gave up waiting for it but before
534            // we were able to kill it).
535            if last_fork_file_len.map_or(true, |last_fork_file_len| {
536                last_fork_file_len == curr_forkfile_size
537            }) {
538                let error = Err(child_error.unwrap_or(TestCaseError::fail(
539                    "Child process was terminated abruptly \
540                     but with successful status",
541                )));
542                replay::append(forkfile.borrow_mut().as_mut().unwrap(), &error)
543                    .expect("Failed to append to replay file");
544                replay.steps.push(error);
545            }
546
547            // Bail if we've gone through too many processes in case the
548            // shrinking process itself is crashing.
549            child_count += 1;
550            if child_count >= 10000 {
551                return Err(TestError::Abort(
552                    "Giving up after 10000 child processes crashed".into(),
553                ));
554            }
555        }
556
557        // Run through the steps in-process (without ever running the actual
558        // tests) to produce the shrunken value and update the persistence
559        // file.
560        self.rng.set_seed(replay.seed);
561        self.run_in_process_with_replay(
562            strategy,
563            |_| panic!("Ran past the end of the replay"),
564            replay.steps.into_iter(),
565            ForkOutput::empty(),
566        )
567    }
568
569    fn run_in_process<S: Strategy>(
570        &mut self,
571        strategy: &S,
572        test: impl Fn(S::Value) -> TestCaseResult,
573    ) -> TestRunResult<S> {
574        let (replay_steps, fork_output) = init_replay(&mut self.rng);
575        self.run_in_process_with_replay(
576            strategy,
577            test,
578            replay_steps.into_iter(),
579            fork_output,
580        )
581    }
582
583    fn run_in_process_with_replay<S: Strategy>(
584        &mut self,
585        strategy: &S,
586        test: impl Fn(S::Value) -> TestCaseResult,
587        mut replay_from_fork: impl Iterator<Item = TestCaseResult>,
588        mut fork_output: ForkOutput,
589    ) -> TestRunResult<S> {
590        let old_rng = self.rng.clone();
591
592        let persisted_failure_seeds: Vec<PersistedSeed> = self
593            .config
594            .failure_persistence
595            .as_ref()
596            .map(|f| f.load_persisted_failures2(self.config.source_file))
597            .unwrap_or_default();
598
599        let mut result_cache = self.new_cache();
600
601        for PersistedSeed(persisted_seed) in
602            persisted_failure_seeds.into_iter().rev()
603        {
604            self.rng.set_seed(persisted_seed);
605            let result = self.gen_and_run_case(
606                strategy,
607                &test,
608                &mut replay_from_fork,
609                &mut *result_cache,
610                &mut fork_output,
611                true,
612            );
613
614            // Terminate the replay, as a new case's failure does below. A child
615            // that exits without doing so looks to the parent like a crash,
616            // and the parent forks another to replay it instead of reporting.
617            if let Err(e) = result {
618                fork_output.terminate();
619                return Err(e);
620            }
621        }
622        self.rng = old_rng;
623
624        while self.successes < self.config.cases {
625            // Generate a new seed and make an RNG from that so that we know
626            // what seed to persist if this case fails.
627            let seed = self.rng.gen_get_seed();
628            let result = self.gen_and_run_case(
629                strategy,
630                &test,
631                &mut replay_from_fork,
632                &mut *result_cache,
633                &mut fork_output,
634                false,
635            );
636            if let Err(TestError::Fail(_, ref value)) = result {
637                if let Some(ref mut failure_persistence) =
638                    self.config.failure_persistence
639                {
640                    let source_file = &self.config.source_file;
641
642                    // Don't update the persistence file if we're a child
643                    // process. The parent relies on it remaining consistent
644                    // and will take care of updating it itself.
645                    if !fork_output.is_in_fork() {
646                        failure_persistence.save_persisted_failure2(
647                            *source_file,
648                            PersistedSeed(seed),
649                            value,
650                        );
651                    }
652                }
653            }
654
655            if let Err(e) = result {
656                fork_output.terminate();
657                return Err(e.into());
658            }
659        }
660
661        fork_output.terminate();
662        Ok(())
663    }
664
665    fn gen_and_run_case<S: Strategy>(
666        &mut self,
667        strategy: &S,
668        f: &impl Fn(S::Value) -> TestCaseResult,
669        replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
670        result_cache: &mut dyn ResultCache,
671        fork_output: &mut ForkOutput,
672        is_from_persisted_seed: bool,
673    ) -> TestRunResult<S> {
674        let case = unwrap_or!(strategy.new_tree(self), msg =>
675                return Err(TestError::Abort(msg)));
676
677        // We only count new cases to our set of successful runs against
678        // `PROPTEST_CASES` config.
679        let ok_type = self.run_one_with_replay(
680            case,
681            f,
682            replay_from_fork,
683            result_cache,
684            fork_output,
685            is_from_persisted_seed,
686        )?;
687        match ok_type {
688            TestCaseOk::NewCaseSuccess | TestCaseOk::ReplayFromForkSuccess => {
689                self.successes += 1
690            }
691            TestCaseOk::PersistedCaseSuccess
692            | TestCaseOk::CacheHitSuccess
693            | TestCaseOk::Reject => (),
694        }
695
696        Ok(())
697    }
698
699    /// Run one specific test case against this runner.
700    ///
701    /// If the test fails, finds the minimal failing test case. If the test
702    /// does not fail, returns whether it succeeded or was filtered out.
703    ///
704    /// This does not honour the `fork` config, and will not be able to
705    /// terminate the run if it runs for longer than `timeout`. However, if the
706    /// test function returns but took longer than `timeout`, the test case
707    /// will fail.
708    pub fn run_one<V: ValueTree>(
709        &mut self,
710        case: V,
711        test: impl Fn(V::Value) -> TestCaseResult,
712    ) -> Result<bool, TestError<V::Value>> {
713        let mut result_cache = self.new_cache();
714        self.run_one_with_replay(
715            case,
716            test,
717            &mut iter::empty::<TestCaseResult>().fuse(),
718            &mut *result_cache,
719            &mut ForkOutput::empty(),
720            false,
721        )
722        .map(|ok_type| match ok_type {
723            TestCaseOk::Reject => false,
724            _ => true,
725        })
726    }
727
728    fn run_one_with_replay<V: ValueTree>(
729        &mut self,
730        mut case: V,
731        test: impl Fn(V::Value) -> TestCaseResult,
732        replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
733        result_cache: &mut dyn ResultCache,
734        fork_output: &mut ForkOutput,
735        is_from_persisted_seed: bool,
736    ) -> Result<TestCaseOk, TestError<V::Value>> {
737        let result = call_test(
738            self,
739            case.current(),
740            &test,
741            replay_from_fork,
742            result_cache,
743            fork_output,
744            is_from_persisted_seed,
745        );
746
747        match result {
748            Ok(success_type) => Ok(success_type),
749            Err(TestCaseError::Fail(why)) => {
750                let why = self
751                    .shrink(
752                        &mut case,
753                        test,
754                        replay_from_fork,
755                        result_cache,
756                        fork_output,
757                        is_from_persisted_seed,
758                    )
759                    .unwrap_or(why);
760                Err(TestError::Fail(why, case.current()))
761            }
762            Err(TestCaseError::Reject(whence)) => {
763                self.reject_global(whence)?;
764                Ok(TestCaseOk::Reject)
765            }
766        }
767    }
768
769    fn shrink<V: ValueTree>(
770        &mut self,
771        case: &mut V,
772        test: impl Fn(V::Value) -> TestCaseResult,
773        replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
774        result_cache: &mut dyn ResultCache,
775        fork_output: &mut ForkOutput,
776        is_from_persisted_seed: bool,
777    ) -> Option<Reason> {
778        // exit early if shrink disabled
779        if self.config.max_shrink_iters == 0 {
780            verbose_message!(
781                self,
782                INFO_LOG,
783                "Shrinking disabled by configuration"
784            );
785            return None
786        }
787
788        #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
789        let start_time = std::time::Instant::now();
790        let mut last_failure = None;
791        let mut iterations = 0;
792
793        verbose_message!(self, TRACE, "Starting shrinking");
794
795        if case.simplify() {
796            loop {
797                let mut timed_out: Option<u64> = None;
798                #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
799                if self.config.max_shrink_time > 0 {
800                    let elapsed = start_time.elapsed();
801                    let elapsed_ms = elapsed
802                        .as_secs()
803                        .saturating_mul(1000)
804                        .saturating_add(elapsed.subsec_millis().into());
805                    if elapsed_ms > self.config.max_shrink_time as u64 {
806                        timed_out = Some(elapsed_ms);
807                    }
808                }
809
810                let bail = if iterations >= self.config.max_shrink_iters() {
811                    #[cfg(feature = "std")]
812                    const CONTROLLER: &str =
813                        "the PROPTEST_MAX_SHRINK_ITERS environment \
814                         variable or ProptestConfig.max_shrink_iters";
815                    #[cfg(not(feature = "std"))]
816                    const CONTROLLER: &str = "ProptestConfig.max_shrink_iters";
817                    verbose_message!(
818                        self,
819                        ALWAYS,
820                        "Aborting shrinking after {} iterations (set {} \
821                         to a large(r) value to shrink more; current \
822                         configuration: {} iterations)",
823                        CONTROLLER,
824                        self.config.max_shrink_iters(),
825                        iterations
826                    );
827                    true
828                } else if let Some(ms) = timed_out {
829                    #[cfg(feature = "std")]
830                    const CONTROLLER: &str =
831                        "the PROPTEST_MAX_SHRINK_TIME environment \
832                         variable or ProptestConfig.max_shrink_time";
833                    #[cfg(feature = "std")]
834                    let current = self.config.max_shrink_time;
835                    #[cfg(not(feature = "std"))]
836                    const CONTROLLER: &str = "(not configurable in no_std)";
837                    #[cfg(not(feature = "std"))]
838                    let current = 0;
839                    verbose_message!(
840                        self,
841                        ALWAYS,
842                        "Aborting shrinking after taking too long: {} ms \
843                         (set {} to a large(r) value to shrink more; current \
844                         configuration: {} ms)",
845                        ms,
846                        CONTROLLER,
847                        current
848                    );
849                    true
850                } else {
851                    false
852                };
853
854                if bail {
855                    // Move back to the most recent failing case
856                    while case.complicate() {
857                        fork_output.append(&Ok(()));
858                    }
859                    break;
860                }
861
862                iterations += 1;
863
864                let result = call_test(
865                    self,
866                    case.current(),
867                    &test,
868                    replay_from_fork,
869                    result_cache,
870                    fork_output,
871                    is_from_persisted_seed,
872                );
873
874                match result {
875                    // Rejections are effectively a pass here,
876                    // since they indicate that any behaviour of
877                    // the function under test is acceptable.
878                    Ok(_) | Err(TestCaseError::Reject(..)) => {
879                        if !case.complicate() {
880                            verbose_message!(
881                                self,
882                                TRACE,
883                                "Cannot complicate further"
884                            );
885
886                            break;
887                        }
888                    }
889                    Err(TestCaseError::Fail(why)) => {
890                        last_failure = Some(why);
891                        if !case.simplify() {
892                            verbose_message!(
893                                self,
894                                TRACE,
895                                "Cannot simplify further"
896                            );
897
898                            break;
899                        }
900                    }
901                }
902            }
903        }
904
905        last_failure
906    }
907
908    /// Update the state to account for a local rejection from `whence`, and
909    /// return `Ok` if the caller should keep going or `Err` to abort.
910    pub fn reject_local(
911        &mut self,
912        whence: impl Into<Reason>,
913    ) -> Result<(), Reason> {
914        if self.local_rejects >= self.config.max_local_rejects {
915            Err("Too many local rejects".into())
916        } else {
917            self.local_rejects += 1;
918            Self::insert_or_increment(
919                &mut self.local_reject_detail,
920                whence.into(),
921            );
922            Ok(())
923        }
924    }
925
926    /// Update the state to account for a global rejection from `whence`, and
927    /// return `Ok` if the caller should keep going or `Err` to abort.
928    fn reject_global<T>(&mut self, whence: Reason) -> Result<(), TestError<T>> {
929        if self.global_rejects >= self.config.max_global_rejects {
930            Err(TestError::Abort("Too many global rejects".into()))
931        } else {
932            self.global_rejects += 1;
933            Self::insert_or_increment(&mut self.global_reject_detail, whence);
934            Ok(())
935        }
936    }
937
938    /// Insert 1 or increment the rejection detail at key for whence.
939    fn insert_or_increment(into: &mut RejectionDetail, whence: Reason) {
940        into.entry(whence)
941            .and_modify(|count| *count += 1)
942            .or_insert(1);
943    }
944
945    /// Increment the counter of flat map regenerations and return whether it
946    /// is still under the configured limit.
947    pub fn flat_map_regen(&self) -> bool {
948        self.flat_map_regens.fetch_add(1, SeqCst)
949            < self.config.max_flat_map_regens as usize
950    }
951
952    fn new_cache(&self) -> Box<dyn ResultCache> {
953        (self.config.result_cache)()
954    }
955}
956
957#[cfg(feature = "fork")]
958fn init_replay(rng: &mut TestRng) -> (Vec<TestCaseResult>, ForkOutput) {
959    use crate::test_runner::replay::{open_file, Replay, ReplayFileStatus::*};
960
961    if let Some(path) = env::var_os(ENV_FORK_FILE) {
962        let mut file = open_file(&path).expect("Failed to open replay file");
963        let loaded =
964            Replay::parse_from(&mut file).expect("Failed to read replay file");
965        match loaded {
966            InProgress(replay) => {
967                rng.set_seed(replay.seed);
968                (replay.steps, ForkOutput { file: Some(file) })
969            }
970
971            Terminated(_) => {
972                panic!("Replay file for child process is terminated?")
973            }
974
975            Corrupt => panic!("Replay file for child process is corrupt"),
976        }
977    } else {
978        (vec![], ForkOutput::empty())
979    }
980}
981
982#[cfg(not(feature = "fork"))]
983fn init_replay(
984    _rng: &mut TestRng,
985) -> (iter::Empty<TestCaseResult>, ForkOutput) {
986    (iter::empty(), ForkOutput::empty())
987}
988
989#[cfg(feature = "fork")]
990fn await_child_without_timeout(
991    child: &mut rusty_fork::ChildWrapper,
992) -> (Option<TestCaseError>, Option<u64>) {
993    let status = child.wait().expect("Failed to wait for child process");
994
995    if status.success() {
996        (None, None)
997    } else {
998        (
999            Some(TestCaseError::fail(format!(
1000                "Child process exited with {}",
1001                status
1002            ))),
1003            None,
1004        )
1005    }
1006}
1007
1008#[cfg(all(feature = "fork", not(feature = "timeout")))]
1009fn await_child(
1010    child: &mut rusty_fork::ChildWrapper,
1011    _: &mut tempfile::NamedTempFile,
1012    _timeout: u32,
1013) -> (Option<TestCaseError>, Option<u64>) {
1014    await_child_without_timeout(child)
1015}
1016
1017#[cfg(all(feature = "fork", feature = "timeout"))]
1018fn await_child(
1019    child: &mut rusty_fork::ChildWrapper,
1020    forkfile: &mut tempfile::NamedTempFile,
1021    timeout: u32,
1022) -> (Option<TestCaseError>, Option<u64>) {
1023    use std::time::Duration;
1024
1025    if 0 == timeout {
1026        return await_child_without_timeout(child);
1027    }
1028
1029    // The child can run for longer than the timeout since it may run
1030    // multiple tests. Each time the timeout expires, we check whether the
1031    // file has grown larger. If it has, we allow the child to keep running
1032    // until the next timeout.
1033    let mut last_forkfile_len = forkfile
1034        .as_file()
1035        .metadata()
1036        .map(|md| md.len())
1037        .unwrap_or(0);
1038
1039    loop {
1040        if let Some(status) = child
1041            .wait_timeout(Duration::from_millis(timeout.into()))
1042            .expect("Failed to wait for child process")
1043        {
1044            if status.success() {
1045                return (None, None);
1046            } else {
1047                return (
1048                    Some(TestCaseError::fail(format!(
1049                        "Child process exited with {}",
1050                        status
1051                    ))),
1052                    None,
1053                );
1054            }
1055        }
1056
1057        let current_len = forkfile
1058            .as_file()
1059            .metadata()
1060            .map(|md| md.len())
1061            .unwrap_or(0);
1062        // If we've gone a full timeout period without the file growing,
1063        // fail the test and kill the child.
1064        if current_len <= last_forkfile_len {
1065            return (
1066                Some(TestCaseError::fail(format!(
1067                    "Timed out waiting for child process"
1068                ))),
1069                Some(current_len),
1070            );
1071        } else {
1072            last_forkfile_len = current_len;
1073        }
1074    }
1075}
1076
1077#[cfg(test)]
1078mod test {
1079    use std::cell::Cell;
1080    use std::fs;
1081
1082    use super::*;
1083    use crate::strategy::Strategy;
1084    use crate::test_runner::{FileFailurePersistence, RngAlgorithm, TestRng};
1085
1086    #[test]
1087    fn gives_up_after_too_many_rejections() {
1088        let config = Config::default();
1089        let mut runner = TestRunner::new(config.clone());
1090        let runs = Cell::new(0);
1091        let result = runner.run(&(0u32..), |_| {
1092            runs.set(runs.get() + 1);
1093            Err(TestCaseError::reject("reject"))
1094        });
1095        match result {
1096            Err(TestError::Abort(_)) => (),
1097            e => panic!("Unexpected result: {:?}", e),
1098        }
1099        assert_eq!(config.max_global_rejects + 1, runs.get());
1100    }
1101
1102    #[test]
1103    fn test_pass() {
1104        let mut runner = TestRunner::default();
1105        let result = runner.run(&(1u32..), |v| {
1106            assert!(v > 0);
1107            Ok(())
1108        });
1109        assert_eq!(Ok(()), result);
1110    }
1111
1112    #[test]
1113    fn test_fail_via_result() {
1114        let mut runner = TestRunner::new(Config {
1115            failure_persistence: None,
1116            ..Config::default()
1117        });
1118        let result = runner.run(&(0u32..10u32), |v| {
1119            if v < 5 {
1120                Ok(())
1121            } else {
1122                Err(TestCaseError::fail("not less than 5"))
1123            }
1124        });
1125
1126        assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
1127    }
1128
1129    #[test]
1130    fn test_fail_via_panic() {
1131        let mut runner = TestRunner::new(Config {
1132            failure_persistence: None,
1133            ..Config::default()
1134        });
1135        let result = runner.run(&(0u32..10u32), |v| {
1136            assert!(v < 5, "not less than 5");
1137            Ok(())
1138        });
1139        assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
1140    }
1141
1142    #[test]
1143    fn persisted_cases_do_not_count_towards_total_cases() {
1144        const FILE: &'static str = "persistence-test.txt";
1145        let _ = fs::remove_file(FILE);
1146
1147        let config = Config {
1148            failure_persistence: Some(Box::new(
1149                FileFailurePersistence::Direct(FILE),
1150            )),
1151            cases: 1,
1152            ..Config::default()
1153        };
1154
1155        let max = 10_000_000i32;
1156        {
1157            TestRunner::new(config.clone())
1158                .run(&(0i32..max), |_v| {
1159                    Err(TestCaseError::Fail("persist a failure".into()))
1160                })
1161                .expect_err("didn't fail?");
1162        }
1163
1164        let run_count = RefCell::new(0);
1165        TestRunner::new(config.clone())
1166            .run(&(0i32..max), |_v| {
1167                *run_count.borrow_mut() += 1;
1168                Ok(())
1169            })
1170            .expect("should succeed");
1171
1172        // Persisted ran, and a new case ran, and only new case counts
1173        // against `cases: 1`.
1174        assert_eq!(run_count.into_inner(), 2);
1175    }
1176
1177    #[derive(Clone, Copy, PartialEq)]
1178    struct PoorlyBehavedDebug(i32);
1179    impl fmt::Debug for PoorlyBehavedDebug {
1180        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1181            write!(f, "\r\n{:?}\r\n", self.0)
1182        }
1183    }
1184
1185    #[test]
1186    fn failing_cases_persisted_and_reloaded() {
1187        const FILE: &'static str = "persistence-test.txt";
1188        let _ = fs::remove_file(FILE);
1189
1190        let max = 10_000_000i32;
1191        let input = (0i32..max).prop_map(PoorlyBehavedDebug);
1192        let config = Config {
1193            failure_persistence: Some(Box::new(
1194                FileFailurePersistence::Direct(FILE),
1195            )),
1196            ..Config::default()
1197        };
1198
1199        // First test with cases that fail above half max, and then below half
1200        // max, to ensure we can correctly parse both lines of the persistence
1201        // file.
1202        let first_sub_failure = {
1203            TestRunner::new(config.clone())
1204                .run(&input, |v| {
1205                    if v.0 < max / 2 {
1206                        Ok(())
1207                    } else {
1208                        Err(TestCaseError::Fail("too big".into()))
1209                    }
1210                })
1211                .expect_err("didn't fail?")
1212        };
1213        let first_super_failure = {
1214            TestRunner::new(config.clone())
1215                .run(&input, |v| {
1216                    if v.0 >= max / 2 {
1217                        Ok(())
1218                    } else {
1219                        Err(TestCaseError::Fail("too small".into()))
1220                    }
1221                })
1222                .expect_err("didn't fail?")
1223        };
1224        let second_sub_failure = {
1225            TestRunner::new(config.clone())
1226                .run(&input, |v| {
1227                    if v.0 < max / 2 {
1228                        Ok(())
1229                    } else {
1230                        Err(TestCaseError::Fail("too big".into()))
1231                    }
1232                })
1233                .expect_err("didn't fail?")
1234        };
1235        let second_super_failure = {
1236            TestRunner::new(config.clone())
1237                .run(&input, |v| {
1238                    if v.0 >= max / 2 {
1239                        Ok(())
1240                    } else {
1241                        Err(TestCaseError::Fail("too small".into()))
1242                    }
1243                })
1244                .expect_err("didn't fail?")
1245        };
1246
1247        assert_eq!(first_sub_failure, second_sub_failure);
1248        assert_eq!(first_super_failure, second_super_failure);
1249    }
1250
1251    #[test]
1252    fn new_rng_makes_separate_rng() {
1253        use rand::RngExt;
1254        let mut runner = TestRunner::default();
1255        let from_1 = runner.new_rng().random::<[u8; 16]>();
1256        let from_2 = runner.rng().random::<[u8; 16]>();
1257        assert_ne!(from_1, from_2);
1258    }
1259
1260    #[test]
1261    fn record_rng_use() {
1262        use rand::RngExt;
1263
1264        // create value with recorder rng
1265        let default_config = Config::default();
1266        let recorder_rng = TestRng::default_rng(RngSeed::Random, RngAlgorithm::Recorder);
1267        let mut runner =
1268            TestRunner::new_with_rng(default_config.clone(), recorder_rng);
1269        let random_byte_array1 = runner.rng().random::<[u8; 16]>();
1270        let bytes_used = runner.bytes_used();
1271        assert!(bytes_used.len() >= 16); // could use more bytes for some reason
1272
1273        // re-create value with pass-through rng
1274        let passthrough_rng =
1275            TestRng::from_seed(RngAlgorithm::PassThrough, &bytes_used);
1276        let mut runner =
1277            TestRunner::new_with_rng(default_config, passthrough_rng);
1278        let random_byte_array2 = runner.rng().random::<[u8; 16]>();
1279
1280        // make sure the same value was created
1281        assert_eq!(random_byte_array1, random_byte_array2);
1282    }
1283
1284    #[cfg(feature = "fork")]
1285    #[test]
1286    fn run_successful_test_in_fork() {
1287        let mut runner = TestRunner::new(Config {
1288            fork: true,
1289            test_name: Some(concat!(
1290                module_path!(),
1291                "::run_successful_test_in_fork"
1292            )),
1293            ..Config::default()
1294        });
1295
1296        assert!(runner.run(&(0u32..1000), |_| Ok(())).is_ok());
1297    }
1298
1299    #[cfg(feature = "fork")]
1300    #[test]
1301    fn normal_failure_in_fork_results_in_correct_failure() {
1302        let mut runner = TestRunner::new(Config {
1303            fork: true,
1304            test_name: Some(concat!(
1305                module_path!(),
1306                "::normal_failure_in_fork_results_in_correct_failure"
1307            )),
1308            ..Config::default()
1309        });
1310
1311        let failure = runner
1312            .run(&(0u32..1000), |v| {
1313                prop_assert!(v < 500);
1314                Ok(())
1315            })
1316            .err()
1317            .unwrap();
1318
1319        match failure {
1320            TestError::Fail(_, value) => assert_eq!(500, value),
1321            failure => panic!("Unexpected failure: {:?}", failure),
1322        }
1323    }
1324
1325    #[cfg(feature = "fork")]
1326    #[test]
1327    fn nonsuccessful_exit_finds_correct_failure() {
1328        let mut runner = TestRunner::new(Config {
1329            fork: true,
1330            test_name: Some(concat!(
1331                module_path!(),
1332                "::nonsuccessful_exit_finds_correct_failure"
1333            )),
1334            ..Config::default()
1335        });
1336
1337        let failure = runner
1338            .run(&(0u32..1000), |v| {
1339                if v >= 500 {
1340                    ::std::process::exit(1);
1341                }
1342                Ok(())
1343            })
1344            .err()
1345            .unwrap();
1346
1347        match failure {
1348            TestError::Fail(_, value) => assert_eq!(500, value),
1349            failure => panic!("Unexpected failure: {:?}", failure),
1350        }
1351    }
1352
1353    #[cfg(feature = "fork")]
1354    #[test]
1355    fn spurious_exit_finds_correct_failure() {
1356        let mut runner = TestRunner::new(Config {
1357            fork: true,
1358            test_name: Some(concat!(
1359                module_path!(),
1360                "::spurious_exit_finds_correct_failure"
1361            )),
1362            ..Config::default()
1363        });
1364
1365        let failure = runner
1366            .run(&(0u32..1000), |v| {
1367                if v >= 500 {
1368                    ::std::process::exit(0);
1369                }
1370                Ok(())
1371            })
1372            .err()
1373            .unwrap();
1374
1375        match failure {
1376            TestError::Fail(_, value) => assert_eq!(500, value),
1377            failure => panic!("Unexpected failure: {:?}", failure),
1378        }
1379    }
1380
1381    #[cfg(feature = "timeout")]
1382    #[test]
1383    fn long_sleep_timeout_finds_correct_failure() {
1384        let mut runner = TestRunner::new(Config {
1385            fork: true,
1386            timeout: 500,
1387            test_name: Some(concat!(
1388                module_path!(),
1389                "::long_sleep_timeout_finds_correct_failure"
1390            )),
1391            ..Config::default()
1392        });
1393
1394        let failure = runner
1395            .run(&(0u32..1000), |v| {
1396                if v >= 500 {
1397                    ::std::thread::sleep(::std::time::Duration::from_millis(
1398                        10_000,
1399                    ));
1400                }
1401                Ok(())
1402            })
1403            .err()
1404            .unwrap();
1405
1406        match failure {
1407            TestError::Fail(_, value) => assert_eq!(500, value),
1408            failure => panic!("Unexpected failure: {:?}", failure),
1409        }
1410    }
1411
1412    #[cfg(feature = "timeout")]
1413    #[test]
1414    fn mid_sleep_timeout_finds_correct_failure() {
1415        let mut runner = TestRunner::new(Config {
1416            fork: true,
1417            timeout: 500,
1418            test_name: Some(concat!(
1419                module_path!(),
1420                "::mid_sleep_timeout_finds_correct_failure"
1421            )),
1422            ..Config::default()
1423        });
1424
1425        let failure = runner
1426            .run(&(0u32..1000), |v| {
1427                if v >= 500 {
1428                    // Sleep a little longer than the timeout. This means that
1429                    // sometimes the test case itself will return before the parent
1430                    // process has noticed the child is timing out, so it's up to
1431                    // the child to mark it as a failure.
1432                    ::std::thread::sleep(::std::time::Duration::from_millis(
1433                        600,
1434                    ));
1435                } else {
1436                    // Sleep a bit so that the parent and child timing don't stay
1437                    // in sync.
1438                    ::std::thread::sleep(::std::time::Duration::from_millis(
1439                        100,
1440                    ))
1441                }
1442                Ok(())
1443            })
1444            .err()
1445            .unwrap();
1446
1447        match failure {
1448            TestError::Fail(_, value) => assert_eq!(500, value),
1449            failure => panic!("Unexpected failure: {:?}", failure),
1450        }
1451    }
1452
1453    #[cfg(feature = "std")]
1454    #[test]
1455    fn duplicate_tests_not_run_with_basic_result_cache() {
1456        use std::cell::{Cell, RefCell};
1457        use std::collections::HashSet;
1458        use std::rc::Rc;
1459
1460        for _ in 0..256 {
1461            let mut runner = TestRunner::new(Config {
1462                failure_persistence: None,
1463                result_cache:
1464                    crate::test_runner::result_cache::basic_result_cache,
1465                ..Config::default()
1466            });
1467            let pass = Rc::new(Cell::new(true));
1468            let seen = Rc::new(RefCell::new(HashSet::new()));
1469            let result =
1470                runner.run(&(0u32..65536u32).prop_map(|v| v % 10), |val| {
1471                    if !seen.borrow_mut().insert(val) {
1472                        println!("Value {} seen more than once", val);
1473                        pass.set(false);
1474                    }
1475
1476                    prop_assert!(val <= 5);
1477                    Ok(())
1478                });
1479
1480            assert!(pass.get());
1481            if let Err(TestError::Fail(_, val)) = result {
1482                assert_eq!(6, val);
1483            } else {
1484                panic!("Incorrect result: {:?}", result);
1485            }
1486        }
1487    }
1488}
1489
1490#[cfg(all(feature = "fork", feature = "timeout", test))]
1491mod timeout_tests {
1492    use core::u32;
1493    use std::thread;
1494    use std::time::Duration;
1495
1496    use super::*;
1497    use crate::test_runner::failure_persistence::FailurePersistence;
1498    use crate::test_runner::rng::Seed;
1499    use crate::test_runner::{MapFailurePersistence, RngAlgorithm};
1500
1501    rusty_fork_test! {
1502        #![rusty_fork(timeout_ms = 4_000)]
1503
1504        #[test]
1505        fn max_shrink_iters_works() {
1506            test_shrink_bail(Config {
1507                max_shrink_iters: 5,
1508                .. Config::default()
1509            });
1510        }
1511
1512        #[test]
1513        fn max_shrink_time_works() {
1514            test_shrink_bail(Config {
1515                max_shrink_time: 1000,
1516                .. Config::default()
1517            });
1518        }
1519
1520        #[test]
1521        fn max_shrink_iters_works_with_forking() {
1522            test_shrink_bail(Config {
1523                fork: true,
1524                test_name: Some(
1525                    concat!(module_path!(),
1526                            "::max_shrink_iters_works_with_forking")),
1527                max_shrink_time: 1000,
1528                .. Config::default()
1529            });
1530        }
1531
1532        #[test]
1533        fn persisted_failure_in_fork_is_reported() {
1534            let value = run_persisted_failure(Config {
1535                fork: true,
1536                test_name: Some(
1537                    concat!(module_path!(),
1538                            "::persisted_failure_in_fork_is_reported")),
1539                .. persisted_failure_config()
1540            }, 0);
1541            assert_eq!(0, value);
1542        }
1543
1544        #[test]
1545        fn persisted_timeout_in_fork_is_reported() {
1546            // Every candidate times out, so any value the shrink stops at is
1547            // a failing case.
1548            run_persisted_failure(Config {
1549                timeout: 100,
1550                max_shrink_iters: 3,
1551                test_name: Some(
1552                    concat!(module_path!(),
1553                            "::persisted_timeout_in_fork_is_reported")),
1554                .. persisted_failure_config()
1555            }, 10_000);
1556        }
1557
1558        #[test]
1559        fn detects_child_failure_to_start() {
1560            let mut runner = TestRunner::new(Config {
1561                timeout: 100,
1562                test_name: Some(
1563                    concat!(module_path!(),
1564                            "::detects_child_failure_to_start")),
1565                .. Config::default()
1566            });
1567            let result = runner.run(&Just(()).prop_map(|()| {
1568                thread::sleep(Duration::from_millis(200))
1569            }), Ok);
1570
1571            if let Err(TestError::Abort(_)) = result {
1572                // OK
1573            } else {
1574                panic!("Unexpected result: {:?}", result);
1575            }
1576        }
1577    }
1578
1579    /// A configuration whose persistence already holds one failing seed, as
1580    /// though an earlier run had recorded it.
1581    fn persisted_failure_config() -> Config {
1582        const SOURCE_FILE: &str = "persisted_failure";
1583        let mut persistence = MapFailurePersistence::default();
1584        persistence.save_persisted_failure2(
1585            Some(SOURCE_FILE),
1586            PersistedSeed(Seed::from_bytes(RngAlgorithm::ChaCha, &[7; 32])),
1587            &(),
1588        );
1589        Config {
1590            failure_persistence: Some(Box::new(persistence)),
1591            source_file: Some(SOURCE_FILE),
1592            rng_algorithm: RngAlgorithm::ChaCha,
1593            ..Config::default()
1594        }
1595    }
1596
1597    /// Run a test that fails every case, after sleeping for `sleep_ms` so
1598    /// that a timeout can be what fails it, and return the shrunken value.
1599    /// The persisted seed is replayed first, so this checks that its failure
1600    /// is reported, rather than aborted or replayed in child after child.
1601    fn run_persisted_failure(config: Config, sleep_ms: u64) -> u32 {
1602        let mut runner = TestRunner::new(config);
1603        let result = runner.run(&(0u32..1000), |_| {
1604            thread::sleep(Duration::from_millis(sleep_ms));
1605            Err(TestCaseError::fail("every case fails"))
1606        });
1607
1608        match result {
1609            Err(TestError::Fail(_, value)) => value,
1610            result => panic!("Unexpected result: {:?}", result),
1611        }
1612    }
1613
1614    fn test_shrink_bail(config: Config) {
1615        let mut runner = TestRunner::new(config);
1616        let result = runner.run(&crate::num::u64::ANY, |v| {
1617            thread::sleep(Duration::from_millis(250));
1618            prop_assert!(v <= u32::MAX as u64);
1619            Ok(())
1620        });
1621
1622        if let Err(TestError::Fail(_, value)) = result {
1623            // Ensure the final value was in fact a failing case.
1624            assert!(value > u32::MAX as u64);
1625        } else {
1626            panic!("Unexpected result: {:?}", result);
1627        }
1628    }
1629}