Skip to content

Commit

Permalink
[#242] #IMPLEMENT 'assemblyName: DotNet.Testcontainers; function: Res…
Browse files Browse the repository at this point in the history
…ourceReaper'

{Add ResourceReaper.}
  • Loading branch information
PSanetra authored and HofmeisterAn committed Feb 3, 2022
1 parent feebebc commit 0d037ca
Show file tree
Hide file tree
Showing 51 changed files with 1,226 additions and 304 deletions.
7 changes: 7 additions & 0 deletions DotNet.Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Testcontainers", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Testcontainers.Tests", "tests\DotNet.Testcontainers.Tests\DotNet.Testcontainers.Tests.csproj", "{3AA620E4-7EDE-4C2F-AA33-52E286AC518E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Testcontainers.ResourceReaper.Tests", "tests\DotNet.Testcontainers.ResourceReaper.Tests\DotNet.Testcontainers.ResourceReaper.Tests.csproj", "{9A944653-5D94-4A98-9265-A5F621A14D06}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -29,9 +31,14 @@ Global
{3AA620E4-7EDE-4C2F-AA33-52E286AC518E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AA620E4-7EDE-4C2F-AA33-52E286AC518E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AA620E4-7EDE-4C2F-AA33-52E286AC518E}.Release|Any CPU.Build.0 = Release|Any CPU
{9A944653-5D94-4A98-9265-A5F621A14D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A944653-5D94-4A98-9265-A5F621A14D06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A944653-5D94-4A98-9265-A5F621A14D06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A944653-5D94-4A98-9265-A5F621A14D06}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EC76857B-A3B8-4B7A-A1B0-8D867A4D1733} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{3AA620E4-7EDE-4C2F-AA33-52E286AC518E} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{9A944653-5D94-4A98-9265-A5F621A14D06} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
107 changes: 63 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,40 @@ Choose from existing pre-configured configurations and start containers within a

Native Windows Docker containers are only supported on Windows. Windows requires the host operating system version to match the container operating system version. You'll find further information about Windows container version compatibility [here](https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility).

Keep in mind to enable the correct Docker engine on Windows host systems to match the container operating system. With Docker CE you can switch the engine with: `$env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon` or `-SwitchLinuxEngine`, `-SwitchWindowsEngine`.
Keep in mind to enable the correct Docker engine on Windows host systems to match the container operating system. With Docker Desktop you can switch the engine either with the tray icon context menu or: `$env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon` or `-SwitchLinuxEngine`, `-SwitchWindowsEngine`.

## Supported commands

To configure a container, use the `TestcontainersBuilder<TestcontainersContainer>` builder, that provides:

