use std::sync::Arc;
use blue_lang_proc::{
deep_copy, install_process_primitives, Pid, ProcState, Strategy, Supervisor, System,
};
use tatara_lisp_eval::vm::{compile_program, Budget, Chunk};
use tatara_lisp_eval::{install_lisp_stdlib_with, install_primitives, Interpreter, Value};
fn system_interp() -> Interpreter<System> {
let mut i = Interpreter::new();
install_primitives(&mut i);
install_lisp_stdlib_with(&mut i, &mut System::new());
install_process_primitives(&mut i);
i
}
fn blue(src: &str) -> Arc<Chunk> {
let forms = blue_lang_syntax::parse_program(src).expect("parse blue");
let text = forms
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join("\n");
let spanned = tatara_lisp::read_spanned(&text).expect("read");
Arc::new(compile_program(&spanned).expect("compile"))
}
fn spawn(sup: &mut Supervisor<System>, sys: &mut System, chunk: Arc<Chunk>, b: Budget) -> Pid {
let pid = sup.spawn(chunk, b, system_interp);
sys.register(pid);
pid
}
fn quantum(q: usize) -> Budget {
Budget::preemptive(q)
}
#[test]
fn a_definition_in_one_process_is_invisible_to_another() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let a = spawn(
&mut sup,
&mut sys,
blue("def secret()\n 111\nend\nsecret()"),
quantum(50),
);
let b = spawn(
&mut sup,
&mut sys,
blue("def secret()\n 222\nend\nsecret()"),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 200);
assert_eq!(
sup.state_of(a).and_then(ProcState::done_int),
Some(111),
"process a must see its OWN definition"
);
assert_eq!(
sup.state_of(b).and_then(ProcState::done_int),
Some(222),
"process b must see its own"
);
}
#[test]
fn a_process_cannot_reach_a_name_defined_in_another() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 0);
let a = spawn(
&mut sup,
&mut sys,
blue("def only_in_a()\n 1\nend\nonly_in_a()"),
quantum(50),
);
let b = spawn(&mut sup, &mut sys, blue("only_in_a()"), quantum(50));
sup.run_to_quiescence(&mut sys, 200);
assert_eq!(
sup.state_of(a).and_then(ProcState::done_int),
Some(1),
"a must succeed"
);
let state = sup.state_of(b).expect("b exists");
assert!(
state.exit_reason().is_some(),
"b must FAIL — the name is not in its environment. Got {state:?}"
);
}
#[test]
fn a_restart_discards_the_processs_bindings() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 2);
let pid = spawn(
&mut sup,
&mut sys,
blue("def poisoned()\n 1\nend\ndef spin(n)\n spin(n + 1)\nend\nspin(0)"),
Budget {
fuel: Some(120),
max_depth: None,
quantum: Some(1_000),
},
);
sup.round(&mut sys);
assert!(
sup.restarts_of(pid).is_some_and(|n| n > 0),
"it must have restarted"
);
assert_eq!(
sup.proc_binds(pid, "poisoned"),
Some(false),
"the restarted incarnation must NOT inherit the crashed one's bindings"
);
}
#[test]
fn the_binding_is_present_before_the_crash() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 0);
let pid = spawn(
&mut sup,
&mut sys,
blue("def poisoned()\n 1\nend\npoisoned()"),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 100);
assert_eq!(
sup.proc_binds(pid, "poisoned"),
Some(true),
"a completed process DOES hold its own definition — so the absence in \
the restart test measures the restart"
);
}
#[test]
fn the_cost_of_isolation_is_counted() {
let mut sys = System::new();
let mut sup: Supervisor<System> = Supervisor::new(Strategy::OneForOne, 3);
assert_eq!(sup.interpreters_built, 0);
for _ in 0..4 {
spawn(&mut sup, &mut sys, blue("1"), quantum(50));
}
assert_eq!(
sup.interpreters_built, 4,
"one per process — the same trade the test framework makes, and visible"
);
}
#[test]
fn a_copied_list_shares_no_allocation_with_the_original() {
let inner = Value::list([Value::Int(1), Value::Int(2)]);
let original = Value::list([inner, Value::Int(3)]);
let copy = deep_copy(&original);
let (Value::List(a), Value::List(b)) = (&original, ©) else {
panic!("both must be lists");
};
assert!(
!Arc::ptr_eq(a, b),
"the outer allocation must be fresh, or the two processes share it"
);
let (Value::List(ai), Value::List(bi)) = (&a[0], &b[0]) else {
panic!("both must nest a list");
};
assert!(
!Arc::ptr_eq(ai, bi),
"the NESTED allocation must be fresh too — a shallow copy shares it"
);
}
#[test]
fn a_deep_copy_preserves_the_value() {
let v = Value::list([
Value::Int(7),
Value::Str("hi".into()),
Value::Bool(true),
Value::list([Value::Int(8)]),
]);
let copy = deep_copy(&v);
assert_eq!(format!("{copy:?}"), format!("{v:?}"));
}
#[test]
fn a_closure_is_refused_rather_than_shared() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 0);
let receiver = spawn(
&mut sup,
&mut sys,
blue("def call_it(f)\n f()\nend\ncall_it(receive())"),
quantum(50),
);
spawn(
&mut sup,
&mut sys,
blue(&format!("def make()\n 1\nend\nsend({}, make)", receiver.0)),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 300);
let state = sup.state_of(receiver).expect("receiver exists");
assert!(
state.exit_reason().is_some(),
"calling the received value must FAIL — a closure must not cross the \
boundary. Got {state:?}"
);
assert_ne!(
state.done_int(),
Some(1),
"if this were 1, process b ran a closure over process a's environment"
);
}
#[test]
fn ordinary_data_still_crosses_the_boundary() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let receiver = spawn(&mut sup, &mut sys, blue("receive()"), quantum(50));
spawn(
&mut sup,
&mut sys,
blue(&format!("send({}, 5 * 9)", receiver.0)),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 200);
assert_eq!(
sup.state_of(receiver).and_then(ProcState::done_int),
Some(45)
);
}