Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Do not run reusable resource tests in parallel #1267

Merged
merged 1 commit into from
Sep 20, 2024
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
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
Loading