use std::sync::Arc;
use blue_lang_proc::{
install_process_primitives, Event, Pid, ProcState, Readiness, 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 quantum(q: usize) -> Budget {
Budget::preemptive(q)
}
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
}
#[test]
fn a_receiver_blocks_then_wakes_with_the_message() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let receiver = spawn(&mut sup, &mut sys, blue("receive()"), quantum(50));
let sender = spawn(
&mut sup,
&mut sys,
blue(&format!("send({}, 99)", receiver.0)),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 100);
assert!(
sup.events
.iter()
.any(|e| matches!(e, Event::Blocked { pid } if *pid == receiver)),
"the receiver must have parked on an empty mailbox: {:?}",
sup.events
);
assert!(
sup.events
.iter()
.any(|e| matches!(e, Event::Woke { pid } if *pid == receiver)),
"and been woken by the message: {:?}",
sup.events
);
assert_eq!(
sup.state_of(receiver).and_then(ProcState::done_int),
Some(99),
"receive() must return what was sent"
);
assert!(sup.state_of(sender).is_some_and(ProcState::is_done));
}
#[test]
fn waiting_costs_no_fuel() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 0);
let receiver = spawn(
&mut sup,
&mut sys,
blue("receive()"),
Budget {
fuel: Some(40),
max_depth: None,
quantum: Some(20),
},
);
let sender = spawn(
&mut sup,
&mut sys,
blue(&format!(
"def spin(n)\n if n == 0\n send({}, 7)\n else\n spin(n - 1)\n end\nend\nspin(300)",
receiver.0
)),
quantum(20),
);
sup.run_to_quiescence(&mut sys, 2000);
assert!(
sup.state_of(sender).is_some_and(ProcState::is_done),
"the slow sender must finish: {:?}",
sup.state_of(sender)
);
assert_eq!(
sup.state_of(receiver).and_then(ProcState::done_int),
Some(7),
"the receiver survived a long wait on 40 instructions of fuel, so \
parking cost it nothing — state: {:?}",
sup.state_of(receiver)
);
}
#[test]
fn a_receiver_with_no_sender_is_a_reported_deadlock() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let lonely = spawn(&mut sup, &mut sys, blue("receive()"), quantum(50));
let rounds = sup.run_to_quiescence(&mut sys, 500);
assert!(
rounds < 500,
"the scheduler must stop rather than spin on a deadlock (ran {rounds} rounds)"
);
assert!(
sup.events
.iter()
.any(|e| matches!(e, Event::Deadlocked { blocked } if blocked.contains(&lonely))),
"the deadlock must be reported and name the stuck process: {:?}",
sup.events
);
assert!(sup.state_of(lonely).is_some_and(ProcState::is_blocked));
}
#[test]
fn a_receiver_with_a_sender_is_not_reported_as_deadlocked() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let r = spawn(&mut sup, &mut sys, blue("receive()"), quantum(50));
spawn(
&mut sup,
&mut sys,
blue(&format!("send({}, 1)", r.0)),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 200);
assert!(
!sup.events
.iter()
.any(|e| matches!(e, Event::Deadlocked { .. })),
"a satisfiable wait must not be reported as a deadlock: {:?}",
sup.events
);
}
#[test]
fn messages_arrive_in_send_order() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let receiver = spawn(
&mut sup,
&mut sys,
blue("receive() * 10 + receive()"),
quantum(50),
);
spawn(
&mut sup,
&mut sys,
blue(&format!("send({p}, 1)\nsend({p}, 2)", p = receiver.0)),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 200);
assert_eq!(
sup.state_of(receiver).and_then(ProcState::done_int),
Some(12),
"FIFO: 1 then 2. A 21 here means the mailbox reordered."
);
}
#[test]
fn self_is_the_running_process() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let a = spawn(&mut sup, &mut sys, blue("self()"), quantum(50));
let b = spawn(&mut sup, &mut sys, blue("self()"), quantum(50));
sup.run_to_quiescence(&mut sys, 100);
assert_eq!(
sup.state_of(a).and_then(ProcState::done_int),
Some(a.0 as i64)
);
assert_eq!(
sup.state_of(b).and_then(ProcState::done_int),
Some(b.0 as i64)
);
assert_ne!(a.0, b.0, "two processes must not share a pid");
}
#[test]
fn a_request_reply_round_trip_completes() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 3);
let server = spawn(&mut sup, &mut sys, blue("send(receive(), 42)"), quantum(50));
let client = spawn(
&mut sup,
&mut sys,
blue(&format!("send({}, self())\nreceive()", server.0)),
quantum(50),
);
sup.run_to_quiescence(&mut sys, 300);
assert_eq!(
sup.state_of(client).and_then(ProcState::done_int),
Some(42),
"the client must receive the server's reply; events: {:?}",
sup.events
);
}
#[test]
fn a_restart_clears_the_mailbox() {
let mut sys = System::new();
let mut sup = Supervisor::new(Strategy::OneForOne, 5);
let crasher = spawn(
&mut sup,
&mut sys,
blue("def spin(n)\n spin(n + 1)\nend\nspin(0)"),
Budget {
fuel: Some(60),
max_depth: None,
quantum: Some(1_000),
},
);
sys.send(crasher, Value::Int(1));
sys.send(crasher, Value::Int(2));
assert_eq!(sys.mail_count(crasher), 2, "precondition: mail is queued");
sup.round(&mut sys);
assert!(
sup.restarts_of(crasher).is_some_and(|n| n > 0),
"it must have restarted"
);
assert_eq!(
sys.mail_count(crasher),
0,
"the restarted incarnation must not inherit the dead one's mail"
);
}
#[test]
fn a_send_to_an_unknown_pid_is_recorded_not_dropped() {
let mut sys = System::new();
sys.send(Pid(9999), Value::Int(5));
assert_eq!(sys.undeliverable.len(), 1);
assert_eq!(sys.undeliverable[0].0, Pid(9999));
}
#[test]
fn readiness_distinguishes_a_pending_message_from_none() {
let mut sys = System::new();
sys.register(Pid(1));
assert!(!sys.is_ready(Pid(1)), "an empty mailbox is not ready");
assert!(!sys.any_pending(), "nothing pending yet");
sys.send(Pid(1), Value::Int(1));
assert!(sys.is_ready(Pid(1)), "a queued message makes it ready");
assert!(sys.any_pending());
}