1use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
8
9use crate::hints::Hints;
10use crate::runner::Error;
11use crate::runner::{self, LimitSeries};
12use crate::stats::Stats;
13use crate::{hints, Fate, Limit, Prng};
14
15#[derive(Debug, Clone)]
17pub struct Config {
18 pub start_limit: Limit,
26 pub end_limit: Limit,
34 pub passes: u64,
38 pub hints_enabled: bool,
43 pub stats_enabled: bool,
48}
49
50#[derive(Debug)]
52pub struct Counterexample {
53 pub prng: Prng,
56 pub limit: Limit,
59 pub hints: Option<Hints>,
64 pub error: Error,
66}
67
68#[derive(Debug)]
70pub struct Report {
71 pub passes: u64,
73 pub stats: Option<Stats>,
75 pub counterexample: Option<Counterexample>,
77}
78
79pub fn run<T>(prng: Prng, config: &Config, test: T) -> Report
84where
85 T: Fn(Fate) + UnwindSafe + RefUnwindSafe,
86{
87 let limit_series = LimitSeries::new(config.start_limit, config.end_limit, config.passes);
88
89 let test_runs = || search_counterexample(prng, limit_series, &test);
90
91 let ((passes, counterexample_without_hints), stats) =
92 runner::util::collect_stats(config.stats_enabled, test_runs);
93
94 let counterexample = if config.hints_enabled {
95 counterexample_without_hints
96 .map(|counterexample| rerun_counterexample(counterexample, &test))
97 } else {
98 counterexample_without_hints
99 };
100
101 Report {
102 passes,
103 stats,
104 counterexample,
105 }
106}
107
108fn search_counterexample<T>(
109 mut prng: Prng,
110 limit_series: LimitSeries,
111 test: &T,
112) -> (u64, Option<Counterexample>)
113where
114 T: Fn(Fate) + UnwindSafe + RefUnwindSafe,
115{
116 let mut passes = 0;
117 let mut limits = limit_series.into_iter();
118
119 let counterexample = loop {
120 let limit = match limits.next() {
121 None => break None,
122 Some(limit) => limit,
123 };
124
125 let prng_before_run = prng.clone();
126
127 let test_result = catch_unwind(|| {
128 let fate = Fate::new(&mut prng, limit);
129 test(fate);
130 prng
131 });
132
133 prng = match test_result {
134 Err(err) => {
135 let counterexample = Counterexample {
136 prng: prng_before_run,
137 limit,
138 hints: None,
139 error: Error(err),
140 };
141 break Some(counterexample);
142 }
143 Ok(prng_after_run) => prng_after_run,
144 };
145
146 passes += 1;
147 };
148
149 (passes, counterexample)
150}
151
152fn rerun_counterexample<T>(counterexample: Counterexample, test: &T) -> Counterexample
153where
154 T: Fn(Fate) + UnwindSafe + RefUnwindSafe,
155{
156 let (test_result, hints) = {
157 let mut prng = counterexample.prng.clone();
158 let limit = counterexample.limit;
159 hints::collect(|| {
160 catch_unwind(move || {
161 let fate = Fate::new(&mut prng, limit);
162 test(fate)
163 })
164 })
165 };
166
167 match test_result {
168 Ok(()) => counterexample,
169 Err(err) => Counterexample {
170 hints: Some(hints),
171 error: Error(err),
172 ..counterexample
173 },
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use crate::runner::repeatedly::{run, Config};
180 use crate::{hints, Prng, Seed};
181
182 fn default_prng() -> Prng {
183 Prng::from_seed(Seed::from(42))
184 }
185
186 fn default_config() -> Config {
187 Config {
188 start_limit: 0.into(),
189 end_limit: 100.into(),
190 passes: 100,
191 hints_enabled: true,
192 stats_enabled: false,
193 }
194 }
195
196 #[test]
197 fn zero_passes_if_test_fails() {
198 let config = default_config();
199 let report = run(default_prng(), &config, |_| panic!());
200 assert_eq!(report.passes, 0);
201 }
202
203 #[test]
204 fn full_passes_if_test_succeeds() {
205 let config = default_config();
206 let report = run(default_prng(), &config, |_| ());
207 assert_eq!(report.passes, config.passes);
208 }
209
210 #[test]
211 fn has_counterproof_if_test_fails() {
212 let config = default_config();
213 let report = run(default_prng(), &config, |_| panic!());
214 assert!(report.counterexample.is_some());
215 }
216
217 #[test]
218 fn no_counterproof_if_test_succeeds() {
219 let config = default_config();
220 let report = run(default_prng(), &config, |_| ());
221 assert!(report.counterexample.is_none());
222 }
223
224 #[test]
225 fn no_hints_if_disabled() {
226 let config = Config {
227 hints_enabled: false,
228 ..default_config()
229 };
230 let report = run(default_prng(), &config, |_| panic!());
231 let counterexample = report.counterexample.unwrap();
232 assert!(counterexample.hints.is_none());
233 }
234
235 #[test]
236 fn no_hints_if_enabled_but_failure_not_reproducible() {
237 if cfg!(feature = "hints") {
238 let config = Config {
239 hints_enabled: true,
240 passes: 1,
241 ..default_config()
242 };
243
244 for _ in 0..10 {
245 let (report, has_failed) = hints::collect(|| {
246 run(default_prng(), &config, |_| {
247 let should_fail = Seed::random().0 % 2 == 0;
248
249 hints::add(|| format!("{}", should_fail));
250
251 if should_fail {
252 panic!();
253 }
254 })
255 });
256
257 let failure_was_not_reproducible =
258 &has_failed.0[0].text == "true" && &has_failed.0[1].text == "false";
259
260 if failure_was_not_reproducible {
261 let counterexample = report.counterexample.unwrap();
262 assert!(counterexample.hints.is_none());
263 }
264 }
265 }
266 }
267
268 #[test]
269 fn has_hints_if_enabled_and_test_deterministic() {
270 let config = Config {
271 hints_enabled: true,
272 ..default_config()
273 };
274 let report = run(default_prng(), &config, |_| panic!());
275 let counterexample = report.counterexample.unwrap();
276 assert!(counterexample.hints.is_some());
277 }
278
279 #[test]
280 fn no_stats_if_disabled_and_test_succeeds() {
281 let config = Config {
282 stats_enabled: false,
283 ..default_config()
284 };
285 let report = run(default_prng(), &config, |_| ());
286 let stats = report.stats;
287 assert!(stats.is_none());
288 }
289
290 #[test]
291 fn no_stats_if_disabled_and_test_fails() {
292 let config = Config {
293 stats_enabled: false,
294 ..default_config()
295 };
296 let report = run(default_prng(), &config, |_| panic!());
297 let stats = report.stats;
298 assert!(stats.is_none());
299 }
300
301 #[test]
302 fn has_stats_if_enabled_test_succeeds() {
303 let config = Config {
304 stats_enabled: true,
305 ..default_config()
306 };
307 let report = run(default_prng(), &config, |_| ());
308 let stats = report.stats;
309 assert!(stats.is_some());
310 }
311
312 #[test]
313 fn has_stats_if_enabled_and_test_fails() {
314 let config = Config {
315 stats_enabled: true,
316 ..default_config()
317 };
318 let report = run(default_prng(), &config, |_| panic!());
319 let stats = report.stats;
320 assert!(stats.is_some());
321 }
322}