From c30967674aeb1c70b71e3aff2be47f733534538d Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sat, 5 Dec 2020 12:40:04 -0500 Subject: [PATCH 1/8] api: remove Send bound from Arbitrary/Testable traits The Send bound is a relic from the past. Indeed, the docs for the Arbitrary trait have been outdated for quite some time. quickcheck stopped running each test in a separate thread once `std::panic::catch_unwind` was stabilized many moons ago. With `catch_unwind`, the `Send` bound is no longer necessary. We do need to retain the `'static` bound though. Without that, implementing shrink seems implausible. Fixes #262, Closes #263 --- src/arbitrary.rs | 14 ++++---------- src/tester.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 4693ce8..e8f0b5f 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -142,11 +142,7 @@ pub fn single_shrinker(value: A) -> Box> { /// /// As of now, all types that implement `Arbitrary` must also implement /// `Clone`. (I'm not sure if this is a permanent restriction.) -/// -/// They must also be sendable and static since every test is run in its own -/// thread using `thread::Builder::spawn`, which requires the `Send + 'static` -/// bounds. -pub trait Arbitrary: Clone + Send + 'static { +pub trait Arbitrary: Clone + 'static { fn arbitrary(g: &mut G) -> Self; fn shrink(&self) -> Box> { @@ -401,7 +397,7 @@ impl Arbitrary for BTreeMap { impl< K: Arbitrary + Eq + Hash, V: Arbitrary, - S: BuildHasher + Default + Clone + Send + 'static, + S: BuildHasher + Default + Clone + 'static, > Arbitrary for HashMap { fn arbitrary(g: &mut G) -> Self { @@ -441,10 +437,8 @@ impl Arbitrary for BinaryHeap { } } -impl< - T: Arbitrary + Eq + Hash, - S: BuildHasher + Default + Clone + Send + 'static, - > Arbitrary for HashSet +impl + Arbitrary for HashSet { fn arbitrary(g: &mut G) -> Self { let vec: Vec = Arbitrary::arbitrary(g); diff --git a/src/tester.rs b/src/tester.rs index ff66fef..491cb48 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -256,8 +256,8 @@ impl TestResult { pub fn must_fail(f: F) -> TestResult where F: FnOnce() -> T, - F: Send + 'static, - T: Send + 'static, + F: 'static, + T: 'static, { let f = panic::AssertUnwindSafe(f); TestResult::from_bool(panic::catch_unwind(f).is_err()) @@ -305,7 +305,7 @@ impl TestResult { /// and potentially shrink those arguments if they produce a failure. /// /// It's unlikely that you'll have to implement this trait yourself. -pub trait Testable: Send + 'static { +pub trait Testable: 'static { fn result(&self, _: &mut G) -> TestResult; } @@ -330,7 +330,7 @@ impl Testable for TestResult { impl Testable for Result where A: Testable, - E: Debug + Send + 'static, + E: Debug + 'static, { fn result(&self, g: &mut G) -> TestResult { match *self { @@ -409,8 +409,8 @@ testable_fn!(A, B, C, D, E, F, G, H); fn safe(fun: F) -> Result where F: FnOnce() -> T, - F: Send + 'static, - T: Send + 'static, + F: 'static, + T: 'static, { panic::catch_unwind(panic::AssertUnwindSafe(fun)).map_err(|any_err| { // Extract common types of panic payload: From a699f79967961ff95dd599ef048a46e61a5b7267 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sun, 27 Dec 2020 12:20:25 -0500 Subject: [PATCH 2/8] macro: add license files Closes #258 --- quickcheck_macros/COPYING | 1 + quickcheck_macros/LICENSE-MIT | 1 + quickcheck_macros/UNLICENSE | 1 + 3 files changed, 3 insertions(+) create mode 120000 quickcheck_macros/COPYING create mode 120000 quickcheck_macros/LICENSE-MIT create mode 120000 quickcheck_macros/UNLICENSE diff --git a/quickcheck_macros/COPYING b/quickcheck_macros/COPYING new file mode 120000 index 0000000..012065c --- /dev/null +++ b/quickcheck_macros/COPYING @@ -0,0 +1 @@ +../COPYING \ No newline at end of file diff --git a/quickcheck_macros/LICENSE-MIT b/quickcheck_macros/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/quickcheck_macros/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/quickcheck_macros/UNLICENSE b/quickcheck_macros/UNLICENSE new file mode 120000 index 0000000..b948d67 --- /dev/null +++ b/quickcheck_macros/UNLICENSE @@ -0,0 +1 @@ +../UNLICENSE \ No newline at end of file From 481c0c1047add82825f387a9778057901baa6dac Mon Sep 17 00:00:00 2001 From: Max Blachman Date: Mon, 19 Aug 2019 02:39:58 -0700 Subject: [PATCH 3/8] api: make arbitrary use full number range This commit tweaks the Arbitrary impls of number types (integers, floats) to use the full range with a small bias toward "problem" values. This is a change from prior behavior that would use the `size` parameter to control the range of integers. In retrospect, using the `size` parameter this way was probably misguided. Instead, it should only be used to control the sizes of data structures instead of also constraining numeric ranges. By constraining numeric ranges, we leave out a huge space of values that are never tested. Fixes #27, Fixes #119, Fixes #190, Fixes #233, Closes #240 --- src/arbitrary.rs | 253 ++++++++++++++++++++++++++++++----------------- src/tests.rs | 11 ++- 2 files changed, 167 insertions(+), 97 deletions(-) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index e8f0b5f..a1db83a 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -24,16 +24,14 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use rand::seq::SliceRandom; use rand::{self, Rng, RngCore}; -/// `Gen` wraps a `rand::RngCore` with parameters to control the distribution of +/// `Gen` wraps a `rand::RngCore` with parameters to control the range of /// random values. /// /// A value with type satisfying the `Gen` trait can be constructed with the /// `gen` function in this crate. pub trait Gen: RngCore { - /// Returns the `size` parameter used by this `Gen`, which controls the size - /// of random values generated. For example, it specifies the maximum length - /// of a randomly generated vector and also will specify the maximum - /// magnitude of a randomly generated number. + /// Controls the range of values of certain types created with this Gen. + /// For an explaination of which types behave how, see `Arbitrary` fn size(&self) -> usize; } @@ -51,9 +49,9 @@ impl StdGen { /// generator. /// /// The `size` parameter controls the size of random values generated. For - /// example, it specifies the maximum length of a randomly generated vector - /// and also will specify the maximum magnitude of a randomly generated - /// number. + /// example, it specifies the maximum length of a randomly generated + /// vector, but not the maximum magnitude of a randomly generated number. + /// For further explaination, see `Arbitrary`. pub fn new(rng: R, size: usize) -> StdGen { StdGen { rng: rng, size: size } } @@ -91,10 +89,10 @@ pub struct StdThreadGen(StdGen); impl StdThreadGen { /// Returns a new thread-local RNG. /// - /// The `size` parameter controls the size of random values generated. For - /// example, it specifies the maximum length of a randomly generated vector - /// and also will specify the maximum magnitude of a randomly generated - /// number. + /// The `size` parameter controls the size of random values generated. + /// For example, it specifies the maximum length of a randomly generated + /// vector, but not the maximum magnitude of a randomly generated number. + /// For further explaination, see `Arbitrary`. pub fn new(size: usize) -> StdThreadGen { StdThreadGen(StdGen { rng: rand::thread_rng(), size: size }) } @@ -138,7 +136,12 @@ pub fn single_shrinker(value: A) -> Box> { /// shrunk. /// /// Aside from shrinking, `Arbitrary` is different from the `std::Rand` trait -/// in that it uses a `Gen` to control the distribution of random values. +/// in that it respects `Gen::size()` for certain types. 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()`, as all `i32` +/// values require `O(1)` memory and operations between `i32`s require `O(1)` +/// time (with the exception of exponentiation). /// /// As of now, all types that implement `Arbitrary` must also implement /// `Clone`. (I'm not sure if this is a permanent restriction.) @@ -232,11 +235,13 @@ macro_rules! impl_arb_for_single_tuple { let iter = ::std::iter::empty(); $( let cloned = self.clone(); - let iter = iter.chain(self.$tuple_index.shrink().map(move |shr_value| { - let mut result = cloned.clone(); - result.$tuple_index = shr_value; - result - })); + let iter = iter.chain( + self.$tuple_index.shrink().map(move |shr_value| { + let mut result = cloned.clone(); + result.$tuple_index = shr_value; + result + }) + ); )* Box::new(iter) } @@ -749,15 +754,23 @@ macro_rules! unsigned_shrinker { }; } +macro_rules! unsigned_problem_values { + ($t:ty) => { + [<$t>::min_value(), 1, <$t>::max_value()] + }; +} + macro_rules! unsigned_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { fn arbitrary(g: &mut G) -> $ty { - #![allow(trivial_numeric_casts)] - let s = g.size() as $ty; - use std::cmp::{min, max}; - g.gen_range(0, max(1, min(s, $ty::max_value()))) + match g.gen_range(0, 10) { + 0 => { + *unsigned_problem_values!($ty).choose(g).unwrap() + }, + _ => g.gen() + } } fn shrink(&self) -> Box> { unsigned_shrinker!($ty); @@ -769,11 +782,7 @@ macro_rules! unsigned_arbitrary { } unsigned_arbitrary! { - usize, u8, u16, u32, u64 -} - -unsigned_arbitrary! { - u128 + usize, u8, u16, u32, u64, u128 } macro_rules! signed_shrinker { @@ -815,19 +824,23 @@ macro_rules! signed_shrinker { }; } +macro_rules! signed_problem_values { + ($t:ty) => { + [<$t>::min_value(), 0, <$t>::max_value()] + }; +} + macro_rules! signed_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { fn arbitrary(g: &mut G) -> $ty { - use std::cmp::{min,max}; - let upper = min(g.size(), $ty::max_value() as usize); - let lower = if upper > $ty::max_value() as usize { - $ty::min_value() - } else { - -(upper as $ty) - }; - g.gen_range(lower, max(1, upper as $ty)) + match g.gen_range(0, 10) { + 0 => { + *signed_problem_values!($ty).choose(g).unwrap() + }, + _ => g.gen() + } } fn shrink(&self) -> Box> { signed_shrinker!($ty); @@ -839,24 +852,42 @@ macro_rules! signed_arbitrary { } signed_arbitrary! { - isize, i8, i16, i32, i64 -} - -signed_arbitrary! { - i128 + isize, i8, i16, i32, i64, i128 +} + +macro_rules! float_problem_values { + ($path:path) => {{ + // hack. see: https://github.com/rust-lang/rust/issues/48067 + use $path as p; + [p::NAN, p::NEG_INFINITY, p::MIN, -0., 0., p::MAX, p::INFINITY] + }}; +} + +macro_rules! float_arbitrary { + ($($t:ty, $path:path, $shrinkable:ty),+) => {$( + impl Arbitrary for $t { + fn arbitrary(g: &mut G) -> $t { + match g.gen_range(0, 10) { + 0 => *float_problem_values!($path).choose(g).unwrap(), + _ => { + use $path as p; + let exp = g.gen_range(0., p::MAX_EXP as i16 as $t); + let mantissa = g.gen_range(1., 2.); + let sign = *[-1., 1.].choose(g).unwrap(); + sign * mantissa * exp.exp2() + } + } + } + fn shrink(&self) -> Box> { + signed_shrinker!($shrinkable); + let it = shrinker::SignedShrinker::new(*self as $shrinkable); + Box::new(it.map(|x| x as $t)) + } + } + )*}; } -impl Arbitrary for f32 { - fn arbitrary(g: &mut G) -> f32 { - let s = g.size(); - g.gen_range(-(s as f32), s as f32) - } - fn shrink(&self) -> Box> { - signed_shrinker!(i32); - let it = shrinker::SignedShrinker::new(*self as i32); - Box::new(it.map(|x| x as f32)) - } -} +float_arbitrary!(f32, std::f32, i32, f64, std::f64, i64); macro_rules! unsigned_non_zero_shrinker { ($ty:tt) => { @@ -904,12 +935,11 @@ macro_rules! unsigned_non_zero_arbitrary { $( impl Arbitrary for $ty { fn arbitrary(g: &mut G) -> $ty { - #![allow(trivial_numeric_casts)] - let s = g.size() as $inner; - use std::cmp::{min, max}; - let non_zero = g.gen_range(1, max(2, min(s, $inner::max_value()))); - debug_assert!(non_zero != 0); - $ty::new(non_zero).expect("non-zero value contsturction failed") + let mut v: $inner = g.gen(); + if v == 0 { + v += 1; + } + $ty::new(v).expect("non-zero value contsturction failed") } fn shrink(&self) -> Box> { @@ -932,18 +962,6 @@ unsigned_non_zero_arbitrary! { NonZeroU128 => u128 } -impl Arbitrary for f64 { - fn arbitrary(g: &mut G) -> f64 { - let s = g.size(); - g.gen_range(-(s as f64), s as f64) - } - fn shrink(&self) -> Box> { - signed_shrinker!(i64); - let it = shrinker::SignedShrinker::new(*self as i64); - Box::new(it.map(|x| x as f64)) - } -} - impl Arbitrary for Wrapping { fn arbitrary(g: &mut G) -> Wrapping { Wrapping(T::arbitrary(g)) @@ -1033,8 +1051,8 @@ impl Arbitrary for RangeFull { impl Arbitrary for Duration { fn arbitrary(gen: &mut G) -> Self { - let seconds = u64::arbitrary(gen); - let nanoseconds = u32::arbitrary(gen) % 1_000_000; + let seconds = gen.gen_range(0, gen.size() as u64); + let nanoseconds = gen.gen_range(0, 1_000_000); Duration::new(seconds, nanoseconds) } @@ -1094,6 +1112,7 @@ impl Arbitrary for SystemTime { #[cfg(test)] mod test { use super::Arbitrary; + use super::StdGen; use rand; use std::collections::{ BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque, @@ -1108,37 +1127,87 @@ mod test { assert_eq!(arby::<()>(), ()); } + macro_rules! arby_int { + ( $signed:expr, $($t:ty),+) => {$( + let mut arbys = (0..1_000_000).map(|_| arby::<$t>()); + let mut problems = if $signed { + signed_problem_values!($t).iter() + } else { + unsigned_problem_values!($t).iter() + }; + assert!(problems.all(|p| arbys.any(|arby| arby == *p)), + "Arbitrary does not generate all problematic values"); + let max = <$t>::max_value(); + let mid = (max + <$t>::min_value()) / 2; + // split full range of $t into chunks + // Arbitrary must return some value in each chunk + let double_chunks: $t = 9; + let chunks = double_chunks * 2; // chunks must be even + let lim: Box> = if $signed { + Box::new((0..=chunks) + .map(|idx| idx - chunks / 2) + .map(|x| mid + max / (chunks / 2) * x)) + } else { + Box::new((0..=chunks).map(|idx| max / chunks * idx)) + }; + let mut lim = lim.peekable(); + while let (Some(low), Some(&high)) = (lim.next(), lim.peek()) { + assert!(arbys.any(|arby| low <= arby && arby <= high), + "Arbitrary doesn't generate numbers in {}..={}", low, high) + } + )*}; + } + #[test] fn arby_int() { - rep(&mut || { - let n: isize = arby(); - assert!(n >= -5 && n <= 5); - }); + arby_int!(true, i8, i16, i32, i64, isize, i128); } #[test] fn arby_uint() { - rep(&mut || { - let n: usize = arby(); - assert!(n <= 5); - }); - } - - fn arby() -> A { - super::Arbitrary::arbitrary(&mut gen()) + arby_int!(false, u8, u16, u32, u64, usize, u128); + } + + macro_rules! arby_float { + ($($t:ty, $path:path),+) => {$({ + use $path as p; + let mut arbys = (0..1_000_000).map(|_| arby::<$t>()); + //NaN != NaN + assert!(arbys.any(|f| f.is_nan()), + "Arbitrary does not generate the problematic value NaN" + ); + for p in float_problem_values!($path).iter().filter(|f| !f.is_nan()) { + assert!(arbys.any(|arby| arby == *p), + "Arbitrary does not generate the problematic value {}", + p + ); + } + // split full range of $t into chunks + // Arbitrary must return some value in each chunk + let double_chunks: i8 = 9; + let chunks = double_chunks * 2; // chunks must be even + let lim = (-double_chunks..=double_chunks) + .map(|idx| <$t>::from(idx)) + .map(|idx| p::MAX/(<$t>::from(chunks/2)) * idx); + let mut lim = lim.peekable(); + while let (Some(low), Some(&high)) = (lim.next(), lim.peek()) { + assert!( + arbys.any(|arby| low <= arby && arby <= high), + "Arbitrary doesn't generate numbers in {:e}..={:e}", + low, + high, + ) + } + })*}; } - fn gen() -> super::StdGen { - super::StdGen::new(rand::thread_rng(), 5) + #[test] + fn arby_float() { + arby_float!(f32, std::f32, f64, std::f64); } - fn rep(f: &mut F) - where - F: FnMut() -> (), - { - for _ in 0..100 { - f() - } + fn arby() -> A { + Arbitrary::arbitrary(&mut StdGen::new(rand::thread_rng(), 5)) } // Shrink testing. diff --git a/src/tests.rs b/src/tests.rs index 1b74d07..7367432 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -132,19 +132,20 @@ fn is_prime(n: usize) -> bool { #[test] #[should_panic] fn sieve_not_prime() { - fn prop_all_prime(n: usize) -> bool { - sieve(n).into_iter().all(is_prime) + fn prop_all_prime(n: u8) -> bool { + sieve(n as usize).into_iter().all(is_prime) } - quickcheck(prop_all_prime as fn(usize) -> bool); + quickcheck(prop_all_prime as fn(u8) -> bool); } #[test] #[should_panic] fn sieve_not_all_primes() { - fn prop_prime_iff_in_the_sieve(n: usize) -> bool { + fn prop_prime_iff_in_the_sieve(n: u8) -> bool { + let n = n as usize; sieve(n) == (0..(n + 1)).filter(|&i| is_prime(i)).collect::>() } - quickcheck(prop_prime_iff_in_the_sieve as fn(usize) -> bool); + quickcheck(prop_prime_iff_in_the_sieve as fn(u8) -> bool); } #[test] From b2a6ba6ef6313baa0df137194235d02db6507193 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 6 Feb 2020 15:26:59 +0100 Subject: [PATCH 4/8] api: add Arbitrary impl for CString We add a little sophistication here for whether the CString is completely valid UTF-8 or whether it's just an arbitrary mix of bytes. (Excluding NUL of course.) Fixes #165, Closes #257 --- src/arbitrary.rs | 50 +++++++++++++++++++++++++++++++++++++++++------- src/tests.rs | 5 +++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/arbitrary.rs b/src/arbitrary.rs index a1db83a..9333f26 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -3,7 +3,7 @@ use std::collections::{ BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque, }; use std::env; -use std::ffi::OsString; +use std::ffi::{CString, OsString}; use std::hash::{BuildHasher, Hash}; use std::iter::{empty, once}; use std::net::{ @@ -277,7 +277,7 @@ impl Arbitrary for Vec { let s = g.size(); g.gen_range(0, s) }; - (0..size).map(|_| Arbitrary::arbitrary(g)).collect() + (0..size).map(|_| A::arbitrary(g)).collect() } fn shrink(&self) -> Box>> { @@ -598,11 +598,7 @@ impl Arbitrary for String { let s = g.size(); g.gen_range(0, s) }; - let mut s = String::with_capacity(size); - for _ in 0..size { - s.push(char::arbitrary(g)); - } - s + (0..size).map(|_| char::arbitrary(g)).collect() } fn shrink(&self) -> Box> { @@ -612,6 +608,46 @@ impl Arbitrary for String { } } +impl Arbitrary for CString { + fn arbitrary(g: &mut G) -> Self { + let size = { + let s = g.size(); + g.gen_range(0, s) + }; + // Use either random bytes or random UTF-8 encoded codepoints. + let utf8: bool = g.gen(); + if utf8 { + CString::new( + (0..) + .map(|_| char::arbitrary(g)) + .filter(|&c| c != '\0') + .take(size) + .collect::(), + ) + } else { + CString::new( + (0..) + .map(|_| u8::arbitrary(g)) + .filter(|&c| c != b'\0') + .take(size) + .collect::>(), + ) + } + .expect("null characters should have been filtered out") + } + + fn shrink(&self) -> Box> { + // Use the implementation for a vec here, but make sure null characters + // are filtered out. + Box::new(VecShrinker::new(self.as_bytes().to_vec()).map(|bytes| { + CString::new( + bytes.into_iter().filter(|&c| c != 0).collect::>(), + ) + .expect("null characters should have been filtered out") + })) + } +} + impl Arbitrary for char { fn arbitrary(g: &mut G) -> char { let mode = g.gen_range(0, 100); diff --git a/src/tests.rs b/src/tests.rs index 7367432..5a02a3f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,7 @@ use std::cmp::Ord; use std::collections::hash_map::DefaultHasher; use std::collections::{HashMap, HashSet}; +use std::ffi::CString; use std::hash::BuildHasherDefault; use std::path::PathBuf; @@ -286,4 +287,8 @@ quickcheck! { ) -> bool { true } + + fn cstring(_p: CString) -> bool { + true + } } From 57ba0d17c5e017878d1435e6e54f4adee0e741bd Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sun, 27 Dec 2020 14:17:57 -0500 Subject: [PATCH 5/8] rand: remove rand as a public dependency This removes the use of the rand_core crate as a public dependency. It is now an implementation detail. We achieve this primarily by turning the `Gen` trait into a concrete type and fixing the fallout. This does make it impossible for callers to use their own `Gen` implementations, but it's unclear how often this was being used (if at all). This does also limit the number of RNG utility routines that callers have easy access to. However, it should be possible to use rand's `SeedableRng::from_{rng,seed}` routines to get access to more general RNGs. Closes #241 --- Cargo.toml | 2 +- README.md | 10 +- src/arbitrary.rs | 295 ++++++++++++++++++++++------------------------- src/lib.rs | 5 +- src/tester.rs | 105 ++++++----------- src/tests.rs | 12 +- 6 files changed, 184 insertions(+), 245 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4e3b90..ddb887b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,5 @@ name = "quickcheck" [dependencies] env_logger = { version = "0.7.0", default-features = false, optional = true } log = { version = "0.4", optional = true } -rand = "0.7" +rand = { version = "0.7", features = ["small_rng"] } rand_core = "0.5" diff --git a/README.md b/README.md index fc2d006..de2cec9 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ trait. Great, so what is this `Testable` business? ```rust pub trait Testable { - fn result(&self, &mut G) -> TestResult; + fn result(&self, &mut Gen) -> TestResult; } ``` @@ -207,7 +207,7 @@ Sure enough, `bool` satisfies the `Testable` trait: ```rust impl Testable for bool { - fn result(&self, _: &mut G) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { TestResult::from_bool(*self) } } @@ -218,7 +218,7 @@ satisfy `Testable` too! ```rust impl Testable for fn(A) -> B { - fn result(&self, g: &mut G) -> TestResult { + fn result(&self, g: &mut Gen) -> TestResult { // elided } } @@ -236,7 +236,7 @@ make sure `TestResult` satisfies `Testable`: ```rust impl Testable for TestResult { - fn result(&self, _: &mut G) -> TestResult { self.clone() } + fn result(&self, _: &mut Gen) -> TestResult { self.clone() } } ``` @@ -406,7 +406,7 @@ the trait `Arbitrary` for the struct `Point`: use quickcheck::{Arbitrary, Gen}; impl Arbitrary for Point { - fn arbitrary(g: &mut G) -> 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 9333f26..bddcc32 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -22,103 +22,60 @@ use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use rand::seq::SliceRandom; -use rand::{self, Rng, RngCore}; +use rand::{self, Rng, SeedableRng}; -/// `Gen` wraps a `rand::RngCore` with parameters to control the range of -/// random values. +/// Gen represents a PRNG. /// -/// A value with type satisfying the `Gen` trait can be constructed with the -/// `gen` function in this crate. -pub trait Gen: RngCore { - /// Controls the range of values of certain types created with this Gen. - /// For an explaination of which types behave how, see `Arbitrary` - fn size(&self) -> usize; -} - -/// StdGen is the default implementation of `Gen`. +/// 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. /// -/// Values of type `StdGen` can be created with the `gen` function in this -/// crate. -pub struct StdGen { - rng: R, +/// It is unspecified whether this is a secure RNG or not. Therefore, callers +/// should assume it is insecure. +pub struct Gen { + rng: rand::rngs::SmallRng, size: usize, } -impl StdGen { - /// Returns a `StdGen` with the given configuration using any random number - /// generator. - /// - /// The `size` parameter controls the size of random values generated. For - /// example, it specifies the maximum length of a randomly generated - /// vector, but not the maximum magnitude of a randomly generated number. - /// For further explaination, see `Arbitrary`. - pub fn new(rng: R, size: usize) -> StdGen { - StdGen { rng: rng, size: size } - } -} - -impl RngCore for StdGen { - fn next_u32(&mut self) -> u32 { - self.rng.next_u32() - } - - // some RNGs implement these more efficiently than the default, so - // we might as well defer to them. - fn next_u64(&mut self) -> u64 { - self.rng.next_u64() - } - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.rng.fill_bytes(dest) - } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - self.rng.try_fill_bytes(dest) - } -} - -impl Gen for StdGen { - fn size(&self) -> usize { - self.size - } -} - -/// StdThreadGen is an RNG in thread-local memory. -/// -/// This is the default RNG used by quickcheck. -pub struct StdThreadGen(StdGen); - -impl StdThreadGen { - /// Returns a new thread-local RNG. +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 not the maximum magnitude of a randomly generated number. - /// For further explaination, see `Arbitrary`. - pub fn new(size: usize) -> StdThreadGen { - StdThreadGen(StdGen { rng: rand::thread_rng(), size: size }) + /// vector, but is and should not be used to control the range of a + /// randomly generated number. (Unless that number is used to control the + /// size of a data structure.) + pub fn new(size: usize) -> Gen { + Gen { rng: rand::rngs::SmallRng::from_entropy(), size: size } } -} -impl RngCore for StdThreadGen { - fn next_u32(&mut self) -> u32 { - self.0.next_u32() + /// Returns the size configured with this generator. + pub fn size(&self) -> usize { + self.size } - // some RNGs implement these more efficiently than the default, so - // we might as well defer to them. - fn next_u64(&mut self) -> u64 { - self.0.next_u64() + /// Choose among the possible alternatives in the slice given. If the slice + /// is empty, then `None` is returned. Otherwise, a non-`None` value is + /// guaranteed to be returned. + pub fn choose<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> { + slice.choose(&mut self.rng) } - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest) - } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - self.0.try_fill_bytes(dest) + + fn gen(&mut self) -> T + where + rand::distributions::Standard: rand::distributions::Distribution, + { + self.rng.gen() } -} -impl Gen for StdThreadGen { - fn size(&self) -> usize { - self.0.size + fn gen_range(&mut self, low: B1, high: B2) -> T + where + T: rand::distributions::uniform::SampleUniform, + B1: rand::distributions::uniform::SampleBorrow + Sized, + B2: rand::distributions::uniform::SampleBorrow + Sized, + { + self.rng.gen_range(low, high) } } @@ -135,32 +92,53 @@ 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 the `std::Rand` trait -/// in that it respects `Gen::size()` for certain types. 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()`, as 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). /// -/// As of now, all types that implement `Arbitrary` must also implement -/// `Clone`. (I'm not sure if this is a permanent restriction.) +/// Additionally, all types that implement `Arbitrary` must also implement +/// `Clone`. pub trait Arbitrary: Clone + 'static { - fn arbitrary(g: &mut G) -> Self; - + /// Return an arbitrary value. + /// + /// Implementations should respect `Gen::size()` when decisions about how + /// big a particular value should be. Implementations should generally + /// defer to other `Arbitrary` implementations to generate other random + /// values when necessary. The `Gen` type also offers a few RNG helper + /// routines. + fn arbitrary(g: &mut Gen) -> Self; + + /// Return an iterator of values that are smaller than itself. + /// + /// The way in which a value is "smaller" is implementation defined. In + /// some cases, the interpretation is obvious: shrinking an integer should + /// produce integers smaller than itself. Others are more complex, for + /// example, shrinking a `Vec` should both shrink its size and shrink its + /// component values. + /// + /// The iterator returned should be bounded to some reasonable size. + /// + /// It is always correct to return an empty iterator, and indeed, this + /// is the default implementation. The downside of this approach is that + /// witnesses to failures in properties will be more inscrutable. fn shrink(&self) -> Box> { empty_shrinker() } } impl Arbitrary for () { - fn arbitrary(_: &mut G) -> () { + fn arbitrary(_: &mut Gen) -> () { () } } impl Arbitrary for bool { - fn arbitrary(g: &mut G) -> bool { + fn arbitrary(g: &mut Gen) -> bool { g.gen() } @@ -174,7 +152,7 @@ impl Arbitrary for bool { } impl Arbitrary for Option { - fn arbitrary(g: &mut G) -> Option { + fn arbitrary(g: &mut Gen) -> Option { if g.gen() { None } else { @@ -194,7 +172,7 @@ impl Arbitrary for Option { } impl Arbitrary for Result { - fn arbitrary(g: &mut G) -> Result { + fn arbitrary(g: &mut Gen) -> Result { if g.gen() { Ok(Arbitrary::arbitrary(g)) } else { @@ -223,7 +201,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 Gen) -> ($($type_param,)*) { ( $( $type_param::arbitrary(g), @@ -272,7 +250,7 @@ impl_arb_for_tuples! { } impl Arbitrary for Vec { - fn arbitrary(g: &mut G) -> Vec { + fn arbitrary(g: &mut Gen) -> Vec { let size = { let s = g.size(); g.gen_range(0, s) @@ -386,7 +364,7 @@ where } impl Arbitrary for BTreeMap { - fn arbitrary(g: &mut G) -> BTreeMap { + fn arbitrary(g: &mut Gen) -> BTreeMap { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -405,7 +383,7 @@ impl< S: BuildHasher + Default + Clone + 'static, > Arbitrary for HashMap { - fn arbitrary(g: &mut G) -> Self { + fn arbitrary(g: &mut Gen) -> Self { let vec: Vec<(K, V)> = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -417,7 +395,7 @@ impl< } impl Arbitrary for BTreeSet { - fn arbitrary(g: &mut G) -> BTreeSet { + fn arbitrary(g: &mut Gen) -> BTreeSet { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -429,7 +407,7 @@ impl Arbitrary for BTreeSet { } impl Arbitrary for BinaryHeap { - fn arbitrary(g: &mut G) -> BinaryHeap { + fn arbitrary(g: &mut Gen) -> BinaryHeap { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -445,7 +423,7 @@ impl Arbitrary for BinaryHeap { impl Arbitrary for HashSet { - fn arbitrary(g: &mut G) -> Self { + fn arbitrary(g: &mut Gen) -> Self { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -457,7 +435,7 @@ impl } impl Arbitrary for LinkedList { - fn arbitrary(g: &mut G) -> LinkedList { + fn arbitrary(g: &mut Gen) -> LinkedList { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -471,7 +449,7 @@ impl Arbitrary for LinkedList { } impl Arbitrary for VecDeque { - fn arbitrary(g: &mut G) -> VecDeque { + fn arbitrary(g: &mut Gen) -> VecDeque { let vec: Vec = Arbitrary::arbitrary(g); vec.into_iter().collect() } @@ -483,7 +461,7 @@ impl Arbitrary for VecDeque { } impl Arbitrary for IpAddr { - fn arbitrary(g: &mut G) -> IpAddr { + fn arbitrary(g: &mut Gen) -> IpAddr { let ipv4: bool = g.gen(); if ipv4 { IpAddr::V4(Arbitrary::arbitrary(g)) @@ -494,13 +472,13 @@ impl Arbitrary for IpAddr { } impl Arbitrary for Ipv4Addr { - fn arbitrary(g: &mut G) -> Ipv4Addr { + fn arbitrary(g: &mut Gen) -> Ipv4Addr { Ipv4Addr::new(g.gen(), g.gen(), g.gen(), g.gen()) } } impl Arbitrary for Ipv6Addr { - fn arbitrary(g: &mut G) -> Ipv6Addr { + fn arbitrary(g: &mut Gen) -> Ipv6Addr { Ipv6Addr::new( g.gen(), g.gen(), @@ -515,25 +493,25 @@ impl Arbitrary for Ipv6Addr { } impl Arbitrary for SocketAddr { - fn arbitrary(g: &mut G) -> SocketAddr { + fn arbitrary(g: &mut Gen) -> SocketAddr { SocketAddr::new(Arbitrary::arbitrary(g), g.gen()) } } impl Arbitrary for SocketAddrV4 { - fn arbitrary(g: &mut G) -> SocketAddrV4 { + fn arbitrary(g: &mut Gen) -> SocketAddrV4 { SocketAddrV4::new(Arbitrary::arbitrary(g), g.gen()) } } impl Arbitrary for SocketAddrV6 { - fn arbitrary(g: &mut G) -> SocketAddrV6 { + fn arbitrary(g: &mut Gen) -> SocketAddrV6 { SocketAddrV6::new(Arbitrary::arbitrary(g), g.gen(), g.gen(), g.gen()) } } impl Arbitrary for PathBuf { - fn arbitrary(g: &mut G) -> 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 = @@ -541,16 +519,18 @@ impl Arbitrary for PathBuf { let temp = env::temp_dir(); #[allow(deprecated)] let home = env::home_dir().unwrap_or(PathBuf::from("/home/user")); - let choices = &[ - here, - temp, - home, - PathBuf::from("."), - PathBuf::from(".."), - PathBuf::from("../../.."), - PathBuf::new(), - ]; - let mut p = choices.choose(g).unwrap().clone(); + let mut p = g + .choose(&[ + here, + temp, + home, + PathBuf::from("."), + PathBuf::from(".."), + PathBuf::from("../../.."), + PathBuf::new(), + ]) + .unwrap() + .to_owned(); p.extend(Vec::::arbitrary(g).iter()); p } @@ -582,7 +562,7 @@ impl Arbitrary for PathBuf { } impl Arbitrary for OsString { - fn arbitrary(g: &mut G) -> OsString { + fn arbitrary(g: &mut Gen) -> OsString { OsString::from(String::arbitrary(g)) } @@ -593,7 +573,7 @@ impl Arbitrary for OsString { } impl Arbitrary for String { - fn arbitrary(g: &mut G) -> String { + fn arbitrary(g: &mut Gen) -> String { let size = { let s = g.size(); g.gen_range(0, s) @@ -609,7 +589,7 @@ impl Arbitrary for String { } impl Arbitrary for CString { - fn arbitrary(g: &mut G) -> Self { + fn arbitrary(g: &mut Gen) -> Self { let size = { let s = g.size(); g.gen_range(0, s) @@ -649,7 +629,7 @@ impl Arbitrary for CString { } impl Arbitrary for char { - fn arbitrary(g: &mut G) -> char { + fn arbitrary(g: &mut Gen) -> char { let mode = g.gen_range(0, 100); match mode { 0..=49 => { @@ -667,20 +647,19 @@ impl Arbitrary for char { } 60..=84 => { // Characters often used in programming languages - [ + g.choose(&[ ' ', ' ', ' ', '\t', '\n', '~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '-', '=', '+', '[', ']', '{', '}', ':', ';', '\'', '"', '\\', '|', ',', '<', '>', '.', '/', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - ] - .choose(g) + ]) .unwrap() .to_owned() } 85..=89 => { // Tricky Unicode, part 1 - [ + g.choose(&[ '\u{0149}', // a deprecated character '\u{fff0}', // some of "Other, format" category: '\u{fff1}', @@ -730,8 +709,7 @@ impl Arbitrary for char { '\u{1680}', // other space characters are already covered by two next // branches - ] - .choose(g) + ]) .unwrap() .to_owned() } @@ -792,7 +770,7 @@ macro_rules! unsigned_shrinker { macro_rules! unsigned_problem_values { ($t:ty) => { - [<$t>::min_value(), 1, <$t>::max_value()] + &[<$t>::min_value(), 1, <$t>::max_value()] }; } @@ -800,10 +778,10 @@ macro_rules! unsigned_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut G) -> $ty { + fn arbitrary(g: &mut Gen) -> $ty { match g.gen_range(0, 10) { 0 => { - *unsigned_problem_values!($ty).choose(g).unwrap() + *g.choose(unsigned_problem_values!($ty)).unwrap() }, _ => g.gen() } @@ -862,7 +840,7 @@ macro_rules! signed_shrinker { macro_rules! signed_problem_values { ($t:ty) => { - [<$t>::min_value(), 0, <$t>::max_value()] + &[<$t>::min_value(), 0, <$t>::max_value()] }; } @@ -870,10 +848,10 @@ macro_rules! signed_arbitrary { ($($ty:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut G) -> $ty { + fn arbitrary(g: &mut Gen) -> $ty { match g.gen_range(0, 10) { 0 => { - *signed_problem_values!($ty).choose(g).unwrap() + *g.choose(signed_problem_values!($ty)).unwrap() }, _ => g.gen() } @@ -895,21 +873,21 @@ 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] + &[p::NAN, p::NEG_INFINITY, p::MIN, -0., 0., p::MAX, p::INFINITY] }}; } macro_rules! float_arbitrary { ($($t:ty, $path:path, $shrinkable:ty),+) => {$( impl Arbitrary for $t { - fn arbitrary(g: &mut G) -> $t { + fn arbitrary(g: &mut Gen) -> $t { match g.gen_range(0, 10) { - 0 => *float_problem_values!($path).choose(g).unwrap(), + 0 => *g.choose(float_problem_values!($path)).unwrap(), _ => { use $path as p; let exp = g.gen_range(0., p::MAX_EXP as i16 as $t); let mantissa = g.gen_range(1., 2.); - let sign = *[-1., 1.].choose(g).unwrap(); + let sign = *g.choose(&[-1., 1.]).unwrap(); sign * mantissa * exp.exp2() } } @@ -970,7 +948,7 @@ macro_rules! unsigned_non_zero_arbitrary { ($($ty:tt => $inner:tt),*) => { $( impl Arbitrary for $ty { - fn arbitrary(g: &mut G) -> $ty { + fn arbitrary(g: &mut Gen) -> $ty { let mut v: $inner = g.gen(); if v == 0 { v += 1; @@ -999,7 +977,7 @@ unsigned_non_zero_arbitrary! { } impl Arbitrary for Wrapping { - fn arbitrary(g: &mut G) -> Wrapping { + fn arbitrary(g: &mut Gen) -> Wrapping { Wrapping(T::arbitrary(g)) } fn shrink(&self) -> Box>> { @@ -1008,7 +986,7 @@ impl Arbitrary for Wrapping { } impl Arbitrary for Bound { - fn arbitrary(g: &mut G) -> Bound { + fn arbitrary(g: &mut Gen) -> Bound { match g.gen_range(0, 3) { 0 => Bound::Included(T::arbitrary(g)), 1 => Bound::Excluded(T::arbitrary(g)), @@ -1029,7 +1007,7 @@ impl Arbitrary for Bound { } impl Arbitrary for Range { - fn arbitrary(g: &mut G) -> Range { + fn arbitrary(g: &mut Gen) -> Range { Arbitrary::arbitrary(g)..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1040,7 +1018,7 @@ impl Arbitrary for Range { } impl Arbitrary for RangeInclusive { - fn arbitrary(g: &mut G) -> RangeInclusive { + fn arbitrary(g: &mut Gen) -> RangeInclusive { Arbitrary::arbitrary(g)..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1053,7 +1031,7 @@ impl Arbitrary for RangeInclusive { } impl Arbitrary for RangeFrom { - fn arbitrary(g: &mut G) -> RangeFrom { + fn arbitrary(g: &mut Gen) -> RangeFrom { Arbitrary::arbitrary(g).. } fn shrink(&self) -> Box>> { @@ -1062,7 +1040,7 @@ impl Arbitrary for RangeFrom { } impl Arbitrary for RangeTo { - fn arbitrary(g: &mut G) -> RangeTo { + fn arbitrary(g: &mut Gen) -> RangeTo { ..Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1071,7 +1049,7 @@ impl Arbitrary for RangeTo { } impl Arbitrary for RangeToInclusive { - fn arbitrary(g: &mut G) -> RangeToInclusive { + fn arbitrary(g: &mut Gen) -> RangeToInclusive { ..=Arbitrary::arbitrary(g) } fn shrink(&self) -> Box>> { @@ -1080,13 +1058,13 @@ impl Arbitrary for RangeToInclusive { } impl Arbitrary for RangeFull { - fn arbitrary(_: &mut G) -> RangeFull { + fn arbitrary(_: &mut Gen) -> RangeFull { .. } } impl Arbitrary for Duration { - fn arbitrary(gen: &mut G) -> Self { + fn arbitrary(gen: &mut Gen) -> Self { let seconds = gen.gen_range(0, gen.size() as u64); let nanoseconds = gen.gen_range(0, 1_000_000); Duration::new(seconds, nanoseconds) @@ -1102,7 +1080,7 @@ impl Arbitrary for Duration { } impl Arbitrary for Box { - fn arbitrary(g: &mut G) -> Box { + fn arbitrary(g: &mut Gen) -> Box { Box::new(A::arbitrary(g)) } @@ -1112,7 +1090,7 @@ impl Arbitrary for Box { } impl Arbitrary for Arc { - fn arbitrary(g: &mut G) -> Arc { + fn arbitrary(g: &mut Gen) -> Arc { Arc::new(A::arbitrary(g)) } @@ -1122,7 +1100,7 @@ impl Arbitrary for Arc { } impl Arbitrary for SystemTime { - fn arbitrary(gen: &mut G) -> Self { + fn arbitrary(gen: &mut Gen) -> Self { let after_epoch = bool::arbitrary(gen); let duration = Duration::arbitrary(gen); if after_epoch { @@ -1147,9 +1125,6 @@ impl Arbitrary for SystemTime { #[cfg(test)] mod test { - use super::Arbitrary; - use super::StdGen; - use rand; use std::collections::{ BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque, }; @@ -1158,6 +1133,8 @@ mod test { use std::num::Wrapping; use std::path::PathBuf; + use super::{Arbitrary, Gen}; + #[test] fn arby_unit() { assert_eq!(arby::<()>(), ()); @@ -1243,7 +1220,7 @@ mod test { } fn arby() -> A { - Arbitrary::arbitrary(&mut StdGen::new(rand::thread_rng(), 5)) + Arbitrary::arbitrary(&mut Gen::new(5)) } // Shrink testing. diff --git a/src/lib.rs b/src/lib.rs index 07f73e4..b3016a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,8 @@ extern crate log; extern crate rand; extern crate rand_core; -pub use crate::arbitrary::{ - empty_shrinker, single_shrinker, Arbitrary, Gen, StdGen, StdThreadGen, -}; +pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; -pub use rand_core::RngCore; /// A macro for writing quickcheck tests. /// diff --git a/src/tester.rs b/src/tester.rs index 491cb48..cb6ef2e 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -3,15 +3,17 @@ use std::env; use std::fmt::Debug; use std::panic; -use crate::tester::Status::{Discard, Fail, Pass}; -use crate::{Arbitrary, Gen, StdThreadGen}; +use crate::{ + tester::Status::{Discard, Fail, Pass}, + Arbitrary, Gen, +}; /// The main QuickCheck type for setting configuration and running QuickCheck. -pub struct QuickCheck { +pub struct QuickCheck { tests: u64, max_tests: u64, min_tests_passed: u64, - gen: G, + gen: Gen, } fn qc_tests() -> u64 { @@ -46,71 +48,56 @@ fn qc_min_tests_passed() -> u64 { } } -impl QuickCheck { +impl QuickCheck { /// Creates a new QuickCheck value. /// - /// This can be used to run QuickCheck on things that implement - /// `Testable`. You may also adjust the configuration, such as - /// the number of tests to run. + /// 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 set to a `StdThreadGen` with a default size of `100`. - pub fn new() -> QuickCheck { - let gen_size = qc_gen_size(); - QuickCheck::with_gen(StdThreadGen::new(gen_size)) + /// By default, the maximum number of passed tests is set to `100`, the max + /// number of overall tests is set to `10000` and the generator is created + /// with a size of `100`. + pub fn new() -> QuickCheck { + let gen = Gen::new(qc_gen_size()); + let tests = qc_tests(); + let max_tests = cmp::max(tests, qc_max_tests()); + let min_tests_passed = qc_min_tests_passed(); + + QuickCheck { tests, max_tests, min_tests_passed, gen } + } + + /// Set the random number generator to be used by QuickCheck. + pub fn gen(self, gen: Gen) -> QuickCheck { + QuickCheck { gen, ..self } } -} -impl QuickCheck { /// Set the number of tests to run. /// /// This actually refers to the maximum number of *passed* tests that /// can occur. Namely, if a test causes a failure, future testing on that /// property stops. Additionally, if tests are discarded, there may be /// fewer than `tests` passed. - pub fn tests(mut self, tests: u64) -> QuickCheck { + pub fn tests(mut self, tests: u64) -> QuickCheck { self.tests = tests; self } - /// Create a new instance of `QuickCheck` using the given generator. - pub fn with_gen(generator: G) -> QuickCheck { - let tests = qc_tests(); - let max_tests = cmp::max(tests, qc_max_tests()); - let min_tests_passed = qc_min_tests_passed(); - - QuickCheck { - tests: tests, - max_tests: max_tests, - min_tests_passed: min_tests_passed, - gen: generator, - } - } - /// Set the maximum number of tests to run. /// /// The number of invocations of a property will never exceed this number. /// This is necessary to cap the number of tests because QuickCheck /// properties can discard tests. - pub fn max_tests(mut self, max_tests: u64) -> QuickCheck { + pub fn max_tests(mut self, max_tests: u64) -> QuickCheck { self.max_tests = max_tests; self } - /// Set the random number generator to be used by QuickCheck. - pub fn gen(self, gen: N) -> QuickCheck { - // unfortunately this is necessary because using QuickCheck{ ..self, gen } - // wouldn't work due to mismatched types. - let QuickCheck { tests, max_tests, min_tests_passed, .. } = self; - QuickCheck { tests, max_tests, min_tests_passed, gen } - } - /// Set the minimum number of tests that needs to pass. /// /// This actually refers to the minimum number of *valid* *passed* tests /// that needs to pass for the property to be considered successful. - pub fn min_tests_passed(mut self, min_tests_passed: u64) -> QuickCheck { + pub fn min_tests_passed(mut self, min_tests_passed: u64) -> QuickCheck { self.min_tests_passed = min_tests_passed; self } @@ -306,23 +293,23 @@ impl TestResult { /// /// It's unlikely that you'll have to implement this trait yourself. pub trait Testable: 'static { - fn result(&self, _: &mut G) -> TestResult; + fn result(&self, _: &mut Gen) -> TestResult; } impl Testable for bool { - fn result(&self, _: &mut G) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { TestResult::from_bool(*self) } } impl Testable for () { - fn result(&self, _: &mut G) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { TestResult::passed() } } impl Testable for TestResult { - fn result(&self, _: &mut G) -> TestResult { + fn result(&self, _: &mut Gen) -> TestResult { self.clone() } } @@ -332,7 +319,7 @@ where A: Testable, E: Debug + 'static, { - fn result(&self, g: &mut G) -> TestResult { + fn result(&self, g: &mut Gen) -> TestResult { match *self { Ok(ref r) => r.result(g), Err(ref err) => TestResult::error(format!("{:?}", err)), @@ -351,9 +338,9 @@ macro_rules! testable_fn { impl Testable for fn($($name),*) -> T { #[allow(non_snake_case)] - fn result(&self, g: &mut G_) -> TestResult { - fn shrink_failure( - g: &mut G_, + fn result(&self, g: &mut Gen) -> TestResult { + fn shrink_failure( + g: &mut Gen, self_: fn($($name),*) -> T, a: ($($name,)*), ) -> Option { @@ -431,8 +418,7 @@ impl AShow for A {} #[cfg(test)] mod test { - use crate::{QuickCheck, StdGen}; - use rand::{self, rngs::OsRng}; + use crate::{Gen, QuickCheck}; #[test] fn shrinking_regression_issue_126() { @@ -445,26 +431,11 @@ mod test { let expected_argument = format!("{:?}", [true, true]); assert_eq!(failing_case.arguments, vec![expected_argument]); } - #[test] fn size_for_small_types_issue_143() { fn t(_: i8) -> bool { true } - QuickCheck::new() - .gen(StdGen::new(rand::thread_rng(), 129)) - .quickcheck(t as fn(i8) -> bool); - } - - #[test] - fn different_generator() { - fn prop(_: i32) -> bool { - true - } - QuickCheck::with_gen(StdGen::new(OsRng, 129)) - .quickcheck(prop as fn(i32) -> bool); - QuickCheck::new() - .gen(StdGen::new(OsRng, 129)) - .quickcheck(prop as fn(i32) -> bool); + QuickCheck::new().gen(Gen::new(129)).quickcheck(t as fn(i8) -> bool); } } diff --git a/src/tests.rs b/src/tests.rs index 5a02a3f..465ef15 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -5,9 +5,7 @@ use std::ffi::CString; use std::hash::BuildHasherDefault; use std::path::PathBuf; -use rand; - -use super::{quickcheck, QuickCheck, StdGen, TestResult}; +use super::{quickcheck, Gen, QuickCheck, TestResult}; #[test] fn prop_oob() { @@ -182,9 +180,7 @@ fn regression_issue_83() { fn prop(_: u8) -> bool { true } - QuickCheck::new() - .gen(StdGen::new(rand::thread_rng(), 1024)) - .quickcheck(prop as fn(u8) -> bool) + QuickCheck::new().gen(Gen::new(1024)).quickcheck(prop as fn(u8) -> bool) } #[test] @@ -192,9 +188,7 @@ fn regression_issue_83_signed() { fn prop(_: i8) -> bool { true } - QuickCheck::new() - .gen(StdGen::new(rand::thread_rng(), 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 From db98d8d165104d0a7bf4bd1fd4032c4b194bbf30 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sun, 27 Dec 2020 15:08:28 -0500 Subject: [PATCH 6/8] deps: upgrade rand to 0.8 This upgrades to the latest version of rand. Closes #264 --- Cargo.toml | 3 +-- src/arbitrary.rs | 37 ++++++++++++++++++------------------- src/lib.rs | 2 -- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ddb887b..35d2371 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,4 @@ name = "quickcheck" [dependencies] env_logger = { version = "0.7.0", default-features = false, optional = true } log = { version = "0.4", optional = true } -rand = { version = "0.7", features = ["small_rng"] } -rand_core = "0.5" +rand = { version = "0.8", default-features = false, features = ["small_rng", "std"] } diff --git a/src/arbitrary.rs b/src/arbitrary.rs index bddcc32..0adede5 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -69,13 +69,12 @@ impl Gen { self.rng.gen() } - fn gen_range(&mut self, low: B1, high: B2) -> T + fn gen_range(&mut self, range: R) -> T where T: rand::distributions::uniform::SampleUniform, - B1: rand::distributions::uniform::SampleBorrow + Sized, - B2: rand::distributions::uniform::SampleBorrow + Sized, + R: rand::distributions::uniform::SampleRange, { - self.rng.gen_range(low, high) + self.rng.gen_range(range) } } @@ -253,7 +252,7 @@ impl Arbitrary for Vec { fn arbitrary(g: &mut Gen) -> Vec { let size = { let s = g.size(); - g.gen_range(0, s) + g.gen_range(0..s) }; (0..size).map(|_| A::arbitrary(g)).collect() } @@ -576,7 +575,7 @@ impl Arbitrary for String { fn arbitrary(g: &mut Gen) -> String { let size = { let s = g.size(); - g.gen_range(0, s) + g.gen_range(0..s) }; (0..size).map(|_| char::arbitrary(g)).collect() } @@ -592,7 +591,7 @@ impl Arbitrary for CString { fn arbitrary(g: &mut Gen) -> Self { let size = { let s = g.size(); - g.gen_range(0, s) + g.gen_range(0..s) }; // Use either random bytes or random UTF-8 encoded codepoints. let utf8: bool = g.gen(); @@ -630,16 +629,16 @@ impl Arbitrary for CString { impl Arbitrary for char { fn arbitrary(g: &mut Gen) -> char { - let mode = g.gen_range(0, 100); + let mode = g.gen_range(0..100); match mode { 0..=49 => { // ASCII + some control characters - g.gen_range(0, 0xB0) as u8 as char + g.gen_range(0..0xB0) as u8 as char } 50..=59 => { // Unicode BMP characters loop { - if let Some(x) = char::from_u32(g.gen_range(0, 0x10000)) { + if let Some(x) = char::from_u32(g.gen_range(0..0x10000)) { return x; } // ignore surrogate pairs @@ -715,7 +714,7 @@ impl Arbitrary for char { } 90..=94 => { // Tricky unicode, part 2 - char::from_u32(g.gen_range(0x2000, 0x2070)).unwrap() + char::from_u32(g.gen_range(0x2000..0x2070)).unwrap() } 95..=99 => { // Completely arbitrary characters @@ -779,7 +778,7 @@ macro_rules! unsigned_arbitrary { $( impl Arbitrary for $ty { fn arbitrary(g: &mut Gen) -> $ty { - match g.gen_range(0, 10) { + match g.gen_range(0..10) { 0 => { *g.choose(unsigned_problem_values!($ty)).unwrap() }, @@ -849,7 +848,7 @@ macro_rules! signed_arbitrary { $( impl Arbitrary for $ty { fn arbitrary(g: &mut Gen) -> $ty { - match g.gen_range(0, 10) { + match g.gen_range(0..10) { 0 => { *g.choose(signed_problem_values!($ty)).unwrap() }, @@ -881,12 +880,12 @@ macro_rules! float_arbitrary { ($($t:ty, $path:path, $shrinkable:ty),+) => {$( impl Arbitrary for $t { fn arbitrary(g: &mut Gen) -> $t { - match g.gen_range(0, 10) { + match g.gen_range(0..10) { 0 => *g.choose(float_problem_values!($path)).unwrap(), _ => { use $path as p; - let exp = g.gen_range(0., p::MAX_EXP as i16 as $t); - let mantissa = g.gen_range(1., 2.); + let exp = g.gen_range((0.)..p::MAX_EXP as i16 as $t); + let mantissa = g.gen_range((1.)..2.); let sign = *g.choose(&[-1., 1.]).unwrap(); sign * mantissa * exp.exp2() } @@ -987,7 +986,7 @@ impl Arbitrary for Wrapping { impl Arbitrary for Bound { fn arbitrary(g: &mut Gen) -> Bound { - match g.gen_range(0, 3) { + match g.gen_range(0..3) { 0 => Bound::Included(T::arbitrary(g)), 1 => Bound::Excluded(T::arbitrary(g)), _ => Bound::Unbounded, @@ -1065,8 +1064,8 @@ impl Arbitrary for 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); + let seconds = gen.gen_range(0..gen.size() as u64); + let nanoseconds = gen.gen_range(0..1_000_000); Duration::new(seconds, nanoseconds) } diff --git a/src/lib.rs b/src/lib.rs index b3016a3..34900ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,6 @@ extern crate env_logger; #[cfg(feature = "use_logging")] #[macro_use] extern crate log; -extern crate rand; -extern crate rand_core; pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; From 315342680ae412ecdf8b0376352d1233d0b1d7b6 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sun, 27 Dec 2020 15:09:23 -0500 Subject: [PATCH 7/8] deps: update env_logger --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 35d2371..fc35dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,6 @@ regex = ["env_logger/regex"] name = "quickcheck" [dependencies] -env_logger = { version = "0.7.0", default-features = false, optional = true } +env_logger = { version = "0.8.2", default-features = false, optional = true } log = { version = "0.4", optional = true } rand = { version = "0.8", default-features = false, features = ["small_rng", "std"] } From 668c3338890c2e2832cc16e20b7519959ff521cb Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sun, 27 Dec 2020 16:01:29 -0500 Subject: [PATCH 8/8] msrv: bump to 1.46.0 and specify policy The next release will be a breaking change release anyway. We update a few other things as well. The examples in particular. --- .github/workflows/ci.yml | 2 +- README.md | 2 +- examples/btree_set_range.rs | 6 +++--- examples/out_of_bounds.rs | 2 -- examples/reverse.rs | 2 -- examples/reverse_single.rs | 2 -- examples/sieve.rs | 2 -- examples/sort.rs | 6 ++---- src/lib.rs | 12 ++++++------ 9 files changed, 13 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5e87dd..b41f2bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: include: - build: pinned os: ubuntu-18.04 - rust: 1.34.0 + rust: 1.46.0 - build: stable os: ubuntu-18.04 rust: stable diff --git a/README.md b/README.md index de2cec9..b360c04 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ default and thus no longer available. ### Minimum Rust version policy -This crate's minimum supported `rustc` version is `1.34.0`. +This crate's minimum supported `rustc` version is `1.46.0`. The current policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if `crate 1.0` requires diff --git a/examples/btree_set_range.rs b/examples/btree_set_range.rs index a89ffa5..0e7418b 100644 --- a/examples/btree_set_range.rs +++ b/examples/btree_set_range.rs @@ -1,9 +1,8 @@ -extern crate quickcheck; - -use quickcheck::{quickcheck, TestResult}; use std::collections::BTreeSet; use std::ops::Bound::{self, *}; +use quickcheck::{quickcheck, TestResult}; + /// Covers every `std::ops::Range*` plus variants with exclusive start. type RangeAny = (Bound, Bound); @@ -11,6 +10,7 @@ type RangeAny = (Bound, Bound); trait RangeBounds { fn contains(&self, _: &T) -> bool; } + impl RangeBounds for RangeAny { fn contains(&self, item: &T) -> bool { (match &self.0 { diff --git a/examples/out_of_bounds.rs b/examples/out_of_bounds.rs index f17519c..10da938 100644 --- a/examples/out_of_bounds.rs +++ b/examples/out_of_bounds.rs @@ -1,5 +1,3 @@ -extern crate quickcheck; - use quickcheck::{quickcheck, TestResult}; fn main() { diff --git a/examples/reverse.rs b/examples/reverse.rs index ffbd2b7..fff6e71 100644 --- a/examples/reverse.rs +++ b/examples/reverse.rs @@ -1,5 +1,3 @@ -extern crate quickcheck; - use quickcheck::quickcheck; fn reverse(xs: &[T]) -> Vec { diff --git a/examples/reverse_single.rs b/examples/reverse_single.rs index 2384f9c..6112509 100644 --- a/examples/reverse_single.rs +++ b/examples/reverse_single.rs @@ -1,5 +1,3 @@ -extern crate quickcheck; - use quickcheck::{quickcheck, TestResult}; fn reverse(xs: &[T]) -> Vec { diff --git a/examples/sieve.rs b/examples/sieve.rs index 2079d56..f05b8e3 100644 --- a/examples/sieve.rs +++ b/examples/sieve.rs @@ -1,5 +1,3 @@ -extern crate quickcheck; - use quickcheck::quickcheck; fn sieve(n: usize) -> Vec { diff --git a/examples/sort.rs b/examples/sort.rs index d85d4ba..0f495a0 100644 --- a/examples/sort.rs +++ b/examples/sort.rs @@ -1,16 +1,14 @@ // This is a buggy quick sort implementation, QuickCheck will find the bug for // you. -extern crate quickcheck; - use quickcheck::quickcheck; fn smaller_than(xs: &[T], pivot: &T) -> Vec { - return xs.iter().filter(|&x| *x < *pivot).map(|x| x.clone()).collect(); + xs.iter().filter(|&x| *x < *pivot).map(|x| x.clone()).collect() } fn larger_than(xs: &[T], pivot: &T) -> Vec { - return xs.iter().filter(|&x| *x > *pivot).map(|x| x.clone()).collect(); + xs.iter().filter(|&x| *x > *pivot).map(|x| x.clone()).collect() } fn sortk(x: &T, xs: &[T]) -> Vec { diff --git a/src/lib.rs b/src/lib.rs index 34900ac..4202afb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,12 +6,6 @@ #![cfg_attr(feature = "i128", feature(i128_type, i128))] -#[cfg(feature = "use_logging")] -extern crate env_logger; -#[cfg(feature = "use_logging")] -#[macro_use] -extern crate log; - pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; @@ -68,6 +62,12 @@ macro_rules! quickcheck { fn env_logger_init() -> Result<(), log::SetLoggerError> { env_logger::try_init() } +#[cfg(feature = "use_logging")] +macro_rules! info { + ($($tt:tt)*) => { + log::info!($($tt)*) + }; +} #[cfg(not(feature = "use_logging"))] fn env_logger_init() {}