444 lines
14 KiB
Rust

use super::*;
fn all_syscalls() -> Vec<Syscall> {
domains::all_entries().map(|entry| entry.syscall).collect()
}
#[test]
fn every_syscall_has_metadata() {
for sc in all_syscalls() {
let m = meta_for(sc);
assert_eq!(m.id, sc as u32, "id mismatch for {:?}", sc);
assert!(!m.module.is_empty(), "module must be non-empty for id=0x{:08X}", m.id);
assert!(!m.name.is_empty(), "name must be non-empty for id=0x{:08X}", m.id);
assert!(m.version > 0, "version must be > 0 for id=0x{:08X}", m.id);
}
use std::collections::HashSet;
let mut ids = HashSet::new();
let mut identities = HashSet::new();
let mut count = 0usize;
for entry in domains::all_entries() {
count += 1;
assert!(ids.insert(entry.meta.id), "duplicate syscall id 0x{:08X}", entry.meta.id);
let parsed = Syscall::from_u32(entry.meta.id).expect("id not recognized by enum mapping");
assert_eq!(parsed as u32, entry.meta.id);
let key = (entry.meta.module, entry.meta.name, entry.meta.version);
assert!(
identities.insert(key),
"duplicate canonical identity: ({}.{}, v{})",
entry.meta.module,
entry.meta.name,
entry.meta.version
);
}
assert_eq!(count, all_syscalls().len());
}
#[test]
fn resolver_returns_expected_id_for_known_identity() {
let id = resolve_syscall("gfx2d", "clear", 1).expect("known identity must resolve");
assert_eq!(id.id, 0x1001);
assert_eq!(id.meta.module, "gfx2d");
assert_eq!(id.meta.name, "clear");
assert_eq!(id.meta.version, 1);
}
#[test]
fn resolver_rejects_unknown_identity() {
let res = resolve_syscall("gfx2d", "nonexistent", 1);
assert!(res.is_none());
let requested = [SyscallIdentity { module: "gfx2d", name: "nonexistent", version: 1 }];
let err = resolve_program_syscalls(&requested, 0).unwrap_err();
match err {
LoadError::UnknownSyscall { module, name, version } => {
assert_eq!(module, "gfx2d");
assert_eq!(name, "nonexistent");
assert_eq!(version, 1);
}
_ => panic!("expected UnknownSyscall error"),
}
}
#[test]
fn resolver_rejects_removed_legacy_gfx_set_sprite_identity() {
assert!(resolve_syscall("gfx2d", "set_sprite", 1).is_none());
let requested = [SyscallIdentity { module: "gfx2d", name: "set_sprite", version: 1 }];
let err = resolve_program_syscalls(&requested, caps::ALL).unwrap_err();
assert_eq!(
err,
LoadError::UnknownSyscall { module: "gfx2d".into(), name: "set_sprite".into(), version: 1 }
);
}
#[test]
fn resolver_enforces_capabilities() {
let requested = [SyscallIdentity { module: "gfx2d", name: "clear", version: 1 }];
let err = resolve_program_syscalls(&requested, 0).unwrap_err();
match err {
LoadError::MissingCapability { required, provided, module, name, version } => {
assert_eq!(module, "gfx2d");
assert_eq!(name, "clear");
assert_eq!(version, 1);
assert_ne!(required, 0);
assert_eq!(provided, 0);
}
_ => panic!("expected MissingCapability error"),
}
let ok = resolve_program_syscalls(&requested, caps::GFX2D).expect("must resolve with caps");
assert_eq!(ok.len(), 1);
assert_eq!(ok[0].id, 0x1001);
}
#[test]
fn resolver_exposes_split_render_capabilities() {
let composer = resolve_syscall("composer", "bind_scene", 1).expect("composer resolves");
assert_eq!(composer.meta.caps, caps::COMPOSER);
let gfx2d = resolve_syscall("gfx2d", "clear", 1).expect("gfx2d resolves");
assert_eq!(gfx2d.meta.caps, caps::GFX2D);
let gfxui = resolve_syscall("gfxui", "clear", 1).expect("gfxui resolves");
assert_eq!(gfxui.id, 0x1201);
assert_eq!(gfxui.meta.caps, caps::GFXUI);
assert!(resolve_syscall("gfx", "clear", 1).is_none());
}
#[test]
fn declared_resolver_returns_expected_id_for_known_identity() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}];
let ok =
resolve_declared_program_syscalls(&declared, caps::GFX2D).expect("must resolve with ABI");
assert_eq!(ok.len(), 1);
assert_eq!(ok[0].id, 0x1001);
}
#[test]
fn declared_resolver_rejects_unknown_identity() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx2d".into(),
name: "nonexistent".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::GFX2D).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::UnknownSyscall {
module: "gfx2d".into(),
name: "nonexistent".into(),
version: 1,
}
);
}
#[test]
fn declared_resolver_rejects_missing_capability() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::NONE).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::MissingCapability {
required: caps::GFX2D,
provided: caps::NONE,
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
}
);
}
#[test]
fn declared_resolver_rejects_abi_mismatch() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx2d".into(),
name: "draw_line".into(),
version: 1,
arg_slots: 4,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::GFX2D).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::AbiMismatch {
module: "gfx2d".into(),
name: "draw_line".into(),
version: 1,
declared_arg_slots: 4,
declared_ret_slots: 0,
expected_arg_slots: 5,
expected_ret_slots: 0,
}
);
}
#[test]
fn status_first_syscall_signatures_are_pinned() {
let clear = meta_for(Syscall::GfxClear);
assert_eq!(clear.arg_slots, 1);
assert_eq!(clear.ret_slots, 0);
let fill_rect = meta_for(Syscall::GfxFillRect);
assert_eq!(fill_rect.arg_slots, 5);
assert_eq!(fill_rect.ret_slots, 0);
let draw_line = meta_for(Syscall::GfxDrawLine);
assert_eq!(draw_line.arg_slots, 5);
assert_eq!(draw_line.ret_slots, 0);
let draw_circle = meta_for(Syscall::GfxDrawCircle);
assert_eq!(draw_circle.arg_slots, 4);
assert_eq!(draw_circle.ret_slots, 0);
let draw_disc = meta_for(Syscall::GfxDrawDisc);
assert_eq!(draw_disc.arg_slots, 5);
assert_eq!(draw_disc.ret_slots, 0);
let draw_square = meta_for(Syscall::GfxDrawSquare);
assert_eq!(draw_square.arg_slots, 6);
assert_eq!(draw_square.ret_slots, 0);
let draw_text = meta_for(Syscall::GfxDrawText);
assert_eq!(draw_text.arg_slots, 4);
assert_eq!(draw_text.ret_slots, 0);
let bind_scene = meta_for(Syscall::ComposerBindScene);
assert_eq!(bind_scene.arg_slots, 1);
assert_eq!(bind_scene.ret_slots, 1);
let unbind_scene = meta_for(Syscall::ComposerUnbindScene);
assert_eq!(unbind_scene.arg_slots, 0);
assert_eq!(unbind_scene.ret_slots, 1);
let set_camera = meta_for(Syscall::ComposerSetCamera);
assert_eq!(set_camera.arg_slots, 2);
assert_eq!(set_camera.ret_slots, 0);
let emit_sprite = meta_for(Syscall::ComposerEmitSprite);
assert_eq!(emit_sprite.arg_slots, 9);
assert_eq!(emit_sprite.ret_slots, 1);
let audio_play_sample = meta_for(Syscall::AudioPlaySample);
assert_eq!(audio_play_sample.arg_slots, 5);
assert_eq!(audio_play_sample.ret_slots, 1);
let audio_play = meta_for(Syscall::AudioPlay);
assert_eq!(audio_play.arg_slots, 7);
assert_eq!(audio_play.ret_slots, 1);
let asset_load = meta_for(Syscall::AssetLoad);
assert_eq!(asset_load.arg_slots, 2);
assert_eq!(asset_load.ret_slots, 2);
let asset_commit = meta_for(Syscall::AssetCommit);
assert_eq!(asset_commit.arg_slots, 1);
assert_eq!(asset_commit.ret_slots, 1);
let asset_cancel = meta_for(Syscall::AssetCancel);
assert_eq!(asset_cancel.arg_slots, 1);
assert_eq!(asset_cancel.ret_slots, 1);
let bank_info = meta_for(Syscall::BankInfo);
assert_eq!(bank_info.arg_slots, 1);
assert_eq!(bank_info.ret_slots, 2);
}
#[test]
fn resolver_rejects_removed_bank_slot_info_identity() {
assert!(resolve_syscall("bank", "slot_info", 1).is_none());
let requested = [SyscallIdentity { module: "bank", name: "slot_info", version: 1 }];
let err = resolve_program_syscalls(&requested, caps::ALL).unwrap_err();
assert_eq!(
err,
LoadError::UnknownSyscall { module: "bank".into(), name: "slot_info".into(), version: 1 }
);
}
#[test]
fn declared_resolver_rejects_legacy_status_first_signatures() {
let declared = vec![
prometeu_bytecode::SyscallDecl {
module: "composer".into(),
name: "bind_scene".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
},
prometeu_bytecode::SyscallDecl {
module: "audio".into(),
name: "play_sample".into(),
version: 1,
arg_slots: 5,
ret_slots: 0,
},
prometeu_bytecode::SyscallDecl {
module: "audio".into(),
name: "play".into(),
version: 1,
arg_slots: 7,
ret_slots: 0,
},
prometeu_bytecode::SyscallDecl {
module: "asset".into(),
name: "load".into(),
version: 1,
arg_slots: 3,
ret_slots: 2,
},
prometeu_bytecode::SyscallDecl {
module: "asset".into(),
name: "commit".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
},
prometeu_bytecode::SyscallDecl {
module: "asset".into(),
name: "cancel".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
},
];
for decl in declared {
let err = resolve_declared_program_syscalls(std::slice::from_ref(&decl), caps::ALL)
.expect_err("legacy signature must be rejected");
match err {
DeclaredLoadError::AbiMismatch {
module,
name,
version,
declared_arg_slots,
declared_ret_slots,
expected_arg_slots,
expected_ret_slots,
} => {
assert_eq!(module, decl.module);
assert_eq!(name, decl.name);
assert_eq!(version, decl.version);
assert_eq!(declared_arg_slots, decl.arg_slots);
assert_eq!(declared_ret_slots, decl.ret_slots);
assert!(
expected_arg_slots != declared_arg_slots
|| expected_ret_slots != declared_ret_slots
);
}
other => panic!("expected AbiMismatch, got {:?}", other),
}
}
}
#[test]
fn declared_resolver_accepts_mixed_status_first_surface_as_a_single_module() {
let declared = vec![
prometeu_bytecode::SyscallDecl {
module: "composer".into(),
name: "bind_scene".into(),
version: 1,
arg_slots: 1,
ret_slots: 1,
},
prometeu_bytecode::SyscallDecl {
module: "composer".into(),
name: "unbind_scene".into(),
version: 1,
arg_slots: 0,
ret_slots: 1,
},
prometeu_bytecode::SyscallDecl {
module: "composer".into(),
name: "emit_sprite".into(),
version: 1,
arg_slots: 9,
ret_slots: 1,
},
prometeu_bytecode::SyscallDecl {
module: "audio".into(),
name: "play".into(),
version: 1,
arg_slots: 7,
ret_slots: 1,
},
prometeu_bytecode::SyscallDecl {
module: "asset".into(),
name: "load".into(),
version: 1,
arg_slots: 2,
ret_slots: 2,
},
prometeu_bytecode::SyscallDecl {
module: "asset".into(),
name: "commit".into(),
version: 1,
arg_slots: 1,
ret_slots: 1,
},
];
let resolved =
resolve_declared_program_syscalls(&declared, caps::COMPOSER | caps::AUDIO | caps::ASSET)
.expect("mixed status-first surface must resolve together");
assert_eq!(resolved.len(), declared.len());
assert_eq!(resolved[0].meta.ret_slots, 1);
assert_eq!(resolved[1].meta.ret_slots, 1);
assert_eq!(resolved[2].meta.ret_slots, 1);
assert_eq!(resolved[3].meta.ret_slots, 1);
assert_eq!(resolved[4].meta.ret_slots, 2);
assert_eq!(resolved[5].meta.ret_slots, 1);
}
#[test]
fn memcard_syscall_signatures_are_pinned() {
let slot_count = meta_for(Syscall::MemSlotCount);
assert_eq!(slot_count.module, "mem");
assert_eq!(slot_count.name, "slot_count");
assert_eq!(slot_count.arg_slots, 0);
assert_eq!(slot_count.ret_slots, 2);
let slot_stat = meta_for(Syscall::MemSlotStat);
assert_eq!(slot_stat.arg_slots, 1);
assert_eq!(slot_stat.ret_slots, 5);
let slot_read = meta_for(Syscall::MemSlotRead);
assert_eq!(slot_read.arg_slots, 3);
assert_eq!(slot_read.ret_slots, 3);
let slot_write = meta_for(Syscall::MemSlotWrite);
assert_eq!(slot_write.arg_slots, 3);
assert_eq!(slot_write.ret_slots, 2);
let slot_commit = meta_for(Syscall::MemSlotCommit);
assert_eq!(slot_commit.arg_slots, 1);
assert_eq!(slot_commit.ret_slots, 1);
let slot_clear = meta_for(Syscall::MemSlotClear);
assert_eq!(slot_clear.arg_slots, 1);
assert_eq!(slot_clear.ret_slots, 1);
}