bottest/tests/unit/math_functions.rs

538 lines
15 KiB
Rust
Raw Permalink Normal View History

2025-12-06 11:05:57 -03:00
//! Unit tests for BASIC math functions from botserver
//!
//! These tests create a Rhai engine, register math functions the same way
//! botserver does, and verify they work correctly.
use rhai::Engine;
// =============================================================================
// ABS Function Tests
// =============================================================================
#[test]
fn test_abs_positive() {
let mut engine = Engine::new();
engine.register_fn("ABS", |n: i64| -> i64 { n.abs() });
engine.register_fn("ABS", |n: f64| -> f64 { n.abs() });
let result: i64 = engine.eval("ABS(42)").unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_abs_negative() {
let mut engine = Engine::new();
engine.register_fn("ABS", |n: i64| -> i64 { n.abs() });
let result: i64 = engine.eval("ABS(-42)").unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_abs_zero() {
let mut engine = Engine::new();
engine.register_fn("ABS", |n: i64| -> i64 { n.abs() });
let result: i64 = engine.eval("ABS(0)").unwrap();
assert_eq!(result, 0);
}
#[test]
fn test_abs_float() {
let mut engine = Engine::new();
engine.register_fn("ABS", |n: f64| -> f64 { n.abs() });
let result: f64 = engine.eval("ABS(-3.14)").unwrap();
assert!((result - 3.14).abs() < f64::EPSILON);
}
// =============================================================================
// ROUND Function Tests
// =============================================================================
#[test]
fn test_round_up() {
let mut engine = Engine::new();
engine.register_fn("ROUND", |n: f64| -> i64 { n.round() as i64 });
let result: i64 = engine.eval("ROUND(3.7)").unwrap();
assert_eq!(result, 4);
}
#[test]
fn test_round_down() {
let mut engine = Engine::new();
engine.register_fn("ROUND", |n: f64| -> i64 { n.round() as i64 });
let result: i64 = engine.eval("ROUND(3.2)").unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_round_half() {
let mut engine = Engine::new();
engine.register_fn("ROUND", |n: f64| -> i64 { n.round() as i64 });
let result: i64 = engine.eval("ROUND(3.5)").unwrap();
assert_eq!(result, 4);
}
#[test]
fn test_round_negative() {
let mut engine = Engine::new();
engine.register_fn("ROUND", |n: f64| -> i64 { n.round() as i64 });
let result: i64 = engine.eval("ROUND(-3.7)").unwrap();
assert_eq!(result, -4);
}
// =============================================================================
// INT / FIX Function Tests (Truncation)
// =============================================================================
#[test]
fn test_int_positive() {
let mut engine = Engine::new();
engine.register_fn("INT", |n: f64| -> i64 { n.trunc() as i64 });
let result: i64 = engine.eval("INT(3.9)").unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_int_negative() {
let mut engine = Engine::new();
engine.register_fn("INT", |n: f64| -> i64 { n.trunc() as i64 });
let result: i64 = engine.eval("INT(-3.9)").unwrap();
assert_eq!(result, -3);
}
#[test]
fn test_fix_alias() {
let mut engine = Engine::new();
engine.register_fn("FIX", |n: f64| -> i64 { n.trunc() as i64 });
let result: i64 = engine.eval("FIX(7.8)").unwrap();
assert_eq!(result, 7);
}
// =============================================================================
// FLOOR / CEIL Function Tests
// =============================================================================
#[test]
fn test_floor_positive() {
let mut engine = Engine::new();
engine.register_fn("FLOOR", |n: f64| -> i64 { n.floor() as i64 });
let result: i64 = engine.eval("FLOOR(3.9)").unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_floor_negative() {
let mut engine = Engine::new();
engine.register_fn("FLOOR", |n: f64| -> i64 { n.floor() as i64 });
let result: i64 = engine.eval("FLOOR(-3.1)").unwrap();
assert_eq!(result, -4);
}
#[test]
fn test_ceil_positive() {
let mut engine = Engine::new();
engine.register_fn("CEIL", |n: f64| -> i64 { n.ceil() as i64 });
let result: i64 = engine.eval("CEIL(3.1)").unwrap();
assert_eq!(result, 4);
}
#[test]
fn test_ceil_negative() {
let mut engine = Engine::new();
engine.register_fn("CEIL", |n: f64| -> i64 { n.ceil() as i64 });
let result: i64 = engine.eval("CEIL(-3.9)").unwrap();
assert_eq!(result, -3);
}
// =============================================================================
// MIN / MAX Function Tests
// =============================================================================
#[test]
fn test_max_basic() {
let mut engine = Engine::new();
engine.register_fn("MAX", |a: i64, b: i64| -> i64 { a.max(b) });
let result: i64 = engine.eval("MAX(5, 10)").unwrap();
assert_eq!(result, 10);
}
#[test]
fn test_max_first_larger() {
let mut engine = Engine::new();
engine.register_fn("MAX", |a: i64, b: i64| -> i64 { a.max(b) });
let result: i64 = engine.eval("MAX(10, 5)").unwrap();
assert_eq!(result, 10);
}
#[test]
fn test_max_equal() {
let mut engine = Engine::new();
engine.register_fn("MAX", |a: i64, b: i64| -> i64 { a.max(b) });
let result: i64 = engine.eval("MAX(7, 7)").unwrap();
assert_eq!(result, 7);
}
#[test]
fn test_max_negative() {
let mut engine = Engine::new();
engine.register_fn("MAX", |a: i64, b: i64| -> i64 { a.max(b) });
let result: i64 = engine.eval("MAX(-5, -10)").unwrap();
assert_eq!(result, -5);
}
#[test]
fn test_min_basic() {
let mut engine = Engine::new();
engine.register_fn("MIN", |a: i64, b: i64| -> i64 { a.min(b) });
let result: i64 = engine.eval("MIN(5, 10)").unwrap();
assert_eq!(result, 5);
}
#[test]
fn test_min_first_smaller() {
let mut engine = Engine::new();
engine.register_fn("MIN", |a: i64, b: i64| -> i64 { a.min(b) });
let result: i64 = engine.eval("MIN(3, 8)").unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_min_negative() {
let mut engine = Engine::new();
engine.register_fn("MIN", |a: i64, b: i64| -> i64 { a.min(b) });
let result: i64 = engine.eval("MIN(-5, -10)").unwrap();
assert_eq!(result, -10);
}
// =============================================================================
// MOD Function Tests
// =============================================================================
#[test]
fn test_mod_basic() {
let mut engine = Engine::new();
engine.register_fn("MOD", |a: i64, b: i64| -> i64 { a % b });
let result: i64 = engine.eval("MOD(17, 5)").unwrap();
assert_eq!(result, 2);
}
#[test]
fn test_mod_no_remainder() {
let mut engine = Engine::new();
engine.register_fn("MOD", |a: i64, b: i64| -> i64 { a % b });
let result: i64 = engine.eval("MOD(10, 5)").unwrap();
assert_eq!(result, 0);
}
#[test]
fn test_mod_smaller_dividend() {
let mut engine = Engine::new();
engine.register_fn("MOD", |a: i64, b: i64| -> i64 { a % b });
let result: i64 = engine.eval("MOD(3, 10)").unwrap();
assert_eq!(result, 3);
}
// =============================================================================
// SGN Function Tests
// =============================================================================
#[test]
fn test_sgn_positive() {
let mut engine = Engine::new();
engine.register_fn("SGN", |n: i64| -> i64 { n.signum() });
let result: i64 = engine.eval("SGN(42)").unwrap();
assert_eq!(result, 1);
}
#[test]
fn test_sgn_negative() {
let mut engine = Engine::new();
engine.register_fn("SGN", |n: i64| -> i64 { n.signum() });
let result: i64 = engine.eval("SGN(-42)").unwrap();
assert_eq!(result, -1);
}
#[test]
fn test_sgn_zero() {
let mut engine = Engine::new();
engine.register_fn("SGN", |n: i64| -> i64 { n.signum() });
let result: i64 = engine.eval("SGN(0)").unwrap();
assert_eq!(result, 0);
}
// =============================================================================
// SQRT / SQR Function Tests
// =============================================================================
#[test]
fn test_sqrt_perfect_square() {
let mut engine = Engine::new();
engine.register_fn("SQRT", |n: f64| -> f64 { n.sqrt() });
let result: f64 = engine.eval("SQRT(16.0)").unwrap();
assert!((result - 4.0).abs() < f64::EPSILON);
}
#[test]
fn test_sqrt_non_perfect() {
let mut engine = Engine::new();
engine.register_fn("SQRT", |n: f64| -> f64 { n.sqrt() });
let result: f64 = engine.eval("SQRT(2.0)").unwrap();
assert!((result - std::f64::consts::SQRT_2).abs() < 0.00001);
}
#[test]
fn test_sqr_alias() {
let mut engine = Engine::new();
engine.register_fn("SQR", |n: f64| -> f64 { n.sqrt() });
let result: f64 = engine.eval("SQR(25.0)").unwrap();
assert!((result - 5.0).abs() < f64::EPSILON);
}
// =============================================================================
// POW Function Tests
// =============================================================================
#[test]
fn test_pow_basic() {
let mut engine = Engine::new();
engine.register_fn("POW", |base: f64, exp: f64| -> f64 { base.powf(exp) });
let result: f64 = engine.eval("POW(2.0, 10.0)").unwrap();
assert!((result - 1024.0).abs() < f64::EPSILON);
}
#[test]
fn test_pow_zero_exponent() {
let mut engine = Engine::new();
engine.register_fn("POW", |base: f64, exp: f64| -> f64 { base.powf(exp) });
let result: f64 = engine.eval("POW(5.0, 0.0)").unwrap();
assert!((result - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_pow_square_root() {
let mut engine = Engine::new();
engine.register_fn("POW", |base: f64, exp: f64| -> f64 { base.powf(exp) });
let result: f64 = engine.eval("POW(9.0, 0.5)").unwrap();
assert!((result - 3.0).abs() < 0.00001);
}
// =============================================================================
// LOG / LOG10 / EXP Function Tests
// =============================================================================
#[test]
fn test_log_e() {
let mut engine = Engine::new();
engine.register_fn("LOG", |n: f64| -> f64 { n.ln() });
let e = std::f64::consts::E;
let result: f64 = engine.eval(&format!("LOG({})", e)).unwrap();
assert!((result - 1.0).abs() < 0.00001);
}
#[test]
fn test_log10_hundred() {
let mut engine = Engine::new();
engine.register_fn("LOG10", |n: f64| -> f64 { n.log10() });
let result: f64 = engine.eval("LOG10(100.0)").unwrap();
assert!((result - 2.0).abs() < f64::EPSILON);
}
#[test]
fn test_exp_zero() {
let mut engine = Engine::new();
engine.register_fn("EXP", |n: f64| -> f64 { n.exp() });
let result: f64 = engine.eval("EXP(0.0)").unwrap();
assert!((result - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_exp_one() {
let mut engine = Engine::new();
engine.register_fn("EXP", |n: f64| -> f64 { n.exp() });
let result: f64 = engine.eval("EXP(1.0)").unwrap();
assert!((result - std::f64::consts::E).abs() < 0.00001);
}
// =============================================================================
// Trigonometric Function Tests
// =============================================================================
#[test]
fn test_sin_zero() {
let mut engine = Engine::new();
engine.register_fn("SIN", |n: f64| -> f64 { n.sin() });
let result: f64 = engine.eval("SIN(0.0)").unwrap();
assert!((result - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_cos_zero() {
let mut engine = Engine::new();
engine.register_fn("COS", |n: f64| -> f64 { n.cos() });
let result: f64 = engine.eval("COS(0.0)").unwrap();
assert!((result - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_tan_zero() {
let mut engine = Engine::new();
engine.register_fn("TAN", |n: f64| -> f64 { n.tan() });
let result: f64 = engine.eval("TAN(0.0)").unwrap();
assert!((result - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_pi_constant() {
let mut engine = Engine::new();
engine.register_fn("PI", || -> f64 { std::f64::consts::PI });
let result: f64 = engine.eval("PI()").unwrap();
assert!((result - std::f64::consts::PI).abs() < f64::EPSILON);
}
// =============================================================================
// VAL Function Tests (String to Number)
// =============================================================================
#[test]
fn test_val_integer() {
let mut engine = Engine::new();
engine.register_fn("VAL", |s: &str| -> f64 {
s.trim().parse::<f64>().unwrap_or(0.0)
});
let result: f64 = engine.eval(r#"VAL("42")"#).unwrap();
assert!((result - 42.0).abs() < f64::EPSILON);
}
#[test]
fn test_val_decimal() {
let mut engine = Engine::new();
engine.register_fn("VAL", |s: &str| -> f64 {
s.trim().parse::<f64>().unwrap_or(0.0)
});
let result: f64 = engine.eval(r#"VAL("3.14")"#).unwrap();
assert!((result - 3.14).abs() < f64::EPSILON);
}
#[test]
fn test_val_negative() {
let mut engine = Engine::new();
engine.register_fn("VAL", |s: &str| -> f64 {
s.trim().parse::<f64>().unwrap_or(0.0)
});
let result: f64 = engine.eval(r#"VAL("-17")"#).unwrap();
assert!((result - (-17.0)).abs() < f64::EPSILON);
}
#[test]
fn test_val_invalid_returns_zero() {
let mut engine = Engine::new();
engine.register_fn("VAL", |s: &str| -> f64 {
s.trim().parse::<f64>().unwrap_or(0.0)
});
let result: f64 = engine.eval(r#"VAL("abc")"#).unwrap();
assert!((result - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_val_with_whitespace() {
let mut engine = Engine::new();
engine.register_fn("VAL", |s: &str| -> f64 {
s.trim().parse::<f64>().unwrap_or(0.0)
});
let result: f64 = engine.eval(r#"VAL(" 42 ")"#).unwrap();
assert!((result - 42.0).abs() < f64::EPSILON);
}
// =============================================================================
// Combined Math Expression Tests
// =============================================================================
#[test]
fn test_combined_abs_sqrt() {
let mut engine = Engine::new();
engine.register_fn("ABS", |n: f64| -> f64 { n.abs() });
engine.register_fn("SQRT", |n: f64| -> f64 { n.sqrt() });
// SQRT(ABS(-16)) should be 4
let result: f64 = engine.eval("SQRT(ABS(-16.0))").unwrap();
assert!((result - 4.0).abs() < f64::EPSILON);
}
#[test]
fn test_combined_round_after_division() {
let mut engine = Engine::new();
engine.register_fn("ROUND", |n: f64| -> i64 { n.round() as i64 });
// ROUND(10.0 / 3.0) should be 3
let result: i64 = engine.eval("ROUND(10.0 / 3.0)").unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_combined_max_of_abs() {
let mut engine = Engine::new();
engine.register_fn("ABS", |n: i64| -> i64 { n.abs() });
engine.register_fn("MAX", |a: i64, b: i64| -> i64 { a.max(b) });
// MAX(ABS(-5), ABS(-10)) should be 10
let result: i64 = engine.eval("MAX(ABS(-5), ABS(-10))").unwrap();
assert_eq!(result, 10);
}
#[test]
fn test_arithmetic_expression() {
let engine = Engine::new();
// Test standard arithmetic without custom functions
let result: i64 = engine.eval("2 + 3 * 4").unwrap();
assert_eq!(result, 14); // Verify operator precedence
let result: i64 = engine.eval("(2 + 3) * 4").unwrap();
assert_eq!(result, 20);
}