#![allow(clippy::approx_constant)]
use mathlex::ast::{BinaryOp, Expression, MathConstant, MathFloat, UnaryOp};
use mathlex::parser::parse;
use proptest::prelude::*;
fn arb_expression() -> impl Strategy<Value = Expression> {
let leaf = prop_oneof![
(-1000i64..1000).prop_map(Expression::Integer),
(-1000.0f64..1000.0)
.prop_filter("No NaN", |f| !f.is_nan())
.prop_map(|f| Expression::Float(MathFloat::from(f))),
"[a-z]".prop_map(Expression::Variable),
prop_oneof![
Just(Expression::Constant(MathConstant::Pi)),
Just(Expression::Constant(MathConstant::E)),
Just(Expression::Constant(MathConstant::I)),
],
];
leaf.prop_recursive(
4, 64, 10, |inner| {
prop_oneof![
(arb_binary_op(), inner.clone(), inner.clone()).prop_map(|(op, left, right)| {
Expression::Binary {
op,
left: Box::new(left),
right: Box::new(right),
}
}),
(arb_unary_op(), inner.clone()).prop_map(|(op, operand)| Expression::Unary {
op,
operand: Box::new(operand),
}),
(
arb_function_name(),
prop::collection::vec(inner.clone(), 1..=2)
)
.prop_map(|(name, args)| Expression::Function { name, args }),
prop::collection::vec(inner.clone(), 1..=3).prop_map(Expression::Vector),
]
},
)
}
fn arb_binary_op() -> impl Strategy<Value = BinaryOp> {
prop_oneof![
Just(BinaryOp::Add),
Just(BinaryOp::Sub),
Just(BinaryOp::Mul),
Just(BinaryOp::Div),
Just(BinaryOp::Pow),
Just(BinaryOp::Mod),
]
}
fn arb_unary_op() -> impl Strategy<Value = UnaryOp> {
prop_oneof![
Just(UnaryOp::Neg),
Just(UnaryOp::Pos),
Just(UnaryOp::Factorial),
]
}
fn arb_function_name() -> impl Strategy<Value = String> {
prop_oneof![
Just("sin".to_string()),
Just("cos".to_string()),
Just("tan".to_string()),
Just("log".to_string()),
Just("exp".to_string()),
Just("sqrt".to_string()),
Just("abs".to_string()),
Just("max".to_string()),
Just("min".to_string()),
]
}
proptest! {
#[test]
fn prop_clone_equals_original(expr in arb_expression()) {
let cloned = expr.clone();
prop_assert_eq!(expr, cloned);
}
#[test]
fn prop_depth_at_least_one(expr in arb_expression()) {
prop_assert!(expr.depth() >= 1);
}
#[test]
fn prop_node_count_at_least_one(expr in arb_expression()) {
prop_assert!(expr.node_count() >= 1);
}
#[test]
fn prop_depth_le_node_count(expr in arb_expression()) {
prop_assert!(expr.depth() <= expr.node_count());
}
#[test]
fn prop_find_variables_no_false_positives(expr in arb_expression()) {
let vars = expr.find_variables();
let expr_str = format!("{:?}", expr);
for var in vars {
prop_assert!(
expr_str.contains(&format!("Variable(\"{}\")", var)),
"Variable {} not found in expression debug output",
var
);
}
}
#[test]
fn prop_find_functions_no_false_positives(expr in arb_expression()) {
let funcs = expr.find_functions();
let expr_str = format!("{:?}", expr);
for func in funcs {
prop_assert!(
expr_str.contains(&format!("name: \"{}\"", func)),
"Function {} not found in expression debug output",
func
);
}
}
#[test]
fn prop_find_constants_no_false_positives(expr in arb_expression()) {
let consts = expr.find_constants();
let expr_str = format!("{:?}", expr);
for constant in consts {
let const_str = format!("{:?}", constant);
prop_assert!(
expr_str.contains(&const_str),
"Constant {} not found in expression debug output",
const_str
);
}
}
#[test]
fn prop_display_non_empty(expr in arb_expression()) {
let display_str = format!("{}", expr);
prop_assert!(!display_str.is_empty());
}
#[test]
fn prop_display_parseable(expr in arb_expression()) {
let display_str = format!("{}", expr);
match parse(&display_str) {
Ok(_parsed_expr) => {
}
Err(_) => {
}
}
}
#[test]
fn prop_substitute_non_matching_identity(expr in arb_expression()) {
let non_existent_var = "nonexistent_variable_xyz";
let replacement = Expression::Integer(999);
let result = expr.substitute(non_existent_var, &replacement);
prop_assert_eq!(expr, result);
}
#[test]
fn prop_double_negation_depth(expr in arb_expression()) {
let neg_once = Expression::Unary {
op: UnaryOp::Neg,
operand: Box::new(expr.clone()),
};
let neg_twice = Expression::Unary {
op: UnaryOp::Neg,
operand: Box::new(neg_once.clone()),
};
prop_assert_eq!(neg_once.depth(), expr.depth() + 1);
prop_assert_eq!(neg_twice.depth(), expr.depth() + 2);
}
#[test]
fn prop_binary_depth(
left in arb_expression(),
right in arb_expression(),
op in arb_binary_op()
) {
let binary = Expression::Binary {
op,
left: Box::new(left.clone()),
right: Box::new(right.clone()),
};
let expected_depth = 1 + left.depth().max(right.depth());
prop_assert_eq!(binary.depth(), expected_depth);
}
#[test]
fn prop_binary_node_count(
left in arb_expression(),
right in arb_expression(),
op in arb_binary_op()
) {
let binary = Expression::Binary {
op,
left: Box::new(left.clone()),
right: Box::new(right.clone()),
};
let expected_count = 1 + left.node_count() + right.node_count();
prop_assert_eq!(binary.node_count(), expected_count);
}
#[test]
fn prop_vector_depth(elements in prop::collection::vec(arb_expression(), 1..=5)) {
let vector = Expression::Vector(elements.clone());
let max_element_depth = elements.iter().map(|e| e.depth()).max().unwrap_or(0);
let expected_depth = if elements.is_empty() { 1 } else { 1 + max_element_depth };
prop_assert_eq!(vector.depth(), expected_depth);
}
#[test]
fn prop_vector_node_count(elements in prop::collection::vec(arb_expression(), 1..=5)) {
let vector = Expression::Vector(elements.clone());
let element_count_sum: usize = elements.iter().map(|e| e.node_count()).sum();
let expected_count = 1 + element_count_sum;
prop_assert_eq!(vector.node_count(), expected_count);
}
#[test]
fn prop_hash_consistency(expr in arb_expression()) {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let cloned = expr.clone();
let mut hasher1 = DefaultHasher::new();
expr.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
cloned.hash(&mut hasher2);
let hash2 = hasher2.finish();
prop_assert_eq!(hash1, hash2);
}
#[test]
fn prop_find_variables_consistent(expr in arb_expression()) {
let vars1 = expr.find_variables();
let vars2 = expr.find_variables();
prop_assert_eq!(vars1, vars2);
}
#[test]
fn prop_find_functions_consistent(expr in arb_expression()) {
let funcs1 = expr.find_functions();
let funcs2 = expr.find_functions();
prop_assert_eq!(funcs1, funcs2);
}
#[test]
fn prop_find_constants_consistent(expr in arb_expression()) {
let consts1 = expr.find_constants();
let consts2 = expr.find_constants();
prop_assert_eq!(consts1, consts2);
}
#[test]
fn prop_math_float_transitivity(
a in -1000.0f64..1000.0,
b in -1000.0f64..1000.0,
c in -1000.0f64..1000.0
) {
let fa = MathFloat::from(a);
let fb = MathFloat::from(b);
let fc = MathFloat::from(c);
if fa == fb && fb == fc {
prop_assert_eq!(fa, fc);
}
}
#[test]
fn prop_math_float_symmetry(a in -1000.0f64..1000.0, b in -1000.0f64..1000.0) {
let fa = MathFloat::from(a);
let fb = MathFloat::from(b);
if fa == fb {
prop_assert_eq!(fb, fa);
}
}
#[test]
fn prop_math_float_reflexivity(a in -1000.0f64..1000.0) {
let fa = MathFloat::from(a);
prop_assert_eq!(fa, fa);
}
#[test]
fn prop_math_float_roundtrip(f in -1000.0f64..1000.0) {
let math_float = MathFloat::from(f);
let roundtrip: f64 = math_float.into();
prop_assert!((f - roundtrip).abs() < 1e-10);
}
}
#[test]
fn test_empty_vector_depth() {
let expr = Expression::Vector(vec![]);
assert_eq!(expr.depth(), 1);
}
#[test]
fn test_empty_vector_node_count() {
let expr = Expression::Vector(vec![]);
assert_eq!(expr.node_count(), 1);
}
#[test]
fn test_single_variable_find_variables() {
let expr = Expression::Variable("x".to_string());
let vars = expr.find_variables();
assert_eq!(vars.len(), 1);
assert!(vars.contains("x"));
}
#[test]
fn test_no_functions_in_leaf() {
let expr = Expression::Integer(42);
assert_eq!(expr.find_functions().len(), 0);
}
#[test]
fn test_no_constants_in_integer() {
let expr = Expression::Integer(42);
assert_eq!(expr.find_constants().len(), 0);
}
#[test]
fn test_pi_constant_found() {
let expr = Expression::Constant(MathConstant::Pi);
let consts = expr.find_constants();
assert_eq!(consts.len(), 1);
assert!(consts.contains(&MathConstant::Pi));
}
#[test]
fn test_display_integer() {
let expr = Expression::Integer(42);
assert_eq!(format!("{}", expr), "42");
}
#[test]
fn test_display_variable() {
let expr = Expression::Variable("x".to_string());
assert_eq!(format!("{}", expr), "x");
}
#[test]
fn test_clone_integer() {
let expr = Expression::Integer(42);
let cloned = expr.clone();
assert_eq!(expr, cloned);
}
#[test]
fn test_hash_equal_expressions() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let expr1 = Expression::Integer(42);
let expr2 = Expression::Integer(42);
let mut hasher1 = DefaultHasher::new();
expr1.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
expr2.hash(&mut hasher2);
let hash2 = hasher2.finish();
assert_eq!(hash1, hash2);
}