use std::comm;
use std::fmt::Show;
use std::io::ChanWriter;
use std::iter;
use std::rand;
use std::task::TaskBuilder;
use super::{Arbitrary, Gen, Shrinker, StdGen};
use tester::trap::safe;
use tester::Status::{Discard, Fail, Pass};
pub struct QuickCheck<G> {
tests: uint,
max_tests: uint,
gen: G,
}
impl QuickCheck<StdGen<rand::TaskRng>> {
pub fn new() -> QuickCheck<StdGen<rand::TaskRng>> {
QuickCheck {
tests: 100,
max_tests: 10000,
gen: StdGen::new(rand::task_rng(), 100),
}
}
}
impl<G: Gen> QuickCheck<G> {
pub fn tests(mut self, tests: uint) -> QuickCheck<G> {
self.tests = tests;
self
}
pub fn max_tests(mut self, max_tests: uint) -> 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<uint, TestResult>
where A: Testable {
let mut ntests: uint = 0;
for _ in iter::range(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) }
#[deriving(Clone, Show)]
pub struct TestResult {
status: Status,
arguments: Vec<String>,
err: String,
}
#[deriving(Clone, Show)]
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: Send, F: FnOnce() -> T + Send>(f: F) -> TestResult {
let (tx, _) = comm::channel();
TestResult::from_bool(
TaskBuilder::new()
.stdout(box ChanWriter::new(tx.clone()))
.stderr(box ChanWriter::new(tx))
.try(f)
.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.as_slice()),
}
}
}
impl<T> Testable for fn() -> T where T: Testable {
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 {
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 {
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 {
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 {
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 $name.clone(),)*);
safe(move || { f($(*$name,)*) }).result($g)
};
if r.is_failure() {
r.arguments = vec![$($name.to_string(),)*];
}
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 {
fn call<G: Gen>(&self, g: &mut G,
_: Option<&A>, _: Option<&B>,
_: Option<&C>, _: Option<&D>)
-> TestResult {
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 {
fn call<G: Gen>(&self, g: &mut G,
a: Option<&A>, _: Option<&B>,
_: Option<&C>, _: Option<&D>)
-> TestResult {
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 {
fn call<G: Gen>(&self, g: &mut G,
a: Option<&A>, b: Option<&B>,
_: Option<&C>, _: Option<&D>)
-> TestResult {
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 {
fn call<G: Gen>(&self, g: &mut G,
a: Option<&A>, b: Option<&B>,
c: Option<&C>, _: Option<&D>)
-> TestResult {
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 {
fn call<G: Gen>(&self, g: &mut G,
a: Option<&A>, b: Option<&B>,
c: Option<&C>, d: Option<&D>)
-> TestResult {
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,
mut shrinker: Box<Shrinker<(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::comm::channel;
use std::io::{ChanReader, ChanWriter};
use std::task::TaskBuilder;
pub fn safe<T: Send, F: FnOnce() -> T + Send>(fun: F) -> Result<T, String> {
let (send, recv) = channel();
let stdout = ChanWriter::new(send.clone());
let stderr = ChanWriter::new(send);
let mut reader = ChanReader::new(recv);
let t = TaskBuilder::new()
.named("safefn")
.stdout(box stdout)
.stderr(box stderr);
match t.try(fun) {
Ok(v) => Ok(v),
Err(_) => {
let s = reader.read_to_string().unwrap();
Err(s.as_slice().trim().into_string())
}
}
}
}
trait AShow : Arbitrary + Show {}
impl<A: Arbitrary + Show> AShow for A {}
fn arby<A: Arbitrary, G: Gen>(g: &mut G) -> A { Arbitrary::arbitrary(g) }