From bf2d5b7874da2d0170e4c4cbb15cbaca9f4834e3 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 23 May 2023 12:44:44 -0400 Subject: [PATCH 01/23] ci: use latest OS versions --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b41f2bc..41d7b2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,25 +22,25 @@ jobs: - win-gnu include: - build: pinned - os: ubuntu-18.04 + os: ubuntu-latest rust: 1.46.0 - build: stable - os: ubuntu-18.04 + os: ubuntu-latest rust: stable - build: beta - os: ubuntu-18.04 + os: ubuntu-latest rust: beta - build: nightly - os: ubuntu-18.04 + os: ubuntu-latest rust: nightly - build: macos os: macos-latest rust: stable - build: win-msvc - os: windows-2019 + os: windows-latest rust: stable - build: win-gnu - os: windows-2019 + os: windows-latest rust: stable-x86_64-gnu steps: - name: Checkout repository @@ -59,7 +59,7 @@ jobs: rustfmt: name: rustfmt - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v1 From 2493aff5207598389c5eefbba432baa6e83ceeae Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 5 Oct 2023 21:53:19 -0400 Subject: [PATCH 02/23] ci: fix it --- .github/workflows/ci.yml | 54 ++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41d7b2c..6258948 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,29 +1,45 @@ name: ci on: pull_request: + branches: + - master push: branches: - master schedule: - cron: '00 01 * * *' + +# The section is needed to drop write-all permissions that are granted on +# `schedule` event. By specifying any permission explicitly all others are set +# to none. By using the principle of least privilege the damage a compromised +# workflow can do (because of an injection or compromised third party tool or +# action) is restricted. Currently the worklow doesn't need any additional +# permission except for pulling the code. Adding labels to issues, commenting +# on pull-requests, etc. may need additional permissions: +# +# Syntax for this section: +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions +# +# Reference for how to assign permissions on a job-by-job basis: +# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs +# +# Reference for available permissions that we can enable if needed: +# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token +permissions: + # to fetch code (actions/checkout) + contents: read + jobs: test: name: test runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: - build: - - pinned - - stable - - beta - - nightly - - macos - - win-msvc - - win-gnu include: - build: pinned os: ubuntu-latest - rust: 1.46.0 + rust: 1.60.0 - build: stable os: ubuntu-latest rust: stable @@ -44,13 +60,11 @@ jobs: rust: stable-x86_64-gnu steps: - name: Checkout repository - uses: actions/checkout@v1 - with: - fetch-depth: 1 + uses: actions/checkout@v4 - name: Install Rust - uses: hecrj/setup-rust-action@v1 + uses: dtolnay/rust-toolchain@master with: - rust-version: ${{ matrix.rust }} + toolchain: ${{ matrix.rust }} - run: cargo build --verbose - run: cargo doc --verbose - run: cargo test --verbose @@ -58,19 +72,15 @@ jobs: - run: cargo test --verbose --manifest-path quickcheck_macros/Cargo.toml rustfmt: - name: rustfmt runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v1 - with: - fetch-depth: 1 + uses: actions/checkout@v4 - name: Install Rust - uses: hecrj/setup-rust-action@v1 + uses: dtolnay/rust-toolchain@master with: - rust-version: stable - - name: Install rustfmt - run: rustup component add rustfmt + toolchain: stable + components: rustfmt - name: Check formatting run: | cargo fmt --all -- --check From db3cb663c689e1c5158004205db3c79fd56ff6cb Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 5 Oct 2023 21:59:54 -0400 Subject: [PATCH 03/23] ci: bump pinned version to 1.61 ... so that memchr builds. Sigh. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6258948..d6f216a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: include: - build: pinned os: ubuntu-latest - rust: 1.60.0 + rust: 1.61.0 - build: stable os: ubuntu-latest rust: stable From aa968a94650b5d4d572c4ef581a7f5eb259aa0d2 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 10 Oct 2023 09:46:25 -0400 Subject: [PATCH 04/23] ci: bump pinned to Rust 1.65 --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6f216a..5786db7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: include: - build: pinned os: ubuntu-latest - rust: 1.61.0 + rust: 1.65.0 - build: stable os: ubuntu-latest rust: stable diff --git a/README.md b/README.md index 2546a63..e32afd6 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ Crate features: ### Minimum Rust version policy -This crate's minimum supported `rustc` version is `1.46.0`. +This crate's minimum supported `rustc` version is `1.65.0`. The current policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if `crate 1.0` requires From 88f1ecaef26ce2a36b293708210516d371f47927 Mon Sep 17 00:00:00 2001 From: Julian Ganz Date: Wed, 5 Mar 2025 08:39:03 +0100 Subject: [PATCH 05/23] Shut up non_fmt_panic warning (#284) * Shut up non_fmt_panic warning Like a few other macros, `panic!` accepts a format string and arguments. The format string is meant to be static and calling it with a message string will generate a warning. Currently, the warning also indicates that `rustc` will generate an error instead in the near future. * Eliminate redundant format! in panic! The `panic!` matro itself does take a foramt string. In fact, the redundant format generated a warning. --- src/arbitrary.rs | 4 ++-- src/tester.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 92f893b..ccc00a2 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -1365,11 +1365,11 @@ mod test { for n in v { let found = shrunk.iter().any(|&i| i == n); if !found { - panic!(format!( + panic!( "Element {:?} was not found \ in shrink results {:?}", n, shrunk - )); + ); } } } diff --git a/src/tester.rs b/src/tester.rs index e2eaa20..d9fe7f2 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -162,7 +162,7 @@ impl QuickCheck { let n_tests_passed = match self.quicktest(f) { Ok(n_tests_passed) => n_tests_passed, - Err(result) => panic!(result.failed_msg()), + Err(result) => panic!("{}", result.failed_msg()), }; if n_tests_passed >= self.min_tests_passed { From 1cb1a4a8ea871454bd8ad8d772d58e7a26b7662a Mon Sep 17 00:00:00 2001 From: Aatif Syed <38045910+aatifsyed@users.noreply.github.com> Date: Wed, 5 Mar 2025 07:44:03 +0000 Subject: [PATCH 06/23] feat: support arbitrary arrays (#319) --- src/arbitrary.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index ccc00a2..66a34ea 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -248,6 +248,12 @@ impl_arb_for_tuples! { (H, 7), } +impl Arbitrary for [A; N] { + fn arbitrary(g: &mut Gen) -> Self { + std::array::from_fn(|_ix| A::arbitrary(g)) + } +} + impl Arbitrary for Vec { fn arbitrary(g: &mut Gen) -> Vec { let size = { From f8800ca1747408d213061a9f7747981b60ce31c5 Mon Sep 17 00:00:00 2001 From: Alex Touchet <26315797+atouchet@users.noreply.github.com> Date: Tue, 4 Mar 2025 23:52:08 -0800 Subject: [PATCH 07/23] Use SPDX license format, remove outdated Travis CI reference, and fix crates.io badge (#309) * Use SPDX license format and remove outdated Travis CI reference * Fix crates.io badge --- Cargo.toml | 4 ++-- README.md | 2 +- quickcheck_macros/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17cc17a..eef7e56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ repository = "https://github.com/BurntSushi/quickcheck" readme = "README.md" keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] categories = ["development-tools::testing"] -license = "Unlicense/MIT" -exclude = ["/.travis.yml", "/Makefile", "/ctags.rust", "/session.vim"] +license = "Unlicense OR MIT" +exclude = ["/Makefile", "/ctags.rust", "/session.vim"] edition = "2018" [workspace] diff --git a/README.md b/README.md index e32afd6..e31569e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ the input space quickly. (It should be the same strategy used in Haskell](https://hackage.haskell.org/package/QuickCheck).) [![Build status](https://github.com/BurntSushi/quickcheck/workflows/ci/badge.svg)](https://github.com/BurntSushi/quickcheck/actions) -[![](https://meritbadge.herokuapp.com/quickcheck)](https://crates.io/crates/quickcheck) +[![crates.io](https://img.shields.io/crates/v/quickcheck.svg)](https://crates.io/crates/quickcheck) Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org). diff --git a/quickcheck_macros/Cargo.toml b/quickcheck_macros/Cargo.toml index 1d9041d..4b990e6 100644 --- a/quickcheck_macros/Cargo.toml +++ b/quickcheck_macros/Cargo.toml @@ -8,7 +8,7 @@ homepage = "https://github.com/BurntSushi/quickcheck" repository = "https://github.com/BurntSushi/quickcheck" readme = "../README.md" keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] -license = "Unlicense/MIT" +license = "Unlicense OR MIT" autotests = false [lib] From 2ca27244afaf1bb0f3eb7a3e5f9dca73394daff5 Mon Sep 17 00:00:00 2001 From: Lynn Date: Wed, 5 Mar 2025 08:53:34 +0100 Subject: [PATCH 08/23] Explain what the crate does in lib.rs docs (#313) Currently https://docs.rs/quickcheck doesn't say much about what this crate does. I think having most info in the README is OK, but probably there should be some info in the crate docs root as well. --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 58dc99d..d3a5ba4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,11 @@ This crate is a port of [Haskell's QuickCheck](https://hackage.haskell.org/package/QuickCheck). +QuickCheck is a library for random testing of program properties. The +programmer provides a specification of the program, in the form of properties +which functions should satisfy, and QuickCheck then tests that the properties +hold in a large number of randomly generated cases. + For detailed examples, please see the [README](https://github.com/BurntSushi/quickcheck). From 84acaec532a09e5c9661505ab665240c7c2e91be Mon Sep 17 00:00:00 2001 From: davidrusu Date: Thu, 6 Mar 2025 13:33:41 +0400 Subject: [PATCH 09/23] only build debug representations of arguments even on failing tests (#304) --- src/tester.rs | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/tester.rs b/src/tester.rs index d9fe7f2..9fd7a68 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -189,7 +189,7 @@ pub fn quickcheck(f: A) { #[derive(Clone, Debug)] pub struct TestResult { status: Status, - arguments: Vec, + arguments: Option>, err: Option, } @@ -224,7 +224,7 @@ impl TestResult { /// When a test is discarded, `quickcheck` will replace it with a /// fresh one (up to a certain limit). pub fn discard() -> TestResult { - TestResult { status: Discard, arguments: vec![], err: None } + TestResult { status: Discard, arguments: None, err: None } } /// Converts a `bool` to a `TestResult`. A `true` value indicates that @@ -233,7 +233,7 @@ impl TestResult { pub fn from_bool(b: bool) -> TestResult { TestResult { status: if b { Pass } else { Fail }, - arguments: vec![], + arguments: None, err: None, } } @@ -266,16 +266,15 @@ impl TestResult { } fn failed_msg(&self) -> String { + let arguments_msg = match self.arguments { + None => format!("No Arguments Provided"), + Some(ref args) => format!("Arguments: ({})", args.join(", ")), + }; match self.err { - None => format!( - "[quickcheck] TEST FAILED. Arguments: ({})", - self.arguments.join(", ") - ), + None => format!("[quickcheck] TEST FAILED. {}", arguments_msg), Some(ref err) => format!( - "[quickcheck] TEST FAILED (runtime error). \ - Arguments: ({})\nError: {}", - self.arguments.join(", "), - err + "[quickcheck] TEST FAILED (runtime error). {}\nError: {}", + arguments_msg, err ), } } @@ -350,7 +349,7 @@ impl r, Fail => { @@ -429,7 +423,7 @@ mod test { .quicktest(thetest as fn(vals: Vec) -> bool) .unwrap_err(); let expected_argument = format!("{:?}", [true, true]); - assert_eq!(failing_case.arguments, vec![expected_argument]); + assert_eq!(failing_case.arguments, Some(vec![expected_argument])); } #[test] From f34179b231002160d41337b20919de1d26ecb539 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Tue, 24 Aug 2021 17:13:49 -0300 Subject: [PATCH 10/23] Avoid some warnings from Clippy --- src/arbitrary.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 66a34ea..496cfd9 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -47,7 +47,7 @@ impl Gen { /// randomly generated number. (Unless that number is used to control the /// size of a data structure.) pub fn new(size: usize) -> Gen { - Gen { rng: rand::rngs::SmallRng::from_entropy(), size: size } + Gen { rng: rand::rngs::SmallRng::from_entropy(), size } } /// Returns the size configured with this generator. @@ -288,8 +288,8 @@ impl VecShrinker { }; let size = seed.len(); Box::new(VecShrinker { - seed: seed, - size: size, + seed, + size, offset: size, element_shrinker: es, }) @@ -519,11 +519,12 @@ impl Arbitrary for PathBuf { fn arbitrary(g: &mut Gen) -> PathBuf { // use some real directories as guesses, so we may end up with // actual working directories in case that is relevant. - let here = - env::current_dir().unwrap_or(PathBuf::from("/test/directory")); + let here = env::current_dir() + .unwrap_or_else(|_| PathBuf::from("/test/directory")); let temp = env::temp_dir(); #[allow(deprecated)] - let home = env::home_dir().unwrap_or(PathBuf::from("/home/user")); + let home = + env::home_dir().unwrap_or_else(|| PathBuf::from("/home/user")); let mut p = g .choose(&[ here, @@ -573,7 +574,7 @@ impl Arbitrary for OsString { fn shrink(&self) -> Box> { let mystring: String = self.clone().into_string().unwrap(); - Box::new(mystring.shrink().map(|s| OsString::from(s))) + Box::new(mystring.shrink().map(OsString::from)) } } @@ -751,7 +752,7 @@ macro_rules! unsigned_shrinker { Box::new( vec![0] .into_iter() - .chain(UnsignedShrinker { x: x, i: x / 2 }), + .chain(UnsignedShrinker { x, i: x / 2 }), ) } } @@ -762,7 +763,7 @@ macro_rules! unsigned_shrinker { fn next(&mut self) -> Option<$ty> { if self.x - self.i < self.x { let result = Some(self.x - self.i); - self.i = self.i / 2; + self.i /= 2; result } else { None @@ -817,7 +818,7 @@ macro_rules! signed_shrinker { if x == 0 { super::empty_shrinker() } else { - let shrinker = SignedShrinker { x: x, i: x / 2 }; + let shrinker = SignedShrinker { x, i: x / 2 }; let mut items = vec![0]; if shrinker.i < 0 && shrinker.x != <$ty>::MIN { items.push(shrinker.x.abs()); @@ -834,7 +835,7 @@ macro_rules! signed_shrinker { || (self.x - self.i).abs() < self.x.abs() { let result = Some(self.x - self.i); - self.i = self.i / 2; + self.i /= 2; result } else { None @@ -927,7 +928,7 @@ macro_rules! unsigned_non_zero_shrinker { } else { Box::new( std::iter::once(1).chain( - UnsignedNonZeroShrinker { x: x, i: x / 2 }, + UnsignedNonZeroShrinker { x, i: x / 2 }, ), ) } @@ -940,7 +941,7 @@ macro_rules! unsigned_non_zero_shrinker { fn next(&mut self) -> Option<$ty> { if self.x - self.i < self.x { let result = Some(self.x - self.i); - self.i = self.i / 2; + self.i /= 2; result } else { None @@ -988,7 +989,7 @@ impl Arbitrary for Wrapping { Wrapping(T::arbitrary(g)) } fn shrink(&self) -> Box>> { - Box::new(self.0.shrink().map(|inner| Wrapping(inner))) + Box::new(self.0.shrink().map(Wrapping)) } } From d75e67c10c2c7ec308923a6869781d3ce2a4647f Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Tue, 24 Aug 2021 16:54:53 -0300 Subject: [PATCH 11/23] Implement the From trait for TestResult --- src/tester.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/tester.rs b/src/tester.rs index 9fd7a68..45242ee 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -186,7 +186,7 @@ pub fn quickcheck(f: A) { /// Describes the status of a single instance of a test. /// /// All testable things must be capable of producing a `TestResult`. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct TestResult { status: Status, arguments: Option>, @@ -194,7 +194,7 @@ pub struct TestResult { } /// Whether a test has passed, failed or been discarded. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] enum Status { Pass, Fail, @@ -280,6 +280,21 @@ impl TestResult { } } +impl From for TestResult { + /// A shorter way of producing a `TestResult` from a `bool`. + /// + /// # Example + /// + /// ```rust + /// # use quickcheck::TestResult; + /// let result: TestResult = (2 > 1).into(); + /// assert_eq!(result, TestResult::passed()); + /// ``` + fn from(b: bool) -> TestResult { + TestResult::from_bool(b) + } +} + /// `Testable` describes types (e.g., a function) whose values can be /// tested. /// From 004d197318e44dbd827838db21860680c8ccef28 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Thu, 6 Mar 2025 05:04:13 -0500 Subject: [PATCH 12/23] Clippy & markdown linting --- README.md | 66 +++++++++++++++--------------------- examples/btree_set_range.rs | 7 ++-- examples/reverse.rs | 2 +- examples/reverse_single.rs | 4 +-- examples/sieve.rs | 2 +- examples/sort.rs | 14 ++++---- quickcheck_macros/src/lib.rs | 6 ++-- src/arbitrary.rs | 59 ++++++++++++++++++-------------- src/tester.rs | 25 +++++++------- src/tests.rs | 28 +++++++-------- 10 files changed, 104 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index e31569e..1f60af7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -quickcheck -========== +# quickcheck + QuickCheck is a way to do property based testing using randomly generated input. This crate comes with the ability to randomly generate and shrink integers, floats, tuples, booleans, lists, strings, options and results. @@ -19,14 +19,12 @@ Haskell](https://hackage.haskell.org/package/QuickCheck).) Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org). - -### Documentation +## Documentation The API is fully documented: [https://docs.rs/quickcheck](https://docs.rs/quickcheck). - -### Simple example +## Simple example Here's an example that tests a function that reverses a vector: @@ -56,7 +54,7 @@ mod tests { This example uses the `quickcheck!` macro, which is backwards compatible with old versions of Rust. -### The `#[quickcheck]` attribute +## The `#[quickcheck]` attribute To make it easier to write QuickCheck tests, the `#[quickcheck]` attribute will convert a property function into a `#[test]` function. @@ -88,8 +86,7 @@ mod tests { } ``` - -### Installation +## Installation `quickcheck` is on `crates.io`, so you can include it in your project like so: @@ -126,8 +123,7 @@ Crate features: - `"regex"`: (Enabled by default.) Enables the use of regexes with `env_logger`. - -### Minimum Rust version policy +## Minimum Rust version policy This crate's minimum supported `rustc` version is `1.65.0`. @@ -145,8 +141,7 @@ With all of that said, currently, `rand` is a public dependency of aggressive than `rand`'s MSRV policy. Otherwise, `quickcheck` will defer to `rand`'s MSRV policy. - -### Compatibility +## Compatibility In general, this crate considers the `Arbitrary` implementations provided as implementation details. Strategies may or may not change over time, which may @@ -154,8 +149,7 @@ cause new test failures, presumably due to the discovery of new bugs due to a new kind of witness being generated. These sorts of changes may happen in semver compatible releases. - -### Alternative Rust crates for property testing +## Alternative Rust crates for property testing The [`proptest`](https://docs.rs/proptest) crate is inspired by the [Hypothesis](https://hypothesis.works) framework for Python. @@ -167,16 +161,14 @@ In particular, `proptest` improves on the concept of shrinking. So if you've ever had problems/frustration with shrinking in `quickcheck`, then `proptest` might be worth a try! - -### Alternatives for fuzzing +## Alternatives for fuzzing Please see the [Rust Fuzz Book](https://rust-fuzz.github.io/book/introduction.html) and the [`arbitrary`](https://crates.io/crates/arbitrary) crate. - -### Discarding test results (or, properties are polymorphic!) +## Discarding test results (or, properties are polymorphic!) Sometimes you want to test a property that only holds for a *subset* of the possible inputs, so that when your property is given an input that is outside @@ -285,8 +277,7 @@ There is also `QUICKCHECK_MIN_TESTS_PASSED` which sets the minimum number of valid tests that need pass (defaults to `0`) in order for it to be considered a success. - -### Shrinking +## Shrinking Shrinking is a crucial part of QuickCheck that simplifies counter-examples for your properties automatically. For example, if you erroneously defined a @@ -313,7 +304,7 @@ quickcheck(prop as fn(Vec) -> bool); Then without shrinking, you might get a counter-example like: -``` +```text [quickcheck] TEST FAILED. Arguments: ([-17, 13, -12, 17, -8, -10, 15, -19, -19, -9, 11, -5, 1, 19, -16, 6]) ``` @@ -321,13 +312,13 @@ Then without shrinking, you might get a counter-example like: Which is pretty mysterious. But with shrinking enabled, you're nearly guaranteed to get this counter-example every time: -``` +```text [quickcheck] TEST FAILED. Arguments: ([0]) ``` Which is going to be much easier to debug. -### More Thorough Checking +## More Thorough Checking Quickcheck uses random input to test, so it won't always find bugs that could be uncovered with a particular @@ -341,7 +332,7 @@ lot, you might also be interested in trying out [`cargo fuzz`](https://github.com/rust-fuzz/cargo-fuzz), which runs in a loop by default. -##### Running in a Loop +### Running in a Loop One approach is to run your quickcheck properties in a loop that just keeps going until you tell it to stop or it finds a bug. @@ -369,7 +360,7 @@ would take time away from quickcheck! Checking the return code and exiting is also important. Without that test, you won't ever notice when a failure happens. -##### Cranking the Number of Tests +### Cranking the Number of Tests Another approach is to just ask quickcheck to run properties more times. You can do this either via the @@ -380,7 +371,7 @@ the loop approach this will take a bounded amount of time, which makes it more suitable for something like a release cycle that wants to really hammer your software. -##### Making Arbitrary Smarter +### Making Arbitrary Smarter This approach entails spending more time generating interesting inputs in your implementations of Arbitrary. The idea is to @@ -390,7 +381,7 @@ and the whole idea of property checking is to take that burden off the programmer. Despite the theoretical discomfort, this approach can turn out to be practical. -### Generating Structs +## Generating Structs It is very simple to generate structs in QuickCheck. Consider the following example, where the struct `Point` is defined: @@ -418,8 +409,7 @@ impl Arbitrary for Point { } ``` - -### Case study: The Sieve of Eratosthenes +## Case study: The Sieve of Eratosthenes The [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) is a simple and elegant way to find all primes less than or equal to `N`. @@ -458,7 +448,7 @@ fn sieve(n: usize) -> Vec { Let's try it on a few inputs by hand: -``` +```text sieve(3) => [2, 3] sieve(5) => [2, 3, 5] sieve(8) => [2, 3, 5, 7, 8] # !!! @@ -503,7 +493,7 @@ A fully working source file with this code is in The output of running this program has this message: -``` +```text [quickcheck] TEST FAILED. Arguments: (4) ``` @@ -558,7 +548,7 @@ fn main() { we see that it fails immediately for value n = 2. -``` +```text [quickcheck] TEST FAILED. Arguments: (2) ``` @@ -566,18 +556,18 @@ If we inspect `sieve()` once again, we see that we mistakenly mark `2` as non-prime. Removing the line `marked[2] = true;` results in both properties passing. -### What's not in this port of QuickCheck? +## What's not in this port of QuickCheck? I think I've captured the key features, but there are still things missing: -* Only functions with 8 or fewer parameters can be quickchecked. This +- Only functions with 8 or fewer parameters can be quickchecked. This limitation can be lifted to some `N`, but requires an implementation for each `n` of the `Testable` trait. -* Functions that fail because of a stack overflow are not caught by QuickCheck. +- Functions that fail because of a stack overflow are not caught by QuickCheck. Therefore, such failures will not have a witness attached to them. (I'd like to fix this, but I don't know how.) -* `Coarbitrary` does not exist in any form in this package. It's unlikely that +- `Coarbitrary` does not exist in any form in this package. It's unlikely that it ever will. -* `Arbitrary` is not implemented for closures. See +- `Arbitrary` is not implemented for closures. See [issue #56](https://github.com/BurntSushi/quickcheck/issues/56) for more details on why. diff --git a/examples/btree_set_range.rs b/examples/btree_set_range.rs index 0e7418b..954c991 100644 --- a/examples/btree_set_range.rs +++ b/examples/btree_set_range.rs @@ -31,9 +31,8 @@ impl RangeBounds for RangeAny { fn panics(range: RangeAny) -> bool { match (&range.0, &range.1) { (Excluded(start), Excluded(end)) => start >= end, - (Included(start), Excluded(end)) - | (Excluded(start), Included(end)) - | (Included(start), Included(end)) => start > end, + (Included(start), Excluded(end) | Included(end)) + | (Excluded(start), Included(end)) => start > end, (Unbounded, _) | (_, Unbounded) => false, } } @@ -43,7 +42,7 @@ fn check_range(set: BTreeSet, range: RangeAny) -> TestResult { if panics(range) { TestResult::discard() } else { - let xs: BTreeSet<_> = set.range(range).cloned().collect(); + let xs: BTreeSet<_> = set.range(range).copied().collect(); TestResult::from_bool( set.iter().all(|x| range.contains(x) == xs.contains(x)), ) diff --git a/examples/reverse.rs b/examples/reverse.rs index fff6e71..66d395b 100644 --- a/examples/reverse.rs +++ b/examples/reverse.rs @@ -3,7 +3,7 @@ use quickcheck::quickcheck; fn reverse(xs: &[T]) -> Vec { let mut rev = vec![]; for x in xs { - rev.insert(0, x.clone()) + rev.insert(0, x.clone()); } rev } diff --git a/examples/reverse_single.rs b/examples/reverse_single.rs index 6112509..cbfd679 100644 --- a/examples/reverse_single.rs +++ b/examples/reverse_single.rs @@ -3,7 +3,7 @@ use quickcheck::{quickcheck, TestResult}; fn reverse(xs: &[T]) -> Vec { let mut rev = vec![]; for x in xs { - rev.insert(0, x.clone()) + rev.insert(0, x.clone()); } rev } @@ -13,7 +13,7 @@ fn main() { if xs.len() != 1 { return TestResult::discard(); } - TestResult::from_bool(xs == reverse(&*xs)) + TestResult::from_bool(xs == reverse(&xs)) } quickcheck(prop as fn(Vec) -> TestResult); } diff --git a/examples/sieve.rs b/examples/sieve.rs index f05b8e3..bb921ea 100644 --- a/examples/sieve.rs +++ b/examples/sieve.rs @@ -31,7 +31,7 @@ fn main() { } fn prop_prime_iff_in_the_sieve(n: usize) -> bool { - sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::>() + sieve(n) == (0..=n).filter(|&i| is_prime(i)).collect::>() } quickcheck(prop_all_prime as fn(usize) -> bool); diff --git a/examples/sort.rs b/examples/sort.rs index 0f495a0..335f5ce 100644 --- a/examples/sort.rs +++ b/examples/sort.rs @@ -4,18 +4,18 @@ use quickcheck::quickcheck; fn smaller_than(xs: &[T], pivot: &T) -> Vec { - xs.iter().filter(|&x| *x < *pivot).map(|x| x.clone()).collect() + xs.iter().filter(|&x| *x < *pivot).cloned().collect() } fn larger_than(xs: &[T], pivot: &T) -> Vec { - xs.iter().filter(|&x| *x > *pivot).map(|x| x.clone()).collect() + xs.iter().filter(|&x| *x > *pivot).cloned().collect() } fn sortk(x: &T, xs: &[T]) -> Vec { - let mut result: Vec = sort(&*smaller_than(xs, x)); - let last_part = sort(&*larger_than(xs, x)); + let mut result: Vec = sort(&smaller_than(xs, x)); + let last_part = sort(&larger_than(xs, x)); result.push(x.clone()); - result.extend(last_part.iter().map(|x| x.clone())); + result.extend(last_part.iter().cloned()); result } @@ -38,9 +38,9 @@ fn main() { } fn keeps_length(xs: Vec) -> bool { - xs.len() == sort(&*xs).len() + xs.len() == sort(&xs).len() } quickcheck(keeps_length as fn(Vec) -> bool); - quickcheck(is_sorted as fn(Vec) -> bool) + quickcheck(is_sorted as fn(Vec) -> bool); } diff --git a/quickcheck_macros/src/lib.rs b/quickcheck_macros/src/lib.rs index 132e8e0..df16d19 100644 --- a/quickcheck_macros/src/lib.rs +++ b/quickcheck_macros/src/lib.rs @@ -31,11 +31,11 @@ pub fn quickcheck(_args: TokenStream, input: TokenStream) -> TokenStream { }); if errors.is_empty() { - let attrs = mem::replace(&mut item_fn.attrs, Vec::new()); + let attrs = mem::take(&mut item_fn.attrs); let name = &item_fn.sig.ident; let fn_type = syn::TypeBareFn { lifetimes: None, - unsafety: item_fn.sig.unsafety.clone(), + unsafety: item_fn.sig.unsafety, abi: item_fn.sig.abi.clone(), fn_token: ::default(), paren_token: syn::token::Paren::default(), @@ -60,7 +60,7 @@ pub fn quickcheck(_args: TokenStream, input: TokenStream) -> TokenStream { } } Ok(syn::Item::Static(mut item_static)) => { - let attrs = mem::replace(&mut item_static.attrs, Vec::new()); + let attrs = mem::take(&mut item_static.attrs); let name = &item_static.ident; quote! { diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 496cfd9..e08b7d3 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -131,9 +131,7 @@ pub trait Arbitrary: Clone + 'static { } impl Arbitrary for () { - fn arbitrary(_: &mut Gen) -> () { - () - } + fn arbitrary(_: &mut Gen) {} } impl Arbitrary for bool { @@ -281,8 +279,9 @@ struct VecShrinker { } impl VecShrinker { + #[allow(clippy::new_ret_no_self)] fn new(seed: Vec) -> Box>> { - let es = match seed.get(0) { + let es = match seed.first() { Some(e) => e.shrink(), None => return empty_shrinker(), }; @@ -350,7 +349,7 @@ where // offset (self.offset == 0 only on first entry to this part of the // iterator) if self.offset == 0 { - self.offset = 1 + self.offset = 1; } match self.next_element() { @@ -358,7 +357,7 @@ where self.seed[..self.offset - 1] .iter() .cloned() - .chain(Some(e).into_iter()) + .chain(Some(e)) .chain(self.seed[self.offset..].iter().cloned()) .collect(), ), @@ -640,7 +639,7 @@ impl Arbitrary for char { match mode { 0..=49 => { // ASCII + some control characters - g.gen_range(0..0xB0) as u8 as char + g.gen_range(0u8..0xB0) as char } 50..=59 => { // Unicode BMP characters @@ -745,6 +744,7 @@ macro_rules! unsigned_shrinker { } impl UnsignedShrinker { + #[allow(clippy::new_ret_no_self)] pub fn new(x: $ty) -> Box> { if x == 0 { super::empty_shrinker() @@ -776,7 +776,7 @@ macro_rules! unsigned_shrinker { macro_rules! unsigned_problem_values { ($t:ty) => { - &[<$t>::min_value(), 1, <$t>::max_value()] + &[<$t>::MIN, 1, <$t>::MAX] }; } @@ -814,6 +814,7 @@ macro_rules! signed_shrinker { } impl SignedShrinker { + #[allow(clippy::new_ret_no_self)] pub fn new(x: $ty) -> Box> { if x == 0 { super::empty_shrinker() @@ -848,7 +849,7 @@ macro_rules! signed_shrinker { macro_rules! signed_problem_values { ($t:ty) => { - &[<$t>::min_value(), 0, <$t>::max_value()] + &[<$t>::MIN, 0, <$t>::MAX] }; } @@ -878,22 +879,27 @@ signed_arbitrary! { } macro_rules! float_problem_values { - ($path:path) => {{ - // hack. see: https://github.com/rust-lang/rust/issues/48067 - use $path as p; - &[p::NAN, p::NEG_INFINITY, p::MIN, -0., 0., p::MAX, p::INFINITY] + ($t:ty) => {{ + &[ + <$t>::NAN, + <$t>::NEG_INFINITY, + <$t>::MIN, + -0., + 0., + <$t>::MAX, + <$t>::INFINITY, + ] }}; } macro_rules! float_arbitrary { - ($($t:ty, $path:path, $shrinkable:ty),+) => {$( + ($($t:ty, $shrinkable:ty),+) => {$( impl Arbitrary for $t { fn arbitrary(g: &mut Gen) -> $t { match g.gen_range(0..10) { - 0 => *g.choose(float_problem_values!($path)).unwrap(), + 0 => *g.choose(float_problem_values!($t)).unwrap(), _ => { - use $path as p; - let exp = g.gen_range((0.)..p::MAX_EXP as i16 as $t); + let exp = g.gen_range((0.)..<$t>::MAX_EXP as i16 as $t); let mantissa = g.gen_range((1.)..2.); let sign = *g.choose(&[-1., 1.]).unwrap(); sign * mantissa * exp.exp2() @@ -909,7 +915,7 @@ macro_rules! float_arbitrary { )*}; } -float_arbitrary!(f32, std::f32, i32, f64, std::f64, i64); +float_arbitrary!(f32, i32, f64, i64); macro_rules! unsigned_non_zero_shrinker { ($ty:tt) => { @@ -920,6 +926,7 @@ macro_rules! unsigned_non_zero_shrinker { } impl UnsignedNonZeroShrinker { + #[allow(clippy::new_ret_no_self)] pub fn new(x: $ty) -> Box> { debug_assert!(x > 0); @@ -1145,7 +1152,7 @@ mod test { #[test] fn arby_unit() { - assert_eq!(arby::<()>(), ()); + let _: () = arby::<()>(); } macro_rules! arby_int { @@ -1158,8 +1165,8 @@ mod test { }; assert!(problems.all(|p| arbys.any(|arby| arby == *p)), "Arbitrary does not generate all problematic values"); - let max = <$t>::max_value(); - let mid = (max + <$t>::min_value()) / 2; + let max = <$t>::MAX; + let mid = (max + <$t>::MIN) / 2; // split full range of $t into chunks // Arbitrary must return some value in each chunk let double_chunks: $t = 9; @@ -1190,14 +1197,13 @@ mod test { } macro_rules! arby_float { - ($($t:ty, $path:path),+) => {$({ - use $path as p; + ($($t:ty),+) => {$({ let mut arbys = (0..1_000_000).map(|_| arby::<$t>()); //NaN != NaN assert!(arbys.any(|f| f.is_nan()), "Arbitrary does not generate the problematic value NaN" ); - for p in float_problem_values!($path).iter().filter(|f| !f.is_nan()) { + for p in float_problem_values!($t).iter().filter(|f| !f.is_nan()) { assert!(arbys.any(|arby| arby == *p), "Arbitrary does not generate the problematic value {}", p @@ -1209,7 +1215,7 @@ mod test { let chunks = double_chunks * 2; // chunks must be even let lim = (-double_chunks..=double_chunks) .map(|idx| <$t>::from(idx)) - .map(|idx| p::MAX/(<$t>::from(chunks/2)) * idx); + .map(|idx| <$t>::MAX/(<$t>::from(chunks/2)) * idx); let mut lim = lim.peekable(); while let (Some(low), Some(&high)) = (lim.next(), lim.peek()) { assert!( @@ -1224,7 +1230,7 @@ mod test { #[test] fn arby_float() { - arby_float!(f32, std::f32, f64, std::f64); + arby_float!(f32, f64); } fn arby() -> A { @@ -1567,6 +1573,7 @@ mod test { eq(Unbounded::, vec![]); } + #[allow(clippy::reversed_empty_ranges)] #[test] fn ranges() { ordered_eq(0..0, vec![]); diff --git a/src/tester.rs b/src/tester.rs index 45242ee..ddbb07f 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -48,6 +48,12 @@ fn qc_min_tests_passed() -> u64 { } } +impl Default for QuickCheck { + fn default() -> Self { + Self::new() + } +} + impl QuickCheck { /// Creates a new QuickCheck value. /// @@ -166,12 +172,12 @@ impl QuickCheck { }; if n_tests_passed >= self.min_tests_passed { - info!("(Passed {} QuickCheck tests.)", n_tests_passed) + info!("(Passed {} QuickCheck tests.)", n_tests_passed); } else { panic!( "(Unable to generate enough tests, {} not discarded.)", n_tests_passed - ) + ); } } } @@ -267,14 +273,13 @@ impl TestResult { fn failed_msg(&self) -> String { let arguments_msg = match self.arguments { - None => format!("No Arguments Provided"), + None => "No Arguments Provided".to_owned(), Some(ref args) => format!("Arguments: ({})", args.join(", ")), }; match self.err { - None => format!("[quickcheck] TEST FAILED. {}", arguments_msg), + None => format!("[quickcheck] TEST FAILED. {arguments_msg}"), Some(ref err) => format!( - "[quickcheck] TEST FAILED (runtime error). {}\nError: {}", - arguments_msg, err + "[quickcheck] TEST FAILED (runtime error). {arguments_msg}\nError: {err}" ), } } @@ -336,14 +341,14 @@ where fn result(&self, g: &mut Gen) -> TestResult { match *self { Ok(ref r) => r.result(g), - Err(ref err) => TestResult::error(format!("{:?}", err)), + Err(ref err) => TestResult::error(format!("{err:?}")), } } } /// Return a vector of the debug formatting of each item in `args` fn debug_reprs(args: &[&dyn Debug]) -> Vec { - args.iter().map(|x| format!("{:?}", x)).collect() + args.iter().map(|x| format!("{x:?}")).collect() } macro_rules! testable_fn { @@ -421,10 +426,6 @@ where }) } -/// Convenient aliases. -trait AShow: Arbitrary + Debug {} -impl AShow for A {} - #[cfg(test)] mod test { use crate::{Gen, QuickCheck}; diff --git a/src/tests.rs b/src/tests.rs index 465ef15..d1df2c5 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,4 +1,3 @@ -use std::cmp::Ord; use std::collections::hash_map::DefaultHasher; use std::collections::{HashMap, HashSet}; use std::ffi::CString; @@ -13,13 +12,12 @@ fn prop_oob() { let zero: Vec = vec![]; zero[0] } - match QuickCheck::new().quicktest(prop as fn() -> bool) { - Ok(n) => panic!( + if let Ok(n) = QuickCheck::new().quicktest(prop as fn() -> bool) { + panic!( "prop_oob should fail with a runtime error \ - but instead it passed {} tests.", + but instead it passed {} tests.", n - ), - _ => return, + ); } } @@ -65,12 +63,12 @@ fn reverse_single() { fn reverse_app() { fn prop(xs: Vec, ys: Vec) -> bool { let mut app = xs.clone(); - app.extend(ys.iter().cloned()); + app.extend(ys.iter().copied()); let app_rev: Vec = app.into_iter().rev().collect(); let rxs: Vec = xs.into_iter().rev().collect(); let mut rev_app = ys.into_iter().rev().collect::>(); - rev_app.extend(rxs.into_iter()); + rev_app.extend(rxs); app_rev == rev_app } @@ -92,7 +90,7 @@ fn max() { #[test] fn sort() { fn prop(mut xs: Vec) -> bool { - xs.sort_by(|x, y| x.cmp(y)); + xs.sort_unstable(); for i in xs.windows(2) { if i[0] > i[1] { return false; @@ -142,7 +140,7 @@ fn sieve_not_prime() { fn sieve_not_all_primes() { fn prop_prime_iff_in_the_sieve(n: u8) -> bool { let n = n as usize; - sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::>() + sieve(n) == (0..=n).filter(|&i| is_prime(i)).collect::>() } quickcheck(prop_prime_iff_in_the_sieve as fn(u8) -> bool); } @@ -170,7 +168,7 @@ fn testable_unit() { #[test] fn testable_unit_panic() { fn panic() { - panic!() + panic!(); } assert!(QuickCheck::new().quicktest(panic as fn()).is_err()); } @@ -180,7 +178,7 @@ fn regression_issue_83() { fn prop(_: u8) -> bool { true } - QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(u8) -> bool) + QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(u8) -> bool); } #[test] @@ -188,7 +186,7 @@ fn regression_issue_83_signed() { fn prop(_: i8) -> bool { true } - QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(i8) -> bool) + QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(i8) -> bool); } // Test that we can show the message after panic @@ -242,7 +240,7 @@ fn all_tests_discarded_min_tests_passed_set() { QuickCheck::new() .tests(16) .min_tests_passed(8) - .quickcheck(prop_discarded as fn(u8) -> TestResult) + .quickcheck(prop_discarded as fn(u8) -> TestResult); } #[test] @@ -251,7 +249,7 @@ fn all_tests_discarded_min_tests_passed_missing() { TestResult::discard() } - QuickCheck::new().quickcheck(prop_discarded as fn(u8) -> TestResult) + QuickCheck::new().quickcheck(prop_discarded as fn(u8) -> TestResult); } quickcheck! { From 44b81bebcf2c815e428e358d3f25486c778746cc Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Thu, 6 Mar 2025 21:05:19 +1100 Subject: [PATCH 13/23] deps: update to env_logger 0.11 (#327) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index eef7e56..fbc62cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,6 @@ regex = ["env_logger/regex"] name = "quickcheck" [dependencies] -env_logger = { version = "0.8.2", default-features = false, optional = true } +env_logger = { version = "0.11", default-features = false, optional = true } log = { version = "0.4", optional = true } rand = { version = "0.8", default-features = false, features = ["getrandom", "small_rng"] } From 32d7bc4edf71419181649f5ce1c244ae691e4530 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Thu, 6 Mar 2025 05:08:04 -0500 Subject: [PATCH 14/23] Upgrade to 2021 edition --- Cargo.toml | 2 +- quickcheck_macros/Cargo.toml | 1 + quickcheck_macros/src/lib.rs | 5 ----- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fbc62cc..19596d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] categories = ["development-tools::testing"] license = "Unlicense OR MIT" exclude = ["/Makefile", "/ctags.rust", "/session.vim"] -edition = "2018" +edition = "2021" [workspace] members = ["quickcheck_macros"] diff --git a/quickcheck_macros/Cargo.toml b/quickcheck_macros/Cargo.toml index 4b990e6..2cf677c 100644 --- a/quickcheck_macros/Cargo.toml +++ b/quickcheck_macros/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/BurntSushi/quickcheck" readme = "../README.md" keywords = ["testing", "quickcheck", "property", "shrinking", "fuzz"] license = "Unlicense OR MIT" +edition = "2021" autotests = false [lib] diff --git a/quickcheck_macros/src/lib.rs b/quickcheck_macros/src/lib.rs index df16d19..2d4538f 100644 --- a/quickcheck_macros/src/lib.rs +++ b/quickcheck_macros/src/lib.rs @@ -1,8 +1,3 @@ -extern crate proc_macro; -extern crate proc_macro2; -extern crate quote; -extern crate syn; - use std::mem; use proc_macro::TokenStream; From 238f340a365b43d76a05087013f312851b35d762 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Thu, 6 Mar 2025 05:09:54 -0500 Subject: [PATCH 15/23] Bump MSRV to 1.71 Required by `env_logger` crate --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5786db7..2179706 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: include: - build: pinned os: ubuntu-latest - rust: 1.65.0 + rust: 1.71.0 - build: stable os: ubuntu-latest rust: stable diff --git a/README.md b/README.md index 1f60af7..be19191 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Crate features: ## Minimum Rust version policy -This crate's minimum supported `rustc` version is `1.65.0`. +This crate's minimum supported `rustc` version is `1.71.0`. The current policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if `crate 1.0` requires From 9ddbbd6b68ba95d51a05b0665e6b2a179b01a6d2 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Thu, 6 Mar 2025 11:12:43 +0100 Subject: [PATCH 16/23] deps: update to syn 2.0 (#317) --- quickcheck_macros/Cargo.toml | 2 +- quickcheck_macros/src/lib.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/quickcheck_macros/Cargo.toml b/quickcheck_macros/Cargo.toml index 2cf677c..f8495e7 100644 --- a/quickcheck_macros/Cargo.toml +++ b/quickcheck_macros/Cargo.toml @@ -20,7 +20,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0", features = ["full"] } [dev-dependencies] quickcheck = { path = "..", version = "1.0.0" } diff --git a/quickcheck_macros/src/lib.rs b/quickcheck_macros/src/lib.rs index 2d4538f..eda6ff4 100644 --- a/quickcheck_macros/src/lib.rs +++ b/quickcheck_macros/src/lib.rs @@ -28,6 +28,13 @@ pub fn quickcheck(_args: TokenStream, input: TokenStream) -> TokenStream { if errors.is_empty() { let attrs = mem::take(&mut item_fn.attrs); let name = &item_fn.sig.ident; + if let Some(variadic) = &item_fn.sig.variadic { + // variadics are just for `extern fn` + errors.push(syn::parse::Error::new( + variadic.span(), + "unsupported variadic", + )); + } let fn_type = syn::TypeBareFn { lifetimes: None, unsafety: item_fn.sig.unsafety, @@ -35,7 +42,7 @@ pub fn quickcheck(_args: TokenStream, input: TokenStream) -> TokenStream { fn_token: ::default(), paren_token: syn::token::Paren::default(), inputs, - variadic: item_fn.sig.variadic.clone(), + variadic: None, output: item_fn.sig.output.clone(), }; From 2c2cd21935e65232238595d476489c63463eb8ce Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Thu, 6 Mar 2025 05:51:50 -0500 Subject: [PATCH 17/23] Update to rand 0.9 --- Cargo.toml | 2 +- README.md | 30 ++-- examples/btree_set_range.rs | 3 +- src/arbitrary.rs | 288 +++++++++++++++++++++--------------- src/lib.rs | 4 +- src/tester.rs | 47 +++--- src/tests.rs | 10 +- 7 files changed, 221 insertions(+), 163 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19596d2..1733aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,4 @@ name = "quickcheck" [dependencies] env_logger = { version = "0.11", default-features = false, optional = true } log = { version = "0.4", optional = true } -rand = { version = "0.8", default-features = false, features = ["getrandom", "small_rng"] } +rand = { version = "0.9", default-features = false, features = ["os_rng", "small_rng"] } diff --git a/README.md b/README.md index be19191..8c55e45 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,8 @@ The API is fully documented: Here's an example that tests a function that reverses a vector: ```rust -#[cfg(test)] -#[macro_use] -extern crate quickcheck; - fn reverse(xs: &[T]) -> Vec { - let mut rev = vec!(); + let mut rev = vec![]; for x in xs.iter() { rev.insert(0, x.clone()) } @@ -43,6 +39,8 @@ fn reverse(xs: &[T]) -> Vec { #[cfg(test)] mod tests { + use quickcheck::quickcheck; + quickcheck! { fn prop(xs: Vec) -> bool { xs == reverse(&reverse(&xs)) @@ -63,16 +61,12 @@ To use the `#[quickcheck]` attribute, you must import the `quickcheck` macro from the `quickcheck_macros` crate: ```rust -#[cfg(test)] -extern crate quickcheck; -#[cfg(test)] -#[macro_use(quickcheck)] -extern crate quickcheck_macros; - #[cfg(test)] mod tests { + use quickcheck_macros::quickcheck; + fn reverse(xs: &[T]) -> Vec { - let mut rev = vec!(); + let mut rev = vec![]; for x in xs { rev.insert(0, x.clone()) } @@ -190,7 +184,7 @@ trait. Great, so what is this `Testable` business? ```rust pub trait Testable { - fn result(&self, &mut Gen) -> TestResult; + fn result(&self, &mut RandomSource) -> TestResult; } ``` @@ -202,7 +196,7 @@ Sure enough, `bool` satisfies the `Testable` trait: ```rust impl Testable for bool { - fn result(&self, _: &mut Gen) -> TestResult { + fn result(&self, _: &mut RandomSource) -> TestResult { TestResult::from_bool(*self) } } @@ -213,7 +207,7 @@ satisfy `Testable` too! ```rust impl Testable for fn(A) -> B { - fn result(&self, g: &mut Gen) -> TestResult { + fn result(&self, g: &mut RandomSource) -> TestResult { // elided } } @@ -231,7 +225,7 @@ make sure `TestResult` satisfies `Testable`: ```rust impl Testable for TestResult { - fn result(&self, _: &mut Gen) -> TestResult { self.clone() } + fn result(&self, _: &mut RandomSource) -> TestResult { self.clone() } } ``` @@ -397,10 +391,10 @@ In order to generate a random `Point` instance, you need to implement the trait `Arbitrary` for the struct `Point`: ```rust -use quickcheck::{Arbitrary, Gen}; +use quickcheck::{Arbitrary, RandomSource}; impl Arbitrary for Point { - fn arbitrary(g: &mut Gen) -> Point { + fn arbitrary(g: &mut RandomSource) -> Point { Point { x: i32::arbitrary(g), y: i32::arbitrary(g), diff --git a/examples/btree_set_range.rs b/examples/btree_set_range.rs index 954c991..bf52e24 100644 --- a/examples/btree_set_range.rs +++ b/examples/btree_set_range.rs @@ -37,7 +37,8 @@ fn panics(range: RangeAny) -> bool { } } -/// Checks that `BTreeSet::range` returns all items contained in the given `range`. +/// Checks that `BTreeSet::range` returns all items contained in the given +/// `range`. fn check_range(set: BTreeSet, range: RangeAny) -> TestResult { if panics(range) { TestResult::discard() diff --git a/src/arbitrary.rs b/src/arbitrary.rs index e08b7d3..7a92aac 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -21,33 +21,33 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use rand::seq::SliceRandom; -use rand::{self, Rng, SeedableRng}; +use rand::prelude::*; +use rand::{Rng, SeedableRng}; -/// Gen represents a PRNG. +/// `RandomSource` represents a PRNG. /// /// It is the source of randomness from which QuickCheck will generate -/// values. An instance of `Gen` is passed to every invocation of +/// values. An instance of `RandomSource` is passed to every invocation of /// `Arbitrary::arbitrary`, which permits callers to use lower level RNG /// routines to generate values. /// /// It is unspecified whether this is a secure RNG or not. Therefore, callers /// should assume it is insecure. -pub struct Gen { +pub struct RandomSource { rng: rand::rngs::SmallRng, size: usize, } -impl Gen { - /// Returns a `Gen` with the given size configuration. +impl RandomSource { + /// Returns a `RandomSource` with the given size configuration. /// /// The `size` parameter controls the size of random values generated. /// For example, it specifies the maximum length of a randomly generated /// vector, but is and should not be used to control the range of a /// randomly generated number. (Unless that number is used to control the /// size of a data structure.) - pub fn new(size: usize) -> Gen { - Gen { rng: rand::rngs::SmallRng::from_entropy(), size } + pub fn new(size: usize) -> RandomSource { + RandomSource { rng: rand::rngs::SmallRng::from_os_rng(), size } } /// Returns the size configured with this generator. @@ -62,19 +62,19 @@ impl Gen { slice.choose(&mut self.rng) } - fn gen(&mut self) -> T + fn random(&mut self) -> T where - rand::distributions::Standard: rand::distributions::Distribution, + rand::distr::StandardUniform: rand::distr::Distribution, { - self.rng.gen() + self.rng.random() } - fn gen_range(&mut self, range: R) -> T + fn random_range(&mut self, range: R) -> T where - T: rand::distributions::uniform::SampleUniform, - R: rand::distributions::uniform::SampleRange, + T: rand::distr::uniform::SampleUniform, + R: rand::distr::uniform::SampleRange, { - self.rng.gen_range(range) + self.rng.random_range(range) } } @@ -92,25 +92,25 @@ pub fn single_shrinker(value: A) -> Box> { /// shrunk. /// /// Aside from shrinking, `Arbitrary` is different from typical RNGs in that -/// it respects `Gen::size()` for controlling how much memory a particular -/// value uses, for practical purposes. For example, `Vec::arbitrary()` -/// respects `Gen::size()` to decide the maximum `len()` of the vector. -/// This behavior is necessary due to practical speed and size limitations. -/// Conversely, `i32::arbitrary()` ignores `size()` since all `i32` values -/// require `O(1)` memory and operations between `i32`s require `O(1)` time -/// (with the exception of exponentiation). +/// it respects `RandomSource::size()` for controlling how much memory a +/// particular value uses, for practical purposes. For example, +/// `Vec::arbitrary()` respects `RandomSource::size()` to decide the maximum +/// `len()` of the vector. This behavior is necessary due to practical speed +/// and size limitations. Conversely, `i32::arbitrary()` ignores `size()` since +/// all `i32` values require `O(1)` memory and operations between `i32`s +/// require `O(1)` time (with the exception of exponentiation). /// /// Additionally, all types that implement `Arbitrary` must also implement /// `Clone`. pub trait Arbitrary: Clone + 'static { /// Return an arbitrary value. /// - /// Implementations should respect `Gen::size()` when decisions about how - /// big a particular value should be. Implementations should generally - /// defer to other `Arbitrary` implementations to generate other random - /// values when necessary. The `Gen` type also offers a few RNG helper - /// routines. - fn arbitrary(g: &mut Gen) -> Self; + /// Implementations should respect `RandomSource::size()` when decisions + /// about how big a particular value should be. Implementations should + /// generally defer to other `Arbitrary` implementations to generate + /// other random values when necessary. The `RandomSource` type also + /// offers a few RNG helper routines. + fn arbitrary(g: &mut RandomSource) -> Self; /// Return an iterator of values that are smaller than itself. /// @@ -131,12 +131,12 @@ pub trait Arbitrary: Clone + 'static { } impl Arbitrary for () { - fn arbitrary(_: &mut Gen) {} + fn arbitrary(_: &mut RandomSource) {} } impl Arbitrary for bool { - fn arbitrary(g: &mut Gen) -> bool { - g.gen() + fn arbitrary(g: &mut RandomSource) -> bool { + g.random() } fn shrink(&self) -> Box> { @@ -149,8 +149,8 @@ impl Arbitrary for bool { } impl Arbitrary for Option { - fn arbitrary(g: &mut Gen) -> Option { - if g.gen() { + fn arbitrary(g: &mut RandomSource) -> Option { + if g.random() { None } else { Some(Arbitrary::arbitrary(g)) @@ -169,8 +169,8 @@ impl Arbitrary for Option { } impl Arbitrary for Result { - fn arbitrary(g: &mut Gen) -> Result { - if g.gen() { + fn arbitrary(g: &mut RandomSource) -> Result { + if g.random() { Ok(Arbitrary::arbitrary(g)) } else { Err(Arbitrary::arbitrary(g)) @@ -198,7 +198,7 @@ macro_rules! impl_arb_for_single_tuple { impl<$($type_param),*> Arbitrary for ($($type_param,)*) where $($type_param: Arbitrary,)* { - fn arbitrary(g: &mut Gen) -> ($($type_param,)*) { + fn arbitrary(g: &mut RandomSource) -> ($($type_param,)*) { ( $( $type_param::arbitrary(g), @@ -247,16 +247,16 @@ impl_arb_for_tuples! { } impl Arbitrary for [A; N] { - fn arbitrary(g: &mut Gen) -> Self { + fn arbitrary(g: &mut RandomSource) -> Self { std::array::from_fn(|_ix| A::arbitrary(g)) } } impl Arbitrary for Vec { - fn arbitrary(g: &mut Gen) -> Vec { + fn arbitrary(g: &mut RandomSource) -> Vec { let size = { let s = g.size(); - g.gen_range(0..s) + g.random_range(0..s) }; (0..size).map(|_| A::arbitrary(g)).collect() } @@ -368,7 +368,7 @@ where } impl Arbitrary for BTreeMap { - fn arbitrary(g: &mut Gen) -> BTreeMap { + fn arbitrary(g: &mut RandomSource) -> BTreeMap { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -387,7 +387,7 @@ impl< S: BuildHasher + Default + Clone + 'static, > Arbitrary for HashMap { - fn arbitrary(g: &mut Gen) -> Self { + fn arbitrary(g: &mut RandomSource) -> Self { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -399,7 +399,7 @@ impl< } impl Arbitrary for BTreeSet { - fn arbitrary(g: &mut Gen) -> BTreeSet { + fn arbitrary(g: &mut RandomSource) -> BTreeSet { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -411,7 +411,7 @@ impl Arbitrary for BTreeSet { } impl Arbitrary for BinaryHeap { - fn arbitrary(g: &mut Gen) -> BinaryHeap { + fn arbitrary(g: &mut RandomSource) -> BinaryHeap { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -427,7 +427,7 @@ impl Arbitrary for BinaryHeap { impl Arbitrary for HashSet { - fn arbitrary(g: &mut Gen) -> Self { + fn arbitrary(g: &mut RandomSource) -> Self { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -439,7 +439,7 @@ impl } impl Arbitrary for LinkedList { - fn arbitrary(g: &mut Gen) -> LinkedList { + fn arbitrary(g: &mut RandomSource) -> LinkedList { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -453,7 +453,7 @@ impl Arbitrary for LinkedList { } impl Arbitrary for VecDeque { - fn arbitrary(g: &mut Gen) -> VecDeque { + fn arbitrary(g: &mut RandomSource) -> VecDeque { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -465,8 +465,8 @@ impl Arbitrary for VecDeque { } impl Arbitrary for IpAddr { - fn arbitrary(g: &mut Gen) -> IpAddr { - let ipv4: bool = g.gen(); + fn arbitrary(g: &mut RandomSource) -> IpAddr { + let ipv4: bool = g.random(); if ipv4 { IpAddr::V4(Arbitrary::arbitrary(g)) } else { @@ -476,46 +476,51 @@ impl Arbitrary for IpAddr { } impl Arbitrary for Ipv4Addr { - fn arbitrary(g: &mut Gen) -> Ipv4Addr { - Ipv4Addr::new(g.gen(), g.gen(), g.gen(), g.gen()) + fn arbitrary(g: &mut RandomSource) -> Ipv4Addr { + Ipv4Addr::new(g.random(), g.random(), g.random(), g.random()) } } impl Arbitrary for Ipv6Addr { - fn arbitrary(g: &mut Gen) -> Ipv6Addr { + fn arbitrary(g: &mut RandomSource) -> Ipv6Addr { Ipv6Addr::new( - g.gen(), - g.gen(), - g.gen(), - g.gen(), - g.gen(), - g.gen(), - g.gen(), - g.gen(), + g.random(), + g.random(), + g.random(), + g.random(), + g.random(), + g.random(), + g.random(), + g.random(), ) } } impl Arbitrary for SocketAddr { - fn arbitrary(g: &mut Gen) -> SocketAddr { - SocketAddr::new(Arbitrary::arbitrary(g), g.gen()) + fn arbitrary(g: &mut RandomSource) -> SocketAddr { + SocketAddr::new(Arbitrary::arbitrary(g), g.random()) } } impl Arbitrary for SocketAddrV4 { - fn arbitrary(g: &mut Gen) -> SocketAddrV4 { - SocketAddrV4::new(Arbitrary::arbitrary(g), g.gen()) + fn arbitrary(g: &mut RandomSource) -> SocketAddrV4 { + SocketAddrV4::new(Arbitrary::arbitrary(g), g.random()) } } impl Arbitrary for SocketAddrV6 { - fn arbitrary(g: &mut Gen) -> SocketAddrV6 { - SocketAddrV6::new(Arbitrary::arbitrary(g), g.gen(), g.gen(), g.gen()) + fn arbitrary(g: &mut RandomSource) -> SocketAddrV6 { + SocketAddrV6::new( + Arbitrary::arbitrary(g), + g.random(), + g.random(), + g.random(), + ) } } impl Arbitrary for PathBuf { - fn arbitrary(g: &mut Gen) -> PathBuf { + fn arbitrary(g: &mut RandomSource) -> PathBuf { // use some real directories as guesses, so we may end up with // actual working directories in case that is relevant. let here = env::current_dir() @@ -567,7 +572,7 @@ impl Arbitrary for PathBuf { } impl Arbitrary for OsString { - fn arbitrary(g: &mut Gen) -> OsString { + fn arbitrary(g: &mut RandomSource) -> OsString { OsString::from(String::arbitrary(g)) } @@ -578,10 +583,10 @@ impl Arbitrary for OsString { } impl Arbitrary for String { - fn arbitrary(g: &mut Gen) -> String { + fn arbitrary(g: &mut RandomSource) -> String { let size = { let s = g.size(); - g.gen_range(0..s) + g.random_range(0..s) }; (0..size).map(|_| char::arbitrary(g)).collect() } @@ -594,13 +599,13 @@ impl Arbitrary for String { } impl Arbitrary for CString { - fn arbitrary(g: &mut Gen) -> Self { + fn arbitrary(g: &mut RandomSource) -> Self { let size = { let s = g.size(); - g.gen_range(0..s) + g.random_range(0..s) }; // Use either random bytes or random UTF-8 encoded codepoints. - let utf8: bool = g.gen(); + let utf8: bool = g.random(); if utf8 { CString::new( (0..) @@ -634,17 +639,18 @@ impl Arbitrary for CString { } impl Arbitrary for char { - fn arbitrary(g: &mut Gen) -> char { - let mode = g.gen_range(0..100); + fn arbitrary(g: &mut RandomSource) -> char { + let mode = g.random_range(0..100); match mode { 0..=49 => { // ASCII + some control characters - g.gen_range(0u8..0xB0) as char + g.random_range(0u8..0xB0) as char } 50..=59 => { // Unicode BMP characters loop { - if let Some(x) = char::from_u32(g.gen_range(0..0x10000)) { + if let Some(x) = char::from_u32(g.random_range(0..0x10000)) + { return x; } // ignore surrogate pairs @@ -720,11 +726,11 @@ impl Arbitrary for char { } 90..=94 => { // Tricky unicode, part 2 - char::from_u32(g.gen_range(0x2000..0x2070)).unwrap() + char::from_u32(g.random_range(0x2000..0x2070)).unwrap() } 95..=99 => { // Completely arbitrary characters - g.gen() + g.random() } _ => unreachable!(), } @@ -784,12 +790,10 @@ macro_rules! unsigned_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut Gen) -> $ty { - match g.gen_range(0..10) { - 0 => { - *g.choose(unsigned_problem_values!($ty)).unwrap() - }, - _ => g.gen() + fn arbitrary(g: &mut RandomSource) -> $ty { + match g.random_range(0..10) { + 0 => *g.choose(unsigned_problem_values!($ty)).unwrap(), + _ => g.random() } } fn shrink(&self) -> Box> { @@ -802,7 +806,33 @@ macro_rules! unsigned_arbitrary { } unsigned_arbitrary! { - usize, u8, u16, u32, u64, u128 + u8, u16, u32, u64, u128 +} + +impl Arbitrary for usize { + fn arbitrary(g: &mut RandomSource) -> usize { + match g.random_range(0..10) { + 0 => *g.choose(unsigned_problem_values!(usize)).unwrap(), + _ => { + #[cfg(target_pointer_width = "16")] + { + g.random::() as usize + } + #[cfg(target_pointer_width = "32")] + { + g.random::() as usize + } + #[cfg(target_pointer_width = "64")] + { + g.random::() as usize + } + } + } + } + fn shrink(&self) -> Box> { + unsigned_shrinker!(usize); + shrinker::UnsignedShrinker::new(*self) + } } macro_rules! signed_shrinker { @@ -857,12 +887,10 @@ macro_rules! signed_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut Gen) -> $ty { - match g.gen_range(0..10) { - 0 => { - *g.choose(signed_problem_values!($ty)).unwrap() - }, - _ => g.gen() + fn arbitrary(g: &mut RandomSource) -> $ty { + match g.random_range(0..10) { + 0 => *g.choose(signed_problem_values!($ty)).unwrap(), + _ => g.random() } } fn shrink(&self) -> Box> { @@ -875,7 +903,33 @@ macro_rules! signed_arbitrary { } signed_arbitrary! { - isize, i8, i16, i32, i64, i128 + i8, i16, i32, i64, i128 +} + +impl Arbitrary for isize { + fn arbitrary(g: &mut RandomSource) -> isize { + match g.random_range(0..10) { + 0 => *g.choose(signed_problem_values!(isize)).unwrap(), + _ => { + #[cfg(target_pointer_width = "16")] + { + g.random::() as isize + } + #[cfg(target_pointer_width = "32")] + { + g.random::() as isize + } + #[cfg(target_pointer_width = "64")] + { + g.random::() as isize + } + } + } + } + fn shrink(&self) -> Box> { + signed_shrinker!(isize); + shrinker::SignedShrinker::new(*self) + } } macro_rules! float_problem_values { @@ -895,12 +949,12 @@ macro_rules! float_problem_values { macro_rules! float_arbitrary { ($($t:ty, $shrinkable:ty),+) => {$( impl Arbitrary for $t { - fn arbitrary(g: &mut Gen) -> $t { - match g.gen_range(0..10) { + fn arbitrary(g: &mut RandomSource) -> $t { + match g.random_range(0..10) { 0 => *g.choose(float_problem_values!($t)).unwrap(), _ => { - let exp = g.gen_range((0.)..<$t>::MAX_EXP as i16 as $t); - let mantissa = g.gen_range((1.)..2.); + let exp = g.random_range((0.)..<$t>::MAX_EXP as i16 as $t); + let mantissa = g.random_range((1.)..2.); let sign = *g.choose(&[-1., 1.]).unwrap(); sign * mantissa * exp.exp2() } @@ -963,12 +1017,12 @@ macro_rules! unsigned_non_zero_arbitrary { ($($ty:tt => $inner:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut Gen) -> $ty { - let mut v: $inner = g.gen(); + fn arbitrary(g: &mut RandomSource) -> $ty { + let mut v = $inner::arbitrary(g); if v == 0 { v += 1; } - $ty::new(v).expect("non-zero value contsturction failed") + $ty::new(v).expect("non-zero value construction failed") } fn shrink(&self) -> Box> { @@ -992,7 +1046,7 @@ unsigned_non_zero_arbitrary! { } impl Arbitrary for Wrapping { - fn arbitrary(g: &mut Gen) -> Wrapping { + fn arbitrary(g: &mut RandomSource) -> Wrapping { Wrapping(T::arbitrary(g)) } fn shrink(&self) -> Box>> { @@ -1001,8 +1055,8 @@ impl Arbitrary for Wrapping { } impl Arbitrary for Bound { - fn arbitrary(g: &mut Gen) -> Bound { - match g.gen_range(0..3) { + fn arbitrary(g: &mut RandomSource) -> Bound { + match g.random_range(0..3) { 0 => Bound::Included(T::arbitrary(g)), 1 => Bound::Excluded(T::arbitrary(g)), _ => Bound::Unbounded, @@ -1022,7 +1076,7 @@ impl Arbitrary for Bound { } impl Arbitrary for Range { - fn arbitrary(g: &mut Gen) -> Range { + fn arbitrary(g: &mut RandomSource) -> Range { Arbitrary::arbitrary(g)..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1033,7 +1087,7 @@ impl Arbitrary for Range { } impl Arbitrary for RangeInclusive { - fn arbitrary(g: &mut Gen) -> RangeInclusive { + fn arbitrary(g: &mut RandomSource) -> RangeInclusive { Arbitrary::arbitrary(g)..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1046,7 +1100,7 @@ impl Arbitrary for RangeInclusive { } impl Arbitrary for RangeFrom { - fn arbitrary(g: &mut Gen) -> RangeFrom { + fn arbitrary(g: &mut RandomSource) -> RangeFrom { Arbitrary::arbitrary(g).. } fn shrink(&self) -> Box>> { @@ -1055,7 +1109,7 @@ impl Arbitrary for RangeFrom { } impl Arbitrary for RangeTo { - fn arbitrary(g: &mut Gen) -> RangeTo { + fn arbitrary(g: &mut RandomSource) -> RangeTo { ..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1064,7 +1118,7 @@ impl Arbitrary for RangeTo { } impl Arbitrary for RangeToInclusive { - fn arbitrary(g: &mut Gen) -> RangeToInclusive { + fn arbitrary(g: &mut RandomSource) -> RangeToInclusive { ..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1073,15 +1127,15 @@ impl Arbitrary for RangeToInclusive { } impl Arbitrary for RangeFull { - fn arbitrary(_: &mut Gen) -> RangeFull { + fn arbitrary(_: &mut RandomSource) -> RangeFull { .. } } impl Arbitrary for Duration { - fn arbitrary(gen: &mut Gen) -> Self { - let seconds = gen.gen_range(0..gen.size() as u64); - let nanoseconds = gen.gen_range(0..1_000_000); + fn arbitrary(gen: &mut RandomSource) -> Self { + let seconds = gen.random_range(0..gen.size() as u64); + let nanoseconds = gen.random_range(0..1_000_000); Duration::new(seconds, nanoseconds) } @@ -1095,7 +1149,7 @@ impl Arbitrary for Duration { } impl Arbitrary for Box { - fn arbitrary(g: &mut Gen) -> Box { + fn arbitrary(g: &mut RandomSource) -> Box { Box::new(A::arbitrary(g)) } @@ -1105,7 +1159,7 @@ impl Arbitrary for Box { } impl Arbitrary for Arc { - fn arbitrary(g: &mut Gen) -> Arc { + fn arbitrary(g: &mut RandomSource) -> Arc { Arc::new(A::arbitrary(g)) } @@ -1115,7 +1169,7 @@ impl Arbitrary for Arc { } impl Arbitrary for SystemTime { - fn arbitrary(gen: &mut Gen) -> Self { + fn arbitrary(gen: &mut RandomSource) -> Self { let after_epoch = bool::arbitrary(gen); let duration = Duration::arbitrary(gen); if after_epoch { @@ -1148,7 +1202,7 @@ mod test { use std::num::Wrapping; use std::path::PathBuf; - use super::{Arbitrary, Gen}; + use super::{Arbitrary, RandomSource}; #[test] fn arby_unit() { @@ -1234,7 +1288,7 @@ mod test { } fn arby() -> A { - Arbitrary::arbitrary(&mut Gen::new(5)) + Arbitrary::arbitrary(&mut RandomSource::new(5)) } // Shrink testing. diff --git a/src/lib.rs b/src/lib.rs index d3a5ba4..d8043d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,9 @@ new kind of witness being generated. These sorts of changes may happen in semver compatible releases. */ -pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; +pub use crate::arbitrary::{ + empty_shrinker, single_shrinker, Arbitrary, RandomSource, +}; pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; /// A macro for writing quickcheck tests. diff --git a/src/tester.rs b/src/tester.rs index ddbb07f..17f465a 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -5,15 +5,16 @@ use std::panic; use crate::{ tester::Status::{Discard, Fail, Pass}, - Arbitrary, Gen, + Arbitrary, RandomSource, }; -/// The main QuickCheck type for setting configuration and running QuickCheck. +/// The main `QuickCheck` type for setting configuration and running +/// `QuickCheck`. pub struct QuickCheck { tests: u64, max_tests: u64, min_tests_passed: u64, - gen: Gen, + gen: RandomSource, } fn qc_tests() -> u64 { @@ -55,17 +56,17 @@ impl Default for QuickCheck { } impl QuickCheck { - /// Creates a new QuickCheck value. + /// Creates a new `QuickCheck` value. /// - /// This can be used to run QuickCheck on things that implement `Testable`. - /// You may also adjust the configuration, such as the number of tests to - /// run. + /// This can be used to run `QuickCheck` on things that implement + /// `Testable`. You may also adjust the configuration, such as the + /// number of tests to run. /// /// By default, the maximum number of passed tests is set to `100`, the max /// number of overall tests is set to `10000` and the generator is created /// with a size of `100`. pub fn new() -> QuickCheck { - let gen = Gen::new(qc_gen_size()); + let gen = RandomSource::new(qc_gen_size()); let tests = qc_tests(); let max_tests = cmp::max(tests, qc_max_tests()); let min_tests_passed = qc_min_tests_passed(); @@ -73,8 +74,8 @@ impl QuickCheck { QuickCheck { tests, max_tests, min_tests_passed, gen } } - /// Set the random number generator to be used by QuickCheck. - pub fn gen(self, gen: Gen) -> QuickCheck { + /// Set the random number generator to be used by `QuickCheck`. + pub fn gen(self, gen: RandomSource) -> QuickCheck { QuickCheck { gen, ..self } } @@ -92,7 +93,7 @@ impl QuickCheck { /// Set the maximum number of tests to run. /// /// The number of invocations of a property will never exceed this number. - /// This is necessary to cap the number of tests because QuickCheck + /// This is necessary to cap the number of tests because `QuickCheck` /// properties can discard tests. pub fn max_tests(mut self, max_tests: u64) -> QuickCheck { self.max_tests = max_tests; @@ -143,7 +144,7 @@ impl QuickCheck { /// /// Note that if the environment variable `RUST_LOG` is set to enable /// `info` level log messages for the `quickcheck` crate, then this will - /// include output on how many QuickCheck tests were passed. + /// include output on how many `QuickCheck` tests were passed. /// /// # Example /// @@ -182,7 +183,7 @@ impl QuickCheck { } } -/// Convenience function for running QuickCheck. +/// Convenience function for running `QuickCheck`. /// /// This is an alias for `QuickCheck::new().quickcheck(f)`. pub fn quickcheck(f: A) { @@ -312,23 +313,23 @@ impl From for TestResult { /// /// It's unlikely that you'll have to implement this trait yourself. pub trait Testable: 'static { - fn result(&self, _: &mut Gen) -> TestResult; + fn result(&self, _: &mut RandomSource) -> TestResult; } impl Testable for bool { - fn result(&self, _: &mut Gen) -> TestResult { + fn result(&self, _: &mut RandomSource) -> TestResult { TestResult::from_bool(*self) } } impl Testable for () { - fn result(&self, _: &mut Gen) -> TestResult { + fn result(&self, _: &mut RandomSource) -> TestResult { TestResult::passed() } } impl Testable for TestResult { - fn result(&self, _: &mut Gen) -> TestResult { + fn result(&self, _: &mut RandomSource) -> TestResult { self.clone() } } @@ -338,7 +339,7 @@ where A: Testable, E: Debug + 'static, { - fn result(&self, g: &mut Gen) -> TestResult { + fn result(&self, g: &mut RandomSource) -> TestResult { match *self { Ok(ref r) => r.result(g), Err(ref err) => TestResult::error(format!("{err:?}")), @@ -357,9 +358,9 @@ macro_rules! testable_fn { impl Testable for fn($($name),*) -> T { #[allow(non_snake_case)] - fn result(&self, g: &mut Gen) -> TestResult { + fn result(&self, g: &mut RandomSource) -> TestResult { fn shrink_failure( - g: &mut Gen, + g: &mut RandomSource, self_: fn($($name),*) -> T, a: ($($name,)*), ) -> Option { @@ -428,7 +429,7 @@ where #[cfg(test)] mod test { - use crate::{Gen, QuickCheck}; + use crate::{QuickCheck, RandomSource}; #[test] fn shrinking_regression_issue_126() { @@ -447,7 +448,9 @@ mod test { fn t(_: i8) -> bool { true } - QuickCheck::new().gen(Gen::new(129)).quickcheck(t as fn(i8) -> bool); + QuickCheck::new() + .gen(RandomSource::new(129)) + .quickcheck(t as fn(i8) -> bool); } #[test] diff --git a/src/tests.rs b/src/tests.rs index d1df2c5..3cf29c5 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -4,7 +4,7 @@ use std::ffi::CString; use std::hash::BuildHasherDefault; use std::path::PathBuf; -use super::{quickcheck, Gen, QuickCheck, TestResult}; +use super::{quickcheck, QuickCheck, RandomSource, TestResult}; #[test] fn prop_oob() { @@ -178,7 +178,9 @@ fn regression_issue_83() { fn prop(_: u8) -> bool { true } - QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(u8) -> bool); + QuickCheck::new() + .gen(RandomSource::new(1024)) + .quickcheck(prop as fn(u8) -> bool); } #[test] @@ -186,7 +188,9 @@ fn regression_issue_83_signed() { fn prop(_: i8) -> bool { true } - QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(i8) -> bool); + QuickCheck::new() + .gen(RandomSource::new(1024)) + .quickcheck(prop as fn(i8) -> bool); } // Test that we can show the message after panic From a0216c932f33373b426f97188ee0960ae129b9e9 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Sat, 8 Mar 2025 01:58:28 -0500 Subject: [PATCH 18/23] Revert `Gen` renaming, rename `gen` method --- README.md | 12 ++-- src/arbitrary.rs | 142 +++++++++++++++++++++++------------------------ src/lib.rs | 4 +- src/tester.rs | 37 ++++++------ src/tests.rs | 6 +- 5 files changed, 102 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 8c55e45..8bef2cb 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ trait. Great, so what is this `Testable` business? ```rust pub trait Testable { - fn result(&self, &mut RandomSource) -> TestResult; + fn result(&self, &mut Gen) -> TestResult; } ``` @@ -196,7 +196,7 @@ Sure enough, `bool` satisfies the `Testable` trait: ```rust impl Testable for bool { - fn result(&self, _: &mut RandomSource) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { TestResult::from_bool(*self) } } @@ -207,7 +207,7 @@ satisfy `Testable` too! ```rust impl Testable for fn(A) -> B { - fn result(&self, g: &mut RandomSource) -> TestResult { + fn result(&self, g: &mut Gen) -> TestResult { // elided } } @@ -225,7 +225,7 @@ make sure `TestResult` satisfies `Testable`: ```rust impl Testable for TestResult { - fn result(&self, _: &mut RandomSource) -> TestResult { self.clone() } + fn result(&self, _: &mut Gen) -> TestResult { self.clone() } } ``` @@ -391,10 +391,10 @@ In order to generate a random `Point` instance, you need to implement the trait `Arbitrary` for the struct `Point`: ```rust -use quickcheck::{Arbitrary, RandomSource}; +use quickcheck::{Arbitrary, Gen}; impl Arbitrary for Point { - fn arbitrary(g: &mut RandomSource) -> Point { + fn arbitrary(g: &mut Gen) -> Point { Point { x: i32::arbitrary(g), y: i32::arbitrary(g), diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 7a92aac..9b2bb61 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -24,30 +24,30 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use rand::prelude::*; use rand::{Rng, SeedableRng}; -/// `RandomSource` represents a PRNG. +/// `Gen` represents a PRNG. /// -/// It is the source of randomness from which QuickCheck will generate -/// values. An instance of `RandomSource` is passed to every invocation of +/// It is the source of randomness from which QuickCheck will generate values. +/// An instance of `Gen` is passed to every invocation of /// `Arbitrary::arbitrary`, which permits callers to use lower level RNG /// routines to generate values. /// /// It is unspecified whether this is a secure RNG or not. Therefore, callers /// should assume it is insecure. -pub struct RandomSource { +pub struct Gen { rng: rand::rngs::SmallRng, size: usize, } -impl RandomSource { - /// Returns a `RandomSource` with the given size configuration. +impl Gen { + /// Returns a `Gen` with the given size configuration. /// /// The `size` parameter controls the size of random values generated. /// For example, it specifies the maximum length of a randomly generated /// vector, but is and should not be used to control the range of a /// randomly generated number. (Unless that number is used to control the /// size of a data structure.) - pub fn new(size: usize) -> RandomSource { - RandomSource { rng: rand::rngs::SmallRng::from_os_rng(), size } + pub fn new(size: usize) -> Gen { + Gen { rng: rand::rngs::SmallRng::from_os_rng(), size } } /// Returns the size configured with this generator. @@ -91,26 +91,26 @@ pub fn single_shrinker(value: A) -> Box> { /// `Arbitrary` describes types whose values can be randomly generated and /// shrunk. /// -/// Aside from shrinking, `Arbitrary` is different from typical RNGs in that -/// it respects `RandomSource::size()` for controlling how much memory a -/// particular value uses, for practical purposes. For example, -/// `Vec::arbitrary()` respects `RandomSource::size()` to decide the maximum -/// `len()` of the vector. This behavior is necessary due to practical speed -/// and size limitations. Conversely, `i32::arbitrary()` ignores `size()` since -/// all `i32` values require `O(1)` memory and operations between `i32`s -/// require `O(1)` time (with the exception of exponentiation). +/// Aside from shrinking, `Arbitrary` is different from typical RNGs in that it +/// respects `Gen::size()` for controlling how much memory a particular value +/// uses, for practical purposes. For example, `Vec::arbitrary()` respects +/// `Gen::size()` to decide the maximum `len()` of the vector. This behavior is +/// necessary due to practical speed and size limitations. Conversely, +/// `i32::arbitrary()` ignores `size()` since all `i32` values require `O(1)` +/// memory and operations between `i32`s require `O(1)` time (with the +/// exception of exponentiation). /// /// Additionally, all types that implement `Arbitrary` must also implement /// `Clone`. pub trait Arbitrary: Clone + 'static { /// Return an arbitrary value. /// - /// Implementations should respect `RandomSource::size()` when decisions - /// about how big a particular value should be. Implementations should - /// generally defer to other `Arbitrary` implementations to generate - /// other random values when necessary. The `RandomSource` type also - /// offers a few RNG helper routines. - fn arbitrary(g: &mut RandomSource) -> Self; + /// Implementations should respect `Gen::size()` when decisions about how + /// big a particular value should be. Implementations should generally + /// defer to other `Arbitrary` implementations to generate other random + /// values when necessary. The `Gen` type also offers a few RNG helper + /// routines. + fn arbitrary(g: &mut Gen) -> Self; /// Return an iterator of values that are smaller than itself. /// @@ -131,11 +131,11 @@ pub trait Arbitrary: Clone + 'static { } impl Arbitrary for () { - fn arbitrary(_: &mut RandomSource) {} + fn arbitrary(_: &mut Gen) {} } impl Arbitrary for bool { - fn arbitrary(g: &mut RandomSource) -> bool { + fn arbitrary(g: &mut Gen) -> bool { g.random() } @@ -149,7 +149,7 @@ impl Arbitrary for bool { } impl Arbitrary for Option { - fn arbitrary(g: &mut RandomSource) -> Option { + fn arbitrary(g: &mut Gen) -> Option { if g.random() { None } else { @@ -169,7 +169,7 @@ impl Arbitrary for Option { } impl Arbitrary for Result { - fn arbitrary(g: &mut RandomSource) -> Result { + fn arbitrary(g: &mut Gen) -> Result { if g.random() { Ok(Arbitrary::arbitrary(g)) } else { @@ -198,7 +198,7 @@ macro_rules! impl_arb_for_single_tuple { impl<$($type_param),*> Arbitrary for ($($type_param,)*) where $($type_param: Arbitrary,)* { - fn arbitrary(g: &mut RandomSource) -> ($($type_param,)*) { + fn arbitrary(g: &mut Gen) -> ($($type_param,)*) { ( $( $type_param::arbitrary(g), @@ -247,13 +247,13 @@ impl_arb_for_tuples! { } impl Arbitrary for [A; N] { - fn arbitrary(g: &mut RandomSource) -> Self { + fn arbitrary(g: &mut Gen) -> Self { std::array::from_fn(|_ix| A::arbitrary(g)) } } impl Arbitrary for Vec { - fn arbitrary(g: &mut RandomSource) -> Vec { + fn arbitrary(g: &mut Gen) -> Vec { let size = { let s = g.size(); g.random_range(0..s) @@ -368,7 +368,7 @@ where } impl Arbitrary for BTreeMap { - fn arbitrary(g: &mut RandomSource) -> BTreeMap { + fn arbitrary(g: &mut Gen) -> BTreeMap { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -387,7 +387,7 @@ impl< S: BuildHasher + Default + Clone + 'static, > Arbitrary for HashMap { - fn arbitrary(g: &mut RandomSource) -> Self { + fn arbitrary(g: &mut Gen) -> Self { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -399,7 +399,7 @@ impl< } impl Arbitrary for BTreeSet { - fn arbitrary(g: &mut RandomSource) -> BTreeSet { + fn arbitrary(g: &mut Gen) -> BTreeSet { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -411,7 +411,7 @@ impl Arbitrary for BTreeSet { } impl Arbitrary for BinaryHeap { - fn arbitrary(g: &mut RandomSource) -> BinaryHeap { + fn arbitrary(g: &mut Gen) -> BinaryHeap { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -427,7 +427,7 @@ impl Arbitrary for BinaryHeap { impl Arbitrary for HashSet { - fn arbitrary(g: &mut RandomSource) -> Self { + fn arbitrary(g: &mut Gen) -> Self { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -439,7 +439,7 @@ impl } impl Arbitrary for LinkedList { - fn arbitrary(g: &mut RandomSource) -> LinkedList { + fn arbitrary(g: &mut Gen) -> LinkedList { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -453,7 +453,7 @@ impl Arbitrary for LinkedList { } impl Arbitrary for VecDeque { - fn arbitrary(g: &mut RandomSource) -> VecDeque { + fn arbitrary(g: &mut Gen) -> VecDeque { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -465,7 +465,7 @@ impl Arbitrary for VecDeque { } impl Arbitrary for IpAddr { - fn arbitrary(g: &mut RandomSource) -> IpAddr { + fn arbitrary(g: &mut Gen) -> IpAddr { let ipv4: bool = g.random(); if ipv4 { IpAddr::V4(Arbitrary::arbitrary(g)) @@ -476,13 +476,13 @@ impl Arbitrary for IpAddr { } impl Arbitrary for Ipv4Addr { - fn arbitrary(g: &mut RandomSource) -> Ipv4Addr { + fn arbitrary(g: &mut Gen) -> Ipv4Addr { Ipv4Addr::new(g.random(), g.random(), g.random(), g.random()) } } impl Arbitrary for Ipv6Addr { - fn arbitrary(g: &mut RandomSource) -> Ipv6Addr { + fn arbitrary(g: &mut Gen) -> Ipv6Addr { Ipv6Addr::new( g.random(), g.random(), @@ -497,19 +497,19 @@ impl Arbitrary for Ipv6Addr { } impl Arbitrary for SocketAddr { - fn arbitrary(g: &mut RandomSource) -> SocketAddr { + fn arbitrary(g: &mut Gen) -> SocketAddr { SocketAddr::new(Arbitrary::arbitrary(g), g.random()) } } impl Arbitrary for SocketAddrV4 { - fn arbitrary(g: &mut RandomSource) -> SocketAddrV4 { + fn arbitrary(g: &mut Gen) -> SocketAddrV4 { SocketAddrV4::new(Arbitrary::arbitrary(g), g.random()) } } impl Arbitrary for SocketAddrV6 { - fn arbitrary(g: &mut RandomSource) -> SocketAddrV6 { + fn arbitrary(g: &mut Gen) -> SocketAddrV6 { SocketAddrV6::new( Arbitrary::arbitrary(g), g.random(), @@ -520,7 +520,7 @@ impl Arbitrary for SocketAddrV6 { } impl Arbitrary for PathBuf { - fn arbitrary(g: &mut RandomSource) -> PathBuf { + fn arbitrary(g: &mut Gen) -> PathBuf { // use some real directories as guesses, so we may end up with // actual working directories in case that is relevant. let here = env::current_dir() @@ -572,7 +572,7 @@ impl Arbitrary for PathBuf { } impl Arbitrary for OsString { - fn arbitrary(g: &mut RandomSource) -> OsString { + fn arbitrary(g: &mut Gen) -> OsString { OsString::from(String::arbitrary(g)) } @@ -583,7 +583,7 @@ impl Arbitrary for OsString { } impl Arbitrary for String { - fn arbitrary(g: &mut RandomSource) -> String { + fn arbitrary(g: &mut Gen) -> String { let size = { let s = g.size(); g.random_range(0..s) @@ -599,7 +599,7 @@ impl Arbitrary for String { } impl Arbitrary for CString { - fn arbitrary(g: &mut RandomSource) -> Self { + fn arbitrary(g: &mut Gen) -> Self { let size = { let s = g.size(); g.random_range(0..s) @@ -639,7 +639,7 @@ impl Arbitrary for CString { } impl Arbitrary for char { - fn arbitrary(g: &mut RandomSource) -> char { + fn arbitrary(g: &mut Gen) -> char { let mode = g.random_range(0..100); match mode { 0..=49 => { @@ -790,7 +790,7 @@ macro_rules! unsigned_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut RandomSource) -> $ty { + fn arbitrary(g: &mut Gen) -> $ty { match g.random_range(0..10) { 0 => *g.choose(unsigned_problem_values!($ty)).unwrap(), _ => g.random() @@ -810,7 +810,7 @@ unsigned_arbitrary! { } impl Arbitrary for usize { - fn arbitrary(g: &mut RandomSource) -> usize { + fn arbitrary(g: &mut Gen) -> usize { match g.random_range(0..10) { 0 => *g.choose(unsigned_problem_values!(usize)).unwrap(), _ => { @@ -887,7 +887,7 @@ macro_rules! signed_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut RandomSource) -> $ty { + fn arbitrary(g: &mut Gen) -> $ty { match g.random_range(0..10) { 0 => *g.choose(signed_problem_values!($ty)).unwrap(), _ => g.random() @@ -907,7 +907,7 @@ signed_arbitrary! { } impl Arbitrary for isize { - fn arbitrary(g: &mut RandomSource) -> isize { + fn arbitrary(g: &mut Gen) -> isize { match g.random_range(0..10) { 0 => *g.choose(signed_problem_values!(isize)).unwrap(), _ => { @@ -949,7 +949,7 @@ macro_rules! float_problem_values { macro_rules! float_arbitrary { ($($t:ty, $shrinkable:ty),+) => {$( impl Arbitrary for $t { - fn arbitrary(g: &mut RandomSource) -> $t { + fn arbitrary(g: &mut Gen) -> $t { match g.random_range(0..10) { 0 => *g.choose(float_problem_values!($t)).unwrap(), _ => { @@ -1017,7 +1017,7 @@ macro_rules! unsigned_non_zero_arbitrary { ($($ty:tt => $inner:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut RandomSource) -> $ty { + fn arbitrary(g: &mut Gen) -> $ty { let mut v = $inner::arbitrary(g); if v == 0 { v += 1; @@ -1046,7 +1046,7 @@ unsigned_non_zero_arbitrary! { } impl Arbitrary for Wrapping { - fn arbitrary(g: &mut RandomSource) -> Wrapping { + fn arbitrary(g: &mut Gen) -> Wrapping { Wrapping(T::arbitrary(g)) } fn shrink(&self) -> Box>> { @@ -1055,7 +1055,7 @@ impl Arbitrary for Wrapping { } impl Arbitrary for Bound { - fn arbitrary(g: &mut RandomSource) -> Bound { + fn arbitrary(g: &mut Gen) -> Bound { match g.random_range(0..3) { 0 => Bound::Included(T::arbitrary(g)), 1 => Bound::Excluded(T::arbitrary(g)), @@ -1076,7 +1076,7 @@ impl Arbitrary for Bound { } impl Arbitrary for Range { - fn arbitrary(g: &mut RandomSource) -> Range { + fn arbitrary(g: &mut Gen) -> Range { Arbitrary::arbitrary(g)..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1087,7 +1087,7 @@ impl Arbitrary for Range { } impl Arbitrary for RangeInclusive { - fn arbitrary(g: &mut RandomSource) -> RangeInclusive { + fn arbitrary(g: &mut Gen) -> RangeInclusive { Arbitrary::arbitrary(g)..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1100,7 +1100,7 @@ impl Arbitrary for RangeInclusive { } impl Arbitrary for RangeFrom { - fn arbitrary(g: &mut RandomSource) -> RangeFrom { + fn arbitrary(g: &mut Gen) -> RangeFrom { Arbitrary::arbitrary(g).. } fn shrink(&self) -> Box>> { @@ -1109,7 +1109,7 @@ impl Arbitrary for RangeFrom { } impl Arbitrary for RangeTo { - fn arbitrary(g: &mut RandomSource) -> RangeTo { + fn arbitrary(g: &mut Gen) -> RangeTo { ..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1118,7 +1118,7 @@ impl Arbitrary for RangeTo { } impl Arbitrary for RangeToInclusive { - fn arbitrary(g: &mut RandomSource) -> RangeToInclusive { + fn arbitrary(g: &mut Gen) -> RangeToInclusive { ..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1127,15 +1127,15 @@ impl Arbitrary for RangeToInclusive { } impl Arbitrary for RangeFull { - fn arbitrary(_: &mut RandomSource) -> RangeFull { + fn arbitrary(_: &mut Gen) -> RangeFull { .. } } impl Arbitrary for Duration { - fn arbitrary(gen: &mut RandomSource) -> Self { - let seconds = gen.random_range(0..gen.size() as u64); - let nanoseconds = gen.random_range(0..1_000_000); + fn arbitrary(rng: &mut Gen) -> Self { + let seconds = rng.random_range(0..rng.size() as u64); + let nanoseconds = rng.random_range(0..1_000_000); Duration::new(seconds, nanoseconds) } @@ -1149,7 +1149,7 @@ impl Arbitrary for Duration { } impl Arbitrary for Box { - fn arbitrary(g: &mut RandomSource) -> Box { + fn arbitrary(g: &mut Gen) -> Box { Box::new(A::arbitrary(g)) } @@ -1159,7 +1159,7 @@ impl Arbitrary for Box { } impl Arbitrary for Arc { - fn arbitrary(g: &mut RandomSource) -> Arc { + fn arbitrary(g: &mut Gen) -> Arc { Arc::new(A::arbitrary(g)) } @@ -1169,9 +1169,9 @@ impl Arbitrary for Arc { } impl Arbitrary for SystemTime { - fn arbitrary(gen: &mut RandomSource) -> Self { - let after_epoch = bool::arbitrary(gen); - let duration = Duration::arbitrary(gen); + fn arbitrary(rng: &mut Gen) -> Self { + let after_epoch = bool::arbitrary(rng); + let duration = Duration::arbitrary(rng); if after_epoch { UNIX_EPOCH + duration } else { @@ -1202,7 +1202,7 @@ mod test { use std::num::Wrapping; use std::path::PathBuf; - use super::{Arbitrary, RandomSource}; + use super::{Arbitrary, Gen}; #[test] fn arby_unit() { @@ -1288,7 +1288,7 @@ mod test { } fn arby() -> A { - Arbitrary::arbitrary(&mut RandomSource::new(5)) + Arbitrary::arbitrary(&mut Gen::new(5)) } // Shrink testing. diff --git a/src/lib.rs b/src/lib.rs index d8043d5..d3a5ba4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,7 @@ new kind of witness being generated. These sorts of changes may happen in semver compatible releases. */ -pub use crate::arbitrary::{ - empty_shrinker, single_shrinker, Arbitrary, RandomSource, -}; +pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; /// A macro for writing quickcheck tests. diff --git a/src/tester.rs b/src/tester.rs index 17f465a..f09326c 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -5,7 +5,7 @@ use std::panic; use crate::{ tester::Status::{Discard, Fail, Pass}, - Arbitrary, RandomSource, + Arbitrary, Gen, }; /// The main `QuickCheck` type for setting configuration and running @@ -14,7 +14,7 @@ pub struct QuickCheck { tests: u64, max_tests: u64, min_tests_passed: u64, - gen: RandomSource, + rng: Gen, } fn qc_tests() -> u64 { @@ -66,17 +66,22 @@ impl QuickCheck { /// number of overall tests is set to `10000` and the generator is created /// with a size of `100`. pub fn new() -> QuickCheck { - let gen = RandomSource::new(qc_gen_size()); + let rng = Gen::new(qc_gen_size()); let tests = qc_tests(); let max_tests = cmp::max(tests, qc_max_tests()); let min_tests_passed = qc_min_tests_passed(); - QuickCheck { tests, max_tests, min_tests_passed, gen } + QuickCheck { tests, max_tests, min_tests_passed, rng } } /// Set the random number generator to be used by `QuickCheck`. - pub fn gen(self, gen: RandomSource) -> QuickCheck { - QuickCheck { gen, ..self } + pub fn set_rng(self, rng: Gen) -> QuickCheck { + QuickCheck { rng, ..self } + } + + #[deprecated(since = "1.1.0", note = "use `set_rng` instead")] + pub fn r#gen(self, rng: Gen) -> QuickCheck { + self.set_rng(rng) } /// Set the number of tests to run. @@ -125,7 +130,7 @@ impl QuickCheck { if n_tests_passed >= self.tests { break; } - match f.result(&mut self.gen) { + match f.result(&mut self.rng) { TestResult { status: Pass, .. } => n_tests_passed += 1, TestResult { status: Discard, .. } => continue, r @ TestResult { status: Fail, .. } => return Err(r), @@ -313,23 +318,23 @@ impl From for TestResult { /// /// It's unlikely that you'll have to implement this trait yourself. pub trait Testable: 'static { - fn result(&self, _: &mut RandomSource) -> TestResult; + fn result(&self, _: &mut Gen) -> TestResult; } impl Testable for bool { - fn result(&self, _: &mut RandomSource) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { TestResult::from_bool(*self) } } impl Testable for () { - fn result(&self, _: &mut RandomSource) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { TestResult::passed() } } impl Testable for TestResult { - fn result(&self, _: &mut RandomSource) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { self.clone() } } @@ -339,7 +344,7 @@ where A: Testable, E: Debug + 'static, { - fn result(&self, g: &mut RandomSource) -> TestResult { + fn result(&self, g: &mut Gen) -> TestResult { match *self { Ok(ref r) => r.result(g), Err(ref err) => TestResult::error(format!("{err:?}")), @@ -358,9 +363,9 @@ macro_rules! testable_fn { impl Testable for fn($($name),*) -> T { #[allow(non_snake_case)] - fn result(&self, g: &mut RandomSource) -> TestResult { + fn result(&self, g: &mut Gen) -> TestResult { fn shrink_failure( - g: &mut RandomSource, + g: &mut Gen, self_: fn($($name),*) -> T, a: ($($name,)*), ) -> Option { @@ -429,7 +434,7 @@ where #[cfg(test)] mod test { - use crate::{QuickCheck, RandomSource}; + use crate::{Gen, QuickCheck}; #[test] fn shrinking_regression_issue_126() { @@ -449,7 +454,7 @@ mod test { true } QuickCheck::new() - .gen(RandomSource::new(129)) + .set_rng(Gen::new(129)) .quickcheck(t as fn(i8) -> bool); } diff --git a/src/tests.rs b/src/tests.rs index 3cf29c5..e1b2f6e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -4,7 +4,7 @@ use std::ffi::CString; use std::hash::BuildHasherDefault; use std::path::PathBuf; -use super::{quickcheck, QuickCheck, RandomSource, TestResult}; +use super::{quickcheck, Gen, QuickCheck, TestResult}; #[test] fn prop_oob() { @@ -179,7 +179,7 @@ fn regression_issue_83() { true } QuickCheck::new() - .gen(RandomSource::new(1024)) + .set_rng(Gen::new(1024)) .quickcheck(prop as fn(u8) -> bool); } @@ -189,7 +189,7 @@ fn regression_issue_83_signed() { true } QuickCheck::new() - .gen(RandomSource::new(1024)) + .set_rng(Gen::new(1024)) .quickcheck(prop as fn(i8) -> bool); } From 87b46b90ec0a951ff0caceb83243b91e861eba6c Mon Sep 17 00:00:00 2001 From: Alex Touchet <26315797+atouchet@users.noreply.github.com> Date: Fri, 7 Mar 2025 23:08:24 -0800 Subject: [PATCH 19/23] Update some links (#332) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8bef2cb..a5693a3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Haskell](https://hackage.haskell.org/package/QuickCheck).) [![Build status](https://github.com/BurntSushi/quickcheck/workflows/ci/badge.svg)](https://github.com/BurntSushi/quickcheck/actions) [![crates.io](https://img.shields.io/crates/v/quickcheck.svg)](https://crates.io/crates/quickcheck) -Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org). +Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org/). ## Documentation @@ -146,11 +146,11 @@ semver compatible releases. ## Alternative Rust crates for property testing The [`proptest`](https://docs.rs/proptest) crate is inspired by the -[Hypothesis](https://hypothesis.works) framework for Python. +[Hypothesis](https://hypothesis.works/) framework for Python. You can read a comparison between `proptest` and `quickcheck` -[here](https://github.com/AltSysrq/proptest/blob/master/proptest/README.md#differences-between-quickcheck-and-proptest) +[here](https://github.com/proptest-rs/proptest/blob/main/proptest/README.md#differences-between-quickcheck-and-proptest) and -[here](https://github.com/AltSysrq/proptest/issues/15#issuecomment-348382287). +[here](https://github.com/proptest-rs/proptest/issues/15#issuecomment-348382287). In particular, `proptest` improves on the concept of shrinking. So if you've ever had problems/frustration with shrinking in `quickcheck`, then `proptest` might be worth a try! From 826f10baa1f66506b770fed231453266c4bda5b1 Mon Sep 17 00:00:00 2001 From: Mathspy Date: Sat, 8 Mar 2025 02:09:40 -0500 Subject: [PATCH 20/23] Add shrinking support for arrays (#330) --- src/arbitrary.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 9b2bb61..897641a 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -250,6 +250,20 @@ impl Arbitrary for [A; N] { fn arbitrary(g: &mut Gen) -> Self { std::array::from_fn(|_ix| A::arbitrary(g)) } + + fn shrink(&self) -> Box> { + let cloned = self.clone(); + let iter = (0..N).flat_map(move |n| { + let cloned = cloned.clone(); + cloned[n].shrink().map(move |shr_value| { + let mut result = cloned.clone(); + result[n] = shr_value; + result + }) + }); + + Box::new(iter) + } } impl Arbitrary for Vec { From 03ab5858656b50d1ef5ce27ceebfd7402ea4494a Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Sat, 8 Mar 2025 02:18:37 -0500 Subject: [PATCH 21/23] Fix README examples --- README.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a5693a3..c4bf1bc 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,12 @@ fn reverse(xs: &[T]) -> Vec { #[cfg(test)] mod tests { use quickcheck::quickcheck; + use super::reverse; - quickcheck! { - fn prop(xs: Vec) -> bool { - xs == reverse(&reverse(&xs)) - } + quickcheck! { + fn prop(xs: Vec) -> bool { + xs == reverse(&reverse(&xs)) + } } } ``` @@ -61,17 +62,18 @@ To use the `#[quickcheck]` attribute, you must import the `quickcheck` macro from the `quickcheck_macros` crate: ```rust +fn reverse(xs: &[T]) -> Vec { + let mut rev = vec![]; + for x in xs { + rev.insert(0, x.clone()) + } + rev +} + #[cfg(test)] mod tests { use quickcheck_macros::quickcheck; - - fn reverse(xs: &[T]) -> Vec { - let mut rev = vec![]; - for x in xs { - rev.insert(0, x.clone()) - } - rev - } + use super::reverse; #[quickcheck] fn double_reversal_is_identity(xs: Vec) -> bool { From 473b6a0639aee631e8935fc2c55ab618d95326e7 Mon Sep 17 00:00:00 2001 From: Julian Ganz Date: Sat, 12 Jun 2021 13:12:53 +0200 Subject: [PATCH 22/23] impl: do recursive shrinking without recursive fn call We try to shrink values recursively, i.e. when a shrunk value witnesses a failure, we'd shrink that value further. Previously, this recursion would be implemented via actual control flow recursion, i.e. a function calling itself. Since the recursion could not be unrolled by the compiler, this could result in stack overflows in some situations. Albeit such an overflow would often hint at a faulty shrinker (e.g. a shrinker yielding the original value), the stack overflow could also occur in other situations. This change switches from a recursive control flow to explicitly swapping out the shrinking iterator during the iteration. --- src/tester.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/tester.rs b/src/tester.rs index f09326c..75f9cc1 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -369,7 +369,9 @@ impl T, a: ($($name,)*), ) -> Option { - for t in a.shrink() { + let mut r = Default::default(); + let mut a = a.shrink(); + while let Some(t) = a.next() { let ($($name,)*) = t.clone(); let mut r_new = safe(move || {self_($($name),*)}).result(g); if r_new.is_failure() { @@ -378,16 +380,16 @@ impl Date: Sat, 12 Jun 2021 15:31:49 +0200 Subject: [PATCH 23/23] impl: move shrinking logic out of inline function In the past, shrinking was implemented using recursion in the control flow. `shrink_failure` would call itself. That function was introduced originally in 5b19e7c21e1afdcabb894668ad61be70bb023ef0 presumably in order to implement recursive shrinking. However, we recently choose an approach which would not rely on recursive control flow but on swapping out an iterator. Thus, the reason why `shrink_failure` existed in the first place doesn't exist any more. This change moves the logic in its original place, but also replaces the `match` which enclosed the call to `shrink_failure` with an `if`. --- src/tester.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/tester.rs b/src/tester.rs index 75f9cc1..5f70f86 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -364,12 +364,12 @@ impl Testable for fn($($name),*) -> T { #[allow(non_snake_case)] fn result(&self, g: &mut Gen) -> TestResult { - fn shrink_failure( - g: &mut Gen, - self_: fn($($name),*) -> T, - a: ($($name,)*), - ) -> Option { - let mut r = Default::default(); + let self_ = *self; + let a: ($($name,)*) = Arbitrary::arbitrary(g); + let ( $($name,)* ) = a.clone(); + let mut r = safe(move || {self_($($name),*)}).result(g); + + if r.is_failure() { let mut a = a.shrink(); while let Some(t) = a.next() { let ($($name,)*) = t.clone(); @@ -382,26 +382,16 @@ impl r, - Fail => { - shrink_failure(g, self_, a).unwrap_or(r) - } - } + r } }}}