use turkos_os_init_restart::{Adapter, Cause, Controller, Error, Phase, Policy}; #[derive(Default)] struct Fake { starts: u8, stops: u8, fail_start_once: bool, fail_stop_once: bool, } impl Adapter for Fake { type Handle = u8; fn start(&mut self) -> Result { self.starts += 1; if self.fail_start_once { self.fail_start_once = false; Err(Cause::Injected) } else { Ok(self.starts) } } fn stop(&mut self, _: &mut u8) -> Result<(), Cause> { self.stops += 1; if self.fail_stop_once { self.fail_stop_once = false; Err(Cause::Busy) } else { Ok(()) } } } fn policy() -> Policy { Policy {max_restarts: 2, base_delay: 2, max_delay: 8} } #[test] fn os_init_restart_bounded_positive() { let mut controller = Controller::new(policy()).unwrap(); let mut adapter = Fake::default(); controller.start_initial(&mut adapter).unwrap(); assert_eq!(controller.owner_count(), 1); controller.on_exit(&mut adapter, 10, true).unwrap(); assert_eq!(controller.phase(), Phase::Backoff {ready_at: 12}); assert_eq!(controller.poll(&mut adapter, 11), Ok(false)); assert_eq!(controller.poll(&mut adapter, 12), Ok(true)); controller.on_exit(&mut adapter, 20, true).unwrap(); assert_eq!(controller.phase(), Phase::Backoff {ready_at: 24}); assert_eq!(controller.poll(&mut adapter, 24), Ok(true)); controller.on_exit(&mut adapter, 30, true).unwrap(); assert_eq!(controller.phase(), Phase::Exhausted); assert_eq!(controller.restarts_used(), 2); assert_eq!(controller.owner_count(), 0); assert_eq!((adapter.starts, adapter.stops), (3, 3)); } #[test] fn os_init_restart_rejects_invalid_policy_and_late_poll() { assert!(matches!(Controller::::new(Policy { max_restarts: 0, base_delay: 2, max_delay: 8, }), Err(Error::InvalidPolicy))); let mut controller = Controller::::new(policy()).unwrap(); let mut adapter = Fake::default(); assert_eq!(controller.poll(&mut adapter, 100), Err(Error::WrongState)); controller.start_initial(&mut adapter).unwrap(); controller.on_exit(&mut adapter, 10, false).unwrap(); assert_eq!(controller.phase(), Phase::Stopped); assert_eq!(controller.owner_count(), 0); assert_eq!(controller.poll(&mut adapter, 100), Err(Error::WrongState)); let mut overflow = Controller::new(policy()).unwrap(); overflow.start_initial(&mut adapter).unwrap(); assert_eq!(overflow.on_exit(&mut adapter, u64::MAX, true), Err(Error::ClockOverflow)); assert_eq!(overflow.phase(), Phase::Exhausted); assert_eq!(overflow.owner_count(), 0); } #[test] fn os_init_restart_failure_retains_owner_and_exhausts_budget() { let mut controller = Controller::new(policy()).unwrap(); let mut adapter = Fake {fail_stop_once: true, fail_start_once: false, ..Fake::default()}; controller.start_initial(&mut adapter).unwrap(); adapter.fail_start_once = true; assert_eq!(controller.on_exit(&mut adapter, 10, true), Err(Error::Stop(Cause::Busy))); assert_eq!(controller.phase(), Phase::Releasing {at: 10, restart: true}); assert_eq!(controller.owner_count(), 1); assert_eq!(controller.poll(&mut adapter, 20), Err(Error::WrongState)); controller.retry_release(&mut adapter).unwrap(); assert_eq!(controller.owner_count(), 0); assert_eq!(controller.phase(), Phase::Backoff {ready_at: 12}); assert_eq!(controller.poll(&mut adapter, 12), Err(Error::Start(Cause::Injected))); assert_eq!(controller.phase(), Phase::Backoff {ready_at: 16}); assert_eq!(controller.restarts_used(), 1); assert_eq!(controller.poll(&mut adapter, 16), Ok(true)); controller.on_exit(&mut adapter, 20, true).unwrap(); assert_eq!(controller.phase(), Phase::Exhausted); assert_eq!(controller.owner_count(), 0); assert_eq!(controller.restarts_used(), 2); }