include!("wait_model.rs"); #[test] fn real_frozen_stage_early_signal_aborts_before_table_sleep() { let mut s = system(); let token = arm(&mut s, 0, 1, 0, None); assert_eq!(s.waits.records()[0].phase, wait::Phase::Staged); assert_eq!( s.waits.with_backend_observation(|d| d.state(s.ids[0])), ThreadState::Running ); s.observations[0].borrow_mut().sequence = 1; let token = match s.waits.depart(token).ok().unwrap() { wait::Departure::Ready(t) => t, _ => panic!(), }; assert_eq!(s.cpu.borrow().departures, 1); assert_eq!(s.hardware.borrow().current, Some(s.ids[0])); assert_eq!(s.waits.winners(), 1); assert_eq!(s.ledger.pins.get(), 2); assert_eq!( s.waits.retire(token).ok().unwrap().outcome, wait::Outcome::Signal ); assert_eq!(s.ledger.pins.get(), 0); } #[test] fn actual_frozen_wait_and_irq_ingress_resolve_once_after_fresh_landing() { let mut s = system(); let token = arm(&mut s, 0, 1, 0, Some(200)); let transfer = match s.waits.depart(token).ok().unwrap() { wait::Departure::Switch(t) => t, _ => panic!(), }; assert_eq!(s.waits.records()[0].phase, wait::Phase::Departed); assert_eq!( s.waits.service(&mut s.ingress[0]), Err(wait::Error::Pending) ); let token = arrive(&mut s, transfer); s.hardware.borrow_mut().in_irq = true; s.observations[0].borrow_mut().sequence = 1; assert_eq!(s.ingress[0].poll(), Ok(true)); let before = s.waits.records(); assert_eq!( s.waits.service(&mut s.ingress[0]), Err(wait::Error::Context) ); assert_eq!(s.waits.records(), before); s.hardware.borrow_mut().in_irq = false; assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(1)); assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(0)); assert_eq!(s.ledger.resolves.get(), 1); assert_eq!( s.waits.with_backend_observation(|d| d.state(s.ids[0])), ThreadState::Ready ); assert_eq!( s.waits.retire(token).ok().unwrap().outcome, wait::Outcome::Signal ); assert_eq!(s.ledger.pins.get(), 0); } fn asleep( s: &mut System, source: usize, target: usize, port: usize, deadline: Option, ) -> wait::WaitToken { let t = arm(s, source, target, port, deadline); let transfer = match s.waits.depart(t).ok().unwrap() { wait::Departure::Switch(t) => t, _ => panic!(), }; arrive(s, transfer) } #[test] fn same_tick_signal_wins_once_and_removes_both_links() { let mut s = system(); let t = asleep(&mut s, 0, 1, 0, Some(200)); { let mut o = s.observations[0].borrow_mut(); o.now = 200; o.sequence = 1; } assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(1)); let r = s.waits.records()[0]; assert_eq!(r.winner, Some(wait::Outcome::Signal)); assert!(!r.signal_link && !r.deadline_link); assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(0)); assert_eq!(s.waits.cancel(&t), Err(wait::Error::Duplicate)); assert_eq!(s.ledger.resolves.get(), 1); assert_eq!( s.waits.retire(t).ok().unwrap().outcome, wait::Outcome::Signal ); assert_eq!(s.ledger.pins.get(), 0); } #[test] fn actual_timeout_wakes_exact_frozen_wait_without_signal() { let mut s = system(); let t = asleep(&mut s, 0, 1, 0, Some(200)); s.observations[0].borrow_mut().now = 200; assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(1)); assert_eq!(s.waits.records()[0].winner, Some(wait::Outcome::Timeout)); assert_eq!( s.waits.with_backend_observation(|d| d.state(s.ids[0])), ThreadState::Ready ); assert_eq!( s.waits.retire(t).ok().unwrap().outcome, wait::Outcome::Timeout ); assert_eq!(s.ledger.resolves.get(), 1); } #[test] fn two_actual_waits_cancel_one_without_touching_other_sleep_episode() { let mut s = system(); let a = asleep(&mut s, 0, 1, 0, None); let b = asleep(&mut s, 1, 2, 1, None); assert_eq!(s.ledger.pins.get(), 4); assert_eq!(s.waits.cancel(&a), Ok(())); assert_eq!(s.waits.records()[0].winner, Some(wait::Outcome::Cancelled)); assert_eq!(s.waits.records()[1].phase, wait::Phase::Sleeping); assert_eq!( s.waits.with_backend_observation(|d| d.state(s.ids[1])), ThreadState::Sleeping ); s.observations[1].borrow_mut().sequence = 1; assert_eq!(s.waits.service(&mut s.ingress[1]), Ok(1)); assert_eq!( s.waits.retire(a).ok().unwrap().outcome, wait::Outcome::Cancelled ); assert_eq!( s.waits.retire(b).ok().unwrap().outcome, wait::Outcome::Signal ); assert_eq!(s.ledger.resolves.get(), 2); assert_eq!(s.ledger.pins.get(), 0); } #[test] fn later_failure_preserves_queryable_earlier_committed_winner() { let mut s = system(); let a = asleep(&mut s, 0, 1, 0, None); let b = asleep(&mut s, 1, 2, 0, None); s.observations[0].borrow_mut().sequence = 1; s.ledger.fail_resolve_thread.set(Some(s.ids[1])); assert_eq!( s.waits.service(&mut s.ingress[0]), Err(wait::Error::Backend) ); let r = s.waits.records(); assert_eq!(r[0].winner, Some(wait::Outcome::Signal)); assert_eq!(r[1].winner, None); assert_eq!(r[1].phase, wait::Phase::Sleeping); assert_eq!(s.waits.winners(), 1); assert_eq!(s.ledger.pins.get(), 4); s.ledger.fail_resolve_thread.set(None); assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(1)); assert_eq!(s.waits.winners(), 2); assert!(s.waits.retire(a).is_ok()); assert!(s.waits.retire(b).is_ok()); assert_eq!(s.ledger.resolves.get(), 2); assert_eq!(s.ledger.pins.get(), 0); } #[test] fn full_ingress_retries_count_each_distinct_observed_sequence_once() { let mut s = system(); s.hardware.borrow_mut().in_irq = true; for n in 1..=2 { s.observations[0].borrow_mut().sequence = n; assert_eq!(s.ingress[0].poll(), Ok(true)); } s.observations[0].borrow_mut().sequence = 3; for _ in 0..4 { assert_eq!(s.ingress[0].poll(), Err(wait::Error::Full)); } assert_eq!(s.ingress[0].lost(), 1); s.observations[0].borrow_mut().sequence = 4; assert_eq!(s.ingress[0].poll(), Err(wait::Error::Full)); assert_eq!(s.ingress[0].lost(), 2); assert_eq!(s.ingress[0].pending(), 2); s.observations[0].borrow_mut().sequence = 3; assert_eq!(s.ingress[0].poll(), Err(wait::Error::Source)); assert_eq!(s.ingress[0].lost(), 2); s.hardware.borrow_mut().in_irq = false; s.observations[0].borrow_mut().sequence = 4; assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(0)); assert_eq!(s.ingress[0].poll(), Ok(true)); assert_eq!(s.ingress[0].lost(), 2); } #[test] fn producer_release_requires_owning_cpu_masked_task_and_retains_exact_ingress() { for kind in 0..3 { let s = system(); let original = s.ingress[0].source(); match kind { 0 => s.hardware.borrow_mut().cpu = 1, 1 => s.hardware.borrow_mut().in_irq = true, _ => s.hardware.borrow_mut().daif = 0, }; let [port, other] = s.ingress; let port = port.into_producer().err().unwrap(); assert_eq!(port.source(), original); assert_eq!(port.pending(), 0); { let mut h = s.hardware.borrow_mut(); h.cpu = 0; h.in_irq = false; h.daif = 0x3c0; } let producer = port.into_producer().ok().unwrap(); assert_eq!(wait::Producer::observe(&producer).unwrap().source, original); assert!(other.into_producer().is_ok()); } } #[test] fn backend_release_requires_actual_owning_cpu_masked_task() { for kind in 0..3 { let s = system(); match kind { 0 => s.hardware.borrow_mut().cpu = 1, 1 => s.hardware.borrow_mut().in_irq = true, _ => s.hardware.borrow_mut().daif = 0, }; let waits = s.waits.into_backend().err().unwrap(); assert_eq!( waits.with_backend_observation(|d| d.bank.bank().record(d.handles[0]).unwrap().thread), s.ids[0] ); assert_eq!(s.hardware.borrow().pins, 3); assert_eq!(s.hardware.borrow().stack_pages, 12); { let mut h = s.hardware.borrow_mut(); h.cpu = 0; h.in_irq = false; h.daif = 0x3c0; } let driver = waits.into_backend().ok().unwrap(); assert_eq!(driver.state(s.ids[0]), ThreadState::Running); } } #[test] fn producer_clock_regression_is_rejected_even_without_new_signal() { let mut s = system(); s.observations[0].borrow_mut().now = 110; assert_eq!(s.ingress[0].poll(), Ok(false)); s.observations[0].borrow_mut().now = 109; assert_eq!(s.ingress[0].poll(), Err(wait::Error::Source)); assert_eq!(s.ingress[0].pending(), 0); assert_eq!(s.ingress[0].lost(), 0); } #[test] fn backend_clock_behind_actual_producer_refuses_before_retain() { let mut s = system(); s.ledger.clock_regression.set(true); let c = s .waits .with_backend_observation(|d| capture(&d.bank, s.handles[0], &s.hardware, 19, false)); let before = s.waits.records(); let failure = s .waits .arm(&s.ingress[0], s.ids[0], s.ids[1], c, None) .err() .unwrap(); assert_eq!(failure.error, wait::Error::Source); assert!(failure.retained.is_none()); assert_eq!(s.waits.records(), before); assert_eq!(s.ledger.retains.get(), 0); } #[test] fn departure_and_landing_clock_regression_preserve_actual_handoff_custody() { let mut s = system(); let t = arm(&mut s, 0, 1, 0, None); s.observations[0].borrow_mut().now = 99; let (e, t) = s.waits.depart(t).err().unwrap(); assert_eq!(e, wait::Error::Source); assert_eq!(s.waits.records()[0].phase, wait::Phase::Staged); s.observations[0].borrow_mut().now = 110; let transfer = match s.waits.depart(t).ok().unwrap() { wait::Departure::Switch(t) => t, _ => panic!(), }; assert_eq!(s.waits.records()[0].clock, 110); s.waits.with_backend_observation(|d| { let (image, nonce, target) = d.landing.unwrap(); s.hardware.borrow_mut().sp = image.sp() - 64; s.hardware.borrow_mut().current = Some(target); let mut cpu = s.cpu.borrow_mut(); cpu.nonce = nonce; cpu.registers = Some(saved::Backend::expected(image)); }); s.observations[0].borrow_mut().now = 109; let (e, transfer) = s.waits.landed(transfer).err().unwrap(); assert_eq!(e, wait::Error::Source); assert_eq!(s.waits.records()[0].phase, wait::Phase::Departed); assert_eq!(s.ledger.pins.get(), 2); s.observations[0].borrow_mut().now = 110; let t = s.waits.landed(transfer).ok().unwrap(); assert_eq!(s.waits.cancel(&t), Ok(())); assert!(s.waits.retire(t).is_ok()); } #[test] fn before_stage_failure_returns_all_new_pins_and_keeps_real_thread_running() { let mut s = system(); s.hardware.borrow_mut().prepare_fail = true; let c = s .waits .with_backend_observation(|d| capture(&d.bank, s.handles[0], &s.hardware, 19, false)); let f = s .waits .arm(&s.ingress[0], s.ids[0], s.ids[1], c, None) .err() .unwrap(); assert_eq!(f.error, wait::Error::Backend); assert!(f.retained.is_none()); assert_eq!(s.ledger.pins.get(), 0); assert_eq!(s.ledger.releases.get(), 1); assert_eq!(s.waits.records()[0].phase, wait::Phase::Empty); assert_eq!( s.waits.with_backend_observation(|d| d.state(s.ids[0])), ThreadState::Running ); } #[test] fn failed_stage_rollback_keeps_actual_pins_and_private_lookup_in_quarantine() { let mut s = system(); s.hardware.borrow_mut().prepare_fail = true; s.ledger.fail_release.set(true); let c = s .waits .with_backend_observation(|d| capture(&d.bank, s.handles[0], &s.hardware, 19, false)); let f = s .waits .arm(&s.ingress[0], s.ids[0], s.ids[1], c, None) .err() .unwrap(); assert!(f.retained.is_some()); assert_eq!(s.waits.records()[0].phase, wait::Phase::Quarantined); assert_eq!(s.ledger.pins.get(), 2); assert_eq!(s.ledger.releases.get(), 0); let waits = s.waits.into_backend().err().unwrap(); assert_eq!(waits.records()[0].phase, wait::Phase::Quarantined); assert_eq!(s.ledger.pins.get(), 2); } #[test] fn before_depart_failure_retains_prepared_and_retry_sleeps_exact_thread() { let mut s = system(); let t = arm(&mut s, 0, 1, 0, None); s.cpu.borrow_mut().depart_fail = Some(FailurePhase::BeforeSwitch); let (e, t) = s.waits.depart(t).err().unwrap(); assert_eq!(e, wait::Error::Backend); assert_eq!(s.waits.records()[0].phase, wait::Phase::Staged); assert_eq!( s.waits.with_backend_observation(|d| d.state(s.ids[0])), ThreadState::Running ); s.cpu.borrow_mut().depart_fail = None; let transfer = match s.waits.depart(t).ok().unwrap() { wait::Departure::Switch(t) => t, _ => panic!(), }; let t = arrive(&mut s, transfer); assert!(s.waits.cancel(&t).is_ok()); assert!(s.waits.retire(t).is_ok()); assert_eq!(s.ledger.resolves.get(), 1); } #[test] fn uncertain_departure_keeps_actual_frozen_wait_and_pins() { let mut s = system(); let t = arm(&mut s, 0, 1, 0, None); s.cpu.borrow_mut().depart_fail = Some(FailurePhase::AfterSwitch); let (e, t) = s.waits.depart(t).err().unwrap(); assert_eq!(e, wait::Error::Quarantined); assert_eq!(s.waits.records()[0].phase, wait::Phase::Quarantined); assert_eq!(s.ledger.pins.get(), 2); assert_eq!( s.waits.with_backend_observation(|d| d.state(s.ids[0])), ThreadState::Sleeping ); assert_eq!(s.waits.cancel(&t), Err(wait::Error::Quarantined)); assert!(s.waits.retire(t).is_err()); } #[test] fn source_epoch_change_cannot_relabel_or_resolve_existing_wait() { let mut s = system(); let t = asleep(&mut s, 0, 1, 0, None); let before = s.waits.records(); s.observations[0].borrow_mut().source.epoch = 2; assert_eq!(s.waits.service(&mut s.ingress[0]), Err(wait::Error::Source)); assert_eq!(s.waits.records(), before); assert_eq!(s.ledger.resolves.get(), 0); assert_eq!(s.ledger.pins.get(), 2); s.observations[0].borrow_mut().source.epoch = 1; assert!(s.waits.cancel(&t).is_ok()); assert!(s.waits.retire(t).is_ok()); } #[test] fn failed_terminal_release_retains_exact_owned_pins_without_second_wake() { let mut s = system(); let t = asleep(&mut s, 0, 1, 0, None); assert!(s.waits.cancel(&t).is_ok()); s.ledger.fail_release.set(true); let (e, t) = s.waits.retire(t).err().unwrap(); assert_eq!(e, wait::Error::Backend); assert_eq!(s.waits.records()[0].phase, wait::Phase::Quarantined); assert_eq!(s.ledger.pins.get(), 2); assert_eq!(s.ledger.resolves.get(), 1); assert!(s.waits.retire(t).is_err()); assert_eq!(s.ledger.resolves.get(), 1); } #[test] fn dead_thread_refuses_wake_callback_and_retains_every_wait_pin() { let mut s = system(); let t = asleep(&mut s, 0, 1, 0, None); s.waits.with_backend_observation(|d| { d.bank.bank().with_backend_observation(|device| { device.inner.table.borrow_mut().cancel(s.ids[0]).unwrap() }) }); s.observations[0].borrow_mut().sequence = 1; let before = s.waits.records(); assert_eq!(s.waits.service(&mut s.ingress[0]), Err(wait::Error::State)); assert_eq!(s.waits.records(), before); assert_eq!(s.ledger.resolves.get(), 0); assert_eq!(s.ledger.pins.get(), 2); assert!(s.waits.retire(t).is_err()); assert_eq!(s.hardware.borrow().stack_pages, 12); } #[test] fn earlier_queued_signal_does_not_wake_new_same_channel_generation() { let mut s = system(); let old = arm(&mut s, 0, 1, 0, None); s.observations[0].borrow_mut().sequence = 1; assert_eq!(s.ingress[0].poll(), Ok(true)); let old = match s.waits.depart(old).ok().unwrap() { wait::Departure::Ready(t) => t, _ => panic!(), }; assert_eq!(s.waits.retire(old).ok().unwrap().generation, 1); let next = arm(&mut s, 0, 1, 0, None); assert_eq!(next.generation(), 2); assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(0)); assert_eq!(s.waits.records()[0].phase, wait::Phase::Staged); assert_eq!(s.waits.winners(), 1); s.observations[0].borrow_mut().sequence = 2; assert_eq!(s.waits.service(&mut s.ingress[0]), Ok(1)); assert_eq!(s.waits.retire(next).ok().unwrap().generation, 2); assert_eq!(s.ledger.pins.get(), 0); } #[test] fn actual_irq_or_wrong_cpu_arm_is_before_any_source_retain() { for mode in 0..3 { let mut s = system(); let c = s .waits .with_backend_observation(|d| capture(&d.bank, s.handles[0], &s.hardware, 19, false)); match mode { 0 => s.hardware.borrow_mut().in_irq = true, 1 => s.hardware.borrow_mut().cpu = 1, _ => s.hardware.borrow_mut().daif = 0, }; let before = s.waits.records(); let e = s .waits .arm(&s.ingress[0], s.ids[0], s.ids[1], c, None) .err() .unwrap(); assert!(matches!(e.error, wait::Error::Context | wait::Error::Cpu)); assert!(e.retained.is_none()); assert_eq!(s.waits.records(), before); assert_eq!(s.ledger.retains.get(), 0); } } #[test] fn exhausted_wait_slot_refuses_before_new_real_pin_or_prepare() { let s = system(); let System { waits, ingress, ids, handles, hardware, cpu, observations, ledger, } = s; let driver = waits.into_backend().ok().unwrap(); let waits = wait::WaitSet::<_, 1>::new(driver).ok().unwrap(); let mut s = System::<1> { waits, ingress, ids, handles, hardware, cpu, observations, ledger, }; let a = asleep(&mut s, 0, 1, 0, None); let before = s.waits.records(); let c = s .waits .with_backend_observation(|d| capture(&d.bank, s.handles[1], &s.hardware, 19, false)); let failure = s .waits .arm(&s.ingress[1], s.ids[1], s.ids[2], c, None) .err() .unwrap(); assert_eq!(failure.error, wait::Error::Full); assert!(failure.retained.is_none()); assert_eq!(s.waits.records(), before); assert_eq!(s.ledger.retains.get(), 1); assert_eq!(s.ledger.pins.get(), 2); assert!(s.waits.cancel(&a).is_ok()); assert!(s.waits.retire(a).is_ok()); } #[test] fn rejected_configuration_returns_actual_exclusive_backend_and_stack_owners() { let s = system(); let backend = s.waits.into_backend().ok().unwrap(); let (e, backend) = wait::WaitSet::<_, 0>::new(backend).err().unwrap(); assert_eq!(e, wait::Error::Configuration); assert_eq!(backend.state(s.ids[0]), ThreadState::Running); assert_eq!(s.hardware.borrow().pins, 3); assert_eq!(s.hardware.borrow().stack_pages, 12); assert!(wait::WaitSet::<_, 8>::new(backend).is_ok()); } #[test] fn failed_pin_validation_rolls_back_before_context_preparation() { let mut s = system(); s.ledger.fail_validate.set(true); let c = s .waits .with_backend_observation(|d| capture(&d.bank, s.handles[0], &s.hardware, 19, false)); let prepares = s.hardware.borrow().prepares; let failure = s .waits .arm(&s.ingress[0], s.ids[0], s.ids[1], c, None) .err() .unwrap(); assert_eq!(failure.error, wait::Error::Backend); assert!(failure.retained.is_none()); assert_eq!(s.ledger.pins.get(), 0); assert_eq!(s.hardware.borrow().prepares, prepares); assert_eq!(s.waits.records()[0].phase, wait::Phase::Empty); }