proptest_quickcheck_interop/lib.rs
1//-
2// Copyright 2017 Mazdak Farrokhzad
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! [`proptest`] is a property testing framework (i.e., the [`QuickCheck`] family)
11//! inspired by the [Hypothesis](http://hypothesis.works/) framework for
12//! Python.
13//!
14//! [`quickcheck`] is another property testing framework for Rust which uses
15//! a shrinking logic similar to Haskell's [`QuickCheck`].
16//!
17//! **This crate provides interoperability between quickcheck and proptest.**
18//! Currently, this is one way - if you've implemented quickcheck's
19//! [`Arbitrary`] trait as well as [`Debug`], which is a temporary requirement,
20//! then you may get back the equivalent [`Strategy`] in proptest.
21//!
22//! ## Status of this crate
23//!
24//! This crate is unlikely to see major changes. Any breaking changes
25//! are only likely to come as a result of changes in the dependencies used by
26//! this crate, in particular proptest or quickcheck. When any of those crates
27//! make breaking changes that affect this crate, then the major version of
28//! this crate will be bumped.
29//!
30//! See the [changelog] for a full list of substantial historical changes,
31//! breaking and otherwise.
32//!
33//! ## Using the interoperability layer
34//!
35//! Assuming that you already have a `Cargo.toml` file in your project with,
36//! among other things, the following:
37//!
38//! ```toml
39//! [dependencies]
40//!
41//! quickcheck = "0.6.0"
42//! proptest = "0.4.1"
43//! ```
44//!
45//! Now add this crate to your dependencies:
46//!
47//! ```toml
48//! [dependencies]
49//!
50//! quickcheck = "0.6.0"
51//! proptest = "0.4.1"
52//! proptest_quickcheck_interop = "2.0.0"
53//! ```
54//!
55//! Let's now assume that `usize` is a complex type for which you have
56//! implemented `quickcheck::Arbitrary`. You wish you reuse this in proptest
57//! or if you simply prefer the implementation provided by quickcheck.
58//! To do so, you can use [`from_qc`]:
59//!
60//! ```rust
61//! // Import crates:
62//! #[macro_use] extern crate proptest;
63//! extern crate proptest_quickcheck_interop as pqci;
64//!
65//! // And what we need into our scope:
66//! use proptest::strategy::Strategy;
67//! use pqci::from_qc;
68//!
69//! /// Given a usize returns the nearest usize that is also even.
70//! fn make_even(x: usize) -> usize {
71//! if x % 2 == 1 { x - 1 } else { x }
72//! }
73//!
74//! proptest! {
75//! /// A property asserting that make_even always produces an even usize.
76//! fn always_even(ref x in from_qc::<usize>().prop_map(make_even)) {
77//! prop_assert!(x % 2 == 0);
78//! }
79//! }
80//!
81//! fn main() {
82//! always_even();
83//! }
84//! ```
85//!
86//! If you want to control the `size` of the input generated by quickcheck
87//! you may instead use [`from_qc_sized(size)`][`from_qc_sized`]. If you use,
88//! [`from_qc`], then the default size used by quickcheck is used.
89//!
90//! [`from_qc`]: https://docs.rs/proptest-quickcheck-interop/2.0.0/proptest_quickcheck_interop/fn.from_qc.html
91//! [`from_qc_sized`]: https://docs.rs/proptest-quickcheck-interop/2.0.0/proptest_quickcheck_interop/fn.from_qc_sized.html
92//!
93//! [changelog]:
94//! https://github.com/Centril/proptest-quickcheck-interop/blob/master/CHANGELOG.md
95//!
96//! [`Debug`]: https://doc.rust-lang.org/nightly/std/fmt/trait.Debug.html
97//!
98//! [`Arbitrary`]: https://docs.rs/quickcheck/0.6.0/quickcheck/trait.Arbitrary.html
99//!
100//! [`proptest`]: https://crates.io/crates/proptest
101//!
102//! [`quickcheck`]: https://crates.io/crates/quickcheck
103//!
104//! [`Strategy`]: https://docs.rs/proptest/0.4.1/proptest/strategy/trait.Strategy.html
105
106#![deny(missing_docs)]
107
108//==============================================================================
109// Imports:
110//==============================================================================
111
112extern crate proptest;
113extern crate quickcheck as quickcheck_crate;
114
115use std::mem;
116use std::marker::PhantomData;
117use std::fmt::{Debug, Formatter, Result as FResult};
118
119use quickcheck_crate::{Arbitrary, Gen, Rng};
120
121use proptest::strategy::*;
122use proptest::test_runner::TestRunner;
123use proptest::prelude::XorShiftRng;
124
125//==============================================================================
126// Convenience API:
127//==============================================================================
128
129/// Constructs a new [`Strategy`] for any type that implements [`quickcheck`]'s
130/// [`Arbitrary`] trait as well as [`Debug`] (a temporary requirement).
131/// You may use this to gain interoperability by reusing implementations of
132/// [`Arbitrary`] that you've already defined for some type.
133///
134/// Using this version, the size parameter controlling the size of inputs will
135/// be the default one used by `quickcheck`. This parameter is passed to the
136/// implementation of `Arbitrary`. If you want to provide some other value,
137/// you may instead use [`from_qc_sized`].
138///
139/// [`Debug`]: https://doc.rust-lang.org/nightly/std/fmt/trait.Debug.html
140/// [`Arbitrary`]: https://docs.rs/quickcheck/0.6.0/quickcheck/trait.Arbitrary.html
141/// [`from_qc_sized`]: https://docs.rs/proptest-quickcheck-interop//proptest_quickcheck_interop/fn.from_qc_sized.html
142/// [`quickcheck`]: https://crates.io/crates/quickcheck
143/// [`Strategy`]: https://docs.rs/proptest/0.4.1/proptest/strategy/trait.Strategy.html
144pub fn from_qc<A: Arbitrary + Debug>() -> QCStrategy<A> {
145 QCStrategy::new(qc_gen_size())
146}
147
148/// Copied from:
149/// https://docs.rs/quickcheck/0.6.0/src/quickcheck/tester.rs.html#35-41
150/// TODO: Remove if @burntsushi makes this pub.
151fn qc_gen_size() -> usize {
152 use std::env;
153 let default = 100;
154 match env::var("QUICKCHECK_GENERATOR_SIZE") {
155 Ok(val) => val.parse().unwrap_or(default),
156 Err(_) => default,
157 }
158}
159
160/// Constructs a new [`Strategy`] for any type that implements [`quickcheck`]'s
161/// [`Arbitrary`] trait as well as [`Debug`] (a temporary requirement).
162/// You may use this to gain interoperability by reusing implementations of
163/// [`Arbitrary`] that you've already defined for some type.
164///
165/// Using this version, you may provide a size parameter controlling the size
166/// of inputs. This parameter is passed to the implementation of `Arbitrary`.
167///
168/// [`Debug`]: https://doc.rust-lang.org/nightly/std/fmt/trait.Debug.html
169/// [`Arbitrary`]: https://docs.rs/quickcheck/0.6.0/quickcheck/trait.Arbitrary.html
170/// [`quickcheck`]: https://crates.io/crates/quickcheck
171/// [`Strategy`]: https://docs.rs/proptest/0.4.1/proptest/strategy/trait.Strategy.html
172pub fn from_qc_sized<A: Arbitrary + Debug>(size: usize) -> QCStrategy<A> {
173 QCStrategy::new(size)
174}
175
176//==============================================================================
177// The strategy
178//==============================================================================
179
180/// `QCStrategy` is a [`Strategy`] that provides interoperability with
181/// [`quickcheck`]'s [`Arbitrary`] trait. If you have any type implementing
182/// [`Arbitrary`] and [`Debug`], which a temporary requirement, then you may
183/// get back the equivalent Strategy in proptest.
184///
185/// [`Debug`]: https://doc.rust-lang.org/nightly/std/fmt/trait.Debug.html
186/// [`Arbitrary`]: https://docs.rs/quickcheck/0.6.0/quickcheck/trait.Arbitrary.html
187/// [`quickcheck`]: https://crates.io/crates/quickcheck
188/// [`Strategy`]: https://docs.rs/proptest/0.4.1/proptest/strategy/trait.Strategy.html
189#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
190pub struct QCStrategy<A: Arbitrary + Debug> {
191 __ph: PhantomData<A>,
192 size: usize,
193}
194
195/// The [`ValueTree`] implementation for [`QCStrategy`].
196///
197/// [`QCStrategy`]: https://docs.rs/proptest-quickcheck-interop//proptest_quickcheck_interop/struct.QCStrategy.html
198/// [`ValueTree`]: https://docs.rs/proptest/0.4.1/proptest/strategy/trait.ValueTree.html
199pub struct QCValueTree<A: Debug> {
200 curr: A,
201 prev: Option<A>,
202 shrinker: Box<Iterator<Item = A>>
203}
204
205impl<A: Arbitrary + Debug> QCStrategy<A> {
206 /// Constructs a new `QCStrategy` given a `size` parameter that:
207 ///
208 /// > controls the size of random values generated. For example, it
209 /// > specifies the maximum length of a randomly generated vector and also
210 /// > will specify the maximum magnitude of a randomly generated number.
211 ///
212 /// as defined by [`quickcheck`]
213 ///
214 /// [`quickcheck`]: https://crates.io/crates/quickcheck
215 pub fn new(size: usize) -> Self {
216 Self { __ph: PhantomData, size }
217 }
218}
219
220impl<A: Debug> QCValueTree<A> {
221 /// Constructs a new `QCValueTree`, that is the [`ValueTree`] for
222 /// [`QCStrategy`] given:
223 /// + the current value,
224 /// + the iterator producing increasingly shrunken versions of the current
225 /// value given.
226 ///
227 /// [`QCStrategy`]: struct.QCStrategy
228 /// [`ValueTree`]: trait.ValueTree
229 fn new(curr: A, shrinker: Box<Iterator<Item = A>>) -> Self {
230 Self { prev: None, curr, shrinker }
231 }
232}
233
234impl<A: Arbitrary + Debug> Strategy for QCStrategy<A> {
235 type Value = QCValueTree<A>;
236
237 fn new_value(&self, runner: &mut TestRunner) -> NewTree<Self> {
238 let mut gen = XorShiftGen::new(runner.rng(), self.size);
239 let curr = A::arbitrary(&mut gen);
240 let shrinker = curr.shrink();
241 Ok(QCValueTree::new(curr, shrinker))
242 }
243}
244
245impl<A: Clone + Debug> ValueTree for QCValueTree<A> {
246 type Value = A;
247
248 fn current(&self) -> Self::Value {
249 self.curr.clone()
250 }
251
252 fn complicate(&mut self) -> bool {
253 // We can only complicate if we previously simplified.
254 // Complicating twice in a row without interleaved simplification
255 // is guaranteed to always yield false for the second call.
256 if let Some(prev) = self.prev.take() {
257 // Throw away the current value!
258 self.curr = prev;
259 true
260 } else {
261 false
262 }
263 }
264
265 fn simplify(&mut self) -> bool {
266 if let Some(simpler) = self.shrinker.next() {
267 // Throw away the previous value and set the current value as prev.
268 // Advance the iterator and set the current value to the next one.
269 self.prev = Some(mem::replace(&mut self.curr, simpler));
270 true
271 } else {
272 // Successive calls to .next() are now assumed to yield None,
273 // Can't shrink anymore.
274 false
275 }
276 }
277}
278
279impl<A: Debug> Debug for QCValueTree<A> {
280 fn fmt(&self, fmt: &mut Formatter) -> FResult {
281 fmt.debug_struct("QCValueTree")
282 .field("curr", &self.curr)
283 .field("prev", &self.prev)
284 // We could change shrinker to be : Vec<A> instead, but that seems
285 // not worth the cost of maintaining all the shrinked-to elements
286 // in the ValueTree. If the iterator is side-effecting the behaviour
287 // may also be quite strange.
288 .field("shrinker", &"<iterator>")
289 .finish()
290 }
291}
292
293//==============================================================================
294// XorShiftGen wrapper
295//==============================================================================
296
297/// A wrapper around a mutable reference of [`XorShiftRng`] and a `size` that
298/// controls the size of random values generated makes for an implementation
299/// of a [`Gen`].
300///
301/// [`XorShiftRng`]: https://docs.rs/rand/0.4.2/rand/struct.XorShiftRng.html
302/// [`Gen`]: https://docs.rs/quickcheck/0.6.0/quickcheck/trait.Gen.html
303#[derive(Debug)]
304struct XorShiftGen<'a> {
305 rng: &'a mut XorShiftRng,
306 size: usize,
307}
308
309impl<'a> XorShiftGen<'a> {
310 /// Construct a new generator given the backing RNG and the
311 /// size controlling the size of random values generated.
312 pub fn new(rng: &'a mut XorShiftRng, size: usize) -> Self {
313 Self { rng, size }
314 }
315}
316
317impl<'a> Rng for XorShiftGen<'a> {
318 fn next_u32(&mut self) -> u32 { self.rng.next_u32() }
319}
320
321impl<'a> Gen for XorShiftGen<'a> {
322 fn size(&self) -> usize { self.size }
323}
324
325//==============================================================================
326// Tests
327//==============================================================================
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 impl Clone for QCValueTree<Vec<u32>> {
334 /// Only implemented so that the implementation can be tested.
335 /// As to why this is not a public API, read the note below.
336 fn clone(&self) -> Self {
337 // We assume that:
338 //
339 // let x = A::arbitrary(gen);
340 // let s1 = x.clone().shrink();
341 // let s2 = x.shrink();
342 // assert_eq(s1.collect::<Vec<A>>(), s2.collect::<Vec<A>>());
343 //
344 // always holds. In other words, that shrinking is deterministic.
345 // We also assume that:
346 //
347 // A::arbitrary(gen);
348 // let s = x.shrink();
349 // let y = x.next()?;
350 // assert_eq!(x.next(), y.shrink().next());
351 //
352 // always holds.
353 //
354 // It may not actually hold for all A, but we don't care, as we only
355 // provide Clone for testing purposes and we pick A = u32 for which
356 // it always holds.
357 QCValueTree {
358 prev: self.prev.clone(),
359 curr: self.curr.clone(),
360 shrinker: self.curr.shrink()
361 }
362 }
363 }
364
365 #[test]
366 fn contract_followed_by_qc_strategy() {
367 check_strategy_sanity(from_qc_sized::<Vec<u32>>(10), None);
368 }
369}