test(host): serialize Central-boot fixtures to close env-var race

CentralDbTestEnvironment sets five process-wide environment variables, and
Program's AddEnvironmentVariables() reads them at an unpredictable point during
host boot. With xUnit collection parallelization on, one fixture's teardown
could clear a var mid-boot for a sibling. Since the secrets adoption (G-4)
three of those keys are ${secret:...} references that fail closed, turning a
previously benign empty value into a SecretNotFoundException that aborts the
boot — an intermittent CI failure.

Adds a HostBootCollection that serializes every fixture booting a real host
while depending on that shared state, folding in the narrower "ActorSystem"
collection so its members stay serialized with each other as before. Site-role
fixtures stay parallel: they call Configuration.Sources.Clear(), dropping the
env-var provider, so they cannot participate in the race.

CentralDbTestEnvironment now also fails fast if two instances are ever live at
once, making a regression (a fixture added outside the collection) deterministic
rather than intermittent — this is what surfaced the disposed-CTS defect fixed
in the previous commit. Fixture teardown is try/finally so a throwing host
teardown can no longer strand the vars for the rest of the run.

Cost: Host.Tests runs ~2m30s -> ~4m10s; the serialized Central-boot classes are
most of the assembly's parallelism.

Fixes: Gitea #15
This commit is contained in:
Joseph Doherty
2026-07-16 23:31:28 -04:00
parent 9110a4eb01
commit d2a6107cdb
8 changed files with 95 additions and 14 deletions
@@ -10,14 +10,11 @@ using ZB.MOM.WW.ScadaBridge.Host.Actors;
namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
[CollectionDefinition("ActorSystem")]
public class ActorSystemCollection : ICollectionFixture<object> { }
/// <summary>
/// Verifies that all expected Central-role actors are created at the correct paths
/// when AkkaHostedService starts.
/// </summary>
[Collection("ActorSystem")]
[Collection(HostBootCollection.Name)]
public class CentralActorPathTests : IAsyncLifetime
{
private WebApplicationFactory<Program>? _factory;
@@ -93,9 +90,20 @@ public class CentralActorPathTests : IAsyncLifetime
public async Task DisposeAsync()
{
_factory?.Dispose();
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", _previousEnv);
_dbEnv?.Dispose();
// try/finally: a throwing host teardown must not strand the process-wide
// environment variables. Without it, one failed _factory.Dispose() leaks
// DOTNET_ENVIRONMENT=Central and the CentralDbTestEnvironment vars into
// every later test in the run.
try
{
_factory?.Dispose();
}
finally
{
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", _previousEnv);
_dbEnv?.Dispose();
}
await Task.CompletedTask;
}
@@ -149,7 +157,7 @@ public class CentralActorPathTests : IAsyncLifetime
/// Verifies that all expected Site-role actors are created at the correct paths
/// when AkkaHostedService starts.
/// </summary>
[Collection("ActorSystem")]
[Collection(HostBootCollection.Name)]
public class SiteActorPathTests : IAsyncLifetime
{
private IHost? _host;