Skip to content

Commit

Permalink
fix: Do not run reusable resource tests in parallel (#1267)
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn authored Sep 20, 2024
1 parent 7174786 commit 340a93a
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 60 deletions.
10 changes: 5 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
<PackageVersion Include="SSH.NET" Version="2023.0.0"/>
<PackageVersion Include="System.Text.Json" Version="6.0.9"/>
<!-- Unit and integration test dependencies: -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="8.2.0"/>
<PackageVersion Include="coverlet.collector" Version="6.0.1"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7"/>
<PackageVersion Include="xunit" Version="2.7.0"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="8.9.1"/>
<PackageVersion Include="coverlet.collector" Version="6.0.2"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2"/>
<PackageVersion Include="xunit" Version="2.9.0"/>
<!-- Third-party client dependencies to connect and interact with the containers: -->
<PackageVersion Include="Apache.NMS.ActiveMQ" Version="2.1.0"/>
<PackageVersion Include="ArangoDBNetStandard" Version="2.0.1"/>
Expand Down
1 change: 1 addition & 0 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Task("Tests")
Filter = param.TestFilter,
ResultsDirectory = param.Paths.Directories.TestResultsDirectoryPath,
ArgumentCustomization = args => args
.AppendSwitchQuoted("--blame-hang-timeout", "5m")
});
});

Expand Down
11 changes: 4 additions & 7 deletions src/Testcontainers/Clients/DockerApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace DotNet.Testcontainers.Clients
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -107,18 +108,14 @@ await RuntimeInitialized.WaitAsync(ct)
runtimeInfo.AppendLine(dockerInfo.OperatingSystem);

runtimeInfo.Append(" Total Memory: ");
runtimeInfo.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0:F} {1}", dockerInfo.MemTotal / Math.Pow(1024, byteUnits.Length), byteUnits[byteUnits.Length - 1]));
runtimeInfo.AppendFormat(CultureInfo.InvariantCulture, "{0:F} {1}", dockerInfo.MemTotal / Math.Pow(1024, byteUnits.Length), byteUnits[byteUnits.Length - 1]);

