PR006: add status-trap-panic conformance regression tests

This commit is contained in:
bQUARKz 2026-03-09 07:15:38 +00:00
parent 70ffadab6c
commit 91f67e3aaf
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 166 additions and 2 deletions

View File

@ -2,8 +2,10 @@ use super::*;
use prometeu_bytecode::TRAP_TYPE; use prometeu_bytecode::TRAP_TYPE;
use prometeu_bytecode::Value; use prometeu_bytecode::Value;
use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::assembler::assemble;
use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl}; use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta, SyscallDecl};
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::hardware::Hardware;
use prometeu_hal::AudioOpStatus;
use prometeu_hal::asset::AssetOpStatus;
use prometeu_hal::InputSignals; use prometeu_hal::InputSignals;
use prometeu_hal::cartridge::Cartridge; use prometeu_hal::cartridge::Cartridge;
use prometeu_hal::syscalls::caps; use prometeu_hal::syscalls::caps;
@ -25,9 +27,17 @@ fn cartridge_with_program(program: Vec<u8>, capabilities: u64) -> Cartridge {
} }
fn serialized_single_function_module(code: Vec<u8>, syscalls: Vec<SyscallDecl>) -> Vec<u8> { fn serialized_single_function_module(code: Vec<u8>, syscalls: Vec<SyscallDecl>) -> Vec<u8> {
serialized_single_function_module_with_consts(code, vec![], syscalls)
}
fn serialized_single_function_module_with_consts(
code: Vec<u8>,
const_pool: Vec<ConstantPoolEntry>,
syscalls: Vec<SyscallDecl>,
) -> Vec<u8> {
BytecodeModule { BytecodeModule {
version: 0, version: 0,
const_pool: vec![], const_pool,
functions: vec![FunctionMeta { functions: vec![FunctionMeta {
code_offset: 0, code_offset: 0,
code_len: code.len() as u32, code_len: code.len() as u32,
@ -275,3 +285,122 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() {
assert_eq!(runtime.telemetry_current.cycles_used, 0); assert_eq!(runtime.telemetry_current.cycles_used, 0);
assert!(matches!(runtime.last_crash_report, Some(CrashReport::VmInit { .. }))); assert!(matches!(runtime.last_crash_report, Some(CrashReport::VmInit { .. })));
} }
#[test]
fn tick_gfx_set_sprite_operational_error_returns_status_not_crash() {
let mut runtime = VirtualMachineRuntime::new(None);
let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new();
let signals = InputSignals::default();
let code = assemble(
"PUSH_CONST 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 1\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
)
.expect("assemble");
let program = serialized_single_function_module_with_consts(
code,
vec![ConstantPoolEntry::String("missing_tile_bank".into())],
vec![SyscallDecl {
module: "gfx".into(),
name: "set_sprite".into(),
version: 1,
arg_slots: 10,
ret_slots: 1,
}],
);
let cartridge = cartridge_with_program(program, caps::GFX);
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
let report = runtime.tick(&mut vm, &signals, &mut hardware);
assert!(report.is_none(), "operational error must not crash");
assert!(vm.is_halted());
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(1)]);
}
#[test]
fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
let mut runtime = VirtualMachineRuntime::new(None);
let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new();
let signals = InputSignals::default();
let code = assemble("PUSH_I32 -1\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nHOSTCALL 0\nHALT")
.expect("assemble");
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "audio".into(),
name: "play_sample".into(),
version: 1,
arg_slots: 5,
ret_slots: 1,
}],
);
let cartridge = cartridge_with_program(program, caps::AUDIO);
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
let report = runtime.tick(&mut vm, &signals, &mut hardware);
assert!(report.is_none(), "operational error must not crash");
assert!(vm.is_halted());
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::ArgRangeInvalid as i64)]);
}
#[test]
fn tick_asset_commit_operational_error_returns_status_not_crash() {
let mut runtime = VirtualMachineRuntime::new(None);
let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new();
let signals = InputSignals::default();
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "asset".into(),
name: "commit".into(),
version: 1,
arg_slots: 1,
ret_slots: 1,
}],
);
let cartridge = cartridge_with_program(program, caps::ASSET);
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
let report = runtime.tick(&mut vm, &signals, &mut hardware);
assert!(report.is_none(), "operational error must not crash");
assert!(vm.is_halted());
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]);
}
#[test]
fn tick_gfx_set_sprite_type_mismatch_surfaces_trap_not_panic() {
let mut runtime = VirtualMachineRuntime::new(None);
let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new();
let signals = InputSignals::default();
let code = assemble(
"PUSH_BOOL 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 1\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
)
.expect("assemble");
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
name: "set_sprite".into(),
version: 1,
arg_slots: 10,
ret_slots: 1,
}],
);
let cartridge = cartridge_with_program(program, caps::GFX);
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
let report = runtime
.tick(&mut vm, &signals, &mut hardware)
.expect("type mismatch must surface as trap");
match report {
CrashReport::VmTrap { trap } => {
assert_eq!(trap.code, TRAP_TYPE);
assert!(trap.message.contains("Expected string asset_name"));
}
other => panic!("expected VmTrap crash report, got {:?}", other),
}
assert!(matches!(runtime.last_crash_report, Some(CrashReport::VmTrap { .. })));
}

View File

@ -2560,6 +2560,41 @@ mod tests {
} }
} }
#[test]
fn test_status_first_syscall_results_count_mismatch_panic() {
// GfxSetSprite (0x1007) expects 1 result.
let code = assemble(
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nSYSCALL 0x1007",
)
.expect("assemble");
struct BadNativeNoReturn;
impl NativeInterface for BadNativeNoReturn {
fn syscall(
&mut self,
_id: u32,
_args: &[Value],
_ret: &mut HostReturn,
_ctx: &mut HostContext,
) -> Result<(), VmFault> {
// Wrong: status-first syscall must return one status slot.
Ok(())
}
}
let mut vm = new_test_vm(code, vec![]);
vm.set_capabilities(prometeu_hal::syscalls::caps::GFX);
let mut native = BadNativeNoReturn;
let mut ctx = HostContext::new(None);
vm.prepare_call("0");
let report = vm.run_budget(100, &mut native, &mut ctx).expect("run");
match report.reason {
LogicalFrameEndingReason::Panic(msg) => assert!(msg.contains("results mismatch")),
other => panic!("Expected Panic, got {:?}", other),
}
}
#[test] #[test]
fn test_loader_hardening_invalid_magic() { fn test_loader_hardening_invalid_magic() {
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();