Skip to main content

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}