quickcheck/
tester.rs

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