use std::fmt::Debug;
pub fn property_test<F: Fn()>(iterations: usize, f: F) {
for i in 0..iterations {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(&f)) {
Ok(_) => {}
Err(e) => {
panic!(
"Property test failed on iteration {}/{}: {:?}",
i + 1,
iterations,
e
);
}
}
}
}
pub fn property_test_with_gen<T, G, F>(iterations: usize, gen: G, property: F)
where
G: Fn() -> T,
F: Fn(T),
{
for i in 0..iterations {
let value = gen();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| property(value))) {
Ok(_) => {}
Err(e) => {
panic!(
"Property test failed on iteration {}/{}: {:?}",
i + 1,
iterations,
e
);
}
}
}
}
pub fn property_test_with_gen1<T, G, F>(iterations: usize, gen: G, property: F)
where
G: Fn() -> T,
F: Fn(T),
{
property_test_with_gen(iterations, gen, property)
}
pub fn property_test_with_gen2<T1, T2, G1, G2, F>(
iterations: usize,
gen1: G1,
gen2: G2,
property: F,
) where
G1: Fn() -> T1,
G2: Fn() -> T2,
F: Fn(T1, T2),
{
for i in 0..iterations {
let val1 = gen1();
let val2 = gen2();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| property(val1, val2))) {
Ok(_) => {}
Err(e) => {
panic!(
"Property test failed on iteration {}/{}: {:?}",
i + 1,
iterations,
e
);
}
}
}
}
pub fn property_test_with_gen3<T1, T2, T3, G1, G2, G3, F>(
iterations: usize,
gen1: G1,
gen2: G2,
gen3: G3,
property: F,
) where
G1: Fn() -> T1,
G2: Fn() -> T2,
G3: Fn() -> T3,
F: Fn(T1, T2, T3),
{
for i in 0..iterations {
let val1 = gen1();
let val2 = gen2();
let val3 = gen3();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| property(val1, val2, val3)))
{
Ok(_) => {}
Err(e) => {
panic!(
"Property test failed on iteration {}/{}: {:?}",
i + 1,
iterations,
e
);
}
}
}
}
pub fn shrink_int(value: i64) -> Vec<i64> {
let mut shrinks = vec![0];
let mut current = value.abs() / 2;
while current > 0 {
shrinks.push(current);
shrinks.push(-current);
current /= 2;
}
shrinks
}
pub fn find_minimal_failing<T: Copy + Debug, F, S>(initial: T, shrink: S, property: F) -> Option<T>
where
F: Fn(T) -> bool,
S: Fn(T) -> Vec<T>,
{
if property(initial) {
return None;
}
let mut current_failure = initial;
for shrunk in shrink(current_failure) {
if !property(shrunk) {
current_failure = shrunk;
}
}
Some(current_failure)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_property_test_passes() {
property_test(10, || {
let a = 5;
let b = 3;
assert_eq!(a + b, b + a);
});
}
#[test]
#[should_panic(expected = "Property test failed")]
fn test_property_test_fails() {
property_test(10, || {
panic!("Intentional failure");
});
}
#[test]
fn test_property_test_with_gen() {
property_test_with_gen(
10,
|| 42,
|value| {
assert_eq!(value, 42);
},
);
}
#[test]
fn test_property_test_with_gen2() {
property_test_with_gen2(
10,
|| 1,
|| 2,
|a, b| {
assert_eq!(a + b, 3);
},
);
}
#[test]
fn test_property_test_with_gen3() {
property_test_with_gen3(
10,
|| 1,
|| 2,
|| 3,
|a, b, c| {
assert_eq!(a + b + c, 6);
},
);
}
#[test]
fn test_shrink_int() {
let shrinks = shrink_int(100);
assert!(shrinks.contains(&0));
assert!(shrinks.contains(&50));
assert!(shrinks.contains(&-50));
assert!(shrinks.contains(&25));
}
#[test]
fn test_find_minimal_failing() {
let minimal = find_minimal_failing(
1000,
shrink_int,
|x| x < 100, );
assert!(minimal.is_some());
let min = minimal.unwrap();
assert!(min >= 100); }
}