mod support; use support::Backend as ModelBackend; use support::*; use turkos_os_context_switch::context::*; #[test] fn os_context_switch_callee_saved_positive() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let ca = capture(&bank, a, &h, 19, false); finish(bank.cooperate(a, b, ca).unwrap()); let cb = capture(&bank, b, &h, 29, false); finish(bank.cooperate(b, a, cb).unwrap()); assert_eq!(bank.record(a).unwrap().image, ca.image); let actual = bank.with_backend_observation(|b| b.registers); if let Image::Cooperative(c) = ca.image { assert_eq!(actual.x19_x30, c.x19_x30); assert_eq!(actual.sp, c.sp); } assert_eq!(bank.snapshot().retained, 2); assert_eq!(h.borrow().pins, 2); } #[test] fn os_context_switch_callee_saved_negative() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let mut c = capture(&bank, a, &h, 19, false); c.abi.offsets[1] += 8; install_capture(&bank, a, &h, c); let before = bank.snapshot(); assert!(matches!(bank.cooperate(a, b, c), Err(Error::Abi))); assert_eq!(bank.snapshot(), before); assert_eq!(h.borrow().prepares, 1); } #[test] fn os_context_switch_simd_positive() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let ca = capture(&bank, a, &h, 0x31, false); finish(bank.cooperate(a, b, ca).unwrap()); assert_eq!( bank.with_backend_observation(|b| b.registers.q), [[0; 2]; 32] ); let cb = capture(&bank, b, &h, 0x73, false); finish(bank.cooperate(b, a, cb).unwrap()); assert_eq!( bank.with_backend_observation(|b| b.registers.q), ca.image.simd().0 ); let fresh = capture(&bank, a, &h, 0x31, false); finish(bank.cooperate(a, b, fresh).unwrap()); assert_eq!( bank.with_backend_observation(|b| b.registers.q), cb.image.simd().0 ); } #[test] fn os_context_switch_simd_negative() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 1, false); bank.with_backend_observation(|b| { b.table .borrow_mut() .exit_current(bank.record(a).unwrap().thread) .unwrap() }); assert!(matches!(bank.cooperate(a, b, c), Err(Error::State))); assert_eq!(bank.retire(a), Err(Error::Active)); assert_eq!(bank.snapshot().active, 1); assert_eq!(h.borrow().pins, 2); } #[test] fn os_context_switch_preempt_positive() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let ca = capture(&bank, a, &h, 23, true); finish(bank.timer(a, b, ca).unwrap()); let cb = capture(&bank, b, &h, 47, true); finish(bank.timer(b, a, cb).unwrap()); assert_eq!(bank.record(a).unwrap().image, ca.image); assert_eq!( bank.with_backend_observation(|b| b.registers.timer), if let Image::Timer(f) = ca.image { Some(f) } else { None } ); } #[test] fn os_context_switch_preempt_negative() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let original = capture(&bank, a, &h, 23, true); h.borrow_mut().critical = 1; assert!(matches!(bank.timer(a, b, original), Err(Error::Context))); let epoch = bank.defer_timer(a, b, original).unwrap(); assert_eq!(bank.deferred_record().unwrap().capture, original); h.borrow_mut().in_irq = false; assert!(matches!( bank.service_deferred(original), Err(Error::Context) )); h.borrow_mut().critical = 0; let fresh = capture(&bank, a, &h, 91, false); finish(bank.service_deferred(fresh).unwrap()); assert_eq!(bank.record(a).unwrap().image, fresh.image); assert_ne!(fresh.image.pc(), original.image.pc()); assert!(bank.deferred_record().is_none()); assert_eq!(bank.last_deferred_record().unwrap().capture, original); assert_eq!(bank.last_deferred_record().unwrap().epoch, epoch); } #[test] fn os_context_switch_idle_positive() { let (mut bank, [_, _], h) = setup_idle(); let r = bank.idle().unwrap().complete().ok().unwrap(); assert_eq!(r.checked_epoch, 1); assert!(r.executed_wfi); assert_eq!(r.wake_epoch, 2); assert!(h.borrow().work); assert_eq!(h.borrow().wfi, 1); assert!(matches!(bank.idle(), Err(Error::Pending))); } #[test] fn os_context_switch_idle_negative() { let (mut bank, [_, _], h) = setup_idle(); let idle = bank.idle().unwrap(); h.borrow_mut().boundary_wake = true; let r = idle.complete().ok().unwrap(); assert_eq!(r.checked_epoch, 2); assert!(r.work_at_handoff); assert!(!r.executed_wfi); assert_eq!(h.borrow().wfi, 0); assert_eq!(h.borrow().wake_epoch, 2); assert_eq!(bank.snapshot().retained, 2); } #[test] fn thousand_modeled_switches_preserve_all_registers_and_inventory() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let mut from = a; let mut to = b; let mut saved = [None, None]; for _ in 0..1000 { let value = if from == a { 17 } else { 43 }; let c = capture(&bank, from, &h, value, false); saved[from.slot()] = Some(c.image); finish(bank.cooperate(from, to, c).unwrap()); if let Some(expected) = saved[to.slot()] { assert_eq!( bank.with_backend_observation(|b| b.registers), ModelBackend::expected(expected) ); } core::mem::swap(&mut from, &mut to); } assert_eq!(bank.snapshot().retained, 2); assert_eq!(h.borrow().pins, 2); assert_eq!(h.borrow().stack_pages, 8); assert_eq!(h.borrow().commits, 1001); } #[test] fn before_switch_returns_same_dispatch_and_unchanged_metadata() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, false); let before = bank.snapshot(); let image = bank.record(a).unwrap().image; let d = bank.cooperate(a, b, c).unwrap(); let actual_prepared = d.with_prepared_observation(|p| p.nonce); h.borrow_mut().commit_fail = Some(FailurePhase::BeforeSwitch); let d = match d.complete() { Err(DispatchError::Before(Error::Backend, d)) => d, _ => panic!(), }; assert_eq!(d.source_capture(), Some(c)); assert_eq!(d.with_prepared_observation(|p| p.nonce), actual_prepared); h.borrow_mut().commit_fail = None; d.abort().unwrap(); assert_eq!(bank.snapshot(), before); assert_eq!(bank.record(a).unwrap().image, image); assert_eq!(h.borrow().commits, 1); assert_eq!(h.borrow().pins, 2); } #[test] fn after_switch_retains_both_bindings_prepared_and_source_capture() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, false); let d = bank.cooperate(a, b, c).unwrap(); h.borrow_mut().commit_fail = Some(FailurePhase::AfterSwitch); assert!(matches!( d.complete(), Err(DispatchError::Quarantined(Error::Backend)) )); let s = bank.snapshot(); assert!(s.faulted && s.retained_switch && s.retained_capture); assert_eq!(s.quarantined, 2); assert_eq!(bank.quarantined_capture(), Some(c)); assert_eq!(bank.retire(a), Err(Error::Quarantined)); assert!(matches!(bank.start(b), Err(Error::Quarantined))); assert_eq!(h.borrow().pins, 2); } #[test] fn finish_failure_is_after_switch_quarantine() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, false); let d = bank.cooperate(a, b, c).unwrap(); h.borrow_mut().finish_fail = true; assert!(matches!( d.complete(), Err(DispatchError::Quarantined(Error::Backend)) )); assert!(bank.snapshot().retained_switch); assert_eq!(h.borrow().pins, 2); } #[test] fn failed_abort_retains_prepared_and_bindings() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, false); let d = bank.cooperate(a, b, c).unwrap(); h.borrow_mut().abort_fail = true; assert_eq!(d.abort(), Err(Error::Backend)); assert!(bank.snapshot().faulted && bank.snapshot().retained_switch); assert_eq!(h.borrow().pins, 2); } #[test] fn changed_cpu_or_daif_before_commit_returns_original_dispatch() { for change in [1, 2] { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, false); let d = bank.cooperate(a, b, c).unwrap(); if change == 1 { h.borrow_mut().cpu = 1; } else { h.borrow_mut().daif &= !128; } let d = match d.complete() { Err(DispatchError::Before(Error::Context, d)) => d, _ => panic!(), }; h.borrow_mut().cpu = 0; h.borrow_mut().daif = 0x3c0; d.abort().unwrap(); assert!(!bank.snapshot().faulted); assert_eq!(h.borrow().commits, 1); } } #[test] fn changed_actual_capture_before_commit_refuses_publication() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, false); let d = bank.cooperate(a, b, c).unwrap(); h.borrow_mut().capture = None; let d = match d.complete() { Err(DispatchError::Before(Error::Capture, d)) => d, _ => panic!(), }; d.abort().unwrap(); assert_eq!(h.borrow().commits, 1); } #[test] fn stale_same_thread_intact_capture_is_not_the_current_handoff() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let old = capture(&bank, a, &h, 17, false); let fresh = capture(&bank, a, &h, 43, false); assert!(fresh.episode > old.episode); let before = bank.snapshot(); let prepares = h.borrow().prepares; { let mut actual = h.borrow_mut(); // The old frame remains intact in the same live owned stack while a // newer assembly handoff is active and its Rust call runs below it. actual.capture = Some(old); actual.sp = fresh.address - 64; } assert!(matches!(bank.cooperate(a, b, old), Err(Error::Capture))); assert_eq!(bank.snapshot(), before); assert_eq!(h.borrow().prepares, prepares); assert_eq!(h.borrow().pins, 2); // A current episode is valid at that same below-frame callback SP. h.borrow_mut().capture = Some(fresh); finish(bank.cooperate(a, b, fresh).unwrap()); } #[test] fn capture_episode_consumes_only_on_success_and_uncertain_episode_is_retained() { for failure in [FailurePhase::BeforeSwitch, FailurePhase::AfterSwitch] { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let mut c = capture(&bank, a, &h, 17, false); c.episode = u64::MAX; install_capture(&bank, a, &h, c); h.borrow_mut().capture_episode = c.episode; h.borrow_mut().commit_fail = Some(failure); let d = bank.cooperate(a, b, c).unwrap(); let quarantined = match d.complete() { Err(DispatchError::Before(Error::Backend, d)) => { assert_eq!(d.source_capture(), Some(c)); h.borrow_mut().commit_fail = None; finish(d); false } Err(DispatchError::Quarantined(Error::Backend)) => true, _ => panic!(), }; if quarantined { assert_eq!(bank.record(a).unwrap().last_capture, 0); assert_eq!(bank.quarantined_capture(), Some(c)); assert_eq!(bank.snapshot().retained_bindings, 2); assert!(bank.snapshot().faulted); } else { assert_eq!(bank.record(a).unwrap().last_capture, u64::MAX); assert_eq!(bank.record(a).unwrap().image, c.image); } } } #[test] fn corrupted_restored_callee_simd_fpcr_and_timer_fail_closed() { for bad in 1..=7 { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let ca = capture(&bank, a, &h, 17, true); finish(bank.timer(a, b, ca).unwrap()); let cb = capture(&bank, b, &h, 43, true); let d = bank.timer(b, a, cb).unwrap(); h.borrow_mut().bad_registers = bad; assert!(matches!(d.complete(), Err(DispatchError::Quarantined(_)))); assert!(bank.snapshot().faulted && bank.snapshot().retained_switch); assert_eq!(h.borrow().pins, 2); } } #[test] fn guard_canary_stack_alignment_and_capture_address_refuse_before_prepare() { for damage in 0..4 { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let mut c = capture(&bank, a, &h, 17, false); if damage == 0 { h.borrow_mut().corrupt_guard = true; } if damage == 1 { h.borrow_mut().corrupt_canary = true; } if damage == 2 { c.address += 8; install_capture(&bank, a, &h, c); } if damage == 3 { if let Image::Cooperative(ref mut f) = c.image { f.sp += 8; } install_capture(&bank, a, &h, c); } assert!(bank.cooperate(a, b, c).is_err()); assert_eq!(h.borrow().prepares, 1); assert_eq!(h.borrow().pins, 2); } } #[test] fn malformed_timer_origin_status_and_every_frame_owner_field_refuse() { for damage in 0..7 { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let mut c = capture(&bank, a, &h, 17, true); if let Image::Timer(ref mut f) = c.image { match damage { 0 => f.vector_slot = 4, 1 => f.spsr |= 128, 2 => f.spsr |= 1 << 20, 3 => f.current_el = 8, 4 => f.canary ^= 1, 5 => f.interrupted_sp += 16, 6 => f.padding = 1, _ => {} } } install_capture(&bank, a, &h, c); assert!(bank.timer(a, b, c).is_err()); assert_eq!(h.borrow().prepares, 1); } } #[test] fn deferred_prepare_failure_retains_original_episode_and_actual_owner() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let original = capture(&bank, a, &h, 17, true); h.borrow_mut().critical = 1; bank.defer_timer(a, b, original).unwrap(); let before = bank.deferred_record(); h.borrow_mut().critical = 0; let fresh = capture(&bank, a, &h, 43, false); h.borrow_mut().prepare_fail = true; assert!(matches!(bank.service_deferred(fresh), Err(Error::Backend))); assert_eq!(bank.deferred_record(), before); assert_eq!(bank.snapshot().active, 1); assert_eq!(h.borrow().pins, 2); } #[test] fn duplicate_deferred_episode_and_target_retire_refuse() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let c = capture(&bank, a, &h, 17, true); h.borrow_mut().critical = 1; bank.defer_timer(a, b, c).unwrap(); let original = bank.deferred_record(); assert_eq!(bank.defer_timer(a, b, c), Err(Error::Pending)); h.borrow_mut().in_irq = false; assert_eq!(bank.retire(b), Err(Error::Active)); assert_eq!(bank.deferred_record(), original); } #[test] fn idle_before_failure_preserves_same_episode_and_wake_authority() { let (mut bank, [_, _], h) = setup_idle(); let idle = bank.idle().unwrap(); h.borrow_mut().idle_fail = Some(FailurePhase::BeforeSwitch); let idle = match idle.complete() { Err(DispatchError::Before(Error::Backend, idle)) => idle, _ => panic!(), }; assert_eq!(idle.epoch(), 1); h.borrow_mut().idle_fail = None; h.borrow_mut().boundary_wake = true; let r = idle.complete().ok().unwrap(); assert!(!r.executed_wfi); assert_eq!(h.borrow().wfi, 0); assert!(!bank.snapshot().faulted); } #[test] fn idle_after_failure_and_bad_atomic_receipt_retain_wake_custody() { for bad in 0..5 { let (mut bank, [_, _], h) = setup_idle(); let idle = bank.idle().unwrap(); if bad == 0 { h.borrow_mut().idle_fail = Some(FailurePhase::AfterSwitch); } else { h.borrow_mut().idle_receipt_bad = bad; h.borrow_mut().work = true; } assert!(matches!( idle.complete(), Err(DispatchError::Quarantined(_)) )); assert!(bank.snapshot().faulted && bank.snapshot().retained_idle); assert_eq!(h.borrow().pins, 2); assert!(matches!(bank.idle(), Err(Error::Quarantined))); } } #[test] fn retire_requires_dead_different_stack_and_releases_actual_pin_then_stack() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let ca = capture(&bank, a, &h, 17, false); finish(bank.cooperate(a, b, ca).unwrap()); assert_eq!(bank.retire(a), Err(Error::Active)); bank.with_backend_observation(|back| { back.table .borrow_mut() .cancel(bank.record(a).unwrap().thread) .unwrap() }); let id = bank.record(a).unwrap().thread; bank.retire(a).unwrap(); assert_eq!(h.borrow().pins, 1); bank.with_backend_observation(|back| back.table.borrow_mut().reap(id).unwrap()); assert_eq!(h.borrow().stack_pages, 4); assert!( bank.with_backend_observation(|back| back.stacks[a.slot()] .borrow() .iter() .all(|v| *v == 0)) ); assert!(matches!(bank.record(a), Err(Error::Stale))); } #[test] fn failed_pin_release_retains_same_binding_and_refuses_reuse() { let (mut bank, [a, b], h) = setup(); finish(bank.start(a).unwrap()); let ca = capture(&bank, a, &h, 17, false); finish(bank.cooperate(a, b, ca).unwrap()); bank.with_backend_observation(|back| { back.table .borrow_mut() .cancel(bank.record(a).unwrap().thread) .unwrap() }); h.borrow_mut().release_fail = true; assert_eq!(bank.retire(a), Err(Error::Backend)); assert_eq!(bank.record(a).unwrap().state, State::Quarantined); assert_eq!(bank.snapshot().retained_bindings, 2); assert_eq!(h.borrow().pins, 2); assert_eq!(bank.retire(a), Err(Error::Quarantined)); } #[test] fn constructor_and_attach_capacity_return_same_actual_resources() { let (b, mut pins, h) = backend(2); let b = match ContextBank::<_, 0>::new(b) { Err((Error::Configuration, b)) => b, _ => panic!(), }; let mut bank = ContextBank::<_, 1>::new(b).ok().unwrap(); bank.attach(pins.remove(0)).ok().unwrap(); let pin = pins.remove(0); let id = pin.id; let returned = match bank.attach(pin) { Err((Error::Capacity, p)) => p, _ => panic!(), }; assert_eq!(returned.id, id); assert_eq!(returned.nonce, id.generation()); assert_eq!(h.borrow().pins, 2); assert_eq!(bank.snapshot().retained, 1); } #[test] fn foreign_context_bank_handle_never_selects_matching_slot_generation() { let (mut first, [a, _], _) = setup(); let (mut second, [_, b], _) = setup(); assert!(matches!(first.start(b), Err(Error::Stale))); assert!(matches!(second.start(a), Err(Error::Stale))); assert_eq!(first.snapshot().ready, 2); assert_eq!(second.snapshot().ready, 2); } #[test] fn retained_actual_ready_thread_prevents_idle() { let (mut bank, [a, _], h) = setup(); finish(bank.start(a).unwrap()); h.borrow_mut().work = false; assert!(matches!(bank.idle(), Err(Error::Pending))); assert_eq!(h.borrow().wfi, 0); assert_eq!(h.borrow().pins, 2); } #[test] fn actual_sleep_episode_wakes_then_saved_context_runs_without_reset() { let (mut bank, [a, b], h) = setup_idle(); let id = bank.record(b).unwrap().thread; let epoch = bank.with_backend_observation(|back| back.sleep_wait.as_ref().unwrap().epoch()); let r = bank.idle().unwrap().complete().ok().unwrap(); assert!(r.executed_wfi); let record = bank.with_backend_observation(|back| back.table.borrow().record(id).unwrap()); assert_eq!(record.state, turkos_os_threads::threads::State::Ready); assert_eq!(record.wait_epoch, epoch); let captured = capture(&bank, a, &h, 17, false); finish(bank.cooperate(a, b, captured).unwrap()); assert_eq!( bank.with_backend_observation(|back| back.table.borrow().record(id).unwrap().state), turkos_os_threads::threads::State::Running ); assert_eq!( bank.with_backend_observation(|back| back.registers), ModelBackend::expected(bank.record(b).unwrap().image) ); assert_eq!(h.borrow().pins, 2); } #[test] fn critical_or_unsafe_idle_refuses_with_same_real_wait_owner() { for critical in [true, false] { let (mut bank, [_, b], h) = setup_idle(); if critical { h.borrow_mut().critical = 1; } else { h.borrow_mut().safe = false; } let id = bank.record(b).unwrap().thread; assert!(matches!(bank.idle(), Err(Error::Context))); assert_eq!( bank.with_backend_observation(|back| back.sleep_wait.as_ref().unwrap().thread()), id ); assert_eq!(h.borrow().wfi, 0); } } #[test] fn idle_abort_failure_keeps_exact_actual_wait_in_quarantine() { let (mut bank, [_, b], h) = setup_idle(); let id = bank.record(b).unwrap().thread; let idle = bank.idle().unwrap(); let epoch = idle.with_prepared_observation(|p| p.wait.as_ref().unwrap().epoch()); h.borrow_mut().abort_fail = true; assert_eq!(idle.abort(), Err(Error::Backend)); assert!(bank.snapshot().faulted && bank.snapshot().retained_idle); assert_eq!( bank.with_retained_idle_observation(|p| p.unwrap().wait.as_ref().unwrap().thread()), id ); assert_eq!( bank.with_retained_idle_observation(|p| p.unwrap().wait.as_ref().unwrap().epoch()), epoch ); assert_eq!(h.borrow().pins, 2); } #[test] fn idle_changed_context_returns_same_actual_wait_before_handoff() { let (mut bank, [_, b], h) = setup_idle(); let id = bank.record(b).unwrap().thread; let idle = bank.idle().unwrap(); let epoch = idle.with_prepared_observation(|p| p.wait.as_ref().unwrap().epoch()); h.borrow_mut().critical = 1; let idle = match idle.complete() { Err(DispatchError::Before(Error::Context, p)) => p, _ => panic!(), }; assert_eq!( idle.with_prepared_observation(|p| p.wait.as_ref().unwrap().thread()), id ); assert_eq!( idle.with_prepared_observation(|p| p.wait.as_ref().unwrap().epoch()), epoch ); h.borrow_mut().critical = 0; idle.abort().unwrap(); assert_eq!( bank.with_backend_observation(|back| back.sleep_wait.as_ref().unwrap().thread()), id ); assert_eq!(h.borrow().wfi, 0); }