• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

.github/workflows/H03-May-2022-7775

examples/H03-May-2022-194154

src/H03-May-2022-2,4221,921

.cargo-checksum.jsonH A D03-May-202289 11

.cargo_vcs_info.jsonH A D01-Jan-197074 65

.gitignoreH A D01-Jan-197043 87

COPYINGH A D01-Jan-1970126 42

Cargo.lockH A D01-Jan-19703.2 KiB125110

Cargo.tomlH A D01-Jan-19701.4 KiB4842

Cargo.toml.orig-cargoH A D01-Jan-1970990 3126

LICENSE-MITH A D01-Jan-19701.1 KiB2217

README.mdH A D01-Jan-197017.9 KiB584449

UNLICENSEH A D01-Jan-19701.2 KiB2520

rustfmt.tomlH A D01-Jan-197044 32

README.md

1quickcheck
2==========
3QuickCheck is a way to do property based testing using randomly generated
4input. This crate comes with the ability to randomly generate and shrink
5integers, floats, tuples, booleans, lists, strings, options and results.
6All QuickCheck needs is a property function—it will then randomly generate
7inputs to that function and call the property for each set of inputs. If the
8property fails (whether by a runtime error like index out-of-bounds or by not
9satisfying your property), the inputs are "shrunk" to find a smaller
10counter-example.
11
12The shrinking strategies for lists and numbers use a binary search to cover
13the input space quickly. (It should be the same strategy used in
14[Koen Claessen's QuickCheck for
15Haskell](https://hackage.haskell.org/package/QuickCheck).)
16
17[![Build status](https://github.com/BurntSushi/quickcheck/workflows/ci/badge.svg)](https://github.com/BurntSushi/quickcheck/actions)
18[![](https://meritbadge.herokuapp.com/quickcheck)](https://crates.io/crates/quickcheck)
19
20Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org).
21
22
23### Documentation
24
25The API is fully documented:
26[https://docs.rs/quickcheck](https://docs.rs/quickcheck).
27
28
29### Simple example
30
31Here's an example that tests a function that reverses a vector:
32
33```rust
34#[cfg(test)]
35#[macro_use]
36extern crate quickcheck;
37
38fn reverse<T: Clone>(xs: &[T]) -> Vec<T> {
39    let mut rev = vec!();
40    for x in xs.iter() {
41        rev.insert(0, x.clone())
42    }
43    rev
44}
45
46#[cfg(test)]
47mod tests {
48  quickcheck! {
49      fn prop(xs: Vec<u32>) -> bool {
50          xs == reverse(&reverse(&xs))
51      }
52  }
53}
54```
55
56This example uses the `quickcheck!` macro, which is backwards compatible with
57old versions of Rust.
58
59### The `#[quickcheck]` attribute
60
61To make it easier to write QuickCheck tests, the `#[quickcheck]` attribute
62will convert a property function into a `#[test]` function.
63
64To use the `#[quickcheck]` attribute, you must import the `quickcheck` macro
65from the `quickcheck_macros` crate:
66
67```rust
68#[cfg(test)]
69extern crate quickcheck;
70#[cfg(test)]
71#[macro_use(quickcheck)]
72extern crate quickcheck_macros;
73
74#[cfg(test)]
75mod tests {
76    fn reverse<T: Clone>(xs: &[T]) -> Vec<T> {
77        let mut rev = vec!();
78        for x in xs {
79            rev.insert(0, x.clone())
80        }
81        rev
82    }
83
84    #[quickcheck]
85    fn double_reversal_is_identity(xs: Vec<isize>) -> bool {
86        xs == reverse(&reverse(&xs))
87    }
88}
89```
90
91
92### Installation
93
94`quickcheck` is on `crates.io`, so you can include it in your project like so:
95
96```toml
97[dependencies]
98quickcheck = "1"
99```
100
101If you're only using `quickcheck` in your test code, then you can add it as a
102development dependency instead:
103
104```toml
105[dev-dependencies]
106quickcheck = "1"
107```
108
109If you want to use the `#[quickcheck]` attribute, then add `quickcheck_macros`
110
111```toml
112[dev-dependencies]
113quickcheck = "1"
114quickcheck_macros = "1"
115```
116
117N.B. When using `quickcheck` (either directly or via the attributes),
118`RUST_LOG=quickcheck` enables `info!` so that it shows useful output
119(like the number of tests passed). This is **not** needed to show
120witnesses for failures.
121
122Crate features:
123
124- `"use_logging"`: (Enabled by default.) Enables the log messages governed
125  `RUST_LOG`.
126- `"regex"`: (Enabled by default.) Enables the use of regexes with
127  `env_logger`.
128
129
130### Minimum Rust version policy
131
132This crate's minimum supported `rustc` version is `1.46.0`.
133
134The current policy is that the minimum Rust version required to use this crate
135can be increased in minor version updates. For example, if `crate 1.0` requires
136Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust
1371.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum
138version of Rust.
139
140In general, this crate will be conservative with respect to the minimum
141supported version of Rust.
142
143With all of that said, currently, `rand` is a public dependency of
144`quickcheck`. Therefore, the MSRV policy above only applies when it is more
145aggressive than `rand`'s MSRV policy. Otherwise, `quickcheck` will defer to
146`rand`'s MSRV policy.
147
148
149### Compatibility
150
151In general, this crate considers the `Arbitrary` implementations provided as
152implementation details. Strategies may or may not change over time, which may
153cause new test failures, presumably due to the discovery of new bugs due to a
154new kind of witness being generated. These sorts of changes may happen in
155semver compatible releases.
156
157
158### Alternative Rust crates for property testing
159
160The [`proptest`](https://docs.rs/proptest) crate is inspired by the
161[Hypothesis](https://hypothesis.works) framework for Python.
162You can read a comparison between `proptest` and `quickcheck`
163[here](https://github.com/AltSysrq/proptest/blob/master/proptest/README.md#differences-between-quickcheck-and-proptest)
164and
165[here](https://github.com/AltSysrq/proptest/issues/15#issuecomment-348382287).
166In particular, `proptest` improves on the concept of shrinking. So if you've
167ever had problems/frustration with shrinking in `quickcheck`, then `proptest`
168might be worth a try!
169
170
171### Alternatives for fuzzing
172
173Please see the
174[Rust Fuzz Book](https://rust-fuzz.github.io/book/introduction.html)
175and the
176[`arbitrary`](https://crates.io/crates/arbitrary) crate.
177
178
179### Discarding test results (or, properties are polymorphic!)
180
181Sometimes you want to test a property that only holds for a *subset* of the
182possible inputs, so that when your property is given an input that is outside
183of that subset, you'd discard it. In particular, the property should *neither*
184pass nor fail on inputs outside of the subset you want to test. But properties
185return boolean values—which either indicate pass or fail.
186
187To fix this, we need to take a step back and look at the type of the
188`quickcheck` function:
189
190```rust
191pub fn quickcheck<A: Testable>(f: A) {
192    // elided
193}
194```
195
196So `quickcheck` can test any value with a type that satisfies the `Testable`
197trait. Great, so what is this `Testable` business?
198
199```rust
200pub trait Testable {
201    fn result(&self, &mut Gen) -> TestResult;
202}
203```
204
205This trait states that a type is testable if it can produce a `TestResult`
206given a source of randomness. (A `TestResult` stores information about the
207results of a test, like whether it passed, failed or has been discarded.)
208
209Sure enough, `bool` satisfies the `Testable` trait:
210
211```rust
212impl Testable for bool {
213    fn result(&self, _: &mut Gen) -> TestResult {
214        TestResult::from_bool(*self)
215    }
216}
217```
218
219But in the example, we gave a *function* to `quickcheck`. Yes, functions can
220satisfy `Testable` too!
221
222```rust
223impl<A: Arbitrary + Debug, B: Testable> Testable for fn(A) -> B {
224    fn result(&self, g: &mut Gen) -> TestResult {
225        // elided
226    }
227}
228```
229
230Which says that a function satisfies `Testable` if and only if it has a single
231parameter type (whose values can be randomly generated and shrunk) and returns
232any type (that also satisfies `Testable`). So a function with type `fn(usize)
233-> bool` satisfies `Testable` since `usize` satisfies `Arbitrary` and `bool`
234satisfies `Testable`.
235
236So to discard a test, we need to return something other than `bool`. What if we
237just returned a `TestResult` directly? That should work, but we'll need to
238make sure `TestResult` satisfies `Testable`:
239
240```rust
241impl Testable for TestResult {
242    fn result(&self, _: &mut Gen) -> TestResult { self.clone() }
243}
244```
245
246Now we can test functions that return a `TestResult` directly.
247
248As an example, let's test our reverse function to make sure that the reverse of
249a vector of length 1 is equal to the vector itself.
250
251```rust
252fn prop(xs: Vec<isize>) -> TestResult {
253    if xs.len() != 1 {
254        return TestResult::discard()
255    }
256    TestResult::from_bool(xs == reverse(&xs))
257}
258quickcheck(prop as fn(Vec<isize>) -> TestResult);
259```
260
261(A full working program for this example is in
262[`examples/reverse_single.rs`](https://github.com/BurntSushi/quickcheck/blob/master/examples/reverse_single.rs).)
263
264So now our property returns a `TestResult`, which allows us to encode a bit
265more information. There are a few more
266[convenience functions defined for the `TestResult`
267type](https://docs.rs/quickcheck/*/quickcheck/struct.TestResult.html).
268For example, we can't just return a `bool`, so we convert a `bool` value to a
269`TestResult`.
270
271(The ability to discard tests allows you to get similar functionality as
272Haskell's `==>` combinator.)
273
274N.B. Since discarding a test means it neither passes nor fails, `quickcheck`
275will try to replace the discarded test with a fresh one. However, if your
276condition is seldom met, it's possible that `quickcheck` will have to settle
277for running fewer tests than usual. By default, if `quickcheck` can't find
278`100` valid tests after trying `10,000` times, then it will give up.
279These parameters may be changed using
280[`QuickCheck::tests`](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.tests)
281and [`QuickCheck::max_tests`](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.max_tests),
282or by setting the `QUICKCHECK_TESTS` and `QUICKCHECK_MAX_TESTS`
283environment variables.
284There is also `QUICKCHECK_MIN_TESTS_PASSED` which sets the minimum number of
285valid tests that need pass (defaults to `0`) in order for it to be considered a
286success.
287
288
289### Shrinking
290
291Shrinking is a crucial part of QuickCheck that simplifies counter-examples for
292your properties automatically. For example, if you erroneously defined a
293function for reversing vectors as: (my apologies for the contrived example)
294
295```rust
296fn reverse<T: Clone>(xs: &[T]) -> Vec<T> {
297    let mut rev = vec![];
298    for i in 1..xs.len() {
299        rev.insert(0, xs[i].clone())
300    }
301    rev
302}
303```
304
305And a property to test that `xs == reverse(reverse(xs))`:
306
307```rust
308fn prop(xs: Vec<isize>) -> bool {
309    xs == reverse(&reverse(&xs))
310}
311quickcheck(prop as fn(Vec<isize>) -> bool);
312```
313
314Then without shrinking, you might get a counter-example like:
315
316```
317[quickcheck] TEST FAILED. Arguments: ([-17, 13, -12, 17, -8, -10, 15, -19,
318-19, -9, 11, -5, 1, 19, -16, 6])
319```
320
321Which is pretty mysterious. But with shrinking enabled, you're nearly
322guaranteed to get this counter-example every time:
323
324```
325[quickcheck] TEST FAILED. Arguments: ([0])
326```
327
328Which is going to be much easier to debug.
329
330### More Thorough Checking
331
332Quickcheck uses random input to test, so it won't
333always find bugs that could be uncovered with a particular
334property. You can improve your odds of finding these latent
335bugs by spending more CPU cycles asking quickcheck to find
336them for you. There are a few different ways to do this, and
337which one you choose is mostly a matter of taste.
338
339If you are finding yourself doing this sort of thing a
340lot, you might also be interested in trying out
341[`cargo fuzz`](https://github.com/rust-fuzz/cargo-fuzz),
342which runs in a loop by default.
343
344##### Running in a Loop
345
346One approach is to run your quickcheck properties in a loop that
347just keeps going until you tell it to stop or it finds a bug.
348For example, you could use a bash script such as the following
349one.
350
351```bash
352#!/usr/bin/bash
353
354while true
355do
356    cargo test qc_
357    if [[ x$? != x0 ]] ; then
358        exit $?
359    fi
360done
361```
362
363One thing to note is that this script passes the `qc_` filter to
364`cargo test`. This assumes that you've prefixed all your quickcheck
365properties with `qc_`. You could leave off the filter, but then
366you would be running all your deterministic tests as well, which
367would take time away from quickcheck!
368
369Checking the return code and exiting is also important. Without that
370test, you won't ever notice when a failure happens.
371
372##### Cranking the Number of Tests
373
374Another approach is to just ask quickcheck to run properties more
375times. You can do this either via the
376[tests()](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.tests)
377method, or via the `QUICKCHECK_TESTS` environment variable.
378This will cause quickcheck to run for a much longer time. Unlike,
379the loop approach this will take a bounded amount of time, which
380makes it more suitable for something like a release cycle that
381wants to really hammer your software.
382
383##### Making Arbitrary Smarter
384
385This approach entails spending more time generating interesting
386inputs in your implementations of Arbitrary. The idea is to
387focus on the corner cases. This approach can be tricky because
388programmers are not usually great at intuiting corner cases,
389and the whole idea of property checking is to take that burden
390off the programmer. Despite the theoretical discomfort, this
391approach can turn out to be practical.
392
393### Generating Structs
394
395It is very simple to generate structs in QuickCheck. Consider the following
396example, where the struct `Point` is defined:
397
398```rust
399struct Point {
400    x: i32,
401    y: i32,
402}
403```
404
405In order to generate a random `Point` instance, you need to implement
406the trait `Arbitrary` for the struct `Point`:
407
408```rust
409use quickcheck::{Arbitrary, Gen};
410
411impl Arbitrary for Point {
412    fn arbitrary(g: &mut Gen) -> Point {
413        Point {
414            x: i32::arbitrary(g),
415            y: i32::arbitrary(g),
416        }
417    }
418}
419```
420
421
422### Case study: The Sieve of Eratosthenes
423
424The [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes)
425is a simple and elegant way to find all primes less than or equal to `N`.
426Briefly, the algorithm works by allocating an array with `N` slots containing
427booleans. Slots marked with `false` correspond to prime numbers (or numbers
428not known to be prime while building the sieve) and slots marked with `true`
429are known to not be prime. For each `n`, all of its multiples in this array
430are marked as true. When all `n` have been checked, the numbers marked `false`
431are returned as the primes.
432
433As you might imagine, there's a lot of potential for off-by-one errors, which
434makes it ideal for randomized testing. So let's take a look at my
435implementation and see if we can spot the bug:
436
437```rust
438fn sieve(n: usize) -> Vec<usize> {
439    if n <= 1 {
440        return vec![];
441    }
442
443    let mut marked = vec![false; n+1];
444    marked[0] = true;
445    marked[1] = true;
446    marked[2] = true;
447    for p in 2..n {
448        for i in (2*p..n).filter(|&n| n % p == 0) {
449            marked[i] = true;
450        }
451    }
452    marked.iter()
453          .enumerate()
454          .filter_map(|(i, &m)| if m { None } else { Some(i) })
455          .collect()
456}
457```
458
459Let's try it on a few inputs by hand:
460
461```
462sieve(3) => [2, 3]
463sieve(5) => [2, 3, 5]
464sieve(8) => [2, 3, 5, 7, 8] # !!!
465```
466
467Something has gone wrong! But where? The bug is rather subtle, but it's an
468easy one to make. It's OK if you can't spot it, because we're going to use
469QuickCheck to help us track it down.
470
471Even before looking at some example outputs, it's good to try and come up with
472some *properties* that are always satisfiable by the output of the function. An
473obvious one for the prime number sieve is to check if all numbers returned are
474prime. For that, we'll need an `is_prime` function:
475
476```rust
477fn is_prime(n: usize) -> bool {
478    n != 0 && n != 1 && (2..).take_while(|i| i*i <= n).all(|i| n % i != 0)
479}
480```
481
482All this is doing is checking to see if any number in `[2, sqrt(n)]` divides
483`n` with base cases for `0` and `1`.
484
485Now we can write our QuickCheck property:
486
487```rust
488fn prop_all_prime(n: usize) -> bool {
489    sieve(n).into_iter().all(is_prime)
490}
491```
492
493And finally, we need to invoke `quickcheck` with our property:
494
495```rust
496fn main() {
497    quickcheck(prop_all_prime as fn(usize) -> bool);
498}
499```
500
501A fully working source file with this code is in
502[`examples/sieve.rs`](https://github.com/BurntSushi/quickcheck/blob/master/examples/sieve.rs).
503
504The output of running this program has this message:
505
506```
507[quickcheck] TEST FAILED. Arguments: (4)
508```
509
510Which says that `sieve` failed the `prop_all_prime` test when given `n = 4`.
511Because of shrinking, it was able to find a (hopefully) minimal counter-example
512for our property.
513
514With such a short counter-example, it's hopefully a bit easier to narrow down
515where the bug is. Since `4` is returned, it's likely never marked as being not
516prime. Since `4` is a multiple of `2`, its slot should be marked as `true` when
517`p = 2` on these lines:
518
519```rust
520for i in (2*p..n).filter(|&n| n % p == 0) {
521    marked[i] = true;
522}
523```
524
525Ah! But does the `..` (range) operator include `n`? Nope! This particular
526operator is a half-open interval.
527
528A `2*p..n` range will never yield `4` when `n = 4`. When we change this to
529`2*p..n+1`, all tests pass.
530
531In addition, if our bug happened to result in an index out-of-bounds error,
532then `quickcheck` can handle it just like any other failure—including
533shrinking on failures caused by runtime errors.
534
535But hold on... we're not done yet. Right now, our property tests that all
536the numbers returned by `sieve` are prime but it doesn't test if the list is
537complete. It does not ensure that all the primes between `0` and `n` are found.
538
539Here's a property that is more comprehensive:
540
541```rust
542fn prop_prime_iff_in_the_sieve(n: usize) -> bool {
543    sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::<Vec<_>>()
544}
545```
546
547It tests that for each number between 0 and n, inclusive, the naive primality test
548yields the same result as the sieve.
549
550Now, if we run it:
551
552```rust
553fn main() {
554    quickcheck(prop_all_prime as fn(usize) -> bool);
555    quickcheck(prop_prime_iff_in_the_sieve as fn(usize) -> bool);
556}
557```
558
559we see that it fails immediately for value n = 2.
560
561```
562[quickcheck] TEST FAILED. Arguments: (2)
563```
564
565If we inspect `sieve()` once again, we see that we mistakenly mark `2` as
566non-prime. Removing the line `marked[2] = true;` results in both properties
567passing.
568
569### What's not in this port of QuickCheck?
570
571I think I've captured the key features, but there are still things missing:
572
573* Only functions with 8 or fewer parameters can be quickchecked. This
574limitation can be lifted to some `N`, but requires an implementation for each
575`n` of the `Testable` trait.
576* Functions that fail because of a stack overflow are not caught by QuickCheck.
577Therefore, such failures will not have a witness attached
578to them. (I'd like to fix this, but I don't know how.)
579* `Coarbitrary` does not exist in any form in this package. It's unlikely that
580it ever will.
581* `Arbitrary` is not implemented for closures. See
582[issue #56](https://github.com/BurntSushi/quickcheck/issues/56)
583for more details on why.
584