acdream/tests/AcDream.App.Tests/Rendering/HostQuiescenceGateTests.cs

75 lines
2.3 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);
Exception? stopError = null;
var stopThread = new Thread(() =>
{
stopStarted.Set();
try
{
gate.StopAccepting();
}
catch (Exception error)
{
stopError = error;
}
})
{
IsBackground = true,
Name = "HostQuiescenceGate external stop contract",
};
stopThread.Start();
bool stopStartedInTime = stopStarted.Wait(TimeSpan.FromSeconds(5));
bool stopBlockedOnCallback = stopStartedInTime && SpinWait.SpinUntil(
() => (stopThread.ThreadState & ThreadState.WaitSleepJoin) != 0,
TimeSpan.FromSeconds(5));
releaseCallback.Set();
await callback;
bool stopJoined = stopThread.Join(TimeSpan.FromSeconds(5));
gate.Invoke(() => calls++);
Assert.True(stopStartedInTime, "the dedicated stop thread did not start");
Assert.True(stopBlockedOnCallback, "stop never waited for the admitted callback");
Assert.True(stopJoined, "stop did not finish after the callback returned");
Assert.Null(stopError);
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);
}
}