using System.Collections.Concurrent; using MBN_STOCK_WEBVIEW.Playout.Runtime; namespace MBN_STOCK_WEBVIEW.Playout.Tests; public sealed class StaDispatcherTests { [Fact] public async Task InvokeAsync_RunsCallbacksOnOneStaThreadInFifoOrder() { await using var dispatcher = new StaDispatcher(capacity: 4); using var firstStarted = new ManualResetEventSlim(); using var releaseFirst = new ManualResetEventSlim(); var calls = new ConcurrentQueue<(int Order, int ThreadId, ApartmentState Apartment)>(); var first = dispatcher.InvokeAsync( () => { calls.Enqueue(( 1, Environment.CurrentManagedThreadId, Thread.CurrentThread.GetApartmentState())); firstStarted.Set(); Assert.True(releaseFirst.Wait(TimeSpan.FromSeconds(5))); return 1; }, TimeSpan.FromSeconds(10), CancellationToken.None); Assert.True(firstStarted.Wait(TimeSpan.FromSeconds(5))); var second = dispatcher.InvokeAsync( () => { calls.Enqueue(( 2, Environment.CurrentManagedThreadId, Thread.CurrentThread.GetApartmentState())); return 2; }, TimeSpan.FromSeconds(10), CancellationToken.None); var third = dispatcher.InvokeAsync( () => { calls.Enqueue(( 3, Environment.CurrentManagedThreadId, Thread.CurrentThread.GetApartmentState())); return 3; }, TimeSpan.FromSeconds(10), CancellationToken.None); releaseFirst.Set(); var results = await Task.WhenAll(first, second, third); Assert.Equal(new[] { 1, 2, 3 }, results); var recorded = calls.ToArray(); Assert.Equal([1, 2, 3], recorded.Select(call => call.Order)); Assert.Single(recorded.Select(call => call.ThreadId).Distinct()); Assert.All(recorded, call => Assert.Equal(ApartmentState.STA, call.Apartment)); Assert.Equal(recorded[0].ThreadId, dispatcher.ManagedThreadId); } [Fact] public async Task InvokeAsync_CancelledWhileQueued_DoesNotRunCallback() { await using var dispatcher = new StaDispatcher(capacity: 2); using var firstStarted = new ManualResetEventSlim(); using var releaseFirst = new ManualResetEventSlim(); var cancelledCallbackRan = false; var first = dispatcher.InvokeAsync( () => { firstStarted.Set(); Assert.True(releaseFirst.Wait(TimeSpan.FromSeconds(5))); return 1; }, TimeSpan.FromSeconds(10), CancellationToken.None); Assert.True(firstStarted.Wait(TimeSpan.FromSeconds(5))); using var cancellation = new CancellationTokenSource(); var cancelled = dispatcher.InvokeAsync( () => { cancelledCallbackRan = true; return 2; }, TimeSpan.FromSeconds(10), cancellation.Token); cancellation.Cancel(); releaseFirst.Set(); await Assert.ThrowsAnyAsync(() => cancelled); Assert.Equal(1, await first); Assert.False(cancelledCallbackRan); } [Fact] public async Task InvokeAsync_WhenBoundedQueueIsFull_CanCancelWithoutRunning() { await using var dispatcher = new StaDispatcher(capacity: 1); using var firstStarted = new ManualResetEventSlim(); using var releaseFirst = new ManualResetEventSlim(); var thirdCallbackRan = false; var first = dispatcher.InvokeAsync( () => { firstStarted.Set(); Assert.True(releaseFirst.Wait(TimeSpan.FromSeconds(5))); return 1; }, TimeSpan.FromSeconds(10), CancellationToken.None); Assert.True(firstStarted.Wait(TimeSpan.FromSeconds(5))); var second = dispatcher.InvokeAsync( () => 2, TimeSpan.FromSeconds(10), CancellationToken.None); using var cancellation = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); var third = dispatcher.InvokeAsync( () => { thirdCallbackRan = true; return 3; }, TimeSpan.FromSeconds(10), cancellation.Token); await Assert.ThrowsAnyAsync(() => third); Assert.False(thirdCallbackRan); releaseFirst.Set(); var results = await Task.WhenAll(first, second); Assert.Equal(new[] { 1, 2 }, results); } [Fact] public async Task InvokeAsync_TimeoutQuarantinesDispatcherAndRejectsLaterWork() { await using var dispatcher = new StaDispatcher(capacity: 2); using var callbackStarted = new ManualResetEventSlim(); using var releaseCallback = new ManualResetEventSlim(); var timedOut = dispatcher.InvokeAsync( () => { callbackStarted.Set(); Assert.True(releaseCallback.Wait(TimeSpan.FromSeconds(5))); return 1; }, TimeSpan.FromMilliseconds(100), CancellationToken.None); Assert.True(callbackStarted.Wait(TimeSpan.FromSeconds(5))); await Assert.ThrowsAsync(() => timedOut); Assert.True(dispatcher.IsQuarantined); await Assert.ThrowsAsync( () => dispatcher.InvokeAsync( () => 2, TimeSpan.FromSeconds(1), CancellationToken.None)); releaseCallback.Set(); } }