//! Monitor tests: `monitor(pid)` delivers exactly one `Down` describing how //! the target terminated, and `demonitor(&m)` tears a registration back down. //! //! 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` — //! the fan-out case where several monitors watch the same target, and that //! `demonitor` removes exactly the one registration it names. //! //! 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::{demonitor, 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.rx.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.rx.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.rx.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.rx.recv().unwrap().reason, DownReason::Exit) { c.fetch_add(1, Ordering::SeqCst); } } }); assert_eq!(count.load(Ordering::SeqCst), 3, "every monitor should see the Down"); } #[test] fn demonitor_stops_delivery() { // Demonitoring a live registration removes the slot's only sender for that // channel; the channel closes, so a later recv() observes Err rather than // a Down. demonitor reports the id it tore down. run(|| { let h = spawn(|| {}); let pid = h.pid(); let m = monitor(pid); assert_eq!(demonitor(&m), Some(m.id), "live registration should be removed"); assert!(m.rx.recv().is_err(), "no Down should arrive after demonitor"); let _ = h.join(); }); } #[test] fn demonitor_one_of_many() { // Three monitors on one target; demonitor the middle. The other two still // fire; the demonitored channel is closed with no Down. Distinct ids mean // tearing one down never disturbs its siblings. run(|| { let h = spawn(|| {}); let pid = h.pid(); let ms = [monitor(pid), monitor(pid), monitor(pid)]; assert_eq!(demonitor(&ms[1]), Some(ms[1].id)); let _ = h.join(); assert!(matches!(ms[0].rx.recv().unwrap().reason, DownReason::Exit)); assert!(matches!(ms[2].rx.recv().unwrap().reason, DownReason::Exit)); assert!(ms[1].rx.recv().is_err(), "demonitored channel should be closed"); }); } #[test] fn demonitor_after_fire_is_none() { // Once the Down has fired, the registration is already drained from the // slot, so demonitor finds nothing to remove and reports None. run(|| { let h = spawn(|| {}); let pid = h.pid(); let m = monitor(pid); let d = m.rx.recv().expect("Down before close"); assert!(matches!(d.reason, DownReason::Exit)); assert_eq!(demonitor(&m), None, "already-fired monitor has nothing to remove"); let _ = h.join(); }); }