- `WithImage` specifies an `IMAGE[:TAG]` to derive the container from.
- `WithWorkingDirectory` specifies and overrides the `WORKDIR` for the instruction sets.
- `WithEntrypoint` specifies and overrides the `ENTRYPOINT` that will run as an executable.
- `WithCommand` specifies and overrides the `COMMAND` instruction provided from the Dockerfile.
- `WithName` sets the container name e. g. `--name nginx`.
- `WithHostname` sets the container hostname e. g. `--hostname my-nginx`.
- `WithEnvironment` sets an environment variable in the container e. g. `-e, --env "test=containers"`.
- `WithLabel` applies metadata to a container e. g. `-l, --label dotnet.testcontainers=awesome`.
- `WithExposedPort` exposes a port inside the container e. g. `--expose=80`.
- `WithPortBinding` publishes a container port to the host e. g. `-p, --publish 80:80`.
- `WithBindMount` binds a path of a file or directory into the container e. g. `-v, --volume .:/tmp`.
- `WithVolumeMount` mounts a managed volume into the container e. g. `--mount type=volume,source=.,destination=/tmp`.
- `WithNetwork` assigns a network to a container e. g. `--network="bridge"`.
- `WithCleanUp` removes a stopped container automatically.
- `WithDockerEndpoint` sets the Docker API endpoint e. g. `-H tcp://0.0.0.0:2376`.
- `WithName` sets the container name e.g. `--name nginx`.
- `WithHostname` sets the container hostname e.g. `--hostname my-nginx`.
- `WithEnvironment` sets an environment variable in the container e.g. `-e, --env "test=containers"`.
- `WithLabel` applies metadata to the container e.g. `-l, --label dotnet.testcontainers=awesome`.
- `WithExposedPort` exposes a port inside the container e.g. `--expose=80`.
- `WithPortBinding` publishes the container port to the host e.g. `-p, --publish 80:80`.
- `WithBindMount` binds a path of a file or directory into the container e.g. `-v, --volume .:/tmp`.
- `WithVolumeMount` mounts a managed volume into the container e.g. `--mount type=volume,source=.,destination=/tmp`.
- `WithNetwork` assigns a network to the container e.g. `--network="bridge"`.
- `WithDockerEndpoint` sets the Docker API endpoint e.g. `-H tcp://0.0.0.0:2376`.
- `WithRegistryAuthentication` basic authentication against a private Docker registry.
- `WithOutputConsumer` redirects `stdout` and `stderr` to capture the Testcontainer output.
- `WithWaitStrategy` sets the wait strategy to complete the Testcontainer start and indicates when it is ready.
- `WithStartupCallback` sets the startup callback to invoke after the Testcontainer start.
- `WithDockerfileDirectory` builds a Docker image based on a Dockerfile (`ImageFromDockerfileBuilder`).
- `WithDeleteIfExists` removes the Docker image before it is rebuilt (`ImageFromDockerfileBuilder`).
- `WithOutputConsumer` redirects `stdout` and `stderr` to capture the container output.
- `WithWaitStrategy` sets the wait strategy to complete the container start and indicates when it is ready.
- `WithStartupCallback` sets the startup callback to invoke after the container start.
- `WithPrivileged` sets the `--privileged` flag.
- `WithAutoRemove` will remove the stopped container automatically like `--rm`.
- `WithCleanUp` will remove the container automatically after all tests have been run (see [Resource Reaper](#Resource Reaper)).
- `WithResourceReaperSessionId` assigns a Resource Reaper session id to the container.

Use the additional builder for image (`ImageFromDockerfileBuilder`), network (`TestcontainersNetworkBuilder`) and volume (`TestcontainersVolumeBuilder`) to set up your individual test environment.

## Resource Reaper

Testcontainers assigns each Docker resource a Resource Reaper session id. After the tests are finished, [Ryuk][moby-ryuk] will take care of remaining Docker resources and removes them. You can change the Resource Reaper session and group Docker resources together with `WithResourceReaperSessionId`. Right now, only Linux containers are supported.

## Pre-configured containers

Expand Down Expand Up @@ -67,9 +76,9 @@ var testcontainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
.WithPortBinding(80)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(80));

await using (var testcontainer = testcontainersBuilder.Build())
await using (var testcontainers = testcontainersBuilder.Build())
{
await testcontainer.StartAsync();
await testcontainers.StartAsync();
var request = WebRequest.Create("http://localhost:80");
}
```
Expand All @@ -80,42 +89,50 @@ Mounts the current directory as volume into the container and runs `hostname > /
var testcontainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("nginx")
.WithName("nginx")
.WithMount(".", "/tmp")
.WithCommand("/bin/bash", "-c", "hostname > /tmp/hostname")
.WithBindMount(".", "/tmp")
.WithEntrypoint("/bin/sh", "-c")
.WithCommand("hostname > /tmp/hostname")
.WithWaitStrategy(Wait.ForUnixContainer().UntilFileExists("/tmp/hostname"));

await using (var testcontainer = testcontainersBuilder.Build())
await using (var testcontainers = testcontainersBuilder.Build())
{
await testcontainer.StartAsync();
await testcontainers.StartAsync();
}
```

Here is an example of a pre-configured Testcontainer. In the example, Testcontainers starts a PostgreSQL database and executes a SQL query.
Here is an example of a pre-configured container. In the example, Testcontainers starts a PostgreSQL database in a [xUnit.net][xunit] test and executes a SQL query.

```csharp
var testcontainersBuilder = new TestcontainersBuilder<PostgreSqlTestcontainer>()
.WithDatabase(new PostgreSqlTestcontainerConfiguration
{
Database = "db",
Username = "postgres",
Password = "postgres",
});

await using (var testcontainer = testcontainersBuilder.Build())
public sealed class PostgreSqlTest : IAsyncLifetime
{
await testcontainer.StartAsync();
private readonly TestcontainerDatabase container = new TestcontainersBuilder<TestcontainerDatabase>()
.WithDatabase(new PostgreSqlTestcontainerConfiguration())
.Build();

using (var connection = new NpgsqlConnection(testcontainer.ConnectionString))
[Fact]
public void ExecuteCommand()
{
connection.Open();

using (var cmd = new NpgsqlCommand())
using (var connection = new NpgsqlConnection(this.container.ConnectionString))
{
cmd.Connection = connection;
cmd.CommandText = "SELECT 1";
cmd.ExecuteReader();
using (var command = new NpgsqlCommand())
{
connection.Open();
command.Connection = connection;
command.CommandText = "SELECT 1";
command.ExecuteReader();
}
}
}

public Task InitializeAsync()
{
return this.container.StartAsync();
}

public Task DisposeAsync()
{
return this.container.DisposeAsync().AsTask();
}
}
```

Expand Down Expand Up @@ -153,6 +170,8 @@ Many thanks to [JetBrains](https://www.jetbrains.com/?from=dotnet-testcontainers

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

[1]: https://github.com/HofmeisterAn/dotnet-testcontainers/blob/develop/src/DotNet.Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs
[2]: https://github.com/HofmeisterAn/dotnet-testcontainers/blob/develop/src/DotNet.Testcontainers.Tests/Unit/Containers/Unix/Database
[3]: https://github.com/HofmeisterAn/dotnet-testcontainers/blob/develop/src/DotNet.Testcontainers.Tests/Unit/Containers/Unix/MessageBroker
[1]: https://github.com/HofmeisterAn/dotnet-testcontainers/blob/develop/tests/DotNet.Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs
[2]: https://github.com/HofmeisterAn/dotnet-testcontainers/blob/develop/tests/DotNet.Testcontainers.Tests/Unit/Containers/Unix/Modules/Databases
[3]: https://github.com/HofmeisterAn/dotnet-testcontainers/blob/develop/tests/DotNet.Testcontainers.Tests/Unit/Containers/Unix/Modules/MessageBrokers
[moby-ryuk]: https://github.com/testcontainers/moby-ryuk
[xunit]: https://xunit.net/
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ variables:
MSBUILDDISABLENODEREUSE: true # https://github.com/dotnet/sdk/issues/9452
DECODE_PERCENTS: true # https://github.com/microsoft/azure-pipelines-agent/pull/3152
TZ: CET # https://stackoverflow.com/q/53510011
dotNetCoreVersion: 6.0.100
dotNetCoreVersion: 6.0.101

trigger:
- master
Expand Down
18 changes: 17 additions & 1 deletion build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Task("Build")
});

Task("Tests")
.IsDependentOn("Pull-Docker-Images")
.Does(() =>
{
foreach(var testProject in param.Projects.OnlyTests)
Expand All @@ -88,7 +89,8 @@ Task("Tests")
ArgumentCustomization = args => args
.Append("/p:Platform=AnyCPU")
.Append("/p:CollectCoverage=true")
.Append("/p:CoverletOutputFormat=opencover")
.Append("/p:CoverletOutputFormat=\"json%2copencover\"") // https://github.com/coverlet-coverage/coverlet/pull/220#issuecomment-431507570.
.Append($"/p:MergeWith=\"{MakeAbsolute(param.Paths.Directories.TestCoverage)}/coverage.net6.0.json\"")
.Append($"/p:CoverletOutput=\"{MakeAbsolute(param.Paths.Directories.TestCoverage)}/\"")
});
}
Expand Down Expand Up @@ -161,6 +163,20 @@ Task("Publish-NuGet-Packages")
}
});

Task("Pull-Docker-Images")
.WithCriteria(() => PlatformFamily.Linux.Equals(Context.Environment.Platform.Family))
.Does(() =>
{
StartProcess("docker", new ProcessSettings
{
RedirectStandardOutput = true,
RedirectStandardError = true,
Arguments = new ProcessArgumentBuilder()
.Append("pull")
.Append("ghcr.io/psanetra/ryuk:2021.12.20")
});
});

Task("Default")
.IsDependentOn("Clean")
.IsDependentOn("Restore-NuGet-Packages")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace DotNet.Testcontainers.Builders
{
using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using JetBrains.Annotations;

Expand Down Expand Up @@ -50,9 +52,27 @@ public interface IImageFromDockerfileBuilder
IImageFromDockerfileBuilder WithDeleteIfExists(bool deleteIfExists);

/// <summary>
/// Builds the instance of <see cref="IImageFromDockerfileBuilder" /> with the given configuration.
/// Adds user-defined metadata to the Docker image.
/// </summary>
/// <param name="name">Label name.</param>
/// <param name="value">Label value.</param>
/// <returns>A configured instance of <see cref="IImageFromDockerfileBuilder" />.</returns>
[PublicAPI]
IImageFromDockerfileBuilder WithLabel(string name, string value);

/// <summary>
/// Sets the resource reaper session id.
/// </summary>
/// <param name="resourceReaperSessionId">The session id of the <see cref="ResourceReaper" /> instance.</param>
/// <returns>A configured instance of <see cref="IImageFromDockerfileBuilder" />.</returns>
/// <remarks>The <see cref="ResourceReaper" /> will delete the resource after the tests has been finished.</remarks>
[PublicAPI]
IImageFromDockerfileBuilder WithResourceReaperSessionId(Guid resourceReaperSessionId);

/// <summary>
/// Builds the instance of <see cref="IImageFromDockerfileBuilder" /> with the given configuration.
/// </summary>
/// <returns>FullName of the created image.</returns>
[PublicAPI]
Task<string> Build();
}
Expand Down
29 changes: 27 additions & 2 deletions src/DotNet.Testcontainers/Builders/ITestcontainersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,29 @@ public interface ITestcontainersBuilder<out TDockerContainer>
ITestcontainersBuilder<TDockerContainer> WithNetwork(IDockerNetwork dockerNetwork);

/// <summary>
/// If true, Testcontainer will remove the Testcontainer on finalize. Otherwise, Testcontainer will keep the Testcontainer.
/// If true, the <see cref="ResourceReaper" /> will remove the stopped Testcontainer automatically. Otherwise, the Testcontainer will be kept.
/// </summary>
/// <param name="cleanUp">True, Testcontainer will remove the Testcontainer on finalize. Otherwise, Testcontainer will keep it.</param>
/// <param name="cleanUp">True, the <see cref="ResourceReaper" /> will remove the stopped Testcontainer automatically. Otherwise, the Testcontainer will be kept.</param>
/// <returns>A configured instance of <see cref="ITestcontainersBuilder{TDockerContainer}" />.</returns>
[PublicAPI]
ITestcontainersBuilder<TDockerContainer> WithCleanUp(bool cleanUp);

/// <summary>
/// If true, the Docker daemon will remove the stopped Testcontainer automatically. Otherwise, the Testcontainer will be kept.
/// </summary>
/// <param name="autoRemove">True, the Docker daemon will remove the stopped Testcontainer automatically. Otherwise, the Testcontainer will be kept.</param>
/// <returns>A configured instance of <see cref="ITestcontainersBuilder{TDockerContainer}" />.</returns>
[PublicAPI]
ITestcontainersBuilder<TDockerContainer> WithAutoRemove(bool autoRemove);

/// <summary>
/// If true, the Testcontainer will get extended privileges. Otherwise, the Testcontainer will be unprivileged.
/// </summary>
/// <param name="privileged">The Testcontainer will get extended privileges. Otherwise, the Testcontainer will be unprivileged.</param>
/// <returns>A configured instance of <see cref="ITestcontainersBuilder{TDockerContainer}" />.</returns>
[PublicAPI]
ITestcontainersBuilder<TDockerContainer> WithPrivileged(bool privileged);

/// <summary>
/// Sets the Docker API endpoint.
/// </summary>
Expand Down Expand Up @@ -301,6 +317,15 @@ public interface ITestcontainersBuilder<out TDockerContainer>
[PublicAPI]
ITestcontainersBuilder<TDockerContainer> WithStartupCallback(Func<IRunningDockerContainer, CancellationToken, Task> startupCallback);

/// <summary>
/// Sets the resource reaper session id.
/// </summary>
/// <param name="resourceReaperSessionId">The session id of the <see cref="ResourceReaper" /> instance.</param>
/// <returns>A configured instance of <see cref="ITestcontainersBuilder{TDockerContainer}" />.</returns>
/// <remarks>The <see cref="ResourceReaper" /> will delete the resource after the tests has been finished.</remarks>
[PublicAPI]
ITestcontainersBuilder<TDockerContainer> WithResourceReaperSessionId(Guid resourceReaperSessionId);

/// <summary>
/// Builds the instance of <see cref="ITestcontainersContainer" /> with the given configuration.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace DotNet.Testcontainers.Builders
{
using System;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Network;
using JetBrains.Annotations;

Expand Down Expand Up @@ -42,6 +44,15 @@ public interface ITestcontainersNetworkBuilder
[PublicAPI]
ITestcontainersNetworkBuilder WithLabel(string name, string value);

/// <summary>
/// Sets the resource reaper session id.
/// </summary>
/// <param name="resourceReaperSessionId">The session id of the <see cref="ResourceReaper" /> instance.</param>
/// <returns>A configured instance of <see cref="IImageFromDockerfileBuilder" />.</returns>
/// <remarks>The <see cref="ResourceReaper" /> will delete the resource after the tests has been finished.</remarks>
[PublicAPI]
ITestcontainersNetworkBuilder WithResourceReaperSessionId(Guid resourceReaperSessionId);

/// <summary>
/// Builds the instance of <see cref="IDockerNetwork" /> with the given configuration.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/DotNet.Testcontainers/Builders/ITestcontainersVolumeBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace DotNet.Testcontainers.Builders
{
using System;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Volumes;
using JetBrains.Annotations;

Expand Down Expand Up @@ -33,6 +35,15 @@ public interface ITestcontainersVolumeBuilder
[PublicAPI]
ITestcontainersVolumeBuilder WithLabel(string name, string value);

/// <summary>
/// Sets the resource reaper session id.
/// </summary>
/// <param name="resourceReaperSessionId">The session id of the <see cref="ResourceReaper" /> instance.</param>
/// <returns>A configured instance of <see cref="IImageFromDockerfileBuilder" />.</returns>
/// <remarks>The <see cref="ResourceReaper" /> will delete the resource after the tests has been finished.</remarks>
[PublicAPI]
ITestcontainersVolumeBuilder WithResourceReaperSessionId(Guid resourceReaperSessionId);

/// <summary>
/// Builds the instance of <see cref="IDockerVolume" /> with the given configuration.
/// </summary>
Expand Down
Loading

0 comments on commit 0d037ca

Please sign in to comment.