fix: add truthful KTAP monitoring diagnostics
This commit is contained in:
@@ -15,6 +15,14 @@ public sealed class DryRunPlayoutEngineTests
|
||||
Assert.Equal(PlayoutConnectionState.DryRunReady, engine.Status.State);
|
||||
Assert.True(engine.Status.IsCommandAvailable);
|
||||
Assert.False(engine.Status.IsConnected);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.NotAttempted,
|
||||
engine.Status.LastKtapConnectState);
|
||||
Assert.False(engine.Status.KtapConnectAttempted);
|
||||
Assert.False(engine.Status.NetworkMonitoringRecordExpected);
|
||||
Assert.False(engine.Status.NetworkMonitoringCheckRequired);
|
||||
Assert.Null(engine.Status.KtapHelloObserved);
|
||||
Assert.Contains("Network Monitoring", engine.Status.Message, StringComparison.Ordinal);
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.IsDryRun);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ public sealed class DynamicK3dSessionTests
|
||||
catch
|
||||
{
|
||||
Assert.False(session.IsConnected);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.Failed,
|
||||
session.LastKtapConnectState);
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -86,19 +89,29 @@ public sealed class DynamicK3dSessionTests
|
||||
(engine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
var lastKtapConnectState = PlayoutKtapConnectState.NotAttempted;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator, releaser);
|
||||
session.Connect(options);
|
||||
try
|
||||
{
|
||||
session.Connect(options);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lastKtapConnectState = session.LastKtapConnectState;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Same(originalFailure, exception);
|
||||
Assert.Equal(PlayoutKtapConnectState.AcceptedUnconfirmed, lastKtapConnectState);
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
@@ -134,19 +147,29 @@ public sealed class DynamicK3dSessionTests
|
||||
(engine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
var lastKtapConnectState = PlayoutKtapConnectState.NotAttempted;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator, releaser);
|
||||
session.Connect(options);
|
||||
try
|
||||
{
|
||||
session.Connect(options);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lastKtapConnectState = session.LastKtapConnectState;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Same(originalFailure, exception);
|
||||
Assert.Equal(PlayoutKtapConnectState.Failed, lastKtapConnectState);
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
@@ -190,6 +213,9 @@ public sealed class DynamicK3dSessionTests
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator);
|
||||
session.Connect(options);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.AcceptedUnconfirmed,
|
||||
session.LastKtapConnectState);
|
||||
session.Prepare(cue, options.LayoutIndex);
|
||||
session.Play(options.LayoutIndex);
|
||||
session.TakeOut(options.LayoutIndex, takeOutScope);
|
||||
@@ -236,6 +262,88 @@ public sealed class DynamicK3dSessionTests
|
||||
Assert.Equal(typeof(object), create.ReturnType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Connect_WhenComActivationFails_ReportsKtapNotAttempted()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
var options = ValidatedPlayoutOptions.Create(TestOptions(scenes.Path));
|
||||
var originalFailure = new InvalidOperationException("fake activation failure");
|
||||
var lastKtapConnectState = PlayoutKtapConnectState.Failed;
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(
|
||||
new ThrowingActivator(originalFailure));
|
||||
try
|
||||
{
|
||||
session.Connect(options);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lastKtapConnectState = session.LastKtapConnectState;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Same(originalFailure, exception);
|
||||
Assert.Equal(PlayoutKtapConnectState.NotAttempted, lastKtapConnectState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreventKtapConnect_DuringActivation_BlocksLateDispatch()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
using var activationStarted = new ManualResetEventSlim();
|
||||
using var releaseActivation = new ManualResetEventSlim();
|
||||
var options = ValidatedPlayoutOptions.Create(TestOptions(scenes.Path));
|
||||
var log = new FakeComLog();
|
||||
var engine = new FakeEngine(log, new FakePlayer(log), new FakeScene(log));
|
||||
var activator = new BlockingEngineActivator(
|
||||
log,
|
||||
engine,
|
||||
activationStarted,
|
||||
releaseActivation);
|
||||
var session = new DynamicK3dSession(activator);
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
|
||||
try
|
||||
{
|
||||
var connectTask = dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using (session)
|
||||
{
|
||||
session.Connect(options);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None);
|
||||
Assert.True(activationStarted.Wait(TimeSpan.FromSeconds(2)));
|
||||
|
||||
Assert.True(session.TryPreventKtapConnect());
|
||||
releaseActivation.Set();
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => connectTask);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.NotAttempted,
|
||||
session.LastKtapConnectState);
|
||||
Assert.DoesNotContain(log.Names, name =>
|
||||
name.StartsWith("KTAPConnect:", StringComparison.Ordinal));
|
||||
}
|
||||
finally
|
||||
{
|
||||
releaseActivation.Set();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Abandon_ReleasesEveryComReferenceWithoutDisconnect()
|
||||
{
|
||||
@@ -325,6 +433,61 @@ public sealed class DynamicK3dSessionTests
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ThrowingActivator : ILateBoundComActivator
|
||||
{
|
||||
private readonly Exception _exception;
|
||||
|
||||
public ThrowingActivator(Exception exception)
|
||||
{
|
||||
_exception = exception;
|
||||
}
|
||||
|
||||
public object Create(Guid classId) => throw _exception;
|
||||
}
|
||||
|
||||
private sealed class BlockingEngineActivator : ILateBoundComActivator
|
||||
{
|
||||
private readonly FakeComLog _log;
|
||||
private readonly FakeEngine _engine;
|
||||
private readonly ManualResetEventSlim _activationStarted;
|
||||
private readonly ManualResetEventSlim _releaseActivation;
|
||||
private readonly object _eventHandler = new();
|
||||
|
||||
public BlockingEngineActivator(
|
||||
FakeComLog log,
|
||||
FakeEngine engine,
|
||||
ManualResetEventSlim activationStarted,
|
||||
ManualResetEventSlim releaseActivation)
|
||||
{
|
||||
_log = log;
|
||||
_engine = engine;
|
||||
_activationStarted = activationStarted;
|
||||
_releaseActivation = releaseActivation;
|
||||
}
|
||||
|
||||
public object Create(Guid classId)
|
||||
{
|
||||
_log.Add($"Create:{classId:B}");
|
||||
if (classId == K3dComConstants.KaEventHandlerClassGuid)
|
||||
{
|
||||
return _eventHandler;
|
||||
}
|
||||
|
||||
if (classId != K3dComConstants.KaEngineClassGuid)
|
||||
{
|
||||
throw new InvalidOperationException("Unexpected fake CLSID.");
|
||||
}
|
||||
|
||||
_activationStarted.Set();
|
||||
if (!_releaseActivation.Wait(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
throw new TimeoutException("fake activation coordination timeout");
|
||||
}
|
||||
|
||||
return _engine;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FakeEngine
|
||||
{
|
||||
private readonly FakeComLog _log;
|
||||
|
||||
@@ -145,6 +145,12 @@ public sealed class IsolatedTestCommandTests : IDisposable
|
||||
Assert.Equal([TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)], delays);
|
||||
Assert.True(report.Completed);
|
||||
Assert.True(report.ComActivationAttempted);
|
||||
Assert.Equal("accepted-unconfirmed", report.LastKtapConnectState);
|
||||
Assert.True(report.KtapConnectAttempted);
|
||||
Assert.True(report.KtapConnectAccepted);
|
||||
Assert.True(report.NetworkMonitoringRecordExpected);
|
||||
Assert.True(report.NetworkMonitoringCheckRequired);
|
||||
Assert.Null(report.NetworkMonitoringVerified);
|
||||
Assert.False(report.OutcomeUnknown);
|
||||
Assert.Equal(6, report.Steps.Count);
|
||||
Assert.True(report.Cleanup.AdapterDisposed);
|
||||
@@ -163,6 +169,9 @@ public sealed class IsolatedTestCommandTests : IDisposable
|
||||
Assert.Equal(["Connect", "Disconnect", "Dispose"], engine.Calls);
|
||||
Assert.True(report.Completed);
|
||||
Assert.True(report.ComActivationAttempted);
|
||||
Assert.Equal("accepted-unconfirmed", report.LastKtapConnectState);
|
||||
Assert.True(report.NetworkMonitoringRecordExpected);
|
||||
Assert.True(report.NetworkMonitoringCheckRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -177,6 +186,9 @@ public sealed class IsolatedTestCommandTests : IDisposable
|
||||
(_, _) => Task.CompletedTask);
|
||||
|
||||
Assert.Null(report.ComActivationAttempted);
|
||||
Assert.Equal("not-attempted", report.LastKtapConnectState);
|
||||
Assert.False(report.NetworkMonitoringRecordExpected);
|
||||
Assert.False(report.NetworkMonitoringCheckRequired);
|
||||
Assert.Equal(["Connect", "Disconnect", "Dispose"], engine.Calls);
|
||||
}
|
||||
|
||||
@@ -192,6 +204,11 @@ public sealed class IsolatedTestCommandTests : IDisposable
|
||||
(_, _) => Task.CompletedTask);
|
||||
|
||||
Assert.Null(report.ComActivationAttempted);
|
||||
Assert.Equal("attempted", report.LastKtapConnectState);
|
||||
Assert.True(report.KtapConnectAttempted);
|
||||
Assert.Null(report.KtapConnectAccepted);
|
||||
Assert.Null(report.NetworkMonitoringRecordExpected);
|
||||
Assert.True(report.NetworkMonitoringCheckRequired);
|
||||
Assert.True(report.OutcomeUnknown);
|
||||
Assert.Equal(["Connect", "Quarantine", "Dispose"], engine.Calls);
|
||||
Assert.True(report.Cleanup.QuarantineCompleted);
|
||||
@@ -492,7 +509,7 @@ public sealed class IsolatedTestCommandTests : IDisposable
|
||||
|
||||
public bool ThrowOnQuarantine { get; init; }
|
||||
|
||||
public PlayoutStatus Status { get; }
|
||||
public PlayoutStatus Status { get; private set; }
|
||||
|
||||
public event EventHandler<PlayoutStatusChangedEventArgs>? StatusChanged
|
||||
{
|
||||
@@ -553,6 +570,21 @@ public sealed class IsolatedTestCommandTests : IDisposable
|
||||
{
|
||||
Calls.Add(call);
|
||||
var code = _codes.GetValueOrDefault(operation, PlayoutResultCode.Success);
|
||||
if (operation == PlayoutOperation.Connect)
|
||||
{
|
||||
Status = Status with
|
||||
{
|
||||
LastKtapConnectState = code switch
|
||||
{
|
||||
PlayoutResultCode.Success => PlayoutKtapConnectState.AcceptedUnconfirmed,
|
||||
PlayoutResultCode.Failed => PlayoutKtapConnectState.Failed,
|
||||
PlayoutResultCode.OutcomeUnknown or PlayoutResultCode.TimedOut =>
|
||||
PlayoutKtapConnectState.Attempted,
|
||||
_ => PlayoutKtapConnectState.NotAttempted
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return Task.FromResult(new PlayoutResult(
|
||||
operation,
|
||||
code,
|
||||
|
||||
@@ -34,9 +34,13 @@ internal sealed class FakeK3dSessionFactory : IK3dSessionFactory
|
||||
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();
|
||||
@@ -45,6 +49,10 @@ internal sealed class FakeK3dSession : IK3dSession
|
||||
|
||||
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; }
|
||||
@@ -60,10 +68,39 @@ internal sealed class FakeK3dSession : IK3dSession
|
||||
public void Connect(ValidatedPlayoutOptions options)
|
||||
{
|
||||
Record("Connect");
|
||||
ConnectAction?.Invoke(options);
|
||||
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");
|
||||
@@ -91,6 +128,7 @@ internal sealed class FakeK3dSession : IK3dSession
|
||||
|
||||
public void Abandon()
|
||||
{
|
||||
TryPreventKtapConnect();
|
||||
if (Interlocked.Exchange(ref _disposed, 1) == 0)
|
||||
{
|
||||
Record("Abandon");
|
||||
|
||||
@@ -37,6 +37,12 @@ public sealed class TornadoPlayoutEngineTests
|
||||
Assert.Equal(PlayoutResultCode.Success, prepare.Code);
|
||||
Assert.Equal(0, sessionFactory.CreateCount);
|
||||
Assert.Equal(0, registration.ProbeCount);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.NotAttempted,
|
||||
engine.Status.LastKtapConnectState);
|
||||
Assert.False(engine.Status.NetworkMonitoringRecordExpected);
|
||||
Assert.False(engine.Status.NetworkMonitoringCheckRequired);
|
||||
Assert.Contains("Network Monitoring", engine.Status.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -94,6 +100,13 @@ public sealed class TornadoPlayoutEngineTests
|
||||
Assert.Equal(expectedSuccess, result.IsSuccess);
|
||||
Assert.Equal(expectedSuccess ? 1 : 0, sessionFactory.CreateCount);
|
||||
Assert.Equal(expectedSuccess ? 1 : 0, registration.ProbeCount);
|
||||
Assert.Equal(
|
||||
expectedSuccess
|
||||
? PlayoutKtapConnectState.AcceptedUnconfirmed
|
||||
: PlayoutKtapConnectState.NotAttempted,
|
||||
engine.Status.LastKtapConnectState);
|
||||
Assert.Equal(expectedSuccess, engine.Status.NetworkMonitoringRecordExpected);
|
||||
Assert.Equal(expectedSuccess, engine.Status.NetworkMonitoringCheckRequired);
|
||||
Assert.NotNull(process.LastPattern);
|
||||
if (expectedSuccess)
|
||||
{
|
||||
@@ -108,6 +121,169 @@ public sealed class TornadoPlayoutEngineTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestConnect_WhenKtapCallFails_ReportsAttemptedFailureEvidence()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
var session = new FakeK3dSession
|
||||
{
|
||||
ConnectAction = _ => throw new InvalidOperationException("fake KTAP failure")
|
||||
};
|
||||
await using var engine = CreateEngine(
|
||||
TestOptions(scenes.Path, "test-scene"),
|
||||
new FakeK3dSessionFactory(() => session),
|
||||
new FakeRegistrationProbe(),
|
||||
new FakeTornadoProcessProbe(new TornadoProcessSnapshot(1, 1, 0)),
|
||||
liveAuthorized: false);
|
||||
|
||||
var result = await engine.ConnectAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(PlayoutResultCode.Failed, result.Code);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.Failed,
|
||||
engine.Status.LastKtapConnectState);
|
||||
Assert.True(engine.Status.KtapConnectAttempted);
|
||||
Assert.Null(engine.Status.KtapConnectAccepted);
|
||||
Assert.Null(engine.Status.NetworkMonitoringRecordExpected);
|
||||
Assert.True(engine.Status.NetworkMonitoringCheckRequired);
|
||||
Assert.Null(engine.Status.KtapHelloObserved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestConnect_WhenKtapCallTimesOut_PreservesAttemptedUnknownEvidence()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
using var connectStarted = new ManualResetEventSlim();
|
||||
using var releaseConnect = new ManualResetEventSlim();
|
||||
var session = new FakeK3dSession
|
||||
{
|
||||
ConnectAction = _ =>
|
||||
{
|
||||
connectStarted.Set();
|
||||
if (!releaseConnect.Wait(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
throw new TimeoutException("fake test coordination timeout");
|
||||
}
|
||||
}
|
||||
};
|
||||
var options = TestOptions(scenes.Path, "test-scene");
|
||||
options.ConnectTimeoutMilliseconds = 100;
|
||||
await using var engine = CreateEngine(
|
||||
options,
|
||||
new FakeK3dSessionFactory(() => session),
|
||||
new FakeRegistrationProbe(),
|
||||
new FakeTornadoProcessProbe(new TornadoProcessSnapshot(1, 1, 0)),
|
||||
liveAuthorized: false);
|
||||
|
||||
try
|
||||
{
|
||||
var connectTask = engine.ConnectAsync(CancellationToken.None);
|
||||
Assert.True(connectStarted.Wait(TimeSpan.FromSeconds(2)));
|
||||
|
||||
var result = await connectTask;
|
||||
|
||||
Assert.Equal(PlayoutResultCode.OutcomeUnknown, result.Code);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.Attempted,
|
||||
engine.Status.LastKtapConnectState);
|
||||
Assert.True(engine.Status.KtapConnectAttempted);
|
||||
Assert.Null(engine.Status.KtapConnectAccepted);
|
||||
Assert.Null(engine.Status.NetworkMonitoringRecordExpected);
|
||||
Assert.True(engine.Status.NetworkMonitoringCheckRequired);
|
||||
Assert.Equal(PlayoutConnectionState.OutcomeUnknown, engine.Status.State);
|
||||
}
|
||||
finally
|
||||
{
|
||||
releaseConnect.Set();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestConnect_TimeoutDuringActivation_PreventsLateKtapDispatch()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
using var activationStarted = new ManualResetEventSlim();
|
||||
using var releaseActivation = new ManualResetEventSlim();
|
||||
var session = new FakeK3dSession
|
||||
{
|
||||
BeforeKtapDispatchAction = () =>
|
||||
{
|
||||
activationStarted.Set();
|
||||
if (!releaseActivation.Wait(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
throw new TimeoutException("fake activation coordination timeout");
|
||||
}
|
||||
}
|
||||
};
|
||||
var options = TestOptions(scenes.Path, "test-scene");
|
||||
options.ConnectTimeoutMilliseconds = 100;
|
||||
await using var engine = CreateEngine(
|
||||
options,
|
||||
new FakeK3dSessionFactory(() => session),
|
||||
new FakeRegistrationProbe(),
|
||||
new FakeTornadoProcessProbe(new TornadoProcessSnapshot(1, 1, 0)),
|
||||
liveAuthorized: false);
|
||||
|
||||
try
|
||||
{
|
||||
var connectTask = engine.ConnectAsync(CancellationToken.None);
|
||||
Assert.True(activationStarted.Wait(TimeSpan.FromSeconds(2)));
|
||||
|
||||
var result = await connectTask;
|
||||
|
||||
Assert.Equal(PlayoutResultCode.OutcomeUnknown, result.Code);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.NotAttempted,
|
||||
engine.Status.LastKtapConnectState);
|
||||
Assert.False(engine.Status.KtapConnectAttempted);
|
||||
Assert.False(engine.Status.NetworkMonitoringRecordExpected);
|
||||
Assert.False(engine.Status.NetworkMonitoringCheckRequired);
|
||||
Assert.Equal(0, session.KtapDispatchCount);
|
||||
|
||||
releaseActivation.Set();
|
||||
Assert.True(SpinWait.SpinUntil(
|
||||
() => session.Calls.Contains("Dispose"),
|
||||
TimeSpan.FromSeconds(2)));
|
||||
Assert.Equal(0, session.KtapDispatchCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
releaseActivation.Set();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LastKtapEvidence_SurvivesLaterFailureBeforeAnotherDispatch()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
var session = new FakeK3dSession();
|
||||
var createCount = 0;
|
||||
var sessionFactory = new FakeK3dSessionFactory(() =>
|
||||
Interlocked.Increment(ref createCount) == 1
|
||||
? session
|
||||
: throw new InvalidOperationException("fake activation-stage failure"));
|
||||
await using var engine = CreateEngine(
|
||||
TestOptions(scenes.Path, "test-scene"),
|
||||
sessionFactory,
|
||||
new FakeRegistrationProbe(),
|
||||
new FakeTornadoProcessProbe(new TornadoProcessSnapshot(1, 1, 0)),
|
||||
liveAuthorized: false);
|
||||
|
||||
Assert.True((await engine.ConnectAsync(CancellationToken.None)).IsSuccess);
|
||||
Assert.True((await engine.DisconnectAsync(CancellationToken.None)).IsSuccess);
|
||||
|
||||
var secondConnect = await engine.ConnectAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(PlayoutResultCode.Failed, secondConnect.Code);
|
||||
Assert.Equal(PlayoutConnectionState.Faulted, engine.Status.State);
|
||||
Assert.Equal(
|
||||
PlayoutKtapConnectState.AcceptedUnconfirmed,
|
||||
engine.Status.LastKtapConnectState);
|
||||
Assert.True(engine.Status.NetworkMonitoringRecordExpected);
|
||||
Assert.True(engine.Status.NetworkMonitoringCheckRequired);
|
||||
Assert.Equal(2, sessionFactory.CreateCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestWorkflow_ExecutesFakeSessionInCommandOrderOnSta()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user