From aebebcae0481da0d2143d254223d680d9ae72783 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 15 May 2026 05:34:33 +0100 Subject: [PATCH] implements PLN-0057 lifecycle tests --- .../src/firmware/firmware.rs | 14 +- .../prometeu-system/src/os/system_os.rs | 121 ++++++++++++++++++ discussion/index.ndjson | 2 +- ...PLN-0057-lifecycle-tests-and-invariants.md | 2 +- 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 2c88ee1f..3d6bf562 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -177,6 +177,8 @@ mod tests { use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource}; use prometeu_hal::syscalls::caps; use prometeu_system::CrashReport; + use prometeu_system::process::ProcessState; + use prometeu_system::task::TaskState; fn halting_program() -> Vec { let code = assemble("HALT").expect("assemble"); @@ -289,7 +291,10 @@ mod tests { firmware.load_cartridge(trapping_game_cartridge()); firmware.tick(&signals, &mut hardware); - assert!(matches!(firmware.state, FirmwareState::GameRunning(_))); + let task_id = match &firmware.state { + FirmwareState::GameRunning(step) => step.task_id, + other => panic!("expected GameRunning state, got {:?}", other), + }; firmware.tick(&signals, &mut hardware); @@ -302,6 +307,13 @@ mod tests { }, other => panic!("expected AppCrashes state, got {:?}", other), } + + let task = firmware.os.task_manager.get(task_id).expect("task should exist"); + let process = + firmware.os.process_manager.get(task.process_id).expect("process should exist"); + + assert_eq!(task.state, TaskState::Crashed); + assert_eq!(process.state, ProcessState::Crashed); } #[test] diff --git a/crates/console/prometeu-system/src/os/system_os.rs b/crates/console/prometeu-system/src/os/system_os.rs index cfc8a92c..ae536400 100644 --- a/crates/console/prometeu-system/src/os/system_os.rs +++ b/crates/console/prometeu-system/src/os/system_os.rs @@ -201,3 +201,124 @@ impl SystemOS { Ok(task.process_id) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::process::ProcessState; + + fn process_state_for_task(os: &SystemOS, task_id: TaskId) -> ProcessState { + let task = os.task_manager.get(task_id).expect("task should exist"); + os.process_manager.get(task.process_id).expect("process should exist").state + } + + #[test] + fn set_foreground_task_marks_task_and_process_running() { + let mut os = SystemOS::new(None); + let task_id = os.create_vm_game_task(42, "Sector Crawl"); + + os.suspend_task(task_id).expect("suspend should succeed"); + os.set_foreground_task(task_id).expect("foreground should succeed"); + + assert_eq!( + os.task_manager.get(task_id).expect("task should exist").state, + TaskState::Foreground + ); + assert_eq!(process_state_for_task(&os, task_id), ProcessState::Running); + } + + #[test] + fn suspend_task_marks_task_and_process_suspended() { + let mut os = SystemOS::new(None); + let task_id = os.create_vm_game_task(42, "Sector Crawl"); + + os.suspend_task(task_id).expect("suspend should succeed"); + + assert_eq!( + os.task_manager.get(task_id).expect("task should exist").state, + TaskState::Suspended + ); + assert_eq!(process_state_for_task(&os, task_id), ProcessState::Suspended); + } + + #[test] + fn resume_task_marks_suspended_task_foreground_and_process_running() { + let mut os = SystemOS::new(None); + let task_id = os.create_vm_game_task(42, "Sector Crawl"); + + os.suspend_task(task_id).expect("suspend should succeed"); + os.resume_task(task_id).expect("resume should succeed"); + + assert_eq!( + os.task_manager.get(task_id).expect("task should exist").state, + TaskState::Foreground + ); + assert_eq!(process_state_for_task(&os, task_id), ProcessState::Running); + } + + #[test] + fn close_task_marks_task_closed_and_process_stopped_without_removing_entities() { + let mut os = SystemOS::new(None); + let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let process_id = os.task_manager.get(task_id).expect("task should exist").process_id; + + os.close_task(task_id).expect("close should succeed"); + + assert_eq!( + os.task_manager.get(task_id).expect("task should remain").state, + TaskState::Closed + ); + assert_eq!( + os.process_manager.get(process_id).expect("process should remain").state, + ProcessState::Stopped + ); + } + + #[test] + fn crash_task_marks_task_and_process_crashed() { + let mut os = SystemOS::new(None); + let task_id = os.create_vm_game_task(42, "Sector Crawl"); + + os.crash_task(task_id, None).expect("crash should succeed"); + + assert_eq!( + os.task_manager.get(task_id).expect("task should exist").state, + TaskState::Crashed + ); + assert_eq!(process_state_for_task(&os, task_id), ProcessState::Crashed); + } + + #[test] + fn lifecycle_operation_missing_task_returns_typed_error() { + let mut os = SystemOS::new(None); + + assert_eq!(os.suspend_task(TaskId(999)), Err(LifecycleError::TaskNotFound(TaskId(999)))); + } + + #[test] + fn lifecycle_operation_missing_process_returns_typed_error() { + let mut os = SystemOS::new(None); + let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let process_id = os.task_manager.get(task_id).expect("task should exist").process_id; + + os.process_manager.mark_stopped(process_id); + os.process_manager.remove_stopped(); + + assert_eq!(os.suspend_task(task_id), Err(LifecycleError::ProcessNotFound(process_id))); + } + + #[test] + fn resume_task_from_non_suspended_state_returns_invalid_transition() { + let mut os = SystemOS::new(None); + let task_id = os.create_vm_game_task(42, "Sector Crawl"); + + assert_eq!( + os.resume_task(task_id), + Err(LifecycleError::InvalidTransition { + task_id, + from: TaskState::Foreground, + operation: LifecycleOperation::Resume, + }) + ); + } +} diff --git a/discussion/index.ndjson b/discussion/index.ndjson index ca894fd5..f2e2d13d 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -30,5 +30,5 @@ {"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"discussion/lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"discussion/lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]} {"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]} -{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[{"id":"AGD-0032","file":"AGD-0032-system-os-lifecycle-process-task-contract.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0025","file":"DEC-0025-systemos-lifecycle-authority-for-tasks-and-processes.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0032"}],"plans":[{"id":"PLN-0055","file":"PLN-0055-systemos-lifecycle-core-api.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]},{"id":"PLN-0056","file":"PLN-0056-firmware-lifecycle-delegation.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]},{"id":"PLN-0057","file":"PLN-0057-lifecycle-tests-and-invariants.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[{"id":"AGD-0032","file":"AGD-0032-system-os-lifecycle-process-task-contract.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0025","file":"DEC-0025-systemos-lifecycle-authority-for-tasks-and-processes.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0032"}],"plans":[{"id":"PLN-0055","file":"PLN-0055-systemos-lifecycle-core-api.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]},{"id":"PLN-0056","file":"PLN-0056-firmware-lifecycle-delegation.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]},{"id":"PLN-0057","file":"PLN-0057-lifecycle-tests-and-invariants.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]}],"lessons":[]} {"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[{"id":"AGD-0033","file":"AGD-0033-system-os-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[{"id":"DEC-0024","file":"DEC-0024-systemos-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14","ref_agenda":"AGD-0033"}],"plans":[{"id":"PLN-0051","file":"PLN-0051-move-vm-runtime-into-services.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0052","file":"PLN-0052-promote-window-manager-to-systemos-service.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0053","file":"PLN-0053-move-logging-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0054","file":"PLN-0054-move-fs-and-memcard-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]}],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0057-lifecycle-tests-and-invariants.md b/discussion/workflow/plans/PLN-0057-lifecycle-tests-and-invariants.md index cf36d39b..99a4b181 100644 --- a/discussion/workflow/plans/PLN-0057-lifecycle-tests-and-invariants.md +++ b/discussion/workflow/plans/PLN-0057-lifecycle-tests-and-invariants.md @@ -2,7 +2,7 @@ id: PLN-0057 ticket: system-os-lifecycle-process-task-contract title: Lifecycle Tests and Invariants -status: open +status: done created: 2026-05-15 ref_decisions: [DEC-0025] tags: [runtime, os, lifecycle, process, task, shell, firmware]