#[path = "support/threads_fixture.rs"] mod threads_fixture; use std::{cell::RefCell, rc::Rc}; use turkos_os_address_spaces::spaces; use turkos_os_frame_metadata::metadata; use turkos_os_process_table::process_table::*; use turkos_os_threads::threads; // CPU, RAM backing and ASID maintenance are explicit host models. Thread, // Spaces and Registry transitions below run their real frozen Rust APIs. struct Ram { pages: Vec>, used: Vec, } struct Memory { ram: Rc>, base: u64, } impl Memory { fn new(base: u64) -> Self { Self { ram: Rc::new(RefCell::new(Ram { pages: vec![vec![0xa5; 4096]; 32], used: vec![false; 32], })), base, } } fn index(&self, a: u64) -> Result { let n = a.checked_sub(self.base).ok_or(metadata::Error::Backend)?; if n % 4096 != 0 || n / 4096 >= 32 || !self.ram.borrow().used[n as usize / 4096] { return Err(metadata::Error::Backend); } Ok(n as usize / 4096) } } impl metadata::Backend for Memory { fn allocate(&mut self) -> Result { let mut r = self.ram.borrow_mut(); let i = r .used .iter() .position(|n| !*n) .ok_or(metadata::Error::NoMemory)?; r.used[i] = true; Ok(self.base + i as u64 * 4096) } fn release(&mut self, a: u64) -> Result<(), metadata::Error> { let i = self.index(a)?; let mut r = self.ram.borrow_mut(); r.pages[i].fill(0); r.used[i] = false; Ok(()) } fn zero_page(&mut self, a: u64) -> Result<(), metadata::Error> { let i = self.index(a)?; self.ram.borrow_mut().pages[i].fill(0); Ok(()) } fn read_byte(&self, a: u64, n: usize) -> Result { Ok(self.ram.borrow().pages[self.index(a)?][n]) } fn available_pages(&self) -> u64 { self.ram.borrow().used.iter().filter(|n| !**n).count() as u64 } } struct Root { handle: metadata::Handle, pin: metadata::Pin, owner: u64, } struct RootOwners { registry: metadata::Registry, active: u64, } impl spaces::Backend for RootOwners { type Root = Root; fn acquire_root(&mut self, owner: u64) -> Result { let handle = self .registry .allocate_zeroed(owner) .map_err(|_| spaces::Error::NoMemory)?; let pin = self .registry .pin(handle, owner) .map_err(|_| spaces::Error::Backing)?; Ok(Root { handle, pin, owner }) } fn root_address(&self, r: &Root) -> u64 { r.handle.address() } fn validate_root(&self, r: &Root) -> Result<(), spaces::Error> { self.registry .read_byte(r.handle, r.owner, 4095) .map(|_| ()) .map_err(|_| spaces::Error::Backing) } fn invalidate_recycled(&mut self, _: u16, old: u64, new: u64) { assert!(new > old); } fn select(&mut self, r: &Root, _: u16) -> Result<(), spaces::Error> { self.validate_root(r)?; self.active = r.handle.address(); Ok(()) } fn restore_kernel(&mut self) -> Result<(), spaces::Error> { self.active = 0; Ok(()) } fn retire_root(&mut self, mut r: Root, _: u16, _: u64) -> Result<(), (spaces::Error, Root)> { if self.active == r.handle.address() { return Err((spaces::Error::Active, r)); } self.registry.unpin(&mut r.pin, r.owner).unwrap(); self.registry.release(r.handle, r.owner).unwrap(); Ok(()) } } struct Bag { pid: ProcessId, owner: u64, thread: Option, spaces: [Option; MAX_SPACES], handles: [Option; MAX_HANDLES], pins: [Option; MAX_HANDLES], image_ready: bool, stack_ready: bool, published: bool, } struct Control { execution: Execution, fail_at: usize, publish_failure: Option, exit_failure: Option, release_failure: bool, changed_cpu: u8, incomplete_stack: bool, prepares: usize, publishes: usize, exits: usize, releases: usize, public: Vec, threads: Vec<(ProcessId, threads::ThreadId)>, silent_publication: bool, forged: Option, } struct Driver { control: Rc>, threads: threads_fixture::ThreadOwners, spaces: spaces::Spaces, frames: metadata::Registry, owner: u64, } impl Driver { fn inventory(&self) -> [usize; 4] { [ self.threads.live(), self.spaces.owned_records().count(), self.frames.snapshot().live_pages, self.frames.owned_records().map(|r| r.pins as usize).sum(), ] } fn observe_inner(&self, bag: &Bag) -> Result { let thread = bag.thread.ok_or(Error::Preparation)?; let state = self.threads.record(thread).state; for h in bag.spaces.iter().flatten() { self.spaces .observe(*h, bag.owner) .map_err(|_| Error::Resource)?; } for h in bag.handles.iter().flatten() { self.frames .read_byte(*h, bag.owner, 4095) .map_err(|_| Error::Resource)?; } Ok(BagObservation { process: bag.pid, resource_owner: bag.owner, thread, state, spaces: bag.spaces, handles: bag.handles, pages: 5 + bag.spaces.iter().flatten().count() as u64 + bag.handles.iter().flatten().count() as u64, image_ready: bag.image_ready, stack_ready: bag.stack_ready, published: bag.published, }) } fn activate(&mut self, id: ProcessId) { let tid = self .control .borrow() .threads .iter() .find(|r| r.0 == id) .unwrap() .1; self.threads.activate(tid); let mut c = self.control.borrow_mut(); c.execution.current = Some(id); c.execution.kernel_bootstrap = false; } fn yield_current(&mut self) { let pid = self.control.borrow().execution.current.unwrap(); let tid = self .control .borrow() .threads .iter() .find(|r| r.0 == pid) .unwrap() .1; self.threads.yield_current(tid); self.control.borrow_mut().execution.current = None; } } unsafe impl Backend for Driver { type Bag = Bag; fn execution(&self) -> Execution { self.control.borrow().execution } fn prepare(&mut self, pid: ProcessId, spawn: Spawn) -> Result> { self.control.borrow_mut().prepares += 1; self.owner += 1; let mut bag = Bag { pid, owner: self.owner, thread: None, spaces: [None; MAX_SPACES], handles: [None; MAX_HANDLES], pins: std::array::from_fn(|_| None), image_ready: false, stack_ready: false, published: false, }; let fail_at = self.control.borrow().fail_at; let failure = |bag| BuildFailure { failure: Failure { error: Error::Preparation, boundary: Boundary::Before, }, partial: Some(bag), }; bag.spaces[0] = Some(self.spaces.create(bag.owner).unwrap()); if fail_at == 1 { return Err(failure(bag)); } bag.thread = Some(self.threads.create()); self.control .borrow_mut() .threads .push((pid, bag.thread.unwrap())); if fail_at == 2 { return Err(failure(bag)); } for i in 0..usize::from(spawn.handles) { let h = self.frames.allocate_zeroed(bag.owner).unwrap(); bag.pins[i] = Some(self.frames.pin(h, bag.owner).unwrap()); bag.handles[i] = Some(h); if fail_at == i + 3 { return Err(failure(bag)); } } bag.image_ready = true; bag.stack_ready = !self.control.borrow().incomplete_stack; if self.control.borrow().changed_cpu == 1 { self.control.borrow_mut().execution.cpu = 1; } Ok(bag) } fn observe(&self, b: &Bag) -> Result { let actual = self.observe_inner(b)?; if let Some(mut forged) = self.control.borrow().forged && forged.process != b.pid { forged.process = b.pid; forged.thread = actual.thread; return Ok(forged); } Ok(actual) } fn publish(&mut self, b: &mut Bag) -> Result<(), Failure> { let mut c = self.control.borrow_mut(); c.publishes += 1; if c.silent_publication { return Ok(()); } if c.publish_failure == Some(Boundary::Before) { return Err(Failure { error: Error::Publication, boundary: Boundary::Before, }); } b.published = true; c.public.push(b.pid); if c.changed_cpu == 2 { c.execution.cpu = 1; } if c.publish_failure == Some(Boundary::After) { return Err(Failure { error: Error::Publication, boundary: Boundary::After, }); } Ok(()) } fn exit(&mut self, b: &mut Bag, _: Outcome) -> Result<(), Failure> { let f = self.control.borrow().exit_failure; self.control.borrow_mut().exits += 1; if f == Some(Boundary::Before) { return Err(Failure { error: Error::Backend, boundary: Boundary::Before, }); } self.threads.exit(b.thread.unwrap()); if self.control.borrow().execution.current == Some(b.pid) { self.control.borrow_mut().execution.current = None; } if self.control.borrow().changed_cpu == 3 { self.control.borrow_mut().execution.cpu = 1; } if f == Some(Boundary::After) { return Err(Failure { error: Error::Backend, boundary: Boundary::After, }); } Ok(()) } fn release(&mut self, mut b: Bag) -> Result<(), (Error, Bag)> { self.control.borrow_mut().releases += 1; if self.control.borrow().release_failure { return Err((Error::Backend, b)); } for i in (0..MAX_HANDLES).rev() { if let Some(mut pin) = b.pins[i].take() { self.frames.unpin(&mut pin, b.owner).unwrap(); } if let Some(h) = b.handles[i].take() { self.frames.release(h, b.owner).unwrap(); } } if let Some(t) = b.thread.take() { self.threads.reap(t); } for h in b.spaces.iter_mut().rev() { if let Some(h) = h.take() { self.spaces.release(h, b.owner).unwrap(); } } self.control.borrow_mut().public.retain(|id| *id != b.pid); self.control .borrow_mut() .threads .retain(|(id, _)| *id != b.pid); { let mut c = self.control.borrow_mut(); match c.changed_cpu { 4 => c.execution.cpu = 1, 5 => c.execution.irq_masked = false, 6 => c.execution.current = Some(b.pid), _ => (), } } Ok(()) } } fn setup() -> (Table, Rc>) { let control = Rc::new(RefCell::new(Control { execution: Execution { cpu: 0, in_irq: false, irq_masked: true, current: None, kernel_bootstrap: true, }, fail_at: 0, publish_failure: None, exit_failure: None, release_failure: false, changed_cpu: 0, incomplete_stack: false, prepares: 0, publishes: 0, exits: 0, releases: 0, public: Vec::new(), threads: Vec::new(), silent_publication: false, forged: None, })); let spaces = spaces::Spaces::new( 77, spaces::Configuration::from_registers(2, 25).unwrap(), RootOwners { registry: metadata::Registry::new(83, Memory::new(0x50000000)).unwrap(), active: 0, }, ) .unwrap(); let driver = Driver { control: control.clone(), threads: threads_fixture::ThreadOwners::new(), spaces, frames: metadata::Registry::new(91, Memory::new(0x60000000)).unwrap(), owner: 100, }; (Table::new(driver).ok().unwrap(), control) } fn spec() -> Spawn { Spawn { handles: 3, code_entry: 0x10000, user_stack_top: 0x80000, } } fn spawn(t: &mut Table) -> ProcessId { let r = t.begin(spec()).unwrap(); let r = t.prepare(r).ok().unwrap(); t.publish(r).ok().unwrap() } // Test-only access is deliberately confined to this new fixture module. fn driver(t: &mut Table) -> &mut Driver { // Backend does not escape the product API. This independent fixture needs // a controlled scheduler operation; use the source-only test hook below. t.process_test_backend() } #[test] fn three_processes_reuse_one_pid_slot_with_distinct_full_generations() { let (mut t, c) = setup(); let mut old = Vec::new(); for n in 1..=3 { let id = spawn(&mut t); assert_eq!(id.slot(), 0); assert_eq!(id.generation(), n); for prior in &old { assert_eq!(t.terminate_child(*prior, 9), Err(Error::Stale)); } driver(&mut t).activate(id); t.exit_current(n as i32).unwrap(); c.borrow_mut().execution.kernel_bootstrap = true; assert_eq!(t.wait_child(id), Ok(n as i32)); old.push(id); assert_eq!(driver(&mut t).inventory(), [0, 0, 0, 0]); } } #[test] fn actual_parent_two_children_bind_spaces_handles_and_one_time_results() { let (mut t, _) = setup(); let a = spawn(&mut t); driver(&mut t).activate(a); let b = spawn(&mut t); let d = spawn(&mut t); assert_eq!(t.record(b).unwrap().parent, Some(a)); assert_eq!(t.record(d).unwrap().parent, Some(a)); assert_eq!(driver(&mut t).inventory(), [3, 3, 9, 9]); t.terminate_child(b, 17).unwrap(); t.terminate_child(d, -3).unwrap(); assert_eq!(t.wait_child(b), Ok(17)); assert_eq!(t.wait_child(b), Err(Error::NoChild)); assert_eq!(t.wait_child(d), Ok(-3)); assert_eq!(driver(&mut t).inventory(), [1, 1, 3, 3]); } #[test] fn foreign_parent_wait_and_termination_preserve_all_real_owners() { let (mut t, _) = setup(); let a = spawn(&mut t); driver(&mut t).activate(a); let b = spawn(&mut t); let d = spawn(&mut t); t.terminate_child(d, 42).unwrap(); driver(&mut t).yield_current(); driver(&mut t).activate(b); let before = driver(&mut t).inventory(); let snapshot = t.snapshot(); assert_eq!(t.wait_child(d), Err(Error::Foreign)); assert_eq!(t.terminate_child(d, 9), Err(Error::Foreign)); assert_eq!(t.snapshot(), snapshot); assert_eq!(driver(&mut t).inventory(), before); driver(&mut t).yield_current(); driver(&mut t).activate(a); assert_eq!(t.wait_child(d), Ok(42)); } #[test] fn every_partial_acquisition_failure_rolls_back_without_public_pid_or_generation() { for fail in 1..=5 { let (mut t, c) = setup(); let before = driver(&mut t).inventory(); c.borrow_mut().fail_at = fail; let r = t.begin(spec()).unwrap(); let (e, r) = t.prepare(r).err().unwrap(); assert_eq!(e, Error::Preparation); assert_eq!(driver(&mut t).inventory(), before); assert_eq!(t.published().count(), 0); c.borrow_mut().fail_at = 0; let r = t.prepare(r).ok().unwrap(); let id = t.publish(r).ok().unwrap(); assert_eq!(id.generation(), 1); driver(&mut t).activate(id); t.exit_current(0).unwrap(); c.borrow_mut().execution.kernel_bootstrap = true; assert_eq!(t.wait_child(id), Ok(0)); assert_eq!(driver(&mut t).inventory(), before); } } #[test] fn failed_partial_retirement_retains_actual_bag_and_quarantines_slot() { let (mut t, c) = setup(); c.borrow_mut().fail_at = 3; c.borrow_mut().release_failure = true; let r = t.begin(spec()).unwrap(); let (e, r) = t.prepare(r).err().unwrap(); assert_eq!(e, Error::Backend); assert_eq!(t.snapshot().quarantined, 1); assert_eq!(driver(&mut t).inventory(), [1, 1, 1, 1]); let actual = t.process_test_bag(0).expect("actual returned bag retained"); assert!(actual.thread.is_some()); assert!(actual.spaces[0].is_some()); assert!(actual.handles[0].is_some()); assert!(actual.pins[0].is_some()); assert_eq!(t.published().count(), 0); assert_eq!(t.abort(r).err().unwrap().0, Error::Quarantined); } #[test] fn missing_stack_cannot_call_publication_or_enter_public_pid_list() { let (mut t, c) = setup(); c.borrow_mut().incomplete_stack = true; let r = t.begin(spec()).unwrap(); let (e, r) = t.prepare(r).err().unwrap(); assert_eq!(e, Error::Preparation); assert_eq!(c.borrow().publishes, 0); assert_eq!(t.publish(r).err().unwrap().0, Error::Quarantined); assert_eq!(t.published().count(), 0); assert_eq!(driver(&mut t).inventory(), [1, 1, 3, 3]); } #[test] fn unprepared_reservation_cannot_enqueue_and_abort_preserves_pid_generation() { let (mut t, c) = setup(); let r = t.begin(spec()).unwrap(); let (e, r) = t.publish(r).err().unwrap(); assert_eq!(e, Error::Preparation); assert_eq!(c.borrow().publishes, 0); t.abort(r).unwrap(); assert_eq!(spawn(&mut t).generation(), 1); } #[test] fn before_publication_error_returns_exact_prepared_receipt_for_retry() { let (mut t, c) = setup(); let r = t.begin(spec()).unwrap(); let r = t.prepare(r).ok().unwrap(); c.borrow_mut().publish_failure = Some(Boundary::Before); let before = driver(&mut t).inventory(); let (e, r) = t.publish(r).err().unwrap(); assert_eq!(e, Error::Publication); assert_eq!(driver(&mut t).inventory(), before); assert_eq!(t.snapshot().constructing, 1); assert_eq!(t.published().count(), 0); c.borrow_mut().publish_failure = None; assert_eq!(t.publish(r).ok().unwrap().generation(), 1); } #[test] fn after_publication_error_retains_actual_enqueued_bag_without_public_pid() { let (mut t, c) = setup(); let r = t.begin(spec()).unwrap(); let r = t.prepare(r).ok().unwrap(); c.borrow_mut().publish_failure = Some(Boundary::After); let (e, r) = t.publish(r).err().unwrap(); assert_eq!(e, Error::Publication); assert_eq!(t.snapshot().quarantined, 1); assert_eq!(t.published().count(), 0); assert_eq!(c.borrow().public.len(), 1); assert_eq!(driver(&mut t).inventory(), [1, 1, 3, 3]); assert_eq!(t.abort(r).err().unwrap().0, Error::Quarantined); } #[test] fn changed_actual_cpu_after_prepare_or_publish_never_publishes_pid() { for mode in [1, 2] { let (mut t, c) = setup(); let r = t.begin(spec()).unwrap(); c.borrow_mut().changed_cpu = mode; if mode == 1 { assert_eq!(t.prepare(r).err().unwrap().0, Error::Context); } else { let r = t.prepare(r).ok().unwrap(); assert_eq!(t.publish(r).err().unwrap().0, Error::Quarantined); } assert_eq!(t.snapshot().quarantined, 1); assert_eq!(t.published().count(), 0); } } #[test] fn irq_wrong_cpu_or_unmasked_begin_refuses_before_any_actual_acquisition() { for mode in 0..3 { let (mut t, c) = setup(); { let mut c = c.borrow_mut(); match mode { 0 => c.execution.in_irq = true, 1 => c.execution.cpu = 1, _ => c.execution.irq_masked = false, } } assert_eq!(t.begin(spec()), Err(Error::Context)); assert_eq!(c.borrow().prepares, 0); assert_eq!(driver(&mut t).inventory(), [0, 0, 0, 0]); } } #[test] fn forgotten_reservation_blocks_bootstrap_reuse_without_implicit_resource_return() { let (mut t, c) = setup(); let r = t.begin(spec()).unwrap(); let r = t.prepare(r).ok().unwrap(); std::mem::forget(r); assert_eq!(t.begin(spec()), Err(Error::Busy)); assert_eq!(c.borrow().releases, 0); assert_eq!(driver(&mut t).inventory(), [1, 1, 3, 3]); drop(t); assert_eq!(c.borrow().releases, 0); } #[test] fn public_dead_result_waits_for_successful_actual_resource_retirement() { let (mut t, c) = setup(); let a = spawn(&mut t); driver(&mut t).activate(a); let b = spawn(&mut t); t.terminate_child(b, 19).unwrap(); c.borrow_mut().release_failure = true; let before = driver(&mut t).inventory(); assert_eq!(t.wait_child(b), Err(Error::Backend)); assert_eq!(driver(&mut t).inventory(), before); assert_eq!(t.snapshot().quarantined, 1); assert_eq!(t.wait_child(b), Err(Error::NoChild)); } #[test] fn parent_with_live_child_cannot_return_its_resources_or_be_reused() { let (mut t, c) = setup(); let a = spawn(&mut t); driver(&mut t).activate(a); let _b = spawn(&mut t); t.exit_current(0).unwrap(); c.borrow_mut().execution.kernel_bootstrap = true; assert_eq!(t.wait_child(a), Err(Error::Busy)); assert_eq!(driver(&mut t).inventory(), [2, 2, 6, 6]); } #[test] fn independent_tables_reject_same_slot_generation_foreign_process_and_receipt() { let (mut a, _) = setup(); let (mut b, _) = setup(); let id = spawn(&mut a); assert_eq!(b.record(id), Err(Error::Foreign)); let receipt = b.begin(spec()).unwrap(); assert_eq!(a.abort(receipt).err().unwrap().0, Error::Foreign); } #[test] fn actual_parent_identity_must_be_live_in_this_table_before_child_acquisition() { let (mut a, _) = setup(); let foreign = spawn(&mut a); let (mut b, c) = setup(); c.borrow_mut().execution.current = Some(foreign); assert_eq!(b.begin(spec()), Err(Error::Foreign)); assert_eq!(c.borrow().prepares, 0); assert_eq!(driver(&mut b).inventory(), [0, 0, 0, 0]); } #[test] fn kernel_bootstrap_capability_cannot_be_supplied_by_a_spawn_request() { let (mut t, c) = setup(); c.borrow_mut().execution.kernel_bootstrap = false; assert_eq!(t.begin(spec()), Err(Error::Context)); assert_eq!(c.borrow().prepares, 0); assert_eq!(t.snapshot(), Snapshot::default()); } #[test] fn full_process_table_capacity_refuses_before_any_resource_callback() { let (mut t, c) = setup(); let parent = spawn(&mut t); driver(&mut t).activate(parent); for _ in 0..7 { spawn(&mut t); } let before = driver(&mut t).inventory(); let prepares = c.borrow().prepares; assert_eq!(t.begin(spec()), Err(Error::Capacity)); assert_eq!(driver(&mut t).inventory(), before); assert_eq!(c.borrow().prepares, prepares); } #[test] fn silent_publication_success_cannot_create_a_public_pid_without_actual_enqueue() { let (mut t, c) = setup(); let r = t.begin(spec()).unwrap(); let r = t.prepare(r).ok().unwrap(); c.borrow_mut().silent_publication = true; assert_eq!(t.publish(r).err().unwrap().0, Error::Quarantined); assert_eq!(t.published().count(), 0); assert_eq!(c.borrow().public.len(), 0); assert_eq!(driver(&mut t).inventory(), [1, 1, 3, 3]); } #[test] fn coherent_foreign_resource_bag_cannot_reuse_another_live_process_owners() { let (mut t, c) = setup(); let parent = spawn(&mut t); driver(&mut t).activate(parent); let mut other = t.record(parent).unwrap().resources; other.state = threads::State::Ready; other.published = false; let r = t.begin(spec()).unwrap(); c.borrow_mut().forged = Some(other); assert_eq!(t.prepare(r).err().unwrap().0, Error::Resource); c.borrow_mut().forged = None; assert_eq!(t.snapshot().quarantined, 1); assert_eq!(t.published().count(), 1); assert_eq!(driver(&mut t).inventory(), [2, 2, 6, 6]); } #[test] fn changed_actual_cpu_after_exit_retains_bag_and_refuses_result_delivery() { let (mut t, c) = setup(); let parent = spawn(&mut t); driver(&mut t).activate(parent); let child = spawn(&mut t); c.borrow_mut().changed_cpu = 3; assert_eq!(t.terminate_child(child, 7), Err(Error::Quarantined)); c.borrow_mut().execution.cpu = 0; assert_eq!(t.wait_child(child), Err(Error::NoChild)); assert_eq!(c.borrow().releases, 0); assert_eq!(driver(&mut t).inventory(), [2, 2, 6, 6]); } #[test] fn before_exit_failure_preserves_live_thread_and_delivers_only_later_real_exit_code() { let (mut t, c) = setup(); let parent = spawn(&mut t); driver(&mut t).activate(parent); let child = spawn(&mut t); let before = t.record(child).unwrap(); c.borrow_mut().exit_failure = Some(Boundary::Before); assert_eq!(t.terminate_child(child, 10), Err(Error::Backend)); assert_eq!(t.record(child), Ok(before)); assert_eq!(t.wait_child(child), Err(Error::Busy)); c.borrow_mut().exit_failure = None; t.terminate_child(child, 11).unwrap(); assert_eq!(t.wait_child(child), Ok(11)); } #[test] fn into_backend_returns_actual_table_while_owners_or_current_process_remain() { let (mut t, c) = setup(); let id = spawn(&mut t); let (e, mut t) = t.into_backend().err().unwrap(); assert_eq!(e, Error::Busy); assert_eq!(t.record(id).unwrap().resources.pages, 9); t.terminate_child(id, 0).unwrap(); assert_eq!(t.wait_child(id), Ok(0)); c.borrow_mut().execution.current = Some(id); let (e, t) = t.into_backend().err().unwrap(); assert_eq!(e, Error::Context); c.borrow_mut().execution.current = None; let backend = t.into_backend().ok().unwrap(); assert_eq!(backend.inventory(), [0, 0, 0, 0]); } #[test] fn release_execution_drift_cannot_clear_hidden_owner_or_reuse_pid_slot() { for mode in 4..=6 { let (mut t, c) = setup(); let r = t.begin(spec()).unwrap(); let r = t.prepare(r).ok().unwrap(); c.borrow_mut().changed_cpu = mode; assert_eq!(t.abort(r).err().unwrap().0, Error::Context); assert_eq!(t.snapshot().quarantined, 1); assert_eq!(t.published().count(), 0); assert!(t.process_test_bag(0).is_none()); assert_eq!(driver(&mut t).inventory(), [0, 0, 0, 0]); c.borrow_mut().execution = Execution { cpu: 0, in_irq: false, irq_masked: true, current: None, kernel_bootstrap: true, }; assert_eq!(t.begin(spec()), Err(Error::Busy)); } } #[test] fn child_result_is_not_delivered_when_successful_retirement_changes_cpu_or_parent() { for mode in 4..=6 { let (mut t, c) = setup(); let parent = spawn(&mut t); driver(&mut t).activate(parent); let child = spawn(&mut t); t.terminate_child(child, 31).unwrap(); c.borrow_mut().changed_cpu = mode; assert_eq!(t.wait_child(child), Err(Error::Context)); assert_eq!(t.snapshot().quarantined, 1); assert_eq!(t.snapshot().published, 1); assert_eq!(driver(&mut t).inventory(), [1, 1, 3, 3]); c.borrow_mut().execution = Execution { cpu: 0, in_irq: false, irq_masked: true, current: Some(parent), kernel_bootstrap: false, }; assert_eq!(t.wait_child(child), Err(Error::NoChild)); } } #[test] fn typed_wait_preserves_zero_forty_two_and_real_fault_separately() { let (mut table, control) = setup(); for code in [0, 42] { let id = spawn(&mut table); driver(&mut table).activate(id); table.exit_current(code).unwrap(); control.borrow_mut().execution.kernel_bootstrap = true; assert_eq!(table.record(id).unwrap().outcome, Some(Outcome::Exited(code))); assert_eq!(table.wait_child_outcome(id), Ok(Outcome::Exited(code))); assert_eq!(driver(&mut table).inventory(), [0, 0, 0, 0]); } let id = spawn(&mut table); driver(&mut table).activate(id); let fault = Fault::from_lower_el_data_abort(0x9000_0004, 0x9000_0000).unwrap(); table.fault_current(fault).unwrap(); control.borrow_mut().execution.kernel_bootstrap = true; let before = table.snapshot(); let owners = driver(&mut table).inventory(); assert_eq!(table.record(id).unwrap().exit_code, None); assert_eq!(table.record(id).unwrap().outcome, Some(Outcome::Fault(fault))); assert_eq!(table.wait_child(id), Err(Error::State)); assert_eq!(table.snapshot(), before); assert_eq!(driver(&mut table).inventory(), owners); assert_eq!(table.wait_child_outcome(id), Ok(Outcome::Fault(fault))); assert_eq!(table.wait_child_outcome(id), Err(Error::NoChild)); assert_eq!(driver(&mut table).inventory(), [0, 0, 0, 0]); } #[test] fn single_outcome_before_and_after_child_exit() { let (mut table, _) = setup(); let parent = spawn(&mut table); driver(&mut table).activate(parent); let later = spawn(&mut table); let before = table.snapshot(); let owners = driver(&mut table).inventory(); assert_eq!(table.wait_child_outcome(later), Err(Error::Busy)); assert_eq!(table.snapshot(), before); assert_eq!(driver(&mut table).inventory(), owners); table.terminate_child(later, 0).unwrap(); assert_eq!(table.wait_child_outcome(later), Ok(Outcome::Exited(0))); let after = table.snapshot(); let owners = driver(&mut table).inventory(); assert_eq!(table.wait_child_outcome(later), Err(Error::NoChild)); assert_eq!(table.snapshot(), after); assert_eq!(driver(&mut table).inventory(), owners); let earlier = spawn(&mut table); table.terminate_child(earlier, 42).unwrap(); assert_eq!(table.wait_child_outcome(earlier), Ok(Outcome::Exited(42))); let after = table.snapshot(); let owners = driver(&mut table).inventory(); assert_eq!(table.wait_child_outcome(earlier), Err(Error::NoChild)); assert_eq!(table.snapshot(), after); assert_eq!(driver(&mut table).inventory(), owners); assert_eq!(owners, [1, 1, 3, 3]); } #[test] fn foreign_waiter_cannot_share_one_retained_outcome() { let (mut table, _) = setup(); let parent = spawn(&mut table); driver(&mut table).activate(parent); let child = spawn(&mut table); let foreign = spawn(&mut table); table.terminate_child(child, 17).unwrap(); driver(&mut table).yield_current(); driver(&mut table).activate(foreign); let before = table.snapshot(); let owners = driver(&mut table).inventory(); assert_eq!(table.wait_child_outcome(child), Err(Error::Foreign)); assert_eq!(table.snapshot(), before); assert_eq!(driver(&mut table).inventory(), owners); driver(&mut table).yield_current(); driver(&mut table).activate(parent); assert_eq!(table.wait_child_outcome(child), Ok(Outcome::Exited(17))); let after = table.snapshot(); let owners = driver(&mut table).inventory(); assert_eq!(table.wait_child_outcome(child), Err(Error::NoChild)); assert_eq!(table.snapshot(), after); assert_eq!(driver(&mut table).inventory(), owners); assert_eq!(owners, [2, 2, 6, 6]); }