#[cfg(test)] mod tests { use crate::ast::environment::Environment; use crate::ast::nodes::UntypedKind; use crate::ast::parser::Parser; use crate::ast::types::Value; #[test] fn test_parse_integer_constant() { let source = "123"; let mut parser = Parser::new(source).expect("Failed to create parser"); let ast = parser.parse_expression().expect("Failed to parse"); if let UntypedKind::Constant(Value::Int(val)) = ast.kind { assert_eq!(val, 123); } else { panic!("Expected Integer constant, got {:?}", ast.kind); } } #[test] fn test_parse_negative_integer() { let source = "-42"; let mut parser = Parser::new(source).expect("Failed to create parser"); let ast = parser.parse_expression().expect("Failed to parse"); if let UntypedKind::Constant(Value::Int(val)) = ast.kind { assert_eq!(val, -42); } else { panic!("Expected Integer constant, got {:?}", ast.kind); } } #[test] fn test_parse_float_constant() { let source = "123.45"; let mut parser = Parser::new(source).expect("Failed to create parser"); let ast = parser.parse_expression().expect("Failed to parse"); if let UntypedKind::Constant(Value::Float(val)) = ast.kind { assert_eq!(val, 123.45); } else { panic!("Expected Float constant, got {:?}", ast.kind); } } #[test] fn test_parse_negative_float() { let source = "-10.5"; let mut parser = Parser::new(source).expect("Failed to create parser"); let ast = parser.parse_expression().expect("Failed to parse"); if let UntypedKind::Constant(Value::Float(val)) = ast.kind { assert_eq!(val, -10.5); } else { panic!("Expected Float constant, got {:?}", ast.kind); } } #[test] fn test_closure_modification_from_source() { let source = r#" (do (def x 10) (def f (fn [] (assign x 20))) (f) x ) "#; let env = Environment::new(); let compiled = env.compile(source).expect("Failed to compile"); let linked = env.link(compiled); let func = env.instantiate(linked); let result: Result = Ok((func.func)(vec![])); match result { Ok(Value::Int(20)) => (), Ok(val) => panic!("Expected Int(20), got {:?}", val), Err(e) => panic!("VM Error: {}", e), } } #[test] fn test_examples() { for opt in [false, true] { let results = crate::utils::tester::run_functional_tests_with_optimization(opt); for res in results { assert!( res.success, "Example {} failed at opt {}: {}", res.name, opt, res.message ); } } } #[test] fn test_debug_mode_logging() { let mut env = Environment::new(); env.optimization = false; let source = "(+ 10 20)"; let result = env.run_debug(source).expect("Failed to run debug"); let (val, logs) = result; // 1. Check value match val { Ok(Value::Int(30)) => (), _ => panic!("Expected Int(30), got {:?}", val), } // 2. Check logs (should have entries for + and constants) assert!(!logs.is_empty(), "Logs should not be empty"); // Look for typical trace patterns let has_call = logs.iter().any(|l| l.contains("CALL")); let has_const = logs.iter().any(|l| l.contains("CONST(10)")); let has_result = logs.iter().any(|l| l.contains("} -> 30")); assert!(has_call, "Logs should contain CALL"); assert!(has_const, "Logs should contain CONST(10)"); assert!(has_result, "Logs should contain result 30"); } #[test] fn test_rtl_operators() { let env = Environment::new(); // --- Arithmetic --- assert_eq!(format!("{}", env.run_script("(+ 10 20)").unwrap()), "30"); assert_eq!(format!("{}", env.run_script("(- 20 10)").unwrap()), "10"); assert_eq!(format!("{}", env.run_script("(* 10 20)").unwrap()), "200"); assert_eq!(format!("{}", env.run_script("(/ 20 10)").unwrap()), "2"); assert_eq!(format!("{}", env.run_script("(// 20 3)").unwrap()), "6"); // 20 // 3 = 6 assert_eq!(format!("{}", env.run_script("(% 20 3)").unwrap()), "2"); // 20 % 3 = 2 // --- Logic / Bitwise --- assert_eq!( format!("{}", env.run_script("(and true false)").unwrap()), "false" ); assert_eq!( format!("{}", env.run_script("(or true false)").unwrap()), "true" ); assert_eq!( format!("{}", env.run_script("(xor true false)").unwrap()), "true" ); assert_eq!( format!("{}", env.run_script("(not true)").unwrap()), "false" ); assert_eq!(format!("{}", env.run_script("(<< 1 2)").unwrap()), "4"); // 1 << 2 = 4 assert_eq!(format!("{}", env.run_script("(>> 4 1)").unwrap()), "2"); // 4 >> 1 = 2 assert_eq!(format!("{}", env.run_script("(and 3 1)").unwrap()), "1"); // 3 & 1 = 1 // --- Comparison --- assert_eq!(format!("{}", env.run_script("(= 10 10)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(= 10 20)").unwrap()), "false"); assert_eq!(format!("{}", env.run_script("(<> 10 20)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(< 10 20)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(> 10 20)").unwrap()), "false"); assert_eq!(format!("{}", env.run_script("(<= 10 10)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(>= 10 10)").unwrap()), "true"); // --- NaN --- assert_eq!(format!("{}", env.run_script("NaN").unwrap()), "NaN"); } #[test] fn test_random_isolation_between_environments() { let env1 = Environment::new(); let env2 = Environment::new(); // 1. Seed env1 env1.run_script("(seed! 123)").unwrap(); let val1_a = env1.run_script("(random)").unwrap(); // 2. env2 should have its own default seed state let val2_a = env2.run_script("(random)").unwrap(); // They are highly unlikely to be equal by default, // and seeding env1 MUST not have seeded env2. assert_ne!( val1_a, val2_a, "Environments must have isolated PRNG states" ); // 3. Seed env2 differently env2.run_script("(seed! 123)").unwrap(); let val2_b = env2.run_script("(random)").unwrap(); // After same seeding, they should match (isolated but identical seed) assert_eq!( val1_a, val2_b, "Different environments with the same seed must produce the same sequence" ); } #[test] fn test_random_seeding_determinism() { let env = Environment::new(); // 1. First run with seed 42 env.run_script("(seed! 42)").unwrap(); let val1 = env.run_script("(random)").unwrap(); // 2. Second run with same seed 42 env.run_script("(seed! 42)").unwrap(); let val2 = env.run_script("(random)").unwrap(); assert_eq!( val1, val2, "Random results must be identical for the same seed" ); // 3. Third run with different seed env.run_script("(seed! 123)").unwrap(); let val3 = env.run_script("(random)").unwrap(); assert_ne!(val1, val3, "Random results must differ for different seeds"); } #[test] fn test_now_function_not_folded() { let env = Environment::new(); let source = "(now)"; // 1. Check result type and value plausibility let result = env.run_script(source).expect("Failed to run script"); if let Value::DateTime(ts) = result { let current = chrono::Utc::now().timestamp_millis(); assert!(ts > 0); assert!(ts <= current); } else { panic!("Expected DateTime, got {:?}", result); } // 2. Verify it's NOT constant folded in the AST dump let dump = env.dump_ast(source).expect("Failed to dump AST"); assert!( dump.contains("Call"), "now() should remain a Call, not a Constant. Dump: \n{}", dump ); assert!( !dump.contains("Constant: #"), "now() should NOT be folded into a specific timestamp constant. Dump: \n{}", dump ); } #[test] fn test_date_parsing() { let env = Environment::new(); let res = env.run_script("(date \"2023-01-01\")").unwrap(); if let Value::DateTime(_) = res { // OK } else { panic!("Expected DateTime, got {:?}", res); } } #[test] fn test_dynamic_call_destructuring_underflow() { let env = Environment::new(); let source = "(do (def call-dynamic (fn [f data] (f data))) (def data [10 [20 30]]) (def x (fn [[a [b c]]] (+ a (+ b c)))) (call-dynamic x data))"; let result = env.run_script(source); if let Err(e) = &result { panic!("Failed: {}", e); } assert_eq!(format!("{}", result.unwrap()), "60"); } #[test] fn test_nested_destructuring_optimization() { let env = Environment::new(); // 1. Tuple-to-Tuple let source_tuple = "((fn [[x y]] (+ x y)) [10 20])"; assert_eq!(format!("{}", env.run_script(source_tuple).unwrap()), "30"); let dump_tuple = env.dump_ast(source_tuple).unwrap(); assert!(dump_tuple.contains("Constant: 30"), "Nested tuple should be folded to 30. Dump:\n{}", dump_tuple); // 2. Record-to-Tuple let source_record = "((fn [[x y]] (+ x y)) {:a 5 :b 7})"; assert_eq!(format!("{}", env.run_script(source_record).unwrap()), "12"); let dump_record = env.dump_ast(source_record).unwrap(); assert!(dump_record.contains("Constant: 12"), "Record-to-Tuple destructuring should be folded to 12. Dump:\n{}", dump_record); } }