mod support; use support::*; use turkos_os_context_switch::context::{Backend as _, FailurePhase}; use turkos_os_runqueue::runqueue::*; #[test] fn os_runqueue_enqueue_positive() { let (mut q, h, _) = setup(3); let before = inventory(&q); assert_eq!(q.ready_order(), [Some(h[0]), Some(h[1]), Some(h[2]), None]); for expected in &h { assert_eq!(select(&mut q, Disposition::Ready), *expected) } assert_eq!(inventory(&q), before); assert_eq!(q.snapshot().members, 3); q.audit().unwrap(); } #[test] fn os_runqueue_enqueue_negative() { let (mut q, h, _) = setup(3); let extra = q.with_backend_observation(|b| b.mint(0)); let before = q.snapshot(); let inventory_before = inventory(&q); let returned = match q.enqueue(extra, Class::Normal) { Err((Error::Duplicate, m)) => m, _ => panic!(), }; assert_eq!(returned.thread, q.record(h[0]).unwrap().thread); assert_eq!(q.snapshot(), before); assert_eq!(inventory(&q), inventory_before); drop(returned); assert_eq!(inventory(&q).0, 3); } #[test] fn os_runqueue_select_positive() { let (mut q, h, _) = setup(3); assert_eq!(select(&mut q, Disposition::Ready), h[0]); assert_eq!(select(&mut q, Disposition::Sleeping), h[1]); assert!(!q.record(h[0]).unwrap().queued); assert_eq!(select(&mut q, Disposition::Ready), h[2]); assert_eq!(select(&mut q, Disposition::Ready), h[1]); assert_eq!( q.with_backend_observation(|b| b.bank.with_backend_observation(|bb| bb .table .borrow() .snapshot() .sleeping)), 1 ); assert_eq!(inventory(&q), (3, 3, 12)); } #[test] fn os_runqueue_select_negative() { let (mut q, _, c) = setup(3); let before = q.snapshot(); let order = q.ready_order(); q.with_backend_observation(|b| { let nonce = b.nonces.borrow()[2].clone(); nonce.set(nonce.get() + 1); }); assert!(matches!(q.select(Disposition::Ready), Err(Error::Stale))); assert_eq!(q.snapshot(), before); assert_eq!(q.ready_order(), order); assert_eq!(c.borrow().prepares, 0); assert_eq!(inventory(&q), (3, 3, 12)); } #[test] fn dead_member_is_skipped_while_later_live_ready_member_progresses() { let (mut q, _, c) = setup(3); q.with_backend_observation(|b| { b.bank.with_backend_observation(|bb| { bb.table.borrow_mut().cancel(b.mint(0).thread).unwrap(); }) }); let selected = select(&mut q, Disposition::Ready); assert_eq!(selected.slot(), 1); assert_eq!(c.borrow().commits, 1); assert_eq!(inventory(&q), (3, 3, 12)); } #[test] fn os_runqueue_quantum_positive() { let (mut q, h, _) = setup(2); let before = inventory(&q); let mut counts = [0; 2]; for _ in 0..20 { let selected = select(&mut q, Disposition::Ready); let index = h.iter().position(|v| *v == selected).unwrap(); for remaining in (0..4).rev() { let t = tick(&q, selected); let result = q.charge(t).ok().unwrap(); assert_eq!(result.remaining, remaining); assert_eq!(result.reschedule, remaining == 0); counts[index] += 1; } } assert_eq!(counts, [40, 40]); assert_eq!(q.snapshot().consumed_ticks, 80); assert_eq!(inventory(&q), before); } #[test] fn os_runqueue_quantum_negative() { let (mut q, h, c) = setup(2); select(&mut q, Disposition::Ready); let t = tick(&q, h[0]); let duplicate = Tick { observation: t.observation, nonce: t.nonce, }; q.charge(t).ok().unwrap(); c.borrow_mut().tick_live = Some(duplicate.nonce); let before = q.record(h[0]).unwrap(); let returned = match q.charge(duplicate) { Err((Error::Tick, t)) => t, _ => panic!(), }; assert_eq!(returned.nonce, 1); assert_eq!(q.record(h[0]).unwrap(), before); assert_eq!(c.borrow().consumed, 1); } #[test] fn os_runqueue_priority_positive() { let (mut q, h, _) = setup(2); q.request_priority(h[1], 1).unwrap(); let mut ticks = [0; 2]; for _ in 0..20 { let selected = select(&mut q, Disposition::Ready); let index = h.iter().position(|v| *v == selected).unwrap(); let count = q.record(selected).unwrap().class.quantum(); for _ in 0..count { let t = tick(&q, selected); q.charge(t).ok().unwrap(); ticks[index] += 1; } } assert_eq!(ticks, [40, 20]); assert_eq!(q.record(h[0]).unwrap().slices, 10); assert_eq!(q.record(h[1]).unwrap().slices, 10); assert_eq!(inventory(&q), (2, 2, 8)); } #[test] fn os_runqueue_priority_negative() { let (mut q, h, c) = setup(2); let before = q.snapshot(); let order = q.ready_order(); let record = q.record(h[0]).unwrap(); assert_eq!(q.request_priority(h[0], 2), Err(Error::Priority)); assert_eq!(q.snapshot(), before); assert_eq!(q.ready_order(), order); assert_eq!(q.record(h[0]).unwrap(), record); assert_eq!(c.borrow().prepares, 0); assert_eq!(select(&mut q, Disposition::Ready), h[0]); } #[test] fn before_switch_keeps_same_prepared_and_complete_queue_inventory() { let (mut q, _, c) = setup(3); let before = q.snapshot(); let order = q.ready_order(); let selection = q.select(Disposition::Ready).unwrap(); let nonce = selection.with_prepared_observation(|p| p.nonce); c.borrow_mut().fail = Some(FailurePhase::BeforeSwitch); let selection = match selection.complete() { Err(SelectionError::Before(Error::Backend, s)) => s, _ => panic!(), }; assert_eq!(selection.with_prepared_observation(|p| p.nonce), nonce); selection.abort().unwrap(); assert_eq!(q.snapshot(), before); assert_eq!(q.ready_order(), order); assert_eq!(inventory(&q), (3, 3, 12)); assert_eq!(c.borrow().commits, 0); } #[test] fn post_switch_uncertainty_retains_real_context_and_all_member_authority() { let (mut q, _, c) = setup(3); c.borrow_mut().fail = Some(FailurePhase::AfterSwitch); assert!(matches!( q.select(Disposition::Ready).unwrap().complete(), Err(SelectionError::Quarantined(Error::Backend)) )); assert!(q.snapshot().faulted && q.snapshot().retained_prepared); assert_eq!(q.with_retained_observation(|p| p.unwrap().nonce), 1); assert_eq!(q.with_backend_observation(|b| b.bank.snapshot().active), 1); assert_eq!(inventory(&q), (3, 3, 12)); assert!(matches!( q.select(Disposition::Ready), Err(Error::Quarantined) )); } #[test] fn finish_abort_and_damaged_post_switch_observation_quarantine() { for mode in 0..3 { let (mut q, _, c) = setup(2); let s = q.select(Disposition::Ready).unwrap(); match mode { 0 => c.borrow_mut().finish_fail = true, 1 => c.borrow_mut().abort_fail = true, _ => c.borrow_mut().bad_observation = true, } if mode == 1 { assert_eq!(s.abort(), Err(Error::Backend)); } else { assert!(matches!(s.complete(), Err(SelectionError::Quarantined(_)))); } assert!(q.snapshot().faulted && q.snapshot().retained_prepared); assert_eq!(inventory(&q), (2, 2, 8)); } } #[test] fn changed_cpu_mask_irq_and_live_generation_refuse_before_commit() { for mode in 0..3 { let (mut q, _, c) = setup(2); let hardware = q.with_backend_observation(|b| b.hardware.clone()); let s = q.select(Disposition::Ready).unwrap(); { let mut hw = hardware.borrow_mut(); match mode { 0 => hw.cpu = 1, 1 => hw.daif = 0, _ => hw.in_irq = true, } } let s = match s.complete() { Err(SelectionError::Before(Error::Context, s)) => s, _ => panic!(), }; s.abort().unwrap(); assert_eq!(c.borrow().commits, 0); assert_eq!(inventory(&q), (2, 2, 8)); } } #[test] fn guard_and_canary_corruption_refuse_without_prepared_or_queue_prefix() { for mode in 0..2 { let (mut q, _, c) = setup(3); q.with_backend_observation(|b| { let mut h = b.hardware.borrow_mut(); if mode == 0 { h.corrupt_guard = true } else { h.corrupt_canary = true } }); let before = q.snapshot(); assert!(matches!(q.select(Disposition::Ready), Err(Error::Guard))); assert_eq!(q.snapshot(), before); assert_eq!(c.borrow().prepares, 0); assert_eq!(inventory(&q), (3, 3, 12)); } } #[test] fn expired_quantum_stale_slice_foreign_source_and_thread_do_not_consume_ticks() { let (mut q, h, c) = setup(2); select(&mut q, Disposition::Ready); for _ in 0..4 { let t = tick(&q, h[0]); q.charge(t).ok().unwrap(); } let t = tick(&q, h[0]); assert!(matches!(q.charge(t), Err((Error::Expired, _)))); assert_eq!(c.borrow().consumed, 4); select(&mut q, Disposition::Ready); for mode in 0..4 { let mut t = tick(&q, h[1]); match mode { 0 => t.observation.slice -= 1, 1 => t.observation.source += 1, 2 => t.observation.thread = q.record(h[0]).unwrap().thread, _ => t.observation.cpu = 1, } let before = q.record(h[1]).unwrap(); assert!(matches!(q.charge(t), Err((Error::Tick, _)))); assert_eq!(q.record(h[1]).unwrap(), before); assert_eq!(c.borrow().consumed, 4); } } #[test] fn actual_sleep_wait_wake_requeues_once_and_preserves_stack_custody() { let (mut q, h, _) = setup(3); select(&mut q, Disposition::Ready); select(&mut q, Disposition::Sleeping); assert_eq!(q.requeue(h[0]), Err(Error::State)); q.with_backend_observation(|b| { b.bank.with_backend_observation(|bb| { let wait = bb.sleep_wait.borrow_mut().take().unwrap(); assert_eq!(wait.thread(), q.record(h[0]).unwrap().thread); bb.table.borrow_mut().wake(wait).unwrap(); }) }); q.requeue(h[0]).unwrap(); assert_eq!(q.requeue(h[0]), Err(Error::Duplicate)); assert_eq!(select(&mut q, Disposition::Ready), h[2]); assert_eq!(select(&mut q, Disposition::Ready), h[0]); assert_eq!( q.with_backend_observation(|b| b.bank.with_backend_observation(|bb| bb .table .borrow() .snapshot() .sleeping)), 0 ); assert_eq!(inventory(&q), (3, 3, 12)); } #[test] fn dead_queued_member_can_be_withdrawn_and_old_private_handle_refuses() { let (mut q, h, _) = setup(3); q.with_backend_observation(|b| { b.bank.with_backend_observation(|bb| { bb.table .borrow_mut() .cancel(q.record(h[1]).unwrap().thread) .unwrap() }) }); let member = q.withdraw(h[1]).unwrap(); assert_eq!( member.thread, q.with_backend_observation(|b| b.bank.record(b.handles[1]).unwrap().thread) ); assert!(matches!(q.record(h[1]), Err(Error::Stale))); assert_eq!(q.ready_order(), [Some(h[0]), Some(h[2]), None, None]); assert_eq!( q.with_backend_observation(|b| b.bank.snapshot().retained_bindings), 3 ); assert_eq!(q.snapshot().members, 2); drop(member); assert_eq!(inventory(&q), (2, 3, 12)); } #[test] fn foreign_queue_handle_and_active_priority_change_refuse() { let (mut q, h, _) = setup(2); let (other, oh, _) = setup(2); let before = q.snapshot(); assert_eq!(q.request_priority(oh[0], 0), Err(Error::Stale)); assert_eq!(q.snapshot(), before); select(&mut q, Disposition::Ready); assert_eq!(q.request_priority(h[0], 1), Err(Error::Busy)); assert_eq!(q.withdraw(h[0]).err(), Some(Error::Busy)); assert_eq!(other.snapshot().members, 2); } #[test] fn prepare_failure_preserves_all_queue_and_actual_thread_state() { let (mut q, _, c) = setup(3); c.borrow_mut().prepare_fail = true; let before = q.snapshot(); let order = q.ready_order(); assert!(matches!(q.select(Disposition::Ready), Err(Error::Backend))); assert_eq!(q.snapshot(), before); assert_eq!(q.ready_order(), order); assert_eq!(c.borrow().prepares, 0); assert_eq!( q.with_backend_observation(|b| b.bank.with_backend_observation(|bb| bb .table .borrow() .snapshot() .ready)), 3 ); } #[test] fn constructor_and_full_capacity_return_exact_nonclone_members() { let (b, members, _) = backend(3); let b = match Queue::<_, 0>::new(b) { Err((Error::Configuration, b)) => b, _ => panic!(), }; let mut q = Queue::<_, 1>::new(b).ok().unwrap(); let mut members = members.into_iter(); q.enqueue(members.next().unwrap(), Class::Normal) .ok() .unwrap(); let member = members.next().unwrap(); let id = member.thread; let returned = match q.enqueue(member, Class::Low) { Err((Error::Capacity, m)) => m, _ => panic!(), }; assert_eq!(returned.thread, id); assert_eq!(q.snapshot().members, 1); assert_eq!( q.with_backend_observation(|b| b.hardware.borrow().stack_pages), 12 ); } #[test] fn lone_runnable_renews_accounting_without_switch_or_neighbor_bypass() { let (mut q, h, c) = setup(1); select(&mut q, Disposition::Ready); assert_eq!(q.renew(), Err(Error::Busy)); for _ in 0..4 { let t = tick(&q, h[0]); q.charge(t).ok().unwrap(); } let image = q.with_backend_observation(|b| b.bank.record(b.handles[0]).unwrap()); let commits = c.borrow().commits; let old = q.record(h[0]).unwrap(); assert_eq!(q.renew().unwrap(), h[0]); assert_eq!(q.record(h[0]).unwrap().remaining, 4); assert!(q.record(h[0]).unwrap().slice > old.slice); assert_eq!(c.borrow().commits, commits); assert_eq!( q.with_backend_observation(|b| b.bank.record(b.handles[0]).unwrap()), image ); let (mut other, ids, _) = setup(2); select(&mut other, Disposition::Ready); for _ in 0..4 { let t = tick(&other, ids[0]); other.charge(t).ok().unwrap(); } let before = other.snapshot(); assert_eq!(other.renew(), Err(Error::Busy)); assert_eq!(other.snapshot(), before); assert_eq!(select(&mut other, Disposition::Ready), ids[1]); } #[test] fn woken_retained_ready_member_blocks_renew_before_explicit_requeue() { let (mut q, h, c) = setup(2); select(&mut q, Disposition::Ready); select(&mut q, Disposition::Sleeping); for _ in 0..4 { let t = tick(&q, h[1]); q.charge(t).ok().unwrap(); } assert!(q.ready_order().iter().all(Option::is_none)); q.with_backend_observation(|b| { b.bank.with_backend_observation(|bb| { let wait = bb.sleep_wait.borrow_mut().take().unwrap(); assert_eq!(wait.thread(), q.record(h[0]).unwrap().thread); bb.table.borrow_mut().wake(wait).unwrap(); }) }); let before = q.snapshot(); let record = q.record(h[1]).unwrap(); let hardware = q.with_backend_observation(|b| { b.bank .with_backend_observation(|bb| (bb.execution(), bb.registers)) }); let commits = c.borrow().commits; assert_eq!(q.renew(), Err(Error::Busy)); assert_eq!(q.snapshot(), before); assert_eq!(q.record(h[1]).unwrap(), record); assert_eq!(c.borrow().commits, commits); assert_eq!( q.with_backend_observation(|b| b .bank .with_backend_observation(|bb| (bb.execution(), bb.registers))), hardware ); q.requeue(h[0]).unwrap(); assert_eq!(select(&mut q, Disposition::Ready), h[0]); assert_eq!(inventory(&q), (2, 2, 8)); }