proptest/test_runner/
runner.rs

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