feat: harden isolated Tornado test workflow
This commit is contained in:
@@ -21,14 +21,27 @@ public sealed class DynamicK3dSessionTests
|
||||
new FakeScene(log),
|
||||
connectResult);
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(engine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
|
||||
var exception = await Assert.ThrowsAnyAsync<Exception>(
|
||||
() => dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator);
|
||||
session.Connect(options);
|
||||
using var session = new DynamicK3dSession(activator, releaser);
|
||||
try
|
||||
{
|
||||
session.Connect(options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Assert.False(session.IsConnected);
|
||||
throw;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
@@ -38,6 +51,114 @@ public sealed class DynamicK3dSessionTests
|
||||
Assert.DoesNotContain(scenes.Path, exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain(connectResult.ToString(), exception.Message, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("GetScenePlayer", log.Names);
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
$"Create:{K3dComConstants.KaEventHandlerClassGuid:B}",
|
||||
$"Create:{K3dComConstants.KaEngineClassGuid:B}",
|
||||
"KTAPConnect:1:127.0.0.1:30001:0",
|
||||
"Disconnect",
|
||||
"Release:Engine",
|
||||
"Release:EventHandler"
|
||||
},
|
||||
log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Disconnect"));
|
||||
Assert.Single(log.ThreadIds.Distinct());
|
||||
Assert.All(log.ApartmentStates, state => Assert.Equal(ApartmentState.STA, state));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Connect_WhenPlayerInitializationFails_DisconnectsOnceAndPreservesOriginalFailure()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
var options = ValidatedPlayoutOptions.Create(TestOptions(scenes.Path));
|
||||
var log = new FakeComLog();
|
||||
var originalFailure = new InvalidOperationException("fake original player failure");
|
||||
var engine = new FakeEngine(
|
||||
log,
|
||||
new FakePlayer(log),
|
||||
new FakeScene(log),
|
||||
playerFailure: originalFailure,
|
||||
disconnectFailure: new InvalidOperationException("fake cleanup failure"));
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(engine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator, releaser);
|
||||
session.Connect(options);
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Same(originalFailure, exception);
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
$"Create:{K3dComConstants.KaEventHandlerClassGuid:B}",
|
||||
$"Create:{K3dComConstants.KaEngineClassGuid:B}",
|
||||
"KTAPConnect:1:127.0.0.1:30001:0",
|
||||
"GetScenePlayerOnChannel:9",
|
||||
"Disconnect",
|
||||
"Release:Engine",
|
||||
"Release:EventHandler"
|
||||
},
|
||||
log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Disconnect"));
|
||||
Assert.Single(log.ThreadIds.Distinct());
|
||||
Assert.All(log.ApartmentStates, state => Assert.Equal(ApartmentState.STA, state));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Connect_WhenKtapInvocationThrows_AttemptsDisconnectAndPreservesOriginalFailure()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
var options = ValidatedPlayoutOptions.Create(TestOptions(scenes.Path));
|
||||
var log = new FakeComLog();
|
||||
var originalFailure = new InvalidOperationException("fake original KTAP failure");
|
||||
var engine = new FakeEngine(
|
||||
log,
|
||||
new FakePlayer(log),
|
||||
new FakeScene(log),
|
||||
connectFailure: originalFailure);
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(engine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator, releaser);
|
||||
session.Connect(options);
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Same(originalFailure, exception);
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
$"Create:{K3dComConstants.KaEventHandlerClassGuid:B}",
|
||||
$"Create:{K3dComConstants.KaEngineClassGuid:B}",
|
||||
"KTAPConnect:1:127.0.0.1:30001:0",
|
||||
"Disconnect",
|
||||
"Release:Engine",
|
||||
"Release:EventHandler"
|
||||
},
|
||||
log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Disconnect"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -115,6 +236,40 @@ public sealed class DynamicK3dSessionTests
|
||||
Assert.Equal(typeof(object), create.ReturnType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Abandon_ReleasesEveryComReferenceWithoutDisconnect()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
var options = ValidatedPlayoutOptions.Create(TestOptions(scenes.Path));
|
||||
var log = new FakeComLog();
|
||||
var player = new FakePlayer(log);
|
||||
var scene = new FakeScene(log);
|
||||
var engine = new FakeEngine(log, player, scene);
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(player, "Player"),
|
||||
(engine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 1);
|
||||
|
||||
await dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator, releaser);
|
||||
session.Connect(options);
|
||||
session.Abandon();
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.DoesNotContain("Disconnect", log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:Player"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:Engine"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:EventHandler"));
|
||||
}
|
||||
|
||||
private static PlayoutOptions TestOptions(string sceneDirectory) => new()
|
||||
{
|
||||
Mode = PlayoutMode.Test,
|
||||
@@ -156,6 +311,8 @@ public sealed class DynamicK3dSessionTests
|
||||
_engine = engine;
|
||||
}
|
||||
|
||||
public object EventHandler => _eventHandler;
|
||||
|
||||
public object Create(Guid classId)
|
||||
{
|
||||
_log.Add($"Create:{classId:B}");
|
||||
@@ -174,17 +331,26 @@ public sealed class DynamicK3dSessionTests
|
||||
private readonly FakePlayer _player;
|
||||
private readonly FakeScene _scene;
|
||||
private readonly int _connectResult;
|
||||
private readonly Exception? _playerFailure;
|
||||
private readonly Exception? _disconnectFailure;
|
||||
private readonly Exception? _connectFailure;
|
||||
|
||||
public FakeEngine(
|
||||
FakeComLog log,
|
||||
FakePlayer player,
|
||||
FakeScene scene,
|
||||
int connectResult = 1)
|
||||
int connectResult = 1,
|
||||
Exception? playerFailure = null,
|
||||
Exception? disconnectFailure = null,
|
||||
Exception? connectFailure = null)
|
||||
{
|
||||
_log = log;
|
||||
_player = player;
|
||||
_scene = scene;
|
||||
_connectResult = connectResult;
|
||||
_playerFailure = playerFailure;
|
||||
_disconnectFailure = disconnectFailure;
|
||||
_connectFailure = connectFailure;
|
||||
}
|
||||
|
||||
public int KTAPConnect(
|
||||
@@ -196,12 +362,22 @@ public sealed class DynamicK3dSessionTests
|
||||
{
|
||||
Assert.NotNull(eventHandler);
|
||||
_log.Add($"KTAPConnect:{tcpMode}:{host}:{hostPort}:{clientPort}");
|
||||
if (_connectFailure is not null)
|
||||
{
|
||||
throw _connectFailure;
|
||||
}
|
||||
|
||||
return _connectResult;
|
||||
}
|
||||
|
||||
public object GetScenePlayerOnChannel(int channel)
|
||||
{
|
||||
_log.Add($"GetScenePlayerOnChannel:{channel}");
|
||||
if (_playerFailure is not null)
|
||||
{
|
||||
throw _playerFailure;
|
||||
}
|
||||
|
||||
return _player;
|
||||
}
|
||||
|
||||
@@ -216,7 +392,34 @@ public sealed class DynamicK3dSessionTests
|
||||
|
||||
public void EndTransaction() => _log.Add("EndTransaction");
|
||||
|
||||
public void Disconnect() => _log.Add("Disconnect");
|
||||
public void Disconnect()
|
||||
{
|
||||
_log.Add("Disconnect");
|
||||
if (_disconnectFailure is not null)
|
||||
{
|
||||
throw _disconnectFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeReleaser : ILateBoundComObjectReleaser
|
||||
{
|
||||
private readonly FakeComLog _log;
|
||||
private readonly (object Value, string Name)[] _objects;
|
||||
|
||||
public FakeReleaser(
|
||||
FakeComLog log,
|
||||
params (object Value, string Name)[] objects)
|
||||
{
|
||||
_log = log;
|
||||
_objects = objects;
|
||||
}
|
||||
|
||||
public void Release(object value)
|
||||
{
|
||||
var match = Assert.Single(_objects, item => ReferenceEquals(item.Value, value));
|
||||
_log.Add($"Release:{match.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FakePlayer
|
||||
|
||||
Reference in New Issue
Block a user