proptest/test_runner/
runner.rs

1//-
2// Copyright 2017, 2018, 2019, 2024 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 algorithm = config.rng_algorithm;
318        TestRunner::new_with_rng(config, TestRng::default_rng(algorithm))
319    }
320
321    /// Create a fresh `TestRunner` with the standard deterministic RNG.
322    ///
323    /// This is sugar for the following:
324    ///
325    /// ```rust
326    /// # use proptest::test_runner::*;
327    /// let config = Config::default();
328    /// let algorithm = config.rng_algorithm;
329    /// TestRunner::new_with_rng(
330    ///     config,
331    ///     TestRng::deterministic_rng(algorithm));
332    /// ```
333    ///
334    /// Refer to `TestRng::deterministic_rng()` for more information on the
335    /// properties of the RNG used here.
336    pub fn deterministic() -> Self {
337        let config = Config::default();
338        let algorithm = config.rng_algorithm;
339        TestRunner::new_with_rng(config, TestRng::deterministic_rng(algorithm))
340    }
341
342    /// Create a fresh `TestRunner` with the given configuration and RNG.
343    pub fn new_with_rng(config: Config, rng: TestRng) -> Self {
344        TestRunner {
345            config: config,
346            successes: 0,
347            local_rejects: 0,
348            global_rejects: 0,
349            rng: rng,
350            flat_map_regens: Arc::new(AtomicUsize::new(0)),
351            local_reject_detail: BTreeMap::new(),
352            global_reject_detail: BTreeMap::new(),
353        }
354    }
355
356    /// Create a fresh `TestRunner` with the same config and global counters as
357    /// this one, but with local state reset and an independent `Rng` (but
358    /// deterministic).
359    pub(crate) fn partial_clone(&mut self) -> Self {
360        TestRunner {
361            config: self.config.clone(),
362            successes: 0,
363            local_rejects: 0,
364            global_rejects: 0,
365            rng: self.new_rng(),
366            flat_map_regens: Arc::clone(&self.flat_map_regens),
367            local_reject_detail: BTreeMap::new(),
368            global_reject_detail: BTreeMap::new(),
369        }
370    }
371
372    /// Returns the RNG for this test run.
373    pub fn rng(&mut self) -> &mut TestRng {
374        &mut self.rng
375    }
376
377    /// Create a new, independent but deterministic RNG from the RNG in this
378    /// runner.
379    pub fn new_rng(&mut self) -> TestRng {
380        self.rng.gen_rng()
381    }
382
383    /// Returns the configuration of this runner.
384    pub fn config(&self) -> &Config {
385        &self.config
386    }
387
388    /// Dumps the bytes obtained from the RNG so far (only works if the RNG is
389    /// set to `Recorder`).
390    ///
391    /// ## Panics
392    ///
393    /// Panics if the RNG does not capture generated data.
394    pub fn bytes_used(&self) -> Vec<u8> {
395        self.rng.bytes_used()
396    }
397
398    /// Run test cases against `f`, choosing inputs via `strategy`.
399    ///
400    /// If any failure cases occur, try to find a minimal failure case and
401    /// report that. If invoking `f` panics, the panic is turned into a
402    /// `TestCaseError::Fail`.
403    ///
404    /// If failure persistence is enabled, all persisted failing cases are
405    /// tested first. If a later non-persisted case fails, its seed is
406    /// persisted before returning failure.
407    ///
408    /// Returns success or failure indicating why the test as a whole failed.
409    pub fn run<S: Strategy>(
410        &mut self,
411        strategy: &S,
412        test: impl Fn(S::Value) -> TestCaseResult,
413    ) -> TestRunResult<S> {
414        if self.config.fork() {
415            self.run_in_fork(strategy, test)
416        } else {
417            self.run_in_process(strategy, test)
418        }
419    }
420
421    #[cfg(not(feature = "fork"))]
422    fn run_in_fork<S: Strategy>(
423        &mut self,
424        _: &S,
425        _: impl Fn(S::Value) -> TestCaseResult,
426    ) -> TestRunResult<S> {
427        unreachable!()
428    }
429
430    #[cfg(feature = "fork")]
431    fn run_in_fork<S: Strategy>(
432        &mut self,
433        strategy: &S,
434        test: impl Fn(S::Value) -> TestCaseResult,
435    ) -> TestRunResult<S> {
436        let mut test = Some(test);
437
438        let test_name = rusty_fork::fork_test::fix_module_path(
439            self.config
440                .test_name
441                .expect("Must supply test_name when forking enabled"),
442        );
443        let forkfile: RefCell<Option<tempfile::NamedTempFile>> =
444            RefCell::new(None);
445        let init_forkfile_size = Cell::new(0u64);
446        let seed = self.rng.new_rng_seed();
447        let mut replay = replay::Replay {
448            seed,
449            steps: vec![],
450        };
451        let mut child_count = 0;
452        let timeout = self.config.timeout();
453
454        fn forkfile_size(forkfile: &Option<tempfile::NamedTempFile>) -> u64 {
455            forkfile.as_ref().map_or(0, |ff| {
456                ff.as_file().metadata().map(|md| md.len()).unwrap_or(0)
457            })
458        }
459
460        loop {
461            let (child_error, last_fork_file_len) = rusty_fork::fork(
462                test_name,
463                rusty_fork_id!(),
464                |cmd| {
465                    let mut forkfile = forkfile.borrow_mut();
466                    if forkfile.is_none() {
467                        *forkfile =
468                            Some(tempfile::NamedTempFile::new().expect(
469                                "Failed to create temporary file for fork",
470                            ));
471                        replay.init_file(forkfile.as_mut().unwrap()).expect(
472                            "Failed to initialise temporary file for fork",
473                        );
474                    }
475
476                    init_forkfile_size.set(forkfile_size(&forkfile));
477
478                    cmd.env(ENV_FORK_FILE, forkfile.as_ref().unwrap().path());
479                },
480                |child, _| {
481                    await_child(
482                        child,
483                        &mut forkfile.borrow_mut().as_mut().unwrap(),
484                        timeout,
485                    )
486                },
487                || match self.run_in_process(strategy, test.take().unwrap()) {
488                    Ok(_) => (),
489                    Err(e) => panic!(
490                        "Test failed normally in child process.\n{}\n{}",
491                        e, self
492                    ),
493                },
494            )
495            .expect("Fork failed");
496
497            let parsed = replay::Replay::parse_from(
498                &mut forkfile.borrow_mut().as_mut().unwrap(),
499            )
500            .expect("Failed to re-read fork file");
501            match parsed {
502                replay::ReplayFileStatus::InProgress(new_replay) => {
503                    replay = new_replay
504                }
505                replay::ReplayFileStatus::Terminated(new_replay) => {
506                    replay = new_replay;
507                    break;
508                }
509                replay::ReplayFileStatus::Corrupt => {
510                    panic!("Child process corrupted replay file")
511                }
512            }
513
514            let curr_forkfile_size = forkfile_size(&forkfile.borrow());
515
516            // If the child failed to append *anything* to the forkfile, it
517            // crashed or timed out before starting even one test case, so
518            // bail.
519            if curr_forkfile_size == init_forkfile_size.get() {
520                return Err(TestError::Abort(
521                    "Child process crashed or timed out before the first test \
522                     started running; giving up."
523                        .into(),
524                ));
525            }
526
527            // The child only terminates early if it outright crashes or we
528            // kill it due to timeout, so add a synthetic failure to the
529            // output. But only do this if the length of the fork file is the
530            // same as when we last saw it, or if the child was not killed due
531            // to timeout. (This is because the child could have appended
532            // something to the file after we gave up waiting for it but before
533            // we were able to kill it).
534            if last_fork_file_len.map_or(true, |last_fork_file_len| {
535                last_fork_file_len == curr_forkfile_size
536            }) {
537                let error = Err(child_error.unwrap_or(TestCaseError::fail(
538                    "Child process was terminated abruptly \
539                     but with successful status",
540                )));
541                replay::append(forkfile.borrow_mut().as_mut().unwrap(), &error)
542                    .expect("Failed to append to replay file");
543                replay.steps.push(error);
544            }
545
546            // Bail if we've gone through too many processes in case the
547            // shrinking process itself is crashing.
548            child_count += 1;
549            if child_count >= 10000 {
550                return Err(TestError::Abort(
551                    "Giving up after 10000 child processes crashed".into(),
552                ));
553            }
554        }
555
556        // Run through the steps in-process (without ever running the actual
557        // tests) to produce the shrunken value and update the persistence
558        // file.
559        self.rng.set_seed(replay.seed);
560        self.run_in_process_with_replay(
561            strategy,
562            |_| panic!("Ran past the end of the replay"),
563            replay.steps.into_iter(),
564            ForkOutput::empty(),
565        )
566    }
567
568    fn run_in_process<S: Strategy>(
569        &mut self,
570        strategy: &S,
571        test: impl Fn(S::Value) -> TestCaseResult,
572    ) -> TestRunResult<S> {
573        let (replay_steps, fork_output) = init_replay(&mut self.rng);
574        self.run_in_process_with_replay(
575            strategy,
576            test,
577            replay_steps.into_iter(),
578            fork_output,
579        )
580    }
581
582    fn run_in_process_with_replay<S: Strategy>(
583        &mut self,
584        strategy: &S,
585        test: impl Fn(S::Value) -> TestCaseResult,
586        mut replay_from_fork: impl Iterator<Item = TestCaseResult>,
587        mut fork_output: ForkOutput,
588    ) -> TestRunResult<S> {
589        let old_rng = self.rng.clone();
590
591        let persisted_failure_seeds: Vec<PersistedSeed> = self
592            .config
593            .failure_persistence
594            .as_ref()
595            .map(|f| f.load_persisted_failures2(self.config.source_file))
596            .unwrap_or_default();
597
598        let mut result_cache = self.new_cache();
599
600        for PersistedSeed(persisted_seed) in
601            persisted_failure_seeds.into_iter().rev()
602        {
603            self.rng.set_seed(persisted_seed);
604            self.gen_and_run_case(
605                strategy,
606                &test,
607                &mut replay_from_fork,
608                &mut *result_cache,
609                &mut fork_output,
610                true,
611            )?;
612        }
613        self.rng = old_rng;
614
615        while self.successes < self.config.cases {
616            // Generate a new seed and make an RNG from that so that we know
617            // what seed to persist if this case fails.
618            let seed = self.rng.gen_get_seed();
619            let result = self.gen_and_run_case(
620                strategy,
621                &test,
622                &mut replay_from_fork,
623                &mut *result_cache,
624                &mut fork_output,
625                false,
626            );
627            if let Err(TestError::Fail(_, ref value)) = result {
628                if let Some(ref mut failure_persistence) =
629                    self.config.failure_persistence
630                {
631                    let source_file = &self.config.source_file;
632
633                    // Don't update the persistence file if we're a child
634                    // process. The parent relies on it remaining consistent
635                    // and will take care of updating it itself.
636                    if !fork_output.is_in_fork() {
637                        failure_persistence.save_persisted_failure2(
638                            *source_file,
639                            PersistedSeed(seed),
640                            value,
641                        );
642                    }
643                }
644            }
645
646            if let Err(e) = result {
647                fork_output.terminate();
648                return Err(e.into());
649            }
650        }
651
652        fork_output.terminate();
653        Ok(())
654    }
655
656    fn gen_and_run_case<S: Strategy>(
657        &mut self,
658        strategy: &S,
659        f: &impl Fn(S::Value) -> TestCaseResult,
660        replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
661        result_cache: &mut dyn ResultCache,
662        fork_output: &mut ForkOutput,
663        is_from_persisted_seed: bool,
664    ) -> TestRunResult<S> {
665        let case = unwrap_or!(strategy.new_tree(self), msg =>
666                return Err(TestError::Abort(msg)));
667
668        // We only count new cases to our set of successful runs against
669        // `PROPTEST_CASES` config.
670        let ok_type = self.run_one_with_replay(
671            case,
672            f,
673            replay_from_fork,
674            result_cache,
675            fork_output,
676            is_from_persisted_seed,
677        )?;
678        match ok_type {
679            TestCaseOk::NewCaseSuccess | TestCaseOk::ReplayFromForkSuccess => {
680                self.successes += 1
681            }
682            TestCaseOk::PersistedCaseSuccess
683            | TestCaseOk::CacheHitSuccess
684            | TestCaseOk::Reject => (),
685        }
686
687        Ok(())
688    }
689
690    /// Run one specific test case against this runner.
691    ///
692    /// If the test fails, finds the minimal failing test case. If the test
693    /// does not fail, returns whether it succeeded or was filtered out.
694    ///
695    /// This does not honour the `fork` config, and will not be able to
696    /// terminate the run if it runs for longer than `timeout`. However, if the
697    /// test function returns but took longer than `timeout`, the test case
698    /// will fail.
699    pub fn run_one<V: ValueTree>(
700        &mut self,
701        case: V,
702        test: impl Fn(V::Value) -> TestCaseResult,
703    ) -> Result<bool, TestError<V::Value>> {
704        let mut result_cache = self.new_cache();
705        self.run_one_with_replay(
706            case,
707            test,
708            &mut iter::empty::<TestCaseResult>().fuse(),
709            &mut *result_cache,
710            &mut ForkOutput::empty(),
711            false,
712        )
713        .map(|ok_type| match ok_type {
714            TestCaseOk::Reject => false,
715            _ => true,
716        })
717    }
718
719    fn run_one_with_replay<V: ValueTree>(
720        &mut self,
721        mut case: V,
722        test: impl Fn(V::Value) -> TestCaseResult,
723        replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
724        result_cache: &mut dyn ResultCache,
725        fork_output: &mut ForkOutput,
726        is_from_persisted_seed: bool,
727    ) -> Result<TestCaseOk, TestError<V::Value>> {
728        let result = call_test(
729            self,
730            case.current(),
731            &test,
732            replay_from_fork,
733            result_cache,
734            fork_output,
735            is_from_persisted_seed,
736        );
737
738        match result {
739            Ok(success_type) => Ok(success_type),
740            Err(TestCaseError::Fail(why)) => {
741                let why = self
742                    .shrink(
743                        &mut case,
744                        test,
745                        replay_from_fork,
746                        result_cache,
747                        fork_output,
748                        is_from_persisted_seed,
749                    )
750                    .unwrap_or(why);
751                Err(TestError::Fail(why, case.current()))
752            }
753            Err(TestCaseError::Reject(whence)) => {
754                self.reject_global(whence)?;
755                Ok(TestCaseOk::Reject)
756            }
757        }
758    }
759
760    fn shrink<V: ValueTree>(
761        &mut self,
762        case: &mut V,
763        test: impl Fn(V::Value) -> TestCaseResult,
764        replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
765        result_cache: &mut dyn ResultCache,
766        fork_output: &mut ForkOutput,
767        is_from_persisted_seed: bool,
768    ) -> Option<Reason> {
769        // exit early if shrink disabled
770        if self.config.max_shrink_iters == 0 {
771            verbose_message!(
772                self,
773                INFO_LOG,
774                "Shrinking disabled by configuration"
775            );
776            return None
777        }
778
779        #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
780        let start_time = std::time::Instant::now();
781        let mut last_failure = None;
782        let mut iterations = 0;
783
784        verbose_message!(self, TRACE, "Starting shrinking");
785
786        if case.simplify() {
787            loop {
788                let mut timed_out: Option<u64> = None;
789                #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
790                if self.config.max_shrink_time > 0 {
791                    let elapsed = start_time.elapsed();
792                    let elapsed_ms = elapsed
793                        .as_secs()
794                        .saturating_mul(1000)
795                        .saturating_add(elapsed.subsec_millis().into());
796                    if elapsed_ms > self.config.max_shrink_time as u64 {
797                        timed_out = Some(elapsed_ms);
798                    }
799                }
800
801                let bail = if iterations >= self.config.max_shrink_iters() {
802                    #[cfg(feature = "std")]
803                    const CONTROLLER: &str =
804                        "the PROPTEST_MAX_SHRINK_ITERS environment \
805                         variable or ProptestConfig.max_shrink_iters";
806                    #[cfg(not(feature = "std"))]
807                    const CONTROLLER: &str = "ProptestConfig.max_shrink_iters";
808                    verbose_message!(
809                        self,
810                        ALWAYS,
811                        "Aborting shrinking after {} iterations (set {} \
812                         to a large(r) value to shrink more; current \
813                         configuration: {} iterations)",
814                        CONTROLLER,
815                        self.config.max_shrink_iters(),
816                        iterations
817                    );
818                    true
819                } else if let Some(ms) = timed_out {
820                    #[cfg(feature = "std")]
821                    const CONTROLLER: &str =
822                        "the PROPTEST_MAX_SHRINK_TIME environment \
823                         variable or ProptestConfig.max_shrink_time";
824                    #[cfg(feature = "std")]
825                    let current = self.config.max_shrink_time;
826                    #[cfg(not(feature = "std"))]
827                    const CONTROLLER: &str = "(not configurable in no_std)";
828                    #[cfg(not(feature = "std"))]
829                    let current = 0;
830                    verbose_message!(
831                        self,
832                        ALWAYS,
833                        "Aborting shrinking after taking too long: {} ms \
834                         (set {} to a large(r) value to shrink more; current \
835                         configuration: {} ms)",
836                        ms,
837                        CONTROLLER,
838                        current
839                    );
840                    true
841                } else {
842                    false
843                };
844
845                if bail {
846                    // Move back to the most recent failing case
847                    while case.complicate() {
848                        fork_output.append(&Ok(()));
849                    }
850                    break;
851                }
852
853                iterations += 1;
854
855                let result = call_test(
856                    self,
857                    case.current(),
858                    &test,
859                    replay_from_fork,
860                    result_cache,
861                    fork_output,
862                    is_from_persisted_seed,
863                );
864
865                match result {
866                    // Rejections are effectively a pass here,
867                    // since they indicate that any behaviour of
868                    // the function under test is acceptable.
869                    Ok(_) | Err(TestCaseError::Reject(..)) => {
870                        if !case.complicate() {
871                            verbose_message!(
872                                self,
873                                TRACE,
874                                "Cannot complicate further"
875                            );
876
877                            break;
878                        }
879                    }
880                    Err(TestCaseError::Fail(why)) => {
881                        last_failure = Some(why);
882                        if !case.simplify() {
883                            verbose_message!(
884                                self,
885                                TRACE,
886                                "Cannot simplify further"
887                            );
888
889                            break;
890                        }
891                    }
892                }
893            }
894        }
895
896        last_failure
897    }
898
899    /// Update the state to account for a local rejection from `whence`, and
900    /// return `Ok` if the caller should keep going or `Err` to abort.
901    pub fn reject_local(
902        &mut self,
903        whence: impl Into<Reason>,
904    ) -> Result<(), Reason> {
905        if self.local_rejects >= self.config.max_local_rejects {
906            Err("Too many local rejects".into())
907        } else {
908            self.local_rejects += 1;
909            Self::insert_or_increment(
910                &mut self.local_reject_detail,
911                whence.into(),
912            );
913            Ok(())
914        }
915    }
916
917    /// Update the state to account for a global rejection from `whence`, and
918    /// return `Ok` if the caller should keep going or `Err` to abort.
919    fn reject_global<T>(&mut self, whence: Reason) -> Result<(), TestError<T>> {
920        if self.global_rejects >= self.config.max_global_rejects {
921            Err(TestError::Abort("Too many global rejects".into()))
922        } else {
923            self.global_rejects += 1;
924            Self::insert_or_increment(&mut self.global_reject_detail, whence);
925            Ok(())
926        }
927    }
928
929    /// Insert 1 or increment the rejection detail at key for whence.
930    fn insert_or_increment(into: &mut RejectionDetail, whence: Reason) {
931        into.entry(whence)
932            .and_modify(|count| *count += 1)
933            .or_insert(1);
934    }
935
936    /// Increment the counter of flat map regenerations and return whether it
937    /// is still under the configured limit.
938    pub fn flat_map_regen(&self) -> bool {
939        self.flat_map_regens.fetch_add(1, SeqCst)
940            < self.config.max_flat_map_regens as usize
941    }
942
943    fn new_cache(&self) -> Box<dyn ResultCache> {
944        (self.config.result_cache)()
945    }
946}
947
948#[cfg(feature = "fork")]
949fn init_replay(rng: &mut TestRng) -> (Vec<TestCaseResult>, ForkOutput) {
950    use crate::test_runner::replay::{open_file, Replay, ReplayFileStatus::*};
951
952    if let Some(path) = env::var_os(ENV_FORK_FILE) {
953        let mut file = open_file(&path).expect("Failed to open replay file");
954        let loaded =
955            Replay::parse_from(&mut file).expect("Failed to read replay file");
956        match loaded {
957            InProgress(replay) => {
958                rng.set_seed(replay.seed);
959                (replay.steps, ForkOutput { file: Some(file) })
960            }
961
962            Terminated(_) => {
963                panic!("Replay file for child process is terminated?")
964            }
965
966            Corrupt => panic!("Replay file for child process is corrupt"),
967        }
968    } else {
969        (vec![], ForkOutput::empty())
970    }
971}
972
973#[cfg(not(feature = "fork"))]
974fn init_replay(
975    _rng: &mut TestRng,
976) -> (iter::Empty<TestCaseResult>, ForkOutput) {
977    (iter::empty(), ForkOutput::empty())
978}
979
980#[cfg(feature = "fork")]
981fn await_child_without_timeout(
982    child: &mut rusty_fork::ChildWrapper,
983) -> (Option<TestCaseError>, Option<u64>) {
984    let status = child.wait().expect("Failed to wait for child process");
985
986    if status.success() {
987        (None, None)
988    } else {
989        (
990            Some(TestCaseError::fail(format!(
991                "Child process exited with {}",
992                status
993            ))),
994            None,
995        )
996    }
997}
998
999#[cfg(all(feature = "fork", not(feature = "timeout")))]
1000fn await_child(
1001    child: &mut rusty_fork::ChildWrapper,
1002    _: &mut tempfile::NamedTempFile,
1003    _timeout: u32,
1004) -> (Option<TestCaseError>, Option<u64>) {
1005    await_child_without_timeout(child)
1006}
1007
1008#[cfg(all(feature = "fork", feature = "timeout"))]
1009fn await_child(
1010    child: &mut rusty_fork::ChildWrapper,
1011    forkfile: &mut tempfile::NamedTempFile,
1012    timeout: u32,
1013) -> (Option<TestCaseError>, Option<u64>) {
1014    use std::time::Duration;
1015
1016    if 0 == timeout {
1017        return await_child_without_timeout(child);
1018    }
1019
1020    // The child can run for longer than the timeout since it may run
1021    // multiple tests. Each time the timeout expires, we check whether the
1022    // file has grown larger. If it has, we allow the child to keep running
1023    // until the next timeout.
1024    let mut last_forkfile_len = forkfile
1025        .as_file()
1026        .metadata()
1027        .map(|md| md.len())
1028        .unwrap_or(0);
1029
1030    loop {
1031        if let Some(status) = child
1032            .wait_timeout(Duration::from_millis(timeout.into()))
1033            .expect("Failed to wait for child process")
1034        {
1035            if status.success() {
1036                return (None, None);
1037            } else {
1038                return (
1039                    Some(TestCaseError::fail(format!(
1040                        "Child process exited with {}",
1041                        status
1042                    ))),
1043                    None,
1044                );
1045            }
1046        }
1047
1048        let current_len = forkfile
1049            .as_file()
1050            .metadata()
1051            .map(|md| md.len())
1052            .unwrap_or(0);
1053        // If we've gone a full timeout period without the file growing,
1054        // fail the test and kill the child.
1055        if current_len <= last_forkfile_len {
1056            return (
1057                Some(TestCaseError::fail(format!(
1058                    "Timed out waiting for child process"
1059                ))),
1060                Some(current_len),
1061            );
1062        } else {
1063            last_forkfile_len = current_len;
1064        }
1065    }
1066}
1067
1068#[cfg(test)]
1069mod test {
1070    use std::cell::Cell;
1071    use std::fs;
1072
1073    use super::*;
1074    use crate::strategy::Strategy;
1075    use crate::test_runner::{FileFailurePersistence, RngAlgorithm, TestRng};
1076
1077    #[test]
1078    fn gives_up_after_too_many_rejections() {
1079        let config = Config::default();
1080        let mut runner = TestRunner::new(config.clone());
1081        let runs = Cell::new(0);
1082        let result = runner.run(&(0u32..), |_| {
1083            runs.set(runs.get() + 1);
1084            Err(TestCaseError::reject("reject"))
1085        });
1086        match result {
1087            Err(TestError::Abort(_)) => (),
1088            e => panic!("Unexpected result: {:?}", e),
1089        }
1090        assert_eq!(config.max_global_rejects + 1, runs.get());
1091    }
1092
1093    #[test]
1094    fn test_pass() {
1095        let mut runner = TestRunner::default();
1096        let result = runner.run(&(1u32..), |v| {
1097            assert!(v > 0);
1098            Ok(())
1099        });
1100        assert_eq!(Ok(()), result);
1101    }
1102
1103    #[test]
1104    fn test_fail_via_result() {
1105        let mut runner = TestRunner::new(Config {
1106            failure_persistence: None,
1107            ..Config::default()
1108        });
1109        let result = runner.run(&(0u32..10u32), |v| {
1110            if v < 5 {
1111                Ok(())
1112            } else {
1113                Err(TestCaseError::fail("not less than 5"))
1114            }
1115        });
1116
1117        assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
1118    }
1119
1120    #[test]
1121    fn test_fail_via_panic() {
1122        let mut runner = TestRunner::new(Config {
1123            failure_persistence: None,
1124            ..Config::default()
1125        });
1126        let result = runner.run(&(0u32..10u32), |v| {
1127            assert!(v < 5, "not less than 5");
1128            Ok(())
1129        });
1130        assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
1131    }
1132
1133    #[test]
1134    fn persisted_cases_do_not_count_towards_total_cases() {
1135        const FILE: &'static str = "persistence-test.txt";
1136        let _ = fs::remove_file(FILE);
1137
1138        let config = Config {
1139            failure_persistence: Some(Box::new(
1140                FileFailurePersistence::Direct(FILE),
1141            )),
1142            cases: 1,
1143            ..Config::default()
1144        };
1145
1146        let max = 10_000_000i32;
1147        {
1148            TestRunner::new(config.clone())
1149                .run(&(0i32..max), |_v| {
1150                    Err(TestCaseError::Fail("persist a failure".into()))
1151                })
1152                .expect_err("didn't fail?");
1153        }
1154
1155        let run_count = RefCell::new(0);
1156        TestRunner::new(config.clone())
1157            .run(&(0i32..max), |_v| {
1158                *run_count.borrow_mut() += 1;
1159                Ok(())
1160            })
1161            .expect("should succeed");
1162
1163        // Persisted ran, and a new case ran, and only new case counts
1164        // against `cases: 1`.
1165        assert_eq!(run_count.into_inner(), 2);
1166    }
1167
1168    #[derive(Clone, Copy, PartialEq)]
1169    struct PoorlyBehavedDebug(i32);
1170    impl fmt::Debug for PoorlyBehavedDebug {
1171        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1172            write!(f, "\r\n{:?}\r\n", self.0)
1173        }
1174    }
1175
1176    #[test]
1177    fn failing_cases_persisted_and_reloaded() {
1178        const FILE: &'static str = "persistence-test.txt";
1179        let _ = fs::remove_file(FILE);
1180
1181        let max = 10_000_000i32;
1182        let input = (0i32..max).prop_map(PoorlyBehavedDebug);
1183        let config = Config {
1184            failure_persistence: Some(Box::new(
1185                FileFailurePersistence::Direct(FILE),
1186            )),
1187            ..Config::default()
1188        };
1189
1190        // First test with cases that fail above half max, and then below half
1191        // max, to ensure we can correctly parse both lines of the persistence
1192        // file.
1193        let first_sub_failure = {
1194            TestRunner::new(config.clone())
1195                .run(&input, |v| {
1196                    if v.0 < max / 2 {
1197                        Ok(())
1198                    } else {
1199                        Err(TestCaseError::Fail("too big".into()))
1200                    }
1201                })
1202                .expect_err("didn't fail?")
1203        };
1204        let first_super_failure = {
1205            TestRunner::new(config.clone())
1206                .run(&input, |v| {
1207                    if v.0 >= max / 2 {
1208                        Ok(())
1209                    } else {
1210                        Err(TestCaseError::Fail("too small".into()))
1211                    }
1212                })
1213                .expect_err("didn't fail?")
1214        };
1215        let second_sub_failure = {
1216            TestRunner::new(config.clone())
1217                .run(&input, |v| {
1218                    if v.0 < max / 2 {
1219                        Ok(())
1220                    } else {
1221                        Err(TestCaseError::Fail("too big".into()))
1222                    }
1223                })
1224                .expect_err("didn't fail?")
1225        };
1226        let second_super_failure = {
1227            TestRunner::new(config.clone())
1228                .run(&input, |v| {
1229                    if v.0 >= max / 2 {
1230                        Ok(())
1231                    } else {
1232                        Err(TestCaseError::Fail("too small".into()))
1233                    }
1234                })
1235                .expect_err("didn't fail?")
1236        };
1237
1238        assert_eq!(first_sub_failure, second_sub_failure);
1239        assert_eq!(first_super_failure, second_super_failure);
1240    }
1241
1242    #[test]
1243    fn new_rng_makes_separate_rng() {
1244        use rand::Rng;
1245        let mut runner = TestRunner::default();
1246        let from_1 = runner.new_rng().gen::<[u8; 16]>();
1247        let from_2 = runner.rng().gen::<[u8; 16]>();
1248        assert_ne!(from_1, from_2);
1249    }
1250
1251    #[test]
1252    fn record_rng_use() {
1253        use rand::Rng;
1254
1255        // create value with recorder rng
1256        let default_config = Config::default();
1257        let recorder_rng = TestRng::default_rng(RngAlgorithm::Recorder);
1258        let mut runner =
1259            TestRunner::new_with_rng(default_config.clone(), recorder_rng);
1260        let random_byte_array1 = runner.rng().gen::<[u8; 16]>();
1261        let bytes_used = runner.bytes_used();
1262        assert!(bytes_used.len() >= 16); // could use more bytes for some reason
1263
1264        // re-create value with pass-through rng
1265        let passthrough_rng =
1266            TestRng::from_seed(RngAlgorithm::PassThrough, &bytes_used);
1267        let mut runner =
1268            TestRunner::new_with_rng(default_config, passthrough_rng);
1269        let random_byte_array2 = runner.rng().gen::<[u8; 16]>();
1270
1271        // make sure the same value was created
1272        assert_eq!(random_byte_array1, random_byte_array2);
1273    }
1274
1275    #[cfg(feature = "fork")]
1276    #[test]
1277    fn run_successful_test_in_fork() {
1278        let mut runner = TestRunner::new(Config {
1279            fork: true,
1280            test_name: Some(concat!(
1281                module_path!(),
1282                "::run_successful_test_in_fork"
1283            )),
1284            ..Config::default()
1285        });
1286
1287        assert!(runner.run(&(0u32..1000), |_| Ok(())).is_ok());
1288    }
1289
1290    #[cfg(feature = "fork")]
1291    #[test]
1292    fn normal_failure_in_fork_results_in_correct_failure() {
1293        let mut runner = TestRunner::new(Config {
1294            fork: true,
1295            test_name: Some(concat!(
1296                module_path!(),
1297                "::normal_failure_in_fork_results_in_correct_failure"
1298            )),
1299            ..Config::default()
1300        });
1301
1302        let failure = runner
1303            .run(&(0u32..1000), |v| {
1304                prop_assert!(v < 500);
1305                Ok(())
1306            })
1307            .err()
1308            .unwrap();
1309
1310        match failure {
1311            TestError::Fail(_, value) => assert_eq!(500, value),
1312            failure => panic!("Unexpected failure: {:?}", failure),
1313        }
1314    }
1315
1316    #[cfg(feature = "fork")]
1317    #[test]
1318    fn nonsuccessful_exit_finds_correct_failure() {
1319        let mut runner = TestRunner::new(Config {
1320            fork: true,
1321            test_name: Some(concat!(
1322                module_path!(),
1323                "::nonsuccessful_exit_finds_correct_failure"
1324            )),
1325            ..Config::default()
1326        });
1327
1328        let failure = runner
1329            .run(&(0u32..1000), |v| {
1330                if v >= 500 {
1331                    ::std::process::exit(1);
1332                }
1333                Ok(())
1334            })
1335            .err()
1336            .unwrap();
1337
1338        match failure {
1339            TestError::Fail(_, value) => assert_eq!(500, value),
1340            failure => panic!("Unexpected failure: {:?}", failure),
1341        }
1342    }
1343
1344    #[cfg(feature = "fork")]
1345    #[test]
1346    fn spurious_exit_finds_correct_failure() {
1347        let mut runner = TestRunner::new(Config {
1348            fork: true,
1349            test_name: Some(concat!(
1350                module_path!(),
1351                "::spurious_exit_finds_correct_failure"
1352            )),
1353            ..Config::default()
1354        });
1355
1356        let failure = runner
1357            .run(&(0u32..1000), |v| {
1358                if v >= 500 {
1359                    ::std::process::exit(0);
1360                }
1361                Ok(())
1362            })
1363            .err()
1364            .unwrap();
1365
1366        match failure {
1367            TestError::Fail(_, value) => assert_eq!(500, value),
1368            failure => panic!("Unexpected failure: {:?}", failure),
1369        }
1370    }
1371
1372    #[cfg(feature = "timeout")]
1373    #[test]
1374    fn long_sleep_timeout_finds_correct_failure() {
1375        let mut runner = TestRunner::new(Config {
1376            fork: true,
1377            timeout: 500,
1378            test_name: Some(concat!(
1379                module_path!(),
1380                "::long_sleep_timeout_finds_correct_failure"
1381            )),
1382            ..Config::default()
1383        });
1384
1385        let failure = runner
1386            .run(&(0u32..1000), |v| {
1387                if v >= 500 {
1388                    ::std::thread::sleep(::std::time::Duration::from_millis(
1389                        10_000,
1390                    ));
1391                }
1392                Ok(())
1393            })
1394            .err()
1395            .unwrap();
1396
1397        match failure {
1398            TestError::Fail(_, value) => assert_eq!(500, value),
1399            failure => panic!("Unexpected failure: {:?}", failure),
1400        }
1401    }
1402
1403    #[cfg(feature = "timeout")]
1404    #[test]
1405    fn mid_sleep_timeout_finds_correct_failure() {
1406        let mut runner = TestRunner::new(Config {
1407            fork: true,
1408            timeout: 500,
1409            test_name: Some(concat!(
1410                module_path!(),
1411                "::mid_sleep_timeout_finds_correct_failure"
1412            )),
1413            ..Config::default()
1414        });
1415
1416        let failure = runner
1417            .run(&(0u32..1000), |v| {
1418                if v >= 500 {
1419                    // Sleep a little longer than the timeout. This means that
1420                    // sometimes the test case itself will return before the parent
1421                    // process has noticed the child is timing out, so it's up to
1422                    // the child to mark it as a failure.
1423                    ::std::thread::sleep(::std::time::Duration::from_millis(
1424                        600,
1425                    ));
1426                } else {
1427                    // Sleep a bit so that the parent and child timing don't stay
1428                    // in sync.
1429                    ::std::thread::sleep(::std::time::Duration::from_millis(
1430                        100,
1431                    ))
1432                }
1433                Ok(())
1434            })
1435            .err()
1436            .unwrap();
1437
1438        match failure {
1439            TestError::Fail(_, value) => assert_eq!(500, value),
1440            failure => panic!("Unexpected failure: {:?}", failure),
1441        }
1442    }
1443
1444    #[cfg(feature = "std")]
1445    #[test]
1446    fn duplicate_tests_not_run_with_basic_result_cache() {
1447        use std::cell::{Cell, RefCell};
1448        use std::collections::HashSet;
1449        use std::rc::Rc;
1450
1451        for _ in 0..256 {
1452            let mut runner = TestRunner::new(Config {
1453                failure_persistence: None,
1454                result_cache:
1455                    crate::test_runner::result_cache::basic_result_cache,
1456                ..Config::default()
1457            });
1458            let pass = Rc::new(Cell::new(true));
1459            let seen = Rc::new(RefCell::new(HashSet::new()));
1460            let result =
1461                runner.run(&(0u32..65536u32).prop_map(|v| v % 10), |val| {
1462                    if !seen.borrow_mut().insert(val) {
1463                        println!("Value {} seen more than once", val);
1464                        pass.set(false);
1465                    }
1466
1467                    prop_assert!(val <= 5);
1468                    Ok(())
1469                });
1470
1471            assert!(pass.get());
1472            if let Err(TestError::Fail(_, val)) = result {
1473                assert_eq!(6, val);
1474            } else {
1475                panic!("Incorrect result: {:?}", result);
1476            }
1477        }
1478    }
1479}
1480
1481#[cfg(all(feature = "fork", feature = "timeout", test))]
1482mod timeout_tests {
1483    use core::u32;
1484    use std::thread;
1485    use std::time::Duration;
1486
1487    use super::*;
1488
1489    rusty_fork_test! {
1490        #![rusty_fork(timeout_ms = 4_000)]
1491
1492        #[test]
1493        fn max_shrink_iters_works() {
1494            test_shrink_bail(Config {
1495                max_shrink_iters: 5,
1496                .. Config::default()
1497            });
1498        }
1499
1500        #[test]
1501        fn max_shrink_time_works() {
1502            test_shrink_bail(Config {
1503                max_shrink_time: 1000,
1504                .. Config::default()
1505            });
1506        }
1507
1508        #[test]
1509        fn max_shrink_iters_works_with_forking() {
1510            test_shrink_bail(Config {
1511                fork: true,
1512                test_name: Some(
1513                    concat!(module_path!(),
1514                            "::max_shrink_iters_works_with_forking")),
1515                max_shrink_time: 1000,
1516                .. Config::default()
1517            });
1518        }
1519
1520        #[test]
1521        fn detects_child_failure_to_start() {
1522            let mut runner = TestRunner::new(Config {
1523                timeout: 100,
1524                test_name: Some(
1525                    concat!(module_path!(),
1526                            "::detects_child_failure_to_start")),
1527                .. Config::default()
1528            });
1529            let result = runner.run(&Just(()).prop_map(|()| {
1530                thread::sleep(Duration::from_millis(200))
1531            }), Ok);
1532
1533            if let Err(TestError::Abort(_)) = result {
1534                // OK
1535            } else {
1536                panic!("Unexpected result: {:?}", result);
1537            }
1538        }
1539    }
1540
1541    fn test_shrink_bail(config: Config) {
1542        let mut runner = TestRunner::new(config);
1543        let result = runner.run(&crate::num::u64::ANY, |v| {
1544            thread::sleep(Duration::from_millis(250));
1545            prop_assert!(v <= u32::MAX as u64);
1546            Ok(())
1547        });
1548
1549        if let Err(TestError::Fail(_, value)) = result {
1550            // Ensure the final value was in fact a failing case.
1551            assert!(value > u32::MAX as u64);
1552        } else {
1553            panic!("Unexpected result: {:?}", result);
1554        }
1555    }
1556}