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;
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#[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
116impl 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 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 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 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 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 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 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 pub fn rng(&mut self) -> &mut TestRng {
360 &mut self.rng
361 }
362
363 pub fn new_rng(&mut self) -> TestRng {
366 self.rng.gen_rng()
367 }
368
369 pub fn config(&self) -> &Config {
371 &self.config
372 }
373
374 pub fn bytes_used(&self) -> Vec<u8> {
381 self.rng.bytes_used()
382 }
383
384 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); 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 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 ::std::thread::sleep(::std::time::Duration::from_millis(
1328 600,
1329 ));
1330 } else {
1331 ::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 } 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 assert!(value > u32::MAX as u64);
1456 } else {
1457 panic!("Unexpected result: {:?}", result);
1458 }
1459 }
1460}