use super::*; fn all_syscalls() -> Vec { 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); }