test(batch55): port 75 NoRace integration tests

Ports 51 tests from norace_1_test.go and 24 tests from norace_2_test.go
as [SkippableFact] integration tests. Creates test harness infrastructure
(IntegrationTestBase, CheckHelper, NatsTestClient, TestServerHelper,
TestCluster) and tags all tests with [Trait("Category", "NoRace")].
Tests skip unless NATS_INTEGRATION_ENABLED=true is set.
This commit is contained in:
Joseph Doherty
2026-03-01 12:17:07 -05:00
parent 41ea272c8a
commit 6a0094524d
8 changed files with 1929 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Helper for polling-style checks in integration tests.
/// Corresponds to Go's checkFor(t, timeout, interval, func) pattern.
/// </summary>
public static class CheckHelper
{
/// <summary>
/// Polls the provided check function until it returns null (success) or the timeout elapses.
/// Throws an exception with the last error message if the timeout is reached.
/// </summary>
/// <param name="timeout">Maximum time to wait.</param>
/// <param name="interval">Polling interval.</param>
/// <param name="check">Function returning null on success, or an error message on failure.</param>
public static async Task CheckFor(TimeSpan timeout, TimeSpan interval, Func<Task<string?>> check)
{
var deadline = DateTime.UtcNow + timeout;
string? lastError = null;
while (DateTime.UtcNow < deadline)
{
lastError = await check();
if (lastError == null) return;
await Task.Delay(interval);
}
throw new InvalidOperationException($"CheckFor timed out after {timeout}: {lastError}");
}
/// <summary>
/// Polls the provided synchronous check function until it returns null (success) or the timeout elapses.
/// </summary>
public static async Task CheckFor(TimeSpan timeout, TimeSpan interval, Func<string?> check)
{
await CheckFor(timeout, interval, () => Task.FromResult(check()));
}
}

View File

@@ -0,0 +1,39 @@
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Xunit.Abstractions;
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Base class for integration tests that require a running NATS server or cluster.
/// Tests derived from this class are skipped when the infrastructure is not available.
/// These tests correspond to Go's !race build tag tests (TestNoRace* functions).
/// </summary>
[Trait("Category", "Integration")]
public abstract class IntegrationTestBase
{
protected readonly ITestOutputHelper Output;
/// <summary>
/// Set to true when the NATS_INTEGRATION_ENABLED environment variable is set to "true".
/// When false, all [SkippableFact] tests will be skipped with an appropriate message.
/// </summary>
public static readonly bool IntegrationEnabled =
string.Equals(
Environment.GetEnvironmentVariable("NATS_INTEGRATION_ENABLED"),
"true",
StringComparison.OrdinalIgnoreCase);
protected IntegrationTestBase(ITestOutputHelper output)
{
Output = output;
}
/// <summary>
/// Returns a skip message when integration tests are not enabled.
/// Use with [SkippableFact]: throw new SkipException(SkipMessage) if !IntegrationEnabled.
/// </summary>
public static string SkipMessage =>
"Integration tests require NATS_INTEGRATION_ENABLED=true and a running NATS server/cluster";
}

View File

@@ -0,0 +1,46 @@
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using NATS.Client.Core;
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Helper for creating NATS client connections in integration tests.
/// Wraps NATS.Client.Core connection setup.
/// </summary>
public sealed class NatsTestClient : IAsyncDisposable
{
private readonly NatsConnection _connection;
private NatsTestClient(NatsConnection connection)
{
_connection = connection;
}
public NatsConnection Connection => _connection;
/// <summary>
/// Connects to a NATS server at the given URL.
/// </summary>
public static async Task<NatsTestClient> Connect(string url)
{
var opts = new NatsOpts { Url = url };
var conn = new NatsConnection(opts);
await conn.ConnectAsync();
return new NatsTestClient(conn);
}
/// <summary>
/// Connects to the local NATS server at its default port (for stub tests).
/// </summary>
public static async Task<NatsTestClient> ConnectToServer(string serverUrl)
{
return await Connect(serverUrl);
}
public async ValueTask DisposeAsync()
{
await _connection.DisposeAsync();
}
}

View File

