1use crate::gen::{Gen, SGen};
2use crate::rng::RNG;
3
4use anyhow::*;
5use std::cell::RefCell;
6use std::fmt::Debug;
7use std::rc::Rc;
8
9pub type MaxSize = u32;
10pub type TestCases = u32;
11pub type FailedCase = String;
12pub type SuccessCount = u32;
13
14pub trait IsFalsified {
16 fn is_falsified(&self) -> bool;
17 fn non_falsified(&self) -> bool {
18 !self.is_falsified()
19 }
20}
21
22#[derive(Clone)]
24pub enum PropResult {
25 Passed {
27 test_cases: TestCases,
29 },
30 Falsified {
32 failure: FailedCase,
34 successes: SuccessCount,
36 },
37 Proved,
38}
39
40impl PropResult {
41 pub fn map<F>(self, f: F) -> PropResult
49 where
50 F: FnOnce(u32) -> u32, {
51 match self {
52 PropResult::Passed { test_cases } => PropResult::Passed {
53 test_cases: f(test_cases),
54 },
55 PropResult::Proved => PropResult::Proved,
56 other => other,
57 }
58 }
59
60 pub fn flat_map<F>(self, f: F) -> PropResult
68 where
69 F: FnOnce(Option<u32>) -> PropResult, {
70 match self {
71 PropResult::Passed { test_cases } => f(Some(test_cases)),
72 PropResult::Proved => f(None),
73 other => other,
74 }
75 }
76
77 pub fn to_result(self) -> Result<String> {
82 match self {
83 p @ PropResult::Passed { .. } => Ok(p.message()),
84 p @ PropResult::Proved => Ok(p.message()),
85 f @ PropResult::Falsified { .. } => Err(anyhow!(f.message())),
86 }
87 }
88
89 pub fn to_result_unit(self) -> Result<()> {
94 self
95 .to_result()
96 .map(|msg| {
97 log::info!("{}", msg);
98 ()
99 })
100 .map_err(|err| {
101 log::error!("{}", err);
102 err
103 })
104 }
105
106 pub fn message(&self) -> String {
111 match self {
112 PropResult::Passed { test_cases } => format!("OK, passed {} tests", test_cases),
113 PropResult::Proved => "OK, proved property".to_string(),
114 PropResult::Falsified { failure, successes } => {
115 format!("Falsified after {} passed tests: {}", successes, failure)
116 }
117 }
118 }
119}
120
121impl IsFalsified for PropResult {
122 fn is_falsified(&self) -> bool {
123 match self {
124 PropResult::Passed { .. } => false,
125 PropResult::Falsified { .. } => true,
126 PropResult::Proved => false,
127 }
128 }
129}
130
131fn random_stream<A>(g: Gen<A>, mut rng: RNG) -> impl Iterator<Item = A>
132where
133 A: Clone + 'static, {
134 std::iter::from_fn(move || {
135 let (a, new_rng) = g.clone().run(rng.clone());
136 rng = new_rng;
137 Some(a)
138 })
139}
140
141pub fn for_all_sgen<A, F, FF>(sgen: SGen<A>, mut test: FF) -> Prop
150where
151 F: FnMut(A) -> bool + 'static,
152 FF: FnMut() -> F + 'static,
153 A: Clone + Debug + 'static, {
154 match sgen {
155 SGen::Unsized(g) => for_all_gen(g, test()),
156 s @ SGen::Sized(..) => for_all_gen_for_size(move |i| s.run(Some(i)), test),
157 }
158}
159
160pub fn for_all_gen_for_size<A, GF, F, FF>(gf: GF, mut test: FF) -> Prop
169where
170 GF: Fn(u32) -> Gen<A> + 'static,
171 F: FnMut(A) -> bool + 'static,
172 FF: FnMut() -> F + 'static,
173 A: Clone + Debug + 'static, {
174 Prop {
175 run_f: Rc::new(RefCell::new(move |max, n, rng| {
176 let cases_per_size = n / max;
177
178 let mut props = Vec::with_capacity(max as usize);
180
181 for i in 0..max {
183 props.push(for_all_gen(gf(i), test()));
184 }
185
186 if props.is_empty() {
188 return PropResult::Passed { test_cases: 0 };
189 }
190
191 let first_prop = props[0].clone();
193 let mut result_prop = Prop::new(move |max, _, rng| first_prop.run(max, cases_per_size, rng));
194
195 for i in 1..props.len() {
197 let p = props[i].clone();
198 let prop = Prop::new(move |max, _, rng| p.run(max, cases_per_size, rng));
199 result_prop = result_prop.and(prop);
200 }
201
202 match result_prop.run(max, n, rng) {
204 _ => PropResult::Proved,
205 }
206 })),
207 }
208}
209
210pub fn for_all_gen<A, F>(g: Gen<A>, mut test: F) -> Prop
219where
220 F: FnMut(A) -> bool + 'static,
221 A: Clone + Debug + 'static, {
222 Prop {
223 run_f: Rc::new(RefCell::new(move |_, n, mut rng| {
224 let mut success_count = 1;
226
227 for _ in 0..n {
228 let (test_value, new_rng) = g.clone().run(rng);
230 rng = new_rng;
231
232 if !test(test_value.clone()) {
234 return PropResult::Falsified {
235 failure: format!("{:?}", test_value),
236 successes: success_count,
237 };
238 }
239
240 success_count += 1;
241 }
242
243 PropResult::Passed { test_cases: n }
245 })),
246 }
247}
248
249pub fn run_with_prop(p: Prop, max_size: MaxSize, test_cases: TestCases, rng: RNG) -> Result<String> {
259 p.run(max_size, test_cases, rng).to_result()
260}
261
262pub fn test_with_prop(p: Prop, max_size: MaxSize, test_cases: TestCases, rng: RNG) -> Result<()> {
272 p.run(max_size, test_cases, rng).to_result_unit()
273}
274
275pub struct Prop {
277 run_f: Rc<RefCell<dyn FnMut(MaxSize, TestCases, RNG) -> PropResult>>,
278}
279
280impl Clone for Prop {
281 fn clone(&self) -> Self {
282 Self {
283 run_f: self.run_f.clone(),
284 }
285 }
286}
287
288impl Prop {
289 pub fn new<F>(f: F) -> Prop
297 where
298 F: Fn(MaxSize, TestCases, RNG) -> PropResult + 'static, {
299 Prop {
300 run_f: Rc::new(RefCell::new(f)),
301 }
302 }
303
304 pub fn run(&self, max_size: MaxSize, test_cases: TestCases, rng: RNG) -> PropResult {
314 let mut f = self.run_f.borrow_mut();
315 f(max_size, test_cases, rng)
316 }
317
318 pub fn tag(self, msg: String) -> Prop {
326 Prop::new(move |max, n, rng| match self.run(max, n, rng) {
327 PropResult::Falsified {
328 failure: e,
329 successes: c,
330 } => PropResult::Falsified {
331 failure: format!("{}\n{}", msg, e),
332 successes: c,
333 },
334 x => x,
335 })
336 }
337
338 pub fn and(self, other: Self) -> Prop {
347 Self::new(
348 move |max: MaxSize, n: TestCases, rng: RNG| match self.run(max, n, rng.clone()) {
349 PropResult::Passed { .. } | PropResult::Proved => other.run(max, n, rng),
350 x => x,
351 },
352 )
353 }
354
355 pub fn or(self, other: Self) -> Prop {
364 Self::new(move |max, n, rng: RNG| match self.run(max, n, rng.clone()) {
365 PropResult::Falsified { failure: msg, .. } => other.clone().tag(msg).run(max, n, rng),
366 x => x,
367 })
368 }
369}
370
371#[cfg(test)]
372mod tests {
373
374 use crate::gen::Gens;
375
376 use super::*;
377 use anyhow::Result;
378 use std::env;
379
380 fn init() {
381 env::set_var("RUST_LOG", "info");
382 let _ = env_logger::builder().is_test(true).try_init();
383 }
384
385 fn new_rng() -> RNG {
386 RNG::new()
387 }
388
389 #[test]
390 fn test_one_of() -> Result<()> {
391 init();
392 let gen = Gens::one_of_values(['a', 'b', 'c', 'x', 'y', 'z']);
393 let prop = for_all_gen(gen, move |a| {
394 log::info!("value = {}", a);
395 true
396 });
397 test_with_prop(prop, 1, 100, new_rng())
398 }
399
400 #[test]
401 fn test_one_of_2() -> Result<()> {
402 init();
403 let mut counter = 0;
404 let gen = Gens::one_of_values(['a', 'b', 'c', 'x', 'y', 'z']);
405 let prop = for_all_gen_for_size(
406 move |size| Gens::list_of_n(size as usize, gen.clone()),
407 move || {
408 move |a| {
409 counter += 1;
410 log::info!("value = {},{:?}", counter, a);
411 true
412 }
413 },
414 );
415 test_with_prop(prop, 10, 100, new_rng())
416 }
417
418 #[test]
419 fn test_prop_result_passed() {
420 init();
421 let result = PropResult::Passed { test_cases: 100 };
422 assert!(!result.is_falsified());
423 assert!(result.non_falsified());
424 assert_eq!(result.message(), "OK, passed 100 tests");
425 }
426
427 #[test]
428 fn test_prop_result_proved() {
429 init();
430 let result = PropResult::Proved;
431 assert!(!result.is_falsified());
432 assert!(result.non_falsified());
433 assert_eq!(result.message(), "OK, proved property");
434 }
435
436 #[test]
437 fn test_prop_result_falsified() {
438 init();
439 let result = PropResult::Falsified {
440 failure: "test failed".to_string(),
441 successes: 42,
442 };
443 assert!(result.is_falsified());
444 assert!(!result.non_falsified());
445 assert_eq!(result.message(), "Falsified after 42 passed tests: test failed");
446 }
447
448 #[test]
449 fn test_prop_result_map() {
450 init();
451 let result = PropResult::Passed { test_cases: 100 };
452 let mapped = result.map(|n| n * 2);
453 match mapped {
454 PropResult::Passed { test_cases } => assert_eq!(test_cases, 200),
455 _ => panic!("Expected PropResult::Passed"),
456 }
457 }
458
459 #[test]
460 fn test_prop_result_flat_map() {
461 init();
462 let result = PropResult::Passed { test_cases: 100 };
463 let flat_mapped = result.flat_map(|n| PropResult::Passed {
464 test_cases: n.unwrap() * 2,
465 });
466 match flat_mapped {
467 PropResult::Passed { test_cases } => assert_eq!(test_cases, 200),
468 _ => panic!("Expected PropResult::Passed"),
469 }
470 }
471
472 #[test]
473 fn test_prop_result_to_result() {
474 init();
475 let passed = PropResult::Passed { test_cases: 100 };
476 let passed_result = passed.to_result();
477 assert!(passed_result.is_ok());
478 assert_eq!(passed_result.unwrap(), "OK, passed 100 tests");
479
480 let falsified = PropResult::Falsified {
481 failure: "test failed".to_string(),
482 successes: 42,
483 };
484 let falsified_result = falsified.to_result();
485 assert!(falsified_result.is_err());
486 }
487
488 #[test]
489 fn test_for_all_gen() {
490 init();
491 let gen = Gens::choose_i32(1, 100);
493 let prop = for_all_gen(gen.clone(), |_| true);
494 let result = prop.run(1, 10, new_rng());
495 match result {
496 PropResult::Passed { test_cases } => assert_eq!(test_cases, 10),
497 _ => panic!("Expected PropResult::Passed"),
498 }
499
500 let prop = for_all_gen(gen, |_| false);
502 let result = prop.run(1, 10, new_rng());
503 assert!(result.is_falsified());
504 }
505
506 #[test]
507 fn test_for_all_sgen() {
508 init();
509 let gen = Gens::choose_i32(1, 100);
511 let sgen = crate::gen::SGen::Unsized(gen.clone());
512 let prop = for_all_sgen(sgen, || |_| true);
513 let result = prop.run(1, 10, new_rng());
514 match result {
515 PropResult::Passed { test_cases } => assert_eq!(test_cases, 10),
516 _ => panic!("Expected PropResult::Passed"),
517 }
518 }
519
520 #[test]
521 fn test_prop_and() {
522 init();
523 let gen1 = Gens::choose_i32(1, 100);
525 let gen2 = Gens::choose_i32(1, 100);
526 let prop1 = for_all_gen(gen1, |_| true);
527 let prop2 = for_all_gen(gen2, |_| true);
528 let combined = prop1.and(prop2);
529 let result = combined.run(1, 10, new_rng());
530 match result {
531 PropResult::Passed { test_cases } => assert_eq!(test_cases, 10),
532 _ => panic!("Expected PropResult::Passed"),
533 }
534
535 let gen1 = Gens::choose_i32(1, 100);
537 let gen2 = Gens::choose_i32(1, 100);
538 let prop1 = for_all_gen(gen1, |_| false);
539 let prop2 = for_all_gen(gen2, |_| true);
540 let combined = prop1.and(prop2);
541 let result = combined.run(1, 10, new_rng());
542 assert!(result.is_falsified());
543 }
544
545 #[test]
546 fn test_prop_or() {
547 init();
548 let gen1 = Gens::choose_i32(1, 100);
550 let gen2 = Gens::choose_i32(1, 100);
551 let prop1 = for_all_gen(gen1, |_| true);
552 let prop2 = for_all_gen(gen2, |_| true);
553 let combined = prop1.or(prop2);
554 let result = combined.run(1, 10, new_rng());
555 match result {
556 PropResult::Passed { test_cases } => assert_eq!(test_cases, 10),
557 _ => panic!("Expected PropResult::Passed"),
558 }
559
560 let gen1 = Gens::choose_i32(1, 100);
562 let gen2 = Gens::choose_i32(1, 100);
563 let prop1 = for_all_gen(gen1, |_| false);
564 let prop2 = for_all_gen(gen2, |_| true);
565 let combined = prop1.or(prop2);
566 let result = combined.run(1, 10, new_rng());
567 match result {
568 PropResult::Passed { test_cases } => assert_eq!(test_cases, 10),
569 _ => panic!("Expected PropResult::Passed"),
570 }
571
572 let gen1 = Gens::choose_i32(1, 100);
574 let gen2 = Gens::choose_i32(1, 100);
575 let prop1 = for_all_gen(gen1, |_| false);
576 let prop2 = for_all_gen(gen2, |_| false);
577 let combined = prop1.or(prop2);
578 let result = combined.run(1, 10, new_rng());
579 assert!(result.is_falsified());
580 }
581
582 #[test]
583 fn test_prop_tag() {
584 init();
585 let gen = Gens::choose_i32(1, 100);
586 let prop = for_all_gen(gen, |_| false);
587 let tagged = prop.tag("Custom error message".to_string());
588 let result = tagged.run(1, 10, new_rng());
589 match result {
590 PropResult::Falsified { failure, .. } => {
591 assert!(failure.contains("Custom error message"));
592 }
593 _ => panic!("Expected PropResult::Falsified"),
594 }
595 }
596
597 #[test]
598 fn test_run_with_prop() -> Result<()> {
599 init();
600 let gen = Gens::choose_i32(1, 100);
601 let prop = for_all_gen(gen, |_| true);
602 let result = run_with_prop(prop, 1, 10, new_rng());
603 assert!(result.is_ok());
604 Ok(())
605 }
606}