blob: 72e5413147b4446628e83503999bcfcc116f9b09 [file] [log] [blame] [edit]
use std::fmt::Debug;
use std::panic;
use std::env;
use std::cmp;
use rand;
use tester::Status::{Discard, Fail, Pass};
use {Arbitrary, Gen, StdGen};
/// The main QuickCheck type for setting configuration and running QuickCheck.
pub struct QuickCheck<G> {
tests: usize,
max_tests: usize,
gen: G,
}
fn qc_tests() -> usize {
let default = 100;
match env::var("QUICKCHECK_TESTS") {
Ok(val) => val.parse().unwrap_or(default),
Err(_) => default,
}
}
fn qc_max_tests() -> usize {
let default = 10_000;
match env::var("QUICKCHECK_MAX_TESTS") {
Ok(val) => val.parse().unwrap_or(default),
Err(_) => default,
}
}
fn qc_gen_size() -> usize {
let default = 100;
match env::var("QUICKCHECK_GENERATOR_SIZE") {
Ok(val) => val.parse().unwrap_or(default),
Err(_) => default,
}
}
impl QuickCheck<StdGen<rand::ThreadRng>> {
/// 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.
///
/// 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 `StdGen` with a default size of `100`.
pub fn new() -> QuickCheck<StdGen<rand::ThreadRng>> {
let tests = qc_tests();
let max_tests = cmp::max(tests, qc_max_tests());
let gen_size = qc_gen_size();
QuickCheck {
tests: tests,
max_tests: max_tests,
gen: StdGen::new(rand::thread_rng(), gen_size),
}
}
}
impl<G: Gen> QuickCheck<G> {
/// 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: usize) -> QuickCheck<G> {
self.tests = tests;
self
}
/// 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: usize) -> QuickCheck<G> {
self.max_tests = max_tests;
self
}
/// Set the random number generator to be used by QuickCheck.
pub fn gen(mut self, gen: G) -> QuickCheck<G> {
self.gen = gen;
self
}
/// Tests a property and returns the result.
///
/// The result returned is either the number of tests passed or a witness
/// of failure.
///
/// (If you're using Rust's unit testing infrastructure, then you'll
/// want to use the `quickcheck` method, which will `panic!` on failure.)
pub fn quicktest<A>(&mut self, f: A) -> Result<usize, TestResult>
where A: Testable {
let mut ntests: usize = 0;
for _ in 0..self.max_tests {
if ntests >= self.tests {
break
}
match f.result(&mut self.gen) {
TestResult { status: Pass, .. } => ntests += 1,
TestResult { status: Discard, .. } => continue,
r @ TestResult { status: Fail, .. } => return Err(r),
}
}
Ok(ntests)
}
/// Tests a property and calls `panic!` on failure.
///
/// The `panic!` message will include a (hopefully) minimal witness of
/// failure.
///
/// It is appropriate to use this method with Rust's unit testing
/// infrastructure.
///
/// Note that if the environment variable `RUST_LOG` is set to enable
/// `info` level log messages for the `quickcheck` crate, then this will
/// include output on how many QuickCheck tests were passed.
///
/// # Example
///
/// ```rust
/// use quickcheck::QuickCheck;
///
/// fn prop_reverse_reverse() {
/// fn revrev(xs: Vec<usize>) -> bool {
/// let rev: Vec<_> = xs.clone().into_iter().rev().collect();
/// let revrev: Vec<_> = rev.into_iter().rev().collect();
/// xs == revrev
/// }
/// QuickCheck::new().quickcheck(revrev as fn(Vec<usize>) -> bool);
/// }
/// ```
pub fn quickcheck<A>(&mut self, f: A) where A: Testable {
// Ignore log init failures, implying it has already been done.
let _ = ::env_logger_init();
match self.quicktest(f) {
Ok(_ntests) => info!("(Passed {} QuickCheck tests.)", _ntests),
Err(result) => panic!(result.failed_msg()),
}
}
}
/// Convenience function for running QuickCheck.
///
/// This is an alias for `QuickCheck::new().quickcheck(f)`.
pub fn quickcheck<A: Testable>(f: A) { QuickCheck::new().quickcheck(f) }
/// Describes the status of a single instance of a test.
///
/// All testable things must be capable of producing a `TestResult`.
#[derive(Clone, Debug)]
pub struct TestResult {
status: Status,
arguments: Vec<String>,
err: Option<String>,
}
/// Whether a test has passed, failed or been discarded.
#[derive(Clone, Debug)]
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<S: Into<String>>(msg: S) -> TestResult {
let mut r = TestResult::from_bool(false);
r.err = Some(msg.into());
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: None,
}
}
/// 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: None,
}
}
/// Tests if a "procedure" fails when executed. The test passes only if
/// `f` generates a task failure during its execution.
pub fn must_fail<T, F>(f: F) -> TestResult
where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static {
let f = panic::AssertUnwindSafe(f);
TestResult::from_bool(panic::catch_unwind(f).is_err())
}
/// 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 {
self.is_failure() && self.err.is_some()
}
fn failed_msg(&self) -> String {
match self.err {
None => {
format!("[quickcheck] TEST FAILED. Arguments: ({})",
self.arguments.connect(", "))
}
Some(ref err) => {
format!("[quickcheck] TEST FAILED (runtime error). \
Arguments: ({})\nError: {}",
self.arguments.connect(", "), 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.
pub trait Testable : Send + 'static {
fn result<G: Gen>(&self, &mut G) -> TestResult;
}
impl Testable for bool {
fn result<G: Gen>(&self, _: &mut G) -> TestResult {
TestResult::from_bool(*self)
}
}
impl Testable for () {
fn result<G: Gen>(&self, _: &mut G) -> TestResult {
TestResult::passed()
}
}
impl Testable for TestResult {
fn result<G: Gen>(&self, _: &mut G) -> TestResult { self.clone() }
}
impl<A, E> Testable for Result<A, E>
where A: Testable, E: Debug + Send + 'static {
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
match *self {
Ok(ref r) => r.result(g),
Err(ref err) => TestResult::error(format!("{:?}", err)),
}
}
}
/// Return a vector of the debug formatting of each item in `args`
fn debug_reprs(args: &[&Debug]) -> Vec<String> {
args.iter().map(|x| format!("{:?}", x)).collect()
}
macro_rules! testable_fn {
($($name: ident),*) => {
impl<T: Testable,
$($name: Arbitrary + Debug),*> Testable for fn($($name),*) -> T {
#[allow(non_snake_case)]
fn result<G_: Gen>(&self, g: &mut G_) -> TestResult {
fn shrink_failure<T: Testable, G_: Gen, $($name: Arbitrary + Debug),*>(
g: &mut G_,
self_: fn($($name),*) -> T,
a: ($($name,)*),
) -> Option<TestResult> {
for t in a.shrink() {
let ($($name,)*) = t.clone();
let mut r_new = safe(move || {self_($($name),*)}).result(g);
if r_new.is_failure() {
{
let ($(ref $name,)*) : ($($name,)*) = t;
r_new.arguments = debug_reprs(&[$($name),*]);
}
// The shrunk value *does* witness a failure, so keep
// trying to shrink it.
let shrunk = shrink_failure(g, self_, t);
// If we couldn't witness a failure on any shrunk value,
// then return the failure we already have.
return Some(shrunk.unwrap_or(r_new))
}
}
None
}
let self_ = *self;
let a: ($($name,)*) = Arbitrary::arbitrary(g);
let ( $($name,)* ) = a.clone();
let mut r = safe(move || {self_($($name),*)}).result(g);
{
let ( $(ref $name,)* ) = a;
r.arguments = debug_reprs(&[$($name),*]);
}
match r.status {
Pass|Discard => r,
Fail => {
shrink_failure(g, self_, a).unwrap_or(r)
}
}
}
}}}
testable_fn!();
testable_fn!(A);
testable_fn!(A, B);
testable_fn!(A, B, C);
testable_fn!(A, B, C, D);
testable_fn!(A, B, C, D, E);
testable_fn!(A, B, C, D, E, F);
testable_fn!(A, B, C, D, E, F, G);
testable_fn!(A, B, C, D, E, F, G, H);
fn safe<T, F>(fun: F) -> Result<T, String>
where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static {
panic::catch_unwind(panic::AssertUnwindSafe(fun)).map_err(|any_err| {
// Extract common types of panic payload:
// panic and assert produce &str or String
if let Some(&s) = any_err.downcast_ref::<&str>() {
s.to_owned()
} else if let Some(s) = any_err.downcast_ref::<String>() {
s.to_owned()
} else {
"UNABLE TO SHOW RESULT OF PANIC.".to_owned()
}
})
}
/// Convenient aliases.
trait AShow : Arbitrary + Debug {}
impl<A: Arbitrary + Debug> AShow for A {}
#[cfg(test)]
mod test {
use QuickCheck;
#[test]
fn shrinking_regression_issue_126() {
fn thetest(vals: Vec<bool>) -> bool {
vals.iter().filter(|&v| *v).count() < 2
}
let failing_case =
QuickCheck::new()
.quicktest(thetest as fn(vals: Vec<bool>) -> bool)
.unwrap_err();
let expected_argument = format!("{:?}", [true, true]);
assert_eq!(failing_case.arguments, vec![expected_argument]);
}
}