From e8e31821ce2d48648e1281feb141ea2ac39d5883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Silveira?= Date: Thu, 2 Nov 2023 15:27:37 +0100 Subject: [PATCH] * attempt to improve docker isolation in tests * minor refactor of global environments --- Directory.Build.props | 2 +- .../ShutdownNodeTests.cs | 6 +- .../ApplicationInfo.cs | 7 ++- .../EventStore.Client.Tests.Common.csproj | 1 + .../Extensions/ConfigurationExtensions.cs | 12 ++++ .../Fixtures/DatabaseWarmup.cs | 14 +++-- .../Fixtures/EventStoreFixture.cs | 16 +++-- .../Fixtures/EventStoreTestCluster.cs | 2 +- .../Fixtures/EventStoreTestNode.cs | 57 ++++++++++------- .../FluentDocker/TestService.cs | 23 +++++++ .../GlobalEnvironment.cs | 62 +++++++++---------- .../{Fixtures => }/Logging.cs | 1 - 12 files changed, 132 insertions(+), 71 deletions(-) create mode 100644 test/EventStore.Client.Tests.Common/Extensions/ConfigurationExtensions.cs rename test/EventStore.Client.Tests.Common/{Fixtures => }/Logging.cs (98%) diff --git a/Directory.Build.props b/Directory.Build.props index 8a4b4fc6b..426dd56e1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,7 +9,7 @@ true preview - Debug + full full pdbonly diff --git a/test/EventStore.Client.Operations.Tests/ShutdownNodeTests.cs b/test/EventStore.Client.Operations.Tests/ShutdownNodeTests.cs index 7f4dddb02..10b102e4c 100644 --- a/test/EventStore.Client.Operations.Tests/ShutdownNodeTests.cs +++ b/test/EventStore.Client.Operations.Tests/ShutdownNodeTests.cs @@ -1,3 +1,5 @@ +using FlakyTest.XUnit.Attributes; + namespace EventStore.Client.Operations.Tests; public class ShutdownNodeTests : IClassFixture { @@ -9,8 +11,8 @@ public ShutdownNodeTests(ITestOutputHelper output, InsecureClientTestFixture fix [Fact] public async Task shutdown_does_not_throw() => await Fixture.Operations.ShutdownAsync(userCredentials: TestCredentials.Root).ShouldNotThrowAsync(); - - [Fact] + + [MaybeFixedFact(1)] public async Task shutdown_without_credentials_throws() => await Fixture.Operations.ShutdownAsync().ShouldThrowAsync(); } \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs index fbbd3dba3..f139f7826 100644 --- a/test/EventStore.Client.Tests.Common/ApplicationInfo.cs +++ b/test/EventStore.Client.Tests.Common/ApplicationInfo.cs @@ -11,6 +11,9 @@ namespace EventStore.Client; +/// +/// Loads configuration and provides information about the application environment. +/// public static class Application { static Application() { ForegroundColor = ConsoleColor.Magenta; @@ -27,7 +30,9 @@ static Application() { Configuration = builder.Build(); - WriteLine($"CONSOLE: {Environment} configuration loaded with {Configuration.AsEnumerable().Count()} entries from {builder.Sources.Count} sources."); + WriteLine($"CONSOLE: {Environment} configuration loaded " + + $"with {Configuration.AsEnumerable().Count()} entries " + + $"from {builder.Sources.Count} sources."); IsDevelopment = IsEnvironment(Environments.Development); IsStaging = IsEnvironment(Environments.Staging); diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index c63d47b29..cd63eaf70 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -13,6 +13,7 @@ + diff --git a/test/EventStore.Client.Tests.Common/Extensions/ConfigurationExtensions.cs b/test/EventStore.Client.Tests.Common/Extensions/ConfigurationExtensions.cs new file mode 100644 index 000000000..3e975fc23 --- /dev/null +++ b/test/EventStore.Client.Tests.Common/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Configuration; + +namespace EventStore.Client.Tests; + +public static class ConfigurationExtensions { + public static void EnsureValue(this IConfiguration configuration, string key, string defaultValue) { + var value = configuration.GetValue(key); + + if (string.IsNullOrEmpty(value)) + configuration[key] = defaultValue; + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/Fixtures/DatabaseWarmup.cs b/test/EventStore.Client.Tests.Common/Fixtures/DatabaseWarmup.cs index f23fe21b1..5fdaa5a6c 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/DatabaseWarmup.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/DatabaseWarmup.cs @@ -13,18 +13,22 @@ static class DatabaseWarmup where T : EventStoreClientBase { static readonly SemaphoreSlim Semaphore = new(1, 1); static readonly ILogger Logger = Log.ForContext(SourceContextPropertyName, typeof(T).Name); + static DatabaseWarmup() { + AppDomain.CurrentDomain.DomainUnload += (_, _) => Completed.Set(false); + } + public static async Task TryExecuteOnce(T client, Func action, CancellationToken cancellationToken = default) { await Semaphore.WaitAsync(cancellationToken); try { - if (!Completed.EnsureCalledOnce()) { + // if (!Completed.EnsureCalledOnce()) { Logger.Warning("*** Warmup started ***"); await TryExecute(client, action, cancellationToken); Logger.Warning("*** Warmup completed ***"); - } - else { - Logger.Information("*** Warmup skipped ***"); - } + // } + // else { + // Logger.Information("*** Warmup skipped ***"); + // } } catch (Exception ex) { if (Application.DebuggerIsAttached) { diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs index e9feb1317..001a0ef2f 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs @@ -4,7 +4,7 @@ namespace EventStore.Client.Tests; -public record EventStoreFixtureOptions(EventStoreClientSettings ClientSettings, IDictionary Environment) { +public record EventStoreFixtureOptions(EventStoreClientSettings ClientSettings, IDictionary Environment) { public EventStoreFixtureOptions RunInMemory(bool runInMemory = true) => this with { Environment = Environment.With(x => x["EVENTSTORE_MEM_DB"] = runInMemory.ToString()) }; @@ -39,7 +39,7 @@ public EventStoreFixture() : this(options => options) { } protected EventStoreFixture(ConfigureFixture configure) { // TODO SS: should I verify the certificates exist here? if (GlobalEnvironment.UseExternalServer) { - Options = new(new(), new Dictionary()); + Options = new(new(), new Dictionary()); Service = new TestBypassService(); } @@ -87,11 +87,10 @@ public void CaptureTestRun(ITestOutputHelper outputHelper) { var testRunId = Logging.CaptureLogs(outputHelper); TestRuns.Add(testRunId); Logger.Information(">>> Test Run {testRunId} {Operation} <<<", testRunId, "starting"); + Service.ReportStatus(); } - public async Task InitializeAsync() { - await Service.Start().ShouldNotThrowAsync(); - + async Task WarmUp() { Logger.Information("*** !!! Warming up database !!! ***"); Users = new(ClientSettings); @@ -110,11 +109,16 @@ public async Task InitializeAsync() { Operations = new(ClientSettings); await Operations.WarmUp().ShouldNotThrowAsync(); + } - Logger.Information("Setup completed"); + public async Task InitializeAsync() { + await Service.Start().ShouldNotThrowAsync(); + + await WarmUp(); await OnSetup().ShouldNotThrowAsync("Failed to run OnSetup!"); } + public async Task DisposeAsync() { try { await OnTearDown(); diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs index dda7e23ac..fcf8e497e 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs @@ -18,7 +18,7 @@ public static EventStoreFixtureOptions DefaultOptions() { .With(x => x.ConnectivitySettings.MaxDiscoverAttempts = 30) .With(x => x.ConnectivitySettings.DiscoveryInterval = TimeSpan.FromSeconds(1)); - var defaultEnvironment = new Dictionary(GlobalEnvironment.Variables) { + var defaultEnvironment = new Dictionary(GlobalEnvironment.Variables) { ["ES_CERTS_CLUSTER"] = Path.Combine(Environment.CurrentDirectory, "certs-cluster"), ["EVENTSTORE_CLUSTER_SIZE"] = "3", ["EVENTSTORE_INT_TCP_PORT"] = "1112", diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs index 8939d7f29..ec851a887 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs @@ -1,3 +1,4 @@ +using System.Net; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Model.Builders; using EventStore.Client.Tests.FluentDocker; @@ -9,28 +10,37 @@ namespace EventStore.Client.Tests; public class EventStoreTestNode(EventStoreFixtureOptions? options = null) : TestContainerService { EventStoreFixtureOptions Options { get; } = options ?? DefaultOptions(); - static int _port = 2213; + static int _port = 2212; static int NextPort() => Interlocked.Increment(ref _port); public static EventStoreFixtureOptions DefaultOptions() { const string connString = "esdb://admin:changeit@localhost:{port}/?tlsVerifyCert=false"; + + var port = NextPort().ToString(); var defaultSettings = EventStoreClientSettings - .Create(connString.Replace("{port}", NextPort().ToString())) + .Create(connString.Replace("{port}", port)) .With(x => x.LoggerFactory = new SerilogLoggerFactory(Log.Logger)) .With(x => x.DefaultDeadline = Application.DebuggerIsAttached ? new TimeSpan?() : TimeSpan.FromSeconds(180)) .With(x => x.ConnectivitySettings.MaxDiscoverAttempts = 30) .With(x => x.ConnectivitySettings.DiscoveryInterval = TimeSpan.FromSeconds(1)); - var defaultEnvironment = new Dictionary(GlobalEnvironment.Variables) { - ["EVENTSTORE_MEM_DB"] = "true", - ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), - ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", - ["EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE"] = "/etc/eventstore/certs/node/node.key", - ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", - ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000", - ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "True" - }; + var defaultEnvironment = new Dictionary(GlobalEnvironment.Variables) { + // ["EVENTSTORE_HTTP_PORT"] = port, + // ["EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS"] = port, + ["EVENTSTORE_NODE_PORT"] = port, + ["EVENTSTORE_NODE_PORT_ADVERTISE_AS"] = port, + ["EVENTSTORE_ADVERTISE_NODE_PORT_TO_CLIENT_AS"] = port, + ["EVENTSTORE_ENABLE_EXTERNAL_TCP"] = "false", + ["EVENTSTORE_MEM_DB"] = "true", + ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), + ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", + ["EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE"] = "/etc/eventstore/certs/node/node.key", + ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", + ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000", + ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "false", + ["EVENTSTORE_DISABLE_LOG_FILE"] = "true" + }; return new(defaultSettings, defaultEnvironment); } @@ -40,17 +50,22 @@ protected override ContainerBuilder Configure() { var port = Options.ClientSettings.ConnectivitySettings.Address.Port; var certsPath = Path.Combine(Environment.CurrentDirectory, "certs"); - var containerName = $"esbd-dotnet-test-{Guid.NewGuid().ToString()[30..]}-{port}"; + var containerName = $"esbd-dotnet-test-{port}-{Guid.NewGuid().ToString()[30..]}"; CertificatesManager.VerifyCertificatesExist(certsPath); // its ok... no really... - + return new Builder() - .UseContainer() - .UseImage(GlobalEnvironment.DockerImage) - .WithName(containerName) - .WithEnvironment(env) - .MountVolume(certsPath, "/etc/eventstore/certs", MountType.ReadOnly) - .ExposePort(port, 2113) - .WaitForHealthy(TimeSpan.FromSeconds(60)); - } + .UseContainer() + .UseImage(Options.Environment["ES_DOCKER_IMAGE"]) + .WithName(containerName) + .WithEnvironment(env) + .MountVolume(certsPath, "/etc/eventstore/certs", MountType.ReadOnly) + .ExposePort(port, port) // 2113 + //.WaitForMessageInLog($"========== [\"0.0.0.0:{port}\"] IS LEADER... SPARTA!") + //.WaitForMessageInLog("'ops' user added to $users.") + .WaitForMessageInLog("'admin' user added to $users."); + //.WaitForHealthy(TimeSpan.FromSeconds(60)); + //HEALTHCHECK &{["CMD-SHELL" "curl --fail --insecure https://localhost:2113/health/live || curl --fail http://localhost:2113/health/live || exit 1"] "5s" "5s" "0s" '\x18'} + + } } \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs index 3b7437ccb..65a083294 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs @@ -9,6 +9,8 @@ namespace EventStore.Client.Tests.FluentDocker; public interface ITestService : IAsyncDisposable { Task Start(); Task Stop(); + + void ReportStatus(); } /// @@ -93,6 +95,27 @@ public virtual async Task Stop() { } } + public void ReportStatus() { + if (Service is IContainerService containerService) { + ReportContainerStatus(containerService); + } + + if (Service is ICompositeService compose) { + foreach (var container in compose.Containers) { + ReportContainerStatus(container); + } + } + + return; + + void ReportContainerStatus(IContainerService service) { + var cfg = service.GetConfiguration(true); + Logger.Information("Container {Name} {State} Ports: {Ports}", service.Name, service.State, cfg.Config.ExposedPorts.Keys); + } + + // var docker = Fd.Hosts().Discover().FirstOrDefault(x => x.IsNative || x.Name == "default")!; + } + public virtual ValueTask DisposeAsync() { try { Network?.Dispose(); diff --git a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs index 534bd6e42..439de1a51 100644 --- a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs +++ b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs @@ -5,48 +5,44 @@ namespace EventStore.Client.Tests; public static class GlobalEnvironment { static GlobalEnvironment() { - Variables = FromConfiguration(Application.Configuration); + EnsureDefaults(Application.Configuration); - UseCluster = false; // Application.Configuration.GetValue("ES_USE_CLUSTER", false); - UseExternalServer = Application.Configuration.GetValue("ES_USE_EXTERNAL_SERVER", false); - DockerImage = Variables["ES_DOCKER_IMAGE"]; - DbLogFormat = Variables["EVENTSTORE_DB_LOG_FORMAT"]; + UseCluster = Application.Configuration.GetValue("ES_USE_CLUSTER"); + UseExternalServer = Application.Configuration.GetValue("ES_USE_EXTERNAL_SERVER"); + DockerImage = Application.Configuration.GetValue("ES_DOCKER_IMAGE")!; + DbLogFormat = Application.Configuration.GetValue("EVENTSTORE_DB_LOG_FORMAT")!; + + Variables = Application.Configuration.AsEnumerable() + .Where(x => x.Key.StartsWith("ES_") || x.Key.StartsWith("EVENTSTORE_")) + .OrderBy(x => x.Key) + .ToImmutableDictionary(x => x.Key, x => x.Value ?? string.Empty)!; + + return; + + static void EnsureDefaults(IConfiguration configuration) { + configuration.EnsureValue("ES_USE_CLUSTER", "false"); + configuration.EnsureValue("ES_USE_EXTERNAL_SERVER", "false"); + + configuration.EnsureValue("ES_DOCKER_REGISTRY", "ghcr.io/eventstore/eventstore"); + configuration.EnsureValue("ES_DOCKER_TAG", "ci"); + configuration.EnsureValue("ES_DOCKER_IMAGE", $"{configuration["ES_DOCKER_REGISTRY"]}:{configuration["ES_DOCKER_TAG"]}"); + + configuration.EnsureValue("EVENTSTORE_MEM_DB", "false"); + configuration.EnsureValue("EVENTSTORE_RUN_PROJECTIONS", "None"); + configuration.EnsureValue("EVENTSTORE_START_STANDARD_PROJECTIONS", "false"); + configuration.EnsureValue("EVENTSTORE_DB_LOG_FORMAT", "V2"); + configuration.EnsureValue("EVENTSTORE_LOG_LEVEL", "Verbose"); + configuration.EnsureValue("EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH", "/etc/eventstore/certs/ca"); + } } - public static ImmutableDictionary Variables { get; } + public static ImmutableDictionary Variables { get; } public static bool UseCluster { get; } public static bool UseExternalServer { get; } public static string DockerImage { get; } public static string DbLogFormat { get; } - public static ImmutableDictionary FromConfiguration(IConfiguration configuration) { - var env = configuration.AsEnumerable() - .Where(x => x.Key.StartsWith("ES_") || x.Key.StartsWith("EVENTSTORE_")) - .ToDictionary(x => x.Key, x => x.Value ?? string.Empty); - - EnsureSet(env, "ES_USE_CLUSTER", "false"); - EnsureSet(env, "ES_USE_EXTERNAL_SERVER", "false"); - - EnsureSet(env, "ES_DOCKER_REGISTRY", "ghcr.io/eventstore/eventstore"); - EnsureSet(env, "ES_DOCKER_TAG", "ci"); - EnsureSet(env, "ES_DOCKER_IMAGE", $"{env["ES_DOCKER_REGISTRY"]}:{env["ES_DOCKER_TAG"]}"); - - EnsureSet(env, "EVENTSTORE_MEM_DB", "false"); - EnsureSet(env, "EVENTSTORE_RUN_PROJECTIONS", "None"); - EnsureSet(env, "EVENTSTORE_START_STANDARD_PROJECTIONS", "false"); - EnsureSet(env, "EVENTSTORE_DB_LOG_FORMAT", "V2"); - EnsureSet(env, "EVENTSTORE_LOG_LEVEL", "Verbose"); - EnsureSet(env, "EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH", "/etc/eventstore/certs/ca"); - - return env.ToImmutableDictionary(); - - static void EnsureSet(IDictionary dic, string key, string value) { - if (!dic.TryGetValue(key, out var actualValue) || string.IsNullOrEmpty(actualValue)) - dic[key] = value; - } - } - #region . Obsolete . //[Obsolete("Use the EventStoreFixture instead so you don't have to use this method.", false)] diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Logging.cs b/test/EventStore.Client.Tests.Common/Logging.cs similarity index 98% rename from test/EventStore.Client.Tests.Common/Fixtures/Logging.cs rename to test/EventStore.Client.Tests.Common/Logging.cs index 03d6149ff..156199742 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Logging.cs +++ b/test/EventStore.Client.Tests.Common/Logging.cs @@ -4,7 +4,6 @@ using Serilog; using Serilog.Events; using Serilog.Formatting.Display; -using Serilog.Sinks.SystemConsole.Themes; using Xunit.Sdk; namespace EventStore.Client.Tests;