Files
RustAst/tests/rtl.rs
T
Brummel 893d9936ed Introduce Rtl struct and from_rtl constructor
This commit refactors the `Environment` struct to better manage the Rust
Runtime Library (RTL) bootstrap state.

A new `Rtl` struct is introduced to hold a frozen snapshot of the
initial RTL state, including scopes, types, purity, values, and
documentation. This snapshot is created once after `rtl::register()`
completes.

The `Environment::new()` constructor is updated to perform a two-phase
bootstrap: first, it registers RTL functions, and then it freezes the
scope 0 and captures the RTL state into the new `Rtl` struct.

A new constructor, `Environment::from_rtl()`, is added. This allows
creating independent execution environments that all share the same
underlying RTL state. This is crucial for isolation between different
execution contexts, as user definitions and loaded modules will not leak
between environments created from the same `Rtl` snapshot.

The `Environment::list_bindings` and `Environment::list_rtl_docs`
methods are updated to use the `rtl` field for accessing the RTL scope
and documentation, reinforcing the concept of a shared, immutable RTL
state.

A new test, `test_environments_from_shared_rtl_are_independent`, is
added to verify that environments created from the same `Rtl` snapshot
are indeed independent and do not leak user-defined bindings.
2026-03-27 22:49:03 +01:00

140 lines
4.7 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");
}