fix: add truthful KTAP monitoring diagnostics
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user