use std::collections::BTreeMap;
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::panic::{self, AssertUnwindSafe};
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::SeqCst;
use rand::{self, XorShiftRng};
use strategy::*;
lazy_static! {
static ref DEFAULT_CONFIG: Config = {
let mut result = Config {
cases: 256,
max_local_rejects: 65536,
max_global_rejects: 1024,
max_flat_map_regens: 1_000_000,
_non_exhaustive: (),
};
fn parse_or_warn(dst: &mut u32, value: OsString, var: &str) {
if let Some(value) = value.to_str() {
if let Ok(value) = value.parse() {
*dst = value;
} else {
eprintln!(
"proptest: The env-var {}={} can't be parsed as u32, \
using default of {}.", var, value, *dst);
}
} else {
eprintln!(
"proptest: The env-var {} is not valid, using \
default of {}.", var, *dst);
}
}
for (var, value) in env::vars_os() {
if let Some(var) = var.to_str() {
match var {
"PROPTEST_CASES" => parse_or_warn(
&mut result.cases, value, "PROPTEST_CASES"),
"PROPTEST_MAX_LOCAL_REJECTS" => parse_or_warn(
&mut result.max_local_rejects, value,
"PROPTEST_MAX_LOCAL_REJECTS"),
"PROPTEST_MAX_GLOBAL_REJECTS" => parse_or_warn(
&mut result.max_global_rejects, value,
"PROPTEST_MAX_GLOBAL_REJECTS"),
"PROPTEST_MAX_FLAT_MAP_REGENS" => parse_or_warn(
&mut result.max_flat_map_regens, value,
"PROPTEST_MAX_FLAT_MAP_REGENS"),
_ => if var.starts_with("PROPTEST_") {
eprintln!("proptest: Ignoring unknown env-var {}.",
var);
},
}
}
}
result
};
}
#[derive(Clone, Debug, PartialEq)]
pub struct Config {
pub cases: u32,
pub max_local_rejects: u32,
pub max_global_rejects: u32,
pub max_flat_map_regens: u32,
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl Config {
pub fn with_cases(n: u32) -> Self {
Self {
cases: n,
.. Config::default()
}
}
}
impl Default for Config {
fn default() -> Self {
DEFAULT_CONFIG.clone()
}
}
#[derive(Debug, Clone)]
pub enum TestCaseError {
Reject(String),
Fail(String),
}
pub type TestCaseResult = Result<(), TestCaseError>;
impl fmt::Display for TestCaseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TestCaseError::Reject(ref whence) =>
write!(f, "Input rejected at {}", whence),
TestCaseError::Fail(ref why) =>
write!(f, "Case failed: {}", why),
}
}
}
impl<E : ::std::error::Error> From<E> for TestCaseError {
fn from(cause: E) -> Self {
TestCaseError::Fail(cause.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TestError<T> {
Abort(String),
Fail(String, T),
}
impl<T : fmt::Debug> fmt::Display for TestError<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TestError::Abort(ref why) =>
write!(f, "Test aborted: {}", why),
TestError::Fail(ref why, ref what) =>
write!(f, "Test failed: {}; minimal failing input: {:?}",
why, what),
}
}
}
impl<T : fmt::Debug> ::std::error::Error for TestError<T> {
fn description(&self) -> &str {
match *self {
TestError::Abort(..) => "Abort",
TestError::Fail(..) => "Fail",
}
}
}
#[derive(Clone)]
pub struct TestRunner {
config: Config,
successes: u32,
local_rejects: u32,
global_rejects: u32,
rng: XorShiftRng,
flat_map_regens: Arc<AtomicUsize>,
local_reject_detail: BTreeMap<String, u32>,
global_reject_detail: BTreeMap<String, u32>,
}
impl fmt::Debug for TestRunner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("TestRunner")
.field("config", &self.config)
.field("successes", &self.successes)
.field("local_rejects", &self.local_rejects)
.field("global_rejects", &self.global_rejects)
.field("rng", &"<XorShiftRng>")
.field("flat_map_regens", &self.flat_map_regens)
.field("local_reject_detail", &self.local_reject_detail)
.field("global_reject_detail", &self.global_reject_detail)
.finish()
}
}
impl fmt::Display for TestRunner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\tsuccesses: {}\n\
\tlocal rejects: {}\n",
self.successes, self.local_rejects)?;
for (whence, count) in &self.local_reject_detail {
writeln!(f, "\t\t{} times at {}", count, whence)?;
}
writeln!(f, "\tglobal rejects: {}", self.global_rejects)?;
for (whence, count) in &self.global_reject_detail {
writeln!(f, "\t\t{} times at {}", count, whence)?;
}
Ok(())
}
}
impl Default for TestRunner {
fn default() -> Self {
Self::new(Config::default())
}
}
impl TestRunner {
pub fn new(config: Config) -> Self {
TestRunner {
config: config,
successes: 0,
local_rejects: 0,
global_rejects: 0,
rng: rand::weak_rng(),
flat_map_regens: Arc::new(AtomicUsize::new(0)),
local_reject_detail: BTreeMap::new(),
global_reject_detail: BTreeMap::new(),
}
}
pub(crate) fn partial_clone(&self) -> Self {
TestRunner {
config: self.config.clone(),
successes: 0,
local_rejects: 0,
global_rejects: 0,
rng: rand::weak_rng(),
flat_map_regens: Arc::clone(&self.flat_map_regens),
local_reject_detail: BTreeMap::new(),
global_reject_detail: BTreeMap::new(),
}
}
pub fn rng(&mut self) -> &mut XorShiftRng {
&mut self.rng
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn run<S : Strategy,
F : Fn (&ValueFor<S>) -> TestCaseResult>
(&mut self, strategy: &S, f: F)
-> Result<(), TestError<ValueFor<S>>>
{
while self.successes < self.config.cases {
let case = match strategy.new_value(self) {
Ok(v) => v,
Err(msg) => return Err(TestError::Abort(msg)),
};
if self.run_one(case, &f)? {
self.successes += 1;
}
}
Ok(())
}
pub fn run_one<V : ValueTree,
F : Fn (&V::Value) -> TestCaseResult>
(&mut self, mut case: V, f: F) -> Result<bool, TestError<V::Value>>
{
macro_rules! test {
($v:expr) => { {
let v = $v;
match panic::catch_unwind(AssertUnwindSafe(|| f(&v))) {
Ok(r) => r,
Err(what) => {
let msg = what.downcast::<&'static str>()
.map(|s| (*s).to_owned())
.or_else(|what| what.downcast::<String>().map(|b| *b))
.unwrap_or_else(
|_| "<unknown panic value>".to_owned());
Err(TestCaseError::Fail(msg))
},
}
} }
}
match test!(case.current()) {
Ok(_) => Ok(true),
Err(TestCaseError::Fail(why)) => {
let mut last_failure = (why, case.current());
if case.simplify() {
loop {
let passed = match test!(case.current()) {
Ok(_) | Err(TestCaseError::Reject(..)) => true,
Err(TestCaseError::Fail(why)) => {
last_failure = (why, case.current());
false
},
};
if passed {
if !case.complicate() {
break;
}
} else if !case.simplify() {
break;
}
}
}
Err(TestError::Fail(last_failure.0, last_failure.1))
},
Err(TestCaseError::Reject(whence)) => {
self.reject_global(&whence)?;
Ok(false)
},
}
}
pub fn reject_local(&mut self, whence: String) -> Result<(),String> {
if self.local_rejects >= self.config.max_local_rejects {
Err("Too many local rejects".to_owned())
} else {
self.local_rejects += 1;
let need_insert = if let Some(count) =
self.local_reject_detail.get_mut(&whence)
{
*count += 1;
false
} else {
true
};
if need_insert {
self.local_reject_detail.insert(whence, 1);
}
Ok(())
}
}
fn reject_global<T>(&mut self, whence: &str) -> Result<(),TestError<T>> {
if self.global_rejects >= self.config.max_global_rejects {
Err(TestError::Abort("Too many global rejects".to_owned()))
} else {
self.global_rejects += 1;
let need_insert = if let Some(count) =
self.global_reject_detail.get_mut(whence)
{
*count += 1;
false
} else {
true
};
if need_insert {
self.global_reject_detail.insert(whence.to_owned(), 1);
}
Ok(())
}
}
pub fn flat_map_regen(&self) -> bool {
self.flat_map_regens.fetch_add(1, SeqCst) <
self.config.max_flat_map_regens as usize
}
}
#[cfg(test)]
mod test {
use std::cell::Cell;
use super::*;
#[test]
fn gives_up_after_too_many_rejections() {
let config = Config::default();
let mut runner = TestRunner::new(config.clone());
let runs = Cell::new(0);
let result = runner.run(&(0u32..), |_| {
runs.set(runs.get() + 1);
Err(TestCaseError::Reject("reject".to_owned()))
});
match result {
Err(TestError::Abort(_)) => (),
e => panic!("Unexpected result: {:?}", e),
}
assert_eq!(config.max_global_rejects + 1, runs.get());
}
#[test]
fn test_pass() {
let mut runner = TestRunner::default();
let result = runner.run(&(1u32..), |&v| { assert!(v > 0); Ok(()) });
assert_eq!(Ok(()), result);
}
#[test]
fn test_fail_via_result() {
let mut runner = TestRunner::default();
let result = runner.run(&(0u32..10u32), |&v| if v < 5 {
Ok(())
} else {
Err(TestCaseError::Fail("not less than 5".to_owned()))
});
assert_eq!(Err(TestError::Fail("not less than 5".to_owned(), 5)),
result);
}
#[test]
fn test_fail_via_panic() {
let mut runner = TestRunner::default();
let result = runner.run(&(0u32..10u32), |&v| {
assert!(v < 5, "not less than 5");
Ok(())
});
assert_eq!(Err(TestError::Fail("not less than 5".to_owned(), 5)),
result);
}
}