1use 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;
42pub 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#[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
118impl 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 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 |_| { },
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 #[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 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 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 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 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 pub fn rng(&mut self) -> &mut TestRng {
374 &mut self.rng
375 }
376
377 pub fn new_rng(&mut self) -> TestRng {
380 self.rng.gen_rng()
381 }
382
383 pub fn config(&self) -> &Config {
385 &self.config
386 }
387
388 pub fn bytes_used(&self) -> Vec<u8> {
395 self.rng.bytes_used()
396 }
397
398 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); 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 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 ::std::thread::sleep(::std::time::Duration::from_millis(
1424 600,
1425 ));
1426 } else {
1427 ::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 } 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 assert!(value > u32::MAX as u64);
1552 } else {
1553 panic!("Unexpected result: {:?}", result);
1554 }
1555 }
1556}