Files
komp_ac/steel_decimal/tests/integration_tests.rs
2025-07-07 13:32:06 +02:00

316 lines
10 KiB
Rust

use rstest::*;
use steel_decimal::{SteelDecimal, FunctionRegistry, FunctionRegistryBuilder};
use steel::steel_vm::engine::Engine;
use steel::rvals::SteelVal;
use std::collections::HashMap;
#[fixture]
fn steel_decimal_instance() -> SteelDecimal {
SteelDecimal::new()
}
#[fixture]
fn vm_with_functions() -> Engine {
let mut vm = Engine::new();
FunctionRegistry::register_all(&mut vm);
vm
}
// End-to-End Transformation and Execution Tests
#[rstest]
#[case("(+ 1.5 2.3)", "3.8")]
#[case("(- 10 4)", "6")]
#[case("(* 3 4)", "12")]
#[case("(/ 15 3)", "5")]
fn test_end_to_end_basic_arithmetic(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) {
let result = steel_decimal_instance.parse_and_execute(input).unwrap();
// Should return a single value
assert_eq!(result.len(), 1);
// Extract the string value
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), expected);
} else {
panic!("Expected StringV, got {:?}", result[0]);
}
}
#[rstest]
#[case("(+ (* 2 3) (/ 12 4))", "9")]
#[case("(- (+ 10 5) (* 2 3))", "9")]
#[case("(* (+ 2 3) (- 8 3))", "25")]
fn test_end_to_end_complex_expressions(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) {
let result = steel_decimal_instance.parse_and_execute(input).unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), expected);
} else {
panic!("Expected StringV, got {:?}", result[0]);
}
}
// Test with variables
#[rstest]
fn test_end_to_end_with_variables() {
let mut variables = HashMap::new();
variables.insert("x".to_string(), "10".to_string());
variables.insert("y".to_string(), "5".to_string());
let steel_decimal_instance = SteelDecimal::with_variables(variables);
let result = steel_decimal_instance.parse_and_execute("(+ $x $y)").unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), "15");
} else {
panic!("Expected StringV, got {:?}", result[0]);
}
}
// Test transformation only
#[rstest]
#[case("(+ 1 2)", "(decimal-add \"1\" \"2\")")]
#[case("(* $x $y)", "(decimal-mul (get-var \"x\") (get-var \"y\"))")]
#[case("(sqrt 16)", "(decimal-sqrt \"16\")")]
fn test_transformation_only(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) {
let result = steel_decimal_instance.transform(input);
assert_eq!(result, expected);
}
// Test function registration
#[rstest]
fn test_function_registration_with_vm() {
let mut vm = Engine::new();
FunctionRegistry::register_all(&mut vm);
// Test that we can execute decimal functions directly
let script = r#"(decimal-add "2.5" "3.7")"#;
let result = vm.compile_and_run_raw_program(script.to_string()).unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), "6.2");
} else {
panic!("Expected StringV, got {:?}", result[0]);
}
}
// Test selective function registration
#[rstest]
fn test_selective_function_registration() {
let mut vm = Engine::new();
FunctionRegistryBuilder::new()
.basic_arithmetic(true)
.advanced_math(false)
.trigonometric(false)
.comparison(true)
.utility(false)
.constants(true)
.financial(false)
.conversion(true)
.register(&mut vm);
// Basic arithmetic should work
let script = r#"(decimal-add "1" "2")"#;
let result = vm.compile_and_run_raw_program(script.to_string());
assert!(result.is_ok());
// Constants should work
let script = r#"(decimal-pi)"#;
let result = vm.compile_and_run_raw_program(script.to_string());
assert!(result.is_ok());
// Comparison should work
let script = r#"(decimal-gt "5" "3")"#;
let result = vm.compile_and_run_raw_program(script.to_string());
assert!(result.is_ok());
}
// Test variable registration
#[rstest]
fn test_variable_registration() {
let mut vm = Engine::new();
let mut variables = HashMap::new();
variables.insert("test_var".to_string(), "42.5".to_string());
variables.insert("another_var".to_string(), "10.0".to_string());
FunctionRegistryBuilder::new()
.basic_arithmetic(true)
.with_variables(variables)
.register(&mut vm);
// Test getting a variable
let script = r#"(get-var "test_var")"#;
let result = vm.compile_and_run_raw_program(script.to_string()).unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), "42.5");
} else {
panic!("Expected StringV, got {:?}", result[0]);
}
// Test checking if variable exists
let script = r#"(has-var? "test_var")"#;
let result = vm.compile_and_run_raw_program(script.to_string()).unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::BoolV(b) = &result[0] {
assert!(b);
} else {
panic!("Expected BoolV, got {:?}", result[0]);
}
// Test non-existent variable
let script = r#"(has-var? "nonexistent")"#;
let result = vm.compile_and_run_raw_program(script.to_string()).unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::BoolV(b) = &result[0] {
assert!(!b);
} else {
panic!("Expected BoolV, got {:?}", result[0]);
}
}
// Test script validation
#[rstest]
#[case("(+ 1 2)", true, "Valid script")]
#[case("(+ 1 2", false, "Unbalanced parentheses")]
#[case("(+ $undefined_var 2)", false, "Undefined variable")]
fn test_script_validation(#[case] script: &str, #[case] should_be_valid: bool, #[case] _description: &str) {
let steel_decimal_instance = SteelDecimal::new();
let result = steel_decimal_instance.validate_script(script);
if should_be_valid {
assert!(result.is_ok(), "Script should be valid: {}", script);
} else {
assert!(result.is_err(), "Script should be invalid: {}", script);
}
}
// Test with defined variables
#[rstest]
fn test_script_validation_with_variables() {
let mut variables = HashMap::new();
variables.insert("x".to_string(), "10".to_string());
variables.insert("y".to_string(), "20".to_string());
let steel_decimal_instance = SteelDecimal::with_variables(variables);
// Should be valid now
assert!(steel_decimal_instance.validate_script("(+ $x $y)").is_ok());
// Still invalid variable
assert!(steel_decimal_instance.validate_script("(+ $x $undefined)").is_err());
}
// Test dependency extraction
#[rstest]
#[case("(+ $x $y)", vec!["x", "y"])]
#[case("(* $price $quantity)", vec!["price", "quantity"])]
#[case("(+ 1 2)", vec![])]
fn test_dependency_extraction(steel_decimal_instance: SteelDecimal, #[case] script: &str, #[case] expected_deps: Vec<&str>) {
let deps = steel_decimal_instance.extract_dependencies(script);
let expected: std::collections::HashSet<String> = expected_deps.into_iter().map(|s| s.to_string()).collect();
assert_eq!(deps, expected);
}
// Test adding variables dynamically
#[rstest]
fn test_dynamic_variable_addition() {
let mut steel_decimal_instance = SteelDecimal::new();
// Should fail initially
assert!(steel_decimal_instance.validate_script("(+ $x $y)").is_err());
// Add variables
steel_decimal_instance.add_variable("x".to_string(), "10".to_string());
steel_decimal_instance.add_variable("y".to_string(), "20".to_string());
// Should work now
assert!(steel_decimal_instance.validate_script("(+ $x $y)").is_ok());
// Test execution
let result = steel_decimal_instance.parse_and_execute("(+ $x $y)").unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), "30");
}
}
// Test error handling
#[rstest]
fn test_error_handling() {
let steel_decimal_instance = SteelDecimal::new();
// Test division by zero
let result = steel_decimal_instance.parse_and_execute("(/ 10 0)");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Division by zero"));
}
// Test complex mathematical expressions
#[rstest]
#[case("(sqrt (+ (* 3 3) (* 4 4)))", "5")] // Pythagorean theorem: sqrt(3² + 4²) = 5
#[case("(abs (- 10 15))", "5")] // |10 - 15| = 5
#[case("(max (min 10 5) 3)", "5")] // max(min(10, 5), 3) = max(5, 3) = 5
fn test_complex_mathematical_expressions(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) {
let result = steel_decimal_instance.parse_and_execute(input).unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
if input.contains("sqrt") {
// For sqrt, just check it starts with the expected digit due to high precision
assert!(s.to_string().starts_with(expected), "Expected sqrt result to start with {}, got: {}", expected, s);
} else {
assert_eq!(s.to_string(), expected);
}
} else {
panic!("Expected StringV, got {:?}", result[0]);
}
}
// Test financial calculations
#[rstest]
fn test_financial_calculations() {
let steel_decimal_instance = SteelDecimal::new();
// Test percentage calculation
let result = steel_decimal_instance.parse_and_execute("(decimal-percentage \"1000\" \"15\")").unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), "150");
}
// Test compound interest (simplified) - expect precision from rust_decimal
let result = steel_decimal_instance.parse_and_execute("(decimal-compound \"1000\" \"0.05\" \"2\")").unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert_eq!(s.to_string(), "1102.5000"); // 1000 * (1.05)^2 = 1102.5000
}
}
// Test constants
#[rstest]
fn test_constants_integration() {
let steel_decimal_instance = SteelDecimal::new();
// Test pi
let result = steel_decimal_instance.parse_and_execute("(decimal-pi)").unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert!(s.to_string().starts_with("3.14159"));
}
// Test using pi in calculation
let result = steel_decimal_instance.parse_and_execute("(decimal-mul (decimal-pi) \"2\")").unwrap();
assert_eq!(result.len(), 1);
if let SteelVal::StringV(s) = &result[0] {
assert!(s.to_string().starts_with("6.28"));
}
}