462 lines
15 KiB
Rust
462 lines
15 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", name: "set_sprite", 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 asset_backlog_info = meta_for(Syscall::AssetBacklogInfo);
|
|
assert_eq!(asset_backlog_info.arg_slots, 0);
|
|
assert_eq!(asset_backlog_info.ret_slots, 7);
|
|
|
|
let asset_backlog_position = meta_for(Syscall::AssetBacklogPosition);
|
|
assert_eq!(asset_backlog_position.arg_slots, 1);
|
|
assert_eq!(asset_backlog_position.ret_slots, 4);
|
|
|
|
let asset_backlog_move = meta_for(Syscall::AssetBacklogMove);
|
|
assert_eq!(asset_backlog_move.arg_slots, 2);
|
|
assert_eq!(asset_backlog_move.ret_slots, 1);
|
|
|
|
let asset_backlog_promote = meta_for(Syscall::AssetBacklogPromote);
|
|
assert_eq!(asset_backlog_promote.arg_slots, 1);
|
|
assert_eq!(asset_backlog_promote.ret_slots, 1);
|
|
|
|
let asset_backlog_demote = meta_for(Syscall::AssetBacklogDemote);
|
|
assert_eq!(asset_backlog_demote.arg_slots, 1);
|
|
assert_eq!(asset_backlog_demote.ret_slots, 1);
|
|
|
|
let asset_target_status = meta_for(Syscall::AssetTargetStatus);
|
|
assert_eq!(asset_target_status.arg_slots, 2);
|
|
assert_eq!(asset_target_status.ret_slots, 6);
|
|
|
|
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", name: "slot_info", 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);
|
|
}
|