PR006: add status-trap-panic conformance regression tests
This commit is contained in:
parent
70ffadab6c
commit
91f67e3aaf
@ -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 { .. })));
|
||||||
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user