Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.Playout.Tests/PlayoutFakes.cs

249 lines
6.7 KiB
C#

using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using MBN_STOCK_WEBVIEW.Playout.Configuration;
using MBN_STOCK_WEBVIEW.Playout.Interop;
using MBN_STOCK_WEBVIEW.Playout.Registration;
using MBN_STOCK_WEBVIEW.Playout.Runtime;
using MBN_STOCK_WEBVIEW.Playout.Safety;
namespace MBN_STOCK_WEBVIEW.Playout.Tests;
internal sealed class FakeK3dSessionFactory : IK3dSessionFactory
{
private readonly Func<FakeK3dSession> _create;
private int _createCount;
public FakeK3dSessionFactory(Func<FakeK3dSession>? create = null)
{
_create = create ?? (() => new FakeK3dSession());
}
public int CreateCount => Volatile.Read(ref _createCount);
public ConcurrentQueue<FakeK3dSession> Sessions { get; } = new();
public IK3dSession Create()
{
Interlocked.Increment(ref _createCount);
var session = _create();
Sessions.Enqueue(session);
return session;
}
}
internal sealed class FakeK3dSession : IK3dSession
{
private int _disposed;
private int _ktapDispatchGate;
private int _ktapDispatchCount;
public bool IsConnected { get; private set; }
public PlayoutKtapConnectState LastKtapConnectState { get; private set; }
public ConcurrentQueue<string> Calls { get; } = new();
public ConcurrentQueue<int> ThreadIds { get; } = new();
public ConcurrentQueue<ApartmentState> ApartmentStates { get; } = new();
public Action<ValidatedPlayoutOptions>? ConnectAction { get; set; }
public Action? BeforeKtapDispatchAction { get; set; }
public int KtapDispatchCount => Volatile.Read(ref _ktapDispatchCount);
public Action? DisconnectAction { get; set; }
public Action<PlayoutCue, int>? PrepareAction { get; set; }
public Action<int>? PlayAction { get; set; }
public Action<int, PlayoutTakeOutScope>? TakeOutAction { get; set; }
public Action? AbandonAction { get; set; }
public Action<string>? CallObserver { get; set; }
public void Connect(ValidatedPlayoutOptions options)
{
Record("Connect");
BeforeKtapDispatchAction?.Invoke();
if (Interlocked.CompareExchange(ref _ktapDispatchGate, 1, 0) != 0)
{
throw new InvalidOperationException("Fake KTAP dispatch was prevented.");
}
LastKtapConnectState = PlayoutKtapConnectState.Attempted;
Interlocked.Increment(ref _ktapDispatchCount);
try
{
ConnectAction?.Invoke(options);
}
catch
{
LastKtapConnectState = PlayoutKtapConnectState.Failed;
throw;
}
LastKtapConnectState = PlayoutKtapConnectState.AcceptedUnconfirmed;
IsConnected = true;
}
public bool TryPreventKtapConnect()
{
var previous = Interlocked.CompareExchange(ref _ktapDispatchGate, 2, 0);
if (previous == 1 && LastKtapConnectState == PlayoutKtapConnectState.NotAttempted)
{
LastKtapConnectState = PlayoutKtapConnectState.Attempted;
}
return previous == 0;
}
public void Disconnect()
{
Record("Disconnect");
DisconnectAction?.Invoke();
IsConnected = false;
}
public void Prepare(PlayoutCue cue, int layoutIndex)
{
Record($"Prepare:{cue.SceneName}");
PrepareAction?.Invoke(cue, layoutIndex);
}
public void Play(int layoutIndex)
{
Record("Play");
PlayAction?.Invoke(layoutIndex);
}
public void TakeOut(int layoutIndex, PlayoutTakeOutScope scope)
{
Record($"TakeOut:{scope}");
TakeOutAction?.Invoke(layoutIndex, scope);
}
public void Abandon()
{
TryPreventKtapConnect();
if (Interlocked.Exchange(ref _disposed, 1) == 0)
{
Record("Abandon");
AbandonAction?.Invoke();
IsConnected = false;
}
}
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
{
Record("Dispose");
IsConnected = false;
}
}
private void Record(string name)
{
Calls.Enqueue(name);
ThreadIds.Enqueue(Environment.CurrentManagedThreadId);
ApartmentStates.Enqueue(Thread.CurrentThread.GetApartmentState());
CallObserver?.Invoke(name);
}
}
internal sealed class FakeRegistrationProbe : IK3dRegistrationProbe
{
private int _probeCount;
public FakeRegistrationProbe(K3dRegistrationReport? report = null)
{
Report = report ?? ReadyReport;
}
public static K3dRegistrationReport ReadyReport { get; } = new(
K3dRegistrationIssue.None,
Is64BitProcess: true,
HasWin64TypeLibrary: true,
IsWin64BinaryAmd64: true,
IsEngineClassReady: true,
IsEventHandlerClassReady: true,
Message: "fake K3D registration ready");
public K3dRegistrationReport Report { get; set; }
public int ProbeCount => Volatile.Read(ref _probeCount);
public K3dRegistrationReport Probe()
{
Interlocked.Increment(ref _probeCount);
return Report;
}
}
internal sealed class FakeTornadoProcessProbe : ITornadoProcessProbe
{
private readonly object _sync = new();
private readonly Queue<TornadoProcessSnapshot> _snapshots = new();
private TornadoProcessSnapshot _last;
public FakeTornadoProcessProbe(params TornadoProcessSnapshot[] snapshots)
{
_last = snapshots.LastOrDefault()
?? new TornadoProcessSnapshot(0, 0, 0);
foreach (var snapshot in snapshots)
{
_snapshots.Enqueue(snapshot);
}
}
public int CaptureCount { get; private set; }
public Regex? LastPattern { get; private set; }
public TornadoProcessSnapshot Capture(Regex? testWindowPattern)
{
lock (_sync)
{
CaptureCount++;
LastPattern = testWindowPattern;
if (_snapshots.Count > 0)
{
_last = _snapshots.Dequeue();
}
return _last;
}
}
}
internal sealed class FakeLiveAuthorization : ILiveAuthorization
{
public FakeLiveAuthorization(bool isAuthorizedForThisLaunch)
{
IsAuthorizedForThisLaunch = isAuthorizedForThisLaunch;
}
public bool IsAuthorizedForThisLaunch { get; }
}
internal sealed class TrackingStaDispatcherFactory : IStaDispatcherFactory
{
private int _createCount;
public int CreateCount => Volatile.Read(ref _createCount);
public ConcurrentQueue<IStaDispatcher> Dispatchers { get; } = new();
public IStaDispatcher Create(int capacity)
{
Interlocked.Increment(ref _createCount);
var dispatcher = new StaDispatcher(capacity);
Dispatchers.Enqueue(dispatcher);
return dispatcher;
}
}