Resolve IntegrationTests-007..010 code-review findings
IntegrationTests-007: the three live test classes contend for shared singletons (one MXAccess COM, one ZB SQL DB, one GLAuth). Added LiveResourcesCollection with DisableParallelization and applied it to all three so they no longer run concurrently. IntegrationTests-008: the three live fact attributes each re-implemented the env-var check. Added IntegrationTestEnvironment.IsEnabled and all three now delegate to it. IntegrationTests-009: reworded the misleading "Mock server call context" XML doc — it is a hand-written stub with no verification behavior. IntegrationTests-010: WaitForMessageAsync ignored cancellation. It now takes an optional CancellationToken linked with the timeout; the smoke test shares one cancellation source with the StreamEvents call context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ using MxGateway.Server.Dashboard;
|
||||
|
||||
namespace MxGateway.IntegrationTests;
|
||||
|
||||
[Collection(LiveResourcesCollection.Name)]
|
||||
public sealed class DashboardLdapLiveTests
|
||||
{
|
||||
[LiveLdapFact]
|
||||
|
||||
@@ -2,6 +2,7 @@ using MxGateway.Server.Galaxy;
|
||||
|
||||
namespace MxGateway.IntegrationTests.Galaxy;
|
||||
|
||||
[Collection(LiveResourcesCollection.Name)]
|
||||
public sealed class GalaxyRepositoryLiveTests
|
||||
{
|
||||
/// <summary>Verifies that the Galaxy Repository can establish a live connection to the ZB database.</summary>
|
||||
|
||||
@@ -18,11 +18,7 @@ public sealed class LiveGalaxyRepositoryFactAttribute : FactAttribute
|
||||
}
|
||||
|
||||
/// <summary>Gets a value indicating whether live Galaxy Repository tests are enabled.</summary>
|
||||
public static bool Enabled =>
|
||||
string.Equals(
|
||||
Environment.GetEnvironmentVariable(EnableVariableName),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
public static bool Enabled => IntegrationTestEnvironment.IsEnabled(EnableVariableName);
|
||||
|
||||
/// <summary>Gets the Galaxy Repository connection string from environment or default.</summary>
|
||||
public static string ConnectionString =>
|
||||
|
||||
@@ -9,9 +9,18 @@ public static class IntegrationTestEnvironment
|
||||
public const string LiveMxAccessEventTimeoutSecondsVariableName = "MXGATEWAY_LIVE_MXACCESS_EVENT_TIMEOUT_SECONDS";
|
||||
|
||||
/// <summary>Gets whether live MXAccess tests are enabled.</summary>
|
||||
public static bool LiveMxAccessTestsEnabled =>
|
||||
public static bool LiveMxAccessTestsEnabled => IsEnabled(LiveMxAccessVariableName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether an opt-in live-test suite is enabled, by comparing the named
|
||||
/// environment variable to <c>1</c>. Shared by every <c>Live*FactAttribute</c>
|
||||
/// so the opt-in check has a single implementation.
|
||||
/// </summary>
|
||||
/// <param name="variableName">The environment variable that gates the suite.</param>
|
||||
/// <returns><see langword="true"/> when the variable is exactly <c>1</c>.</returns>
|
||||
public static bool IsEnabled(string variableName) =>
|
||||
string.Equals(
|
||||
Environment.GetEnvironmentVariable(LiveMxAccessVariableName),
|
||||
Environment.GetEnvironmentVariable(variableName),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
|
||||
|
||||
@@ -12,9 +12,5 @@ public sealed class LiveLdapFactAttribute : FactAttribute
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Enabled =>
|
||||
string.Equals(
|
||||
Environment.GetEnvironmentVariable(EnableVariableName),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
public static bool Enabled => IntegrationTestEnvironment.IsEnabled(EnableVariableName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace MxGateway.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// xUnit collection that serializes every live integration-test class. The live
|
||||
/// suites contend for genuinely shared singletons — one MXAccess COM provider,
|
||||
/// one <c>ZB</c> SQL database, and one GLAuth instance with a per-IP failure
|
||||
/// lockout — so they must not run in parallel with one another. Placing each
|
||||
/// live class in this collection disables xUnit's default cross-class
|
||||
/// parallelism for them while leaving non-live tests free to parallelize.
|
||||
/// </summary>
|
||||
[CollectionDefinition(Name, DisableParallelization = true)]
|
||||
public sealed class LiveResourcesCollection
|
||||
{
|
||||
/// <summary>The collection name applied via <c>[Collection]</c> on live test classes.</summary>
|
||||
public const string Name = "Live external resources";
|
||||
}
|
||||
@@ -17,6 +17,7 @@ using Xunit.Abstractions;
|
||||
|
||||
namespace MxGateway.IntegrationTests;
|
||||
|
||||
[Collection(LiveResourcesCollection.Name)]
|
||||
public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
{
|
||||
private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(15);
|
||||
@@ -40,6 +41,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
string? sessionId = null;
|
||||
RecordingServerStreamWriter<MxEvent>? eventWriter = null;
|
||||
Task? streamTask = null;
|
||||
using CancellationTokenSource streamCancellation = new();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -61,7 +63,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
streamTask = fixture.Service.StreamEvents(
|
||||
new StreamEventsRequest { SessionId = sessionId },
|
||||
eventWriter,
|
||||
new TestServerCallContext());
|
||||
new TestServerCallContext(streamCancellation.Token));
|
||||
|
||||
MxCommandReply registerReply = await fixture.Service.Invoke(
|
||||
CreateRegisterRequest(sessionId),
|
||||
@@ -94,7 +96,8 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
MxEvent dataChange = await eventWriter
|
||||
.WaitForMessageAsync(
|
||||
candidate => candidate.Family == MxEventFamily.OnDataChange,
|
||||
IntegrationTestEnvironment.LiveMxAccessEventTimeout)
|
||||
IntegrationTestEnvironment.LiveMxAccessEventTimeout,
|
||||
streamCancellation.Token)
|
||||
.ConfigureAwait(false);
|
||||
LogEvent(dataChange);
|
||||
|
||||
@@ -560,12 +563,20 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
/// </summary>
|
||||
/// <param name="predicate">Filter the awaited message must satisfy.</param>
|
||||
/// <param name="timeout">The maximum total time to wait.</param>
|
||||
/// <param name="cancellationToken">
|
||||
/// Token observed alongside the timeout so a per-test cancellation (for example the
|
||||
/// gRPC call context's token) aborts the wait promptly instead of hanging until the
|
||||
/// timeout elapses.
|
||||
/// </param>
|
||||
/// <returns>The first message that satisfies the predicate.</returns>
|
||||
public async Task<T> WaitForMessageAsync(
|
||||
Func<T, bool> predicate,
|
||||
TimeSpan timeout)
|
||||
TimeSpan timeout,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using CancellationTokenSource timeoutCancellation = new(timeout);
|
||||
using CancellationTokenSource linkedCancellation =
|
||||
CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellation.Token, cancellationToken);
|
||||
int scanned = 0;
|
||||
|
||||
while (true)
|
||||
@@ -586,7 +597,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
|
||||
try
|
||||
{
|
||||
await messageArrived.WaitAsync(timeoutCancellation.Token).ConfigureAwait(false);
|
||||
await messageArrived.WaitAsync(linkedCancellation.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (timeoutCancellation.IsCancellationRequested)
|
||||
{
|
||||
@@ -598,7 +609,9 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock server call context for testing gRPC calls.
|
||||
/// Minimal <see cref="ServerCallContext"/> stub for invoking the gRPC service
|
||||
/// in-process. It is a hand-written fake with no verification behavior — it
|
||||
/// only supplies the context values the service reads during a call.
|
||||
/// </summary>
|
||||
private sealed class TestServerCallContext(CancellationToken cancellationToken = default) : ServerCallContext
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user