//! Mailbox-registry tests (RFC 014). Run under the scheduler: registration //! captures a live actor's channel, resolution checks liveness. //! //! Workers register their *own* inbox (`register` claims the current actor); //! the root closure resolves and sends by name. A `ready` handshake closes the //! register-then-send race without busy-waiting on `whereis`. use smarm::{channel, register, run, send, spawn, unregister, whereis, Name, RegisterError, SendError}; const SVC: Name = Name::new("svc"); #[test] fn register_then_send_by_name_delivers() { run(|| { let (ready_tx, ready_rx) = channel::<()>(); let (tx, rx) = channel::(); let h = spawn(move || { register(SVC, tx).unwrap(); ready_tx.send(()).unwrap(); assert_eq!(rx.recv().unwrap(), 42); }); ready_rx.recv().unwrap(); // worker has registered assert_eq!(whereis("svc"), Some(h.pid())); send(SVC, 42).unwrap(); h.join().unwrap(); }); } #[test] fn one_actor_many_typed_channels_route_by_type() { // Same name, two message types: capability separation falls out of the // type parameter — `Name` and `Name<&str>` hit different channels of // the one actor (RFC 014 §4.7). run(|| { let (ready_tx, ready_rx) = channel::<()>(); let (cmd_tx, cmd_rx) = channel::(); let (adm_tx, adm_rx) = channel::<&'static str>(); let h = spawn(move || { register(Name::::new("port"), cmd_tx).unwrap(); register(Name::<&'static str>::new("port"), adm_tx).unwrap(); ready_tx.send(()).unwrap(); assert_eq!(cmd_rx.recv().unwrap(), 7); assert_eq!(adm_rx.recv().unwrap(), "halt"); }); ready_rx.recv().unwrap(); send(Name::::new("port"), 7u64).unwrap(); send(Name::<&'static str>::new("port"), "halt").unwrap(); h.join().unwrap(); }); } #[test] fn name_held_by_live_actor_is_taken() { run(|| { let (ready_tx, ready_rx) = channel::<()>(); let (tx_a, rx_a) = channel::(); let a = spawn(move || { register(SVC, tx_a).unwrap(); ready_tx.send(()).unwrap(); assert_eq!(rx_a.recv().unwrap(), 0); // wait to be released }); ready_rx.recv().unwrap(); // Root tries to claim a live actor's name for itself -> NameTaken. let (tx_b, _rx_b) = channel::(); assert_eq!(register(SVC, tx_b), Err(RegisterError::NameTaken { holder: a.pid() })); send(SVC, 0).unwrap(); // release a (delivers to the holder, a) a.join().unwrap(); }); } #[test] fn dead_holder_is_pruned_and_name_taken_over() { // a registers, signals, then dies. Its slot index is typically reused by b; // b's registration must prune the stale binding and take the name over. run(|| { let (rt1, rr1) = channel::<()>(); let (tx1, _rx1) = channel::(); let a = spawn(move || { register(SVC, tx1).unwrap(); rt1.send(()).unwrap(); // then return -> die, _rx1 dropped }); rr1.recv().unwrap(); let a_pid = a.pid(); a.join().unwrap(); // a is dead; "svc" now points at a stale pid let (rt2, rr2) = channel::<()>(); let (tx2, rx2) = channel::(); let b = spawn(move || { register(SVC, tx2).unwrap(); // takes over the freed name rt2.send(()).unwrap(); assert_eq!(rx2.recv().unwrap(), 9); }); rr2.recv().unwrap(); assert_ne!(b.pid(), a_pid); // distinct incarnation even if slot reused assert_eq!(whereis("svc"), Some(b.pid())); send(SVC, 9).unwrap(); b.join().unwrap(); }); } #[test] fn send_errors_unresolved_and_no_channel() { run(|| { // No actor at all. assert!(matches!(send(Name::::new("ghost"), 1u64), Err(SendError::Unresolved(_)))); let (ready_tx, ready_rx) = channel::<()>(); let (tx, rx) = channel::(); let h = spawn(move || { register(Name::::new("svc2"), tx).unwrap(); ready_tx.send(()).unwrap(); assert_eq!(rx.recv().unwrap(), 0); }); ready_rx.recv().unwrap(); // Right actor, wrong message type: it has a u64 channel, not a String. let e = send(Name::::new("svc2"), "x".to_string()); assert!(matches!(e, Err(SendError::NoChannel(_)))); assert_eq!(e.unwrap_err().into_inner(), "x"); // message handed back send(Name::::new("svc2"), 0u64).unwrap(); h.join().unwrap(); }); } #[test] fn unregister_frees_the_name_only() { run(|| { let (ready_tx, ready_rx) = channel::<()>(); let (done_tx, done_rx) = channel::<()>(); let (tx, _rx) = channel::(); // _rx moves into the actor, kept open let h = spawn(move || { register(SVC, tx).unwrap(); let _keep_open = _rx; ready_tx.send(()).unwrap(); done_rx.recv().unwrap(); // released over a separate channel }); ready_rx.recv().unwrap(); assert_eq!(whereis("svc"), Some(h.pid())); assert_eq!(unregister("svc"), Some(h.pid())); assert_eq!(whereis("svc"), None); assert!(matches!(send(SVC, 1u64), Err(SendError::Unresolved(_)))); done_tx.send(()).unwrap(); h.join().unwrap(); }); }