@@ -0,0 +1,67 @@
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Xunit.Abstractions;
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Stub helper for creating multi-server NATS clusters in integration tests.
/// In the Go test suite, this corresponds to createJetStreamCluster(t, numServers, name) and
/// createJetStreamSuperCluster(t, numClusters, numServersPerCluster).
///
/// This stub is a placeholder until cluster orchestration is implemented.
/// Tests using this helper will skip unless NATS_INTEGRATION_ENABLED=true.
/// </summary>
public sealed class TestCluster : IDisposable
{
private readonly ITestOutputHelper _output;
/// <summary>Number of servers in the cluster.</summary>
public int ServerCount { get; }
/// <summary>Cluster name.</summary>
public string Name { get; }
/// <summary>URL of a random server in the cluster (stub returns the default URL).</summary>
public string RandomServerUrl =>
Environment.GetEnvironmentVariable("NATS_TEST_SERVER_URL") ?? "nats://localhost:4222";
private TestCluster(ITestOutputHelper output, int serverCount, string name)
{
_output = output;
ServerCount = serverCount;
Name = name;
}
/// <summary>
/// Creates a stub JetStream cluster. Throws NotSupportedException when integration is disabled.
/// Corresponds to createJetStreamCluster(t, numServers, name) in Go tests.
/// </summary>
public static TestCluster CreateJetStreamCluster(int numServers, string name, ITestOutputHelper output)
{
if (!IntegrationTestBase.IntegrationEnabled)
throw new NotSupportedException(IntegrationTestBase.SkipMessage);
output.WriteLine($"Creating JetStream cluster '{name}' with {numServers} servers (stub)");
return new TestCluster(output, numServers, name);
}
/// <summary>
/// Creates a stub JetStream super-cluster.
/// Corresponds to createJetStreamSuperCluster(t, numClusters, numServers) in Go tests.
/// </summary>
public static TestCluster CreateJetStreamSuperCluster(int numClusters, int numServersPerCluster, ITestOutputHelper output)
{
if (!IntegrationTestBase.IntegrationEnabled)
throw new NotSupportedException(IntegrationTestBase.SkipMessage);
output.WriteLine($"Creating JetStream super-cluster with {numClusters} clusters x {numServersPerCluster} servers (stub)");
return new TestCluster(output, numClusters * numServersPerCluster, $"SuperCluster({numClusters}x{numServersPerCluster})");
}
public void Dispose()
{
_output.WriteLine($"Shutting down cluster '{Name}'");
}
}

View File

@@ -0,0 +1,53 @@
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Xunit.Abstractions;
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Stub helper for creating test server instances in integration tests.
/// In the Go test suite, this corresponds to RunBasicJetStreamServer(t) which
/// starts an embedded NATS server with JetStream enabled.
///
/// This stub is a placeholder until the .NET NATS server can be embedded
/// in test scenarios. Tests using this helper will skip unless
/// NATS_INTEGRATION_ENABLED=true and a server is running at the configured URL.
/// </summary>
public static class TestServerHelper
{
/// <summary>
/// Default URL for the test NATS server when running integration tests.
/// Override via NATS_TEST_SERVER_URL environment variable.
/// </summary>
public static string DefaultServerUrl =>
Environment.GetEnvironmentVariable("NATS_TEST_SERVER_URL") ?? "nats://localhost:4222";
/// <summary>
/// Stub for RunBasicJetStreamServer(t) from Go tests.
/// Returns the URL of the server to connect to.
/// Throws NotSupportedException when integration tests are not enabled.
/// </summary>
public static string RunBasicJetStreamServer(ITestOutputHelper output)
{
if (!IntegrationTestBase.IntegrationEnabled)
throw new NotSupportedException(IntegrationTestBase.SkipMessage);
output.WriteLine($"Using JetStream server at {DefaultServerUrl}");
return DefaultServerUrl;
}
/// <summary>
/// Stub for RunServer(opts) from Go tests.
/// Returns the URL of the server to connect to.
/// </summary>
public static string RunServer(ITestOutputHelper output, string? url = null)
{
if (!IntegrationTestBase.IntegrationEnabled)
throw new NotSupportedException(IntegrationTestBase.SkipMessage);
var serverUrl = url ?? DefaultServerUrl;
output.WriteLine($"Using server at {serverUrl}");
return serverUrl;
}
}