251 lines
8.0 KiB
Rust
251 lines
8.0 KiB
Rust
use myc::ast::environment::Environment;
|
|
use myc::ast::types::Value;
|
|
|
|
#[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");
|
|
assert_eq!(format!("{}", env.run_script("(% 20 3)").unwrap()), "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");
|
|
assert_eq!(format!("{}", env.run_script("(>> 4 1)").unwrap()), "2");
|
|
assert_eq!(format!("{}", env.run_script("(and 3 1)").unwrap()), "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();
|
|
|
|
let val1_a = env1.run_script("(do (def rand (make-random 123)) (rand))").unwrap();
|
|
let val2_a = env2.run_script("(do (def rand (make-random)) (rand))").unwrap();
|
|
|
|
assert_ne!(
|
|
val1_a, val2_a,
|
|
"Environments must have isolated PRNG states"
|
|
);
|
|
|
|
let val2_b = env2.run_script("(do (def rand-same (make-random 123)) (rand-same))").unwrap();
|
|
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();
|
|
|
|
let val1 = env.run_script("(do (def rand1 (make-random 42)) (rand1))").unwrap();
|
|
let val2 = env.run_script("(do (def rand2 (make-random 42)) (rand2))").unwrap();
|
|
|
|
assert_eq!(val1, val2, "Random results must be identical for the same seed");
|
|
|
|
let val3 = env.run_script("(do (def rand3 (make-random 123)) (rand3))").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)";
|
|
|
|
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);
|
|
}
|
|
|
|
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_environments_from_shared_rtl_are_independent() {
|
|
let rtl = Environment::new().rtl();
|
|
|
|
// Two fresh environments from the same frozen RTL snapshot.
|
|
let env1 = Environment::from_rtl(rtl.clone());
|
|
let env2 = Environment::from_rtl(rtl.clone());
|
|
|
|
// Define a variable in env1 only.
|
|
env1.run_script("(do (def x 42) 0)").unwrap();
|
|
|
|
// x must not be visible in env2.
|
|
assert!(env2.run_script("x").is_err(), "User binding leaked across environments");
|
|
|
|
// RTL functions must work in both environments.
|
|
assert_eq!(format!("{}", env1.run_script("(+ 1 2)").unwrap()), "3");
|
|
assert_eq!(format!("{}", env2.run_script("(+ 1 2)").unwrap()), "3");
|
|
}
|
|
|
|
// --- Bug fixes for wildcard matches ---
|
|
|
|
/// `type-of` must return :function for native functions (e.g. `print`).
|
|
/// Previously returned :unknown because Value::Function.static_type() yields Any,
|
|
/// and the type-of match did not handle Any.
|
|
#[test]
|
|
fn test_type_of_native_function() {
|
|
let env = Environment::new();
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(type-of print)").unwrap()),
|
|
":function",
|
|
);
|
|
}
|
|
|
|
/// `type-of` must return :function for closures, not :closure.
|
|
/// Closures are functions from the user's perspective.
|
|
#[test]
|
|
fn test_type_of_closure() {
|
|
let env = Environment::new();
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(type-of (fn [x] x))").unwrap()),
|
|
":function",
|
|
);
|
|
}
|
|
|
|
/// `type-of` must return :field-accessor for field accessors.
|
|
#[test]
|
|
fn test_type_of_field_accessor() {
|
|
let env = Environment::new();
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(type-of .name)").unwrap()),
|
|
":field-accessor",
|
|
);
|
|
}
|
|
|
|
/// The `=` operator must compare tuples structurally, not return false.
|
|
#[test]
|
|
fn test_equality_tuples() {
|
|
let env = Environment::new();
|
|
// Use def bindings because vector literals in call position get flattened.
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 3]) (= a b))").unwrap()),
|
|
"true",
|
|
);
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 4]) (= a b))").unwrap()),
|
|
"false",
|
|
);
|
|
// Nested tuples
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a [[1 2] [3 4]]) (def b [[1 2] [3 4]]) (= a b))").unwrap()),
|
|
"true",
|
|
);
|
|
}
|
|
|
|
/// The `<>` operator must compare tuples structurally.
|
|
#[test]
|
|
fn test_inequality_tuples() {
|
|
let env = Environment::new();
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 3]) (<> a b))").unwrap()),
|
|
"false",
|
|
);
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 4]) (<> a b))").unwrap()),
|
|
"true",
|
|
);
|
|
}
|
|
|
|
/// The `=` operator must compare field accessors by identity.
|
|
#[test]
|
|
fn test_equality_field_accessors() {
|
|
let env = Environment::new();
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a .name) (def b .name) (= a b))").unwrap()),
|
|
"true",
|
|
);
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a .name) (def b .age) (= a b))").unwrap()),
|
|
"false",
|
|
);
|
|
}
|
|
|
|
/// The `<>` operator must compare field accessors by identity.
|
|
#[test]
|
|
fn test_inequality_field_accessors() {
|
|
let env = Environment::new();
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a .name) (def b .age) (<> a b))").unwrap()),
|
|
"true",
|
|
);
|
|
assert_eq!(
|
|
format!("{}", env.run_script("(do (def a .name) (def b .name) (<> a b))").unwrap()),
|
|
"false",
|
|
);
|
|
}
|
|
|
|
/// Arithmetic operators with incompatible types (e.g. Float - Record)
|
|
/// must be caught at compile time, not silently return Void at runtime.
|
|
#[test]
|
|
fn test_arithmetic_type_mismatch_is_compile_error() {
|
|
let env = Environment::new();
|
|
|
|
// Float - Record must fail
|
|
let res = env.run_script("(do (def r {:price 42.0}) (- 10.0 r))");
|
|
assert!(
|
|
res.is_err(),
|
|
"Float - Record should be a compile error, not silently return Void"
|
|
);
|
|
}
|