var labels = dockerInfo.Labels;
if (labels != null && labels.Count > 0)
{
runtimeInfo.AppendLine();
runtimeInfo.AppendLine(" Labels: ");

foreach (var label in labels)
{
runtimeInfo.Append(" ");
runtimeInfo.AppendLine(label);
}
runtimeInfo.Append(string.Join(Environment.NewLine, labels.Select(label => " " + label)));
}
Logger.LogInformation("{RuntimeInfo}", runtimeInfo);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Testcontainers/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
{
if (IsEnabled(logLevel))
{
Console.Out.WriteLine("[testcontainers.org {0:hh\\:mm\\:ss\\.ff}] {1}", _stopwatch.Elapsed, formatter.Invoke(state, exception));
var message = exception == null ? formatter.Invoke(state, null) : string.Join(Environment.NewLine, formatter.Invoke(state, exception), exception);
Console.Out.WriteLine("[testcontainers.org {0:hh\\:mm\\:ss\\.ff}] {1}", _stopwatch.Elapsed, message);
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Testcontainers.Commons/CommonImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace DotNet.Testcontainers.Commons;
[PublicAPI]
public static class CommonImages
{
public static readonly IImage Ryuk = new DockerImage("testcontainers/ryuk:0.6.0");
public static readonly IImage Ryuk = new DockerImage("testcontainers/ryuk:0.9.0");

public static readonly IImage Alpine = new DockerImage("alpine:3.17");

Expand Down
86 changes: 53 additions & 33 deletions tests/Testcontainers.Platform.Linux.Tests/DependsOnTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,77 @@ namespace Testcontainers.Tests;

public sealed class DependsOnTest : IAsyncLifetime
{
private const string DependsOnKey = "org.testcontainers.depends-on";
private readonly FilterByProperty _filters = new FilterByProperty();

private const string DependsOnValue = "true";
private readonly IList<IAsyncDisposable> _disposables = new List<IAsyncDisposable>();

private readonly IContainer _container = new ContainerBuilder()
.DependsOn(new ContainerBuilder()
private readonly string _labelKey = Guid.NewGuid().ToString("D");

private readonly string _labelValue = Guid.NewGuid().ToString("D");

public DependsOnTest()
{
_filters.Add("label", string.Join("=", _labelKey, _labelValue));
}

public async Task InitializeAsync()
{
var childContainer1 = new ContainerBuilder()
.WithImage(CommonImages.Alpine)
.WithLabel(DependsOnKey, DependsOnValue)
.Build())
.DependsOn(new ContainerBuilder()
.WithLabel(_labelKey, _labelValue)
.Build();

var childContainer2 = new ContainerBuilder()
.WithImage(CommonImages.Alpine)
.WithLabel(DependsOnKey, DependsOnValue)
.Build())
.DependsOn(new NetworkBuilder()
.WithLabel(DependsOnKey, DependsOnValue)
.Build())
.DependsOn(new VolumeBuilder()
.WithLabel(DependsOnKey, DependsOnValue)
.Build(), "/workdir")
.WithImage(CommonImages.Alpine)
.WithLabel(DependsOnKey, DependsOnValue)
.Build();

public Task InitializeAsync()
{
return _container.StartAsync();
.WithLabel(_labelKey, _labelValue)
.Build();

var network = new NetworkBuilder()
.WithLabel(_labelKey, _labelValue)
.Build();

var volume = new VolumeBuilder()
.WithLabel(_labelKey, _labelValue)
.Build();

var parentContainer = new ContainerBuilder()
.DependsOn(childContainer1)
.DependsOn(childContainer2)
.DependsOn(network)
.DependsOn(volume, "/workdir")
.WithImage(CommonImages.Alpine)
.WithLabel(_labelKey, _labelValue)
.Build();

await parentContainer.StartAsync()
.ConfigureAwait(false);

_disposables.Add(parentContainer);
_disposables.Add(childContainer1);
_disposables.Add(childContainer2);
_disposables.Add(network);
_disposables.Add(volume);
}

public Task DisposeAsync()
{
return _container.DisposeAsync().AsTask();
return Task.WhenAll(_disposables.Select(disposable => disposable.DisposeAsync().AsTask()));
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task DependsOnCreatesDependentResources()
{
// Given
using var clientConfiguration = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(ResourceReaper.DefaultSessionId);
using var clientConfiguration = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(Guid.NewGuid());

using var client = clientConfiguration.CreateClient();

var labelFilter = new Dictionary<string, bool> { { string.Join("=", DependsOnKey, DependsOnValue), true } };

var filters = new Dictionary<string, IDictionary<string, bool>> { { "label", labelFilter } };

var containersListParameters = new ContainersListParameters { All = true, Filters = filters };
var containersListParameters = new ContainersListParameters { All = true, Filters = _filters };

var networksListParameters = new NetworksListParameters { Filters = filters };
var networksListParameters = new NetworksListParameters { Filters = _filters };

var volumesListParameters = new VolumesListParameters { Filters = filters };
var volumesListParameters = new VolumesListParameters { Filters = _filters };

// When
var containers = await client.Containers.ListContainersAsync(containersListParameters)
Expand All @@ -61,12 +81,12 @@ public async Task DependsOnCreatesDependentResources()
var networks = await client.Networks.ListNetworksAsync(networksListParameters)
.ConfigureAwait(true);

var volumesListResponse = await client.Volumes.ListAsync(volumesListParameters)
var response = await client.Volumes.ListAsync(volumesListParameters)
.ConfigureAwait(true);

// Then
Assert.Equal(3, containers.Count);
Assert.Single(networks);
Assert.Single(volumesListResponse.Volumes);
Assert.Single(response.Volumes);
}
}
38 changes: 25 additions & 13 deletions tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
namespace Testcontainers.Tests;

public sealed class ReusableResourceTest : IAsyncLifetime, IDisposable
// We cannot run these tests in parallel because they interfere with the port
// forwarding tests. When the port forwarding container is running, Testcontainers
// automatically inject the necessary extra hosts into the builder configuration
// using `WithPortForwarding()` internally. Depending on when the test framework
// starts the port forwarding container, these extra hosts can lead to flakiness.
// This happens because the reuse hash changes, resulting in two containers with
// the same labels running instead of one.
[CollectionDefinition(nameof(ReusableResourceTest), DisableParallelization = true)]
[Collection(nameof(ReusableResourceTest))]
public sealed class ReusableResourceTest : IAsyncLifetime
{
private readonly DockerClient _dockerClient = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(Guid.NewGuid()).CreateClient();

private readonly FilterByProperty _filters = new FilterByProperty();

private readonly IList<IAsyncDisposable> _disposables = new List<IAsyncDisposable>();
Expand Down Expand Up @@ -63,31 +70,36 @@ public Task DisposeAsync()
}));
}

public void Dispose()
{
_dockerClient.Dispose();
}

[Fact]
public async Task ShouldReuseExistingResource()
{
var containers = await _dockerClient.Containers.ListContainersAsync(new ContainersListParameters { Filters = _filters })
using var clientConfiguration = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(Guid.NewGuid());

using var client = clientConfiguration.CreateClient();

var containersListParameters = new ContainersListParameters { All = true, Filters = _filters };

var networksListParameters = new NetworksListParameters { Filters = _filters };

var volumesListParameters = new VolumesListParameters { Filters = _filters };

var containers = await client.Containers.ListContainersAsync(containersListParameters)
.ConfigureAwait(true);

var networks = await _dockerClient.Networks.ListNetworksAsync(new NetworksListParameters { Filters = _filters })
var networks = await client.Networks.ListNetworksAsync(networksListParameters)
.ConfigureAwait(true);

var response = await _dockerClient.Volumes.ListAsync(new VolumesListParameters { Filters = _filters })
var response = await client.Volumes.ListAsync(volumesListParameters)
.ConfigureAwait(true);

Assert.Single(containers);
Assert.Single(networks);
Assert.Single(response.Volumes);
}

public static class ReuseHash
public static class ReuseHashTest
{
public sealed class NotEqual
public sealed class NotEqualTest
{
[Fact]
public void ForDifferentNames()
Expand Down

0 comments on commit 340a93a

Please sign in to comment.