//! Monitor tests: `monitor(pid)` delivers exactly one `Down` describing how //! the target terminated. //! //! A monitor is unidirectional and one-shot: it never propagates failure to //! the watcher (that's a link), it just drops a `Down` into the returned //! channel. These tests pin the three reasons — `Exit`, `Panic`, `NoProc` — //! and the fan-out case where several monitors watch the same target. //! //! Scheduling note: under the default single-thread runtime the parent runs //! until it parks, so every `monitor()` call below registers while the target //! is still `Runnable` (it hasn't been resumed yet). Registration and //! `finalize_actor` both serialize on the shared mutex, so a target that is //! alive at registration time always produces a real `Down`, never a missed //! one. use smarm::{monitor, run, spawn, DownReason}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; #[test] fn monitor_sees_normal_exit() { let ok = Arc::new(AtomicBool::new(false)); let o = ok.clone(); run(move || { let h = spawn(|| {}); let pid = h.pid(); let down = monitor(pid); let d = down.recv().expect("monitor channel closed before Down"); assert_eq!(d.pid, pid, "Down reported the wrong pid"); if matches!(d.reason, DownReason::Exit) { o.store(true, Ordering::SeqCst); } let _ = h.join(); }); assert!(ok.load(Ordering::SeqCst), "expected DownReason::Exit"); } #[test] fn monitor_sees_panic() { let ok = Arc::new(AtomicBool::new(false)); let o = ok.clone(); run(move || { let h = spawn(|| panic!("boom")); let pid = h.pid(); let down = monitor(pid); let d = down.recv().expect("monitor channel closed before Down"); assert_eq!(d.pid, pid); if matches!(d.reason, DownReason::Panic) { o.store(true, Ordering::SeqCst); } // Drain the panic outcome so the joiner path is exercised too; the // payload goes here, not to the monitor. let _ = h.join(); }); assert!(ok.load(Ordering::SeqCst), "expected DownReason::Panic"); } #[test] fn monitor_already_dead_target_is_noproc() { let ok = Arc::new(AtomicBool::new(false)); let o = ok.clone(); run(move || { let h = spawn(|| {}); let pid = h.pid(); // Consume the only handle on a finished child: the slot is reclaimed // and its generation bumped, so `pid` is now stale. h.join().unwrap(); let down = monitor(pid); let d = down.recv().expect("NoProc Down should be delivered immediately"); assert_eq!(d.pid, pid); if matches!(d.reason, DownReason::NoProc) { o.store(true, Ordering::SeqCst); } }); assert!(ok.load(Ordering::SeqCst), "expected DownReason::NoProc"); } #[test] fn multiple_monitors_all_notified() { let count = Arc::new(AtomicUsize::new(0)); let c = count.clone(); run(move || { let h = spawn(|| {}); let pid = h.pid(); let monitors = [monitor(pid), monitor(pid), monitor(pid)]; let _ = h.join(); for m in monitors { if matches!(m.recv().unwrap().reason, DownReason::Exit) { c.fetch_add(1, Ordering::SeqCst); } } }); assert_eq!(count.load(Ordering::SeqCst), 3, "every monitor should see the Down"); }