From 9a2be12007382c865ceeeb1b44de4ffd183c5572 Mon Sep 17 00:00:00 2001 From: Caleb Lloyd Date: Tue, 6 May 2025 21:43:06 -0400 Subject: [PATCH] register services in base class Signed-off-by: Caleb Lloyd --- .../NatsIntegrationFixture.cs | 55 +++++++---- test/IntegrationTests/TestBase.cs | 92 ++++++------------- util/PerfTest/Program.cs | 4 +- 3 files changed, 67 insertions(+), 84 deletions(-) diff --git a/test/IntegrationTests/NatsIntegrationFixture.cs b/test/IntegrationTests/NatsIntegrationFixture.cs index 4946e89..754fe85 100644 --- a/test/IntegrationTests/NatsIntegrationFixture.cs +++ b/test/IntegrationTests/NatsIntegrationFixture.cs @@ -1,8 +1,9 @@ -using System; -using System.Threading; -using System.Threading.Tasks; using Aspire.Hosting; +using CodeCargo.NatsDistributedCache.TestUtils; +using Microsoft.Extensions.Logging; using NATS.Client.Core; +using NATS.Client.KeyValueStore; +using NATS.Net; namespace CodeCargo.NatsDistributedCache.IntegrationTests; @@ -13,13 +14,14 @@ public class NatsIntegrationFixture : IAsyncLifetime { private static readonly TimeSpan StartupTimeout = TimeSpan.FromSeconds(30); private DistributedApplication? _app; - private INatsConnection? _natsConnection; + private int _disposed; + private ServiceProvider? _serviceProvider; /// - /// Gets the NATS connection for accessing the test NATS server + /// Gets the NATS connection /// - public INatsConnection NatsConnection => _natsConnection ?? throw new InvalidOperationException( - "NATS connection not initialized. Make sure InitializeAsync has been called."); + public INatsConnection NatsConnection => _serviceProvider?.GetRequiredService() + ?? throw new InvalidOperationException("InitializeAsync was not called"); /// /// Initializes the fixture by starting the NATS server and creating a connection @@ -43,31 +45,48 @@ public async ValueTask InitializeAsync() throw new InvalidOperationException("Cannot find connection string for NATS"); } - // Create a NATS connection - var opts = new NatsOpts + // service provider + var services = new ServiceCollection(); + services.AddLogging(builder => { - Url = natsConnectionString, - Name = "IntegrationTest", - RequestReplyMode = NatsRequestReplyMode.Direct, - }; - _natsConnection = new NatsConnection(opts); + builder.AddConsole(); + }); + services.AddNatsTestServices(natsConnectionString); + _serviceProvider = services.BuildServiceProvider(); + + // create the KV store + var kvContext = NatsConnection.CreateKeyValueStoreContext(); + await kvContext.CreateOrUpdateStoreAsync( + new NatsKVConfig("cache") { LimitMarkerTTL = TimeSpan.FromSeconds(1), }, + TestContext.Current.CancellationToken); } + /// + /// Configures the services with the NATS connection + /// + /// The service collection to configure + public void ConfigureServices(IServiceCollection services) => services.AddSingleton(NatsConnection); + /// /// Disposes the fixture by shutting down the NATS server /// public async ValueTask DisposeAsync() { - if (_natsConnection != null) + if (Interlocked.Increment(ref _disposed) != 1) { - await _natsConnection.DisposeAsync(); - _natsConnection = null; + return; } if (_app != null) { + var stopCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await _app.StopAsync(stopCts.Token); await _app.DisposeAsync(); - _app = null; + } + + if (_serviceProvider != null) + { + await _serviceProvider.DisposeAsync(); } GC.SuppressFinalize(this); diff --git a/test/IntegrationTests/TestBase.cs b/test/IntegrationTests/TestBase.cs index ff88424..3aa8bf6 100644 --- a/test/IntegrationTests/TestBase.cs +++ b/test/IntegrationTests/TestBase.cs @@ -1,55 +1,31 @@ -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; using CodeCargo.NatsDistributedCache.TestUtils.Services.Logging; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NATS.Client.Core; -using NATS.Client.JetStream; using NATS.Client.KeyValueStore; using NATS.Net; -using Xunit; -using Xunit.Sdk; namespace CodeCargo.NatsDistributedCache.IntegrationTests; /// /// Base class for NATS integration tests that provides test output logging and fixture access /// -public abstract class TestBase(NatsIntegrationFixture fixture) : IAsyncLifetime +public abstract class TestBase : IAsyncDisposable { - private const string ServiceProviderKey = "ServiceProvider"; - private readonly NatsIntegrationFixture _fixture = fixture ?? throw new ArgumentNullException(nameof(fixture)); - private static readonly ConcurrentBag _serviceProviders = new(); - private ServiceProvider? _serviceProvider; + private int _disposed; /// - /// Gets the NATS connection from the fixture + /// Constructor that sets up the service provider with test output logging /// - protected INatsConnection NatsConnection => _fixture.NatsConnection; - - /// - /// Gets the service provider configured with test logging and NATS services - /// - protected IServiceProvider ServiceProvider => _serviceProvider ?? - throw new InvalidOperationException("Service provider not initialized. Make sure InitializeAsync has been called."); - - // Static constructor to register for test class configuration - static TestBase() - { - // xUnit v3 will call ConfigureTestClass - } - - /// - /// Configures the test class - this method is called by xUnit v3 - /// - /// The test context - public static void ConfigureTestClass(TestContext context) + /// The NATS integration fixture + protected TestBase(NatsIntegrationFixture fixture) { - // Get the test output helper - var output = context.TestOutputHelper; + // Get the test output helper from the current test context + var testContext = TestContext.Current; + var output = testContext.TestOutputHelper; if (output == null) - return; + { + throw new InvalidOperationException("TestOutputHelper was not available in the current test context"); + } // Create a service collection and configure logging var services = new ServiceCollection(); @@ -59,48 +35,34 @@ public static void ConfigureTestClass(TestContext context) builder.AddXUnitTestOutput(output); }); - // Build the service provider and store it in the context's key-value storage - var serviceProvider = services.BuildServiceProvider(); - _serviceProviders.Add(serviceProvider); - context.KeyValueStorage[ServiceProviderKey] = serviceProvider; + // Configure the service collection with NATS connection + fixture.ConfigureServices(services); + + // Build service provider + ServiceProvider = services.BuildServiceProvider(); } /// - /// Initializes the test by ensuring the KV store exists + /// Gets the service provider configured with test logging and NATS services /// - public virtual async ValueTask InitializeAsync() - { - // Setup service provider for this instance - var services = new ServiceCollection(); - - // Add NATS connection directly - services.AddSingleton(_fixture.NatsConnection); - - // Add logging - services.AddLogging(builder => - { - builder.ClearProviders(); - }); + protected ServiceProvider ServiceProvider { get; } - // Build service provider - _serviceProvider = services.BuildServiceProvider(); - _serviceProviders.Add(_serviceProvider); - - // Create or ensure KV store exists - var jsContext = NatsConnection.CreateJetStreamContext(); - var kvContext = new NatsKVContext(jsContext); - await kvContext.CreateOrUpdateStoreAsync(new NatsKVConfig("cache")); - } + /// + /// Gets the NATS connection from the service provider + /// + protected INatsConnection NatsConnection => ServiceProvider.GetRequiredService(); /// /// Cleanup after the test /// public virtual async ValueTask DisposeAsync() { - if (_serviceProvider != null) + if (Interlocked.Increment(ref _disposed) != 1) { - await _serviceProvider.DisposeAsync(); - _serviceProvider = null; + return; } + + await ServiceProvider.DisposeAsync(); + GC.SuppressFinalize(this); } } diff --git a/util/PerfTest/Program.cs b/util/PerfTest/Program.cs index 8208aaf..d21fac8 100644 --- a/util/PerfTest/Program.cs +++ b/util/PerfTest/Program.cs @@ -51,7 +51,9 @@ Console.WriteLine("Creating KV store..."); var nats = host.Services.GetRequiredService(); var kv = nats.CreateKeyValueStoreContext(); -await kv.CreateStoreAsync(new NatsKVConfig("cache"), startupCts.Token); +await kv.CreateOrUpdateStoreAsync( + new NatsKVConfig("cache") { LimitMarkerTTL = TimeSpan.FromSeconds(1) }, + startupCts.Token); Console.WriteLine("KV store created"); // Run the host