Skip to main content

falsify/
lib.rs

1//! Simple usage example (wrap it in a test):
2//!
3//! ```
4//! use falsify::*;
5//!
6//! let rng = make_test_rng();
7//! let arb = arb_usize(rng);
8//! let property = |n| n < 10;
9//! if let Some(falsifier) = falsify(property, arb) {
10//!     let shrinker = shrink_usize_binary_search(falsifier);
11//!     let smallest_falsifier = shrink(property, shrinker);
12//!     assert_eq!(smallest_falsifier, 10);
13//! }
14//! ```
15
16// https://doc.rust-lang.org/unstable-book/language-features/coroutines.html
17#![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
55/// Takes the seed value from the `FALSIFY_SEED` environment variable, if set.
56pub 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}