1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
}
}