311 lines
10 KiB
Rust
311 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] {
|
|
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)
|
|
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.5");
|
|
}
|
|
}
|
|
|
|
// 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"));
|
|
}
|
|
}
|