use rand;
use std::fmt::Debug;
use std::thread;
use super::{Arbitrary, Gen, StdGen};
use tester::trap::safe;
use tester::Status::{Discard, Fail, Pass};
pub struct QuickCheck<G> {
tests: usize,
max_tests: usize,
gen: G,
}
impl QuickCheck<StdGen<rand::ThreadRng>> {
pub fn new() -> QuickCheck<StdGen<rand::ThreadRng>> {
QuickCheck {
tests: 100,
max_tests: 10000,
gen: StdGen::new(rand::thread_rng(), 100),
}
}
}
impl<G: Gen> QuickCheck<G> {
pub fn tests(mut self, tests: usize) -> QuickCheck<G> {
self.tests = tests;
self
}
pub fn max_tests(mut self, max_tests: usize) -> QuickCheck<G> {
self.max_tests = max_tests;
self
}
pub fn gen(mut self, gen: G) -> QuickCheck<G> {
self.gen = gen;
self
}
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
}
let r = f.result(&mut self.gen);
match r.status {
Pass => ntests += 1,
Discard => continue,
Fail => return Err(r),
}
}
Ok(ntests)
}
pub fn quickcheck<A>(&mut self, f: A) where A: Testable {
match self.quicktest(f) {
Ok(ntests) => info!("(Passed {} QuickCheck tests.)", ntests),
Err(result) => panic!(result.failed_msg()),
}
}
}
pub fn quickcheck<A: Testable>(f: A) { QuickCheck::new().quickcheck(f) }
#[derive(Clone, Debug)]
pub struct TestResult {
status: Status,
arguments: Vec<String>,
err: String,
}
#[derive(Clone, Debug)]
enum Status { Pass, Fail, Discard }
impl TestResult {
pub fn passed() -> TestResult { TestResult::from_bool(true) }
pub fn failed() -> TestResult { TestResult::from_bool(false) }
pub fn error(msg: &str) -> TestResult {
let mut r = TestResult::from_bool(false);
r.err = msg.to_string();
r
}
pub fn discard() -> TestResult {
TestResult {
status: Discard,
arguments: vec![],
err: "".to_string(),
}
}
pub fn from_bool(b: bool) -> TestResult {
TestResult {
status: if b { Pass } else { Fail },
arguments: vec![],
err: "".to_string(),
}
}
pub fn must_fail<T, F>(f: F) -> TestResult
where T: Send, F: FnOnce() -> T + Send + 'static {
TestResult::from_bool(
thread::Builder::new()
.spawn(move || { let _ = f(); })
.unwrap()
.join()
.is_err())
}
pub fn is_failure(&self) -> bool {
match self.status {
Fail => true,
Pass|Discard => false,
}
}
pub fn is_error(&self) -> bool {
self.is_failure() && self.err.len() > 0
}
fn failed_msg(&self) -> String {
if self.err.len() == 0 {
format!(
"[quickcheck] TEST FAILED. Arguments: ({})",
self.arguments.connect(", "))
} else {
format!(
"[quickcheck] TEST FAILED (runtime error). \
Arguments: ({})\nError: {}",
self.arguments.connect(", "), self.err)
}
}
}
pub trait Testable : Send {
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 TestResult {
fn result<G: Gen>(&self, _: &mut G) -> TestResult { self.clone() }
}
impl<A> Testable for Result<A, String> where A: Testable {
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
match *self {
Ok(ref r) => r.result(g),
Err(ref err) => TestResult::error(&**err),
}
}
}
impl<T> Testable for fn() -> T where T: Testable + 'static {
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
shrink::<G, T, (), (), (), (), fn() -> T>(g, self)
}
}
impl<A, T> Testable for fn(A) -> T where A: AShow, T: Testable + 'static {
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
shrink::<G, T, A, (), (), (), fn(A) -> T>(g, self)
}
}
impl<A, B, T> Testable for fn(A, B) -> T
where A: AShow, B: AShow, T: Testable + 'static {
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
shrink::<G, T, A, B, (), (), fn(A, B) -> T>(g, self)
}
}
impl<A, B, C, T> Testable for fn(A, B, C) -> T
where A: AShow, B: AShow, C: AShow, T: Testable + 'static {
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
shrink::<G, T, A, B, C, (), fn(A, B, C) -> T>(g, self)
}
}
impl<A, B, C, D, T,> Testable for fn(A, B, C, D) -> T
where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable + 'static {
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
shrink::<G, T, A, B, C, D, fn(A, B, C, D) -> T>(g, self)
}
}
trait Fun<A, B, C, D, T> {
fn call<G>(&self, g: &mut G,
a: Option<&A>, b: Option<&B>,
c: Option<&C>, d: Option<&D>)
-> TestResult
where G: Gen;
}
macro_rules! impl_fun_call {
($f:expr, $g:expr, $($name:ident,)+) => ({
let ($($name,)*) = ($($name.unwrap(),)*);
let f = $f;
let mut r = {
let ($($name,)*) = ($(Box::new($name.clone()),)*);
safe(move || { f($(*$name,)*) }).result($g)
};
if r.is_failure() {
r.arguments = vec![$(format!("{:?}", $name),)*];
}
r
});
}
impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn() -> T
where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable + 'static {
fn call<G>(&self, g: &mut G,
_: Option<&A>, _: Option<&B>,
_: Option<&C>, _: Option<&D>)
-> TestResult where G: Gen {
let f = *self;
safe(move || { f() }).result(g)
}
}
impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A) -> T
where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable + 'static {
fn call<G>(&self, g: &mut G,
a: Option<&A>, _: Option<&B>,
_: Option<&C>, _: Option<&D>)
-> TestResult where G: Gen {
impl_fun_call!(*self, g, a,)
}
}
impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A, B) -> T
where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable + 'static {
fn call<G>(&self, g: &mut G,
a: Option<&A>, b: Option<&B>,
_: Option<&C>, _: Option<&D>)
-> TestResult where G: Gen {
impl_fun_call!(*self, g, a, b,)
}
}
impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A, B, C) -> T
where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable + 'static {
fn call<G>(&self, g: &mut G,
a: Option<&A>, b: Option<&B>,
c: Option<&C>, _: Option<&D>)
-> TestResult where G: Gen {
impl_fun_call!(*self, g, a, b, c,)
}
}
impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A, B, C, D) -> T
where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable + 'static {
fn call<G>(&self, g: &mut G,
a: Option<&A>, b: Option<&B>,
c: Option<&C>, d: Option<&D>)
-> TestResult where G: Gen {
impl_fun_call!(*self, g, a, b, c, d,)
}
}
fn shrink<G, T, A, B, C, D, F>(g: &mut G, fun: &F) -> TestResult
where G: Gen, T: Testable, A: AShow, B: AShow, C: AShow, D: AShow,
F: Fun<A, B, C, D, T> {
let (a, b, c, d): (A, B, C, D) = arby(g);
let r = fun.call(g, Some(&a), Some(&b), Some(&c), Some(&d));
match r.status {
Pass|Discard => r,
Fail => shrink_failure(g, (a, b, c, d).shrink(), fun).unwrap_or(r),
}
}
fn shrink_failure<G, T, A, B, C, D, F>
(g: &mut G,
shrinker: Box<Iterator<Item=(A, B, C, D)>+'static>,
fun: &F)
-> Option<TestResult>
where G: Gen, T: Testable, A: AShow, B: AShow, C: AShow, D: AShow,
F: Fun<A, B, C, D, T> {
for (a, b, c, d) in shrinker {
let r = fun.call(g, Some(&a), Some(&b), Some(&c), Some(&d));
match r.status {
Pass|Discard => continue,
Fail => {
let shrunk = shrink_failure(g, (a, b, c, d).shrink(), fun);
return Some(shrunk.unwrap_or(r))
},
}
}
None
}
#[cfg(quickfail)]
mod trap {
pub fn safe<T: Send, F: FnOnce() -> T>(fun: F) -> Result<T, String> {
Ok(fun())
}
}
#[cfg(not(quickfail))]
mod trap {
use std::borrow::ToOwned;
use std::sync::mpsc::channel;
use std::thread;
pub fn safe<T, F>(fun: F) -> Result<T, String>
where T: Send + 'static, F: FnOnce() -> T + Send + 'static {
let t = thread::Builder::new().name("safefn".to_owned());
let (send_ret, recv_ret) = channel();
let run = move || send_ret.send(fun()).unwrap();
match t.spawn(run).unwrap().join() {
Ok(()) => Ok(recv_ret.recv().unwrap()),
Err(any_err) => {
match any_err.downcast_ref::<String>() {
Some(ref s) => Err(s.trim().to_owned()),
None => Err("UNABLE TO SHOW RESULT OF PANIC.".to_owned()),
}
}
}
}
}
trait AShow : Arbitrary + Debug {}
impl<A: Arbitrary + Debug> AShow for A {}
fn arby<A: Arbitrary, G: Gen>(g: &mut G) -> A { Arbitrary::arbitrary(g) }