quickcheck-1.0.3/.cargo_vcs_info.json0000644000000001120000000000000131400ustar { "git": { "sha1": "defde6fb0ce20b0c8c4e672aa9ae821f7d1f5b38" } } quickcheck-1.0.3/.github/workflows/ci.yml000064400000000000000000000033420000000000000164310ustar 00000000000000name: ci on: pull_request: push: branches: - master schedule: - cron: '00 01 * * *' jobs: test: name: test runs-on: ${{ matrix.os }} strategy: matrix: build: - pinned - stable - beta - nightly - macos - win-msvc - win-gnu include: - build: pinned os: ubuntu-18.04 rust: 1.46.0 - build: stable os: ubuntu-18.04 rust: stable - build: beta os: ubuntu-18.04 rust: beta - build: nightly os: ubuntu-18.04 rust: nightly - build: macos os: macos-latest rust: stable - build: win-msvc os: windows-2019 rust: stable - build: win-gnu os: windows-2019 rust: stable-x86_64-gnu steps: - name: Checkout repository uses: actions/checkout@v1 with: fetch-depth: 1 - name: Install Rust uses: hecrj/setup-rust-action@v1 with: rust-version: ${{ matrix.rust }} - run: cargo build --verbose - run: cargo doc --verbose - run: cargo test --verbose - run: cargo build --verbose --manifest-path quickcheck_macros/Cargo.toml - run: cargo test --verbose --manifest-path quickcheck_macros/Cargo.toml rustfmt: name: rustfmt runs-on: ubuntu-18.04 steps: - name: Checkout repository uses: actions/checkout@v1 with: fetch-depth: 1 - name: Install Rust uses: hecrj/setup-rust-action@v1 with: rust-version: stable - name: Install rustfmt run: rustup component add rustfmt - name: Check formatting run: | cargo fmt --all -- --check quickcheck-1.0.3/.gitignore000064400000000000000000000000530000000000000137020ustar 00000000000000doc .*.swp tags target build Cargo.lock *~ quickcheck-1.0.3/COPYING000064400000000000000000000001760000000000000127530ustar 00000000000000This project is dual-licensed under the Unlicense and MIT licenses. You may use this code under the terms of either license. quickcheck-1.0.3/Cargo.lock0000644000000062360000000000000111300ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "aho-corasick" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ "memchr", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "env_logger" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" dependencies = [ "log", "regex", ] [[package]] name = "getrandom" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "log" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "quickcheck" version = "1.0.3" dependencies = [ "env_logger", "log", "rand", ] [[package]] name = "rand" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a76330fb486679b4ace3670f117bbc9e16204005c4bde9c4bd372f45bed34f12" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8b34ba8cfb21243bd8df91854c830ff0d785fff2e82ebd4434c2644cb9ada18" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", "thread_local", ] [[package]] name = "regex-syntax" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" [[package]] name = "thread_local" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" dependencies = [ "lazy_static", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" quickcheck-1.0.3/Cargo.toml0000644000000026730000000000000111540ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "quickcheck" version = "1.0.3" authors = ["Andrew Gallant "] exclude = ["/.travis.yml", "/Makefile", "/ctags.rust", "/session.vim"] description = "Automatic property based testing with shrinking." homepage = "https://github.com/BurntSushi/quickcheck" documentation = "https://docs.rs/quickcheck" readme = "README.md" keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] categories = ["development-tools::testing"] license = "Unlicense/MIT" repository = "https://github.com/BurntSushi/quickcheck" [lib] name = "quickcheck" [dependencies.env_logger] version = "0.8.2" optional = true default-features = false [dependencies.log] version = "0.4" optional = true [dependencies.rand] version = "0.8" features = ["getrandom", "small_rng"] default-features = false [features] default = ["regex", "use_logging"] regex = ["env_logger/regex"] use_logging = ["log", "env_logger"] quickcheck-1.0.3/Cargo.toml.orig000064400000000000000000000017360000000000000146120ustar 00000000000000[package] name = "quickcheck" version = "1.0.3" #:version authors = ["Andrew Gallant "] description = "Automatic property based testing with shrinking." documentation = "https://docs.rs/quickcheck" homepage = "https://github.com/BurntSushi/quickcheck" repository = "https://github.com/BurntSushi/quickcheck" readme = "README.md" keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] categories = ["development-tools::testing"] license = "Unlicense/MIT" exclude = ["/.travis.yml", "/Makefile", "/ctags.rust", "/session.vim"] edition = "2018" [workspace] members = ["quickcheck_macros"] [features] default = ["regex", "use_logging"] use_logging = ["log", "env_logger"] regex = ["env_logger/regex"] [lib] name = "quickcheck" [dependencies] env_logger = { version = "0.8.2", default-features = false, optional = true } log = { version = "0.4", optional = true } rand = { version = "0.8", default-features = false, features = ["getrandom", "small_rng"] } quickcheck-1.0.3/LICENSE-MIT000064400000000000000000000020710000000000000133500ustar 00000000000000The MIT License (MIT) Copyright (c) 2015 Andrew Gallant Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. quickcheck-1.0.3/README.md000064400000000000000000000436100000000000000131770ustar 00000000000000quickcheck ========== QuickCheck is a way to do property based testing using randomly generated input. This crate comes with the ability to randomly generate and shrink integers, floats, tuples, booleans, lists, strings, options and results. All QuickCheck needs is a property function—it will then randomly generate inputs to that function and call the property for each set of inputs. If the property fails (whether by a runtime error like index out-of-bounds or by not satisfying your property), the inputs are "shrunk" to find a smaller counter-example. The shrinking strategies for lists and numbers use a binary search to cover the input space quickly. (It should be the same strategy used in [Koen Claessen's QuickCheck for Haskell](https://hackage.haskell.org/package/QuickCheck).) [![Build status](https://github.com/BurntSushi/quickcheck/workflows/ci/badge.svg)](https://github.com/BurntSushi/quickcheck/actions) [![](https://meritbadge.herokuapp.com/quickcheck)](https://crates.io/crates/quickcheck) Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org). ### Documentation The API is fully documented: [https://docs.rs/quickcheck](https://docs.rs/quickcheck). ### Simple example Here's an example that tests a function that reverses a vector: ```rust #[cfg(test)] #[macro_use] extern crate quickcheck; fn reverse(xs: &[T]) -> Vec { let mut rev = vec!(); for x in xs.iter() { rev.insert(0, x.clone()) } rev } #[cfg(test)] mod tests { quickcheck! { fn prop(xs: Vec) -> bool { xs == reverse(&reverse(&xs)) } } } ``` This example uses the `quickcheck!` macro, which is backwards compatible with old versions of Rust. ### The `#[quickcheck]` attribute To make it easier to write QuickCheck tests, the `#[quickcheck]` attribute will convert a property function into a `#[test]` function. To use the `#[quickcheck]` attribute, you must import the `quickcheck` macro from the `quickcheck_macros` crate: ```rust #[cfg(test)] extern crate quickcheck; #[cfg(test)] #[macro_use(quickcheck)] extern crate quickcheck_macros; #[cfg(test)] mod tests { fn reverse(xs: &[T]) -> Vec { let mut rev = vec!(); for x in xs { rev.insert(0, x.clone()) } rev } #[quickcheck] fn double_reversal_is_identity(xs: Vec) -> bool { xs == reverse(&reverse(&xs)) } } ``` ### Installation `quickcheck` is on `crates.io`, so you can include it in your project like so: ```toml [dependencies] quickcheck = "1" ``` If you're only using `quickcheck` in your test code, then you can add it as a development dependency instead: ```toml [dev-dependencies] quickcheck = "1" ``` If you want to use the `#[quickcheck]` attribute, then add `quickcheck_macros` ```toml [dev-dependencies] quickcheck = "1" quickcheck_macros = "1" ``` N.B. When using `quickcheck` (either directly or via the attributes), `RUST_LOG=quickcheck` enables `info!` so that it shows useful output (like the number of tests passed). This is **not** needed to show witnesses for failures. Crate features: - `"use_logging"`: (Enabled by default.) Enables the log messages governed `RUST_LOG`. - `"regex"`: (Enabled by default.) Enables the use of regexes with `env_logger`. ### Minimum Rust version policy This crate's minimum supported `rustc` version is `1.46.0`. The current policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if `crate 1.0` requires Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust 1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum version of Rust. In general, this crate will be conservative with respect to the minimum supported version of Rust. With all of that said, currently, `rand` is a public dependency of `quickcheck`. Therefore, the MSRV policy above only applies when it is more aggressive than `rand`'s MSRV policy. Otherwise, `quickcheck` will defer to `rand`'s MSRV policy. ### Compatibility In general, this crate considers the `Arbitrary` implementations provided as implementation details. Strategies may or may not change over time, which may cause new test failures, presumably due to the discovery of new bugs due to a new kind of witness being generated. These sorts of changes may happen in semver compatible releases. ### Alternative Rust crates for property testing The [`proptest`](https://docs.rs/proptest) crate is inspired by the [Hypothesis](https://hypothesis.works) framework for Python. You can read a comparison between `proptest` and `quickcheck` [here](https://github.com/AltSysrq/proptest/blob/master/proptest/README.md#differences-between-quickcheck-and-proptest) and [here](https://github.com/AltSysrq/proptest/issues/15#issuecomment-348382287). In particular, `proptest` improves on the concept of shrinking. So if you've ever had problems/frustration with shrinking in `quickcheck`, then `proptest` might be worth a try! ### Alternatives for fuzzing Please see the [Rust Fuzz Book](https://rust-fuzz.github.io/book/introduction.html) and the [`arbitrary`](https://crates.io/crates/arbitrary) crate. ### Discarding test results (or, properties are polymorphic!) Sometimes you want to test a property that only holds for a *subset* of the possible inputs, so that when your property is given an input that is outside of that subset, you'd discard it. In particular, the property should *neither* pass nor fail on inputs outside of the subset you want to test. But properties return boolean values—which either indicate pass or fail. To fix this, we need to take a step back and look at the type of the `quickcheck` function: ```rust pub fn quickcheck(f: A) { // elided } ``` So `quickcheck` can test any value with a type that satisfies the `Testable` trait. Great, so what is this `Testable` business? ```rust pub trait Testable { fn result(&self, &mut Gen) -> TestResult; } ``` This trait states that a type is testable if it can produce a `TestResult` given a source of randomness. (A `TestResult` stores information about the results of a test, like whether it passed, failed or has been discarded.) Sure enough, `bool` satisfies the `Testable` trait: ```rust impl Testable for bool { fn result(&self, _: &mut Gen) -> TestResult { TestResult::from_bool(*self) } } ``` But in the example, we gave a *function* to `quickcheck`. Yes, functions can satisfy `Testable` too! ```rust impl Testable for fn(A) -> B { fn result(&self, g: &mut Gen) -> TestResult { // elided } } ``` Which says that a function satisfies `Testable` if and only if it has a single parameter type (whose values can be randomly generated and shrunk) and returns any type (that also satisfies `Testable`). So a function with type `fn(usize) -> bool` satisfies `Testable` since `usize` satisfies `Arbitrary` and `bool` satisfies `Testable`. So to discard a test, we need to return something other than `bool`. What if we just returned a `TestResult` directly? That should work, but we'll need to make sure `TestResult` satisfies `Testable`: ```rust impl Testable for TestResult { fn result(&self, _: &mut Gen) -> TestResult { self.clone() } } ``` Now we can test functions that return a `TestResult` directly. As an example, let's test our reverse function to make sure that the reverse of a vector of length 1 is equal to the vector itself. ```rust fn prop(xs: Vec) -> TestResult { if xs.len() != 1 { return TestResult::discard() } TestResult::from_bool(xs == reverse(&xs)) } quickcheck(prop as fn(Vec) -> TestResult); ``` (A full working program for this example is in [`examples/reverse_single.rs`](https://github.com/BurntSushi/quickcheck/blob/master/examples/reverse_single.rs).) So now our property returns a `TestResult`, which allows us to encode a bit more information. There are a few more [convenience functions defined for the `TestResult` type](https://docs.rs/quickcheck/*/quickcheck/struct.TestResult.html). For example, we can't just return a `bool`, so we convert a `bool` value to a `TestResult`. (The ability to discard tests allows you to get similar functionality as Haskell's `==>` combinator.) N.B. Since discarding a test means it neither passes nor fails, `quickcheck` will try to replace the discarded test with a fresh one. However, if your condition is seldom met, it's possible that `quickcheck` will have to settle for running fewer tests than usual. By default, if `quickcheck` can't find `100` valid tests after trying `10,000` times, then it will give up. These parameters may be changed using [`QuickCheck::tests`](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.tests) and [`QuickCheck::max_tests`](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.max_tests), or by setting the `QUICKCHECK_TESTS` and `QUICKCHECK_MAX_TESTS` environment variables. There is also `QUICKCHECK_MIN_TESTS_PASSED` which sets the minimum number of valid tests that need pass (defaults to `0`) in order for it to be considered a success. ### Shrinking Shrinking is a crucial part of QuickCheck that simplifies counter-examples for your properties automatically. For example, if you erroneously defined a function for reversing vectors as: (my apologies for the contrived example) ```rust fn reverse(xs: &[T]) -> Vec { let mut rev = vec![]; for i in 1..xs.len() { rev.insert(0, xs[i].clone()) } rev } ``` And a property to test that `xs == reverse(reverse(xs))`: ```rust fn prop(xs: Vec) -> bool { xs == reverse(&reverse(&xs)) } quickcheck(prop as fn(Vec) -> bool); ``` Then without shrinking, you might get a counter-example like: ``` [quickcheck] TEST FAILED. Arguments: ([-17, 13, -12, 17, -8, -10, 15, -19, -19, -9, 11, -5, 1, 19, -16, 6]) ``` Which is pretty mysterious. But with shrinking enabled, you're nearly guaranteed to get this counter-example every time: ``` [quickcheck] TEST FAILED. Arguments: ([0]) ``` Which is going to be much easier to debug. ### More Thorough Checking Quickcheck uses random input to test, so it won't always find bugs that could be uncovered with a particular property. You can improve your odds of finding these latent bugs by spending more CPU cycles asking quickcheck to find them for you. There are a few different ways to do this, and which one you choose is mostly a matter of taste. If you are finding yourself doing this sort of thing a lot, you might also be interested in trying out [`cargo fuzz`](https://github.com/rust-fuzz/cargo-fuzz), which runs in a loop by default. ##### Running in a Loop One approach is to run your quickcheck properties in a loop that just keeps going until you tell it to stop or it finds a bug. For example, you could use a bash script such as the following one. ```bash #!/usr/bin/bash while true do cargo test qc_ if [[ x$? != x0 ]] ; then exit $? fi done ``` One thing to note is that this script passes the `qc_` filter to `cargo test`. This assumes that you've prefixed all your quickcheck properties with `qc_`. You could leave off the filter, but then you would be running all your deterministic tests as well, which would take time away from quickcheck! Checking the return code and exiting is also important. Without that test, you won't ever notice when a failure happens. ##### Cranking the Number of Tests Another approach is to just ask quickcheck to run properties more times. You can do this either via the [tests()](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.tests) method, or via the `QUICKCHECK_TESTS` environment variable. This will cause quickcheck to run for a much longer time. Unlike, the loop approach this will take a bounded amount of time, which makes it more suitable for something like a release cycle that wants to really hammer your software. ##### Making Arbitrary Smarter This approach entails spending more time generating interesting inputs in your implementations of Arbitrary. The idea is to focus on the corner cases. This approach can be tricky because programmers are not usually great at intuiting corner cases, and the whole idea of property checking is to take that burden off the programmer. Despite the theoretical discomfort, this approach can turn out to be practical. ### Generating Structs It is very simple to generate structs in QuickCheck. Consider the following example, where the struct `Point` is defined: ```rust struct Point { x: i32, y: i32, } ``` In order to generate a random `Point` instance, you need to implement the trait `Arbitrary` for the struct `Point`: ```rust use quickcheck::{Arbitrary, Gen}; impl Arbitrary for Point { fn arbitrary(g: &mut Gen) -> Point { Point { x: i32::arbitrary(g), y: i32::arbitrary(g), } } } ``` ### Case study: The Sieve of Eratosthenes The [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) is a simple and elegant way to find all primes less than or equal to `N`. Briefly, the algorithm works by allocating an array with `N` slots containing booleans. Slots marked with `false` correspond to prime numbers (or numbers not known to be prime while building the sieve) and slots marked with `true` are known to not be prime. For each `n`, all of its multiples in this array are marked as true. When all `n` have been checked, the numbers marked `false` are returned as the primes. As you might imagine, there's a lot of potential for off-by-one errors, which makes it ideal for randomized testing. So let's take a look at my implementation and see if we can spot the bug: ```rust fn sieve(n: usize) -> Vec { if n <= 1 { return vec![]; } let mut marked = vec![false; n+1]; marked[0] = true; marked[1] = true; marked[2] = true; for p in 2..n { for i in (2*p..n).filter(|&n| n % p == 0) { marked[i] = true; } } marked.iter() .enumerate() .filter_map(|(i, &m)| if m { None } else { Some(i) }) .collect() } ``` Let's try it on a few inputs by hand: ``` sieve(3) => [2, 3] sieve(5) => [2, 3, 5] sieve(8) => [2, 3, 5, 7, 8] # !!! ``` Something has gone wrong! But where? The bug is rather subtle, but it's an easy one to make. It's OK if you can't spot it, because we're going to use QuickCheck to help us track it down. Even before looking at some example outputs, it's good to try and come up with some *properties* that are always satisfiable by the output of the function. An obvious one for the prime number sieve is to check if all numbers returned are prime. For that, we'll need an `is_prime` function: ```rust fn is_prime(n: usize) -> bool { n != 0 && n != 1 && (2..).take_while(|i| i*i <= n).all(|i| n % i != 0) } ``` All this is doing is checking to see if any number in `[2, sqrt(n)]` divides `n` with base cases for `0` and `1`. Now we can write our QuickCheck property: ```rust fn prop_all_prime(n: usize) -> bool { sieve(n).into_iter().all(is_prime) } ``` And finally, we need to invoke `quickcheck` with our property: ```rust fn main() { quickcheck(prop_all_prime as fn(usize) -> bool); } ``` A fully working source file with this code is in [`examples/sieve.rs`](https://github.com/BurntSushi/quickcheck/blob/master/examples/sieve.rs). The output of running this program has this message: ``` [quickcheck] TEST FAILED. Arguments: (4) ``` Which says that `sieve` failed the `prop_all_prime` test when given `n = 4`. Because of shrinking, it was able to find a (hopefully) minimal counter-example for our property. With such a short counter-example, it's hopefully a bit easier to narrow down where the bug is. Since `4` is returned, it's likely never marked as being not prime. Since `4` is a multiple of `2`, its slot should be marked as `true` when `p = 2` on these lines: ```rust for i in (2*p..n).filter(|&n| n % p == 0) { marked[i] = true; } ``` Ah! But does the `..` (range) operator include `n`? Nope! This particular operator is a half-open interval. A `2*p..n` range will never yield `4` when `n = 4`. When we change this to `2*p..n+1`, all tests pass. In addition, if our bug happened to result in an index out-of-bounds error, then `quickcheck` can handle it just like any other failure—including shrinking on failures caused by runtime errors. But hold on... we're not done yet. Right now, our property tests that all the numbers returned by `sieve` are prime but it doesn't test if the list is complete. It does not ensure that all the primes between `0` and `n` are found. Here's a property that is more comprehensive: ```rust fn prop_prime_iff_in_the_sieve(n: usize) -> bool { sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::>() } ``` It tests that for each number between 0 and n, inclusive, the naive primality test yields the same result as the sieve. Now, if we run it: ```rust fn main() { quickcheck(prop_all_prime as fn(usize) -> bool); quickcheck(prop_prime_iff_in_the_sieve as fn(usize) -> bool); } ``` we see that it fails immediately for value n = 2. ``` [quickcheck] TEST FAILED. Arguments: (2) ``` If we inspect `sieve()` once again, we see that we mistakenly mark `2` as non-prime. Removing the line `marked[2] = true;` results in both properties passing. ### What's not in this port of QuickCheck? I think I've captured the key features, but there are still things missing: * Only functions with 8 or fewer parameters can be quickchecked. This limitation can be lifted to some `N`, but requires an implementation for each `n` of the `Testable` trait. * Functions that fail because of a stack overflow are not caught by QuickCheck. Therefore, such failures will not have a witness attached to them. (I'd like to fix this, but I don't know how.) * `Coarbitrary` does not exist in any form in this package. It's unlikely that it ever will. * `Arbitrary` is not implemented for closures. See [issue #56](https://github.com/BurntSushi/quickcheck/issues/56) for more details on why. quickcheck-1.0.3/UNLICENSE000064400000000000000000000022730000000000000131700ustar 00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to quickcheck-1.0.3/examples/btree_set_range.rs000064400000000000000000000033070000000000000172330ustar 00000000000000use std::collections::BTreeSet; use std::ops::Bound::{self, *}; use quickcheck::{quickcheck, TestResult}; /// Covers every `std::ops::Range*` plus variants with exclusive start. type RangeAny = (Bound, Bound); /// Mimic `RangeBounds::contains`, stabilized in Rust 1.35. trait RangeBounds { fn contains(&self, _: &T) -> bool; } impl RangeBounds for RangeAny { fn contains(&self, item: &T) -> bool { (match &self.0 { Included(start) => start <= item, Excluded(start) => start < item, Unbounded => true, }) && (match &self.1 { Included(end) => item <= end, Excluded(end) => item < end, Unbounded => true, }) } } /// Checks conditions where `BTreeSet::range` panics: /// - Panics if range start > end. /// - Panics if range start == end and both bounds are Excluded. fn panics(range: RangeAny) -> bool { match (&range.0, &range.1) { (Excluded(start), Excluded(end)) => start >= end, (Included(start), Excluded(end)) | (Excluded(start), Included(end)) | (Included(start), Included(end)) => start > end, (Unbounded, _) | (_, Unbounded) => false, } } /// Checks that `BTreeSet::range` returns all items contained in the given `range`. fn check_range(set: BTreeSet, range: RangeAny) -> TestResult { if panics(range) { TestResult::discard() } else { let xs: BTreeSet<_> = set.range(range).cloned().collect(); TestResult::from_bool( set.iter().all(|x| range.contains(x) == xs.contains(x)), ) } } fn main() { quickcheck(check_range as fn(_, _) -> TestResult); } quickcheck-1.0.3/examples/out_of_bounds.rs000064400000000000000000000005540000000000000167510ustar 00000000000000use quickcheck::{quickcheck, TestResult}; fn main() { fn prop(length: usize, index: usize) -> TestResult { let v: Vec<_> = (0..length).collect(); if index < length { TestResult::discard() } else { TestResult::must_fail(move || v[index]) } } quickcheck(prop as fn(usize, usize) -> TestResult); } quickcheck-1.0.3/examples/reverse.rs000064400000000000000000000005470000000000000155610ustar 00000000000000use quickcheck::quickcheck; fn reverse(xs: &[T]) -> Vec { let mut rev = vec![]; for x in xs { rev.insert(0, x.clone()) } rev } fn main() { fn equality_after_applying_twice(xs: Vec) -> bool { xs == reverse(&reverse(&xs)) } quickcheck(equality_after_applying_twice as fn(Vec) -> bool); } quickcheck-1.0.3/examples/reverse_single.rs000064400000000000000000000006540000000000000171210ustar 00000000000000use quickcheck::{quickcheck, TestResult}; fn reverse(xs: &[T]) -> Vec { let mut rev = vec![]; for x in xs { rev.insert(0, x.clone()) } rev } fn main() { fn prop(xs: Vec) -> TestResult { if xs.len() != 1 { return TestResult::discard(); } TestResult::from_bool(xs == reverse(&*xs)) } quickcheck(prop as fn(Vec) -> TestResult); } quickcheck-1.0.3/examples/sieve.rs000064400000000000000000000016620000000000000152200ustar 00000000000000use quickcheck::quickcheck; fn sieve(n: usize) -> Vec { if n <= 1 { return vec![]; } let mut marked = vec![false; n + 1]; marked[0] = true; marked[1] = true; marked[2] = true; for p in 2..n { for i in (2 * p..n).filter(|&n| n % p == 0) { marked[i] = true; } } marked .iter() .enumerate() .filter_map(|(i, &m)| if m { None } else { Some(i) }) .collect() } fn is_prime(n: usize) -> bool { n != 0 && n != 1 && (2..).take_while(|i| i * i <= n).all(|i| n % i != 0) } fn main() { fn prop_all_prime(n: usize) -> bool { sieve(n).into_iter().all(is_prime) } fn prop_prime_iff_in_the_sieve(n: usize) -> bool { sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::>() } quickcheck(prop_all_prime as fn(usize) -> bool); quickcheck(prop_prime_iff_in_the_sieve as fn(usize) -> bool); } quickcheck-1.0.3/examples/sort.rs000064400000000000000000000022450000000000000150720ustar 00000000000000// This is a buggy quick sort implementation, QuickCheck will find the bug for // you. use quickcheck::quickcheck; fn smaller_than(xs: &[T], pivot: &T) -> Vec { xs.iter().filter(|&x| *x < *pivot).map(|x| x.clone()).collect() } fn larger_than(xs: &[T], pivot: &T) -> Vec { xs.iter().filter(|&x| *x > *pivot).map(|x| x.clone()).collect() } fn sortk(x: &T, xs: &[T]) -> Vec { let mut result: Vec = sort(&*smaller_than(xs, x)); let last_part = sort(&*larger_than(xs, x)); result.push(x.clone()); result.extend(last_part.iter().map(|x| x.clone())); result } fn sort(list: &[T]) -> Vec { if list.is_empty() { vec![] } else { sortk(&list[0], &list[1..]) } } fn main() { fn is_sorted(xs: Vec) -> bool { for win in xs.windows(2) { if win[0] > win[1] { return false; } } true } fn keeps_length(xs: Vec) -> bool { xs.len() == sort(&*xs).len() } quickcheck(keeps_length as fn(Vec) -> bool); quickcheck(is_sorted as fn(Vec) -> bool) } quickcheck-1.0.3/rustfmt.toml000064400000000000000000000000540000000000000143140ustar 00000000000000max_width = 79 use_small_heuristics = "max" quickcheck-1.0.3/src/arbitrary.rs000064400000000000000000001345200000000000000150550ustar 00000000000000use std::char; use std::collections::{ BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque, }; use std::env; use std::ffi::{CString, OsString}; use std::hash::{BuildHasher, Hash}; use std::iter::{empty, once}; use std::net::{ IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, }; use std::num::Wrapping; use std::num::{ NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, }; use std::ops::{ Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, }; use std::path::PathBuf; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use rand::seq::SliceRandom; use rand::{self, Rng, SeedableRng}; /// Gen represents a PRNG. /// /// It is the source of randomness from which QuickCheck will generate /// values. An instance of `Gen` is passed to every invocation of /// `Arbitrary::arbitrary`, which permits callers to use lower level RNG /// routines to generate values. /// /// It is unspecified whether this is a secure RNG or not. Therefore, callers /// should assume it is insecure. pub struct Gen { rng: rand::rngs::SmallRng, size: usize, } impl Gen { /// Returns a `Gen` with the given size configuration. /// /// The `size` parameter controls the size of random values generated. /// For example, it specifies the maximum length of a randomly generated /// vector, but is and should not be used to control the range of a /// randomly generated number. (Unless that number is used to control the /// size of a data structure.) pub fn new(size: usize) -> Gen { Gen { rng: rand::rngs::SmallRng::from_entropy(), size: size } } /// Returns the size configured with this generator. pub fn size(&self) -> usize { self.size } /// Choose among the possible alternatives in the slice given. If the slice /// is empty, then `None` is returned. Otherwise, a non-`None` value is /// guaranteed to be returned. pub fn choose<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> { slice.choose(&mut self.rng) } fn gen(&mut self) -> T where rand::distributions::Standard: rand::distributions::Distribution, { self.rng.gen() } fn gen_range(&mut self, range: R) -> T where T: rand::distributions::uniform::SampleUniform, R: rand::distributions::uniform::SampleRange, { self.rng.gen_range(range) } } /// Creates a shrinker with zero elements. pub fn empty_shrinker() -> Box> { Box::new(empty()) } /// Creates a shrinker with a single element. pub fn single_shrinker(value: A) -> Box> { Box::new(once(value)) } /// `Arbitrary` describes types whose values can be randomly generated and /// shrunk. /// /// Aside from shrinking, `Arbitrary` is different from typical RNGs in that /// it respects `Gen::size()` for controlling how much memory a particular /// value uses, for practical purposes. For example, `Vec::arbitrary()` /// respects `Gen::size()` to decide the maximum `len()` of the vector. /// This behavior is necessary due to practical speed and size limitations. /// Conversely, `i32::arbitrary()` ignores `size()` since all `i32` values /// require `O(1)` memory and operations between `i32`s require `O(1)` time /// (with the exception of exponentiation). /// /// Additionally, all types that implement `Arbitrary` must also implement /// `Clone`. pub trait Arbitrary: Clone + 'static { /// Return an arbitrary value. /// /// Implementations should respect `Gen::size()` when decisions about how /// big a particular value should be. Implementations should generally /// defer to other `Arbitrary` implementations to generate other random /// values when necessary. The `Gen` type also offers a few RNG helper /// routines. fn arbitrary(g: &mut Gen) -> Self; /// Return an iterator of values that are smaller than itself. /// /// The way in which a value is "smaller" is implementation defined. In /// some cases, the interpretation is obvious: shrinking an integer should /// produce integers smaller than itself. Others are more complex, for /// example, shrinking a `Vec` should both shrink its size and shrink its /// component values. /// /// The iterator returned should be bounded to some reasonable size. /// /// It is always correct to return an empty iterator, and indeed, this /// is the default implementation. The downside of this approach is that /// witnesses to failures in properties will be more inscrutable. fn shrink(&self) -> Box> { empty_shrinker() } } impl Arbitrary for () { fn arbitrary(_: &mut Gen) -> () { () } } impl Arbitrary for bool { fn arbitrary(g: &mut Gen) -> bool { g.gen() } fn shrink(&self) -> Box> { if *self { single_shrinker(false) } else { empty_shrinker() } } } impl Arbitrary for Option { fn arbitrary(g: &mut Gen) -> Option { if g.gen() { None } else { Some(Arbitrary::arbitrary(g)) } } fn shrink(&self) -> Box>> { match *self { None => empty_shrinker(), Some(ref x) => { let chain = single_shrinker(None).chain(x.shrink().map(Some)); Box::new(chain) } } } } impl Arbitrary for Result { fn arbitrary(g: &mut Gen) -> Result { if g.gen() { Ok(Arbitrary::arbitrary(g)) } else { Err(Arbitrary::arbitrary(g)) } } fn shrink(&self) -> Box>> { match *self { Ok(ref x) => { let xs = x.shrink(); let tagged = xs.map(Ok); Box::new(tagged) } Err(ref x) => { let xs = x.shrink(); let tagged = xs.map(Err); Box::new(tagged) } } } } macro_rules! impl_arb_for_single_tuple { ($(($type_param:ident, $tuple_index:tt),)*) => { impl<$($type_param),*> Arbitrary for ($($type_param,)*) where $($type_param: Arbitrary,)* { fn arbitrary(g: &mut Gen) -> ($($type_param,)*) { ( $( $type_param::arbitrary(g), )* ) } fn shrink(&self) -> Box> { let iter = ::std::iter::empty(); $( let cloned = self.clone(); let iter = iter.chain( self.$tuple_index.shrink().map(move |shr_value| { let mut result = cloned.clone(); result.$tuple_index = shr_value; result }) ); )* Box::new(iter) } } }; } macro_rules! impl_arb_for_tuples { (@internal [$($acc:tt,)*]) => { }; (@internal [$($acc:tt,)*] ($type_param:ident, $tuple_index:tt), $($rest:tt,)*) => { impl_arb_for_single_tuple!($($acc,)* ($type_param, $tuple_index),); impl_arb_for_tuples!(@internal [$($acc,)* ($type_param, $tuple_index),] $($rest,)*); }; ($(($type_param:ident, $tuple_index:tt),)*) => { impl_arb_for_tuples!(@internal [] $(($type_param, $tuple_index),)*); }; } impl_arb_for_tuples! { (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), } impl Arbitrary for Vec { fn arbitrary(g: &mut Gen) -> Vec { let size = { let s = g.size(); g.gen_range(0..s) }; (0..size).map(|_| A::arbitrary(g)).collect() } fn shrink(&self) -> Box>> { VecShrinker::new(self.clone()) } } ///Iterator which returns successive attempts to shrink the vector `seed` struct VecShrinker { seed: Vec, /// How much which is removed when trying with smaller vectors size: usize, /// The end of the removed elements offset: usize, /// The shrinker for the element at `offset` once shrinking of individual /// elements are attempted element_shrinker: Box>, } impl VecShrinker { fn new(seed: Vec) -> Box>> { let es = match seed.get(0) { Some(e) => e.shrink(), None => return empty_shrinker(), }; let size = seed.len(); Box::new(VecShrinker { seed: seed, size: size, offset: size, element_shrinker: es, }) } /// Returns the next shrunk element if any, `offset` points to the index /// after the returned element after the function returns fn next_element(&mut self) -> Option { loop { match self.element_shrinker.next() { Some(e) => return Some(e), None => match self.seed.get(self.offset) { Some(e) => { self.element_shrinker = e.shrink(); self.offset += 1; } None => return None, }, } } } } impl Iterator for VecShrinker where A: Arbitrary, { type Item = Vec; fn next(&mut self) -> Option> { // Try with an empty vector first if self.size == self.seed.len() { self.size /= 2; self.offset = self.size; return Some(vec![]); } if self.size != 0 { // Generate a smaller vector by removing the elements between // (offset - size) and offset let xs1 = self.seed[..(self.offset - self.size)] .iter() .chain(&self.seed[self.offset..]) .cloned() .collect(); self.offset += self.size; // Try to reduce the amount removed from the vector once all // previous sizes tried if self.offset > self.seed.len() { self.size /= 2; self.offset = self.size; } Some(xs1) } else { // A smaller vector did not work so try to shrink each element of // the vector instead Reuse `offset` as the index determining which // element to shrink // The first element shrinker is already created so skip the first // offset (self.offset == 0 only on first entry to this part of the // iterator) if self.offset == 0 { self.offset = 1 } match self.next_element() { Some(e) => Some( self.seed[..self.offset - 1] .iter() .cloned() .chain(Some(e).into_iter()) .chain(self.seed[self.offset..].iter().cloned()) .collect(), ), None => None, } } } } impl Arbitrary for BTreeMap { fn arbitrary(g: &mut Gen) -> BTreeMap { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } fn shrink(&self) -> Box>> { let vec: Vec<(K, V)> = self.clone().into_iter().collect(); Box::new( vec.shrink().map(|v| v.into_iter().collect::>()), ) } } impl< K: Arbitrary + Eq + Hash, V: Arbitrary, S: BuildHasher + Default + Clone + 'static, > Arbitrary for HashMap { fn arbitrary(g: &mut Gen) -> Self { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } fn shrink(&self) -> Box> { let vec: Vec<(K, V)> = self.clone().into_iter().collect(); Box::new(vec.shrink().map(|v| v.into_iter().collect::())) } } impl Arbitrary for BTreeSet { fn arbitrary(g: &mut Gen) -> BTreeSet { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } fn shrink(&self) -> Box>> { let vec: Vec = self.clone().into_iter().collect(); Box::new(vec.shrink().map(|v| v.into_iter().collect::>())) } } impl Arbitrary for BinaryHeap { fn arbitrary(g: &mut Gen) -> BinaryHeap { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } fn shrink(&self) -> Box>> { let vec: Vec = self.clone().into_iter().collect(); Box::new( vec.shrink().map(|v| v.into_iter().collect::>()), ) } } impl Arbitrary for HashSet { fn arbitrary(g: &mut Gen) -> Self { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } fn shrink(&self) -> Box> { let vec: Vec = self.clone().into_iter().collect(); Box::new(vec.shrink().map(|v| v.into_iter().collect::())) } } impl Arbitrary for LinkedList { fn arbitrary(g: &mut Gen) -> LinkedList { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } fn shrink(&self) -> Box>> { let vec: Vec = self.clone().into_iter().collect(); Box::new( vec.shrink().map(|v| v.into_iter().collect::>()), ) } } impl Arbitrary for VecDeque { fn arbitrary(g: &mut Gen) -> VecDeque { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } fn shrink(&self) -> Box>> { let vec: Vec = self.clone().into_iter().collect(); Box::new(vec.shrink().map(|v| v.into_iter().collect::>())) } } impl Arbitrary for IpAddr { fn arbitrary(g: &mut Gen) -> IpAddr { let ipv4: bool = g.gen(); if ipv4 { IpAddr::V4(Arbitrary::arbitrary(g)) } else { IpAddr::V6(Arbitrary::arbitrary(g)) } } } impl Arbitrary for Ipv4Addr { fn arbitrary(g: &mut Gen) -> Ipv4Addr { Ipv4Addr::new(g.gen(), g.gen(), g.gen(), g.gen()) } } impl Arbitrary for Ipv6Addr { fn arbitrary(g: &mut Gen) -> Ipv6Addr { Ipv6Addr::new( g.gen(), g.gen(), g.gen(), g.gen(), g.gen(), g.gen(), g.gen(), g.gen(), ) } } impl Arbitrary for SocketAddr { fn arbitrary(g: &mut Gen) -> SocketAddr { SocketAddr::new(Arbitrary::arbitrary(g), g.gen()) } } impl Arbitrary for SocketAddrV4 { fn arbitrary(g: &mut Gen) -> SocketAddrV4 { SocketAddrV4::new(Arbitrary::arbitrary(g), g.gen()) } } impl Arbitrary for SocketAddrV6 { fn arbitrary(g: &mut Gen) -> SocketAddrV6 { SocketAddrV6::new(Arbitrary::arbitrary(g), g.gen(), g.gen(), g.gen()) } } impl Arbitrary for PathBuf { fn arbitrary(g: &mut Gen) -> PathBuf { // use some real directories as guesses, so we may end up with // actual working directories in case that is relevant. let here = env::current_dir().unwrap_or(PathBuf::from("/test/directory")); let temp = env::temp_dir(); #[allow(deprecated)] let home = env::home_dir().unwrap_or(PathBuf::from("/home/user")); let mut p = g .choose(&[ here, temp, home, PathBuf::from("."), PathBuf::from(".."), PathBuf::from("../../.."), PathBuf::new(), ]) .unwrap() .to_owned(); p.extend(Vec::::arbitrary(g).iter()); p } fn shrink(&self) -> Box> { let mut shrunk = vec![]; let mut popped = self.clone(); if popped.pop() { shrunk.push(popped); } // Iterating over a Path performs a small amount of normalization. let normalized = self.iter().collect::(); if normalized.as_os_str() != self.as_os_str() { shrunk.push(normalized); } // Add the canonicalized variant only if canonicalizing the path // actually does something, making it (hopefully) smaller. Also, ignore // canonicalization if canonicalization errors. if let Ok(canonicalized) = self.canonicalize() { if canonicalized.as_os_str() != self.as_os_str() { shrunk.push(canonicalized); } } Box::new(shrunk.into_iter()) } } impl Arbitrary for OsString { fn arbitrary(g: &mut Gen) -> OsString { OsString::from(String::arbitrary(g)) } fn shrink(&self) -> Box> { let mystring: String = self.clone().into_string().unwrap(); Box::new(mystring.shrink().map(|s| OsString::from(s))) } } impl Arbitrary for String { fn arbitrary(g: &mut Gen) -> String { let size = { let s = g.size(); g.gen_range(0..s) }; (0..size).map(|_| char::arbitrary(g)).collect() } fn shrink(&self) -> Box> { // Shrink a string by shrinking a vector of its characters. let chars: Vec = self.chars().collect(); Box::new(chars.shrink().map(|x| x.into_iter().collect::())) } } impl Arbitrary for CString { fn arbitrary(g: &mut Gen) -> Self { let size = { let s = g.size(); g.gen_range(0..s) }; // Use either random bytes or random UTF-8 encoded codepoints. let utf8: bool = g.gen(); if utf8 { CString::new( (0..) .map(|_| char::arbitrary(g)) .filter(|&c| c != '\0') .take(size) .collect::(), ) } else { CString::new( (0..) .map(|_| u8::arbitrary(g)) .filter(|&c| c != b'\0') .take(size) .collect::>(), ) } .expect("null characters should have been filtered out") } fn shrink(&self) -> Box> { // Use the implementation for a vec here, but make sure null characters // are filtered out. Box::new(VecShrinker::new(self.as_bytes().to_vec()).map(|bytes| { CString::new( bytes.into_iter().filter(|&c| c != 0).collect::>(), ) .expect("null characters should have been filtered out") })) } } impl Arbitrary for char { fn arbitrary(g: &mut Gen) -> char { let mode = g.gen_range(0..100); match mode { 0..=49 => { // ASCII + some control characters g.gen_range(0..0xB0) as u8 as char } 50..=59 => { // Unicode BMP characters loop { if let Some(x) = char::from_u32(g.gen_range(0..0x10000)) { return x; } // ignore surrogate pairs } } 60..=84 => { // Characters often used in programming languages g.choose(&[ ' ', ' ', ' ', '\t', '\n', '~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '-', '=', '+', '[', ']', '{', '}', ':', ';', '\'', '"', '\\', '|', ',', '<', '>', '.', '/', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ]) .unwrap() .to_owned() } 85..=89 => { // Tricky Unicode, part 1 g.choose(&[ '\u{0149}', // a deprecated character '\u{fff0}', // some of "Other, format" category: '\u{fff1}', '\u{fff2}', '\u{fff3}', '\u{fff4}', '\u{fff5}', '\u{fff6}', '\u{fff7}', '\u{fff8}', '\u{fff9}', '\u{fffA}', '\u{fffB}', '\u{fffC}', '\u{fffD}', '\u{fffE}', '\u{fffF}', '\u{0600}', '\u{0601}', '\u{0602}', '\u{0603}', '\u{0604}', '\u{0605}', '\u{061C}', '\u{06DD}', '\u{070F}', '\u{180E}', '\u{110BD}', '\u{1D173}', '\u{e0001}', // tag '\u{e0020}', // tag space '\u{e000}', '\u{e001}', '\u{ef8ff}', // private use '\u{f0000}', '\u{ffffd}', '\u{ffffe}', '\u{fffff}', '\u{100000}', '\u{10FFFD}', '\u{10FFFE}', '\u{10FFFF}', // "Other, surrogate" characters are so that very special // that they are not even allowed in safe Rust, //so omitted here '\u{3000}', // ideographic space '\u{1680}', // other space characters are already covered by two next // branches ]) .unwrap() .to_owned() } 90..=94 => { // Tricky unicode, part 2 char::from_u32(g.gen_range(0x2000..0x2070)).unwrap() } 95..=99 => { // Completely arbitrary characters g.gen() } _ => unreachable!(), } } fn shrink(&self) -> Box> { Box::new((*self as u32).shrink().filter_map(char::from_u32)) } } macro_rules! unsigned_shrinker { ($ty:ty) => { mod shrinker { pub struct UnsignedShrinker { x: $ty, i: $ty, } impl UnsignedShrinker { pub fn new(x: $ty) -> Box> { if x == 0 { super::empty_shrinker() } else { Box::new( vec![0] .into_iter() .chain(UnsignedShrinker { x: x, i: x / 2 }), ) } } } impl Iterator for UnsignedShrinker { type Item = $ty; fn next(&mut self) -> Option<$ty> { if self.x - self.i < self.x { let result = Some(self.x - self.i); self.i = self.i / 2; result } else { None } } } } }; } macro_rules! unsigned_problem_values { ($t:ty) => { &[<$t>::min_value(), 1, <$t>::max_value()] }; } macro_rules! unsigned_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { fn arbitrary(g: &mut Gen) -> $ty { match g.gen_range(0..10) { 0 => { *g.choose(unsigned_problem_values!($ty)).unwrap() }, _ => g.gen() } } fn shrink(&self) -> Box> { unsigned_shrinker!($ty); shrinker::UnsignedShrinker::new(*self) } } )* } } unsigned_arbitrary! { usize, u8, u16, u32, u64, u128 } macro_rules! signed_shrinker { ($ty:ty) => { mod shrinker { pub struct SignedShrinker { x: $ty, i: $ty, } impl SignedShrinker { pub fn new(x: $ty) -> Box> { if x == 0 { super::empty_shrinker() } else { let shrinker = SignedShrinker { x: x, i: x / 2 }; let mut items = vec![0]; if shrinker.i < 0 && shrinker.x != <$ty>::MIN { items.push(shrinker.x.abs()); } Box::new(items.into_iter().chain(shrinker)) } } } impl Iterator for SignedShrinker { type Item = $ty; fn next(&mut self) -> Option<$ty> { if self.x == <$ty>::MIN || (self.x - self.i).abs() < self.x.abs() { let result = Some(self.x - self.i); self.i = self.i / 2; result } else { None } } } } }; } macro_rules! signed_problem_values { ($t:ty) => { &[<$t>::min_value(), 0, <$t>::max_value()] }; } macro_rules! signed_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { fn arbitrary(g: &mut Gen) -> $ty { match g.gen_range(0..10) { 0 => { *g.choose(signed_problem_values!($ty)).unwrap() }, _ => g.gen() } } fn shrink(&self) -> Box> { signed_shrinker!($ty); shrinker::SignedShrinker::new(*self) } } )* } } signed_arbitrary! { isize, i8, i16, i32, i64, i128 } macro_rules! float_problem_values { ($path:path) => {{ // hack. see: https://github.com/rust-lang/rust/issues/48067 use $path as p; &[p::NAN, p::NEG_INFINITY, p::MIN, -0., 0., p::MAX, p::INFINITY] }}; } macro_rules! float_arbitrary { ($($t:ty, $path:path, $shrinkable:ty),+) => {$( impl Arbitrary for $t { fn arbitrary(g: &mut Gen) -> $t { match g.gen_range(0..10) { 0 => *g.choose(float_problem_values!($path)).unwrap(), _ => { use $path as p; let exp = g.gen_range((0.)..p::MAX_EXP as i16 as $t); let mantissa = g.gen_range((1.)..2.); let sign = *g.choose(&[-1., 1.]).unwrap(); sign * mantissa * exp.exp2() } } } fn shrink(&self) -> Box> { signed_shrinker!($shrinkable); let it = shrinker::SignedShrinker::new(*self as $shrinkable); Box::new(it.map(|x| x as $t)) } } )*}; } float_arbitrary!(f32, std::f32, i32, f64, std::f64, i64); macro_rules! unsigned_non_zero_shrinker { ($ty:tt) => { mod shrinker { pub struct UnsignedNonZeroShrinker { x: $ty, i: $ty, } impl UnsignedNonZeroShrinker { pub fn new(x: $ty) -> Box> { debug_assert!(x > 0); if x == 1 { super::empty_shrinker() } else { Box::new( std::iter::once(1).chain( UnsignedNonZeroShrinker { x: x, i: x / 2 }, ), ) } } } impl Iterator for UnsignedNonZeroShrinker { type Item = $ty; fn next(&mut self) -> Option<$ty> { if self.x - self.i < self.x { let result = Some(self.x - self.i); self.i = self.i / 2; result } else { None } } } } }; } macro_rules! unsigned_non_zero_arbitrary { ($($ty:tt => $inner:tt),*) => { $( impl Arbitrary for $ty { fn arbitrary(g: &mut Gen) -> $ty { let mut v: $inner = g.gen(); if v == 0 { v += 1; } $ty::new(v).expect("non-zero value contsturction failed") } fn shrink(&self) -> Box> { unsigned_non_zero_shrinker!($inner); Box::new(shrinker::UnsignedNonZeroShrinker::new(self.get()) .map($ty::new) .map(Option::unwrap)) } } )* } } unsigned_non_zero_arbitrary! { NonZeroUsize => usize, NonZeroU8 => u8, NonZeroU16 => u16, NonZeroU32 => u32, NonZeroU64 => u64, NonZeroU128 => u128 } impl Arbitrary for Wrapping { fn arbitrary(g: &mut Gen) -> Wrapping { Wrapping(T::arbitrary(g)) } fn shrink(&self) -> Box>> { Box::new(self.0.shrink().map(|inner| Wrapping(inner))) } } impl Arbitrary for Bound { fn arbitrary(g: &mut Gen) -> Bound { match g.gen_range(0..3) { 0 => Bound::Included(T::arbitrary(g)), 1 => Bound::Excluded(T::arbitrary(g)), _ => Bound::Unbounded, } } fn shrink(&self) -> Box>> { match *self { Bound::Included(ref x) => { Box::new(x.shrink().map(Bound::Included)) } Bound::Excluded(ref x) => { Box::new(x.shrink().map(Bound::Excluded)) } Bound::Unbounded => empty_shrinker(), } } } impl Arbitrary for Range { fn arbitrary(g: &mut Gen) -> Range { Arbitrary::arbitrary(g)..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { Box::new( (self.start.clone(), self.end.clone()).shrink().map(|(s, e)| s..e), ) } } impl Arbitrary for RangeInclusive { fn arbitrary(g: &mut Gen) -> RangeInclusive { Arbitrary::arbitrary(g)..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { Box::new( (self.start().clone(), self.end().clone()) .shrink() .map(|(s, e)| s..=e), ) } } impl Arbitrary for RangeFrom { fn arbitrary(g: &mut Gen) -> RangeFrom { Arbitrary::arbitrary(g).. } fn shrink(&self) -> Box>> { Box::new(self.start.clone().shrink().map(|start| start..)) } } impl Arbitrary for RangeTo { fn arbitrary(g: &mut Gen) -> RangeTo { ..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { Box::new(self.end.clone().shrink().map(|end| ..end)) } } impl Arbitrary for RangeToInclusive { fn arbitrary(g: &mut Gen) -> RangeToInclusive { ..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { Box::new(self.end.clone().shrink().map(|end| ..=end)) } } impl Arbitrary for RangeFull { fn arbitrary(_: &mut Gen) -> RangeFull { .. } } impl Arbitrary for Duration { fn arbitrary(gen: &mut Gen) -> Self { let seconds = gen.gen_range(0..gen.size() as u64); let nanoseconds = gen.gen_range(0..1_000_000); Duration::new(seconds, nanoseconds) } fn shrink(&self) -> Box> { Box::new( (self.as_secs(), self.subsec_nanos()) .shrink() .map(|(secs, nanos)| Duration::new(secs, nanos % 1_000_000)), ) } } impl Arbitrary for Box { fn arbitrary(g: &mut Gen) -> Box { Box::new(A::arbitrary(g)) } fn shrink(&self) -> Box>> { Box::new((**self).shrink().map(Box::new)) } } impl Arbitrary for Arc { fn arbitrary(g: &mut Gen) -> Arc { Arc::new(A::arbitrary(g)) } fn shrink(&self) -> Box>> { Box::new((**self).shrink().map(Arc::new)) } } impl Arbitrary for SystemTime { fn arbitrary(gen: &mut Gen) -> Self { let after_epoch = bool::arbitrary(gen); let duration = Duration::arbitrary(gen); if after_epoch { UNIX_EPOCH + duration } else { UNIX_EPOCH - duration } } fn shrink(&self) -> Box> { let duration = match self.duration_since(UNIX_EPOCH) { Ok(duration) => duration, Err(e) => e.duration(), }; Box::new( duration .shrink() .flat_map(|d| vec![UNIX_EPOCH + d, UNIX_EPOCH - d]), ) } } #[cfg(test)] mod test { use std::collections::{ BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque, }; use std::fmt::Debug; use std::hash::Hash; use std::num::Wrapping; use std::path::PathBuf; use super::{Arbitrary, Gen}; #[test] fn arby_unit() { assert_eq!(arby::<()>(), ()); } macro_rules! arby_int { ( $signed:expr, $($t:ty),+) => {$( let mut arbys = (0..1_000_000).map(|_| arby::<$t>()); let mut problems = if $signed { signed_problem_values!($t).iter() } else { unsigned_problem_values!($t).iter() }; assert!(problems.all(|p| arbys.any(|arby| arby == *p)), "Arbitrary does not generate all problematic values"); let max = <$t>::max_value(); let mid = (max + <$t>::min_value()) / 2; // split full range of $t into chunks // Arbitrary must return some value in each chunk let double_chunks: $t = 9; let chunks = double_chunks * 2; // chunks must be even let lim: Box> = if $signed { Box::new((0..=chunks) .map(|idx| idx - chunks / 2) .map(|x| mid + max / (chunks / 2) * x)) } else { Box::new((0..=chunks).map(|idx| max / chunks * idx)) }; let mut lim = lim.peekable(); while let (Some(low), Some(&high)) = (lim.next(), lim.peek()) { assert!(arbys.any(|arby| low <= arby && arby <= high), "Arbitrary doesn't generate numbers in {}..={}", low, high) } )*}; } #[test] fn arby_int() { arby_int!(true, i8, i16, i32, i64, isize, i128); } #[test] fn arby_uint() { arby_int!(false, u8, u16, u32, u64, usize, u128); } macro_rules! arby_float { ($($t:ty, $path:path),+) => {$({ use $path as p; let mut arbys = (0..1_000_000).map(|_| arby::<$t>()); //NaN != NaN assert!(arbys.any(|f| f.is_nan()), "Arbitrary does not generate the problematic value NaN" ); for p in float_problem_values!($path).iter().filter(|f| !f.is_nan()) { assert!(arbys.any(|arby| arby == *p), "Arbitrary does not generate the problematic value {}", p ); } // split full range of $t into chunks // Arbitrary must return some value in each chunk let double_chunks: i8 = 9; let chunks = double_chunks * 2; // chunks must be even let lim = (-double_chunks..=double_chunks) .map(|idx| <$t>::from(idx)) .map(|idx| p::MAX/(<$t>::from(chunks/2)) * idx); let mut lim = lim.peekable(); while let (Some(low), Some(&high)) = (lim.next(), lim.peek()) { assert!( arbys.any(|arby| low <= arby && arby <= high), "Arbitrary doesn't generate numbers in {:e}..={:e}", low, high, ) } })*}; } #[test] fn arby_float() { arby_float!(f32, std::f32, f64, std::f64); } fn arby() -> A { Arbitrary::arbitrary(&mut Gen::new(5)) } // Shrink testing. #[test] fn unit() { eq((), vec![]); } #[test] fn bools() { eq(false, vec![]); eq(true, vec![false]); } #[test] fn options() { eq(None::<()>, vec![]); eq(Some(false), vec![None]); eq(Some(true), vec![None, Some(false)]); } #[test] fn results() { // Result doesn't implement the Hash trait, so these tests // depends on the order of shrunk results. Ug. // TODO: Fix this. ordered_eq(Ok::(true), vec![Ok(false)]); ordered_eq(Err::<(), bool>(true), vec![Err(false)]); } #[test] fn tuples() { eq((false, false), vec![]); eq((true, false), vec![(false, false)]); eq((true, true), vec![(false, true), (true, false)]); } #[test] fn triples() { eq((false, false, false), vec![]); eq((true, false, false), vec![(false, false, false)]); eq( (true, true, false), vec![(false, true, false), (true, false, false)], ); } #[test] fn quads() { eq((false, false, false, false), vec![]); eq((true, false, false, false), vec![(false, false, false, false)]); eq( (true, true, false, false), vec![(false, true, false, false), (true, false, false, false)], ); } #[test] fn ints() { // TODO: Test overflow? eq(5isize, vec![0, 3, 4]); eq(-5isize, vec![5, 0, -3, -4]); eq(0isize, vec![]); } #[test] fn ints8() { eq(5i8, vec![0, 3, 4]); eq(-5i8, vec![5, 0, -3, -4]); eq(0i8, vec![]); } #[test] fn ints16() { eq(5i16, vec![0, 3, 4]); eq(-5i16, vec![5, 0, -3, -4]); eq(0i16, vec![]); } #[test] fn ints32() { eq(5i32, vec![0, 3, 4]); eq(-5i32, vec![5, 0, -3, -4]); eq(0i32, vec![]); } #[test] fn ints64() { eq(5i64, vec![0, 3, 4]); eq(-5i64, vec![5, 0, -3, -4]); eq(0i64, vec![]); } #[test] fn ints128() { eq(5i128, vec![0, 3, 4]); eq(-5i128, vec![5, 0, -3, -4]); eq(0i128, vec![]); } #[test] fn uints() { eq(5usize, vec![0, 3, 4]); eq(0usize, vec![]); } #[test] fn uints8() { eq(5u8, vec![0, 3, 4]); eq(0u8, vec![]); } #[test] fn uints16() { eq(5u16, vec![0, 3, 4]); eq(0u16, vec![]); } #[test] fn uints32() { eq(5u32, vec![0, 3, 4]); eq(0u32, vec![]); } #[test] fn uints64() { eq(5u64, vec![0, 3, 4]); eq(0u64, vec![]); } #[test] fn uints128() { eq(5u128, vec![0, 3, 4]); eq(0u128, vec![]); } macro_rules! define_float_eq { ($ty:ty) => { fn eq(s: $ty, v: Vec<$ty>) { let shrunk: Vec<$ty> = s.shrink().collect(); for n in v { let found = shrunk.iter().any(|&i| i == n); if !found { panic!(format!( "Element {:?} was not found \ in shrink results {:?}", n, shrunk )); } } } }; } #[test] fn floats32() { define_float_eq!(f32); eq(0.0, vec![]); eq(-0.0, vec![]); eq(1.0, vec![0.0]); eq(2.0, vec![0.0, 1.0]); eq(-2.0, vec![0.0, 2.0, -1.0]); eq(1.5, vec![0.0]); } #[test] fn floats64() { define_float_eq!(f64); eq(0.0, vec![]); eq(-0.0, vec![]); eq(1.0, vec![0.0]); eq(2.0, vec![0.0, 1.0]); eq(-2.0, vec![0.0, 2.0, -1.0]); eq(1.5, vec![0.0]); } #[test] fn wrapping_ints32() { eq(Wrapping(5i32), vec![Wrapping(0), Wrapping(3), Wrapping(4)]); eq( Wrapping(-5i32), vec![Wrapping(5), Wrapping(0), Wrapping(-3), Wrapping(-4)], ); eq(Wrapping(0i32), vec![]); } #[test] fn vecs() { eq( { let it: Vec = vec![]; it }, vec![], ); eq( { let it: Vec> = vec![vec![]]; it }, vec![vec![]], ); eq(vec![1isize], vec![vec![], vec![0]]); eq(vec![11isize], vec![vec![], vec![0], vec![6], vec![9], vec![10]]); eq( vec![3isize, 5], vec![ vec![], vec![5], vec![3], vec![0, 5], vec![2, 5], vec![3, 0], vec![3, 3], vec![3, 4], ], ); } macro_rules! map_tests { ($name:ident, $ctor:expr) => { #[test] fn $name() { ordered_eq($ctor, vec![]); { let mut map = $ctor; map.insert(1usize, 1isize); let shrinks = vec![ $ctor, { let mut m = $ctor; m.insert(0, 1); m }, { let mut m = $ctor; m.insert(1, 0); m }, ]; ordered_eq(map, shrinks); } } }; } map_tests!(btreemap, BTreeMap::::new()); map_tests!(hashmap, HashMap::::new()); macro_rules! list_tests { ($name:ident, $ctor:expr, $push:ident) => { #[test] fn $name() { ordered_eq($ctor, vec![]); { let mut list = $ctor; list.$push(2usize); let shrinks = vec![ $ctor, { let mut m = $ctor; m.$push(0); m }, { let mut m = $ctor; m.$push(1); m }, ]; ordered_eq(list, shrinks); } } }; } list_tests!(btreesets, BTreeSet::::new(), insert); list_tests!(hashsets, HashSet::::new(), insert); list_tests!(linkedlists, LinkedList::::new(), push_back); list_tests!(vecdeques, VecDeque::::new(), push_back); #[test] fn binaryheaps() { ordered_eq( BinaryHeap::::new().into_iter().collect::>(), vec![], ); { let mut heap = BinaryHeap::::new(); heap.push(2usize); let shrinks = vec![vec![], vec![0], vec![1]]; ordered_eq(heap.into_iter().collect::>(), shrinks); } } #[test] fn chars() { eq('\x00', vec![]); } // All this jazz is for testing set equality on the results of a shrinker. fn eq(s: A, v: Vec) { let (left, right) = (shrunk(s), set(v)); assert_eq!(left, right); } fn shrunk(s: A) -> HashSet { set(s.shrink()) } fn set>(xs: I) -> HashSet { xs.into_iter().collect() } fn ordered_eq(s: A, v: Vec) { let (left, right) = (s.shrink().collect::>(), v); assert_eq!(left, right); } #[test] fn bounds() { use std::ops::Bound::*; for i in -5..=5 { ordered_eq(Included(i), i.shrink().map(Included).collect()); ordered_eq(Excluded(i), i.shrink().map(Excluded).collect()); } eq(Unbounded::, vec![]); } #[test] fn ranges() { ordered_eq(0..0, vec![]); ordered_eq(1..1, vec![0..1, 1..0]); ordered_eq(3..5, vec![0..5, 2..5, 3..0, 3..3, 3..4]); ordered_eq(5..3, vec![0..3, 3..3, 4..3, 5..0, 5..2]); ordered_eq(3.., vec![0.., 2..]); ordered_eq(..3, vec![..0, ..2]); ordered_eq(.., vec![]); ordered_eq(3..=5, vec![0..=5, 2..=5, 3..=0, 3..=3, 3..=4]); ordered_eq(..=3, vec![..=0, ..=2]); } #[test] fn pathbuf() { ordered_eq( PathBuf::from("/home/foo//.././bar"), vec![ PathBuf::from("/home/foo//.."), PathBuf::from("/home/foo/../bar"), ], ); } } quickcheck-1.0.3/src/lib.rs000064400000000000000000000050000000000000000136120ustar 00000000000000/*! This crate is a port of [Haskell's QuickCheck](https://hackage.haskell.org/package/QuickCheck). For detailed examples, please see the [README](https://github.com/BurntSushi/quickcheck). # Compatibility In general, this crate considers the `Arbitrary` implementations provided as implementation details. Strategies may or may not change over time, which may cause new test failures, presumably due to the discovery of new bugs due to a new kind of witness being generated. These sorts of changes may happen in semver compatible releases. */ pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; /// A macro for writing quickcheck tests. /// /// This macro takes as input one or more property functions to test, and /// produces a proper `#[test]` function for each property. If the property /// fails, the behavior is as if `quickcheck` were called on the property /// (i.e., it panics and fails the test). /// /// Note that this macro doesn't support `mut` or patterns in parameters. /// /// # Example /// /// ```rust /// # #[macro_use] extern crate quickcheck; fn main() { /// quickcheck! { /// fn prop_reverse_reverse(xs: Vec) -> bool { /// let rev: Vec<_> = xs.clone().into_iter().rev().collect(); /// let revrev: Vec<_> = rev.into_iter().rev().collect(); /// xs == revrev /// } /// }; /// # } /// ``` #[macro_export] macro_rules! quickcheck { (@as_items $($i:item)*) => ($($i)*); { $( $(#[$m:meta])* fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { $($code:tt)* } )* } => ( $crate::quickcheck! { @as_items $( #[test] $(#[$m])* fn $fn_name() { fn prop($($arg_name: $arg_ty),*) -> $ret { $($code)* } $crate::quickcheck(prop as fn($($arg_ty),*) -> $ret); } )* } ) } #[cfg(feature = "use_logging")] fn env_logger_init() -> Result<(), log::SetLoggerError> { env_logger::try_init() } #[cfg(feature = "use_logging")] macro_rules! info { ($($tt:tt)*) => { log::info!($($tt)*) }; } #[cfg(not(feature = "use_logging"))] fn env_logger_init() {} #[cfg(not(feature = "use_logging"))] macro_rules! info { ($($_ignore:tt)*) => { () }; } mod arbitrary; mod tester; #[cfg(test)] mod tests; quickcheck-1.0.3/src/tester.rs000064400000000000000000000325640000000000000143710ustar 00000000000000use std::cmp; use std::env; use std::fmt::Debug; use std::panic; use crate::{ tester::Status::{Discard, Fail, Pass}, Arbitrary, Gen, }; /// The main QuickCheck type for setting configuration and running QuickCheck. pub struct QuickCheck { tests: u64, max_tests: u64, min_tests_passed: u64, gen: Gen, } fn qc_tests() -> u64 { let default = 100; match env::var("QUICKCHECK_TESTS") { Ok(val) => val.parse().unwrap_or(default), Err(_) => default, } } fn qc_max_tests() -> u64 { let default = 10_000; match env::var("QUICKCHECK_MAX_TESTS") { Ok(val) => val.parse().unwrap_or(default), Err(_) => default, } } fn qc_gen_size() -> usize { let default = 100; match env::var("QUICKCHECK_GENERATOR_SIZE") { Ok(val) => val.parse().unwrap_or(default), Err(_) => default, } } fn qc_min_tests_passed() -> u64 { let default = 0; match env::var("QUICKCHECK_MIN_TESTS_PASSED") { Ok(val) => val.parse().unwrap_or(default), Err(_) => default, } } impl QuickCheck { /// Creates a new QuickCheck value. /// /// This can be used to run QuickCheck on things that implement `Testable`. /// You may also adjust the configuration, such as the number of tests to /// run. /// /// By default, the maximum number of passed tests is set to `100`, the max /// number of overall tests is set to `10000` and the generator is created /// with a size of `100`. pub fn new() -> QuickCheck { let gen = Gen::new(qc_gen_size()); let tests = qc_tests(); let max_tests = cmp::max(tests, qc_max_tests()); let min_tests_passed = qc_min_tests_passed(); QuickCheck { tests, max_tests, min_tests_passed, gen } } /// Set the random number generator to be used by QuickCheck. pub fn gen(self, gen: Gen) -> QuickCheck { QuickCheck { gen, ..self } } /// Set the number of tests to run. /// /// This actually refers to the maximum number of *passed* tests that /// can occur. Namely, if a test causes a failure, future testing on that /// property stops. Additionally, if tests are discarded, there may be /// fewer than `tests` passed. pub fn tests(mut self, tests: u64) -> QuickCheck { self.tests = tests; self } /// Set the maximum number of tests to run. /// /// The number of invocations of a property will never exceed this number. /// This is necessary to cap the number of tests because QuickCheck /// properties can discard tests. pub fn max_tests(mut self, max_tests: u64) -> QuickCheck { self.max_tests = max_tests; self } /// Set the minimum number of tests that needs to pass. /// /// This actually refers to the minimum number of *valid* *passed* tests /// that needs to pass for the property to be considered successful. pub fn min_tests_passed(mut self, min_tests_passed: u64) -> QuickCheck { self.min_tests_passed = min_tests_passed; self } /// Tests a property and returns the result. /// /// The result returned is either the number of tests passed or a witness /// of failure. /// /// (If you're using Rust's unit testing infrastructure, then you'll /// want to use the `quickcheck` method, which will `panic!` on failure.) pub fn quicktest(&mut self, f: A) -> Result where A: Testable, { let mut n_tests_passed = 0; for _ in 0..self.max_tests { if n_tests_passed >= self.tests { break; } match f.result(&mut self.gen) { TestResult { status: Pass, .. } => n_tests_passed += 1, TestResult { status: Discard, .. } => continue, r @ TestResult { status: Fail, .. } => return Err(r), } } Ok(n_tests_passed) } /// Tests a property and calls `panic!` on failure. /// /// The `panic!` message will include a (hopefully) minimal witness of /// failure. /// /// It is appropriate to use this method with Rust's unit testing /// infrastructure. /// /// Note that if the environment variable `RUST_LOG` is set to enable /// `info` level log messages for the `quickcheck` crate, then this will /// include output on how many QuickCheck tests were passed. /// /// # Example /// /// ```rust /// use quickcheck::QuickCheck; /// /// fn prop_reverse_reverse() { /// fn revrev(xs: Vec) -> bool { /// let rev: Vec<_> = xs.clone().into_iter().rev().collect(); /// let revrev: Vec<_> = rev.into_iter().rev().collect(); /// xs == revrev /// } /// QuickCheck::new().quickcheck(revrev as fn(Vec) -> bool); /// } /// ``` pub fn quickcheck(&mut self, f: A) where A: Testable, { // Ignore log init failures, implying it has already been done. let _ = crate::env_logger_init(); let n_tests_passed = match self.quicktest(f) { Ok(n_tests_passed) => n_tests_passed, Err(result) => panic!(result.failed_msg()), }; if n_tests_passed >= self.min_tests_passed { info!("(Passed {} QuickCheck tests.)", n_tests_passed) } else { panic!( "(Unable to generate enough tests, {} not discarded.)", n_tests_passed ) } } } /// Convenience function for running QuickCheck. /// /// This is an alias for `QuickCheck::new().quickcheck(f)`. pub fn quickcheck(f: A) { QuickCheck::new().quickcheck(f) } /// Describes the status of a single instance of a test. /// /// All testable things must be capable of producing a `TestResult`. #[derive(Clone, Debug)] pub struct TestResult { status: Status, arguments: Vec, err: Option, } /// Whether a test has passed, failed or been discarded. #[derive(Clone, Debug)] enum Status { Pass, Fail, Discard, } impl TestResult { /// Produces a test result that indicates the current test has passed. pub fn passed() -> TestResult { TestResult::from_bool(true) } /// Produces a test result that indicates the current test has failed. pub fn failed() -> TestResult { TestResult::from_bool(false) } /// Produces a test result that indicates failure from a runtime error. pub fn error>(msg: S) -> TestResult { let mut r = TestResult::from_bool(false); r.err = Some(msg.into()); r } /// Produces a test result that instructs `quickcheck` to ignore it. /// This is useful for restricting the domain of your properties. /// When a test is discarded, `quickcheck` will replace it with a /// fresh one (up to a certain limit). pub fn discard() -> TestResult { TestResult { status: Discard, arguments: vec![], err: None } } /// Converts a `bool` to a `TestResult`. A `true` value indicates that /// the test has passed and a `false` value indicates that the test /// has failed. pub fn from_bool(b: bool) -> TestResult { TestResult { status: if b { Pass } else { Fail }, arguments: vec![], err: None, } } /// Tests if a "procedure" fails when executed. The test passes only if /// `f` generates a task failure during its execution. pub fn must_fail(f: F) -> TestResult where F: FnOnce() -> T, F: 'static, T: 'static, { let f = panic::AssertUnwindSafe(f); TestResult::from_bool(panic::catch_unwind(f).is_err()) } /// Returns `true` if and only if this test result describes a failing /// test. pub fn is_failure(&self) -> bool { match self.status { Fail => true, Pass | Discard => false, } } /// Returns `true` if and only if this test result describes a failing /// test as a result of a run time error. pub fn is_error(&self) -> bool { self.is_failure() && self.err.is_some() } fn failed_msg(&self) -> String { match self.err { None => format!( "[quickcheck] TEST FAILED. Arguments: ({})", self.arguments.join(", ") ), Some(ref err) => format!( "[quickcheck] TEST FAILED (runtime error). \ Arguments: ({})\nError: {}", self.arguments.join(", "), err ), } } } /// `Testable` describes types (e.g., a function) whose values can be /// tested. /// /// Anything that can be tested must be capable of producing a `TestResult` /// given a random number generator. This is trivial for types like `bool`, /// which are just converted to either a passing or failing test result. /// /// For functions, an implementation must generate random arguments /// and potentially shrink those arguments if they produce a failure. /// /// It's unlikely that you'll have to implement this trait yourself. pub trait Testable: 'static { fn result(&self, _: &mut Gen) -> TestResult; } impl Testable for bool { fn result(&self, _: &mut Gen) -> TestResult { TestResult::from_bool(*self) } } impl Testable for () { fn result(&self, _: &mut Gen) -> TestResult { TestResult::passed() } } impl Testable for TestResult { fn result(&self, _: &mut Gen) -> TestResult { self.clone() } } impl Testable for Result where A: Testable, E: Debug + 'static, { fn result(&self, g: &mut Gen) -> TestResult { match *self { Ok(ref r) => r.result(g), Err(ref err) => TestResult::error(format!("{:?}", err)), } } } /// Return a vector of the debug formatting of each item in `args` fn debug_reprs(args: &[&dyn Debug]) -> Vec { args.iter().map(|x| format!("{:?}", x)).collect() } macro_rules! testable_fn { ($($name: ident),*) => { impl Testable for fn($($name),*) -> T { #[allow(non_snake_case)] fn result(&self, g: &mut Gen) -> TestResult { fn shrink_failure( g: &mut Gen, self_: fn($($name),*) -> T, a: ($($name,)*), ) -> Option { for t in a.shrink() { let ($($name,)*) = t.clone(); let mut r_new = safe(move || {self_($($name),*)}).result(g); if r_new.is_failure() { { let ($(ref $name,)*) : ($($name,)*) = t; r_new.arguments = debug_reprs(&[$($name),*]); } // The shrunk value *does* witness a failure, so keep // trying to shrink it. let shrunk = shrink_failure(g, self_, t); // If we couldn't witness a failure on any shrunk value, // then return the failure we already have. return Some(shrunk.unwrap_or(r_new)) } } None } let self_ = *self; let a: ($($name,)*) = Arbitrary::arbitrary(g); let ( $($name,)* ) = a.clone(); let mut r = safe(move || {self_($($name),*)}).result(g); { let ( $(ref $name,)* ) = a; r.arguments = debug_reprs(&[$($name),*]); } match r.status { Pass|Discard => r, Fail => { shrink_failure(g, self_, a).unwrap_or(r) } } } }}} testable_fn!(); testable_fn!(A); testable_fn!(A, B); testable_fn!(A, B, C); testable_fn!(A, B, C, D); testable_fn!(A, B, C, D, E); testable_fn!(A, B, C, D, E, F); testable_fn!(A, B, C, D, E, F, G); testable_fn!(A, B, C, D, E, F, G, H); fn safe(fun: F) -> Result where F: FnOnce() -> T, F: 'static, T: 'static, { panic::catch_unwind(panic::AssertUnwindSafe(fun)).map_err(|any_err| { // Extract common types of panic payload: // panic and assert produce &str or String if let Some(&s) = any_err.downcast_ref::<&str>() { s.to_owned() } else if let Some(s) = any_err.downcast_ref::() { s.to_owned() } else { "UNABLE TO SHOW RESULT OF PANIC.".to_owned() } }) } /// Convenient aliases. trait AShow: Arbitrary + Debug {} impl AShow for A {} #[cfg(test)] mod test { use crate::{Gen, QuickCheck}; #[test] fn shrinking_regression_issue_126() { fn thetest(vals: Vec) -> bool { vals.iter().filter(|&v| *v).count() < 2 } let failing_case = QuickCheck::new() .quicktest(thetest as fn(vals: Vec) -> bool) .unwrap_err(); let expected_argument = format!("{:?}", [true, true]); assert_eq!(failing_case.arguments, vec![expected_argument]); } #[test] fn size_for_small_types_issue_143() { fn t(_: i8) -> bool { true } QuickCheck::new().gen(Gen::new(129)).quickcheck(t as fn(i8) -> bool); } #[test] fn regression_signed_shrinker_panic() { fn foo_can_shrink(v: i8) -> bool { let _ = crate::Arbitrary::shrink(&v).take(100).count(); true } crate::quickcheck(foo_can_shrink as fn(i8) -> bool); } } quickcheck-1.0.3/src/tests.rs000064400000000000000000000145200000000000000142150ustar 00000000000000use std::cmp::Ord; use std::collections::hash_map::DefaultHasher; use std::collections::{HashMap, HashSet}; use std::ffi::CString; use std::hash::BuildHasherDefault; use std::path::PathBuf; use super::{quickcheck, Gen, QuickCheck, TestResult}; #[test] fn prop_oob() { fn prop() -> bool { let zero: Vec = vec![]; zero[0] } match QuickCheck::new().quicktest(prop as fn() -> bool) { Ok(n) => panic!( "prop_oob should fail with a runtime error \ but instead it passed {} tests.", n ), _ => return, } } #[test] fn prop_reverse_reverse() { fn prop(xs: Vec) -> bool { let rev: Vec<_> = xs.clone().into_iter().rev().collect(); let revrev: Vec<_> = rev.into_iter().rev().collect(); xs == revrev } quickcheck(prop as fn(Vec) -> bool); } quickcheck! { fn prop_reverse_reverse_macro(xs: Vec) -> bool { let rev: Vec<_> = xs.clone().into_iter().rev().collect(); let revrev: Vec<_> = rev.into_iter().rev().collect(); xs == revrev } #[should_panic] fn prop_macro_panic(_x: u32) -> bool { assert!(false); false } } #[test] fn reverse_single() { fn prop(xs: Vec) -> TestResult { if xs.len() != 1 { TestResult::discard() } else { TestResult::from_bool( xs == xs.clone().into_iter().rev().collect::>(), ) } } quickcheck(prop as fn(Vec) -> TestResult); } #[test] fn reverse_app() { fn prop(xs: Vec, ys: Vec) -> bool { let mut app = xs.clone(); app.extend(ys.iter().cloned()); let app_rev: Vec = app.into_iter().rev().collect(); let rxs: Vec = xs.into_iter().rev().collect(); let mut rev_app = ys.into_iter().rev().collect::>(); rev_app.extend(rxs.into_iter()); app_rev == rev_app } quickcheck(prop as fn(Vec, Vec) -> bool); } #[test] fn max() { fn prop(x: isize, y: isize) -> TestResult { if x > y { TestResult::discard() } else { TestResult::from_bool(::std::cmp::max(x, y) == y) } } quickcheck(prop as fn(isize, isize) -> TestResult); } #[test] fn sort() { fn prop(mut xs: Vec) -> bool { xs.sort_by(|x, y| x.cmp(y)); for i in xs.windows(2) { if i[0] > i[1] { return false; } } true } quickcheck(prop as fn(Vec) -> bool); } fn sieve(n: usize) -> Vec { if n <= 1 { return vec![]; } let mut marked = vec![false; n + 1]; marked[0] = true; marked[1] = true; marked[2] = true; for p in 2..n { for i in (2 * p..n).filter(|&n| n % p == 0) { marked[i] = true; } } marked .iter() .enumerate() .filter_map(|(i, &m)| if m { None } else { Some(i) }) .collect() } fn is_prime(n: usize) -> bool { n != 0 && n != 1 && (2..).take_while(|i| i * i <= n).all(|i| n % i != 0) } #[test] #[should_panic] fn sieve_not_prime() { fn prop_all_prime(n: u8) -> bool { sieve(n as usize).into_iter().all(is_prime) } quickcheck(prop_all_prime as fn(u8) -> bool); } #[test] #[should_panic] fn sieve_not_all_primes() { fn prop_prime_iff_in_the_sieve(n: u8) -> bool { let n = n as usize; sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::>() } quickcheck(prop_prime_iff_in_the_sieve as fn(u8) -> bool); } #[test] fn testable_result() { fn result() -> Result { Ok(true) } quickcheck(result as fn() -> Result); } #[test] #[should_panic] fn testable_result_err() { quickcheck(Err:: as fn(i32) -> Result); } #[test] fn testable_unit() { fn do_nothing() {} quickcheck(do_nothing as fn()); } #[test] fn testable_unit_panic() { fn panic() { panic!() } assert!(QuickCheck::new().quicktest(panic as fn()).is_err()); } #[test] fn regression_issue_83() { fn prop(_: u8) -> bool { true } QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(u8) -> bool) } #[test] fn regression_issue_83_signed() { fn prop(_: i8) -> bool { true } QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(i8) -> bool) } // Test that we can show the message after panic #[test] #[should_panic(expected = "foo")] fn panic_msg_1() { fn prop() -> bool { panic!("foo"); } quickcheck(prop as fn() -> bool); } #[test] #[should_panic(expected = "foo")] fn panic_msg_2() { fn prop() -> bool { assert!("foo" == "bar"); true } quickcheck(prop as fn() -> bool); } #[test] #[should_panic(expected = "foo")] fn panic_msg_3() { fn prop() -> bool { assert_eq!("foo", "bar"); true } quickcheck(prop as fn() -> bool); } #[test] #[should_panic] fn regression_issue_107_hang() { fn prop(a: Vec) -> bool { a.contains(&1) } quickcheck(prop as fn(_) -> bool); } #[test] #[should_panic( expected = "(Unable to generate enough tests, 0 not discarded.)" )] fn all_tests_discarded_min_tests_passed_set() { fn prop_discarded(_: u8) -> TestResult { TestResult::discard() } QuickCheck::new() .tests(16) .min_tests_passed(8) .quickcheck(prop_discarded as fn(u8) -> TestResult) } #[test] fn all_tests_discarded_min_tests_passed_missing() { fn prop_discarded(_: u8) -> TestResult { TestResult::discard() } QuickCheck::new().quickcheck(prop_discarded as fn(u8) -> TestResult) } quickcheck! { /// The following is a very simplistic test, which only verifies /// that our PathBuf::arbitrary does not panic. Still, that's /// something! :) fn pathbuf(_p: PathBuf) -> bool { true } fn basic_hashset(_set: HashSet) -> bool { true } fn basic_hashmap(_map: HashMap) -> bool { true } fn substitute_hashset( _set: HashSet> ) -> bool { true } fn substitute_hashmap( _map: HashMap> ) -> bool { true } fn cstring(_p: CString) -> bool { true } }