prop_check_rs/
prop.rs

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
14/// The trait to return the failure of the property.
15pub trait IsFalsified {
16  fn is_falsified(&self) -> bool;
17  fn non_falsified(&self) -> bool {
18    !self.is_falsified()
19  }
20}
21
22/// Represents the result of the property.
23#[derive(Clone)]
24pub enum PropResult {
25  /// The property is passed.
26  Passed {
27    /// The number of test cases.
28    test_cases: TestCases,
29  },
30  /// The property is falsified.
31  Falsified {
32    /// The failure of the property.
33    failure: FailedCase,
34    /// The number of successes.
35    successes: SuccessCount,
36  },
37  Proved,
38}
39
40impl PropResult {
41  /// The `map` method can change the number of test cases.
42  ///
43  /// # Arguments
44  /// - `f` - The function to change the number of test cases.
45  ///
46  /// # Returns
47  /// - `PropResult` - The new PropResult.
48  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  /// The `flat_map` method can change the number of test cases.
61  ///
62  /// # Arguments
63  /// - `f` - The function to change the number of test cases.
64  ///
65  /// # Returns
66  /// - `PropResult` - The new PropResult.
67  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  /// The `to_result` method can convert the PropResult to Result.
78  ///
79  /// # Returns
80  /// - `Result<String>` - The result of the PropResult.
81  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  /// The `to_result_unit` method can convert the PropResult to Result with the message.
90  ///
91  /// # Returns
92  /// - `Result<()>` - The result without the message of the PropResult
93  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  /// The `message` method can return the message of the PropResult.
107  ///
108  /// # Returns
109  /// - `String` - The message of the PropResult.
110  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
141/// Returns a Prop that executes a function to evaluate properties using SGen.
142///
143/// # Arguments
144/// - `sgen` - The SGen.
145/// - `test` - The function to evaluate the properties.
146///
147/// # Returns
148/// - `Prop` - The new Prop.
149pub 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
160/// Returns a Prop that executes a function to evaluate properties using Gen with size.
161///
162/// # Arguments
163/// - `gf` - The function to create a Gen with size.
164/// - `f` - The function to evaluate the properties.
165///
166/// # Returns
167/// - `Prop` - The new Prop.
168pub 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      // Create a Vec with pre-allocated capacity
179      let mut props = Vec::with_capacity(max as usize);
180
181      // Replace iterator chains with a simple loop
182      for i in 0..max {
183        props.push(for_all_gen(gf(i), test()));
184      }
185
186      // Early return if empty
187      if props.is_empty() {
188        return PropResult::Passed { test_cases: 0 };
189      }
190
191      // Clone and get the first Prop
192      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      // Process the remaining Props
196      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      // Execute the final result
203      match result_prop.run(max, n, rng) {
204        _ => PropResult::Proved,
205      }
206    })),
207  }
208}
209
210/// Returns a Prop that executes a function to evaluate properties using Gen.
211///
212/// # Arguments
213/// - `g` - The Gen.
214/// - `test` - The function to evaluate the properties.
215///
216/// # Returns
217/// - `Prop` - The new Prop.
218pub 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      // Replace iterator chains with a simple loop
225      let mut success_count = 1;
226
227      for _ in 0..n {
228        // Generate test value
229        let (test_value, new_rng) = g.clone().run(rng);
230        rng = new_rng;
231
232        // Execute test
233        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      // If all tests pass
244      PropResult::Passed { test_cases: n }
245    })),
246  }
247}
248
249/// Execute the Prop.
250///
251/// # Arguments
252/// - `max_size` - The maximum size of the generated value.
253/// - `test_cases` - The number of test cases.
254/// - `rng` - The random number generator.
255///
256/// # Returns
257/// - `Result<String>` - The result of the Prop.
258pub 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
262/// Execute the Prop.
263///
264/// # Arguments
265/// - `max_size` - The maximum size of the generated value.
266/// - `test_cases` - The number of test cases.
267/// - `rng` - The random number generator.
268///
269/// # Returns
270/// - `Result<()>` - The result of the Prop.
271pub 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
275/// Represents the property.
276pub 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  /// Create a new Prop.
290  ///
291  /// # Arguments
292  /// - `f` - The function to evaluate the properties.
293  ///
294  /// # Returns
295  /// - `Prop` - The new Prop.
296  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  /// Execute the Prop.
305  ///
306  /// # Arguments
307  /// - `max_size` - The maximum size of the generated value.
308  /// - `test_cases` - The number of test cases.
309  /// - `rng` - The random number generator.
310  ///
311  /// # Returns
312  /// - `PropResult` - The result of the Prop.
313  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  /// The `tag` method can add a message to the PropResult.
319  ///
320  /// # Arguments
321  /// - `msg` - The message.
322  ///
323  /// # Returns
324  /// - `Prop` - The tagged Prop.
325  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  /// The `and` method can combine a other Prop.
339  /// If the first Prop is passed, the second Prop is executed.
340  ///
341  /// # Arguments
342  /// - `other` - The other Prop.
343  ///
344  /// # Returns
345  /// - `Prop` - The combined Prop.
346  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  /// The 'or' method can combine a other Prop.
356  /// If the first Prop is falsified, the second Prop is executed.
357  ///
358  /// # Arguments
359  /// - `other` - The other Prop.
360  ///
361  /// # Returns
362  /// - `Prop` - The combined Prop.
363  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    // Property that always succeeds
492    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    // Property that always fails
501    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    // Property that always succeeds
510    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    // Properties that both succeed
524    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    // Property where the first one fails
536    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    // Properties that both succeed
549    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    // Property where the first one fails
561    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    // Properties that both fail
573    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}