Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,39 +1,20 @@
namespace DotNet.Testcontainers.Configurations
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using DotNet.Testcontainers.Clients;
using DotNet.Testcontainers.Containers;

internal sealed class JsonIgnoreRuntimeResourceLabels : JsonConverter<IReadOnlyDictionary<string, string>>
internal sealed class JsonIgnoreRuntimeResourceLabels : JsonOrderedKeysConverter
{
private static readonly ISet<string> IgnoreLabels = new HashSet<string> { ResourceReaper.ResourceReaperSessionLabel, TestcontainersClient.TestcontainersVersionLabel, TestcontainersClient.TestcontainersSessionIdLabel };

public override bool CanConvert(Type typeToConvert)
{
return typeof(IEnumerable<KeyValuePair<string, string>>).IsAssignableFrom(typeToConvert);
}

public override IReadOnlyDictionary<string, string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<IReadOnlyDictionary<string, string>>(ref reader);
}

public override void Write(Utf8JsonWriter writer, IReadOnlyDictionary<string, string> value, JsonSerializerOptions options)
{
var labels = value.Where(label => !IgnoreLabels.Contains(label.Key)).ToDictionary(label => label.Key, label => label.Value);

writer.WriteStartObject();

foreach (var label in labels)
{
writer.WriteString(label.Key, label.Value);
}

writer.WriteEndObject();
base.Write(writer, labels, options);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace DotNet.Testcontainers.Configurations
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

internal class JsonOrderedKeysConverter : JsonConverter<IReadOnlyDictionary<string, string>>
{
public override bool CanConvert(Type typeToConvert)
{
return typeof(IEnumerable<KeyValuePair<string, string>>).IsAssignableFrom(typeToConvert);
}

public override IReadOnlyDictionary<string, string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<IReadOnlyDictionary<string, string>>(ref reader);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

public override void Write(Utf8JsonWriter writer, IReadOnlyDictionary<string, string> value, JsonSerializerOptions options)
{
writer.WriteStartObject();

foreach (var item in value.OrderBy(item => item.Key))
{
writer.WriteString(item.Key, item.Value);
}

writer.WriteEndObject();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ namespace DotNet.Testcontainers.Configurations
[PublicAPI]
public class ResourceConfiguration<TCreateResourceEntity> : IResourceConfiguration<TCreateResourceEntity>
{
private static readonly JsonSerializerOptions JsonSerializerOptions;

static ResourceConfiguration()
{
JsonSerializerOptions = new JsonSerializerOptions { Converters = { new JsonOrderedKeysConverter() } };
}

/// <summary>
/// Initializes a new instance of the <see cref="ResourceConfiguration{TCreateResourceEntity}" /> class.
/// </summary>
Expand Down Expand Up @@ -88,7 +95,7 @@ protected ResourceConfiguration(IResourceConfiguration<TCreateResourceEntity> ol
/// <inheritdoc />
public virtual string GetReuseHash()
{
var jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(this, GetType());
var jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(this, GetType(), JsonSerializerOptions);

#if NET6_0_OR_GREATER
return Convert.ToBase64String(SHA1.HashData(jsonUtf8Bytes));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,44 @@ public async Task ShouldReuseExistingResource()

public static class ReuseHashTest
{
public sealed class EqualTest
{
[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public void ForSameConfigurationCreatedInDifferentOrder()
{
var env1 = new Dictionary<string, string>
{
["keyA"] = "valueA",
["keyB"] = "valueB",
};
var env2 = new Dictionary<string, string>
{
["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);
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public void ForGivenConfiguration()
{
var env = new Dictionary<string, string>
{
["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);
}
}

public sealed class NotEqualTest
{
[Fact]
Expand Down