//! Integration tests for the `library_lifecycle` module. use sarasacw_omrf::library_lifecycle::LibraryLifecycle; use std::sync::atomic::{AtomicUsize, Ordering}; use thiserror::Error as TeError; #[derive(Debug, TeError)] enum TestErr { #[error("boom")] Boom, } #[derive(Debug)] struct State { value: usize, } /// Coverage: a single paired startup/shutdown runs init once and destroy once, destroy receives the /// state produced by init, both report that they ran the closure, and the count returns to zero. #[test] fn paired_startup_shutdown() { let inits = AtomicUsize::new(0); let destroys = AtomicUsize::new(0); let lc: LibraryLifecycle = LibraryLifecycle::new(); let ran_init = lc .startup(|| -> Result { inits.fetch_add(1, Ordering::SeqCst); Ok(State { value: 42 }) }) .unwrap(); assert!(ran_init); let ran_destroy = lc .shutdown(|s| { destroys.fetch_add(1, Ordering::SeqCst); assert_eq!(s.value, 42); }) .unwrap(); assert!(ran_destroy); assert_eq!(inits.load(Ordering::SeqCst), 1); assert_eq!(destroys.load(Ordering::SeqCst), 1); assert_eq!(lc.count(), 0); } /// Coverage: multiple startups followed by matching shutdowns run init and destroy only on the /// boundary transitions (once each); the init/destroy closures passed on non-transition calls are /// dropped without being run, and the bool return reflects exactly which calls ran a closure. #[test] fn multiple_startups_shutdowns_run_only_on_boundaries() { let inits = AtomicUsize::new(0); let destroys = AtomicUsize::new(0); let lc: LibraryLifecycle = LibraryLifecycle::new(); let startup_ran: Vec = (0..3) .map(|_| { lc.startup(|| -> Result { inits.fetch_add(1, Ordering::SeqCst); Ok(State { value: 7 }) }) .unwrap() }) .collect(); assert_eq!(startup_ran, vec![true, false, false]); assert_eq!(lc.count(), 3); assert_eq!(inits.load(Ordering::SeqCst), 1); let shutdown_ran: Vec = (0..3) .map(|_| { lc.shutdown(|_s| { destroys.fetch_add(1, Ordering::SeqCst); }) .unwrap() }) .collect(); assert_eq!(shutdown_ran, vec![false, false, true]); assert_eq!(inits.load(Ordering::SeqCst), 1); assert_eq!(destroys.load(Ordering::SeqCst), 1); assert_eq!(lc.count(), 0); } /// Coverage: after a full cycle, a new cycle re-runs init and destroy (re-initialization works). #[test] fn reinitialization_runs_init_and_destroy_again() { let inits = AtomicUsize::new(0); let destroys = AtomicUsize::new(0); let lc: LibraryLifecycle = LibraryLifecycle::new(); for _ in 0..2 { let ran_init = lc .startup(|| -> Result { inits.fetch_add(1, Ordering::SeqCst); Ok(State { value: 1 }) }) .unwrap(); assert!(ran_init); let ran_destroy = lc .shutdown(|_s| { destroys.fetch_add(1, Ordering::SeqCst); }) .unwrap(); assert!(ran_destroy); } assert_eq!(inits.load(Ordering::SeqCst), 2); assert_eq!(destroys.load(Ordering::SeqCst), 2); assert_eq!(lc.count(), 0); } /// Coverage: `shutdown` with the count already at zero is an error (unbalanced call). #[test] fn shutdown_when_idle_is_error() { let lc: LibraryLifecycle = LibraryLifecycle::new(); assert!(lc.shutdown(|_| ()).is_err()); } /// Coverage: `with_state` before any `startup` (and after the matching `shutdown`) is an error /// because there is no active state. #[test] fn with_state_when_not_active_is_error() { let lc: LibraryLifecycle = LibraryLifecycle::new(); assert!(lc.with_state(|_s| ()).is_err()); lc.startup(|| -> Result { Ok(State { value: 5 }) }) .unwrap(); lc.shutdown(|_| ()).unwrap(); assert!(lc.with_state(|_s| ()).is_err()); } /// Coverage: a failing init closure leaves the count at zero and the state absent, and the error is /// propagated (type-erased into `Error::Init`). #[test] fn init_failure_keeps_count_zero_and_propagates_error() { let lc: LibraryLifecycle = LibraryLifecycle::new(); let r = lc.startup(|| -> Result { Err(TestErr::Boom) }); assert!(r.is_err()); assert_eq!(lc.count(), 0); // State is absent, so accessing it errors. assert!(lc.with_state(|_s| ()).is_err()); } /// Coverage: `with_state` reads the stored state during an active lifecycle. #[test] fn with_state_reads_active_state() { let lc: LibraryLifecycle = LibraryLifecycle::new(); lc.startup(|| -> Result { Ok(State { value: 99 }) }) .unwrap(); let observed = lc.with_state(|s| s.value).unwrap(); assert_eq!(observed, 99); lc.shutdown(|_| ()).unwrap(); }