#[path = "model.rs"] mod fixture; use fixture::*; use turkos_os_runqueue::runqueue as rq; use turkos_os_scheduler_policy::policy::{self as p, Backend as _}; use turkos_os_threads::threads as th; #[test] fn three_frozen_tasks_ten_thousand_fifo_slices_preserve_pages_and_fair_progress() { let (mut p, d, h) = policy_setup(); let owners = d.owners(); assert_eq!(owners, (3, 3, 12)); for _ in 0..10000 { let active = d.advance(false); d.reject_duplicates(active); let s = d.charge(&mut p, 100); assert_eq!(s.cpu_running, s.ticks); assert_eq!(s.cpu_idle, 0); assert_eq!(d.owners(), owners); } let progress = d.source.borrow().progress; assert!(progress[..3].iter().all(|x| *x >= 3333)); assert_eq!(progress[..3].iter().sum::(), 10000); for handle in h { let r = p.record(handle).unwrap(); assert_eq!(r.time.total(), Some(10000)); assert!(r.time.running >= 3333); assert_eq!(r.observation.guard_descriptor, 0); } } #[test] fn independent_cpu_idle_and_per_thread_ready_ledgers_sum_exact_ticks() { let (mut p, d, h) = policy_setup(); d.charge(&mut p, 1); d.advance(false); d.charge(&mut p, 1); let s = p.snapshot(); assert_eq!((s.cpu_idle, s.cpu_running, s.ticks), (1, 1, 2)); for h in h { let r = p.record(h).unwrap(); assert_eq!(r.time.total(), Some(2)); assert_eq!(r.time.sleeping + r.time.dead, 0); } } #[test] fn actual_sleep_wait_records_high_waiter_low_owner_chain_without_busy_loop() { let (mut p, d, h) = policy_setup(); d.advance(false); d.charge(&mut p, 1); d.link(0, 1); assert_eq!(d.advance(true), d.handles[1]); assert_eq!(d.state(0), th::State::Sleeping); d.context(|b| assert!(b.waits.borrow()[0].is_some())); d.charge(&mut p, 1); let r = p.record(h[0]).unwrap(); assert_eq!(r.time.sleeping, 1); assert_eq!(r.inversion_ticks, 1); assert_eq!(r.chain_len, 1); assert_eq!(r.last_chain[0], Some(d.threads[1])); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn real_low_owner_sleep_with_running_high_waiter_is_rejected_before_tick_consumption() { let (mut p, d, _) = policy_setup(); d.advance(false); d.advance(true); d.link(0, 1); d.advance(true); assert_eq!(d.state(1), th::State::Sleeping); d.wake(0); d.advance(false); assert_eq!(d.state(0), th::State::Running); let t = d.issue(1); let before = p.snapshot(); let (e, t) = p.charge(t).err().unwrap(); assert_eq!(e, p::Error::BusyLoop); assert_eq!(p.snapshot(), before); assert_eq!(d.source.borrow().consumed, 0); assert_eq!(d.source.borrow().live, Some(t.observation)); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn actual_cancelled_dead_thread_never_receives_later_body_callback() { let (mut p, d, h) = policy_setup(); d.advance(false); d.advance(true); d.cancel(0); let progress = d.source.borrow().progress[0]; for _ in 0..12 { d.advance(false); d.charge(&mut p, 1); } assert_eq!(d.source.borrow().progress[0], progress); let r = p.record(h[0]).unwrap(); assert_eq!(r.time.dead, 12); assert_eq!(r.time.total(), Some(12)); let m = p.unregister(h[0]).unwrap(); assert_eq!(m.thread, d.threads[0]); assert_eq!(d.owners(), (3, 3, 12)); assert_eq!(p.record(h[0]), Err(p::Error::Stale)); } #[test] fn duplicate_registration_returns_exact_owned_subscription() { let (mut p, d, _) = policy_setup(); let m = Subscription { handle: d.handles[0], thread: d.threads[0], issuer: d.source.clone(), }; let (e, m) = p.register(m).err().unwrap(); assert_eq!(e, p::Error::Duplicate); assert_eq!(m.thread, d.threads[0]); assert!(std::rc::Rc::ptr_eq(&m.issuer, &d.source)); assert_eq!(p.snapshot().members, 3); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn constructor_refusal_returns_actual_backend_and_frozen_owners() { let (b, d, _) = raw(); d.hardware().borrow_mut().in_irq = true; let (e, b) = p::Policy::<_, 4>::new(b).err().unwrap(); assert_eq!(e, p::Error::Context); assert!(b.same_queue(&d)); assert_eq!(d.owners(), (3, 3, 12)); d.hardware().borrow_mut().in_irq = false; let (e, b) = p::Policy::<_, 0>::new(b).err().unwrap(); assert_eq!(e, p::Error::Configuration); assert_eq!(b.epoch(), 0); } #[test] fn actual_cpu_irq_and_mask_gates_return_the_same_delivery() { for k in 0..3 { let (mut p, d, _) = policy_setup(); let t = d.issue(1); let hw = d.hardware(); { let mut h = hw.borrow_mut(); if k == 0 { h.cpu = 1; } else if k == 1 { h.in_irq = true; } else { h.daif = 0; } } let before = p.snapshot(); let (e, t) = p.charge(t).err().unwrap(); assert_eq!( e, if k == 0 { p::Error::Cpu } else { p::Error::Context } ); assert_eq!(p.snapshot(), before); assert_eq!(d.source.borrow().live, Some(t.observation)); assert_eq!(d.source.borrow().consumed, 0); assert_eq!(d.owners(), (3, 3, 12)); } } #[test] fn read_only_queue_observer_gates_before_table_borrow_in_irq_wrong_cpu_or_i0() { let (_, d, _) = policy_setup(); let hw = d.hardware(); for k in 0..3 { { let mut h = hw.borrow_mut(); h.cpu = 0; h.in_irq = false; h.daif = 128; } { let mut h = hw.borrow_mut(); if k == 0 { h.cpu = 1; } else if k == 1 { h.in_irq = true; } else { h.daif = 0; } } assert_eq!(d.observer_while_table_exclusive(), Err(rq::Error::Context)); } assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn canary_guard_and_page_owner_change_refuse_without_consuming_delivery() { for k in 0..4 { let (mut p, d, _) = policy_setup(); d.advance(false); if k == 0 { d.hardware().borrow_mut().corrupt_canary = true; } else if k == 1 { d.hardware().borrow_mut().corrupt_guard = true; } else if k == 2 { d.source.borrow_mut().fingerprint_fault = true; } else { d.source.borrow_mut().pages_fault = true; } let t = d.issue(1); let before = p.snapshot(); let (e, t) = p.charge(t).err().unwrap(); assert_eq!( e, if k < 2 { p::Error::Guard } else { p::Error::Resource } ); assert_eq!(p.snapshot(), before); assert_eq!(d.source.borrow().live, Some(t.observation)); assert_eq!(d.owners(), (3, 3, 12)); } } #[test] fn caller_forged_tick_source_sequence_epoch_and_current_are_refused() { for k in 0..4 { let (mut p, d, _) = policy_setup(); let mut t = d.issue(1); match k { 0 => t.observation.source += 1, 1 => t.observation.sequence += 1, 2 => t.observation.queue_epoch += 1, _ => t.observation.current = Some(d.threads[0]), }; d.rebind_delivery(&mut t); let (e, t) = p.charge(t).err().unwrap(); assert_eq!(e, p::Error::Tick); assert_eq!(d.source.borrow().live, Some(t.observation)); assert_eq!(p.snapshot().ticks, 0); } } #[test] fn counter_regression_and_repeat_cannot_double_charge() { let (mut p, d, _) = policy_setup(); d.charge(&mut p, 5); let mut t = d.issue(1); t.observation.counter = 5; d.rebind_delivery(&mut t); let (e, mut t) = p.charge(t).err().unwrap(); assert_eq!(e, p::Error::Clock); t.observation.counter = 4; d.rebind_delivery(&mut t); assert_eq!(p.charge(t).err().unwrap().0, p::Error::Clock); assert_eq!(p.snapshot().ticks, 1); assert_eq!(d.source.borrow().consumed, 1); } #[test] fn consumed_delivery_cannot_reappear_as_duplicate_sequence() { let (mut p, d, _) = policy_setup(); d.charge(&mut p, 1); let mut t = d.issue(1); t.observation.sequence = 1; d.rebind_delivery(&mut t); assert_eq!(p.charge(t).err().unwrap().0, p::Error::Tick); assert_eq!(p.snapshot().ticks, 1); } #[test] fn actual_class_slice_and_progress_regression_cannot_rewrite_history() { for k in 0..3 { let (mut p, d, _) = policy_setup(); d.advance(false); d.charge(&mut p, 1); if k == 0 { d.source.borrow_mut().class_fault = true; } else if k == 1 { d.source.borrow_mut().slice_fault = true; } else { d.source.borrow_mut().progress[0] = 0; } let before = p.snapshot(); assert_eq!(p.charge(d.issue(1)).err().unwrap().0, p::Error::State); assert_eq!(p.snapshot(), before); } } #[test] fn owner_cycles_and_unknown_live_owner_refuse_without_partial_ledger_commit() { for kind in 0..2 { let (mut p, d, _) = policy_setup(); if kind == 0 { d.link(0, 1); d.link(1, 0); } else { let (_, other, _) = policy_setup(); let mut source = d.source.borrow_mut(); source.epochs[0] = 1; source.links[0] = Some(other.threads[0]); } let before = p.snapshot(); assert_eq!(p.charge(d.issue(1)).err().unwrap().0, p::Error::OwnerChain); assert_eq!(p.snapshot(), before); assert_eq!(d.source.borrow().consumed, 0); } } #[test] fn dead_owner_cannot_be_unregistered_while_real_waiter_link_remains() { let (mut p, d, h) = policy_setup(); d.link(0, 1); d.cancel(1); assert_eq!(p.unregister(h[1]).err(), Some(p::Error::Busy)); assert_eq!(p.snapshot().members, 3); d.clear_link(0); assert_eq!(p.unregister(h[1]).unwrap().thread, d.threads[1]); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn failed_actual_member_observation_returns_tick_without_any_ledger_commit() { let (mut p, d, _) = policy_setup(); d.source.borrow_mut().member_fault = true; let t = d.issue(1); assert_eq!(p.charge(t).err().unwrap().0, p::Error::Stale); assert_eq!(p.snapshot().ticks, 0); assert_eq!(d.source.borrow().consumed, 0); } #[test] fn long_run_contract_requires_actual_fresh_progress_duration_and_bounded_samples() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); assert_eq!(p.finish_long_run(&run), Err(p::Error::Duration)); for _ in 0..180 { d.advance(false); d.charge(&mut p, 20000); } assert_eq!(p.finish_long_run(&run).unwrap().ticks, 180); assert_eq!(d.owners(), (3, 3, 12)); d.source.borrow_mut().progress[0] = 0; assert_eq!(p.finish_long_run(&run), Err(p::Error::State)); } #[test] fn mathematically_one_hour_without_sixty_samples_is_rejected() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); for _ in 0..3 { d.advance(false); d.charge(&mut p, 1200000); } assert_eq!(p.finish_long_run(&run), Err(p::Error::Duration)); } #[test] fn conservative_whole_history_sample_gap_cannot_be_reset_at_begin() { let (mut p, d, _) = policy_setup(); d.charge(&mut p, 60001); let run = p.begin_long_run().unwrap(); for _ in 0..180 { d.advance(false); d.charge(&mut p, 20000); } assert_eq!(p.finish_long_run(&run), Err(p::Error::Duration)); } #[test] fn policy_release_refuses_live_members_and_actual_irq_context() { let (p, d, _) = policy_setup(); let p = p.into_backend().err().unwrap(); assert_eq!(p.snapshot().members, 3); let (b, d2, _) = raw(); let p = p::Policy::<_, 4>::new(b).ok().unwrap(); d2.hardware().borrow_mut().in_irq = true; let p = p.into_backend().err().unwrap(); assert_eq!(p.snapshot().members, 0); d2.hardware().borrow_mut().in_irq = false; assert!(p.into_backend().is_ok()); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn real_ready_thread_omitted_from_frozen_queue_is_detected_as_starved() { let (mut p, d, _) = policy_setup(); d.advance(false); d.charge(&mut p, 1); d.advance(true); d.wake_without_enqueue(0); assert_eq!(d.state(0), th::State::Ready); let mut refused = false; for _ in 0..6 { d.advance(false); match p.charge(d.issue(1)) { Ok(_) => (), Err((e, _)) => { assert_eq!(e, p::Error::Fairness); refused = true; break; } } } assert!(refused); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn a_valid_tick_failure_cannot_rewrite_any_host_stack_byte() { let (mut p, d, _) = policy_setup(); d.advance(false); let before = d.stack_bytes(); let mut t = d.issue(1); t.observation.sequence = 17; d.rebind_delivery(&mut t); assert_eq!(p.charge(t).err().unwrap().0, p::Error::Tick); assert_eq!(d.stack_bytes(), before); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn registration_guard_failure_returns_same_subscription_before_publication() { let (b, d, mut members) = raw(); let mut p = p::Policy::<_, 4>::new(b).ok().unwrap(); d.hardware().borrow_mut().corrupt_guard = true; let m = members.remove(0); let (e, m) = p.register(m).err().unwrap(); assert_eq!(e, p::Error::Guard); assert_eq!(m.thread, d.threads[0]); assert_eq!(p.snapshot().members, 0); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn rejected_source_clock_change_returns_exact_delivery() { let (mut p, d, _) = policy_setup(); let t = d.issue(1); d.source.borrow_mut().frequency += 1; let (e, t) = p.charge(t).err().unwrap(); assert_eq!(e, p::Error::Clock); assert_eq!(d.source.borrow().live, Some(t.observation)); assert_eq!(d.source.borrow().consumed, 0); } #[test] fn backend_future_counter_cannot_be_accepted_from_a_delivery() { let (mut p, d, _) = policy_setup(); let mut t = d.issue(1); t.observation.counter = 2; d.rebind_delivery(&mut t); assert_eq!(p.charge(t).err().unwrap().0, p::Error::Clock); assert_eq!(p.snapshot().ticks, 0); assert_eq!(d.source.borrow().consumed, 0); } #[test] fn running_membership_cannot_be_returned_or_release_stack_owners() { let (mut p, d, h) = policy_setup(); d.advance(false); assert_eq!(p.unregister(h[0]).err(), Some(p::Error::Busy)); assert_eq!(p.snapshot().members, 3); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn actual_dead_owner_in_a_wait_chain_refuses_whole_tick() { let (mut p, d, _) = policy_setup(); d.link(0, 1); d.cancel(1); let before = p.snapshot(); assert_eq!(p.charge(d.issue(1)).err().unwrap().0, p::Error::OwnerChain); assert_eq!(p.snapshot(), before); assert_eq!(d.source.borrow().consumed, 0); } #[test] fn long_run_baseline_refuses_actual_cancelled_dead_member_with_all_pins_retained() { let (mut p, d, _) = policy_setup(); d.cancel(0); assert_eq!(p.begin_long_run(), Err(p::Error::State)); assert_eq!(d.state(0), th::State::Dead); assert_eq!(d.owners(), (3, 3, 12)); assert_eq!(p.snapshot().members, 3); } #[test] fn initial_progress_then_actual_dead_members_for_hour_cannot_pass_workload() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); for _ in 0..3 { d.advance(false); d.charge(&mut p, 1); } let before = d.source.borrow().progress; d.cancel(0); d.cancel(1); for _ in 0..60 { d.charge(&mut p, 60000); } assert_eq!(p.snapshot().ticks, 63); assert_eq!(d.state(0), th::State::Dead); assert_eq!(d.state(1), th::State::Dead); assert_eq!(d.source.borrow().progress, before); assert_eq!( p.workload_status(&run).unwrap().failed, Some(p::Error::State) ); assert_eq!(p.finish_long_run(&run), Err(p::Error::State)); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn final_unsampled_actual_dead_transition_refuses_previously_proven_hour() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); d.hour(&mut p); assert!(p.finish_long_run(&run).is_ok()); assert_eq!(p.workload_status(&run).unwrap().failed, None); d.cancel(0); assert_eq!(p.finish_long_run(&run), Err(p::Error::State)); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn total_initial_progress_does_not_cover_later_stopped_live_workload() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); for _ in 0..3 { d.advance(false); d.charge(&mut p, 1); } for _ in 0..180 { d.advance_without_progress(); d.charge(&mut p, 20000); } assert!( d.threads .iter() .enumerate() .all(|(i, _)| matches!(d.state(i), th::State::Ready | th::State::Running)) ); assert_eq!(d.source.borrow().progress[..3], [1, 1, 1]); assert_eq!(p.snapshot().ticks, 183); assert_eq!( p.workload_status(&run).unwrap().failed, Some(p::Error::Fairness) ); assert_eq!(p.finish_long_run(&run), Err(p::Error::Fairness)); assert_eq!(d.owners(), (3, 3, 12)); } #[test] fn late_new_progress_cannot_erase_earlier_failed_deadline() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); for _ in 0..3 { d.advance(false); d.charge(&mut p, 1); } for _ in 0..4 { d.advance_without_progress(); d.charge(&mut p, 20000); } assert_eq!( p.workload_status(&run).unwrap().failed, Some(p::Error::Fairness) ); d.hour(&mut p); assert_eq!( p.workload_status(&run).unwrap().failed, Some(p::Error::Fairness) ); assert_eq!(p.finish_long_run(&run), Err(p::Error::Fairness)); } #[test] fn every_live_member_requires_sixty_distinct_charged_progress_advances() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); d.hour(&mut p); let status = p.workload_status(&run).unwrap(); assert_eq!(status.minimum_progress_samples, 60); assert_eq!(status.maximum_progress_gap, 60000); assert_eq!(status.members, 3); assert!(p.finish_long_run(&run).is_ok()); } #[test] fn unsampled_late_progress_cannot_extend_validated_hour_with_stopped_work() { let (mut p, d, _) = policy_setup(); let run = p.begin_long_run().unwrap(); d.hour(&mut p); assert!(p.finish_long_run(&run).is_ok()); d.source.borrow_mut().counter += 60001; d.advance(false); assert_eq!(p.finish_long_run(&run), Err(p::Error::Fairness)); assert_eq!(d.owners(), (3, 3, 12)); }