diff --git a/src/Testcontainers/Configurations/Commons/DefaultJsonSerializerOptions.cs b/src/Testcontainers/Configurations/Commons/DefaultJsonSerializerOptions.cs new file mode 100644 index 000000000..b89139092 --- /dev/null +++ b/src/Testcontainers/Configurations/Commons/DefaultJsonSerializerOptions.cs @@ -0,0 +1,15 @@ +namespace DotNet.Testcontainers.Configurations +{ + using System.Text.Json; + + internal static class DefaultJsonSerializerOptions + { + static DefaultJsonSerializerOptions() + { + Instance.Converters.Add(new JsonOrderedKeysConverter()); + } + + public static JsonSerializerOptions Instance { get; } + = new JsonSerializerOptions(); + } +} diff --git a/src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs b/src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs index aab61b0ce..8a87e9543 100644 --- a/src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs +++ b/src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs @@ -8,12 +8,16 @@ namespace DotNet.Testcontainers.Configurations internal sealed class JsonIgnoreRuntimeResourceLabels : JsonOrderedKeysConverter { - private static readonly ISet IgnoreLabels = new HashSet { ResourceReaper.ResourceReaperSessionLabel, TestcontainersClient.TestcontainersVersionLabel, TestcontainersClient.TestcontainersSessionIdLabel }; + private static readonly ISet IgnoreLabels = new HashSet + { + ResourceReaper.ResourceReaperSessionLabel, + TestcontainersClient.TestcontainersVersionLabel, + TestcontainersClient.TestcontainersSessionIdLabel, + }; public override void Write(Utf8JsonWriter writer, IReadOnlyDictionary value, JsonSerializerOptions options) { var labels = value.Where(label => !IgnoreLabels.Contains(label.Key)).ToDictionary(label => label.Key, label => label.Value); - base.Write(writer, labels, options); } } diff --git a/src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs b/src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs index 7b7f7e1f5..024b5e6ae 100644 --- a/src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs +++ b/src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs @@ -14,13 +14,6 @@ namespace DotNet.Testcontainers.Configurations [PublicAPI] public class ResourceConfiguration : IResourceConfiguration { - private static readonly JsonSerializerOptions JsonSerializerOptions; - - static ResourceConfiguration() - { - JsonSerializerOptions = new JsonSerializerOptions { Converters = { new JsonOrderedKeysConverter() } }; - } - /// /// Initializes a new instance of the class. /// @@ -95,7 +88,7 @@ protected ResourceConfiguration(IResourceConfiguration ol /// public virtual string GetReuseHash() { - var jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(this, GetType(), JsonSerializerOptions); + var jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(this, GetType(), DefaultJsonSerializerOptions.Instance); #if NET6_0_OR_GREATER return Convert.ToBase64String(SHA1.HashData(jsonUtf8Bytes)); diff --git a/tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs b/tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs index f0afa32b4..f3db0da7d 100644 --- a/tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs +++ b/tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs @@ -104,37 +104,76 @@ public sealed class EqualTest { [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public void ForSameConfigurationCreatedInDifferentOrder() + public void ForKnownConfiguration() { - var env1 = new Dictionary - { - ["keyA"] = "valueA", - ["keyB"] = "valueB", - }; - var env2 = new Dictionary - { - ["keyB"] = "valueB", - ["keyA"] = "valueA", - }; - var hash1 = new ReuseHashContainerBuilder().WithEnvironment(env1).WithLabel("labelA", "A").WithLabel("labelB", "B").GetReuseHash(); - var hash2 = new ReuseHashContainerBuilder().WithEnvironment(env2).WithLabel("labelB", "B").WithLabel("labelA", "A").GetReuseHash(); - Assert.Equal(hash1, hash2); + // Given + var env = new Dictionary(); + env["keyA"] = "valueA"; + env["keyB"] = "valueB"; + + // When + var hash = new ReuseHashContainerBuilder() + .WithEnvironment(env) + .WithLabel("labelA", "A") + .WithLabel("labelB", "B") + .GetReuseHash(); + + // Then + + // The hash is calculated from the minified JSON. For readability, the JSON + // shown below is formatted. `Dtj7Jx6NVlbDUnA3vmH1nNZw+o8=` is the + // Base64-encoded SHA-1 hash for this JSON (minified): + // + // { + // "Image": null, + // "Name": null, + // "Entrypoint": null, + // "Command": [], + // "Environments": { + // "keyA": "valueA", + // "keyB": "valueB" + // }, + // "ExposedPorts": {}, + // "PortBindings": {}, + // "NetworkAliases": [], + // "Labels": { + // "labelA": "A", + // "labelB": "B", + // "org.testcontainers": "true", + // "org.testcontainers.lang": "dotnet" + // } + // } + Assert.Equal("Dtj7Jx6NVlbDUnA3vmH1nNZw+o8=", hash); } [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public void ForGivenConfiguration() + public void ForSameConfigurationInDifferentOrder() { - var env = new Dictionary - { - ["keyB"] = "valueB", - ["keyA"] = "valueA", - }; - var hash = new ReuseHashContainerBuilder().WithEnvironment(env).WithLabel("labelB", "B").WithLabel("labelA", "A").GetReuseHash(); - - // 50MEP+vnxEkQFo5PrndJ7oKOfh8= is the base64 encoded SHA1 of this JSON: - // {"Image":null,"Name":null,"Entrypoint":null,"Command":[],"Environments":{"keyA":"valueA","keyB":"valueB"},"ExposedPorts":{},"PortBindings":{},"NetworkAliases":[],"ExtraHosts":[],"Labels":{"labelA":"A","labelB":"B","org.testcontainers":"true","org.testcontainers.lang":"dotnet"}} - Assert.Equal("50MEP+vnxEkQFo5PrndJ7oKOfh8=", hash); + // Given + var env1 = new Dictionary(); + env1["keyA"] = "valueA"; + env1["keyB"] = "valueB"; + + var env2 = new Dictionary(); + env2["keyB"] = "valueB"; + env2["keyA"] = "valueA"; + + // When + var hash1 = new ReuseHashContainerBuilder() + .WithEnvironment(env1) + .WithLabel("labelA", "A") + .WithLabel("labelB", "B") + .GetReuseHash(); + + var hash2 = new ReuseHashContainerBuilder() + .WithEnvironment(env2) + .WithLabel("labelB", "B") + .WithLabel("labelA", "A") + .GetReuseHash(); + + // Then + Assert.Equal(hash1, hash2); } } @@ -226,7 +265,12 @@ public void EnabledCleanUpThrowsException() private sealed class ReuseHashContainerBuilder : ContainerBuilder { public ReuseHashContainerBuilder() : this(new ContainerConfiguration()) - => DockerResourceConfiguration = Init().DockerResourceConfiguration; + { + // By default, the constructor calls `Init()`, which sets up the default builder + // configurations, including ones for the port forwarding container if it's running. + // To avoid applying those settings during tests, this class intentionally doesn't + // call `Init()`. + } private ReuseHashContainerBuilder(ContainerConfiguration configuration) : base(configuration) => DockerResourceConfiguration = configuration; diff --git a/tests/Testcontainers.Platform.Linux.Tests/WaitStrategyModeTest.cs b/tests/Testcontainers.Platform.Linux.Tests/WaitStrategyModeTest.cs index ea1fdd2b3..9e65d1828 100644 --- a/tests/Testcontainers.Platform.Linux.Tests/WaitStrategyModeTest.cs +++ b/tests/Testcontainers.Platform.Linux.Tests/WaitStrategyModeTest.cs @@ -2,17 +2,13 @@ namespace Testcontainers.Tests; public abstract class WaitStrategyModeTest : IAsyncLifetime { - private const string Message = "Hello, World!"; - private readonly IContainer _container; private WaitStrategyModeTest(WaitStrategyMode waitStrategyMode) { _container = new ContainerBuilder() .WithImage(CommonImages.Alpine) - .WithEntrypoint("/bin/sh", "-c") - .WithCommand("echo " + Message) - .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(Message, o => o.WithMode(waitStrategyMode))) + .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(), o => o.WithMode(waitStrategyMode))) .Build(); } @@ -68,4 +64,12 @@ public async Task StartAsyncShouldSucceedWhenContainerIsNotRunning() Assert.Null(exception); } } + + private sealed class WaitUntil : IWaitUntil + { + public Task UntilAsync(IContainer container) + { + return Task.FromResult(TestcontainersStates.Exited.Equals(container.State)); + } + } } \ No newline at end of file