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 seed = config.rng_seed;
318 let algorithm = config.rng_algorithm;
319 TestRunner::new_with_rng(config, TestRng::default_rng(seed, algorithm))
320 }
321
322 pub fn deterministic() -> Self {
338 let config = Config::default();
339 let algorithm = config.rng_algorithm;
340 TestRunner::new_with_rng(config, TestRng::deterministic_rng(algorithm))
341 }
342
343 pub fn new_with_rng(config: Config, rng: TestRng) -> Self {
345 TestRunner {
346 config: config,
347 successes: 0,
348 local_rejects: 0,
349 global_rejects: 0,
350 rng: rng,
351 flat_map_regens: Arc::new(AtomicUsize::new(0)),
352 local_reject_detail: BTreeMap::new(),
353 global_reject_detail: BTreeMap::new(),
354 }
355 }
356
357 pub(crate) fn partial_clone(&mut self) -> Self {
361 TestRunner {
362 config: self.config.clone(),
363 successes: 0,
364 local_rejects: 0,
365 global_rejects: 0,
366 rng: self.new_rng(),
367 flat_map_regens: Arc::clone(&self.flat_map_regens),
368 local_reject_detail: BTreeMap::new(),
369 global_reject_detail: BTreeMap::new(),
370 }
371 }
372
373 pub fn rng(&mut self) -> &mut TestRng {
375 &mut self.rng
376 }
377
378 pub fn new_rng(&mut self) -> TestRng {
381 self.rng.gen_rng()
382 }
383
384 pub fn config(&self) -> &Config {
386 &self.config
387 }
388
389 pub fn bytes_used(&self) -> Vec<u8> {
396 self.rng.bytes_used()
397 }
398
399 pub fn run<S: Strategy>(
411 &mut self,
412 strategy: &S,
413 test: impl Fn(S::Value) -> TestCaseResult,
414 ) -> TestRunResult<S> {
415 if self.config.fork() {
416 self.run_in_fork(strategy, test)
417 } else {
418 self.run_in_process(strategy, test)
419 }
420 }
421
422 #[cfg(not(feature = "fork"))]
423 fn run_in_fork<S: Strategy>(
424 &mut self,
425 _: &S,
426 _: impl Fn(S::Value) -> TestCaseResult,
427 ) -> TestRunResult<S> {
428 unreachable!()
429 }
430
431 #[cfg(feature = "fork")]
432 fn run_in_fork<S: Strategy>(
433 &mut self,
434 strategy: &S,
435 test: impl Fn(S::Value) -> TestCaseResult,
436 ) -> TestRunResult<S> {
437 let mut test = Some(test);
438
439 let test_name = rusty_fork::fork_test::fix_module_path(
440 self.config
441 .test_name
442 .expect("Must supply test_name when forking enabled"),
443 );
444 let forkfile: RefCell<Option<tempfile::NamedTempFile>> =
445 RefCell::new(None);
446 let init_forkfile_size = Cell::new(0u64);
447 let seed = self.rng.new_rng_seed();
448 let mut replay = replay::Replay {
449 seed,
450 steps: vec![],
451 };
452 let mut child_count = 0;
453 let timeout = self.config.timeout();
454
455 fn forkfile_size(forkfile: &Option<tempfile::NamedTempFile>) -> u64 {
456 forkfile.as_ref().map_or(0, |ff| {
457 ff.as_file().metadata().map(|md| md.len()).unwrap_or(0)
458 })
459 }
460
461 loop {
462 let (child_error, last_fork_file_len) = rusty_fork::fork(
463 test_name,
464 rusty_fork_id!(),
465 |cmd| {
466 let mut forkfile = forkfile.borrow_mut();
467 if forkfile.is_none() {
468 *forkfile =
469 Some(tempfile::NamedTempFile::new().expect(
470 "Failed to create temporary file for fork",
471 ));
472 replay.init_file(forkfile.as_mut().unwrap()).expect(
473 "Failed to initialise temporary file for fork",
474 );
475 }
476
477 init_forkfile_size.set(forkfile_size(&forkfile));
478
479 cmd.env(ENV_FORK_FILE, forkfile.as_ref().unwrap().path());
480 },
481 |child, _| {
482 await_child(
483 child,
484 &mut forkfile.borrow_mut().as_mut().unwrap(),
485 timeout,
486 )
487 },
488 || match self.run_in_process(strategy, test.take().unwrap()) {
489 Ok(_) => (),
490 Err(e) => panic!(
491 "Test failed normally in child process.\n{}\n{}",
492 e, self
493 ),
494 },
495 )
496 .expect("Fork failed");
497
498 let parsed = replay::Replay::parse_from(
499 &mut forkfile.borrow_mut().as_mut().unwrap(),
500 )
501 .expect("Failed to re-read fork file");
502 match parsed {
503 replay::ReplayFileStatus::InProgress(new_replay) => {
504 replay = new_replay
505 }
506 replay::ReplayFileStatus::Terminated(new_replay) => {
507 replay = new_replay;
508 break;
509 }
510 replay::ReplayFileStatus::Corrupt => {
511 panic!("Child process corrupted replay file")
512 }
513 }
514
515 let curr_forkfile_size = forkfile_size(&forkfile.borrow());
516
517 if curr_forkfile_size == init_forkfile_size.get() {
521 return Err(TestError::Abort(
522 "Child process crashed or timed out before the first test \
523 started running; giving up."
524 .into(),
525 ));
526 }
527
528 if last_fork_file_len.map_or(true, |last_fork_file_len| {
536 last_fork_file_len == curr_forkfile_size
537 }) {
538 let error = Err(child_error.unwrap_or(TestCaseError::fail(
539 "Child process was terminated abruptly \
540 but with successful status",
541 )));
542 replay::append(forkfile.borrow_mut().as_mut().unwrap(), &error)
543 .expect("Failed to append to replay file");
544 replay.steps.push(error);
545 }
546
547 child_count += 1;
550 if child_count >= 10000 {
551 return Err(TestError::Abort(
552 "Giving up after 10000 child processes crashed".into(),
553 ));
554 }
555 }
556
557 self.rng.set_seed(replay.seed);
561 self.run_in_process_with_replay(
562 strategy,
563 |_| panic!("Ran past the end of the replay"),
564 replay.steps.into_iter(),
565 ForkOutput::empty(),
566 )
567 }
568
569 fn run_in_process<S: Strategy>(
570 &mut self,
571 strategy: &S,
572 test: impl Fn(S::Value) -> TestCaseResult,
573 ) -> TestRunResult<S> {
574 let (replay_steps, fork_output) = init_replay(&mut self.rng);
575 self.run_in_process_with_replay(
576 strategy,
577 test,
578 replay_steps.into_iter(),
579 fork_output,
580 )
581 }
582
583 fn run_in_process_with_replay<S: Strategy>(
584 &mut self,
585 strategy: &S,
586 test: impl Fn(S::Value) -> TestCaseResult,
587 mut replay_from_fork: impl Iterator<Item = TestCaseResult>,
588 mut fork_output: ForkOutput,
589 ) -> TestRunResult<S> {
590 let old_rng = self.rng.clone();
591
592 let persisted_failure_seeds: Vec<PersistedSeed> = self
593 .config
594 .failure_persistence
595 .as_ref()
596 .map(|f| f.load_persisted_failures2(self.config.source_file))
597 .unwrap_or_default();
598
599 let mut result_cache = self.new_cache();
600
601 for PersistedSeed(persisted_seed) in
602 persisted_failure_seeds.into_iter().rev()
603 {
604 self.rng.set_seed(persisted_seed);
605 let result = self.gen_and_run_case(
606 strategy,
607 &test,
608 &mut replay_from_fork,
609 &mut *result_cache,
610 &mut fork_output,
611 true,
612 );
613
614 if let Err(e) = result {
618 fork_output.terminate();
619 return Err(e);
620 }
621 }
622 self.rng = old_rng;
623
624 while self.successes < self.config.cases {
625 let seed = self.rng.gen_get_seed();
628 let result = self.gen_and_run_case(
629 strategy,
630 &test,
631 &mut replay_from_fork,
632 &mut *result_cache,
633 &mut fork_output,
634 false,
635 );
636 if let Err(TestError::Fail(_, ref value)) = result {
637 if let Some(ref mut failure_persistence) =
638 self.config.failure_persistence
639 {
640 let source_file = &self.config.source_file;
641
642 if !fork_output.is_in_fork() {
646 failure_persistence.save_persisted_failure2(
647 *source_file,
648 PersistedSeed(seed),
649 value,
650 );
651 }
652 }
653 }
654
655 if let Err(e) = result {
656 fork_output.terminate();
657 return Err(e.into());
658 }
659 }
660
661 fork_output.terminate();
662 Ok(())
663 }
664
665 fn gen_and_run_case<S: Strategy>(
666 &mut self,
667 strategy: &S,
668 f: &impl Fn(S::Value) -> TestCaseResult,
669 replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
670 result_cache: &mut dyn ResultCache,
671 fork_output: &mut ForkOutput,
672 is_from_persisted_seed: bool,
673 ) -> TestRunResult<S> {
674 let case = unwrap_or!(strategy.new_tree(self), msg =>
675 return Err(TestError::Abort(msg)));
676
677 let ok_type = self.run_one_with_replay(
680 case,
681 f,
682 replay_from_fork,
683 result_cache,
684 fork_output,
685 is_from_persisted_seed,
686 )?;
687 match ok_type {
688 TestCaseOk::NewCaseSuccess | TestCaseOk::ReplayFromForkSuccess => {
689 self.successes += 1
690 }
691 TestCaseOk::PersistedCaseSuccess
692 | TestCaseOk::CacheHitSuccess
693 | TestCaseOk::Reject => (),
694 }
695
696 Ok(())
697 }
698
699 pub fn run_one<V: ValueTree>(
709 &mut self,
710 case: V,
711 test: impl Fn(V::Value) -> TestCaseResult,
712 ) -> Result<bool, TestError<V::Value>> {
713 let mut result_cache = self.new_cache();
714 self.run_one_with_replay(
715 case,
716 test,
717 &mut iter::empty::<TestCaseResult>().fuse(),
718 &mut *result_cache,
719 &mut ForkOutput::empty(),
720 false,
721 )
722 .map(|ok_type| match ok_type {
723 TestCaseOk::Reject => false,
724 _ => true,
725 })
726 }
727
728 fn run_one_with_replay<V: ValueTree>(
729 &mut self,
730 mut case: V,
731 test: impl Fn(V::Value) -> TestCaseResult,
732 replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
733 result_cache: &mut dyn ResultCache,
734 fork_output: &mut ForkOutput,
735 is_from_persisted_seed: bool,
736 ) -> Result<TestCaseOk, TestError<V::Value>> {
737 let result = call_test(
738 self,
739 case.current(),
740 &test,
741 replay_from_fork,
742 result_cache,
743 fork_output,
744 is_from_persisted_seed,
745 );
746
747 match result {
748 Ok(success_type) => Ok(success_type),
749 Err(TestCaseError::Fail(why)) => {
750 let why = self
751 .shrink(
752 &mut case,
753 test,
754 replay_from_fork,
755 result_cache,
756 fork_output,
757 is_from_persisted_seed,
758 )
759 .unwrap_or(why);
760 Err(TestError::Fail(why, case.current()))
761 }
762 Err(TestCaseError::Reject(whence)) => {
763 self.reject_global(whence)?;
764 Ok(TestCaseOk::Reject)
765 }
766 }
767 }
768
769 fn shrink<V: ValueTree>(
770 &mut self,
771 case: &mut V,
772 test: impl Fn(V::Value) -> TestCaseResult,
773 replay_from_fork: &mut impl Iterator<Item = TestCaseResult>,
774 result_cache: &mut dyn ResultCache,
775 fork_output: &mut ForkOutput,
776 is_from_persisted_seed: bool,
777 ) -> Option<Reason> {
778 if self.config.max_shrink_iters == 0 {
780 verbose_message!(
781 self,
782 INFO_LOG,
783 "Shrinking disabled by configuration"
784 );
785 return None
786 }
787
788 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
789 let start_time = std::time::Instant::now();
790 let mut last_failure = None;
791 let mut iterations = 0;
792
793 verbose_message!(self, TRACE, "Starting shrinking");
794
795 if case.simplify() {
796 loop {
797 let mut timed_out: Option<u64> = None;
798 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
799 if self.config.max_shrink_time > 0 {
800 let elapsed = start_time.elapsed();
801 let elapsed_ms = elapsed
802 .as_secs()
803 .saturating_mul(1000)
804 .saturating_add(elapsed.subsec_millis().into());
805 if elapsed_ms > self.config.max_shrink_time as u64 {
806 timed_out = Some(elapsed_ms);
807 }
808 }
809
810 let bail = if iterations >= self.config.max_shrink_iters() {
811 #[cfg(feature = "std")]
812 const CONTROLLER: &str =
813 "the PROPTEST_MAX_SHRINK_ITERS environment \
814 variable or ProptestConfig.max_shrink_iters";
815 #[cfg(not(feature = "std"))]
816 const CONTROLLER: &str = "ProptestConfig.max_shrink_iters";
817 verbose_message!(
818 self,
819 ALWAYS,
820 "Aborting shrinking after {} iterations (set {} \
821 to a large(r) value to shrink more; current \
822 configuration: {} iterations)",
823 CONTROLLER,
824 self.config.max_shrink_iters(),
825 iterations
826 );
827 true
828 } else if let Some(ms) = timed_out {
829 #[cfg(feature = "std")]
830 const CONTROLLER: &str =
831 "the PROPTEST_MAX_SHRINK_TIME environment \
832 variable or ProptestConfig.max_shrink_time";
833 #[cfg(feature = "std")]
834 let current = self.config.max_shrink_time;
835 #[cfg(not(feature = "std"))]
836 const CONTROLLER: &str = "(not configurable in no_std)";
837 #[cfg(not(feature = "std"))]
838 let current = 0;
839 verbose_message!(
840 self,
841 ALWAYS,
842 "Aborting shrinking after taking too long: {} ms \
843 (set {} to a large(r) value to shrink more; current \
844 configuration: {} ms)",
845 ms,
846 CONTROLLER,
847 current
848 );
849 true
850 } else {
851 false
852 };
853
854 if bail {
855 while case.complicate() {
857 fork_output.append(&Ok(()));
858 }
859 break;
860 }
861
862 iterations += 1;
863
864 let result = call_test(
865 self,
866 case.current(),
867 &test,
868 replay_from_fork,
869 result_cache,
870 fork_output,
871 is_from_persisted_seed,
872 );
873
874 match result {
875 Ok(_) | Err(TestCaseError::Reject(..)) => {
879 if !case.complicate() {
880 verbose_message!(
881 self,
882 TRACE,
883 "Cannot complicate further"
884 );
885
886 break;
887 }
888 }
889 Err(TestCaseError::Fail(why)) => {
890 last_failure = Some(why);
891 if !case.simplify() {
892 verbose_message!(
893 self,
894 TRACE,
895 "Cannot simplify further"
896 );
897
898 break;
899 }
900 }
901 }
902 }
903 }
904
905 last_failure
906 }
907
908 pub fn reject_local(
911 &mut self,
912 whence: impl Into<Reason>,
913 ) -> Result<(), Reason> {
914 if self.local_rejects >= self.config.max_local_rejects {
915 Err("Too many local rejects".into())
916 } else {
917 self.local_rejects += 1;
918 Self::insert_or_increment(
919 &mut self.local_reject_detail,
920 whence.into(),
921 );
922 Ok(())
923 }
924 }
925
926 fn reject_global<T>(&mut self, whence: Reason) -> Result<(), TestError<T>> {
929 if self.global_rejects >= self.config.max_global_rejects {
930 Err(TestError::Abort("Too many global rejects".into()))
931 } else {
932 self.global_rejects += 1;
933 Self::insert_or_increment(&mut self.global_reject_detail, whence);
934 Ok(())
935 }
936 }
937
938 fn insert_or_increment(into: &mut RejectionDetail, whence: Reason) {
940 into.entry(whence)
941 .and_modify(|count| *count += 1)
942 .or_insert(1);
943 }
944
945 pub fn flat_map_regen(&self) -> bool {
948 self.flat_map_regens.fetch_add(1, SeqCst)
949 < self.config.max_flat_map_regens as usize
950 }
951
952 fn new_cache(&self) -> Box<dyn ResultCache> {
953 (self.config.result_cache)()
954 }
955}
956
957#[cfg(feature = "fork")]
958fn init_replay(rng: &mut TestRng) -> (Vec<TestCaseResult>, ForkOutput) {
959 use crate::test_runner::replay::{open_file, Replay, ReplayFileStatus::*};
960
961 if let Some(path) = env::var_os(ENV_FORK_FILE) {
962 let mut file = open_file(&path).expect("Failed to open replay file");
963 let loaded =
964 Replay::parse_from(&mut file).expect("Failed to read replay file");
965 match loaded {
966 InProgress(replay) => {
967 rng.set_seed(replay.seed);
968 (replay.steps, ForkOutput { file: Some(file) })
969 }
970
971 Terminated(_) => {
972 panic!("Replay file for child process is terminated?")
973 }
974
975 Corrupt => panic!("Replay file for child process is corrupt"),
976 }
977 } else {
978 (vec![], ForkOutput::empty())
979 }
980}
981
982#[cfg(not(feature = "fork"))]
983fn init_replay(
984 _rng: &mut TestRng,
985) -> (iter::Empty<TestCaseResult>, ForkOutput) {
986 (iter::empty(), ForkOutput::empty())
987}
988
989#[cfg(feature = "fork")]
990fn await_child_without_timeout(
991 child: &mut rusty_fork::ChildWrapper,
992) -> (Option<TestCaseError>, Option<u64>) {
993 let status = child.wait().expect("Failed to wait for child process");
994
995 if status.success() {
996 (None, None)
997 } else {
998 (
999 Some(TestCaseError::fail(format!(
1000 "Child process exited with {}",
1001 status
1002 ))),
1003 None,
1004 )
1005 }
1006}
1007
1008#[cfg(all(feature = "fork", not(feature = "timeout")))]
1009fn await_child(
1010 child: &mut rusty_fork::ChildWrapper,
1011 _: &mut tempfile::NamedTempFile,
1012 _timeout: u32,
1013) -> (Option<TestCaseError>, Option<u64>) {
1014 await_child_without_timeout(child)
1015}
1016
1017#[cfg(all(feature = "fork", feature = "timeout"))]
1018fn await_child(
1019 child: &mut rusty_fork::ChildWrapper,
1020 forkfile: &mut tempfile::NamedTempFile,
1021 timeout: u32,
1022) -> (Option<TestCaseError>, Option<u64>) {
1023 use std::time::Duration;
1024
1025 if 0 == timeout {
1026 return await_child_without_timeout(child);
1027 }
1028
1029 let mut last_forkfile_len = forkfile
1034 .as_file()
1035 .metadata()
1036 .map(|md| md.len())
1037 .unwrap_or(0);
1038
1039 loop {
1040 if let Some(status) = child
1041 .wait_timeout(Duration::from_millis(timeout.into()))
1042 .expect("Failed to wait for child process")
1043 {
1044 if status.success() {
1045 return (None, None);
1046 } else {
1047 return (
1048 Some(TestCaseError::fail(format!(
1049 "Child process exited with {}",
1050 status
1051 ))),
1052 None,
1053 );
1054 }
1055 }
1056
1057 let current_len = forkfile
1058 .as_file()
1059 .metadata()
1060 .map(|md| md.len())
1061 .unwrap_or(0);
1062 if current_len <= last_forkfile_len {
1065 return (
1066 Some(TestCaseError::fail(format!(
1067 "Timed out waiting for child process"
1068 ))),
1069 Some(current_len),
1070 );
1071 } else {
1072 last_forkfile_len = current_len;
1073 }
1074 }
1075}
1076
1077#[cfg(test)]
1078mod test {
1079 use std::cell::Cell;
1080 use std::fs;
1081
1082 use super::*;
1083 use crate::strategy::Strategy;
1084 use crate::test_runner::{FileFailurePersistence, RngAlgorithm, TestRng};
1085
1086 #[test]
1087 fn gives_up_after_too_many_rejections() {
1088 let config = Config::default();
1089 let mut runner = TestRunner::new(config.clone());
1090 let runs = Cell::new(0);
1091 let result = runner.run(&(0u32..), |_| {
1092 runs.set(runs.get() + 1);
1093 Err(TestCaseError::reject("reject"))
1094 });
1095 match result {
1096 Err(TestError::Abort(_)) => (),
1097 e => panic!("Unexpected result: {:?}", e),
1098 }
1099 assert_eq!(config.max_global_rejects + 1, runs.get());
1100 }
1101
1102 #[test]
1103 fn test_pass() {
1104 let mut runner = TestRunner::default();
1105 let result = runner.run(&(1u32..), |v| {
1106 assert!(v > 0);
1107 Ok(())
1108 });
1109 assert_eq!(Ok(()), result);
1110 }
1111
1112 #[test]
1113 fn test_fail_via_result() {
1114 let mut runner = TestRunner::new(Config {
1115 failure_persistence: None,
1116 ..Config::default()
1117 });
1118 let result = runner.run(&(0u32..10u32), |v| {
1119 if v < 5 {
1120 Ok(())
1121 } else {
1122 Err(TestCaseError::fail("not less than 5"))
1123 }
1124 });
1125
1126 assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
1127 }
1128
1129 #[test]
1130 fn test_fail_via_panic() {
1131 let mut runner = TestRunner::new(Config {
1132 failure_persistence: None,
1133 ..Config::default()
1134 });
1135 let result = runner.run(&(0u32..10u32), |v| {
1136 assert!(v < 5, "not less than 5");
1137 Ok(())
1138 });
1139 assert_eq!(Err(TestError::Fail("not less than 5".into(), 5)), result);
1140 }
1141
1142 #[test]
1143 fn persisted_cases_do_not_count_towards_total_cases() {
1144 const FILE: &'static str = "persistence-test.txt";
1145 let _ = fs::remove_file(FILE);
1146
1147 let config = Config {
1148 failure_persistence: Some(Box::new(
1149 FileFailurePersistence::Direct(FILE),
1150 )),
1151 cases: 1,
1152 ..Config::default()
1153 };
1154
1155 let max = 10_000_000i32;
1156 {
1157 TestRunner::new(config.clone())
1158 .run(&(0i32..max), |_v| {
1159 Err(TestCaseError::Fail("persist a failure".into()))
1160 })
1161 .expect_err("didn't fail?");
1162 }
1163
1164 let run_count = RefCell::new(0);
1165 TestRunner::new(config.clone())
1166 .run(&(0i32..max), |_v| {
1167 *run_count.borrow_mut() += 1;
1168 Ok(())
1169 })
1170 .expect("should succeed");
1171
1172 assert_eq!(run_count.into_inner(), 2);
1175 }
1176
1177 #[derive(Clone, Copy, PartialEq)]
1178 struct PoorlyBehavedDebug(i32);
1179 impl fmt::Debug for PoorlyBehavedDebug {
1180 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1181 write!(f, "\r\n{:?}\r\n", self.0)
1182 }
1183 }
1184
1185 #[test]
1186 fn failing_cases_persisted_and_reloaded() {
1187 const FILE: &'static str = "persistence-test.txt";
1188 let _ = fs::remove_file(FILE);
1189
1190 let max = 10_000_000i32;
1191 let input = (0i32..max).prop_map(PoorlyBehavedDebug);
1192 let config = Config {
1193 failure_persistence: Some(Box::new(
1194 FileFailurePersistence::Direct(FILE),
1195 )),
1196 ..Config::default()
1197 };
1198
1199 let first_sub_failure = {
1203 TestRunner::new(config.clone())
1204 .run(&input, |v| {
1205 if v.0 < max / 2 {
1206 Ok(())
1207 } else {
1208 Err(TestCaseError::Fail("too big".into()))
1209 }
1210 })
1211 .expect_err("didn't fail?")
1212 };
1213 let first_super_failure = {
1214 TestRunner::new(config.clone())
1215 .run(&input, |v| {
1216 if v.0 >= max / 2 {
1217 Ok(())
1218 } else {
1219 Err(TestCaseError::Fail("too small".into()))
1220 }
1221 })
1222 .expect_err("didn't fail?")
1223 };
1224 let second_sub_failure = {
1225 TestRunner::new(config.clone())
1226 .run(&input, |v| {
1227 if v.0 < max / 2 {
1228 Ok(())
1229 } else {
1230 Err(TestCaseError::Fail("too big".into()))
1231 }
1232 })
1233 .expect_err("didn't fail?")
1234 };
1235 let second_super_failure = {
1236 TestRunner::new(config.clone())
1237 .run(&input, |v| {
1238 if v.0 >= max / 2 {
1239 Ok(())
1240 } else {
1241 Err(TestCaseError::Fail("too small".into()))
1242 }
1243 })
1244 .expect_err("didn't fail?")
1245 };
1246
1247 assert_eq!(first_sub_failure, second_sub_failure);
1248 assert_eq!(first_super_failure, second_super_failure);
1249 }
1250
1251 #[test]
1252 fn new_rng_makes_separate_rng() {
1253 use rand::RngExt;
1254 let mut runner = TestRunner::default();
1255 let from_1 = runner.new_rng().random::<[u8; 16]>();
1256 let from_2 = runner.rng().random::<[u8; 16]>();
1257 assert_ne!(from_1, from_2);
1258 }
1259
1260 #[test]
1261 fn record_rng_use() {
1262 use rand::RngExt;
1263
1264 let default_config = Config::default();
1266 let recorder_rng = TestRng::default_rng(RngSeed::Random, RngAlgorithm::Recorder);
1267 let mut runner =
1268 TestRunner::new_with_rng(default_config.clone(), recorder_rng);
1269 let random_byte_array1 = runner.rng().random::<[u8; 16]>();
1270 let bytes_used = runner.bytes_used();
1271 assert!(bytes_used.len() >= 16); let passthrough_rng =
1275 TestRng::from_seed(RngAlgorithm::PassThrough, &bytes_used);
1276 let mut runner =
1277 TestRunner::new_with_rng(default_config, passthrough_rng);
1278 let random_byte_array2 = runner.rng().random::<[u8; 16]>();
1279
1280 assert_eq!(random_byte_array1, random_byte_array2);
1282 }
1283
1284 #[cfg(feature = "fork")]
1285 #[test]
1286 fn run_successful_test_in_fork() {
1287 let mut runner = TestRunner::new(Config {
1288 fork: true,
1289 test_name: Some(concat!(
1290 module_path!(),
1291 "::run_successful_test_in_fork"
1292 )),
1293 ..Config::default()
1294 });
1295
1296 assert!(runner.run(&(0u32..1000), |_| Ok(())).is_ok());
1297 }
1298
1299 #[cfg(feature = "fork")]
1300 #[test]
1301 fn normal_failure_in_fork_results_in_correct_failure() {
1302 let mut runner = TestRunner::new(Config {
1303 fork: true,
1304 test_name: Some(concat!(
1305 module_path!(),
1306 "::normal_failure_in_fork_results_in_correct_failure"
1307 )),
1308 ..Config::default()
1309 });
1310
1311 let failure = runner
1312 .run(&(0u32..1000), |v| {
1313 prop_assert!(v < 500);
1314 Ok(())
1315 })
1316 .err()
1317 .unwrap();
1318
1319 match failure {
1320 TestError::Fail(_, value) => assert_eq!(500, value),
1321 failure => panic!("Unexpected failure: {:?}", failure),
1322 }
1323 }
1324
1325 #[cfg(feature = "fork")]
1326 #[test]
1327 fn nonsuccessful_exit_finds_correct_failure() {
1328 let mut runner = TestRunner::new(Config {
1329 fork: true,
1330 test_name: Some(concat!(
1331 module_path!(),
1332 "::nonsuccessful_exit_finds_correct_failure"
1333 )),
1334 ..Config::default()
1335 });
1336
1337 let failure = runner
1338 .run(&(0u32..1000), |v| {
1339 if v >= 500 {
1340 ::std::process::exit(1);
1341 }
1342 Ok(())
1343 })
1344 .err()
1345 .unwrap();
1346
1347 match failure {
1348 TestError::Fail(_, value) => assert_eq!(500, value),
1349 failure => panic!("Unexpected failure: {:?}", failure),
1350 }
1351 }
1352
1353 #[cfg(feature = "fork")]
1354 #[test]
1355 fn spurious_exit_finds_correct_failure() {
1356 let mut runner = TestRunner::new(Config {
1357 fork: true,
1358 test_name: Some(concat!(
1359 module_path!(),
1360 "::spurious_exit_finds_correct_failure"
1361 )),
1362 ..Config::default()
1363 });
1364
1365 let failure = runner
1366 .run(&(0u32..1000), |v| {
1367 if v >= 500 {
1368 ::std::process::exit(0);
1369 }
1370 Ok(())
1371 })
1372 .err()
1373 .unwrap();
1374
1375 match failure {
1376 TestError::Fail(_, value) => assert_eq!(500, value),
1377 failure => panic!("Unexpected failure: {:?}", failure),
1378 }
1379 }
1380
1381 #[cfg(feature = "timeout")]
1382 #[test]
1383 fn long_sleep_timeout_finds_correct_failure() {
1384 let mut runner = TestRunner::new(Config {
1385 fork: true,
1386 timeout: 500,
1387 test_name: Some(concat!(
1388 module_path!(),
1389 "::long_sleep_timeout_finds_correct_failure"
1390 )),
1391 ..Config::default()
1392 });
1393
1394 let failure = runner
1395 .run(&(0u32..1000), |v| {
1396 if v >= 500 {
1397 ::std::thread::sleep(::std::time::Duration::from_millis(
1398 10_000,
1399 ));
1400 }
1401 Ok(())
1402 })
1403 .err()
1404 .unwrap();
1405
1406 match failure {
1407 TestError::Fail(_, value) => assert_eq!(500, value),
1408 failure => panic!("Unexpected failure: {:?}", failure),
1409 }
1410 }
1411
1412 #[cfg(feature = "timeout")]
1413 #[test]
1414 fn mid_sleep_timeout_finds_correct_failure() {
1415 let mut runner = TestRunner::new(Config {
1416 fork: true,
1417 timeout: 500,
1418 test_name: Some(concat!(
1419 module_path!(),
1420 "::mid_sleep_timeout_finds_correct_failure"
1421 )),
1422 ..Config::default()
1423 });
1424
1425 let failure = runner
1426 .run(&(0u32..1000), |v| {
1427 if v >= 500 {
1428 ::std::thread::sleep(::std::time::Duration::from_millis(
1433 600,
1434 ));
1435 } else {
1436 ::std::thread::sleep(::std::time::Duration::from_millis(
1439 100,
1440 ))
1441 }
1442 Ok(())
1443 })
1444 .err()
1445 .unwrap();
1446
1447 match failure {
1448 TestError::Fail(_, value) => assert_eq!(500, value),
1449 failure => panic!("Unexpected failure: {:?}", failure),
1450 }
1451 }
1452
1453 #[cfg(feature = "std")]
1454 #[test]
1455 fn duplicate_tests_not_run_with_basic_result_cache() {
1456 use std::cell::{Cell, RefCell};
1457 use std::collections::HashSet;
1458 use std::rc::Rc;
1459
1460 for _ in 0..256 {
1461 let mut runner = TestRunner::new(Config {
1462 failure_persistence: None,
1463 result_cache:
1464 crate::test_runner::result_cache::basic_result_cache,
1465 ..Config::default()
1466 });
1467 let pass = Rc::new(Cell::new(true));
1468 let seen = Rc::new(RefCell::new(HashSet::new()));
1469 let result =
1470 runner.run(&(0u32..65536u32).prop_map(|v| v % 10), |val| {
1471 if !seen.borrow_mut().insert(val) {
1472 println!("Value {} seen more than once", val);
1473 pass.set(false);
1474 }
1475
1476 prop_assert!(val <= 5);
1477 Ok(())
1478 });
1479
1480 assert!(pass.get());
1481 if let Err(TestError::Fail(_, val)) = result {
1482 assert_eq!(6, val);
1483 } else {
1484 panic!("Incorrect result: {:?}", result);
1485 }
1486 }
1487 }
1488}
1489
1490#[cfg(all(feature = "fork", feature = "timeout", test))]
1491mod timeout_tests {
1492 use core::u32;
1493 use std::thread;
1494 use std::time::Duration;
1495
1496 use super::*;
1497 use crate::test_runner::failure_persistence::FailurePersistence;
1498 use crate::test_runner::rng::Seed;
1499 use crate::test_runner::{MapFailurePersistence, RngAlgorithm};
1500
1501 rusty_fork_test! {
1502 #![rusty_fork(timeout_ms = 4_000)]
1503
1504 #[test]
1505 fn max_shrink_iters_works() {
1506 test_shrink_bail(Config {
1507 max_shrink_iters: 5,
1508 .. Config::default()
1509 });
1510 }
1511
1512 #[test]
1513 fn max_shrink_time_works() {
1514 test_shrink_bail(Config {
1515 max_shrink_time: 1000,
1516 .. Config::default()
1517 });
1518 }
1519
1520 #[test]
1521 fn max_shrink_iters_works_with_forking() {
1522 test_shrink_bail(Config {
1523 fork: true,
1524 test_name: Some(
1525 concat!(module_path!(),
1526 "::max_shrink_iters_works_with_forking")),
1527 max_shrink_time: 1000,
1528 .. Config::default()
1529 });
1530 }
1531
1532 #[test]
1533 fn persisted_failure_in_fork_is_reported() {
1534 let value = run_persisted_failure(Config {
1535 fork: true,
1536 test_name: Some(
1537 concat!(module_path!(),
1538 "::persisted_failure_in_fork_is_reported")),
1539 .. persisted_failure_config()
1540 }, 0);
1541 assert_eq!(0, value);
1542 }
1543
1544 #[test]
1545 fn persisted_timeout_in_fork_is_reported() {
1546 run_persisted_failure(Config {
1549 timeout: 100,
1550 max_shrink_iters: 3,
1551 test_name: Some(
1552 concat!(module_path!(),
1553 "::persisted_timeout_in_fork_is_reported")),
1554 .. persisted_failure_config()
1555 }, 10_000);
1556 }
1557
1558 #[test]
1559 fn detects_child_failure_to_start() {
1560 let mut runner = TestRunner::new(Config {
1561 timeout: 100,
1562 test_name: Some(
1563 concat!(module_path!(),
1564 "::detects_child_failure_to_start")),
1565 .. Config::default()
1566 });
1567 let result = runner.run(&Just(()).prop_map(|()| {
1568 thread::sleep(Duration::from_millis(200))
1569 }), Ok);
1570
1571 if let Err(TestError::Abort(_)) = result {
1572 } else {
1574 panic!("Unexpected result: {:?}", result);
1575 }
1576 }
1577 }
1578
1579 fn persisted_failure_config() -> Config {
1582 const SOURCE_FILE: &str = "persisted_failure";
1583 let mut persistence = MapFailurePersistence::default();
1584 persistence.save_persisted_failure2(
1585 Some(SOURCE_FILE),
1586 PersistedSeed(Seed::from_bytes(RngAlgorithm::ChaCha, &[7; 32])),
1587 &(),
1588 );
1589 Config {
1590 failure_persistence: Some(Box::new(persistence)),
1591 source_file: Some(SOURCE_FILE),
1592 rng_algorithm: RngAlgorithm::ChaCha,
1593 ..Config::default()
1594 }
1595 }
1596
1597 fn run_persisted_failure(config: Config, sleep_ms: u64) -> u32 {
1602 let mut runner = TestRunner::new(config);
1603 let result = runner.run(&(0u32..1000), |_| {
1604 thread::sleep(Duration::from_millis(sleep_ms));
1605 Err(TestCaseError::fail("every case fails"))
1606 });
1607
1608 match result {
1609 Err(TestError::Fail(_, value)) => value,
1610 result => panic!("Unexpected result: {:?}", result),
1611 }
1612 }
1613
1614 fn test_shrink_bail(config: Config) {
1615 let mut runner = TestRunner::new(config);
1616 let result = runner.run(&crate::num::u64::ANY, |v| {
1617 thread::sleep(Duration::from_millis(250));
1618 prop_assert!(v <= u32::MAX as u64);
1619 Ok(())
1620 });
1621
1622 if let Err(TestError::Fail(_, value)) = result {
1623 assert!(value > u32::MAX as u64);
1625 } else {
1626 panic!("Unexpected result: {:?}", result);
1627 }
1628 }
1629}