Name |
Date |
Size |
#Lines |
LOC |
||
---|---|---|---|---|---|---|
.. | - | - | ||||
.github/workflows/ | 03-May-2024 | - | 77 | 75 | ||
examples/ | 03-May-2024 | - | 194 | 154 | ||
patches/ | 03-May-2024 | - | 21 | 18 | ||
src/ | 03-May-2024 | - | 2,424 | 1,923 | ||
.cargo_vcs_info.json | D | 03-May-2024 | 74 | 6 | 5 | |
.gitignore | D | 03-May-2024 | 43 | 8 | 7 | |
Android.bp | D | 03-May-2024 | 2.6 KiB | 97 | 92 | |
COPYING | D | 03-May-2024 | 126 | 4 | 2 | |
Cargo.toml | D | 03-May-2024 | 1.4 KiB | 48 | 42 | |
Cargo.toml.orig | D | 03-May-2024 | 990 | 31 | 26 | |
LICENSE | D | 03-May-2024 | 1.1 KiB | 22 | 17 | |
LICENSE-MIT | D | 03-May-2024 | 1.1 KiB | 22 | 17 | |
METADATA | D | 03-May-2024 | 397 | 20 | 19 | |
MODULE_LICENSE_MIT | D | 03-May-2024 | 0 | |||
OWNERS | D | 03-May-2024 | 47 | 2 | 1 | |
README.md | D | 03-May-2024 | 17.9 KiB | 584 | 449 | |
TEST_MAPPING | D | 03-May-2024 | 478 | 28 | 27 | |
UNLICENSE | D | 03-May-2024 | 1.2 KiB | 25 | 20 | |
cargo2android.json | D | 03-May-2024 | 76 | 6 | 6 | |
rustfmt.toml | D | 03-May-2024 | 44 | 3 | 2 |
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[](https://github.com/BurntSushi/quickcheck/actions) 18[](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