├── .github └── workflows │ └── ci.yml ├── .gitignore ├── COPYING ├── Cargo.toml ├── LICENSE-MIT ├── Makefile ├── README.md ├── UNLICENSE ├── ctags.rust ├── examples ├── btree_set_range.rs ├── out_of_bounds.rs ├── reverse.rs ├── reverse_single.rs ├── sieve.rs └── sort.rs ├── quickcheck_macros ├── COPYING ├── Cargo.toml ├── LICENSE-MIT ├── UNLICENSE ├── examples │ └── attribute.rs ├── src │ └── lib.rs └── tests │ └── macro.rs ├── rustfmt.toml ├── session.vim └── src ├── arbitrary.rs ├── lib.rs ├── tester.rs └── tests.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '00 01 * * *' 11 | 12 | # The section is needed to drop write-all permissions that are granted on 13 | # `schedule` event. By specifying any permission explicitly all others are set 14 | # to none. By using the principle of least privilege the damage a compromised 15 | # workflow can do (because of an injection or compromised third party tool or 16 | # action) is restricted. Currently the worklow doesn't need any additional 17 | # permission except for pulling the code. Adding labels to issues, commenting 18 | # on pull-requests, etc. may need additional permissions: 19 | # 20 | # Syntax for this section: 21 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions 22 | # 23 | # Reference for how to assign permissions on a job-by-job basis: 24 | # https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs 25 | # 26 | # Reference for available permissions that we can enable if needed: 27 | # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token 28 | permissions: 29 | # to fetch code (actions/checkout) 30 | contents: read 31 | 32 | jobs: 33 | test: 34 | name: test 35 | runs-on: ${{ matrix.os }} 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | include: 40 | - build: pinned 41 | os: ubuntu-latest 42 | rust: 1.71.0 43 | - build: stable 44 | os: ubuntu-latest 45 | rust: stable 46 | - build: beta 47 | os: ubuntu-latest 48 | rust: beta 49 | - build: nightly 50 | os: ubuntu-latest 51 | rust: nightly 52 | - build: macos 53 | os: macos-latest 54 | rust: stable 55 | - build: win-msvc 56 | os: windows-latest 57 | rust: stable 58 | - build: win-gnu 59 | os: windows-latest 60 | rust: stable-x86_64-gnu 61 | steps: 62 | - name: Checkout repository 63 | uses: actions/checkout@v4 64 | - name: Install Rust 65 | uses: dtolnay/rust-toolchain@master 66 | with: 67 | toolchain: ${{ matrix.rust }} 68 | - run: cargo build --verbose 69 | - run: cargo doc --verbose 70 | - run: cargo test --verbose 71 | - run: cargo build --verbose --manifest-path quickcheck_macros/Cargo.toml 72 | - run: cargo test --verbose --manifest-path quickcheck_macros/Cargo.toml 73 | 74 | rustfmt: 75 | runs-on: ubuntu-latest 76 | steps: 77 | - name: Checkout repository 78 | uses: actions/checkout@v4 79 | - name: Install Rust 80 | uses: dtolnay/rust-toolchain@master 81 | with: 82 | toolchain: stable 83 | components: rustfmt 84 | - name: Check formatting 85 | run: | 86 | cargo fmt --all -- --check 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | .*.swp 3 | tags 4 | target 5 | build 6 | Cargo.lock 7 | *~ 8 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This project is dual-licensed under the Unlicense and MIT licenses. 2 | 3 | You may use this code under the terms of either license. 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quickcheck" 3 | version = "1.0.3" #:version 4 | authors = ["Andrew Gallant "] 5 | description = "Automatic property based testing with shrinking." 6 | documentation = "https://docs.rs/quickcheck" 7 | homepage = "https://github.com/BurntSushi/quickcheck" 8 | repository = "https://github.com/BurntSushi/quickcheck" 9 | readme = "README.md" 10 | keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] 11 | categories = ["development-tools::testing"] 12 | license = "Unlicense OR MIT" 13 | exclude = ["/Makefile", "/ctags.rust", "/session.vim"] 14 | edition = "2021" 15 | 16 | [workspace] 17 | members = ["quickcheck_macros"] 18 | 19 | [features] 20 | default = ["regex", "use_logging"] 21 | use_logging = ["log", "env_logger"] 22 | regex = ["env_logger/regex"] 23 | 24 | [lib] 25 | name = "quickcheck" 26 | 27 | [dependencies] 28 | env_logger = { version = "0.11", default-features = false, optional = true } 29 | log = { version = "0.4", optional = true } 30 | rand = { version = "0.9", default-features = false, features = ["os_rng", "small_rng"] } 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Gallant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | echo Nothing to do... 3 | 4 | ctags: 5 | ctags --recurse --options=ctags.rust --languages=Rust 6 | 7 | docs: 8 | cargo doc 9 | in-dir ./target/doc fix-perms 10 | rscp ./target/doc/* gopher:~/www/burntsushi.net/rustdoc/ 11 | 12 | push: 13 | git push origin master 14 | git push github master 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quickcheck 2 | 3 | QuickCheck is a way to do property based testing using randomly generated 4 | input. This crate comes with the ability to randomly generate and shrink 5 | integers, floats, tuples, booleans, lists, strings, options and results. 6 | All QuickCheck needs is a property function—it will then randomly generate 7 | inputs to that function and call the property for each set of inputs. If the 8 | property fails (whether by a runtime error like index out-of-bounds or by not 9 | satisfying your property), the inputs are "shrunk" to find a smaller 10 | counter-example. 11 | 12 | The shrinking strategies for lists and numbers use a binary search to cover 13 | the input space quickly. (It should be the same strategy used in 14 | [Koen Claessen's QuickCheck for 15 | Haskell](https://hackage.haskell.org/package/QuickCheck).) 16 | 17 | [![Build status](https://github.com/BurntSushi/quickcheck/workflows/ci/badge.svg)](https://github.com/BurntSushi/quickcheck/actions) 18 | [![crates.io](https://img.shields.io/crates/v/quickcheck.svg)](https://crates.io/crates/quickcheck) 19 | 20 | Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org/). 21 | 22 | ## Documentation 23 | 24 | The API is fully documented: 25 | [https://docs.rs/quickcheck](https://docs.rs/quickcheck). 26 | 27 | ## Simple example 28 | 29 | Here's an example that tests a function that reverses a vector: 30 | 31 | ```rust 32 | fn reverse(xs: &[T]) -> Vec { 33 | let mut rev = vec![]; 34 | for x in xs.iter() { 35 | rev.insert(0, x.clone()) 36 | } 37 | rev 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use quickcheck::quickcheck; 43 | use super::reverse; 44 | 45 | quickcheck! { 46 | fn prop(xs: Vec) -> bool { 47 | xs == reverse(&reverse(&xs)) 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | This example uses the `quickcheck!` macro, which is backwards compatible with 54 | old versions of Rust. 55 | 56 | ## The `#[quickcheck]` attribute 57 | 58 | To make it easier to write QuickCheck tests, the `#[quickcheck]` attribute 59 | will convert a property function into a `#[test]` function. 60 | 61 | To use the `#[quickcheck]` attribute, you must import the `quickcheck` macro 62 | from the `quickcheck_macros` crate: 63 | 64 | ```rust 65 | fn reverse(xs: &[T]) -> Vec { 66 | let mut rev = vec![]; 67 | for x in xs { 68 | rev.insert(0, x.clone()) 69 | } 70 | rev 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use quickcheck_macros::quickcheck; 76 | use super::reverse; 77 | 78 | #[quickcheck] 79 | fn double_reversal_is_identity(xs: Vec) -> bool { 80 | xs == reverse(&reverse(&xs)) 81 | } 82 | } 83 | ``` 84 | 85 | ## Installation 86 | 87 | `quickcheck` is on `crates.io`, so you can include it in your project like so: 88 | 89 | ```toml 90 | [dependencies] 91 | quickcheck = "1" 92 | ``` 93 | 94 | If you're only using `quickcheck` in your test code, then you can add it as a 95 | development dependency instead: 96 | 97 | ```toml 98 | [dev-dependencies] 99 | quickcheck = "1" 100 | ``` 101 | 102 | If you want to use the `#[quickcheck]` attribute, then add `quickcheck_macros` 103 | 104 | ```toml 105 | [dev-dependencies] 106 | quickcheck = "1" 107 | quickcheck_macros = "1" 108 | ``` 109 | 110 | N.B. When using `quickcheck` (either directly or via the attributes), 111 | `RUST_LOG=quickcheck` enables `info!` so that it shows useful output 112 | (like the number of tests passed). This is **not** needed to show 113 | witnesses for failures. 114 | 115 | Crate features: 116 | 117 | - `"use_logging"`: (Enabled by default.) Enables the log messages governed 118 | `RUST_LOG`. 119 | - `"regex"`: (Enabled by default.) Enables the use of regexes with 120 | `env_logger`. 121 | 122 | ## Minimum Rust version policy 123 | 124 | This crate's minimum supported `rustc` version is `1.71.0`. 125 | 126 | The current policy is that the minimum Rust version required to use this crate 127 | can be increased in minor version updates. For example, if `crate 1.0` requires 128 | Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust 129 | 1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum 130 | version of Rust. 131 | 132 | In general, this crate will be conservative with respect to the minimum 133 | supported version of Rust. 134 | 135 | With all of that said, currently, `rand` is a public dependency of 136 | `quickcheck`. Therefore, the MSRV policy above only applies when it is more 137 | aggressive than `rand`'s MSRV policy. Otherwise, `quickcheck` will defer to 138 | `rand`'s MSRV policy. 139 | 140 | ## Compatibility 141 | 142 | In general, this crate considers the `Arbitrary` implementations provided as 143 | implementation details. Strategies may or may not change over time, which may 144 | cause new test failures, presumably due to the discovery of new bugs due to a 145 | new kind of witness being generated. These sorts of changes may happen in 146 | semver compatible releases. 147 | 148 | ## Alternative Rust crates for property testing 149 | 150 | The [`proptest`](https://docs.rs/proptest) crate is inspired by the 151 | [Hypothesis](https://hypothesis.works/) framework for Python. 152 | You can read a comparison between `proptest` and `quickcheck` 153 | [here](https://github.com/proptest-rs/proptest/blob/main/proptest/README.md#differences-between-quickcheck-and-proptest) 154 | and 155 | [here](https://github.com/proptest-rs/proptest/issues/15#issuecomment-348382287). 156 | In particular, `proptest` improves on the concept of shrinking. So if you've 157 | ever had problems/frustration with shrinking in `quickcheck`, then `proptest` 158 | might be worth a try! 159 | 160 | ## Alternatives for fuzzing 161 | 162 | Please see the 163 | [Rust Fuzz Book](https://rust-fuzz.github.io/book/introduction.html) 164 | and the 165 | [`arbitrary`](https://crates.io/crates/arbitrary) crate. 166 | 167 | ## Discarding test results (or, properties are polymorphic!) 168 | 169 | Sometimes you want to test a property that only holds for a *subset* of the 170 | possible inputs, so that when your property is given an input that is outside 171 | of that subset, you'd discard it. In particular, the property should *neither* 172 | pass nor fail on inputs outside of the subset you want to test. But properties 173 | return boolean values—which either indicate pass or fail. 174 | 175 | To fix this, we need to take a step back and look at the type of the 176 | `quickcheck` function: 177 | 178 | ```rust 179 | pub fn quickcheck(f: A) { 180 | // elided 181 | } 182 | ``` 183 | 184 | So `quickcheck` can test any value with a type that satisfies the `Testable` 185 | trait. Great, so what is this `Testable` business? 186 | 187 | ```rust 188 | pub trait Testable { 189 | fn result(&self, &mut Gen) -> TestResult; 190 | } 191 | ``` 192 | 193 | This trait states that a type is testable if it can produce a `TestResult` 194 | given a source of randomness. (A `TestResult` stores information about the 195 | results of a test, like whether it passed, failed or has been discarded.) 196 | 197 | Sure enough, `bool` satisfies the `Testable` trait: 198 | 199 | ```rust 200 | impl Testable for bool { 201 | fn result(&self, _: &mut Gen) -> TestResult { 202 | TestResult::from_bool(*self) 203 | } 204 | } 205 | ``` 206 | 207 | But in the example, we gave a *function* to `quickcheck`. Yes, functions can 208 | satisfy `Testable` too! 209 | 210 | ```rust 211 | impl Testable for fn(A) -> B { 212 | fn result(&self, g: &mut Gen) -> TestResult { 213 | // elided 214 | } 215 | } 216 | ``` 217 | 218 | Which says that a function satisfies `Testable` if and only if it has a single 219 | parameter type (whose values can be randomly generated and shrunk) and returns 220 | any type (that also satisfies `Testable`). So a function with type `fn(usize) 221 | -> bool` satisfies `Testable` since `usize` satisfies `Arbitrary` and `bool` 222 | satisfies `Testable`. 223 | 224 | So to discard a test, we need to return something other than `bool`. What if we 225 | just returned a `TestResult` directly? That should work, but we'll need to 226 | make sure `TestResult` satisfies `Testable`: 227 | 228 | ```rust 229 | impl Testable for TestResult { 230 | fn result(&self, _: &mut Gen) -> TestResult { self.clone() } 231 | } 232 | ``` 233 | 234 | Now we can test functions that return a `TestResult` directly. 235 | 236 | As an example, let's test our reverse function to make sure that the reverse of 237 | a vector of length 1 is equal to the vector itself. 238 | 239 | ```rust 240 | fn prop(xs: Vec) -> TestResult { 241 | if xs.len() != 1 { 242 | return TestResult::discard() 243 | } 244 | TestResult::from_bool(xs == reverse(&xs)) 245 | } 246 | quickcheck(prop as fn(Vec) -> TestResult); 247 | ``` 248 | 249 | (A full working program for this example is in 250 | [`examples/reverse_single.rs`](https://github.com/BurntSushi/quickcheck/blob/master/examples/reverse_single.rs).) 251 | 252 | So now our property returns a `TestResult`, which allows us to encode a bit 253 | more information. There are a few more 254 | [convenience functions defined for the `TestResult` 255 | type](https://docs.rs/quickcheck/*/quickcheck/struct.TestResult.html). 256 | For example, we can't just return a `bool`, so we convert a `bool` value to a 257 | `TestResult`. 258 | 259 | (The ability to discard tests allows you to get similar functionality as 260 | Haskell's `==>` combinator.) 261 | 262 | N.B. Since discarding a test means it neither passes nor fails, `quickcheck` 263 | will try to replace the discarded test with a fresh one. However, if your 264 | condition is seldom met, it's possible that `quickcheck` will have to settle 265 | for running fewer tests than usual. By default, if `quickcheck` can't find 266 | `100` valid tests after trying `10,000` times, then it will give up. 267 | These parameters may be changed using 268 | [`QuickCheck::tests`](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.tests) 269 | and [`QuickCheck::max_tests`](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.max_tests), 270 | or by setting the `QUICKCHECK_TESTS` and `QUICKCHECK_MAX_TESTS` 271 | environment variables. 272 | There is also `QUICKCHECK_MIN_TESTS_PASSED` which sets the minimum number of 273 | valid tests that need pass (defaults to `0`) in order for it to be considered a 274 | success. 275 | 276 | ## Shrinking 277 | 278 | Shrinking is a crucial part of QuickCheck that simplifies counter-examples for 279 | your properties automatically. For example, if you erroneously defined a 280 | function for reversing vectors as: (my apologies for the contrived example) 281 | 282 | ```rust 283 | fn reverse(xs: &[T]) -> Vec { 284 | let mut rev = vec![]; 285 | for i in 1..xs.len() { 286 | rev.insert(0, xs[i].clone()) 287 | } 288 | rev 289 | } 290 | ``` 291 | 292 | And a property to test that `xs == reverse(reverse(xs))`: 293 | 294 | ```rust 295 | fn prop(xs: Vec) -> bool { 296 | xs == reverse(&reverse(&xs)) 297 | } 298 | quickcheck(prop as fn(Vec) -> bool); 299 | ``` 300 | 301 | Then without shrinking, you might get a counter-example like: 302 | 303 | ```text 304 | [quickcheck] TEST FAILED. Arguments: ([-17, 13, -12, 17, -8, -10, 15, -19, 305 | -19, -9, 11, -5, 1, 19, -16, 6]) 306 | ``` 307 | 308 | Which is pretty mysterious. But with shrinking enabled, you're nearly 309 | guaranteed to get this counter-example every time: 310 | 311 | ```text 312 | [quickcheck] TEST FAILED. Arguments: ([0]) 313 | ``` 314 | 315 | Which is going to be much easier to debug. 316 | 317 | ## More Thorough Checking 318 | 319 | Quickcheck uses random input to test, so it won't 320 | always find bugs that could be uncovered with a particular 321 | property. You can improve your odds of finding these latent 322 | bugs by spending more CPU cycles asking quickcheck to find 323 | them for you. There are a few different ways to do this, and 324 | which one you choose is mostly a matter of taste. 325 | 326 | If you are finding yourself doing this sort of thing a 327 | lot, you might also be interested in trying out 328 | [`cargo fuzz`](https://github.com/rust-fuzz/cargo-fuzz), 329 | which runs in a loop by default. 330 | 331 | ### Running in a Loop 332 | 333 | One approach is to run your quickcheck properties in a loop that 334 | just keeps going until you tell it to stop or it finds a bug. 335 | For example, you could use a bash script such as the following 336 | one. 337 | 338 | ```bash 339 | #!/usr/bin/bash 340 | 341 | while true 342 | do 343 | cargo test qc_ 344 | if [[ x$? != x0 ]] ; then 345 | exit $? 346 | fi 347 | done 348 | ``` 349 | 350 | One thing to note is that this script passes the `qc_` filter to 351 | `cargo test`. This assumes that you've prefixed all your quickcheck 352 | properties with `qc_`. You could leave off the filter, but then 353 | you would be running all your deterministic tests as well, which 354 | would take time away from quickcheck! 355 | 356 | Checking the return code and exiting is also important. Without that 357 | test, you won't ever notice when a failure happens. 358 | 359 | ### Cranking the Number of Tests 360 | 361 | Another approach is to just ask quickcheck to run properties more 362 | times. You can do this either via the 363 | [tests()](https://docs.rs/quickcheck/*/quickcheck/struct.QuickCheck.html#method.tests) 364 | method, or via the `QUICKCHECK_TESTS` environment variable. 365 | This will cause quickcheck to run for a much longer time. Unlike, 366 | the loop approach this will take a bounded amount of time, which 367 | makes it more suitable for something like a release cycle that 368 | wants to really hammer your software. 369 | 370 | ### Making Arbitrary Smarter 371 | 372 | This approach entails spending more time generating interesting 373 | inputs in your implementations of Arbitrary. The idea is to 374 | focus on the corner cases. This approach can be tricky because 375 | programmers are not usually great at intuiting corner cases, 376 | and the whole idea of property checking is to take that burden 377 | off the programmer. Despite the theoretical discomfort, this 378 | approach can turn out to be practical. 379 | 380 | ## Generating Structs 381 | 382 | It is very simple to generate structs in QuickCheck. Consider the following 383 | example, where the struct `Point` is defined: 384 | 385 | ```rust 386 | struct Point { 387 | x: i32, 388 | y: i32, 389 | } 390 | ``` 391 | 392 | In order to generate a random `Point` instance, you need to implement 393 | the trait `Arbitrary` for the struct `Point`: 394 | 395 | ```rust 396 | use quickcheck::{Arbitrary, Gen}; 397 | 398 | impl Arbitrary for Point { 399 | fn arbitrary(g: &mut Gen) -> Point { 400 | Point { 401 | x: i32::arbitrary(g), 402 | y: i32::arbitrary(g), 403 | } 404 | } 405 | } 406 | ``` 407 | 408 | ## Case study: The Sieve of Eratosthenes 409 | 410 | The [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) 411 | is a simple and elegant way to find all primes less than or equal to `N`. 412 | Briefly, the algorithm works by allocating an array with `N` slots containing 413 | booleans. Slots marked with `false` correspond to prime numbers (or numbers 414 | not known to be prime while building the sieve) and slots marked with `true` 415 | are known to not be prime. For each `n`, all of its multiples in this array 416 | are marked as true. When all `n` have been checked, the numbers marked `false` 417 | are returned as the primes. 418 | 419 | As you might imagine, there's a lot of potential for off-by-one errors, which 420 | makes it ideal for randomized testing. So let's take a look at my 421 | implementation and see if we can spot the bug: 422 | 423 | ```rust 424 | fn sieve(n: usize) -> Vec { 425 | if n <= 1 { 426 | return vec![]; 427 | } 428 | 429 | let mut marked = vec![false; n+1]; 430 | marked[0] = true; 431 | marked[1] = true; 432 | marked[2] = true; 433 | for p in 2..n { 434 | for i in (2*p..n).filter(|&n| n % p == 0) { 435 | marked[i] = true; 436 | } 437 | } 438 | marked.iter() 439 | .enumerate() 440 | .filter_map(|(i, &m)| if m { None } else { Some(i) }) 441 | .collect() 442 | } 443 | ``` 444 | 445 | Let's try it on a few inputs by hand: 446 | 447 | ```text 448 | sieve(3) => [2, 3] 449 | sieve(5) => [2, 3, 5] 450 | sieve(8) => [2, 3, 5, 7, 8] # !!! 451 | ``` 452 | 453 | Something has gone wrong! But where? The bug is rather subtle, but it's an 454 | easy one to make. It's OK if you can't spot it, because we're going to use 455 | QuickCheck to help us track it down. 456 | 457 | Even before looking at some example outputs, it's good to try and come up with 458 | some *properties* that are always satisfiable by the output of the function. An 459 | obvious one for the prime number sieve is to check if all numbers returned are 460 | prime. For that, we'll need an `is_prime` function: 461 | 462 | ```rust 463 | fn is_prime(n: usize) -> bool { 464 | n != 0 && n != 1 && (2..).take_while(|i| i*i <= n).all(|i| n % i != 0) 465 | } 466 | ``` 467 | 468 | All this is doing is checking to see if any number in `[2, sqrt(n)]` divides 469 | `n` with base cases for `0` and `1`. 470 | 471 | Now we can write our QuickCheck property: 472 | 473 | ```rust 474 | fn prop_all_prime(n: usize) -> bool { 475 | sieve(n).into_iter().all(is_prime) 476 | } 477 | ``` 478 | 479 | And finally, we need to invoke `quickcheck` with our property: 480 | 481 | ```rust 482 | fn main() { 483 | quickcheck(prop_all_prime as fn(usize) -> bool); 484 | } 485 | ``` 486 | 487 | A fully working source file with this code is in 488 | [`examples/sieve.rs`](https://github.com/BurntSushi/quickcheck/blob/master/examples/sieve.rs). 489 | 490 | The output of running this program has this message: 491 | 492 | ```text 493 | [quickcheck] TEST FAILED. Arguments: (4) 494 | ``` 495 | 496 | Which says that `sieve` failed the `prop_all_prime` test when given `n = 4`. 497 | Because of shrinking, it was able to find a (hopefully) minimal counter-example 498 | for our property. 499 | 500 | With such a short counter-example, it's hopefully a bit easier to narrow down 501 | where the bug is. Since `4` is returned, it's likely never marked as being not 502 | prime. Since `4` is a multiple of `2`, its slot should be marked as `true` when 503 | `p = 2` on these lines: 504 | 505 | ```rust 506 | for i in (2*p..n).filter(|&n| n % p == 0) { 507 | marked[i] = true; 508 | } 509 | ``` 510 | 511 | Ah! But does the `..` (range) operator include `n`? Nope! This particular 512 | operator is a half-open interval. 513 | 514 | A `2*p..n` range will never yield `4` when `n = 4`. When we change this to 515 | `2*p..n+1`, all tests pass. 516 | 517 | In addition, if our bug happened to result in an index out-of-bounds error, 518 | then `quickcheck` can handle it just like any other failure—including 519 | shrinking on failures caused by runtime errors. 520 | 521 | But hold on... we're not done yet. Right now, our property tests that all 522 | the numbers returned by `sieve` are prime but it doesn't test if the list is 523 | complete. It does not ensure that all the primes between `0` and `n` are found. 524 | 525 | Here's a property that is more comprehensive: 526 | 527 | ```rust 528 | fn prop_prime_iff_in_the_sieve(n: usize) -> bool { 529 | sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::>() 530 | } 531 | ``` 532 | 533 | It tests that for each number between 0 and n, inclusive, the naive primality test 534 | yields the same result as the sieve. 535 | 536 | Now, if we run it: 537 | 538 | ```rust 539 | fn main() { 540 | quickcheck(prop_all_prime as fn(usize) -> bool); 541 | quickcheck(prop_prime_iff_in_the_sieve as fn(usize) -> bool); 542 | } 543 | ``` 544 | 545 | we see that it fails immediately for value n = 2. 546 | 547 | ```text 548 | [quickcheck] TEST FAILED. Arguments: (2) 549 | ``` 550 | 551 | If we inspect `sieve()` once again, we see that we mistakenly mark `2` as 552 | non-prime. Removing the line `marked[2] = true;` results in both properties 553 | passing. 554 | 555 | ## What's not in this port of QuickCheck? 556 | 557 | I think I've captured the key features, but there are still things missing: 558 | 559 | - Only functions with 8 or fewer parameters can be quickchecked. This 560 | limitation can be lifted to some `N`, but requires an implementation for each 561 | `n` of the `Testable` trait. 562 | - Functions that fail because of a stack overflow are not caught by QuickCheck. 563 | Therefore, such failures will not have a witness attached 564 | to them. (I'd like to fix this, but I don't know how.) 565 | - `Coarbitrary` does not exist in any form in this package. It's unlikely that 566 | it ever will. 567 | - `Arbitrary` is not implemented for closures. See 568 | [issue #56](https://github.com/BurntSushi/quickcheck/issues/56) 569 | for more details on why. 570 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /ctags.rust: -------------------------------------------------------------------------------- 1 | --langdef=Rust 2 | --langmap=Rust:.rs 3 | --regex-Rust=/^[ \t]*(#\[[^\]]\][ \t]*)*(pub[ \t]+)?(extern[ \t]+)?("[^"]+"[ \t]+)?(unsafe[ \t]+)?fn[ \t]+([a-zA-Z0-9_]+)/\6/f,functions,function definitions/ 4 | --regex-Rust=/^[ \t]*(pub[ \t]+)?type[ \t]+([a-zA-Z0-9_]+)/\2/T,types,type definitions/ 5 | --regex-Rust=/^[ \t]*(pub[ \t]+)?enum[ \t]+([a-zA-Z0-9_]+)/\2/g,enum,enumeration names/ 6 | --regex-Rust=/^[ \t]*(pub[ \t]+)?struct[ \t]+([a-zA-Z0-9_]+)/\2/s,structure names/ 7 | --regex-Rust=/^[ \t]*(pub[ \t]+)?mod[ \t]+([a-zA-Z0-9_]+)/\2/m,modules,module names/ 8 | --regex-Rust=/^[ \t]*(pub[ \t]+)?static[ \t]+([a-zA-Z0-9_]+)/\2/c,consts,static constants/ 9 | --regex-Rust=/^[ \t]*(pub[ \t]+)?trait[ \t]+([a-zA-Z0-9_]+)/\2/t,traits,traits/ 10 | --regex-Rust=/^[ \t]*(pub[ \t]+)?impl([ \t\n]+<.*>)?[ \t]+([a-zA-Z0-9_]+)/\3/i,impls,trait implementations/ 11 | --regex-Rust=/^[ \t]*macro_rules![ \t]+([a-zA-Z0-9_]+)/\1/d,macros,macro definitions/ 12 | -------------------------------------------------------------------------------- /examples/btree_set_range.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::ops::Bound::{self, *}; 3 | 4 | use quickcheck::{quickcheck, TestResult}; 5 | 6 | /// Covers every `std::ops::Range*` plus variants with exclusive start. 7 | type RangeAny = (Bound, Bound); 8 | 9 | /// Mimic `RangeBounds::contains`, stabilized in Rust 1.35. 10 | trait RangeBounds { 11 | fn contains(&self, _: &T) -> bool; 12 | } 13 | 14 | impl RangeBounds for RangeAny { 15 | fn contains(&self, item: &T) -> bool { 16 | (match &self.0 { 17 | Included(start) => start <= item, 18 | Excluded(start) => start < item, 19 | Unbounded => true, 20 | }) && (match &self.1 { 21 | Included(end) => item <= end, 22 | Excluded(end) => item < end, 23 | Unbounded => true, 24 | }) 25 | } 26 | } 27 | 28 | /// Checks conditions where `BTreeSet::range` panics: 29 | /// - Panics if range start > end. 30 | /// - Panics if range start == end and both bounds are Excluded. 31 | fn panics(range: RangeAny) -> bool { 32 | match (&range.0, &range.1) { 33 | (Excluded(start), Excluded(end)) => start >= end, 34 | (Included(start), Excluded(end) | Included(end)) 35 | | (Excluded(start), Included(end)) => start > end, 36 | (Unbounded, _) | (_, Unbounded) => false, 37 | } 38 | } 39 | 40 | /// Checks that `BTreeSet::range` returns all items contained in the given 41 | /// `range`. 42 | fn check_range(set: BTreeSet, range: RangeAny) -> TestResult { 43 | if panics(range) { 44 | TestResult::discard() 45 | } else { 46 | let xs: BTreeSet<_> = set.range(range).copied().collect(); 47 | TestResult::from_bool( 48 | set.iter().all(|x| range.contains(x) == xs.contains(x)), 49 | ) 50 | } 51 | } 52 | 53 | fn main() { 54 | quickcheck(check_range as fn(_, _) -> TestResult); 55 | } 56 | -------------------------------------------------------------------------------- /examples/out_of_bounds.rs: -------------------------------------------------------------------------------- 1 | use quickcheck::{quickcheck, TestResult}; 2 | 3 | fn main() { 4 | fn prop(length: usize, index: usize) -> TestResult { 5 | let v: Vec<_> = (0..length).collect(); 6 | if index < length { 7 | TestResult::discard() 8 | } else { 9 | TestResult::must_fail(move || v[index]) 10 | } 11 | } 12 | quickcheck(prop as fn(usize, usize) -> TestResult); 13 | } 14 | -------------------------------------------------------------------------------- /examples/reverse.rs: -------------------------------------------------------------------------------- 1 | use quickcheck::quickcheck; 2 | 3 | fn reverse(xs: &[T]) -> Vec { 4 | let mut rev = vec![]; 5 | for x in xs { 6 | rev.insert(0, x.clone()); 7 | } 8 | rev 9 | } 10 | 11 | fn main() { 12 | fn equality_after_applying_twice(xs: Vec) -> bool { 13 | xs == reverse(&reverse(&xs)) 14 | } 15 | quickcheck(equality_after_applying_twice as fn(Vec) -> bool); 16 | } 17 | -------------------------------------------------------------------------------- /examples/reverse_single.rs: -------------------------------------------------------------------------------- 1 | use quickcheck::{quickcheck, TestResult}; 2 | 3 | fn reverse(xs: &[T]) -> Vec { 4 | let mut rev = vec![]; 5 | for x in xs { 6 | rev.insert(0, x.clone()); 7 | } 8 | rev 9 | } 10 | 11 | fn main() { 12 | fn prop(xs: Vec) -> TestResult { 13 | if xs.len() != 1 { 14 | return TestResult::discard(); 15 | } 16 | TestResult::from_bool(xs == reverse(&xs)) 17 | } 18 | quickcheck(prop as fn(Vec) -> TestResult); 19 | } 20 | -------------------------------------------------------------------------------- /examples/sieve.rs: -------------------------------------------------------------------------------- 1 | use quickcheck::quickcheck; 2 | 3 | fn sieve(n: usize) -> Vec { 4 | if n <= 1 { 5 | return vec![]; 6 | } 7 | 8 | let mut marked = vec![false; n + 1]; 9 | marked[0] = true; 10 | marked[1] = true; 11 | marked[2] = true; 12 | for p in 2..n { 13 | for i in (2 * p..n).filter(|&n| n % p == 0) { 14 | marked[i] = true; 15 | } 16 | } 17 | marked 18 | .iter() 19 | .enumerate() 20 | .filter_map(|(i, &m)| if m { None } else { Some(i) }) 21 | .collect() 22 | } 23 | 24 | fn is_prime(n: usize) -> bool { 25 | n != 0 && n != 1 && (2..).take_while(|i| i * i <= n).all(|i| n % i != 0) 26 | } 27 | 28 | fn main() { 29 | fn prop_all_prime(n: usize) -> bool { 30 | sieve(n).into_iter().all(is_prime) 31 | } 32 | 33 | fn prop_prime_iff_in_the_sieve(n: usize) -> bool { 34 | sieve(n) == (0..=n).filter(|&i| is_prime(i)).collect::>() 35 | } 36 | 37 | quickcheck(prop_all_prime as fn(usize) -> bool); 38 | quickcheck(prop_prime_iff_in_the_sieve as fn(usize) -> bool); 39 | } 40 | -------------------------------------------------------------------------------- /examples/sort.rs: -------------------------------------------------------------------------------- 1 | // This is a buggy quick sort implementation, QuickCheck will find the bug for 2 | // you. 3 | 4 | use quickcheck::quickcheck; 5 | 6 | fn smaller_than(xs: &[T], pivot: &T) -> Vec { 7 | xs.iter().filter(|&x| *x < *pivot).cloned().collect() 8 | } 9 | 10 | fn larger_than(xs: &[T], pivot: &T) -> Vec { 11 | xs.iter().filter(|&x| *x > *pivot).cloned().collect() 12 | } 13 | 14 | fn sortk(x: &T, xs: &[T]) -> Vec { 15 | let mut result: Vec = sort(&smaller_than(xs, x)); 16 | let last_part = sort(&larger_than(xs, x)); 17 | result.push(x.clone()); 18 | result.extend(last_part.iter().cloned()); 19 | result 20 | } 21 | 22 | fn sort(list: &[T]) -> Vec { 23 | if list.is_empty() { 24 | vec![] 25 | } else { 26 | sortk(&list[0], &list[1..]) 27 | } 28 | } 29 | 30 | fn main() { 31 | fn is_sorted(xs: Vec) -> bool { 32 | for win in xs.windows(2) { 33 | if win[0] > win[1] { 34 | return false; 35 | } 36 | } 37 | true 38 | } 39 | 40 | fn keeps_length(xs: Vec) -> bool { 41 | xs.len() == sort(&xs).len() 42 | } 43 | quickcheck(keeps_length as fn(Vec) -> bool); 44 | 45 | quickcheck(is_sorted as fn(Vec) -> bool); 46 | } 47 | -------------------------------------------------------------------------------- /quickcheck_macros/COPYING: -------------------------------------------------------------------------------- 1 | ../COPYING -------------------------------------------------------------------------------- /quickcheck_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quickcheck_macros" 3 | version = "1.1.0" #:version 4 | authors = ["Andrew Gallant "] 5 | description = "A macro attribute for quickcheck." 6 | documentation = "https://docs.rs/quickcheck" 7 | homepage = "https://github.com/BurntSushi/quickcheck" 8 | repository = "https://github.com/BurntSushi/quickcheck" 9 | readme = "../README.md" 10 | keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] 11 | license = "Unlicense OR MIT" 12 | edition = "2021" 13 | autotests = false 14 | 15 | [lib] 16 | name = "quickcheck_macros" 17 | path = "src/lib.rs" 18 | proc-macro = true 19 | 20 | [dependencies] 21 | proc-macro2 = "1.0" 22 | quote = "1.0" 23 | syn = { version = "2.0", features = ["full"] } 24 | 25 | [dev-dependencies] 26 | quickcheck = { path = "..", version = "1.0.0" } 27 | -------------------------------------------------------------------------------- /quickcheck_macros/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /quickcheck_macros/UNLICENSE: -------------------------------------------------------------------------------- 1 | ../UNLICENSE -------------------------------------------------------------------------------- /quickcheck_macros/examples/attribute.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | extern crate quickcheck; 4 | extern crate quickcheck_macros; 5 | 6 | use quickcheck_macros::quickcheck; 7 | 8 | fn reverse(xs: &[T]) -> Vec { 9 | let mut rev = vec![]; 10 | for x in xs { 11 | rev.insert(0, x.clone()) 12 | } 13 | rev 14 | } 15 | 16 | #[quickcheck] 17 | fn double_reversal_is_identity(xs: Vec) -> bool { 18 | xs == reverse(&reverse(&xs)) 19 | } 20 | 21 | fn main() {} 22 | -------------------------------------------------------------------------------- /quickcheck_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use proc_macro::TokenStream; 4 | use quote::quote; 5 | use syn::{ 6 | parse::{Parse, Parser}, 7 | parse_quote, 8 | spanned::Spanned, 9 | }; 10 | 11 | #[proc_macro_attribute] 12 | pub fn quickcheck(_args: TokenStream, input: TokenStream) -> TokenStream { 13 | let output = match syn::Item::parse.parse(input.clone()) { 14 | Ok(syn::Item::Fn(mut item_fn)) => { 15 | let mut inputs = syn::punctuated::Punctuated::new(); 16 | let mut errors = Vec::new(); 17 | 18 | item_fn.sig.inputs.iter().for_each(|input| match *input { 19 | syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { 20 | inputs.push(parse_quote!(_: #ty)); 21 | } 22 | _ => errors.push(syn::parse::Error::new( 23 | input.span(), 24 | "unsupported kind of function argument", 25 | )), 26 | }); 27 | 28 | if errors.is_empty() { 29 | let attrs = mem::take(&mut item_fn.attrs); 30 | let name = &item_fn.sig.ident; 31 | if let Some(variadic) = &item_fn.sig.variadic { 32 | // variadics are just for `extern fn` 33 | errors.push(syn::parse::Error::new( 34 | variadic.span(), 35 | "unsupported variadic", 36 | )); 37 | } 38 | let fn_type = syn::TypeBareFn { 39 | lifetimes: None, 40 | unsafety: item_fn.sig.unsafety, 41 | abi: item_fn.sig.abi.clone(), 42 | fn_token: ::default(), 43 | paren_token: syn::token::Paren::default(), 44 | inputs, 45 | variadic: None, 46 | output: item_fn.sig.output.clone(), 47 | }; 48 | 49 | quote! { 50 | #[test] 51 | #(#attrs)* 52 | fn #name() { 53 | #item_fn 54 | ::quickcheck::quickcheck(#name as #fn_type) 55 | } 56 | } 57 | } else { 58 | errors 59 | .iter() 60 | .map(syn::parse::Error::to_compile_error) 61 | .collect() 62 | } 63 | } 64 | Ok(syn::Item::Static(mut item_static)) => { 65 | let attrs = mem::take(&mut item_static.attrs); 66 | let name = &item_static.ident; 67 | 68 | quote! { 69 | #[test] 70 | #(#attrs)* 71 | fn #name() { 72 | #item_static 73 | ::quickcheck::quickcheck(#name) 74 | } 75 | } 76 | } 77 | _ => { 78 | let span = proc_macro2::TokenStream::from(input).span(); 79 | let msg = 80 | "#[quickcheck] is only supported on statics and functions"; 81 | 82 | syn::parse::Error::new(span, msg).to_compile_error() 83 | } 84 | }; 85 | 86 | output.into() 87 | } 88 | -------------------------------------------------------------------------------- /quickcheck_macros/tests/macro.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | 3 | extern crate quickcheck; 4 | extern crate quickcheck_macros; 5 | 6 | use quickcheck::TestResult; 7 | use quickcheck_macros::quickcheck; 8 | 9 | #[quickcheck] 10 | fn min(x: isize, y: isize) -> TestResult { 11 | if x < y { 12 | TestResult::discard() 13 | } else { 14 | TestResult::from_bool(::std::cmp::min(x, y) == y) 15 | } 16 | } 17 | 18 | #[quickcheck] 19 | #[should_panic] 20 | fn fail_fn() -> bool { false } 21 | 22 | #[quickcheck] 23 | static static_bool: bool = true; 24 | 25 | #[quickcheck] 26 | #[should_panic] 27 | static fail_static_bool: bool = false; 28 | 29 | // If static_bool wasn't turned into a test function, then this should 30 | // result in a compiler error. 31 | #[test] 32 | fn static_bool_test_is_function() { 33 | static_bool() 34 | } 35 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 79 2 | use_small_heuristics = "max" 3 | -------------------------------------------------------------------------------- /session.vim: -------------------------------------------------------------------------------- 1 | au BufWritePost *.rs silent!make ctags > /dev/null 2>&1 2 | -------------------------------------------------------------------------------- /src/arbitrary.rs: -------------------------------------------------------------------------------- 1 | use std::char; 2 | use std::collections::{ 3 | BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque, 4 | }; 5 | use std::env; 6 | use std::ffi::{CString, OsString}; 7 | use std::hash::{BuildHasher, Hash}; 8 | use std::iter::{empty, once}; 9 | use std::net::{ 10 | IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, 11 | }; 12 | use std::num::Wrapping; 13 | use std::num::{ 14 | NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, 15 | }; 16 | use std::ops::{ 17 | Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, 18 | RangeToInclusive, 19 | }; 20 | use std::path::PathBuf; 21 | use std::sync::Arc; 22 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 23 | 24 | use rand::prelude::*; 25 | use rand::{Rng, SeedableRng}; 26 | 27 | /// `Gen` represents a PRNG. 28 | /// 29 | /// It is the source of randomness from which QuickCheck will generate values. 30 | /// An instance of `Gen` is passed to every invocation of 31 | /// `Arbitrary::arbitrary`, which permits callers to use lower level RNG 32 | /// routines to generate values. 33 | /// 34 | /// It is unspecified whether this is a secure RNG or not. Therefore, callers 35 | /// should assume it is insecure. 36 | pub struct Gen { 37 | rng: rand::rngs::SmallRng, 38 | size: usize, 39 | } 40 | 41 | impl Gen { 42 | /// Returns a `Gen` with the given size configuration. 43 | /// 44 | /// The `size` parameter controls the size of random values generated. 45 | /// For example, it specifies the maximum length of a randomly generated 46 | /// vector, but is and should not be used to control the range of a 47 | /// randomly generated number. (Unless that number is used to control the 48 | /// size of a data structure.) 49 | pub fn new(size: usize) -> Gen { 50 | Gen { rng: rand::rngs::SmallRng::from_os_rng(), size } 51 | } 52 | 53 | /// Returns the size configured with this generator. 54 | pub fn size(&self) -> usize { 55 | self.size 56 | } 57 | 58 | /// Choose among the possible alternatives in the slice given. If the slice 59 | /// is empty, then `None` is returned. Otherwise, a non-`None` value is 60 | /// guaranteed to be returned. 61 | pub fn choose<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> { 62 | slice.choose(&mut self.rng) 63 | } 64 | 65 | fn random(&mut self) -> T 66 | where 67 | rand::distr::StandardUniform: rand::distr::Distribution, 68 | { 69 | self.rng.random() 70 | } 71 | 72 | fn random_range(&mut self, range: R) -> T 73 | where 74 | T: rand::distr::uniform::SampleUniform, 75 | R: rand::distr::uniform::SampleRange, 76 | { 77 | self.rng.random_range(range) 78 | } 79 | } 80 | 81 | /// Creates a shrinker with zero elements. 82 | pub fn empty_shrinker() -> Box> { 83 | Box::new(empty()) 84 | } 85 | 86 | /// Creates a shrinker with a single element. 87 | pub fn single_shrinker(value: A) -> Box> { 88 | Box::new(once(value)) 89 | } 90 | 91 | /// `Arbitrary` describes types whose values can be randomly generated and 92 | /// shrunk. 93 | /// 94 | /// Aside from shrinking, `Arbitrary` is different from typical RNGs in that it 95 | /// respects `Gen::size()` for controlling how much memory a particular value 96 | /// uses, for practical purposes. For example, `Vec::arbitrary()` respects 97 | /// `Gen::size()` to decide the maximum `len()` of the vector. This behavior is 98 | /// necessary due to practical speed and size limitations. Conversely, 99 | /// `i32::arbitrary()` ignores `size()` since all `i32` values require `O(1)` 100 | /// memory and operations between `i32`s require `O(1)` time (with the 101 | /// exception of exponentiation). 102 | /// 103 | /// Additionally, all types that implement `Arbitrary` must also implement 104 | /// `Clone`. 105 | pub trait Arbitrary: Clone + 'static { 106 | /// Return an arbitrary value. 107 | /// 108 | /// Implementations should respect `Gen::size()` when decisions about how 109 | /// big a particular value should be. Implementations should generally 110 | /// defer to other `Arbitrary` implementations to generate other random 111 | /// values when necessary. The `Gen` type also offers a few RNG helper 112 | /// routines. 113 | fn arbitrary(g: &mut Gen) -> Self; 114 | 115 | /// Return an iterator of values that are smaller than itself. 116 | /// 117 | /// The way in which a value is "smaller" is implementation defined. In 118 | /// some cases, the interpretation is obvious: shrinking an integer should 119 | /// produce integers smaller than itself. Others are more complex, for 120 | /// example, shrinking a `Vec` should both shrink its size and shrink its 121 | /// component values. 122 | /// 123 | /// The iterator returned should be bounded to some reasonable size. 124 | /// 125 | /// It is always correct to return an empty iterator, and indeed, this 126 | /// is the default implementation. The downside of this approach is that 127 | /// witnesses to failures in properties will be more inscrutable. 128 | fn shrink(&self) -> Box> { 129 | empty_shrinker() 130 | } 131 | } 132 | 133 | impl Arbitrary for () { 134 | fn arbitrary(_: &mut Gen) {} 135 | } 136 | 137 | impl Arbitrary for bool { 138 | fn arbitrary(g: &mut Gen) -> bool { 139 | g.random() 140 | } 141 | 142 | fn shrink(&self) -> Box> { 143 | if *self { 144 | single_shrinker(false) 145 | } else { 146 | empty_shrinker() 147 | } 148 | } 149 | } 150 | 151 | impl Arbitrary for Option { 152 | fn arbitrary(g: &mut Gen) -> Option { 153 | if g.random() { 154 | None 155 | } else { 156 | Some(Arbitrary::arbitrary(g)) 157 | } 158 | } 159 | 160 | fn shrink(&self) -> Box>> { 161 | match *self { 162 | None => empty_shrinker(), 163 | Some(ref x) => { 164 | let chain = single_shrinker(None).chain(x.shrink().map(Some)); 165 | Box::new(chain) 166 | } 167 | } 168 | } 169 | } 170 | 171 | impl Arbitrary for Result { 172 | fn arbitrary(g: &mut Gen) -> Result { 173 | if g.random() { 174 | Ok(Arbitrary::arbitrary(g)) 175 | } else { 176 | Err(Arbitrary::arbitrary(g)) 177 | } 178 | } 179 | 180 | fn shrink(&self) -> Box>> { 181 | match *self { 182 | Ok(ref x) => { 183 | let xs = x.shrink(); 184 | let tagged = xs.map(Ok); 185 | Box::new(tagged) 186 | } 187 | Err(ref x) => { 188 | let xs = x.shrink(); 189 | let tagged = xs.map(Err); 190 | Box::new(tagged) 191 | } 192 | } 193 | } 194 | } 195 | 196 | macro_rules! impl_arb_for_single_tuple { 197 | ($(($type_param:ident, $tuple_index:tt),)*) => { 198 | impl<$($type_param),*> Arbitrary for ($($type_param,)*) 199 | where $($type_param: Arbitrary,)* 200 | { 201 | fn arbitrary(g: &mut Gen) -> ($($type_param,)*) { 202 | ( 203 | $( 204 | $type_param::arbitrary(g), 205 | )* 206 | ) 207 | } 208 | 209 | fn shrink(&self) -> Box> { 210 | let iter = ::std::iter::empty(); 211 | $( 212 | let cloned = self.clone(); 213 | let iter = iter.chain( 214 | self.$tuple_index.shrink().map(move |shr_value| { 215 | let mut result = cloned.clone(); 216 | result.$tuple_index = shr_value; 217 | result 218 | }) 219 | ); 220 | )* 221 | Box::new(iter) 222 | } 223 | } 224 | }; 225 | } 226 | 227 | macro_rules! impl_arb_for_tuples { 228 | (@internal [$($acc:tt,)*]) => { }; 229 | (@internal [$($acc:tt,)*] ($type_param:ident, $tuple_index:tt), $($rest:tt,)*) => { 230 | impl_arb_for_single_tuple!($($acc,)* ($type_param, $tuple_index),); 231 | impl_arb_for_tuples!(@internal [$($acc,)* ($type_param, $tuple_index),] $($rest,)*); 232 | }; 233 | ($(($type_param:ident, $tuple_index:tt),)*) => { 234 | impl_arb_for_tuples!(@internal [] $(($type_param, $tuple_index),)*); 235 | }; 236 | } 237 | 238 | impl_arb_for_tuples! { 239 | (A, 0), 240 | (B, 1), 241 | (C, 2), 242 | (D, 3), 243 | (E, 4), 244 | (F, 5), 245 | (G, 6), 246 | (H, 7), 247 | } 248 | 249 | impl Arbitrary for [A; N] { 250 | fn arbitrary(g: &mut Gen) -> Self { 251 | std::array::from_fn(|_ix| A::arbitrary(g)) 252 | } 253 | 254 | fn shrink(&self) -> Box> { 255 | let cloned = self.clone(); 256 | let iter = (0..N).flat_map(move |n| { 257 | let cloned = cloned.clone(); 258 | cloned[n].shrink().map(move |shr_value| { 259 | let mut result = cloned.clone(); 260 | result[n] = shr_value; 261 | result 262 | }) 263 | }); 264 | 265 | Box::new(iter) 266 | } 267 | } 268 | 269 | impl Arbitrary for Vec { 270 | fn arbitrary(g: &mut Gen) -> Vec { 271 | let size = { 272 | let s = g.size(); 273 | g.random_range(0..s) 274 | }; 275 | (0..size).map(|_| A::arbitrary(g)).collect() 276 | } 277 | 278 | fn shrink(&self) -> Box>> { 279 | VecShrinker::new(self.clone()) 280 | } 281 | } 282 | 283 | ///Iterator which returns successive attempts to shrink the vector `seed` 284 | struct VecShrinker { 285 | seed: Vec, 286 | /// How much which is removed when trying with smaller vectors 287 | size: usize, 288 | /// The end of the removed elements 289 | offset: usize, 290 | /// The shrinker for the element at `offset` once shrinking of individual 291 | /// elements are attempted 292 | element_shrinker: Box>, 293 | } 294 | 295 | impl VecShrinker { 296 | #[allow(clippy::new_ret_no_self)] 297 | fn new(seed: Vec) -> Box>> { 298 | let es = match seed.first() { 299 | Some(e) => e.shrink(), 300 | None => return empty_shrinker(), 301 | }; 302 | let size = seed.len(); 303 | Box::new(VecShrinker { 304 | seed, 305 | size, 306 | offset: size, 307 | element_shrinker: es, 308 | }) 309 | } 310 | 311 | /// Returns the next shrunk element if any, `offset` points to the index 312 | /// after the returned element after the function returns 313 | fn next_element(&mut self) -> Option { 314 | loop { 315 | match self.element_shrinker.next() { 316 | Some(e) => return Some(e), 317 | None => match self.seed.get(self.offset) { 318 | Some(e) => { 319 | self.element_shrinker = e.shrink(); 320 | self.offset += 1; 321 | } 322 | None => return None, 323 | }, 324 | } 325 | } 326 | } 327 | } 328 | 329 | impl Iterator for VecShrinker 330 | where 331 | A: Arbitrary, 332 | { 333 | type Item = Vec; 334 | fn next(&mut self) -> Option> { 335 | // Try with an empty vector first 336 | if self.size == self.seed.len() { 337 | self.size /= 2; 338 | self.offset = self.size; 339 | return Some(vec![]); 340 | } 341 | if self.size != 0 { 342 | // Generate a smaller vector by removing the elements between 343 | // (offset - size) and offset 344 | let xs1 = self.seed[..(self.offset - self.size)] 345 | .iter() 346 | .chain(&self.seed[self.offset..]) 347 | .cloned() 348 | .collect(); 349 | self.offset += self.size; 350 | // Try to reduce the amount removed from the vector once all 351 | // previous sizes tried 352 | if self.offset > self.seed.len() { 353 | self.size /= 2; 354 | self.offset = self.size; 355 | } 356 | Some(xs1) 357 | } else { 358 | // A smaller vector did not work so try to shrink each element of 359 | // the vector instead Reuse `offset` as the index determining which 360 | // element to shrink 361 | 362 | // The first element shrinker is already created so skip the first 363 | // offset (self.offset == 0 only on first entry to this part of the 364 | // iterator) 365 | if self.offset == 0 { 366 | self.offset = 1; 367 | } 368 | 369 | match self.next_element() { 370 | Some(e) => Some( 371 | self.seed[..self.offset - 1] 372 | .iter() 373 | .cloned() 374 | .chain(Some(e)) 375 | .chain(self.seed[self.offset..].iter().cloned()) 376 | .collect(), 377 | ), 378 | None => None, 379 | } 380 | } 381 | } 382 | } 383 | 384 | impl Arbitrary for BTreeMap { 385 | fn arbitrary(g: &mut Gen) -> BTreeMap { 386 | let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); 387 | vec.into_iter().collect() 388 | } 389 | 390 | fn shrink(&self) -> Box>> { 391 | let vec: Vec<(K, V)> = self.clone().into_iter().collect(); 392 | Box::new( 393 | vec.shrink().map(|v| v.into_iter().collect::>()), 394 | ) 395 | } 396 | } 397 | 398 | impl< 399 | K: Arbitrary + Eq + Hash, 400 | V: Arbitrary, 401 | S: BuildHasher + Default + Clone + 'static, 402 | > Arbitrary for HashMap 403 | { 404 | fn arbitrary(g: &mut Gen) -> Self { 405 | let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); 406 | vec.into_iter().collect() 407 | } 408 | 409 | fn shrink(&self) -> Box> { 410 | let vec: Vec<(K, V)> = self.clone().into_iter().collect(); 411 | Box::new(vec.shrink().map(|v| v.into_iter().collect::())) 412 | } 413 | } 414 | 415 | impl Arbitrary for BTreeSet { 416 | fn arbitrary(g: &mut Gen) -> BTreeSet { 417 | let vec: Vec = Arbitrary::arbitrary(g); 418 | vec.into_iter().collect() 419 | } 420 | 421 | fn shrink(&self) -> Box>> { 422 | let vec: Vec = self.clone().into_iter().collect(); 423 | Box::new(vec.shrink().map(|v| v.into_iter().collect::>())) 424 | } 425 | } 426 | 427 | impl Arbitrary for BinaryHeap { 428 | fn arbitrary(g: &mut Gen) -> BinaryHeap { 429 | let vec: Vec = Arbitrary::arbitrary(g); 430 | vec.into_iter().collect() 431 | } 432 | 433 | fn shrink(&self) -> Box>> { 434 | let vec: Vec = self.clone().into_iter().collect(); 435 | Box::new( 436 | vec.shrink().map(|v| v.into_iter().collect::>()), 437 | ) 438 | } 439 | } 440 | 441 | impl 442 | Arbitrary for HashSet 443 | { 444 | fn arbitrary(g: &mut Gen) -> Self { 445 | let vec: Vec = Arbitrary::arbitrary(g); 446 | vec.into_iter().collect() 447 | } 448 | 449 | fn shrink(&self) -> Box> { 450 | let vec: Vec = self.clone().into_iter().collect(); 451 | Box::new(vec.shrink().map(|v| v.into_iter().collect::())) 452 | } 453 | } 454 | 455 | impl Arbitrary for LinkedList { 456 | fn arbitrary(g: &mut Gen) -> LinkedList { 457 | let vec: Vec = Arbitrary::arbitrary(g); 458 | vec.into_iter().collect() 459 | } 460 | 461 | fn shrink(&self) -> Box>> { 462 | let vec: Vec = self.clone().into_iter().collect(); 463 | Box::new( 464 | vec.shrink().map(|v| v.into_iter().collect::>()), 465 | ) 466 | } 467 | } 468 | 469 | impl Arbitrary for VecDeque { 470 | fn arbitrary(g: &mut Gen) -> VecDeque { 471 | let vec: Vec = Arbitrary::arbitrary(g); 472 | vec.into_iter().collect() 473 | } 474 | 475 | fn shrink(&self) -> Box>> { 476 | let vec: Vec = self.clone().into_iter().collect(); 477 | Box::new(vec.shrink().map(|v| v.into_iter().collect::>())) 478 | } 479 | } 480 | 481 | impl Arbitrary for IpAddr { 482 | fn arbitrary(g: &mut Gen) -> IpAddr { 483 | let ipv4: bool = g.random(); 484 | if ipv4 { 485 | IpAddr::V4(Arbitrary::arbitrary(g)) 486 | } else { 487 | IpAddr::V6(Arbitrary::arbitrary(g)) 488 | } 489 | } 490 | } 491 | 492 | impl Arbitrary for Ipv4Addr { 493 | fn arbitrary(g: &mut Gen) -> Ipv4Addr { 494 | Ipv4Addr::new(g.random(), g.random(), g.random(), g.random()) 495 | } 496 | } 497 | 498 | impl Arbitrary for Ipv6Addr { 499 | fn arbitrary(g: &mut Gen) -> Ipv6Addr { 500 | Ipv6Addr::new( 501 | g.random(), 502 | g.random(), 503 | g.random(), 504 | g.random(), 505 | g.random(), 506 | g.random(), 507 | g.random(), 508 | g.random(), 509 | ) 510 | } 511 | } 512 | 513 | impl Arbitrary for SocketAddr { 514 | fn arbitrary(g: &mut Gen) -> SocketAddr { 515 | SocketAddr::new(Arbitrary::arbitrary(g), g.random()) 516 | } 517 | } 518 | 519 | impl Arbitrary for SocketAddrV4 { 520 | fn arbitrary(g: &mut Gen) -> SocketAddrV4 { 521 | SocketAddrV4::new(Arbitrary::arbitrary(g), g.random()) 522 | } 523 | } 524 | 525 | impl Arbitrary for SocketAddrV6 { 526 | fn arbitrary(g: &mut Gen) -> SocketAddrV6 { 527 | SocketAddrV6::new( 528 | Arbitrary::arbitrary(g), 529 | g.random(), 530 | g.random(), 531 | g.random(), 532 | ) 533 | } 534 | } 535 | 536 | impl Arbitrary for PathBuf { 537 | fn arbitrary(g: &mut Gen) -> PathBuf { 538 | // use some real directories as guesses, so we may end up with 539 | // actual working directories in case that is relevant. 540 | let here = env::current_dir() 541 | .unwrap_or_else(|_| PathBuf::from("/test/directory")); 542 | let temp = env::temp_dir(); 543 | #[allow(deprecated)] 544 | let home = 545 | env::home_dir().unwrap_or_else(|| PathBuf::from("/home/user")); 546 | let mut p = g 547 | .choose(&[ 548 | here, 549 | temp, 550 | home, 551 | PathBuf::from("."), 552 | PathBuf::from(".."), 553 | PathBuf::from("../../.."), 554 | PathBuf::new(), 555 | ]) 556 | .unwrap() 557 | .to_owned(); 558 | p.extend(Vec::::arbitrary(g).iter()); 559 | p 560 | } 561 | 562 | fn shrink(&self) -> Box> { 563 | let mut shrunk = vec![]; 564 | let mut popped = self.clone(); 565 | if popped.pop() { 566 | shrunk.push(popped); 567 | } 568 | 569 | // Iterating over a Path performs a small amount of normalization. 570 | let normalized = self.iter().collect::(); 571 | if normalized.as_os_str() != self.as_os_str() { 572 | shrunk.push(normalized); 573 | } 574 | 575 | // Add the canonicalized variant only if canonicalizing the path 576 | // actually does something, making it (hopefully) smaller. Also, ignore 577 | // canonicalization if canonicalization errors. 578 | if let Ok(canonicalized) = self.canonicalize() { 579 | if canonicalized.as_os_str() != self.as_os_str() { 580 | shrunk.push(canonicalized); 581 | } 582 | } 583 | 584 | Box::new(shrunk.into_iter()) 585 | } 586 | } 587 | 588 | impl Arbitrary for OsString { 589 | fn arbitrary(g: &mut Gen) -> OsString { 590 | OsString::from(String::arbitrary(g)) 591 | } 592 | 593 | fn shrink(&self) -> Box> { 594 | let mystring: String = self.clone().into_string().unwrap(); 595 | Box::new(mystring.shrink().map(OsString::from)) 596 | } 597 | } 598 | 599 | impl Arbitrary for String { 600 | fn arbitrary(g: &mut Gen) -> String { 601 | let size = { 602 | let s = g.size(); 603 | g.random_range(0..s) 604 | }; 605 | (0..size).map(|_| char::arbitrary(g)).collect() 606 | } 607 | 608 | fn shrink(&self) -> Box> { 609 | // Shrink a string by shrinking a vector of its characters. 610 | let chars: Vec = self.chars().collect(); 611 | Box::new(chars.shrink().map(|x| x.into_iter().collect::())) 612 | } 613 | } 614 | 615 | impl Arbitrary for CString { 616 | fn arbitrary(g: &mut Gen) -> Self { 617 | let size = { 618 | let s = g.size(); 619 | g.random_range(0..s) 620 | }; 621 | // Use either random bytes or random UTF-8 encoded codepoints. 622 | let utf8: bool = g.random(); 623 | if utf8 { 624 | CString::new( 625 | (0..) 626 | .map(|_| char::arbitrary(g)) 627 | .filter(|&c| c != '\0') 628 | .take(size) 629 | .collect::(), 630 | ) 631 | } else { 632 | CString::new( 633 | (0..) 634 | .map(|_| u8::arbitrary(g)) 635 | .filter(|&c| c != b'\0') 636 | .take(size) 637 | .collect::>(), 638 | ) 639 | } 640 | .expect("null characters should have been filtered out") 641 | } 642 | 643 | fn shrink(&self) -> Box> { 644 | // Use the implementation for a vec here, but make sure null characters 645 | // are filtered out. 646 | Box::new(VecShrinker::new(self.as_bytes().to_vec()).map(|bytes| { 647 | CString::new( 648 | bytes.into_iter().filter(|&c| c != 0).collect::>(), 649 | ) 650 | .expect("null characters should have been filtered out") 651 | })) 652 | } 653 | } 654 | 655 | impl Arbitrary for char { 656 | fn arbitrary(g: &mut Gen) -> char { 657 | let mode = g.random_range(0..100); 658 | match mode { 659 | 0..=49 => { 660 | // ASCII + some control characters 661 | g.random_range(0u8..0xB0) as char 662 | } 663 | 50..=59 => { 664 | // Unicode BMP characters 665 | loop { 666 | if let Some(x) = char::from_u32(g.random_range(0..0x10000)) 667 | { 668 | return x; 669 | } 670 | // ignore surrogate pairs 671 | } 672 | } 673 | 60..=84 => { 674 | // Characters often used in programming languages 675 | g.choose(&[ 676 | ' ', ' ', ' ', '\t', '\n', '~', '`', '!', '@', '#', '$', 677 | '%', '^', '&', '*', '(', ')', '_', '-', '=', '+', '[', 678 | ']', '{', '}', ':', ';', '\'', '"', '\\', '|', ',', '<', 679 | '>', '.', '/', '?', '0', '1', '2', '3', '4', '5', '6', 680 | '7', '8', '9', 681 | ]) 682 | .unwrap() 683 | .to_owned() 684 | } 685 | 85..=89 => { 686 | // Tricky Unicode, part 1 687 | g.choose(&[ 688 | '\u{0149}', // a deprecated character 689 | '\u{fff0}', // some of "Other, format" category: 690 | '\u{fff1}', 691 | '\u{fff2}', 692 | '\u{fff3}', 693 | '\u{fff4}', 694 | '\u{fff5}', 695 | '\u{fff6}', 696 | '\u{fff7}', 697 | '\u{fff8}', 698 | '\u{fff9}', 699 | '\u{fffA}', 700 | '\u{fffB}', 701 | '\u{fffC}', 702 | '\u{fffD}', 703 | '\u{fffE}', 704 | '\u{fffF}', 705 | '\u{0600}', 706 | '\u{0601}', 707 | '\u{0602}', 708 | '\u{0603}', 709 | '\u{0604}', 710 | '\u{0605}', 711 | '\u{061C}', 712 | '\u{06DD}', 713 | '\u{070F}', 714 | '\u{180E}', 715 | '\u{110BD}', 716 | '\u{1D173}', 717 | '\u{e0001}', // tag 718 | '\u{e0020}', // tag space 719 | '\u{e000}', 720 | '\u{e001}', 721 | '\u{ef8ff}', // private use 722 | '\u{f0000}', 723 | '\u{ffffd}', 724 | '\u{ffffe}', 725 | '\u{fffff}', 726 | '\u{100000}', 727 | '\u{10FFFD}', 728 | '\u{10FFFE}', 729 | '\u{10FFFF}', 730 | // "Other, surrogate" characters are so that very special 731 | // that they are not even allowed in safe Rust, 732 | //so omitted here 733 | '\u{3000}', // ideographic space 734 | '\u{1680}', 735 | // other space characters are already covered by two next 736 | // branches 737 | ]) 738 | .unwrap() 739 | .to_owned() 740 | } 741 | 90..=94 => { 742 | // Tricky unicode, part 2 743 | char::from_u32(g.random_range(0x2000..0x2070)).unwrap() 744 | } 745 | 95..=99 => { 746 | // Completely arbitrary characters 747 | g.random() 748 | } 749 | _ => unreachable!(), 750 | } 751 | } 752 | 753 | fn shrink(&self) -> Box> { 754 | Box::new((*self as u32).shrink().filter_map(char::from_u32)) 755 | } 756 | } 757 | 758 | macro_rules! unsigned_shrinker { 759 | ($ty:ty) => { 760 | mod shrinker { 761 | pub struct UnsignedShrinker { 762 | x: $ty, 763 | i: $ty, 764 | } 765 | 766 | impl UnsignedShrinker { 767 | #[allow(clippy::new_ret_no_self)] 768 | pub fn new(x: $ty) -> Box> { 769 | if x == 0 { 770 | super::empty_shrinker() 771 | } else { 772 | Box::new( 773 | vec![0] 774 | .into_iter() 775 | .chain(UnsignedShrinker { x, i: x / 2 }), 776 | ) 777 | } 778 | } 779 | } 780 | 781 | impl Iterator for UnsignedShrinker { 782 | type Item = $ty; 783 | fn next(&mut self) -> Option<$ty> { 784 | if self.x - self.i < self.x { 785 | let result = Some(self.x - self.i); 786 | self.i /= 2; 787 | result 788 | } else { 789 | None 790 | } 791 | } 792 | } 793 | } 794 | }; 795 | } 796 | 797 | macro_rules! unsigned_problem_values { 798 | ($t:ty) => { 799 | &[<$t>::MIN, 1, <$t>::MAX] 800 | }; 801 | } 802 | 803 | macro_rules! unsigned_arbitrary { 804 | ($($ty:tt),*) => { 805 | $( 806 | impl Arbitrary for $ty { 807 | fn arbitrary(g: &mut Gen) -> $ty { 808 | match g.random_range(0..10) { 809 | 0 => *g.choose(unsigned_problem_values!($ty)).unwrap(), 810 | _ => g.random() 811 | } 812 | } 813 | fn shrink(&self) -> Box> { 814 | unsigned_shrinker!($ty); 815 | shrinker::UnsignedShrinker::new(*self) 816 | } 817 | } 818 | )* 819 | } 820 | } 821 | 822 | unsigned_arbitrary! { 823 | u8, u16, u32, u64, u128 824 | } 825 | 826 | impl Arbitrary for usize { 827 | fn arbitrary(g: &mut Gen) -> usize { 828 | match g.random_range(0..10) { 829 | 0 => *g.choose(unsigned_problem_values!(usize)).unwrap(), 830 | _ => { 831 | #[cfg(target_pointer_width = "16")] 832 | { 833 | g.random::() as usize 834 | } 835 | #[cfg(target_pointer_width = "32")] 836 | { 837 | g.random::() as usize 838 | } 839 | #[cfg(target_pointer_width = "64")] 840 | { 841 | g.random::() as usize 842 | } 843 | } 844 | } 845 | } 846 | fn shrink(&self) -> Box> { 847 | unsigned_shrinker!(usize); 848 | shrinker::UnsignedShrinker::new(*self) 849 | } 850 | } 851 | 852 | macro_rules! signed_shrinker { 853 | ($ty:ty) => { 854 | mod shrinker { 855 | pub struct SignedShrinker { 856 | x: $ty, 857 | i: $ty, 858 | } 859 | 860 | impl SignedShrinker { 861 | #[allow(clippy::new_ret_no_self)] 862 | pub fn new(x: $ty) -> Box> { 863 | if x == 0 { 864 | super::empty_shrinker() 865 | } else { 866 | let shrinker = SignedShrinker { x, i: x / 2 }; 867 | let mut items = vec![0]; 868 | if shrinker.i < 0 && shrinker.x != <$ty>::MIN { 869 | items.push(shrinker.x.abs()); 870 | } 871 | Box::new(items.into_iter().chain(shrinker)) 872 | } 873 | } 874 | } 875 | 876 | impl Iterator for SignedShrinker { 877 | type Item = $ty; 878 | fn next(&mut self) -> Option<$ty> { 879 | if self.x == <$ty>::MIN 880 | || (self.x - self.i).abs() < self.x.abs() 881 | { 882 | let result = Some(self.x - self.i); 883 | self.i /= 2; 884 | result 885 | } else { 886 | None 887 | } 888 | } 889 | } 890 | } 891 | }; 892 | } 893 | 894 | macro_rules! signed_problem_values { 895 | ($t:ty) => { 896 | &[<$t>::MIN, 0, <$t>::MAX] 897 | }; 898 | } 899 | 900 | macro_rules! signed_arbitrary { 901 | ($($ty:tt),*) => { 902 | $( 903 | impl Arbitrary for $ty { 904 | fn arbitrary(g: &mut Gen) -> $ty { 905 | match g.random_range(0..10) { 906 | 0 => *g.choose(signed_problem_values!($ty)).unwrap(), 907 | _ => g.random() 908 | } 909 | } 910 | fn shrink(&self) -> Box> { 911 | signed_shrinker!($ty); 912 | shrinker::SignedShrinker::new(*self) 913 | } 914 | } 915 | )* 916 | } 917 | } 918 | 919 | signed_arbitrary! { 920 | i8, i16, i32, i64, i128 921 | } 922 | 923 | impl Arbitrary for isize { 924 | fn arbitrary(g: &mut Gen) -> isize { 925 | match g.random_range(0..10) { 926 | 0 => *g.choose(signed_problem_values!(isize)).unwrap(), 927 | _ => { 928 | #[cfg(target_pointer_width = "16")] 929 | { 930 | g.random::() as isize 931 | } 932 | #[cfg(target_pointer_width = "32")] 933 | { 934 | g.random::() as isize 935 | } 936 | #[cfg(target_pointer_width = "64")] 937 | { 938 | g.random::() as isize 939 | } 940 | } 941 | } 942 | } 943 | fn shrink(&self) -> Box> { 944 | signed_shrinker!(isize); 945 | shrinker::SignedShrinker::new(*self) 946 | } 947 | } 948 | 949 | macro_rules! float_problem_values { 950 | ($t:ty) => {{ 951 | &[ 952 | <$t>::NAN, 953 | <$t>::NEG_INFINITY, 954 | <$t>::MIN, 955 | -0., 956 | 0., 957 | <$t>::MAX, 958 | <$t>::INFINITY, 959 | ] 960 | }}; 961 | } 962 | 963 | macro_rules! float_arbitrary { 964 | ($($t:ty, $shrinkable:ty),+) => {$( 965 | impl Arbitrary for $t { 966 | fn arbitrary(g: &mut Gen) -> $t { 967 | match g.random_range(0..10) { 968 | 0 => *g.choose(float_problem_values!($t)).unwrap(), 969 | _ => { 970 | let exp = g.random_range((0.)..<$t>::MAX_EXP as i16 as $t); 971 | let mantissa = g.random_range((1.)..2.); 972 | let sign = *g.choose(&[-1., 1.]).unwrap(); 973 | sign * mantissa * exp.exp2() 974 | } 975 | } 976 | } 977 | fn shrink(&self) -> Box> { 978 | signed_shrinker!($shrinkable); 979 | let it = shrinker::SignedShrinker::new(*self as $shrinkable); 980 | Box::new(it.map(|x| x as $t)) 981 | } 982 | } 983 | )*}; 984 | } 985 | 986 | float_arbitrary!(f32, i32, f64, i64); 987 | 988 | macro_rules! unsigned_non_zero_shrinker { 989 | ($ty:tt) => { 990 | mod shrinker { 991 | pub struct UnsignedNonZeroShrinker { 992 | x: $ty, 993 | i: $ty, 994 | } 995 | 996 | impl UnsignedNonZeroShrinker { 997 | #[allow(clippy::new_ret_no_self)] 998 | pub fn new(x: $ty) -> Box> { 999 | debug_assert!(x > 0); 1000 | 1001 | if x == 1 { 1002 | super::empty_shrinker() 1003 | } else { 1004 | Box::new( 1005 | std::iter::once(1).chain( 1006 | UnsignedNonZeroShrinker { x, i: x / 2 }, 1007 | ), 1008 | ) 1009 | } 1010 | } 1011 | } 1012 | 1013 | impl Iterator for UnsignedNonZeroShrinker { 1014 | type Item = $ty; 1015 | 1016 | fn next(&mut self) -> Option<$ty> { 1017 | if self.x - self.i < self.x { 1018 | let result = Some(self.x - self.i); 1019 | self.i /= 2; 1020 | result 1021 | } else { 1022 | None 1023 | } 1024 | } 1025 | } 1026 | } 1027 | }; 1028 | } 1029 | 1030 | macro_rules! unsigned_non_zero_arbitrary { 1031 | ($($ty:tt => $inner:tt),*) => { 1032 | $( 1033 | impl Arbitrary for $ty { 1034 | fn arbitrary(g: &mut Gen) -> $ty { 1035 | let mut v = $inner::arbitrary(g); 1036 | if v == 0 { 1037 | v += 1; 1038 | } 1039 | $ty::new(v).expect("non-zero value construction failed") 1040 | } 1041 | 1042 | fn shrink(&self) -> Box> { 1043 | unsigned_non_zero_shrinker!($inner); 1044 | Box::new(shrinker::UnsignedNonZeroShrinker::new(self.get()) 1045 | .map($ty::new) 1046 | .map(Option::unwrap)) 1047 | } 1048 | } 1049 | )* 1050 | } 1051 | } 1052 | 1053 | unsigned_non_zero_arbitrary! { 1054 | NonZeroUsize => usize, 1055 | NonZeroU8 => u8, 1056 | NonZeroU16 => u16, 1057 | NonZeroU32 => u32, 1058 | NonZeroU64 => u64, 1059 | NonZeroU128 => u128 1060 | } 1061 | 1062 | impl Arbitrary for Wrapping { 1063 | fn arbitrary(g: &mut Gen) -> Wrapping { 1064 | Wrapping(T::arbitrary(g)) 1065 | } 1066 | fn shrink(&self) -> Box>> { 1067 | Box::new(self.0.shrink().map(Wrapping)) 1068 | } 1069 | } 1070 | 1071 | impl Arbitrary for Bound { 1072 | fn arbitrary(g: &mut Gen) -> Bound { 1073 | match g.random_range(0..3) { 1074 | 0 => Bound::Included(T::arbitrary(g)), 1075 | 1 => Bound::Excluded(T::arbitrary(g)), 1076 | _ => Bound::Unbounded, 1077 | } 1078 | } 1079 | fn shrink(&self) -> Box>> { 1080 | match *self { 1081 | Bound::Included(ref x) => { 1082 | Box::new(x.shrink().map(Bound::Included)) 1083 | } 1084 | Bound::Excluded(ref x) => { 1085 | Box::new(x.shrink().map(Bound::Excluded)) 1086 | } 1087 | Bound::Unbounded => empty_shrinker(), 1088 | } 1089 | } 1090 | } 1091 | 1092 | impl Arbitrary for Range { 1093 | fn arbitrary(g: &mut Gen) -> Range { 1094 | Arbitrary::arbitrary(g)..Arbitrary::arbitrary(g) 1095 | } 1096 | fn shrink(&self) -> Box>> { 1097 | Box::new( 1098 | (self.start.clone(), self.end.clone()).shrink().map(|(s, e)| s..e), 1099 | ) 1100 | } 1101 | } 1102 | 1103 | impl Arbitrary for RangeInclusive { 1104 | fn arbitrary(g: &mut Gen) -> RangeInclusive { 1105 | Arbitrary::arbitrary(g)..=Arbitrary::arbitrary(g) 1106 | } 1107 | fn shrink(&self) -> Box>> { 1108 | Box::new( 1109 | (self.start().clone(), self.end().clone()) 1110 | .shrink() 1111 | .map(|(s, e)| s..=e), 1112 | ) 1113 | } 1114 | } 1115 | 1116 | impl Arbitrary for RangeFrom { 1117 | fn arbitrary(g: &mut Gen) -> RangeFrom { 1118 | Arbitrary::arbitrary(g).. 1119 | } 1120 | fn shrink(&self) -> Box>> { 1121 | Box::new(self.start.clone().shrink().map(|start| start..)) 1122 | } 1123 | } 1124 | 1125 | impl Arbitrary for RangeTo { 1126 | fn arbitrary(g: &mut Gen) -> RangeTo { 1127 | ..Arbitrary::arbitrary(g) 1128 | } 1129 | fn shrink(&self) -> Box>> { 1130 | Box::new(self.end.clone().shrink().map(|end| ..end)) 1131 | } 1132 | } 1133 | 1134 | impl Arbitrary for RangeToInclusive { 1135 | fn arbitrary(g: &mut Gen) -> RangeToInclusive { 1136 | ..=Arbitrary::arbitrary(g) 1137 | } 1138 | fn shrink(&self) -> Box>> { 1139 | Box::new(self.end.clone().shrink().map(|end| ..=end)) 1140 | } 1141 | } 1142 | 1143 | impl Arbitrary for RangeFull { 1144 | fn arbitrary(_: &mut Gen) -> RangeFull { 1145 | .. 1146 | } 1147 | } 1148 | 1149 | impl Arbitrary for Duration { 1150 | fn arbitrary(rng: &mut Gen) -> Self { 1151 | let seconds = rng.random_range(0..rng.size() as u64); 1152 | let nanoseconds = rng.random_range(0..1_000_000); 1153 | Duration::new(seconds, nanoseconds) 1154 | } 1155 | 1156 | fn shrink(&self) -> Box> { 1157 | Box::new( 1158 | (self.as_secs(), self.subsec_nanos()) 1159 | .shrink() 1160 | .map(|(secs, nanos)| Duration::new(secs, nanos % 1_000_000)), 1161 | ) 1162 | } 1163 | } 1164 | 1165 | impl Arbitrary for Box { 1166 | fn arbitrary(g: &mut Gen) -> Box { 1167 | Box::new(A::arbitrary(g)) 1168 | } 1169 | 1170 | fn shrink(&self) -> Box>> { 1171 | Box::new((**self).shrink().map(Box::new)) 1172 | } 1173 | } 1174 | 1175 | impl Arbitrary for Arc { 1176 | fn arbitrary(g: &mut Gen) -> Arc { 1177 | Arc::new(A::arbitrary(g)) 1178 | } 1179 | 1180 | fn shrink(&self) -> Box>> { 1181 | Box::new((**self).shrink().map(Arc::new)) 1182 | } 1183 | } 1184 | 1185 | impl Arbitrary for SystemTime { 1186 | fn arbitrary(rng: &mut Gen) -> Self { 1187 | let after_epoch = bool::arbitrary(rng); 1188 | let duration = Duration::arbitrary(rng); 1189 | if after_epoch { 1190 | UNIX_EPOCH + duration 1191 | } else { 1192 | UNIX_EPOCH - duration 1193 | } 1194 | } 1195 | 1196 | fn shrink(&self) -> Box> { 1197 | let duration = match self.duration_since(UNIX_EPOCH) { 1198 | Ok(duration) => duration, 1199 | Err(e) => e.duration(), 1200 | }; 1201 | Box::new( 1202 | duration 1203 | .shrink() 1204 | .flat_map(|d| vec![UNIX_EPOCH + d, UNIX_EPOCH - d]), 1205 | ) 1206 | } 1207 | } 1208 | 1209 | #[cfg(test)] 1210 | mod test { 1211 | use std::collections::{ 1212 | BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque, 1213 | }; 1214 | use std::fmt::Debug; 1215 | use std::hash::Hash; 1216 | use std::num::Wrapping; 1217 | use std::path::PathBuf; 1218 | 1219 | use super::{Arbitrary, Gen}; 1220 | 1221 | #[test] 1222 | fn arby_unit() { 1223 | let _: () = arby::<()>(); 1224 | } 1225 | 1226 | macro_rules! arby_int { 1227 | ( $signed:expr, $($t:ty),+) => {$( 1228 | let mut arbys = (0..1_000_000).map(|_| arby::<$t>()); 1229 | let mut problems = if $signed { 1230 | signed_problem_values!($t).iter() 1231 | } else { 1232 | unsigned_problem_values!($t).iter() 1233 | }; 1234 | assert!(problems.all(|p| arbys.any(|arby| arby == *p)), 1235 | "Arbitrary does not generate all problematic values"); 1236 | let max = <$t>::MAX; 1237 | let mid = (max + <$t>::MIN) / 2; 1238 | // split full range of $t into chunks 1239 | // Arbitrary must return some value in each chunk 1240 | let double_chunks: $t = 9; 1241 | let chunks = double_chunks * 2; // chunks must be even 1242 | let lim: Box> = if $signed { 1243 | Box::new((0..=chunks) 1244 | .map(|idx| idx - chunks / 2) 1245 | .map(|x| mid + max / (chunks / 2) * x)) 1246 | } else { 1247 | Box::new((0..=chunks).map(|idx| max / chunks * idx)) 1248 | }; 1249 | let mut lim = lim.peekable(); 1250 | while let (Some(low), Some(&high)) = (lim.next(), lim.peek()) { 1251 | assert!(arbys.any(|arby| low <= arby && arby <= high), 1252 | "Arbitrary doesn't generate numbers in {}..={}", low, high) 1253 | } 1254 | )*}; 1255 | } 1256 | 1257 | #[test] 1258 | fn arby_int() { 1259 | arby_int!(true, i8, i16, i32, i64, isize, i128); 1260 | } 1261 | 1262 | #[test] 1263 | fn arby_uint() { 1264 | arby_int!(false, u8, u16, u32, u64, usize, u128); 1265 | } 1266 | 1267 | macro_rules! arby_float { 1268 | ($($t:ty),+) => {$({ 1269 | let mut arbys = (0..1_000_000).map(|_| arby::<$t>()); 1270 | //NaN != NaN 1271 | assert!(arbys.any(|f| f.is_nan()), 1272 | "Arbitrary does not generate the problematic value NaN" 1273 | ); 1274 | for p in float_problem_values!($t).iter().filter(|f| !f.is_nan()) { 1275 | assert!(arbys.any(|arby| arby == *p), 1276 | "Arbitrary does not generate the problematic value {}", 1277 | p 1278 | ); 1279 | } 1280 | // split full range of $t into chunks 1281 | // Arbitrary must return some value in each chunk 1282 | let double_chunks: i8 = 9; 1283 | let chunks = double_chunks * 2; // chunks must be even 1284 | let lim = (-double_chunks..=double_chunks) 1285 | .map(|idx| <$t>::from(idx)) 1286 | .map(|idx| <$t>::MAX/(<$t>::from(chunks/2)) * idx); 1287 | let mut lim = lim.peekable(); 1288 | while let (Some(low), Some(&high)) = (lim.next(), lim.peek()) { 1289 | assert!( 1290 | arbys.any(|arby| low <= arby && arby <= high), 1291 | "Arbitrary doesn't generate numbers in {:e}..={:e}", 1292 | low, 1293 | high, 1294 | ) 1295 | } 1296 | })*}; 1297 | } 1298 | 1299 | #[test] 1300 | fn arby_float() { 1301 | arby_float!(f32, f64); 1302 | } 1303 | 1304 | fn arby() -> A { 1305 | Arbitrary::arbitrary(&mut Gen::new(5)) 1306 | } 1307 | 1308 | // Shrink testing. 1309 | #[test] 1310 | fn unit() { 1311 | eq((), vec![]); 1312 | } 1313 | 1314 | #[test] 1315 | fn bools() { 1316 | eq(false, vec![]); 1317 | eq(true, vec![false]); 1318 | } 1319 | 1320 | #[test] 1321 | fn options() { 1322 | eq(None::<()>, vec![]); 1323 | eq(Some(false), vec![None]); 1324 | eq(Some(true), vec![None, Some(false)]); 1325 | } 1326 | 1327 | #[test] 1328 | fn results() { 1329 | // Result doesn't implement the Hash trait, so these tests 1330 | // depends on the order of shrunk results. Ug. 1331 | // TODO: Fix this. 1332 | ordered_eq(Ok::(true), vec![Ok(false)]); 1333 | ordered_eq(Err::<(), bool>(true), vec![Err(false)]); 1334 | } 1335 | 1336 | #[test] 1337 | fn tuples() { 1338 | eq((false, false), vec![]); 1339 | eq((true, false), vec![(false, false)]); 1340 | eq((true, true), vec![(false, true), (true, false)]); 1341 | } 1342 | 1343 | #[test] 1344 | fn triples() { 1345 | eq((false, false, false), vec![]); 1346 | eq((true, false, false), vec![(false, false, false)]); 1347 | eq( 1348 | (true, true, false), 1349 | vec![(false, true, false), (true, false, false)], 1350 | ); 1351 | } 1352 | 1353 | #[test] 1354 | fn quads() { 1355 | eq((false, false, false, false), vec![]); 1356 | eq((true, false, false, false), vec![(false, false, false, false)]); 1357 | eq( 1358 | (true, true, false, false), 1359 | vec![(false, true, false, false), (true, false, false, false)], 1360 | ); 1361 | } 1362 | 1363 | #[test] 1364 | fn ints() { 1365 | // TODO: Test overflow? 1366 | eq(5isize, vec![0, 3, 4]); 1367 | eq(-5isize, vec![5, 0, -3, -4]); 1368 | eq(0isize, vec![]); 1369 | } 1370 | 1371 | #[test] 1372 | fn ints8() { 1373 | eq(5i8, vec![0, 3, 4]); 1374 | eq(-5i8, vec![5, 0, -3, -4]); 1375 | eq(0i8, vec![]); 1376 | } 1377 | 1378 | #[test] 1379 | fn ints16() { 1380 | eq(5i16, vec![0, 3, 4]); 1381 | eq(-5i16, vec![5, 0, -3, -4]); 1382 | eq(0i16, vec![]); 1383 | } 1384 | 1385 | #[test] 1386 | fn ints32() { 1387 | eq(5i32, vec![0, 3, 4]); 1388 | eq(-5i32, vec![5, 0, -3, -4]); 1389 | eq(0i32, vec![]); 1390 | } 1391 | 1392 | #[test] 1393 | fn ints64() { 1394 | eq(5i64, vec![0, 3, 4]); 1395 | eq(-5i64, vec![5, 0, -3, -4]); 1396 | eq(0i64, vec![]); 1397 | } 1398 | 1399 | #[test] 1400 | fn ints128() { 1401 | eq(5i128, vec![0, 3, 4]); 1402 | eq(-5i128, vec![5, 0, -3, -4]); 1403 | eq(0i128, vec![]); 1404 | } 1405 | 1406 | #[test] 1407 | fn uints() { 1408 | eq(5usize, vec![0, 3, 4]); 1409 | eq(0usize, vec![]); 1410 | } 1411 | 1412 | #[test] 1413 | fn uints8() { 1414 | eq(5u8, vec![0, 3, 4]); 1415 | eq(0u8, vec![]); 1416 | } 1417 | 1418 | #[test] 1419 | fn uints16() { 1420 | eq(5u16, vec![0, 3, 4]); 1421 | eq(0u16, vec![]); 1422 | } 1423 | 1424 | #[test] 1425 | fn uints32() { 1426 | eq(5u32, vec![0, 3, 4]); 1427 | eq(0u32, vec![]); 1428 | } 1429 | 1430 | #[test] 1431 | fn uints64() { 1432 | eq(5u64, vec![0, 3, 4]); 1433 | eq(0u64, vec![]); 1434 | } 1435 | 1436 | #[test] 1437 | fn uints128() { 1438 | eq(5u128, vec![0, 3, 4]); 1439 | eq(0u128, vec![]); 1440 | } 1441 | 1442 | macro_rules! define_float_eq { 1443 | ($ty:ty) => { 1444 | fn eq(s: $ty, v: Vec<$ty>) { 1445 | let shrunk: Vec<$ty> = s.shrink().collect(); 1446 | for n in v { 1447 | let found = shrunk.iter().any(|&i| i == n); 1448 | if !found { 1449 | panic!( 1450 | "Element {:?} was not found \ 1451 | in shrink results {:?}", 1452 | n, shrunk 1453 | ); 1454 | } 1455 | } 1456 | } 1457 | }; 1458 | } 1459 | 1460 | #[test] 1461 | fn floats32() { 1462 | define_float_eq!(f32); 1463 | 1464 | eq(0.0, vec![]); 1465 | eq(-0.0, vec![]); 1466 | eq(1.0, vec![0.0]); 1467 | eq(2.0, vec![0.0, 1.0]); 1468 | eq(-2.0, vec![0.0, 2.0, -1.0]); 1469 | eq(1.5, vec![0.0]); 1470 | } 1471 | 1472 | #[test] 1473 | fn floats64() { 1474 | define_float_eq!(f64); 1475 | 1476 | eq(0.0, vec![]); 1477 | eq(-0.0, vec![]); 1478 | eq(1.0, vec![0.0]); 1479 | eq(2.0, vec![0.0, 1.0]); 1480 | eq(-2.0, vec![0.0, 2.0, -1.0]); 1481 | eq(1.5, vec![0.0]); 1482 | } 1483 | 1484 | #[test] 1485 | fn wrapping_ints32() { 1486 | eq(Wrapping(5i32), vec![Wrapping(0), Wrapping(3), Wrapping(4)]); 1487 | eq( 1488 | Wrapping(-5i32), 1489 | vec![Wrapping(5), Wrapping(0), Wrapping(-3), Wrapping(-4)], 1490 | ); 1491 | eq(Wrapping(0i32), vec![]); 1492 | } 1493 | 1494 | #[test] 1495 | fn vecs() { 1496 | eq( 1497 | { 1498 | let it: Vec = vec![]; 1499 | it 1500 | }, 1501 | vec![], 1502 | ); 1503 | eq( 1504 | { 1505 | let it: Vec> = vec![vec![]]; 1506 | it 1507 | }, 1508 | vec![vec![]], 1509 | ); 1510 | eq(vec![1isize], vec![vec![], vec![0]]); 1511 | eq(vec![11isize], vec![vec![], vec![0], vec![6], vec![9], vec![10]]); 1512 | eq( 1513 | vec![3isize, 5], 1514 | vec![ 1515 | vec![], 1516 | vec![5], 1517 | vec![3], 1518 | vec![0, 5], 1519 | vec![2, 5], 1520 | vec![3, 0], 1521 | vec![3, 3], 1522 | vec![3, 4], 1523 | ], 1524 | ); 1525 | } 1526 | 1527 | macro_rules! map_tests { 1528 | ($name:ident, $ctor:expr) => { 1529 | #[test] 1530 | fn $name() { 1531 | ordered_eq($ctor, vec![]); 1532 | 1533 | { 1534 | let mut map = $ctor; 1535 | map.insert(1usize, 1isize); 1536 | 1537 | let shrinks = vec![ 1538 | $ctor, 1539 | { 1540 | let mut m = $ctor; 1541 | m.insert(0, 1); 1542 | m 1543 | }, 1544 | { 1545 | let mut m = $ctor; 1546 | m.insert(1, 0); 1547 | m 1548 | }, 1549 | ]; 1550 | 1551 | ordered_eq(map, shrinks); 1552 | } 1553 | } 1554 | }; 1555 | } 1556 | 1557 | map_tests!(btreemap, BTreeMap::::new()); 1558 | map_tests!(hashmap, HashMap::::new()); 1559 | 1560 | macro_rules! list_tests { 1561 | ($name:ident, $ctor:expr, $push:ident) => { 1562 | #[test] 1563 | fn $name() { 1564 | ordered_eq($ctor, vec![]); 1565 | 1566 | { 1567 | let mut list = $ctor; 1568 | list.$push(2usize); 1569 | 1570 | let shrinks = vec![ 1571 | $ctor, 1572 | { 1573 | let mut m = $ctor; 1574 | m.$push(0); 1575 | m 1576 | }, 1577 | { 1578 | let mut m = $ctor; 1579 | m.$push(1); 1580 | m 1581 | }, 1582 | ]; 1583 | 1584 | ordered_eq(list, shrinks); 1585 | } 1586 | } 1587 | }; 1588 | } 1589 | 1590 | list_tests!(btreesets, BTreeSet::::new(), insert); 1591 | list_tests!(hashsets, HashSet::::new(), insert); 1592 | list_tests!(linkedlists, LinkedList::::new(), push_back); 1593 | list_tests!(vecdeques, VecDeque::::new(), push_back); 1594 | 1595 | #[test] 1596 | fn binaryheaps() { 1597 | ordered_eq( 1598 | BinaryHeap::::new().into_iter().collect::>(), 1599 | vec![], 1600 | ); 1601 | 1602 | { 1603 | let mut heap = BinaryHeap::::new(); 1604 | heap.push(2usize); 1605 | 1606 | let shrinks = vec![vec![], vec![0], vec![1]]; 1607 | 1608 | ordered_eq(heap.into_iter().collect::>(), shrinks); 1609 | } 1610 | } 1611 | 1612 | #[test] 1613 | fn chars() { 1614 | eq('\x00', vec![]); 1615 | } 1616 | 1617 | // All this jazz is for testing set equality on the results of a shrinker. 1618 | fn eq(s: A, v: Vec) { 1619 | let (left, right) = (shrunk(s), set(v)); 1620 | assert_eq!(left, right); 1621 | } 1622 | fn shrunk(s: A) -> HashSet { 1623 | set(s.shrink()) 1624 | } 1625 | fn set>(xs: I) -> HashSet { 1626 | xs.into_iter().collect() 1627 | } 1628 | 1629 | fn ordered_eq(s: A, v: Vec) { 1630 | let (left, right) = (s.shrink().collect::>(), v); 1631 | assert_eq!(left, right); 1632 | } 1633 | 1634 | #[test] 1635 | fn bounds() { 1636 | use std::ops::Bound::*; 1637 | for i in -5..=5 { 1638 | ordered_eq(Included(i), i.shrink().map(Included).collect()); 1639 | ordered_eq(Excluded(i), i.shrink().map(Excluded).collect()); 1640 | } 1641 | eq(Unbounded::, vec![]); 1642 | } 1643 | 1644 | #[allow(clippy::reversed_empty_ranges)] 1645 | #[test] 1646 | fn ranges() { 1647 | ordered_eq(0..0, vec![]); 1648 | ordered_eq(1..1, vec![0..1, 1..0]); 1649 | ordered_eq(3..5, vec![0..5, 2..5, 3..0, 3..3, 3..4]); 1650 | ordered_eq(5..3, vec![0..3, 3..3, 4..3, 5..0, 5..2]); 1651 | ordered_eq(3.., vec![0.., 2..]); 1652 | ordered_eq(..3, vec![..0, ..2]); 1653 | ordered_eq(.., vec![]); 1654 | ordered_eq(3..=5, vec![0..=5, 2..=5, 3..=0, 3..=3, 3..=4]); 1655 | ordered_eq(..=3, vec![..=0, ..=2]); 1656 | } 1657 | 1658 | #[test] 1659 | fn pathbuf() { 1660 | ordered_eq( 1661 | PathBuf::from("/home/foo//.././bar"), 1662 | vec![ 1663 | PathBuf::from("/home/foo//.."), 1664 | PathBuf::from("/home/foo/../bar"), 1665 | ], 1666 | ); 1667 | } 1668 | } 1669 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This crate is a port of 3 | [Haskell's QuickCheck](https://hackage.haskell.org/package/QuickCheck). 4 | 5 | QuickCheck is a library for random testing of program properties. The 6 | programmer provides a specification of the program, in the form of properties 7 | which functions should satisfy, and QuickCheck then tests that the properties 8 | hold in a large number of randomly generated cases. 9 | 10 | For detailed examples, please see the 11 | [README](https://github.com/BurntSushi/quickcheck). 12 | 13 | # Compatibility 14 | 15 | In general, this crate considers the `Arbitrary` implementations provided as 16 | implementation details. Strategies may or may not change over time, which may 17 | cause new test failures, presumably due to the discovery of new bugs due to a 18 | new kind of witness being generated. These sorts of changes may happen in 19 | semver compatible releases. 20 | */ 21 | 22 | pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; 23 | pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; 24 | 25 | /// A macro for writing quickcheck tests. 26 | /// 27 | /// This macro takes as input one or more property functions to test, and 28 | /// produces a proper `#[test]` function for each property. If the property 29 | /// fails, the behavior is as if `quickcheck` were called on the property 30 | /// (i.e., it panics and fails the test). 31 | /// 32 | /// Note that this macro doesn't support `mut` or patterns in parameters. 33 | /// 34 | /// # Example 35 | /// 36 | /// ```rust 37 | /// # #[macro_use] extern crate quickcheck; fn main() { 38 | /// quickcheck! { 39 | /// fn prop_reverse_reverse(xs: Vec) -> bool { 40 | /// let rev: Vec<_> = xs.clone().into_iter().rev().collect(); 41 | /// let revrev: Vec<_> = rev.into_iter().rev().collect(); 42 | /// xs == revrev 43 | /// } 44 | /// }; 45 | /// # } 46 | /// ``` 47 | #[macro_export] 48 | macro_rules! quickcheck { 49 | (@as_items $($i:item)*) => ($($i)*); 50 | { 51 | $( 52 | $(#[$m:meta])* 53 | fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { 54 | $($code:tt)* 55 | } 56 | )* 57 | } => ( 58 | $crate::quickcheck! { 59 | @as_items 60 | $( 61 | #[test] 62 | $(#[$m])* 63 | fn $fn_name() { 64 | fn prop($($arg_name: $arg_ty),*) -> $ret { 65 | $($code)* 66 | } 67 | $crate::quickcheck(prop as fn($($arg_ty),*) -> $ret); 68 | } 69 | )* 70 | } 71 | ) 72 | } 73 | 74 | #[cfg(feature = "use_logging")] 75 | fn env_logger_init() -> Result<(), log::SetLoggerError> { 76 | env_logger::try_init() 77 | } 78 | #[cfg(feature = "use_logging")] 79 | macro_rules! info { 80 | ($($tt:tt)*) => { 81 | log::info!($($tt)*) 82 | }; 83 | } 84 | 85 | #[cfg(not(feature = "use_logging"))] 86 | fn env_logger_init() {} 87 | #[cfg(not(feature = "use_logging"))] 88 | macro_rules! info { 89 | ($($_ignore:tt)*) => { 90 | () 91 | }; 92 | } 93 | 94 | mod arbitrary; 95 | mod tester; 96 | 97 | #[cfg(test)] 98 | mod tests; 99 | -------------------------------------------------------------------------------- /src/tester.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::env; 3 | use std::fmt::Debug; 4 | use std::panic; 5 | 6 | use crate::{ 7 | tester::Status::{Discard, Fail, Pass}, 8 | Arbitrary, Gen, 9 | }; 10 | 11 | /// The main `QuickCheck` type for setting configuration and running 12 | /// `QuickCheck`. 13 | pub struct QuickCheck { 14 | tests: u64, 15 | max_tests: u64, 16 | min_tests_passed: u64, 17 | rng: Gen, 18 | } 19 | 20 | fn qc_tests() -> u64 { 21 | let default = 100; 22 | match env::var("QUICKCHECK_TESTS") { 23 | Ok(val) => val.parse().unwrap_or(default), 24 | Err(_) => default, 25 | } 26 | } 27 | 28 | fn qc_max_tests() -> u64 { 29 | let default = 10_000; 30 | match env::var("QUICKCHECK_MAX_TESTS") { 31 | Ok(val) => val.parse().unwrap_or(default), 32 | Err(_) => default, 33 | } 34 | } 35 | 36 | fn qc_gen_size() -> usize { 37 | let default = 100; 38 | match env::var("QUICKCHECK_GENERATOR_SIZE") { 39 | Ok(val) => val.parse().unwrap_or(default), 40 | Err(_) => default, 41 | } 42 | } 43 | 44 | fn qc_min_tests_passed() -> u64 { 45 | let default = 0; 46 | match env::var("QUICKCHECK_MIN_TESTS_PASSED") { 47 | Ok(val) => val.parse().unwrap_or(default), 48 | Err(_) => default, 49 | } 50 | } 51 | 52 | impl Default for QuickCheck { 53 | fn default() -> Self { 54 | Self::new() 55 | } 56 | } 57 | 58 | impl QuickCheck { 59 | /// Creates a new `QuickCheck` value. 60 | /// 61 | /// This can be used to run `QuickCheck` on things that implement 62 | /// `Testable`. You may also adjust the configuration, such as the 63 | /// number of tests to run. 64 | /// 65 | /// By default, the maximum number of passed tests is set to `100`, the max 66 | /// number of overall tests is set to `10000` and the generator is created 67 | /// with a size of `100`. 68 | pub fn new() -> QuickCheck { 69 | let rng = Gen::new(qc_gen_size()); 70 | let tests = qc_tests(); 71 | let max_tests = cmp::max(tests, qc_max_tests()); 72 | let min_tests_passed = qc_min_tests_passed(); 73 | 74 | QuickCheck { tests, max_tests, min_tests_passed, rng } 75 | } 76 | 77 | /// Set the random number generator to be used by `QuickCheck`. 78 | pub fn set_rng(self, rng: Gen) -> QuickCheck { 79 | QuickCheck { rng, ..self } 80 | } 81 | 82 | #[deprecated(since = "1.1.0", note = "use `set_rng` instead")] 83 | pub fn r#gen(self, rng: Gen) -> QuickCheck { 84 | self.set_rng(rng) 85 | } 86 | 87 | /// Set the number of tests to run. 88 | /// 89 | /// This actually refers to the maximum number of *passed* tests that 90 | /// can occur. Namely, if a test causes a failure, future testing on that 91 | /// property stops. Additionally, if tests are discarded, there may be 92 | /// fewer than `tests` passed. 93 | pub fn tests(mut self, tests: u64) -> QuickCheck { 94 | self.tests = tests; 95 | self 96 | } 97 | 98 | /// Set the maximum number of tests to run. 99 | /// 100 | /// The number of invocations of a property will never exceed this number. 101 | /// This is necessary to cap the number of tests because `QuickCheck` 102 | /// properties can discard tests. 103 | pub fn max_tests(mut self, max_tests: u64) -> QuickCheck { 104 | self.max_tests = max_tests; 105 | self 106 | } 107 | 108 | /// Set the minimum number of tests that needs to pass. 109 | /// 110 | /// This actually refers to the minimum number of *valid* *passed* tests 111 | /// that needs to pass for the property to be considered successful. 112 | pub fn min_tests_passed(mut self, min_tests_passed: u64) -> QuickCheck { 113 | self.min_tests_passed = min_tests_passed; 114 | self 115 | } 116 | 117 | /// Tests a property and returns the result. 118 | /// 119 | /// The result returned is either the number of tests passed or a witness 120 | /// of failure. 121 | /// 122 | /// (If you're using Rust's unit testing infrastructure, then you'll 123 | /// want to use the `quickcheck` method, which will `panic!` on failure.) 124 | pub fn quicktest(&mut self, f: A) -> Result 125 | where 126 | A: Testable, 127 | { 128 | let mut n_tests_passed = 0; 129 | for _ in 0..self.max_tests { 130 | if n_tests_passed >= self.tests { 131 | break; 132 | } 133 | match f.result(&mut self.rng) { 134 | TestResult { status: Pass, .. } => n_tests_passed += 1, 135 | TestResult { status: Discard, .. } => continue, 136 | r @ TestResult { status: Fail, .. } => return Err(r), 137 | } 138 | } 139 | Ok(n_tests_passed) 140 | } 141 | 142 | /// Tests a property and calls `panic!` on failure. 143 | /// 144 | /// The `panic!` message will include a (hopefully) minimal witness of 145 | /// failure. 146 | /// 147 | /// It is appropriate to use this method with Rust's unit testing 148 | /// infrastructure. 149 | /// 150 | /// Note that if the environment variable `RUST_LOG` is set to enable 151 | /// `info` level log messages for the `quickcheck` crate, then this will 152 | /// include output on how many `QuickCheck` tests were passed. 153 | /// 154 | /// # Example 155 | /// 156 | /// ```rust 157 | /// use quickcheck::QuickCheck; 158 | /// 159 | /// fn prop_reverse_reverse() { 160 | /// fn revrev(xs: Vec) -> bool { 161 | /// let rev: Vec<_> = xs.clone().into_iter().rev().collect(); 162 | /// let revrev: Vec<_> = rev.into_iter().rev().collect(); 163 | /// xs == revrev 164 | /// } 165 | /// QuickCheck::new().quickcheck(revrev as fn(Vec) -> bool); 166 | /// } 167 | /// ``` 168 | pub fn quickcheck(&mut self, f: A) 169 | where 170 | A: Testable, 171 | { 172 | // Ignore log init failures, implying it has already been done. 173 | let _ = crate::env_logger_init(); 174 | 175 | let n_tests_passed = match self.quicktest(f) { 176 | Ok(n_tests_passed) => n_tests_passed, 177 | Err(result) => panic!("{}", result.failed_msg()), 178 | }; 179 | 180 | if n_tests_passed >= self.min_tests_passed { 181 | info!("(Passed {} QuickCheck tests.)", n_tests_passed); 182 | } else { 183 | panic!( 184 | "(Unable to generate enough tests, {} not discarded.)", 185 | n_tests_passed 186 | ); 187 | } 188 | } 189 | } 190 | 191 | /// Convenience function for running `QuickCheck`. 192 | /// 193 | /// This is an alias for `QuickCheck::new().quickcheck(f)`. 194 | pub fn quickcheck(f: A) { 195 | QuickCheck::new().quickcheck(f) 196 | } 197 | 198 | /// Describes the status of a single instance of a test. 199 | /// 200 | /// All testable things must be capable of producing a `TestResult`. 201 | #[derive(Clone, Debug, PartialEq)] 202 | pub struct TestResult { 203 | status: Status, 204 | arguments: Option>, 205 | err: Option, 206 | } 207 | 208 | /// Whether a test has passed, failed or been discarded. 209 | #[derive(Clone, Debug, PartialEq)] 210 | enum Status { 211 | Pass, 212 | Fail, 213 | Discard, 214 | } 215 | 216 | impl TestResult { 217 | /// Produces a test result that indicates the current test has passed. 218 | pub fn passed() -> TestResult { 219 | TestResult::from_bool(true) 220 | } 221 | 222 | /// Produces a test result that indicates the current test has failed. 223 | pub fn failed() -> TestResult { 224 | TestResult::from_bool(false) 225 | } 226 | 227 | /// Produces a test result that indicates failure from a runtime error. 228 | pub fn error>(msg: S) -> TestResult { 229 | let mut r = TestResult::from_bool(false); 230 | r.err = Some(msg.into()); 231 | r 232 | } 233 | 234 | /// Produces a test result that instructs `quickcheck` to ignore it. 235 | /// This is useful for restricting the domain of your properties. 236 | /// When a test is discarded, `quickcheck` will replace it with a 237 | /// fresh one (up to a certain limit). 238 | pub fn discard() -> TestResult { 239 | TestResult { status: Discard, arguments: None, err: None } 240 | } 241 | 242 | /// Converts a `bool` to a `TestResult`. A `true` value indicates that 243 | /// the test has passed and a `false` value indicates that the test 244 | /// has failed. 245 | pub fn from_bool(b: bool) -> TestResult { 246 | TestResult { 247 | status: if b { Pass } else { Fail }, 248 | arguments: None, 249 | err: None, 250 | } 251 | } 252 | 253 | /// Tests if a "procedure" fails when executed. The test passes only if 254 | /// `f` generates a task failure during its execution. 255 | pub fn must_fail(f: F) -> TestResult 256 | where 257 | F: FnOnce() -> T, 258 | F: 'static, 259 | T: 'static, 260 | { 261 | let f = panic::AssertUnwindSafe(f); 262 | TestResult::from_bool(panic::catch_unwind(f).is_err()) 263 | } 264 | 265 | /// Returns `true` if and only if this test result describes a failing 266 | /// test. 267 | pub fn is_failure(&self) -> bool { 268 | match self.status { 269 | Fail => true, 270 | Pass | Discard => false, 271 | } 272 | } 273 | 274 | /// Returns `true` if and only if this test result describes a failing 275 | /// test as a result of a run time error. 276 | pub fn is_error(&self) -> bool { 277 | self.is_failure() && self.err.is_some() 278 | } 279 | 280 | fn failed_msg(&self) -> String { 281 | let arguments_msg = match self.arguments { 282 | None => "No Arguments Provided".to_owned(), 283 | Some(ref args) => format!("Arguments: ({})", args.join(", ")), 284 | }; 285 | match self.err { 286 | None => format!("[quickcheck] TEST FAILED. {arguments_msg}"), 287 | Some(ref err) => format!( 288 | "[quickcheck] TEST FAILED (runtime error). {arguments_msg}\nError: {err}" 289 | ), 290 | } 291 | } 292 | } 293 | 294 | impl From for TestResult { 295 | /// A shorter way of producing a `TestResult` from a `bool`. 296 | /// 297 | /// # Example 298 | /// 299 | /// ```rust 300 | /// # use quickcheck::TestResult; 301 | /// let result: TestResult = (2 > 1).into(); 302 | /// assert_eq!(result, TestResult::passed()); 303 | /// ``` 304 | fn from(b: bool) -> TestResult { 305 | TestResult::from_bool(b) 306 | } 307 | } 308 | 309 | /// `Testable` describes types (e.g., a function) whose values can be 310 | /// tested. 311 | /// 312 | /// Anything that can be tested must be capable of producing a `TestResult` 313 | /// given a random number generator. This is trivial for types like `bool`, 314 | /// which are just converted to either a passing or failing test result. 315 | /// 316 | /// For functions, an implementation must generate random arguments 317 | /// and potentially shrink those arguments if they produce a failure. 318 | /// 319 | /// It's unlikely that you'll have to implement this trait yourself. 320 | pub trait Testable: 'static { 321 | fn result(&self, _: &mut Gen) -> TestResult; 322 | } 323 | 324 | impl Testable for bool { 325 | fn result(&self, _: &mut Gen) -> TestResult { 326 | TestResult::from_bool(*self) 327 | } 328 | } 329 | 330 | impl Testable for () { 331 | fn result(&self, _: &mut Gen) -> TestResult { 332 | TestResult::passed() 333 | } 334 | } 335 | 336 | impl Testable for TestResult { 337 | fn result(&self, _: &mut Gen) -> TestResult { 338 | self.clone() 339 | } 340 | } 341 | 342 | impl Testable for Result 343 | where 344 | A: Testable, 345 | E: Debug + 'static, 346 | { 347 | fn result(&self, g: &mut Gen) -> TestResult { 348 | match *self { 349 | Ok(ref r) => r.result(g), 350 | Err(ref err) => TestResult::error(format!("{err:?}")), 351 | } 352 | } 353 | } 354 | 355 | /// Return a vector of the debug formatting of each item in `args` 356 | fn debug_reprs(args: &[&dyn Debug]) -> Vec { 357 | args.iter().map(|x| format!("{x:?}")).collect() 358 | } 359 | 360 | macro_rules! testable_fn { 361 | ($($name: ident),*) => { 362 | 363 | impl Testable for fn($($name),*) -> T { 365 | #[allow(non_snake_case)] 366 | fn result(&self, g: &mut Gen) -> TestResult { 367 | fn shrink_failure( 368 | g: &mut Gen, 369 | self_: fn($($name),*) -> T, 370 | a: ($($name,)*), 371 | ) -> Option { 372 | for t in a.shrink() { 373 | let ($($name,)*) = t.clone(); 374 | let mut r_new = safe(move || {self_($($name),*)}).result(g); 375 | if r_new.is_failure() { 376 | { 377 | let ($(ref $name,)*) : ($($name,)*) = t; 378 | r_new.arguments = Some(debug_reprs(&[$($name),*])); 379 | } 380 | 381 | // The shrunk value *does* witness a failure, so keep 382 | // trying to shrink it. 383 | let shrunk = shrink_failure(g, self_, t); 384 | 385 | // If we couldn't witness a failure on any shrunk value, 386 | // then return the failure we already have. 387 | return Some(shrunk.unwrap_or(r_new)) 388 | } 389 | } 390 | None 391 | } 392 | 393 | let self_ = *self; 394 | let a: ($($name,)*) = Arbitrary::arbitrary(g); 395 | let ( $($name,)* ) = a.clone(); 396 | let r = safe(move || {self_($($name),*)}).result(g); 397 | match r.status { 398 | Pass|Discard => r, 399 | Fail => { 400 | shrink_failure(g, self_, a).unwrap_or(r) 401 | } 402 | } 403 | } 404 | }}} 405 | 406 | testable_fn!(); 407 | testable_fn!(A); 408 | testable_fn!(A, B); 409 | testable_fn!(A, B, C); 410 | testable_fn!(A, B, C, D); 411 | testable_fn!(A, B, C, D, E); 412 | testable_fn!(A, B, C, D, E, F); 413 | testable_fn!(A, B, C, D, E, F, G); 414 | testable_fn!(A, B, C, D, E, F, G, H); 415 | 416 | fn safe(fun: F) -> Result 417 | where 418 | F: FnOnce() -> T, 419 | F: 'static, 420 | T: 'static, 421 | { 422 | panic::catch_unwind(panic::AssertUnwindSafe(fun)).map_err(|any_err| { 423 | // Extract common types of panic payload: 424 | // panic and assert produce &str or String 425 | if let Some(&s) = any_err.downcast_ref::<&str>() { 426 | s.to_owned() 427 | } else if let Some(s) = any_err.downcast_ref::() { 428 | s.to_owned() 429 | } else { 430 | "UNABLE TO SHOW RESULT OF PANIC.".to_owned() 431 | } 432 | }) 433 | } 434 | 435 | #[cfg(test)] 436 | mod test { 437 | use crate::{Gen, QuickCheck}; 438 | 439 | #[test] 440 | fn shrinking_regression_issue_126() { 441 | fn thetest(vals: Vec) -> bool { 442 | vals.iter().filter(|&v| *v).count() < 2 443 | } 444 | let failing_case = QuickCheck::new() 445 | .quicktest(thetest as fn(vals: Vec) -> bool) 446 | .unwrap_err(); 447 | let expected_argument = format!("{:?}", [true, true]); 448 | assert_eq!(failing_case.arguments, Some(vec![expected_argument])); 449 | } 450 | 451 | #[test] 452 | fn size_for_small_types_issue_143() { 453 | fn t(_: i8) -> bool { 454 | true 455 | } 456 | QuickCheck::new() 457 | .set_rng(Gen::new(129)) 458 | .quickcheck(t as fn(i8) -> bool); 459 | } 460 | 461 | #[test] 462 | fn regression_signed_shrinker_panic() { 463 | fn foo_can_shrink(v: i8) -> bool { 464 | let _ = crate::Arbitrary::shrink(&v).take(100).count(); 465 | true 466 | } 467 | crate::quickcheck(foo_can_shrink as fn(i8) -> bool); 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::DefaultHasher; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::ffi::CString; 4 | use std::hash::BuildHasherDefault; 5 | use std::path::PathBuf; 6 | 7 | use super::{quickcheck, Gen, QuickCheck, TestResult}; 8 | 9 | #[test] 10 | fn prop_oob() { 11 | fn prop() -> bool { 12 | let zero: Vec = vec![]; 13 | zero[0] 14 | } 15 | if let Ok(n) = QuickCheck::new().quicktest(prop as fn() -> bool) { 16 | panic!( 17 | "prop_oob should fail with a runtime error \ 18 | but instead it passed {} tests.", 19 | n 20 | ); 21 | } 22 | } 23 | 24 | #[test] 25 | fn prop_reverse_reverse() { 26 | fn prop(xs: Vec) -> bool { 27 | let rev: Vec<_> = xs.clone().into_iter().rev().collect(); 28 | let revrev: Vec<_> = rev.into_iter().rev().collect(); 29 | xs == revrev 30 | } 31 | quickcheck(prop as fn(Vec) -> bool); 32 | } 33 | 34 | quickcheck! { 35 | fn prop_reverse_reverse_macro(xs: Vec) -> bool { 36 | let rev: Vec<_> = xs.clone().into_iter().rev().collect(); 37 | let revrev: Vec<_> = rev.into_iter().rev().collect(); 38 | xs == revrev 39 | } 40 | 41 | #[should_panic] 42 | fn prop_macro_panic(_x: u32) -> bool { 43 | assert!(false); 44 | false 45 | } 46 | } 47 | 48 | #[test] 49 | fn reverse_single() { 50 | fn prop(xs: Vec) -> TestResult { 51 | if xs.len() != 1 { 52 | TestResult::discard() 53 | } else { 54 | TestResult::from_bool( 55 | xs == xs.clone().into_iter().rev().collect::>(), 56 | ) 57 | } 58 | } 59 | quickcheck(prop as fn(Vec) -> TestResult); 60 | } 61 | 62 | #[test] 63 | fn reverse_app() { 64 | fn prop(xs: Vec, ys: Vec) -> bool { 65 | let mut app = xs.clone(); 66 | app.extend(ys.iter().copied()); 67 | let app_rev: Vec = app.into_iter().rev().collect(); 68 | 69 | let rxs: Vec = xs.into_iter().rev().collect(); 70 | let mut rev_app = ys.into_iter().rev().collect::>(); 71 | rev_app.extend(rxs); 72 | 73 | app_rev == rev_app 74 | } 75 | quickcheck(prop as fn(Vec, Vec) -> bool); 76 | } 77 | 78 | #[test] 79 | fn max() { 80 | fn prop(x: isize, y: isize) -> TestResult { 81 | if x > y { 82 | TestResult::discard() 83 | } else { 84 | TestResult::from_bool(::std::cmp::max(x, y) == y) 85 | } 86 | } 87 | quickcheck(prop as fn(isize, isize) -> TestResult); 88 | } 89 | 90 | #[test] 91 | fn sort() { 92 | fn prop(mut xs: Vec) -> bool { 93 | xs.sort_unstable(); 94 | for i in xs.windows(2) { 95 | if i[0] > i[1] { 96 | return false; 97 | } 98 | } 99 | true 100 | } 101 | quickcheck(prop as fn(Vec) -> bool); 102 | } 103 | 104 | fn sieve(n: usize) -> Vec { 105 | if n <= 1 { 106 | return vec![]; 107 | } 108 | 109 | let mut marked = vec![false; n + 1]; 110 | marked[0] = true; 111 | marked[1] = true; 112 | marked[2] = true; 113 | for p in 2..n { 114 | for i in (2 * p..n).filter(|&n| n % p == 0) { 115 | marked[i] = true; 116 | } 117 | } 118 | marked 119 | .iter() 120 | .enumerate() 121 | .filter_map(|(i, &m)| if m { None } else { Some(i) }) 122 | .collect() 123 | } 124 | 125 | fn is_prime(n: usize) -> bool { 126 | n != 0 && n != 1 && (2..).take_while(|i| i * i <= n).all(|i| n % i != 0) 127 | } 128 | 129 | #[test] 130 | #[should_panic] 131 | fn sieve_not_prime() { 132 | fn prop_all_prime(n: u8) -> bool { 133 | sieve(n as usize).into_iter().all(is_prime) 134 | } 135 | quickcheck(prop_all_prime as fn(u8) -> bool); 136 | } 137 | 138 | #[test] 139 | #[should_panic] 140 | fn sieve_not_all_primes() { 141 | fn prop_prime_iff_in_the_sieve(n: u8) -> bool { 142 | let n = n as usize; 143 | sieve(n) == (0..=n).filter(|&i| is_prime(i)).collect::>() 144 | } 145 | quickcheck(prop_prime_iff_in_the_sieve as fn(u8) -> bool); 146 | } 147 | 148 | #[test] 149 | fn testable_result() { 150 | fn result() -> Result { 151 | Ok(true) 152 | } 153 | quickcheck(result as fn() -> Result); 154 | } 155 | 156 | #[test] 157 | #[should_panic] 158 | fn testable_result_err() { 159 | quickcheck(Err:: as fn(i32) -> Result); 160 | } 161 | 162 | #[test] 163 | fn testable_unit() { 164 | fn do_nothing() {} 165 | quickcheck(do_nothing as fn()); 166 | } 167 | 168 | #[test] 169 | fn testable_unit_panic() { 170 | fn panic() { 171 | panic!(); 172 | } 173 | assert!(QuickCheck::new().quicktest(panic as fn()).is_err()); 174 | } 175 | 176 | #[test] 177 | fn regression_issue_83() { 178 | fn prop(_: u8) -> bool { 179 | true 180 | } 181 | QuickCheck::new() 182 | .set_rng(Gen::new(1024)) 183 | .quickcheck(prop as fn(u8) -> bool); 184 | } 185 | 186 | #[test] 187 | fn regression_issue_83_signed() { 188 | fn prop(_: i8) -> bool { 189 | true 190 | } 191 | QuickCheck::new() 192 | .set_rng(Gen::new(1024)) 193 | .quickcheck(prop as fn(i8) -> bool); 194 | } 195 | 196 | // Test that we can show the message after panic 197 | #[test] 198 | #[should_panic(expected = "foo")] 199 | fn panic_msg_1() { 200 | fn prop() -> bool { 201 | panic!("foo"); 202 | } 203 | quickcheck(prop as fn() -> bool); 204 | } 205 | 206 | #[test] 207 | #[should_panic(expected = "foo")] 208 | fn panic_msg_2() { 209 | fn prop() -> bool { 210 | assert!("foo" == "bar"); 211 | true 212 | } 213 | quickcheck(prop as fn() -> bool); 214 | } 215 | 216 | #[test] 217 | #[should_panic(expected = "foo")] 218 | fn panic_msg_3() { 219 | fn prop() -> bool { 220 | assert_eq!("foo", "bar"); 221 | true 222 | } 223 | quickcheck(prop as fn() -> bool); 224 | } 225 | 226 | #[test] 227 | #[should_panic] 228 | fn regression_issue_107_hang() { 229 | fn prop(a: Vec) -> bool { 230 | a.contains(&1) 231 | } 232 | quickcheck(prop as fn(_) -> bool); 233 | } 234 | 235 | #[test] 236 | #[should_panic( 237 | expected = "(Unable to generate enough tests, 0 not discarded.)" 238 | )] 239 | fn all_tests_discarded_min_tests_passed_set() { 240 | fn prop_discarded(_: u8) -> TestResult { 241 | TestResult::discard() 242 | } 243 | 244 | QuickCheck::new() 245 | .tests(16) 246 | .min_tests_passed(8) 247 | .quickcheck(prop_discarded as fn(u8) -> TestResult); 248 | } 249 | 250 | #[test] 251 | fn all_tests_discarded_min_tests_passed_missing() { 252 | fn prop_discarded(_: u8) -> TestResult { 253 | TestResult::discard() 254 | } 255 | 256 | QuickCheck::new().quickcheck(prop_discarded as fn(u8) -> TestResult); 257 | } 258 | 259 | quickcheck! { 260 | /// The following is a very simplistic test, which only verifies 261 | /// that our PathBuf::arbitrary does not panic. Still, that's 262 | /// something! :) 263 | fn pathbuf(_p: PathBuf) -> bool { 264 | true 265 | } 266 | 267 | fn basic_hashset(_set: HashSet) -> bool { 268 | true 269 | } 270 | 271 | fn basic_hashmap(_map: HashMap) -> bool { 272 | true 273 | } 274 | 275 | fn substitute_hashset( 276 | _set: HashSet> 277 | ) -> bool { 278 | true 279 | } 280 | 281 | fn substitute_hashmap( 282 | _map: HashMap> 283 | ) -> bool { 284 | true 285 | } 286 | 287 | fn cstring(_p: CString) -> bool { 288 | true 289 | } 290 | } 291 | --------------------------------------------------------------------------------