Files
sharpemu/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs
T
kostyaff 6dda6589d0 test: add Kernel/Loader unit tests (22 tests) (#373)
- SelfLoader: reject unknown magic, truncated headers; parse PS5 SELF embedded ELF
- KernelMemory: MapNamedFlexibleMemory/mprotect/munmap argument validation
- KernelEventQueue: create/delete/add/trigger/wait lifecycle

Co-authored-by: OMP <omp@local>
2026-07-18 20:19:55 +03:00

181 lines
6.9 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using Xunit;
namespace SharpEmu.Libs.Tests.Kernel;
public sealed class KernelEventQueueCompatExportsTests
{
private const ulong MemoryBase = 0x1_0000_0000;
private const int MemorySize = 0x4000;
[Fact]
public void CreateEqueue_WritesNonZeroHandleAndSucceeds()
{
var (context, outAddress) = NewContextWithOutSlot();
context[CpuRegister.Rdi] = outAddress;
var result = KernelEventQueueCompatExports.KernelCreateEqueue(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, result);
Assert.True(context.TryReadUInt64(outAddress, out var handle));
Assert.NotEqual(0UL, handle);
Assert.True(KernelEventQueueCompatExports.IsValidEqueue(handle));
}
[Fact]
public void CreateEqueue_NullOutAddressReturnsInvalidArgument()
{
var (context, _) = NewContextWithOutSlot();
context[CpuRegister.Rdi] = 0;
var result = KernelEventQueueCompatExports.KernelCreateEqueue(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT, result);
}
[Fact]
public void DeleteEqueue_RemovesQueueFromRegistry()
{
var (context, outAddress) = NewContextWithOutSlot();
context[CpuRegister.Rdi] = outAddress;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, KernelEventQueueCompatExports.KernelCreateEqueue(context));
Assert.True(context.TryReadUInt64(outAddress, out var handle));
context[CpuRegister.Rdi] = handle;
var result = KernelEventQueueCompatExports.KernelDeleteEqueue(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, result);
Assert.False(KernelEventQueueCompatExports.IsValidEqueue(handle));
}
[Fact]
public void AddUserEvent_OnUnknownQueueReturnsNotFound()
{
var (context, _) = NewContextWithOutSlot();
const ulong unknownHandle = 0xDEAD_BEEF;
context[CpuRegister.Rdi] = unknownHandle;
context[CpuRegister.Rsi] = 42;
var result = KernelEventQueueCompatExports.KernelAddUserEvent(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, result);
}
[Fact]
public void AddUserEvent_OnValidQueueSucceeds()
{
var handle = CreateEqueue();
var (context, _) = NewContextWithOutSlot();
context[CpuRegister.Rdi] = handle;
context[CpuRegister.Rsi] = 0x1234;
var result = KernelEventQueueCompatExports.KernelAddUserEvent(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, result);
}
[Fact]
public void TriggerUserEvent_OnUnknownQueueReturnsNotFound()
{
var (context, _) = NewContextWithOutSlot();
context[CpuRegister.Rdi] = 0xDEAD_BEEF;
context[CpuRegister.Rsi] = 1;
context[CpuRegister.Rdx] = 0;
var result = KernelEventQueueCompatExports.KernelTriggerUserEvent(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, result);
}
[Fact]
public void TriggerUserEvent_OnUnregisteredEventReturnsNotFound()
{
var handle = CreateEqueue();
var (context, _) = NewContextWithOutSlot();
context[CpuRegister.Rdi] = handle;
context[CpuRegister.Rsi] = 0xABCD; // never registered
context[CpuRegister.Rdx] = 0;
var result = KernelEventQueueCompatExports.KernelTriggerUserEvent(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, result);
}
// Full lifecycle: create -> register user event -> trigger -> wait delivers
// the queued event with the registered ident/filter and the trigger data.
// DequeueEvents runs before the blocking path, so a pre-triggered queue
// returns immediately without touching the guest thread scheduler.
[Fact]
public void CreateAddTriggerWait_DeliversTriggeredUserEvent()
{
const ulong eventIdent = 0x4242;
const ulong triggerData = 0x55AA_55AA;
var handle = CreateEqueue();
// Register the user event on the queue.
var (addCtx, _) = NewContextWithOutSlot();
addCtx[CpuRegister.Rdi] = handle;
addCtx[CpuRegister.Rsi] = eventIdent;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelAddUserEvent(addCtx));
// Trigger it with a distinct data payload.
var (triggerCtx, _) = NewContextWithOutSlot();
triggerCtx[CpuRegister.Rdi] = handle;
triggerCtx[CpuRegister.Rsi] = eventIdent;
triggerCtx[CpuRegister.Rdx] = triggerData;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelTriggerUserEvent(triggerCtx));
// Wait should deliver the single pending event without blocking.
var memory = new FakeCpuMemory(MemoryBase, MemorySize);
var waitContext = new CpuContext(memory, Generation.Gen5);
const ulong eventsAddress = MemoryBase + 0x100;
const ulong outCountAddress = MemoryBase + 0x300;
waitContext[CpuRegister.Rdi] = handle;
waitContext[CpuRegister.Rsi] = eventsAddress;
waitContext[CpuRegister.Rdx] = 1; // capacity
waitContext[CpuRegister.Rcx] = outCountAddress;
waitContext[CpuRegister.R8] = 0; // no timeout -> would block, but event is pending
var result = KernelEventQueueCompatExports.KernelWaitEqueue(waitContext);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, result);
Assert.True(waitContext.TryReadUInt32(outCountAddress, out var delivered));
Assert.Equal(1u, delivered);
// KernelEvent layout (0x20): ident(0x00) filter(0x08) flags(0x0A)
// fflags(0x0C) data(0x10) userdata(0x18).
Span<byte> evt = stackalloc byte[0x20];
Assert.True(memory.TryRead(eventsAddress, evt));
Assert.Equal(eventIdent, BinaryPrimitives.ReadUInt64LittleEndian(evt[0x00..]));
Assert.Equal(KernelEventQueueCompatExports.KernelEventFilterUser,
BinaryPrimitives.ReadInt16LittleEndian(evt[0x08..]));
Assert.Equal(triggerData, BinaryPrimitives.ReadUInt64LittleEndian(evt[0x10..]));
}
private static ulong CreateEqueue()
{
var memory = new FakeCpuMemory(MemoryBase, MemorySize);
var context = new CpuContext(memory, Generation.Gen5);
const ulong outAddress = MemoryBase + 0x10;
context[CpuRegister.Rdi] = outAddress;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelCreateEqueue(context));
Assert.True(context.TryReadUInt64(outAddress, out var handle));
return handle;
}
private static (CpuContext Context, ulong OutAddress) NewContextWithOutSlot()
{
var memory = new FakeCpuMemory(MemoryBase, MemorySize);
var context = new CpuContext(memory, Generation.Gen5);
return (context, MemoryBase + 0x10);
}
}