acdream/tests/AcDream.App.Tests/Rendering/HostQuiescenceGateTests.cs
Erik 18d4b999de refactor(app): own native window callback lifetime
Publish the fixed Silk callback binding before acquisition, quiesce admitted callbacks before teardown, and retain failed physical detach ownership for retry. Preserve the frozen callback order while covering partial event accessors, concurrency, reentrancy, and shutdown completion.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 09:59:10 +02:00

57 lines
1.5 KiB
C#

using AcDream.App.Rendering;
namespace AcDream.App.Tests.Rendering;
public sealed class HostQuiescenceGateTests
{
[Fact]
public async Task ExternalStopWaitsForAdmittedCallbackToReturn()
{
var gate = new HostQuiescenceGate();
using var callbackEntered = new ManualResetEventSlim(false);
using var releaseCallback = new ManualResetEventSlim(false);
int calls = 0;
Task callback = Task.Run(() => gate.Invoke(() =>
{
callbackEntered.Set();
Assert.True(releaseCallback.Wait(TimeSpan.FromSeconds(5)));
calls++;
}));
Assert.True(callbackEntered.Wait(TimeSpan.FromSeconds(5)));
using var stopStarted = new ManualResetEventSlim(false);
Task stop = Task.Run(() =>
{
stopStarted.Set();
gate.StopAccepting();
});
Assert.True(stopStarted.Wait(TimeSpan.FromSeconds(5)));
await Task.Delay(50);
Assert.False(stop.IsCompleted);
releaseCallback.Set();
await callback;
await stop;
gate.Invoke(() => calls++);
Assert.Equal(1, calls);
Assert.False(gate.IsAccepting);
}
[Fact]
public void CallbackCanStopGateReentrantlyWithoutDeadlock()
{
var gate = new HostQuiescenceGate();
int calls = 0;
gate.Invoke(() =>
{
calls++;
gate.StopAccepting();
});
gate.Invoke(() => calls++);
Assert.Equal(1, calls);
Assert.False(gate.IsAccepting);
}
}