quickcheck/
tester.rs

1use std::cmp;
2use std::env;
3use std::fmt::Debug;
4use std::panic;
5
6use crate::{
7    tester::Status::{Discard, Fail, Pass},
8    Arbitrary, Gen,
9};
10
11/// The main QuickCheck type for setting configuration and running QuickCheck.
12pub struct QuickCheck {
13    tests: u64,
14    max_tests: u64,
15    min_tests_passed: u64,
16    gen: Gen,
17}
18
19fn qc_tests() -> u64 {
20    let default = 100;
21    match env::var("QUICKCHECK_TESTS") {
22        Ok(val) => val.parse().unwrap_or(default),
23        Err(_) => default,
24    }
25}
26
27fn qc_max_tests() -> u64 {
28    let default = 10_000;
29    match env::var("QUICKCHECK_MAX_TESTS") {
30        Ok(val) => val.parse().unwrap_or(default),
31        Err(_) => default,
32    }
33}
34
35fn qc_gen_size() -> usize {
36    let default = 100;
37    match env::var("QUICKCHECK_GENERATOR_SIZE") {
38        Ok(val) => val.parse().unwrap_or(default),
39        Err(_) => default,
40    }
41}
42
43fn qc_min_tests_passed() -> u64 {
44    let default = 0;
45    match env::var("QUICKCHECK_MIN_TESTS_PASSED") {
46        Ok(val) => val.parse().unwrap_or(default),
47        Err(_) => default,
48    }
49}
50
51impl QuickCheck {
52    /// Creates a new QuickCheck value.
53    ///
54    /// This can be used to run QuickCheck on things that implement `Testable`.
55    /// You may also adjust the configuration, such as the number of tests to
56    /// run.
57    ///
58    /// By default, the maximum number of passed tests is set to `100`, the max
59    /// number of overall tests is set to `10000` and the generator is created
60    /// with a size of `100`.
61    pub fn new() -> QuickCheck {
62        let gen = Gen::new(qc_gen_size());
63        let tests = qc_tests();
64        let max_tests = cmp::max(tests, qc_max_tests());
65        let min_tests_passed = qc_min_tests_passed();
66
67        QuickCheck { tests, max_tests, min_tests_passed, gen }
68    }
69
70    /// Set the random number generator to be used by QuickCheck.
71    pub fn gen(self, gen: Gen) -> QuickCheck {
72        QuickCheck { gen, ..self }
73    }
74
75    /// Set the number of tests to run.
76    ///
77    /// This actually refers to the maximum number of *passed* tests that
78    /// can occur. Namely, if a test causes a failure, future testing on that
79    /// property stops. Additionally, if tests are discarded, there may be
80    /// fewer than `tests` passed.
81    pub fn tests(mut self, tests: u64) -> QuickCheck {
82        self.tests = tests;
83        self
84    }
85
86    /// Set the maximum number of tests to run.
87    ///
88    /// The number of invocations of a property will never exceed this number.
89    /// This is necessary to cap the number of tests because QuickCheck
90    /// properties can discard tests.
91    pub fn max_tests(mut self, max_tests: u64) -> QuickCheck {
92        self.max_tests = max_tests;
93        self
94    }
95
96    /// Set the minimum number of tests that needs to pass.
97    ///
98    /// This actually refers to the minimum number of *valid* *passed* tests
99    /// that needs to pass for the property to be considered successful.
100    pub fn min_tests_passed(mut self, min_tests_passed: u64) -> QuickCheck {
101        self.min_tests_passed = min_tests_passed;
102        self
103    }
104
105    /// Tests a property and returns the result.
106    ///
107    /// The result returned is either the number of tests passed or a witness
108    /// of failure.
109    ///
110    /// (If you're using Rust's unit testing infrastructure, then you'll
111    /// want to use the `quickcheck` method, which will `panic!` on failure.)
112    pub fn quicktest<A>(&mut self, f: A) -> Result<u64, TestResult>
113    where
114        A: Testable,
115    {
116        let mut n_tests_passed = 0;
117        for _ in 0..self.max_tests {
118            if n_tests_passed >= self.tests {
119                break;
120            }
121            match f.result(&mut self.gen) {
122                TestResult { status: Pass, .. } => n_tests_passed += 1,
123                TestResult { status: Discard, .. } => continue,
124                r @ TestResult { status: Fail, .. } => return Err(r),
125            }
126        }
127        Ok(n_tests_passed)
128    }
129
130    /// Tests a property and calls `panic!` on failure.
131    ///
132    /// The `panic!` message will include a (hopefully) minimal witness of
133    /// failure.
134    ///
135    /// It is appropriate to use this method with Rust's unit testing
136    /// infrastructure.
137    ///
138    /// Note that if the environment variable `RUST_LOG` is set to enable
139    /// `info` level log messages for the `quickcheck` crate, then this will
140    /// include output on how many QuickCheck tests were passed.
141    ///
142    /// # Example
143    ///
144    /// ```rust
145    /// use quickcheck::QuickCheck;
146    ///
147    /// fn prop_reverse_reverse() {
148    ///     fn revrev(xs: Vec<usize>) -> bool {
149    ///         let rev: Vec<_> = xs.clone().into_iter().rev().collect();
150    ///         let revrev: Vec<_> = rev.into_iter().rev().collect();
151    ///         xs == revrev
152    ///     }
153    ///     QuickCheck::new().quickcheck(revrev as fn(Vec<usize>) -> bool);
154    /// }
155    /// ```
156    pub fn quickcheck<A>(&mut self, f: A)
157    where
158        A: Testable,
159    {
160        // Ignore log init failures, implying it has already been done.
161        let _ = crate::env_logger_init();
162
163        let n_tests_passed = match self.quicktest(f) {
164            Ok(n_tests_passed) => n_tests_passed,
165            Err(result) => panic!(result.failed_msg()),
166        };
167
168        if n_tests_passed >= self.min_tests_passed {
169            info!("(Passed {} QuickCheck tests.)", n_tests_passed)
170        } else {
171            panic!(
172                "(Unable to generate enough tests, {} not discarded.)",
173                n_tests_passed
174            )
175        }
176    }
177}
178
179/// Convenience function for running QuickCheck.
180///
181/// This is an alias for `QuickCheck::new().quickcheck(f)`.
182pub fn quickcheck<A: Testable>(f: A) {
183    QuickCheck::new().quickcheck(f)
184}
185
186/// Describes the status of a single instance of a test.
187///
188/// All testable things must be capable of producing a `TestResult`.
189#[derive(Clone, Debug)]
190pub struct TestResult {
191    status: Status,
192    arguments: Vec<String>,
193    err: Option<String>,
194}
195
196/// Whether a test has passed, failed or been discarded.
197#[derive(Clone, Debug)]
198enum Status {
199    Pass,
200    Fail,
201    Discard,
202}
203
204impl TestResult {
205    /// Produces a test result that indicates the current test has passed.
206    pub fn passed() -> TestResult {
207        TestResult::from_bool(true)
208    }
209
210    /// Produces a test result that indicates the current test has failed.
211    pub fn failed() -> TestResult {
212        TestResult::from_bool(false)
213    }
214
215    /// Produces a test result that indicates failure from a runtime error.
216    pub fn error<S: Into<String>>(msg: S) -> TestResult {
217        let mut r = TestResult::from_bool(false);
218        r.err = Some(msg.into());
219        r
220    }
221
222    /// Produces a test result that instructs `quickcheck` to ignore it.
223    /// This is useful for restricting the domain of your properties.
224    /// When a test is discarded, `quickcheck` will replace it with a
225    /// fresh one (up to a certain limit).
226    pub fn discard() -> TestResult {
227        TestResult { status: Discard, arguments: vec![], err: None }
228    }
229
230    /// Converts a `bool` to a `TestResult`. A `true` value indicates that
231    /// the test has passed and a `false` value indicates that the test
232    /// has failed.
233    pub fn from_bool(b: bool) -> TestResult {
234        TestResult {
235            status: if b { Pass } else { Fail },
236            arguments: vec![],
237            err: None,
238        }
239    }
240
241    /// Tests if a "procedure" fails when executed. The test passes only if
242    /// `f` generates a task failure during its execution.
243    pub fn must_fail<T, F>(f: F) -> TestResult
244    where
245        F: FnOnce() -> T,
246        F: 'static,
247        T: 'static,
248    {
249        let f = panic::AssertUnwindSafe(f);
250        TestResult::from_bool(panic::catch_unwind(f).is_err())
251    }
252
253    /// Returns `true` if and only if this test result describes a failing
254    /// test.
255    pub fn is_failure(&self) -> bool {
256        match self.status {
257            Fail => true,
258            Pass | Discard => false,
259        }
260    }
261
262    /// Returns `true` if and only if this test result describes a failing
263    /// test as a result of a run time error.
264    pub fn is_error(&self) -> bool {
265        self.is_failure() && self.err.is_some()
266    }
267
268    fn failed_msg(&self) -> String {
269        match self.err {
270            None => format!(
271                "[quickcheck] TEST FAILED. Arguments: ({})",
272                self.arguments.join(", ")
273            ),
274            Some(ref err) => format!(
275                "[quickcheck] TEST FAILED (runtime error). \
276                 Arguments: ({})\nError: {}",
277                self.arguments.join(", "),
278                err
279            ),
280        }
281    }
282}
283
284/// `Testable` describes types (e.g., a function) whose values can be
285/// tested.
286///
287/// Anything that can be tested must be capable of producing a `TestResult`
288/// given a random number generator. This is trivial for types like `bool`,
289/// which are just converted to either a passing or failing test result.
290///
291/// For functions, an implementation must generate random arguments
292/// and potentially shrink those arguments if they produce a failure.
293///
294/// It's unlikely that you'll have to implement this trait yourself.
295pub trait Testable: 'static {
296    fn result(&self, _: &mut Gen) -> TestResult;
297}
298
299impl Testable for bool {
300    fn result(&self, _: &mut Gen) -> TestResult {
301        TestResult::from_bool(*self)
302    }
303}
304
305impl Testable for () {
306    fn result(&self, _: &mut Gen) -> TestResult {
307        TestResult::passed()
308    }
309}
310
311impl Testable for TestResult {
312    fn result(&self, _: &mut Gen) -> TestResult {
313        self.clone()
314    }
315}
316
317impl<A, E> Testable for Result<A, E>
318where
319    A: Testable,
320    E: Debug + 'static,
321{
322    fn result(&self, g: &mut Gen) -> TestResult {
323        match *self {
324            Ok(ref r) => r.result(g),
325            Err(ref err) => TestResult::error(format!("{:?}", err)),
326        }
327    }
328}
329
330/// Return a vector of the debug formatting of each item in `args`
331fn debug_reprs(args: &[&dyn Debug]) -> Vec<String> {
332    args.iter().map(|x| format!("{:?}", x)).collect()
333}
334
335macro_rules! testable_fn {
336    ($($name: ident),*) => {
337
338impl<T: Testable,
339     $($name: Arbitrary + Debug),*> Testable for fn($($name),*) -> T {
340    #[allow(non_snake_case)]
341    fn result(&self, g: &mut Gen) -> TestResult {
342        fn shrink_failure<T: Testable, $($name: Arbitrary + Debug),*>(
343            g: &mut Gen,
344            self_: fn($($name),*) -> T,
345            a: ($($name,)*),
346        ) -> Option<TestResult> {
347            for t in a.shrink() {
348                let ($($name,)*) = t.clone();
349                let mut r_new = safe(move || {self_($($name),*)}).result(g);
350                if r_new.is_failure() {
351                    {
352                        let ($(ref $name,)*) : ($($name,)*) = t;
353                        r_new.arguments = debug_reprs(&[$($name),*]);
354                    }
355
356                    // The shrunk value *does* witness a failure, so keep
357                    // trying to shrink it.
358                    let shrunk = shrink_failure(g, self_, t);
359
360                    // If we couldn't witness a failure on any shrunk value,
361                    // then return the failure we already have.
362                    return Some(shrunk.unwrap_or(r_new))
363                }
364            }
365            None
366        }
367
368        let self_ = *self;
369        let a: ($($name,)*) = Arbitrary::arbitrary(g);
370        let ( $($name,)* ) = a.clone();
371        let mut r = safe(move || {self_($($name),*)}).result(g);
372
373        {
374            let ( $(ref $name,)* ) = a;
375            r.arguments = debug_reprs(&[$($name),*]);
376        }
377        match r.status {
378            Pass|Discard => r,
379            Fail => {
380                shrink_failure(g, self_, a).unwrap_or(r)
381            }
382        }
383    }
384}}}
385
386testable_fn!();
387testable_fn!(A);
388testable_fn!(A, B);
389testable_fn!(A, B, C);
390testable_fn!(A, B, C, D);
391testable_fn!(A, B, C, D, E);
392testable_fn!(A, B, C, D, E, F);
393testable_fn!(A, B, C, D, E, F, G);
394testable_fn!(A, B, C, D, E, F, G, H);
395
396fn safe<T, F>(fun: F) -> Result<T, String>
397where
398    F: FnOnce() -> T,
399    F: 'static,
400    T: 'static,
401{
402    panic::catch_unwind(panic::AssertUnwindSafe(fun)).map_err(|any_err| {
403        // Extract common types of panic payload:
404        // panic and assert produce &str or String
405        if let Some(&s) = any_err.downcast_ref::<&str>() {
406            s.to_owned()
407        } else if let Some(s) = any_err.downcast_ref::<String>() {
408            s.to_owned()
409        } else {
410            "UNABLE TO SHOW RESULT OF PANIC.".to_owned()
411        }
412    })
413}
414
415/// Convenient aliases.
416trait AShow: Arbitrary + Debug {}
417impl<A: Arbitrary + Debug> AShow for A {}
418
419#[cfg(test)]
420mod test {
421    use crate::{Gen, QuickCheck};
422
423    #[test]
424    fn shrinking_regression_issue_126() {
425        fn thetest(vals: Vec<bool>) -> bool {
426            vals.iter().filter(|&v| *v).count() < 2
427        }
428        let failing_case = QuickCheck::new()
429            .quicktest(thetest as fn(vals: Vec<bool>) -> bool)
430            .unwrap_err();
431        let expected_argument = format!("{:?}", [true, true]);
432        assert_eq!(failing_case.arguments, vec![expected_argument]);
433    }
434
435    #[test]
436    fn size_for_small_types_issue_143() {
437        fn t(_: i8) -> bool {
438            true
439        }
440        QuickCheck::new().gen(Gen::new(129)).quickcheck(t as fn(i8) -> bool);
441    }
442
443    #[test]
444    fn regression_signed_shrinker_panic() {
445        fn foo_can_shrink(v: i8) -> bool {
446            let _ = crate::Arbitrary::shrink(&v).take(100).count();
447            true
448        }
449        crate::quickcheck(foo_can_shrink as fn(i8) -> bool);
450    }
451}