diff --git a/AUTHORS.txt b/AUTHORS.txt index 910c928e913ed..61059b706ce18 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -23,6 +23,7 @@ Andreas Ots Andrei Formiga Andrew Chin Andrew Dunham +Andrew Gallant Andrew Paseltiner Anthony Juckel aochagavia diff --git a/mk/crates.mk b/mk/crates.mk index b75b5ba81e2bd..07ac371c31aac 100644 --- a/mk/crates.mk +++ b/mk/crates.mk @@ -51,8 +51,9 @@ TARGET_CRATES := libc std green rustuv native flate arena glob term semver \ uuid serialize sync getopts collections num test time rand \ - workcache url log regex graphviz core -HOST_CRATES := syntax rustc rustdoc fourcc hexfloat regex_macros fmt_macros + workcache url log regex graphviz core quickcheck +HOST_CRATES := syntax rustc rustdoc fourcc hexfloat regex_macros fmt_macros \ + quickcheck_macros CRATES := $(TARGET_CRATES) $(HOST_CRATES) TOOLS := compiletest rustdoc rustc @@ -89,6 +90,8 @@ DEPS_log := std sync DEPS_regex := std collections DEPS_regex_macros = syntax std regex DEPS_fmt_macros = std +DEPS_quickcheck = std collections log rand +DEPS_quickcheck_macros = syntax std TOOL_DEPS_compiletest := test green rustuv getopts TOOL_DEPS_rustdoc := rustdoc native diff --git a/src/doc/index.md b/src/doc/index.md index 0bfc9baaa1688..00cda0503b89d 100644 --- a/src/doc/index.md +++ b/src/doc/index.md @@ -40,6 +40,7 @@ li {list-style-type: none; } * [The `libc` bindings](libc/index.html) * [The `native` 1:1 threading runtime](native/index.html) * [The `num` arbitrary precision numerics library](num/index.html) +* [The `quickcheck` crate for intelligent random testing](quickcheck/index.html) * [The `rand` library for random numbers and distributions](rand/index.html) * [The `regex` library for regular expressions](regex/index.html) * [The `rustc` compiler](rustc/index.html) diff --git a/src/libquickcheck/arbitrary.rs b/src/libquickcheck/arbitrary.rs new file mode 100644 index 0000000000000..a80c12bfddbca --- /dev/null +++ b/src/libquickcheck/arbitrary.rs @@ -0,0 +1,712 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::num; +use std::str; +use rand::Rng; + +/// `Gen` wraps a `rand::Rng` with parameters to control the distribution of +/// random values. +/// +/// A value with type satisfying the `Gen` trait can be constructed with the +/// `gen` function in this crate. +pub trait Gen : Rng { + fn size(&self) -> uint; +} + +/// StdGen is the default implementation of `Gen`. +/// +/// Values of type `StdGen` can be created with the `gen` function in this +/// crate. +pub struct StdGen { + rng: R, + size: uint, +} + +impl StdGen { + /// Returns a `Gen` 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 generator vector + /// and also will specify the maximum magnitude of a randomly generated number. + pub fn new(rng: R, size: uint) -> StdGen { + StdGen { rng: rng, size: size } + } +} + +impl Rng 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) } +} + +impl Gen for StdGen { + fn size(&self) -> uint { self.size } +} + +/// `~Shrinker` is an existential type that represents an arbitrary iterator +/// by satisfying the `Iterator` trait. +/// +/// This makes writing shrinkers easier. +/// You should not have to implement this trait directly. By default, all +/// types which implement the `Iterator` trait also implement the `Shrinker` +/// trait. +/// +/// The `A` type variable corresponds to the elements yielded by the iterator. +pub trait Shrinker { + /// Wraps `.next()`. + fn next_shrink(&mut self) -> Option; +} + +impl Iterator for Box> { + fn next(&mut self) -> Option { self.next_shrink() } +} + +impl> Shrinker for A { + fn next_shrink(&mut self) -> Option { self.next() } +} + +/// A shrinker than yields no values. +pub struct EmptyShrinker; + +impl EmptyShrinker { + /// Creates a shrinker with zero elements. + pub fn new() -> Box> { + box EmptyShrinker:: as Box> + } +} + +impl Iterator for EmptyShrinker { + fn next(&mut self) -> Option { None } +} + +/// A shrinker than yields a single value. +pub struct SingleShrinker { + value: Option +} + +impl SingleShrinker { + /// Creates a shrinker with a single element. + pub fn new(value: A) -> Box> { + box SingleShrinker { value: Some(value) } as Box> + } +} + +impl Iterator for SingleShrinker { + fn next(&mut self) -> Option { self.value.take() } +} + +/// `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 uses a `Gen` to control the distribution of random values. +/// +/// 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 since every test is run inside its own task. +/// (This permits failures to include task failures.) +pub trait Arbitrary : Clone + Send { + fn arbitrary(g: &mut G) -> Self; + fn shrink(&self) -> Box> { + EmptyShrinker::new() + } +} + +impl Arbitrary for () { + fn arbitrary(_: &mut G) -> () { () } +} + +impl Arbitrary for bool { + fn arbitrary(g: &mut G) -> bool { g.gen() } + fn shrink(&self) -> Box> { + match *self { + true => SingleShrinker::new(false), + false => EmptyShrinker::new(), + } + } +} + +impl Arbitrary for Option { + fn arbitrary(g: &mut G) -> Option { + if g.gen() { + None + } else { + Some(Arbitrary::arbitrary(g)) + } + } + + fn shrink(&self) -> Box>> { + match *self { + None => { + EmptyShrinker::new() + } + Some(ref x) => { + let chain = SingleShrinker::new(None).chain(x.shrink().map(Some)); + box chain as Box>> + } + } + } +} + +impl Arbitrary for Result { + fn arbitrary(g: &mut G) -> Result { + if g.gen() { + Ok(Arbitrary::arbitrary(g)) + } else { + Err(Arbitrary::arbitrary(g)) + } + } + + fn shrink(&self) -> Box>> { + match *self { + Ok(ref x) => { + let xs: Box> = x.shrink(); + let tagged = xs.map::<'static, Result>(Ok); + box tagged as Box>> + } + Err(ref x) => { + let xs: Box> = x.shrink(); + let tagged = xs.map::<'static, Result>(Err); + box tagged as Box>> + } + } + } +} + +impl Arbitrary for (A, B) { + fn arbitrary(g: &mut G) -> (A, B) { + return (Arbitrary::arbitrary(g), Arbitrary::arbitrary(g)) + } + + // Shrinking a tuple is done by shrinking the first element and generating + // a new tuple with each shrunk element from the first along with a copy of + // the given second element. Vice versa for the second element. More + // precisely: + // + // shrink((a, b)) = + // let (sa, sb) = (a.shrink(), b.shrink()); + // vec!((sa1, b), ..., (saN, b), (a, sb1), ..., (a, sbN)) + // + fn shrink(&self) -> Box> { + let (ref a, ref b) = *self; + let sas = a.shrink().scan(b, |b, a| { + Some((a, b.clone())) + }); + let sbs = b.shrink().scan(a, |a, b| { + Some((a.clone(), b)) + }); + box sas.chain(sbs) as Box> + } +} + +impl Arbitrary for (A, B, C) { + fn arbitrary(g: &mut G) -> (A, B, C) { + return ( + Arbitrary::arbitrary(g), + Arbitrary::arbitrary(g), + Arbitrary::arbitrary(g), + ) + } + + fn shrink(&self) -> Box> { + let (ref a, ref b, ref c) = *self; + let sas = a.shrink().scan((b, c), |&(b, c), a| { + Some((a, b.clone(), c.clone())) + }); + let sbs = b.shrink().scan((a, c), |&(a, c), b| { + Some((a.clone(), b, c.clone())) + }); + let scs = c.shrink().scan((a, b), |&(a, b), c| { + Some((a.clone(), b.clone(), c)) + }); + box sas.chain(sbs).chain(scs) as Box> + } +} + +impl Arbitrary for Vec { + fn arbitrary(g: &mut G) -> Vec { + let size = { let s = g.size(); g.gen_range(0, s) }; + Vec::from_fn(size, |_| Arbitrary::arbitrary(g)) + } + + fn shrink(&self) -> Box>> { + if self.len() == 0 { + return EmptyShrinker::new() + } + + // Start the shrunk values with an empty vector. + let mut xs: Vec> = vec!(vec!()); + + // Explore the space of different sized vectors without shrinking + // any of the elements. + let mut k = self.len() / 2; + while k > 0 { + xs.push_all_move(shuffle_vec(self.as_slice(), k)); + k = k / 2; + } + + // Now explore the space of vectors where each element is shrunk + // in turn. A new vector is generated for each shrunk value of each + // element. + for (i, x) in self.iter().enumerate() { + for sx in x.shrink() { + let mut change_one = self.clone(); + *change_one.get_mut(i) = sx; + xs.push(change_one); + } + } + box xs.move_iter() as Box>> + } +} + +impl Arbitrary for StrBuf { + fn arbitrary(g: &mut G) -> StrBuf { + let size = { let s = g.size(); g.gen_range(0, s) }; + g.gen_ascii_str(size).to_strbuf() + } + + fn shrink(&self) -> Box> { + // Shrink a string by shrinking a vector of its characters. + let chars: Vec = self.as_slice().chars().collect(); + box chars.shrink().map(|x| x.move_iter().collect::()) as Box> + } +} + +impl Arbitrary for ~str { + fn arbitrary(g: &mut G) -> ~str { + let size = { let s = g.size(); g.gen_range(0, s) }; + g.gen_ascii_str(size) + } + + fn shrink(&self) -> Box> { + // Shrink a string by shrinking a vector of its characters. + let chars: Vec = self.chars().collect(); + let mut strs: Vec<~str> = vec!(); + for x in chars.shrink() { + strs.push(str::from_chars(x.as_slice())); + } + box strs.move_iter() as Box> + } +} + +impl Arbitrary for char { + fn arbitrary(g: &mut G) -> char { g.gen() } + + fn shrink(&self) -> Box> { + // No char shrinking for now. + EmptyShrinker::new() + } +} + +impl Arbitrary for int { + fn arbitrary(g: &mut G) -> int { + let s = g.size(); g.gen_range(-(s as int), s as int) + } + fn shrink(&self) -> Box> { + SignedShrinker::new(*self) + } +} + +impl Arbitrary for i8 { + fn arbitrary(g: &mut G) -> i8 { + let s = g.size(); g.gen_range(-(s as i8), s as i8) + } + fn shrink(&self) -> Box> { + SignedShrinker::new(*self) + } +} + +impl Arbitrary for i16 { + fn arbitrary(g: &mut G) -> i16 { + let s = g.size(); g.gen_range(-(s as i16), s as i16) + } + fn shrink(&self) -> Box> { + SignedShrinker::new(*self) + } +} + +impl Arbitrary for i32 { + fn arbitrary(g: &mut G) -> i32 { + let s = g.size(); g.gen_range(-(s as i32), s as i32) + } + fn shrink(&self) -> Box> { + SignedShrinker::new(*self) + } +} + +impl Arbitrary for i64 { + fn arbitrary(g: &mut G) -> i64 { + let s = g.size(); g.gen_range(-(s as i64), s as i64) + } + fn shrink(&self) -> Box> { + SignedShrinker::new(*self) + } +} + +impl Arbitrary for uint { + fn arbitrary(g: &mut G) -> uint { + let s = g.size(); g.gen_range(0, s) + } + fn shrink(&self) -> Box> { + UnsignedShrinker::new(*self) + } +} + +impl Arbitrary for u8 { + fn arbitrary(g: &mut G) -> u8 { + let s = g.size(); g.gen_range(0, s as u8) + } + fn shrink(&self) -> Box> { + UnsignedShrinker::new(*self) + } +} + +impl Arbitrary for u16 { + fn arbitrary(g: &mut G) -> u16 { + let s = g.size(); g.gen_range(0, s as u16) + } + fn shrink(&self) -> Box> { + UnsignedShrinker::new(*self) + } +} + +impl Arbitrary for u32 { + fn arbitrary(g: &mut G) -> u32 { + let s = g.size(); g.gen_range(0, s as u32) + } + fn shrink(&self) -> Box> { + UnsignedShrinker::new(*self) + } +} + +impl Arbitrary for u64 { + fn arbitrary(g: &mut G) -> u64 { + let s = g.size(); g.gen_range(0, s as u64) + } + fn shrink(&self) -> Box> { + UnsignedShrinker::new(*self) + } +} + +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> { + let it = SignedShrinker::new(self.to_i32().unwrap()); + box it.map(|x| x.to_f32().unwrap()) as Box> + } +} + +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> { + let it = SignedShrinker::new(self.to_i64().unwrap()); + box it.map(|x| x.to_f64().unwrap()) as Box> + } +} + +/// Returns a sequence of vectors with each contiguous run of elements of +/// length `k` removed. +fn shuffle_vec(xs: &[A], k: uint) -> Vec> { + fn shuffle(xs: &[A], k: uint, n: uint) -> Vec> { + if k > n { + return vec!() + } + let xs1: Vec = xs.slice_to(k).iter().map(|x| x.clone()).collect(); + let xs2: Vec = xs.slice_from(k).iter().map(|x| x.clone()).collect(); + if xs2.len() == 0 { + return vec!(vec!()) + } + + let cat = |x: &Vec| { + let mut pre = xs1.clone(); + pre.push_all_move(x.clone()); + pre + }; + let shuffled = shuffle(xs2.as_slice(), k, n-k); + let mut more: Vec> = shuffled.iter().map(cat).collect(); + more.unshift(xs2); + more + } + shuffle(xs, k, xs.len()) +} + +fn half(x: A) -> A { x / num::cast(2).unwrap() } + +struct SignedShrinker { + x: A, + i: A, +} + +impl SignedShrinker { + fn new(x: A) -> Box> { + if x.is_zero() { + EmptyShrinker::::new() + } else { + let shrinker = SignedShrinker { + x: x, + i: half(x), + }; + if shrinker.i.is_negative() { + let start = vec![num::zero(), shrinker.x.abs()].move_iter(); + box start.chain(shrinker) as Box> + } else { + box { vec![num::zero()] }.move_iter().chain(shrinker) as Box> + } + } + } +} + +impl Iterator for SignedShrinker { + fn next(&mut self) -> Option { + if (self.x - self.i).abs() < self.x.abs() { + let result = Some(self.x - self.i); + self.i = half(self.i); + result + } else { + None + } + } +} + +struct UnsignedShrinker { + x: A, + i: A, +} + +impl UnsignedShrinker { + fn new(x: A) -> Box> { + if x.is_zero() { + EmptyShrinker::::new() + } else { + box { vec![num::zero()] }.move_iter().chain( + UnsignedShrinker { + x: x, + i: half(x), + } + ) as Box> + } + } +} + +impl Iterator for UnsignedShrinker { + fn next(&mut self) -> Option { + if self.x - self.i < self.x { + let result = Some(self.x - self.i); + self.i = half(self.i); + result + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use std::fmt::Show; + use std::hash::Hash; + use std::iter; + use collections::HashSet; + use rand; + use super::Arbitrary; + + // Arbitrary testing. (Not much here. What else can I reasonably test?) + #[test] + fn arby_unit() { + assert_eq!(arby::<()>(), ()); + } + + #[test] + fn arby_int() { + rep(|| { let n: int = arby(); assert!(n >= -5 && n <= 5); } ); + } + + #[test] + fn arby_uint() { + rep(|| { let n: uint = arby(); assert!(n <= 5); } ); + } + + fn arby() -> A { + super::Arbitrary::arbitrary(&mut gen()) + } + + fn gen() -> super::StdGen { + super::StdGen::new(rand::task_rng(), 5) + } + + fn rep(f: ||) { + for _ in iter::range(0, 100) { + f() + } + } + + // Shrink testing. + #[test] + fn unit() { + eq((), vec!()); + } + + #[test] + fn bools() { + eq(false, vec!()); + eq(true, vec!(false)); + } + + #[test] + fn options() { + eq(None::<()>, vec!()); + eq(Some(false), vec!(None)); + eq(Some(true), vec!(None, Some(false))); + } + + #[test] + fn results() { + // FIXME #14097: Result doesn't implement the Hash + // trait, so these tests depends on the order of shrunk + // results. Ug. + ordered_eq(Ok::(true), vec!(Ok(false))); + ordered_eq(Err::<(), bool>(true), vec!(Err(false))); + } + + #[test] + fn tuples() { + eq((false, false), vec!()); + eq((true, false), vec!((false, false))); + eq((true, true), vec!((false, true), (true, false))); + } + + #[test] + fn triples() { + eq((false, false, false), vec!()); + eq((true, false, false), vec!((false, false, false))); + eq((true, true, false), + vec!((false, true, false), (true, false, false))); + } + + #[test] + fn ints() { + // FIXME #14097: Test overflow? + eq(5i, vec!(0, 3, 4)); + eq(-5i, vec!(5, 0, -3, -4)); + eq(0i, vec!()); + } + + #[test] + fn ints8() { + eq(5i8, vec!(0, 3, 4)); + eq(-5i8, vec!(5, 0, -3, -4)); + eq(0i8, vec!()); + } + + #[test] + fn ints16() { + eq(5i16, vec!(0, 3, 4)); + eq(-5i16, vec!(5, 0, -3, -4)); + eq(0i16, vec!()); + } + + #[test] + fn ints32() { + eq(5i32, vec!(0, 3, 4)); + eq(-5i32, vec!(5, 0, -3, -4)); + eq(0i32, vec!()); + } + + #[test] + fn ints64() { + eq(5i64, vec!(0, 3, 4)); + eq(-5i64, vec!(5, 0, -3, -4)); + eq(0i64, vec!()); + } + + #[test] + fn uints() { + eq(5u, vec!(0, 3, 4)); + eq(0u, vec!()); + } + + #[test] + fn uints8() { + eq(5u8, vec!(0, 3, 4)); + eq(0u8, vec!()); + } + + #[test] + fn uints16() { + eq(5u16, vec!(0, 3, 4)); + eq(0u16, vec!()); + } + + #[test] + fn uints32() { + eq(5u32, vec!(0, 3, 4)); + eq(0u32, vec!()); + } + + #[test] + fn uints64() { + eq(5u64, vec!(0, 3, 4)); + eq(0u64, vec!()); + } + + #[test] + fn vecs() { + eq({let it: Vec = vec!(); it}, vec!()); + eq({let it: Vec> = vec!(vec!()); it}, vec!(vec!())); + eq(vec!(1), vec!(vec!(), vec!(0))); + eq(vec!(11), vec!(vec!(), vec!(0), vec!(6), vec!(9), vec!(10))); + eq( + vec!(3, 5), + vec!(vec!(), vec!(5), vec!(3), vec!(0,5), vec!(2,5), + vec!(3,0), vec!(3,3), vec!(3,4)) + ); + } + + #[test] + fn chars() { + eq('a', vec!()); + } + + #[test] + fn strs() { + eq("".to_owned(), vec!()); + eq("A".to_owned(), vec!("".to_owned())); + eq("ABC".to_owned(), vec!("".to_owned(), + "AB".to_owned(), + "BC".to_owned(), + "AC".to_owned())); + } + + // All this jazz is for testing set equality on the results of a shrinker. + fn eq(s: A, v: Vec) { + let (left, right) = (shrunk(s), set(v)); + assert!(left.eq(&right) && right.eq(&left)); + } + fn shrunk(s: A) -> HashSet { + set(s.shrink().collect()) + } + fn set(xs: Vec) -> HashSet { + xs.move_iter().collect() + } + + fn ordered_eq(s: A, v: Vec) { + let (left, right) = (s.shrink().collect::>(), v); + assert!(left.eq(&right) && right.eq(&left)); + } +} diff --git a/src/libquickcheck/lib.rs b/src/libquickcheck/lib.rs new file mode 100755 index 0000000000000..e442693a02239 --- /dev/null +++ b/src/libquickcheck/lib.rs @@ -0,0 +1,1000 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This crate is a port of [Haskell's +//! QuickCheck](http://hackage.haskell.org/package/QuickCheck) for +//! intelligent random testing. +//! +//! 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. +//! All QuickCheck needs is a property function---it will then randomly generate +//! inputs to that function and call the property for each set of inputs. If the +//! property fails (whether by a runtime error like index out-of-bounds or by not +//! satisfying your property), the inputs are "shrunk" to find a smaller +//! counter-example. +//! +//! The shrinking strategies for lists and numbers use a binary search to cover +//! the input space quickly. (It should be the same strategy used in +//! [Koen Claessen's QuickCheck for +//! Haskell](http://hackage.haskell.org/package/QuickCheck).) +//! +//! +//! # Simple example +//! +//! Here's a complete working program that tests a function that +//! reverses a vector: +//! +//! ```rust +//! extern crate quickcheck; +//! +//! use quickcheck::quickcheck; +//! +//! fn reverse(xs: &[T]) -> Vec { +//! let mut rev = vec!(); +//! for x in xs.iter() { +//! rev.unshift(x.clone()) +//! } +//! rev +//! } +//! +//! fn main() { +//! fn prop(xs: Vec) -> bool { +//! xs == reverse(reverse(xs.as_slice()).as_slice()) +//! } +//! quickcheck(prop); +//! } +//! ``` +//! +//! # The `#[quickcheck]` attribute +//! +//! To make it easier to write QuickCheck tests, the `#[quickcheck]` attribute +//! will convert a property function into a `#[test]` function. +//! +//! To use the `#[quickcheck]` attribute, you must enable the `phase` feature and +//! import the `quickcheck_macros` crate as a syntax extension: +//! +//! ```rust +//! #![feature(phase)] +//! #[phase(syntax)] +//! extern crate quickcheck_macros; +//! extern crate quickcheck; +//! +//! # fn main() {} +//! # pub fn reverse(xs: &[T]) -> Vec { +//! # let mut rev = vec!(); +//! # for x in xs.iter() { rev.unshift(x.clone()) } +//! # rev +//! # } +//! #[quickcheck] +//! fn double_reversal_is_identity(xs: Vec) -> bool { +//! xs == reverse(reverse(xs.as_slice()).as_slice()) +//! } +//! ``` +//! +//! The `#[quickcheck]` attribute also works with static items. The reason will +//! be apparent in the next section. +//! +//! # 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 +//! of that subset, you'd discard it. In particular, the property should *neither* +//! pass nor fail on inputs outside of the subset you want to test. But properties +//! return boolean values---which either indicate pass or fail. +//! +//! To fix this, we need to take a step back and look at the type of the +//! `quickcheck` function: +//! +//! ```rust +//! # use quickcheck::Testable; +//! # fn main() {} +//! pub fn quickcheck(f: A) { +//! // elided +//! } +//! ``` +//! +//! So `quickcheck` can test any value with a type that satisfies the `Testable` +//! trait. Great, so what is this `Testable` business? +//! +//! ```rust +//! # use quickcheck::{Gen, TestResult}; +//! +//! pub trait Testable { +//! fn result(&self, &mut G) -> TestResult; +//! } +//! # pub fn main() {} +//! ``` +//! +//! This trait states that a type is testable if it can produce a `TestResult` +//! given a source of randomness. (A `TestResult` stores information about the +//! results of a test, like whether it passed, failed or has been discarded.) +//! +//! Sure enough, `bool` satisfies the `Testable` trait: +//! +//! ```rust,ignore +//! impl Testable for bool { +//! fn result(&self, _: &mut G) -> TestResult { +//! TestResult::from_bool(*self) +//! } +//! } +//! ``` +//! +//! But in the example, we gave a *function* to `quickcheck`. Yes, functions can +//! satisfy `Testable` too! +//! +//! ```rust,ignore +//! impl Testable for fn(A) -> B { +//! fn result(&self, g: &mut G) -> TestResult { +//! // elided +//! +//! } +//! } +//! ``` +//! +//! Which says that a function satisfies `Testable` if and only if it has a single +//! parameter type (whose values can be randomly generated and shrunk) and returns +//! any type (that also satisfies `Testable`). So a function with type +//! `fn(uint) -> bool` satisfies `Testable` since `uint` satisfies `Arbitrary` and +//! `bool` satisfies `Testable`. +//! +//! So to discard a test, we need to return something other than `bool`. What if we +//! just returned a `TestResult` directly? That should work, but we'll need to +//! make sure `TestResult` satisfies `Testable`: +//! +//! ```rust,ignore +//! impl Testable for TestResult { +//! fn result(&self, _: &mut G) -> TestResult { self.clone() } +//! } +//! ``` +//! +//! Now we can test functions that return a `TestResult` directly. +//! +//! As an example, let's test our reverse function to make sure that the reverse of +//! a vector of length 1 is equal to the vector itself. +//! +//! ```rust +//! use quickcheck::{quickcheck, TestResult}; +//! +//! # fn reverse(xs: &[T]) -> Vec { +//! # let mut rev = vec!(); +//! # for x in xs.iter() { rev.unshift(x.clone()) } +//! # rev +//! # } +//! fn prop(xs: Vec) -> TestResult { +//! if xs.len() != 1 { +//! return TestResult::discard() +//! } +//! TestResult::from_bool(xs == reverse(xs.as_slice())) +//! } +//! quickcheck(prop); +//! ``` +//! +//! So now our property returns a `TestResult`, which allows us to +//! encode a bit more information. There are a few more [convenience +//! functions defined for the `TestResult` +//! type](struct.TestResult.html). For example, we can't just return +//! a `bool`, so we convert a `bool` value to a `TestResult`. +//! +//! (The ability to discard tests allows you to get similar functionality as +//! Haskell's `==>` combinator.) +//! +//! N.B. Since discarding a test means it neither passes nor fails, +//! `quickcheck` will try to replace the discarded test with a fresh +//! one. However, if your condition is seldom met, it's possible that +//! `quickcheck` will have to settle for running fewer tests than +//! usual. By default, if `quickcheck` can't find `100` valid tests +//! after trying `10,000` times, then it will give up. This parameter +//! may be changed using +//! [`quickcheck_config`](fn.quickcheck_config.html). +//! +//! +//! # Shrinking +//! +//! Shrinking is a crucial part of QuickCheck that simplifies counter-examples for +//! your properties automatically. For example, if you erroneously defined a +//! function for reversing vectors as: +//! +//! ```rust,should_fail +//! use std::iter; +//! use quickcheck::quickcheck; +//! +//! fn reverse(xs: &[T]) -> Vec { +//! let mut rev = vec!(); +//! for i in iter::range(1, xs.len()) { +//! rev.unshift(xs[i].clone()) +//! } +//! rev +//! } +//! +//! /// A property that tests that reversing twice is the same as the +//! /// original vector +//! fn prop(xs: Vec) -> bool { +//! xs == reverse(reverse(xs.as_slice()).as_slice()) +//! } +//! quickcheck(prop); +//! ``` +//! +//! Then without shrinking, you might get a counter-example like: +//! +//! ```notrust +//! [quickcheck] TEST FAILED. Arguments: ([-17, 13, -12, 17, -8, -10, 15, -19, +//! -19, -9, 11, -5, 1, 19, -16, 6]) +//! ``` +//! +//! Which is pretty mysterious. But with shrinking enabled, you're nearly +//! guaranteed to get this counter-example every time: +//! +//! ```notrust +//! [quickcheck] TEST FAILED. Arguments: ([0]) +//! ``` +//! +//! Which is going to be much easier to debug. +//! +//! +//! # Case study: The Sieve of Eratosthenes +//! +//! The [Sieve of Eratosthenes](http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) +//! is a simple and elegant way to find all primes less than or equal to `N`. +//! Briefly, the algorithm works by allocating an array with `N` slots containing +//! booleans. Slots marked with `false` correspond to prime numbers (or numbers +//! not known to be prime while building the sieve) and slots marked with `true` +//! are known to not be prime. For each `n`, all of its multiples in this array +//! are marked as true. When all `n` have been checked, the numbers marked `false` +//! are returned as the primes. +//! +//! As you might imagine, there's a lot of potential for off-by-one +//! errors, which makes it ideal for randomized testing. So let's take +//! a look at an implementation and see if we can spot the bug: +//! +//! ```rust,should_fail +//! use std::iter; +//! use quickcheck::quickcheck; +//! +//! fn sieve(n: uint) -> Vec { +//! if n <= 1 { +//! return vec!() +//! } +//! +//! let mut marked = Vec::from_fn(n+1, |_| false); +//! *marked.get_mut(0) = true; +//! *marked.get_mut(1) = true; +//! *marked.get_mut(2) = false; +//! for p in iter::range(2, n) { +//! for i in iter::range_step(2 * p, n, p) { // whoops! +//! *marked.get_mut(i) = true; +//! } +//! } +//! let mut primes = vec!(); +//! for (i, m) in marked.iter().enumerate() { +//! if !m { primes.push(i) } +//! } +//! primes +//! } +//! +//! /* +//! Let's try it on a few inputs by hand: +//! +//! sieve(3) => [2, 3] +//! sieve(5) => [2, 3, 5] +//! sieve(8) => [2, 3, 5, 7, 8] # !!! +//! +//! Something has gone wrong! But where? The bug is rather subtle, but it's an +//! easy one to make. It's OK if you can't spot it, because we're going to use +//! QuickCheck to help us track it down. +//! +//! Even before looking at some example outputs, it's good to try and come up with +//! some *properties* that are always satisfiable by the output of the function. An +//! obvious one for the prime number sieve is to check if all numbers returned are +//! prime. For that, we'll need an `is_prime` function: +//! */ +//! +//! /// Check if `n` is prime by trial division. +//! fn is_prime(n: uint) -> bool { +//! // some base cases: +//! if n == 0 || n == 1 { +//! return false +//! } else if n == 2 { +//! return true +//! } +//! +//! // run through the numbers in `[2, sqrt(n)]` (inclusive) to +//! // see if any divide `n`. +//! let max_possible = (n as f64).sqrt().ceil() as uint; +//! for i in iter::range_inclusive(2, max_possible) { +//! if n % i == 0 { +//! return false +//! } +//! } +//! return true +//! } +//! +//! /* +//! Now we can write our quickcheck property. +//! */ +//! +//! fn prop_all_prime(n: uint) -> bool { +//! let primes = sieve(n); +//! primes.iter().all(|&i| is_prime(i)) +//! } +//! +//! fn main() { +//! // invoke quickcheck +//! quickcheck(prop_all_prime); +//! } +//! ``` +//! +//! The output of running this program has this message: +//! +//! ```notrust +//! [quickcheck] TEST FAILED. Arguments: (4) +//! ``` +//! +//! Which says that `sieve` failed the `prop_all_prime` test when given `n = 4`. +//! Because of shrinking, it was able to find a (hopefully) minimal counter-example +//! for our property. +//! +//! With such a short counter-example, it's hopefully a bit easier to narrow down +//! where the bug is. Since `4` is returned, it's likely never marked as being not +//! prime. Since `4` is a multiple of `2`, its slot should be marked as `true` when +//! `p = 2` on these lines: +//! +//! ```rust +//! # use std::iter; +//! # let (p, n, mut marked) = (2u, 4, vec!(false, false, false, false)); +//! for i in iter::range_step(2 * p, n, p) { +//! *marked.get_mut(i) = true; +//! } +//! ``` +//! +//! Ah! But does the `range_step` function include `n`? Its documentation says +//! +//! > Return an iterator over the range [start, stop) by step. It handles overflow +//! > by stopping. +//! +//! Shucks. The `range_step` function will never yield `4` when `n = 4`. We could +//! use `n + 1`, but the `std::iter` crate also has a +//! [`range_step_inclusive`](../std/iter/fn.range_step_inclusive.html) +//! which seems clearer. +//! +//! Changing the call to `range_step_inclusive` results in `sieve` passing all +//! tests for the `prop_all_prime` property. +//! +//! In addition, if our bug happened to result in an index out-of-bounds error, +//! then `quickcheck` can handle it just like any other failure---including +//! shrinking on failures caused by runtime errors. +//! +//! +//! # What's not in this port of QuickCheck? +//! +//! The key features have been captured, but there are still things +//! missing: +//! +//! * As of now, only functions with 3 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. Therefore, such failures will not have a witness +//! attached to them. +//! * `Coarbitrary` does not exist in any form in this package. +//! +//! # Laziness +//! +//! A key aspect for writing good shrinkers is a good lazy +//! abstraction. For this, iterators were chosen as the +//! representation. The insistence on this point has resulted in the +//! use of an existential type. +//! +//! Note though that the shrinker for vectors is not yet lazy. Its +//! algorithm is relatively complex, so it will take a bit more work +//! to get it to use iterators like the rest of the shrinking +//! strategies. + +#![crate_id = "quickcheck#0.11-pre"] +#![license = "MIT/ASL2"] +#![crate_type = "rlib"] +#![crate_type = "dylib"] +#![experimental] +#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "http://www.rust-lang.org/favicon.ico", + html_root_url = "http://static.rust-lang.org/doc/master")] + +#![feature(phase)] + +extern crate collections; +#[phase(syntax, link)] extern crate log; +extern crate rand; + +// During tests, this links with the `quickcheck` crate so that the +// `#[quickcheck]` attribute can be tested. +#[cfg(test)] +extern crate quickcheck; + +pub use arbitrary::{Arbitrary, Gen, StdGen, Shrinker, EmptyShrinker, SingleShrinker}; +pub use tester::{Testable, TestResult, Config}; +pub use tester::{quickcheck, quickcheck_config, quicktest, quicktest_config}; +pub use tester::{DEFAULT_CONFIG, DEFAULT_SIZE}; + +mod arbitrary; + +mod tester { + use std::fmt::Show; + use std::iter; + use rand::task_rng; + use super::{Arbitrary, Gen, Shrinker, StdGen}; + use tester::trap::safe; + + /// Default size hint used in `quickcheck` for sampling from a random + /// distribution. + pub static DEFAULT_SIZE: uint = 100; + + /// Default configuration used in `quickcheck`. + pub static DEFAULT_CONFIG: Config = Config{ + tests: 100, + max_tests: 10000, + }; + + /// Does randomized testing on `f` and produces a possibly minimal + /// witness for test failures. + /// + /// This function is equivalent to calling `quickcheck_config` with + /// `DEFAULT_CONFIG` and a `Gen` with size `DEFAULT_SIZE`. + /// + /// As of now, it is intended for `quickcheck` to be used inside Rust's + /// unit testing system. For example, to check if + /// `reverse(reverse(xs)) == xs`, you could use: + /// + /// ```rust + /// use quickcheck::quickcheck; + /// + /// fn prop_reverse_reverse() { + /// fn revrev(xs: Vec) -> bool { + /// let mut revrev = xs.clone(); + /// revrev.reverse(); + /// revrev.reverse(); + /// + /// xs == revrev + /// } + /// quickcheck(revrev); + /// } + /// # fn main() { prop_reverse_reverse() } + /// ``` + /// + /// In particular, `quickcheck` will call `fail!` if it finds a + /// test failure. The failure message will include a witness to the + /// failure. + pub fn quickcheck(f: A) { + let g = &mut StdGen::new(task_rng(), DEFAULT_SIZE); + quickcheck_config(DEFAULT_CONFIG, g, f) + } + + /// Does randomized testing on `f` with the given config and produces a + /// possibly minimal witness for test failures. + pub fn quickcheck_config(c: Config, g: &mut G, f: A) { + match quicktest_config(c, g, f) { + Ok(ntests) => debug!("[quickcheck] Passed {:u} tests.", ntests), + Err(r) => fail!(r.failed_msg()), + } + } + + /// Like `quickcheck`, but returns either the number of tests passed + /// or a witness of failure. + pub fn quicktest(f: A) -> Result { + let g = &mut StdGen::new(task_rng(), DEFAULT_SIZE); + quicktest_config(DEFAULT_CONFIG, g, f) + } + + /// Like `quickcheck_config`, but returns either the number of tests passed + /// or a witness of failure. + pub fn quicktest_config + (c: Config, g: &mut G, f: A) + -> Result { + let mut ntests: uint = 0; + for _ in iter::range(0, c.max_tests) { + if ntests >= c.tests { + break + } + let r = f.result(g); + match r.status { + Pass => ntests = ntests + 1, + Discard => continue, + Fail => { + return Err(r) + } + } + } + Ok(ntests) + } + + /// Config contains various parameters for controlling automated testing. + /// + /// Note that the distribution of random values is controlled by the + /// generator passed to `quickcheck_config`. + pub struct Config { + /// The number of tests to run on a function where the result is + /// either a pass or a failure. (i.e., This doesn't include discarded + /// test results.) + pub tests: uint, + + /// The maximum number of tests to run for each function including + /// discarded test results. + pub max_tests: uint, + } + + /// Describes the status of a single instance of a test. + /// + /// All testable things must be capable of producing a `TestResult`. + #[deriving(Clone, Show)] + pub struct TestResult { + status: Status, + arguments: Vec, + err: StrBuf, + } + + /// Whether a test has passed, failed or been discarded. + #[deriving(Clone, Show)] + enum Status { Pass, Fail, Discard } + + impl TestResult { + /// Produces a test result that indicates the current test has passed. + pub fn passed() -> TestResult { TestResult::from_bool(true) } + + /// Produces a test result that indicates the current test has failed. + pub fn failed() -> TestResult { TestResult::from_bool(false) } + + /// Produces a test result that indicates failure from a runtime + /// error. + pub fn error(msg: &str) -> TestResult { + let mut r = TestResult::from_bool(false); + r.err = msg.to_strbuf(); + r + } + + /// Produces a test result that instructs `quickcheck` to ignore it. + /// This is useful for restricting the domain of your properties. + /// 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: "".to_strbuf(), } + } + + /// Converts a `bool` to a `TestResult`. A `true` value indicates that + /// the test has passed and a `false` value indicates that the test + /// has failed. + pub fn from_bool(b: bool) -> TestResult { + TestResult { + status: if b { Pass } else { Fail }, + arguments: vec!(), + err: "".to_strbuf(), + } + } + + /// Returns `true` if and only if this test result describes a failing + /// test. + pub fn is_failure(&self) -> bool { + match self.status { + Fail => true, + Pass|Discard => false, + } + } + + /// Returns `true` if and only if this test result describes a failing + /// test as a result of a run time error. + pub fn is_error(&self) -> bool { + return self.is_failure() && self.err.len() > 0 + } + + fn failed_msg(&self) -> ~str { + if self.err.len() == 0 { + return format!( + "[quickcheck] TEST FAILED. Arguments: ({})", + self.arguments.connect(", ")) + } else { + return format!( + "[quickcheck] TEST FAILED (runtime error). \ + Arguments: ({})\nError: {}", + self.arguments.connect(", "), self.err) + } + } + } + + /// `Testable` describes types (e.g., a function) whose values can be + /// tested. + /// + /// Anything that can be tested must be capable of producing a `TestResult` + /// given a random number generator. This is trivial for types like `bool`, + /// which are just converted to either a passing or failing test result. + /// + /// For functions, an implementation must generate random arguments + /// and potentially shrink those arguments if they produce a failure. + /// + /// It's unlikely that you'll have to implement this trait yourself. + /// This comes with a caveat: currently, only functions with 3 parameters + /// or fewer (both `fn` and `||` types) satisfy `Testable`. + pub trait Testable : Send { + fn result(&self, &mut G) -> TestResult; + } + + impl Testable for bool { + fn result(&self, _: &mut G) -> TestResult { + TestResult::from_bool(*self) + } + } + + impl Testable for TestResult { + fn result(&self, _: &mut G) -> TestResult { self.clone() } + } + + impl Testable for Result { + fn result(&self, g: &mut G) -> TestResult { + match *self { + Ok(ref r) => r.result(g), + Err(ref err) => TestResult::error(*err), + } + } + } + + impl Testable for fn() -> T { + fn result(&self, g: &mut G) -> TestResult { + shrink:: T>(g, self) + } + } + + impl Testable for fn(A) -> T { + fn result(&self, g: &mut G) -> TestResult { + shrink:: T>(g, self) + } + } + + impl Testable for fn(A, B) -> T { + fn result(&self, g: &mut G) -> TestResult { + shrink:: T>(g, self) + } + } + + impl + Testable for fn(A, B, C) -> T { + fn result(&self, g: &mut G) -> TestResult { + shrink:: T>(g, self) + } + } + + trait Fun { + fn call(&self, g: &mut G, + a: Option<&A>, b: Option<&B>, c: Option<&C>) + -> TestResult; + } + + impl Fun for fn() -> T { + fn call(&self, g: &mut G, + _: Option<&A>, _: Option<&B>, _: Option<&C>) + -> TestResult { + let f = *self; + safe(proc() { f() }).result(g) + } + } + + impl Fun for fn(A) -> T { + fn call(&self, g: &mut G, + a: Option<&A>, _: Option<&B>, _: Option<&C>) + -> TestResult { + let a = a.unwrap(); + let oa = box a.clone(); + let f = *self; + let mut r = safe(proc() { f(*oa) }).result(g); + if r.is_failure() { + r.arguments = vec!(a.to_str().to_strbuf()); + } + r + } + } + + impl Fun for fn(A, B) -> T { + fn call(&self, g: &mut G, + a: Option<&A>, b: Option<&B>, _: Option<&C>) + -> TestResult { + let (a, b) = (a.unwrap(), b.unwrap()); + let (oa, ob) = (box a.clone(), box b.clone()); + let f = *self; + let mut r = safe(proc() { f(*oa, *ob) }).result(g); + if r.is_failure() { + r.arguments = vec!(a.to_str().to_strbuf(), b.to_str().to_strbuf()); + } + r + } + } + + impl Fun for fn(A, B, C) -> T { + fn call(&self, g: &mut G, + a: Option<&A>, b: Option<&B>, c: Option<&C>) + -> TestResult { + let (a, b, c) = (a.unwrap(), b.unwrap(), c.unwrap()); + let (oa, ob, oc) = (box a.clone(), box b.clone(), box c.clone()); + let f = *self; + let mut r = safe(proc() { f(*oa, *ob, *oc) }).result(g); + if r.is_failure() { + r.arguments = vec!(a.to_str().to_strbuf(), + b.to_str().to_strbuf(), + c.to_str().to_strbuf()); + } + r + } + } + + fn shrink> + (g: &mut G, fun: &F) -> TestResult { + let (a, b, c): (A, B, C) = arby(g); + let r = fun.call(g, Some(&a), Some(&b), Some(&c)); + match r.status { + Pass|Discard => r, + Fail => shrink_failure(g, (a, b, c).shrink(), fun).unwrap_or(r), + } + } + + fn shrink_failure> + (g: &mut G, mut shrinker: Box>, fun: &F) + -> Option { + for (a, b, c) in shrinker { + let r = fun.call(g, Some(&a), Some(&b), Some(&c)); + match r.status { + // The shrunk value does not witness a failure, so + // throw it away. + Pass|Discard => continue, + + // The shrunk value *does* witness a failure, so keep trying + // to shrink it. + Fail => { + let shrunk = shrink_failure(g, (a, b, c).shrink(), fun); + + // If we couldn't witness a failure on any shrunk value, + // then return the failure we already have. + return Some(shrunk.unwrap_or(r)) + }, + } + } + None + } + + #[cfg(quickfail)] + mod trap { + pub fn safe(fun: proc() -> T) -> Result { + Ok(fun()) + } + } + + #[cfg(not(quickfail))] + mod trap { + use std::comm::channel; + use std::io::{ChanReader, ChanWriter}; + use std::task::TaskBuilder; + + // This is my bright idea for capturing runtime errors caused by a + // test. Actually, it looks like rustc uses a similar approach. + // The problem is, this is used for *each* test case passed to a + // property, whereas rustc does it once for each test. + // + // I'm not entirely sure there's much of an alternative either. + // We could launch a single task and pass arguments over a channel, + // but the task would need to be restarted if it failed due to a + // runtime error. Since these are rare, it'd probably be more efficient + // then this approach, but it would also be more complex. + // + // Moreover, this feature seems to prevent an implementation of + // Testable for a stack closure type. *sigh* + pub fn safe(fun: proc():Send -> T) -> Result { + let (send, recv) = channel(); + let stdout = ChanWriter::new(send.clone()); + let stderr = ChanWriter::new(send); + let mut reader = ChanReader::new(recv); + + let mut t = TaskBuilder::new(); + t.opts.name = Some(("safefn".to_owned()).into_maybe_owned()); + t.opts.stdout = Some(box stdout as Box); + t.opts.stderr = Some(box stderr as Box); + + match t.try(fun) { + Ok(v) => Ok(v), + Err(_) => { + let s = reader.read_to_str().unwrap(); + Err(s.trim().into_owned()) + } + } + } + } + + /// Convenient aliases. + trait AShow : Arbitrary + Show {} + impl AShow for A {} + fn arby(g: &mut G) -> A { Arbitrary::arbitrary(g) } +} + +#[cfg(test)] +mod test { + use std::cmp::TotalOrd; + use std::iter; + use rand::task_rng; + use super::{Config, Testable, TestResult, StdGen}; + use super::{quickcheck_config, quicktest_config}; + + static SIZE: uint = 100; + static CONFIG: Config = Config { + tests: 100, + max_tests: 10000, + }; + + fn qcheck(f: A) { + quickcheck_config(CONFIG, &mut StdGen::new(task_rng(), SIZE), f) + } + + fn qtest(f: A) -> Result { + quicktest_config(CONFIG, &mut StdGen::new(task_rng(), SIZE), f) + } + + #[test] + fn prop_oob() { + fn prop() -> bool { + let zero: Vec = vec!(); + *zero.get(0) + } + match qtest(prop) { + Ok(n) => fail!("prop_oob should fail with a runtime error \ + but instead it passed {} tests.", n), + _ => return, + } + } + + #[test] + fn prop_reverse_reverse() { + fn prop(xs: Vec) -> bool { + let rev: Vec = xs.clone().move_iter().rev().collect(); + let revrev = rev.move_iter().rev().collect(); + xs == revrev + } + qcheck(prop); + } + + #[test] + fn reverse_single() { + fn prop(xs: Vec) -> TestResult { + if xs.len() != 1 { + return TestResult::discard() + } + return TestResult::from_bool( + xs == xs.clone().move_iter().rev().collect() + ) + } + qcheck(prop); + } + + #[test] + fn reverse_app() { + fn prop(xs: Vec, ys: Vec) -> bool { + let app = xs.clone().append(ys.as_slice()); + let app_rev: Vec = app.move_iter().rev().collect(); + + let rxs = xs.move_iter().rev().collect(); + let mut rev_app = ys.move_iter().rev().collect::>(); + rev_app.push_all_move(rxs); + + app_rev == rev_app + } + qcheck(prop); + } + + #[test] + fn max() { + fn prop(x: int, y: int) -> TestResult { + if x > y { + return TestResult::discard() + } else { + return TestResult::from_bool(::std::cmp::max(x, y) == y) + } + } + qcheck(prop); + } + + #[test] + fn sort() { + fn prop(mut xs: Vec) -> bool { + xs.sort_by(|x, y| x.cmp(y)); + let upto = if xs.len() == 0 { 0 } else { xs.len()-1 }; + for i in iter::range(0, upto) { + if xs.get(i) > xs.get(i+1) { + return false + } + } + true + } + qcheck(prop); + } + + #[test] + #[should_fail] + fn sieve_of_eratosthenes() { + fn sieve(n: uint) -> Vec { + if n <= 1 { + return vec!() + } + + let mut marked = Vec::from_fn(n+1, |_| false); + *marked.get_mut(0) = true; + *marked.get_mut(1) = true; + *marked.get_mut(2) = false; + for p in iter::range(2, n) { + for i in iter::range_step(2 * p, n, p) { // whoops! + *marked.get_mut(i) = true; + } + } + let mut primes = vec!(); + for (i, m) in marked.iter().enumerate() { + if !m { primes.push(i) } + } + primes + } + + fn prop(n: uint) -> bool { + let primes = sieve(n); + primes.iter().all(|&i| is_prime(i)) + } + fn is_prime(n: uint) -> bool { + if n == 0 || n == 1 { + return false + } else if n == 2 { + return true + } + + let max_possible = (n as f64).sqrt().ceil() as uint; + for i in iter::range_inclusive(2, max_possible) { + if n % i == 0 { + return false + } + } + return true + } + qcheck(prop); + } + + #[cfg(not(stage1))] + mod attrib { + #[phase(syntax)] + extern crate quickcheck_macros; + + use quickcheck::TestResult; + + #[quickcheck] + fn min(x: int, y: int) -> TestResult { + if x < y { + return TestResult::discard() + } else { + return TestResult::from_bool(::std::cmp::min(x, y) == y) + } + } + + #[quickcheck] + #[should_fail] + fn fail_fn() -> bool { false } + + #[quickcheck] + static static_bool: bool = true; + + #[quickcheck] + #[should_fail] + static fail_static_bool: bool = false; + + // If static_bool wasn't turned into a test function, then this should + // result in a compiler error. + #[test] + fn static_bool_test_is_function() { + static_bool() + } + } +} diff --git a/src/libquickcheck_macros/lib.rs b/src/libquickcheck_macros/lib.rs new file mode 100644 index 0000000000000..62f8abd57f46a --- /dev/null +++ b/src/libquickcheck_macros/lib.rs @@ -0,0 +1,89 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This crate provides the `#[quickcheck]` attribute. Its use is +//! documented in the `quickcheck` crate. + +#![crate_id = "quickcheck_macros#0.11-pre"] +#![crate_type = "dylib"] +#![experimental] +#![license = "MIT/ASL2"] +#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "http://www.rust-lang.org/favicon.ico", + html_root_url = "http://static.rust-lang.org/doc/master")] + +#![feature(macro_registrar, managed_boxes)] + +extern crate syntax; + +use syntax::ast; +use syntax::codemap; +use syntax::parse::token; +use syntax::ext::base::{ SyntaxExtension, ItemModifier, ExtCtxt }; +use syntax::ext::build::AstBuilder; + +/// For the `#[quickcheck]` attribute. Do not use. +#[macro_registrar] +#[doc(hidden)] +pub fn macro_registrar(register: |ast::Name, SyntaxExtension|) { + register(token::intern("quickcheck"), ItemModifier(expand_meta_quickcheck)); +} + +/// Expands the `#[quickcheck]` attribute. +/// +/// Expands: +/// ``` +/// #[quickcheck] +/// fn check_something(_: uint) -> bool { +/// true +/// } +/// ``` +/// to: +/// ``` +/// #[test] +/// fn check_something() { +/// fn check_something(_: uint) -> bool { +/// true +/// } +/// ::quickcheck::quickcheck(check_something) +/// } +/// ``` +fn expand_meta_quickcheck(cx: &mut ExtCtxt, + span: codemap::Span, + _: @ast::MetaItem, + item: @ast::Item) -> @ast::Item { + match item.node { + ast::ItemFn(..) | ast::ItemStatic(..) => { + // Copy original function without attributes + let prop = @ast::Item {attrs: Vec::new(), ..(*item).clone()}; + // ::quickcheck::quickcheck + let check_ident = token::str_to_ident("quickcheck"); + let check_path = vec!(check_ident, check_ident); + // Wrap original function in new outer function, calling ::quickcheck::quickcheck() + let fn_decl = @codemap::respan(span, ast::DeclItem(prop)); + let inner_fn = @codemap::respan(span, ast::StmtDecl(fn_decl, ast::DUMMY_NODE_ID)); + let inner_ident = cx.expr_ident(span, prop.ident); + let check_call = cx.expr_call_global(span, check_path, vec![inner_ident]); + let body = cx.block(span, vec![inner_fn], Some(check_call)); + let test = cx.item_fn(span, item.ident, Vec::new(), cx.ty_nil(), body); + + // Copy attributes from original function + let mut attrs = item.attrs.clone(); + // Add #[test] attribute + attrs.push(cx.attribute(span, cx.meta_word(span, token::intern_and_get_ident("test")))); + // Attach the attributes to the outer function + @ast::Item {attrs: attrs, ..(*test).clone()} + }, + _ => { + cx.span_err(span, "#[quickcheck] only supported on statics and functions"); + item + } + } +} diff --git a/src/test/compile-fail-fulldeps/syntax-extension-quickcheck-bad-type.rs b/src/test/compile-fail-fulldeps/syntax-extension-quickcheck-bad-type.rs new file mode 100644 index 0000000000000..547b509911621 --- /dev/null +++ b/src/test/compile-fail-fulldeps/syntax-extension-quickcheck-bad-type.rs @@ -0,0 +1,21 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-stage1 +// compile-flags: --test + +#![feature(phase)] + +extern crate quickcheck; +#[phase(syntax)] extern crate quickcheck_macros; + +#[quickcheck] +//~^ ERROR failed to find an implementation of trait quickcheck::tester::Testable for uint +fn a() -> uint { 0 } diff --git a/src/test/compile-fail-fulldeps/syntax-extension-quickcheck-unsupported-items.rs b/src/test/compile-fail-fulldeps/syntax-extension-quickcheck-unsupported-items.rs new file mode 100644 index 0000000000000..b6a26aa9a878c --- /dev/null +++ b/src/test/compile-fail-fulldeps/syntax-extension-quickcheck-unsupported-items.rs @@ -0,0 +1,63 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-stage1 + +#![feature(phase)] + +extern crate quickcheck; +#[phase(syntax)] extern crate quickcheck_macros; + +// Tests to make sure that `#[quickcheck]` will produce a compile error when +// applied to anything other than a function or static item. + +#[quickcheck] //~ ERROR #[quickcheck] only supported on statics and functions +pub mod foo { + pub fn bar() {} +} + +#[quickcheck] //~ ERROR #[quickcheck] only supported on statics and functions +extern "C" { + pub fn baz(name: *u8) -> u8; +} + +#[quickcheck] //~ ERROR #[quickcheck] only supported on statics and functions +type Blah = [uint, ..8]; + +#[quickcheck] //~ ERROR #[quickcheck] only supported on statics and functions +enum Maybe { + Just(T), + Nothing +} + +#[quickcheck] //~ ERROR #[quickcheck] only supported on statics and functions +pub struct Stuff { + a: Maybe<&'static int>, + b: Option +} + +#[quickcheck] //~ ERROR #[quickcheck] only supported on statics and functions +pub trait Thing { + fn do_it(&self) -> T; +} + +#[quickcheck] //~ ERROR #[quickcheck] only supported on statics and functions +impl Thing for Stuff { + fn do_it(&self) -> u32 { + return self.b.unwrap_or(42); + } +} + +static CONSTANT: int = 2; + +fn main () { + let s = Stuff {a: Just(&CONSTANT), b: None}; + println!("{}", s.do_it()); +}