use std::fmt;
use std::panic;
use data::*;
use generators::*;
#[derive(Debug, Clone)]
pub struct CheckConfig {
num_tests: usize,
max_skips: usize,
}
impl Default for CheckConfig {
fn default() -> Self {
let num_tests = 100;
CheckConfig {
num_tests: num_tests,
max_skips: num_tests * 10,
}
}
}
impl CheckConfig {
pub fn num_tests(&self, num_tests: usize) -> Self {
CheckConfig {
num_tests,
..self.clone()
}
}
pub fn max_skips(&self, max_skips: usize) -> Self {
CheckConfig {
max_skips,
..self.clone()
}
}
pub fn property<G: Generator>(&self, gen: G) -> Property<G> {
Property {
config: self.clone(),
gen: gen,
}
}
}
pub struct Property<G> {
config: CheckConfig,
gen: G,
}
pub trait CheckResult {
fn is_failure(&self) -> bool;
}
pub fn property<G: Generator>(gen: G) -> Property<G> {
CheckConfig::default().property(gen)
}
impl<G: Generator> Property<G>
where
G::Item: fmt::Debug,
{
pub fn check<R: CheckResult + fmt::Debug, F: Fn(G::Item) -> R>(self, subject: F) {
let mut tests_run = 0usize;
let mut items_skipped = 0usize;
while tests_run < self.config.num_tests {
let mut pool = InfoPool::new();
trace!("Tests run: {}; skipped:{}", tests_run, items_skipped);
match self.gen.generate(&mut pool.tap()) {
Ok(arg) => {
let res = Self::attempt(&subject, arg);
trace!(
"Result: {:?} -> {:?}",
self.gen.generate(&mut pool.replay()),
res
);
tests_run += 1;
if res.is_failure() {
let minpool = find_minimal(
&self.gen,
pool,
|v| Self::attempt(&subject, v).is_failure(),
);
panic!(
"Predicate failed for argument {:?}; check returned {:?}",
self.gen.generate(&mut minpool.replay()),
res
)
}
}
Err(DataError::SkipItem) => {
trace!("Skip: {:?}", self.gen.generate(&mut pool.replay()));
items_skipped += 1;
if items_skipped >= self.config.max_skips {
panic!(
"Could not finish on {}/{} tests (have skipped {} times)",
tests_run,
self.config.num_tests,
items_skipped
);
}
}
Err(e) => {
trace!("Gen failure: {:?}", self.gen.generate(&mut pool.replay()));
debug!("{:?}", e);
}
}
}
trace!("Completing okay");
}
fn attempt<R: CheckResult, F: Fn(G::Item) -> R>(subject: F, arg: G::Item) -> Result<R, String> {
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| subject(arg)));
match res {
Ok(r) => Ok(r),
Err(err) => {
let msg = if let Some(s) = err.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = err.downcast_ref::<String>() {
s.to_string()
} else {
format!("Unrecognised panic result: {:?}", err)
};
Err(msg)
}
}
}
}
impl CheckResult for bool {
fn is_failure(&self) -> bool {
!self
}
}
impl<O: CheckResult, E> CheckResult for Result<O, E> {
fn is_failure(&self) -> bool {
self.as_ref().map(|r| r.is_failure()).unwrap_or(true)
}
}
impl CheckResult for () {
fn is_failure(&self) -> bool {
false
}
}