use crate::ergonomic::closure_property::ClosureProperty;
pub fn commutative<T, R, F>(op: F) -> ClosureProperty<impl Fn((T, T)) -> bool>
where
F: Fn(T, T) -> R + Clone + 'static,
T: Clone + 'static,
R: PartialEq + 'static,
{
ClosureProperty::new(move |(a, b): (T, T)| {
let ab = op(a.clone(), b.clone());
let ba = op(b, a);
ab == ba
})
}
pub fn associative<T, F>(op: F) -> ClosureProperty<impl Fn((T, T, T)) -> bool>
where
F: Fn(T, T) -> T + Clone + 'static,
T: PartialEq + Clone + 'static,
{
ClosureProperty::new(move |(a, b, c): (T, T, T)| {
let ab_c = op(op(a.clone(), b.clone()), c.clone());
let a_bc = op(a, op(b, c));
ab_c == a_bc
})
}
pub fn idempotent<T, F>(f: F) -> ClosureProperty<impl Fn(T) -> bool>
where
F: Fn(T) -> T + Clone + 'static,
T: PartialEq + Clone + 'static,
{
ClosureProperty::new(move |x: T| {
let fx = f(x.clone());
let ffx = f(fx.clone());
fx == ffx
})
}
pub fn round_trip<T, U, E, D>(encode: E, decode: D) -> ClosureProperty<impl Fn(T) -> bool>
where
E: Fn(T) -> U + Clone + 'static,
D: Fn(U) -> T + Clone + 'static,
T: PartialEq + Clone + 'static,
U: Clone + 'static,
{
ClosureProperty::new(move |input: T| {
let encoded = encode(input.clone());
let decoded = decode(encoded);
decoded == input
})
}
pub fn inverse<T, F, G>(f: F, g: G) -> ClosureProperty<impl Fn(T) -> bool>
where
F: Fn(T) -> T + Clone + 'static,
G: Fn(T) -> T + Clone + 'static,
T: PartialEq + Clone + 'static,
{
ClosureProperty::new(move |x: T| {
let fgx = f(g(x.clone()));
let gfx = g(f(x.clone()));
fgx == x && gfx == x
})
}
pub fn monotonic_increasing<T, R, F>(f: F) -> ClosureProperty<impl Fn((T, T)) -> bool>
where
F: Fn(T) -> R + Clone + 'static,
T: PartialOrd + Clone + 'static,
R: PartialOrd + 'static,
{
ClosureProperty::new(move |(a, b): (T, T)| {
if a <= b {
f(a.clone()) <= f(b.clone())
} else {
true }
})
}
pub fn monotonic_decreasing<T, R, F>(f: F) -> ClosureProperty<impl Fn((T, T)) -> bool>
where
F: Fn(T) -> R + Clone + 'static,
T: PartialOrd + Clone + 'static,
R: PartialOrd + 'static,
{
ClosureProperty::new(move |(a, b): (T, T)| {
if a <= b {
f(a.clone()) >= f(b.clone())
} else {
true }
})
}
pub fn has_identity<T, F>(op: F, identity: T) -> ClosureProperty<impl Fn(T) -> bool>
where
F: Fn(T, T) -> T + Clone + 'static,
T: PartialEq + Clone + 'static,
{
ClosureProperty::new(move |x: T| {
let result_left = op(x.clone(), identity.clone());
let result_right = op(identity.clone(), x.clone());
result_left == x && result_right == x
})
}
pub fn distributive<T, F, G>(f: F, g: G) -> ClosureProperty<impl Fn((T, T, T)) -> bool>
where
F: Fn(T, T) -> T + Clone + 'static,
G: Fn(T, T) -> T + Clone + 'static,
T: PartialEq + Clone + 'static,
{
ClosureProperty::new(move |(a, b, c): (T, T, T)| {
let left = f(a.clone(), g(b.clone(), c.clone()));
let right = g(f(a.clone(), b), f(a, c));
left == right
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::execution::check;
use crate::primitives::{IntGenerator, TupleStrategy2, TupleStrategy3};
#[test]
fn test_commutative_addition() {
let property = commutative(|a: i32, b: i32| a.wrapping_add(b));
let _gen1 = IntGenerator::new(-100, 100);
let _gen2 = IntGenerator::new(-100, 100);
let tuple_gen = TupleStrategy2::<i32, i32>::new();
let result = check(tuple_gen, property);
assert!(result.is_ok());
}
#[test]
fn test_associative_addition() {
let property = associative(|a: i32, b: i32| a.wrapping_add(b));
let tuple_gen = TupleStrategy3::<i32, i32, i32>::new();
let result = check(tuple_gen, property);
assert!(result.is_ok());
}
#[test]
fn test_idempotent_abs() {
let property = idempotent(|x: i32| x.abs());
let result = check(IntGenerator::new(-100, 100), property);
assert!(result.is_ok());
}
#[test]
fn test_round_trip_string_conversion() {
let property = round_trip(
|x: i32| x.to_string(),
|s: String| s.parse::<i32>().unwrap(),
);
let result = check(IntGenerator::new(-100, 100), property);
assert!(result.is_ok());
}
#[test]
fn test_identity_addition() {
let property = has_identity(|a: i32, b: i32| a.wrapping_add(b), 0);
let result = check(IntGenerator::new(-100, 100), property);
assert!(result.is_ok());
}
}