1#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
18
19mod arb;
20mod shrinking;
21mod sip;
22
23pub use arb::*;
24use rand::rngs::StdRng;
25use rand::{SeedableRng, TryRng};
26pub use shrinking::*;
27pub use sip::HasherBuilder;
28
29use std::cell::RefCell;
30use std::env;
31use std::ops::{Coroutine, CoroutineState};
32use std::panic::{RefUnwindSafe, catch_unwind};
33use std::pin::pin;
34use std::rc::Rc;
35
36static SEED_ENV_VAR: &str = "FALSIFY_SEED";
37
38#[derive(Debug, Copy, Clone)]
39pub enum TestResult {
40 Pass,
41 Fail,
42 Reject,
43}
44
45impl From<bool> for TestResult {
46 fn from(cond: bool) -> Self {
47 if cond {
48 TestResult::Pass
49 } else {
50 TestResult::Fail
51 }
52 }
53}
54
55pub fn make_test_rng() -> Rc<RefCell<StdRng>> {
57 let mut std_rng: StdRng = rand::make_rng();
58 let seed: u64 = if let Ok(seed_string) = env::var(SEED_ENV_VAR) {
59 if let Ok(seed) = seed_string.parse() {
60 seed
61 } else {
62 panic!("Unable to parse {seed_string:?} as seed!");
63 }
64 } else {
65 std_rng.try_next_u64().unwrap()
66 };
67 eprintln!("Seed: {seed}");
68 let seeded_std_rng = StdRng::seed_from_u64(seed);
69 Rc::new(RefCell::new(seeded_std_rng))
70}
71
72pub fn make_rng_with_seed(seed: u64) -> Rc<RefCell<StdRng>> {
73 eprintln!("Seed: {seed}");
74 let seeded_std_rng = StdRng::seed_from_u64(seed);
75 Rc::new(RefCell::new(seeded_std_rng))
76}
77
78pub fn falsify_with_rejections<T: Clone + RefUnwindSafe>(
79 test: impl Fn(T) -> TestResult + RefUnwindSafe,
80 mut arb_t: impl ArbGen<T> + Unpin,
81 mut tries: usize,
82 reset: impl Fn(),
83) -> Option<T> {
84 while tries > 0 {
85 let value = match pin!(&mut arb_t).resume(()) {
86 CoroutineState::Yielded(value) => value,
87 CoroutineState::Complete(()) => {
88 if tries > 0 {
89 panic!(
90 "Test generator finished prematurely before producing {tries} test cases!"
91 );
92 }
93 return None;
94 }
95 };
96 let result = catch_unwind(|| test(value.clone()));
97 match result {
98 Ok(TestResult::Pass) => {
99 tries -= 1;
100 reset();
101 continue;
102 }
103 Ok(TestResult::Reject) => {
104 reset();
105 continue;
106 }
107 Ok(TestResult::Fail) | Err(..) => return Some(value),
108 }
109 }
110 None
111}
112
113pub fn falsify_times<T: Clone + RefUnwindSafe>(
114 test: impl Fn(T) -> bool + RefUnwindSafe,
115 arb_t: impl ArbGen<T> + Unpin,
116 tries: usize,
117) -> Option<T> {
118 falsify_with_rejections(|value| TestResult::from(test(value)), arb_t, tries, || {})
119}
120
121pub fn falsify<T: Clone + RefUnwindSafe>(
122 test: impl Fn(T) -> bool + RefUnwindSafe,
123 arb_t: impl ArbGen<T> + Unpin,
124) -> Option<T> {
125 falsify_with_rejections(|value| TestResult::from(test(value)), arb_t, 100, || {})
126}
127
128pub fn falsify_with_reset<T: Clone + RefUnwindSafe>(
129 test: impl Fn(T) -> bool + RefUnwindSafe,
130 reset: impl Fn(),
131 arb_t: impl ArbGen<T> + Unpin,
132) -> Option<T> {
133 falsify_with_rejections(|value| TestResult::from(test(value)), arb_t, 100, reset)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use shrinking::{shrink, shrink_usize_binary_search};
140 use std::assert_matches;
141
142 #[test]
143 fn test_arb_usize() {
144 let rng = make_test_rng();
145 let arb = arb_usize(rng);
146 assert_matches!(falsify(|n| n % 2 == 0 || n % 2 == 1, arb), None);
147 }
148
149 #[test]
150 fn test_arb_vec_of_usize() {
151 let rng = make_test_rng();
152 let arb_usize = arb_usize(rng.clone());
153 let arb_vec = arb_vec_of(arb_usize, rng, 50);
154 assert_matches!(
155 falsify(
156 |mut v| {
157 let original = v.clone();
158 v.reverse();
159 v.reverse();
160 v == original
161 },
162 arb_vec,
163 ),
164 None
165 );
166 }
167
168 #[test]
169 fn test_falsify_then_shrink() {
170 let rng = make_test_rng();
171 let arb = arb_usize(rng);
172 let test = |n| n < 10;
173 if let Some(falsifier) = falsify(test, arb) {
174 let shrinker = shrink_usize_binary_search(falsifier);
175 let smallest = shrink(test, shrinker);
176 assert_eq!(smallest, 10);
177 };
178 }
179}