From 8a2e2d606ec339f8c55aadc2c67350d61f936298 Mon Sep 17 00:00:00 2001 From: Jacob Marks Date: Fri, 28 Jul 2023 15:58:33 +1000 Subject: [PATCH 1/4] Restore output consumer support --- .../Builders/ContainerBuilder`3.cs | 1 - .../Containers/DockerContainer.cs | 2 ++ .../Unix/TestcontainersContainerTest.cs | 31 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Testcontainers/Builders/ContainerBuilder`3.cs b/src/Testcontainers/Builders/ContainerBuilder`3.cs index 9f3c6cf81..13ac2b573 100644 --- a/src/Testcontainers/Builders/ContainerBuilder`3.cs +++ b/src/Testcontainers/Builders/ContainerBuilder`3.cs @@ -328,7 +328,6 @@ public TBuilderEntity WithPrivileged(bool privileged) } /// - [Obsolete("It is no longer necessary to assign an output consumer to read the container's log messages.\nUse IContainer.GetLogsAsync(DateTime, DateTime, bool, CancellationToken) instead.")] public TBuilderEntity WithOutputConsumer(IOutputConsumer outputConsumer) { return Clone(new ContainerConfiguration(outputConsumer: outputConsumer)); diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs index 1d36df8e8..75f764a60 100644 --- a/src/Testcontainers/Containers/DockerContainer.cs +++ b/src/Testcontainers/Containers/DockerContainer.cs @@ -423,6 +423,8 @@ async Task CheckWaitStrategyAsync(IWaitUntil wait) .ConfigureAwait(false); } + await _client.Container.AttachAsync(_container.ID, _configuration.OutputConsumer, ct); + await _client.StartAsync(_container.ID, ct) .ConfigureAwait(false); diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index 1353148e8..8b281e755 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -279,6 +279,37 @@ public async Task HostnameShouldMatchDockerGatewayAddress(string expectedHostnam Assert.Equal(expectedHostname, container.Hostname); } + [Fact] + public async Task OutputConsumer() + { + // Given + var unixTimeInMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture); + + using var consumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream()); + + // When + var containerBuilder = new ContainerBuilder() + .WithImage(CommonImages.Alpine) + .WithEntrypoint("/bin/sh", "-c", $"printf \"%s\" \"{unixTimeInMilliseconds}\" | tee /dev/stderr") + .WithOutputConsumer(consumer) + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilMessageIsLogged(unixTimeInMilliseconds) + .UntilMessageIsLogged(unixTimeInMilliseconds)); + + await using (var container = containerBuilder.Build()) + await container.StartAsync(); + + consumer.Stdout.Seek(0, SeekOrigin.Begin); + consumer.Stderr.Seek(0, SeekOrigin.Begin); + + // Then + using (var streamReader = new StreamReader(consumer.Stdout, leaveOpen: true)) + Assert.Equal(unixTimeInMilliseconds, await streamReader.ReadToEndAsync()); + + using (var streamReader = new StreamReader(consumer.Stderr, leaveOpen: true)) + Assert.Equal(unixTimeInMilliseconds, await streamReader.ReadToEndAsync()); + } + [Fact] public async Task WaitStrategy() { From 3f4a194b9ab75af0b28ce272cb91a4010f4ccd5e Mon Sep 17 00:00:00 2001 From: Jacob Marks Date: Fri, 28 Jul 2023 17:10:57 +1000 Subject: [PATCH 2/4] Update docs --- docs/api/create_docker_container.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/create_docker_container.md b/docs/api/create_docker_container.md index 63322f439..e476356e1 100644 --- a/docs/api/create_docker_container.md +++ b/docs/api/create_docker_container.md @@ -116,7 +116,7 @@ Assert.Equal(MagicNumber, magicNumber); ## Supported commands | Builder method | Description | -|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `DependsOn` | Sets the dependent resource to resolve and create or start before starting this container configuration. | | `WithDockerEndpoint` | Sets the Docker daemon socket to connect to. | | `WithAutoRemove` | Will remove the stopped container automatically, similar to `--rm`. | @@ -141,6 +141,7 @@ Assert.Equal(MagicNumber, magicNumber); | `WithNetworkAliases` | Assigns a network-scoped aliases to the container e.g. `--network-alias "alias"`. | | `WithExtraHost` | Adds a custom host-to-IP mapping to the container's `/etc/hosts` respectively `%WINDIR%\\system32\\drivers\\etc\\hosts` e.g. `--add-host "host.testcontainers.internal:172.17.0.2"`. | | `WithPrivileged` | Sets the `--privileged` flag. | +| `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. | | `WithCreateParameterModifier` | Allows low level modifications of the Docker container create parameter. | From e470a0558f78d84ba4d60ed4d2ab6dfd8ba2d21f Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sun, 30 Jul 2023 23:03:30 +0200 Subject: [PATCH 3/4] Add ITestcontainersClient.AttachAsync again --- .../Clients/ITestcontainersClient.cs | 9 +++++ .../Clients/TestcontainersClient.cs | 6 ++++ .../WaitStrategies/IWaitForContainerOS.cs | 11 ------ .../WaitStrategies/WaitForContainerOS.cs | 7 ---- .../Containers/DockerContainer.cs | 3 +- .../WaitUntilHttpRequestIsSucceededTest.cs | 12 ++++--- .../Unix/TestcontainersContainerTest.cs | 34 +++++++++++-------- 7 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/Testcontainers/Clients/ITestcontainersClient.cs b/src/Testcontainers/Clients/ITestcontainersClient.cs index b388b872a..dcb8b6554 100644 --- a/src/Testcontainers/Clients/ITestcontainersClient.cs +++ b/src/Testcontainers/Clients/ITestcontainersClient.cs @@ -95,6 +95,15 @@ internal interface ITestcontainersClient /// Task that completes when the container has been removed. Task RemoveAsync(string id, CancellationToken ct = default); + /// + /// Attaches to the container and copies the output to the . + /// + /// The container id. + /// The stdout and stderr consumer. + /// Cancellation token. + /// Task that completes when the container's stdout and stderr has been copied to the consumer. + Task AttachAsync(string id, IOutputConsumer outputConsumer, CancellationToken ct = default); + /// /// Executes a command in the container. /// diff --git a/src/Testcontainers/Clients/TestcontainersClient.cs b/src/Testcontainers/Clients/TestcontainersClient.cs index 530c85ddd..71cdcce71 100644 --- a/src/Testcontainers/Clients/TestcontainersClient.cs +++ b/src/Testcontainers/Clients/TestcontainersClient.cs @@ -164,6 +164,12 @@ await Container.RemoveAsync(id, ct) } } + /// + public Task AttachAsync(string id, IOutputConsumer outputConsumer, CancellationToken ct = default) + { + return Container.AttachAsync(id, outputConsumer, ct); + } + /// public Task ExecAsync(string id, IList command, CancellationToken ct = default) { diff --git a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs index 80f4b2b5d..ad7d64d30 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs @@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Configurations { using System; using System.Collections.Generic; - using System.IO; using System.Text.RegularExpressions; using JetBrains.Annotations; @@ -74,16 +73,6 @@ public interface IWaitForContainerOS [PublicAPI] IWaitForContainerOS UntilMessageIsLogged(Regex pattern); - /// - /// Waits until the message is logged in the steam. - /// - /// The stream to be searched. - /// The message to be checked. - /// A configured instance of . - [PublicAPI] - [Obsolete("It is no longer necessary to assign an output consumer to read the container's log messages.\nUse IWaitForContainerOS.UntilMessageIsLogged(string) or IWaitForContainerOS.UntilMessageIsLogged(Regex) instead.")] - IWaitForContainerOS UntilMessageIsLogged(Stream stream, string message); - /// /// Waits until the operation is completed successfully. /// diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs index 39e2f1f08..88b2161d2 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs @@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Configurations { using System; using System.Collections.Generic; - using System.IO; using System.Text.RegularExpressions; /// @@ -52,12 +51,6 @@ public IWaitForContainerOS UntilMessageIsLogged(Regex pattern) return AddCustomWaitStrategy(new UntilMessageIsLogged(pattern)); } - /// - public virtual IWaitForContainerOS UntilMessageIsLogged(Stream stream, string message) - { - return AddCustomWaitStrategy(new UntilMessageIsLogged(message)); - } - /// public virtual IWaitForContainerOS UntilOperationIsSucceeded(Func operation, int maxCallCount) { diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs index 75f764a60..3127a9962 100644 --- a/src/Testcontainers/Containers/DockerContainer.cs +++ b/src/Testcontainers/Containers/DockerContainer.cs @@ -423,7 +423,8 @@ async Task CheckWaitStrategyAsync(IWaitUntil wait) .ConfigureAwait(false); } - await _client.Container.AttachAsync(_container.ID, _configuration.OutputConsumer, ct); + await _client.AttachAsync(_container.ID, _configuration.OutputConsumer, ct) + .ConfigureAwait(false); await _client.StartAsync(_container.ID, ct) .ConfigureAwait(false); diff --git a/tests/Testcontainers.Tests/Unit/Configurations/WaitUntilHttpRequestIsSucceededTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/WaitUntilHttpRequestIsSucceededTest.cs index 33cc395fb..f1d197819 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/WaitUntilHttpRequestIsSucceededTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/WaitUntilHttpRequestIsSucceededTest.cs @@ -90,10 +90,10 @@ public async Task HttpWaitStrategyUsesCustomHttpClientHandler() var cookieContainer = new CookieContainer(); cookieContainer.Add(new Cookie("Key1", "Value1", "/", _container.Hostname)); - var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(new HttpClientHandler - { - CookieContainer = cookieContainer - }); + using var httpMessageHandler = new HttpClientHandler(); + httpMessageHandler.CookieContainer = cookieContainer; + + var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(httpMessageHandler); // When var succeeded = await httpWaitStrategy.UntilAsync(_container) @@ -115,7 +115,9 @@ await Task.Delay(TimeSpan.FromSeconds(1)) public async Task HttpWaitStrategyCanReuseCustomHttpClientHandler() { // Given - var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(new HttpClientHandler()); + using var httpMessageHandler = new HttpClientHandler(); + + var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(httpMessageHandler); // When await httpWaitStrategy.UntilAsync(_container) diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index 8b281e755..bda8a2366 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -1,6 +1,7 @@ namespace DotNet.Testcontainers.Tests.Unit { using System; + using System.Globalization; using System.IO; using System.Net; using System.Net.Sockets; @@ -283,31 +284,36 @@ public async Task HostnameShouldMatchDockerGatewayAddress(string expectedHostnam public async Task OutputConsumer() { // Given - var unixTimeInMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture); - using var consumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream()); + var unixTimeInMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture); + // When - var containerBuilder = new ContainerBuilder() + await using var container = new ContainerBuilder() .WithImage(CommonImages.Alpine) - .WithEntrypoint("/bin/sh", "-c", $"printf \"%s\" \"{unixTimeInMilliseconds}\" | tee /dev/stderr") + .WithEntrypoint("/bin/sh", "-c") + .WithCommand($"printf \"%s\" \"{unixTimeInMilliseconds}\" | tee /dev/stderr") .WithOutputConsumer(consumer) - .WithWaitStrategy(Wait.ForUnixContainer() - .UntilMessageIsLogged(unixTimeInMilliseconds) - .UntilMessageIsLogged(unixTimeInMilliseconds)); + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(unixTimeInMilliseconds)) + .Build(); - await using (var container = containerBuilder.Build()) - await container.StartAsync(); + await container.StartAsync() + .ConfigureAwait(false); consumer.Stdout.Seek(0, SeekOrigin.Begin); consumer.Stderr.Seek(0, SeekOrigin.Begin); - // Then - using (var streamReader = new StreamReader(consumer.Stdout, leaveOpen: true)) - Assert.Equal(unixTimeInMilliseconds, await streamReader.ReadToEndAsync()); + using var stdoutReader = new StreamReader(consumer.Stdout, leaveOpen: true); + var stdout = await stdoutReader.ReadToEndAsync() + .ConfigureAwait(false); - using (var streamReader = new StreamReader(consumer.Stderr, leaveOpen: true)) - Assert.Equal(unixTimeInMilliseconds, await streamReader.ReadToEndAsync()); + using var stderrReader = new StreamReader(consumer.Stdout, leaveOpen: true); + var stderr = await stderrReader.ReadToEndAsync() + .ConfigureAwait(false); + + // Then + Assert.Equal(unixTimeInMilliseconds, stdout); + Assert.Equal(unixTimeInMilliseconds, stderr); } [Fact] From 9d44724751c4bab8c0134304fdf84ab10f8cea5d Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sun, 30 Jul 2023 23:20:56 +0200 Subject: [PATCH 4/4] fix: Use correct stream in test --- docs/api/create_docker_container.md | 2 +- .../Unit/Containers/Unix/TestcontainersContainerTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/create_docker_container.md b/docs/api/create_docker_container.md index e476356e1..82679a07a 100644 --- a/docs/api/create_docker_container.md +++ b/docs/api/create_docker_container.md @@ -116,7 +116,7 @@ Assert.Equal(MagicNumber, magicNumber); ## Supported commands | Builder method | Description | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `DependsOn` | Sets the dependent resource to resolve and create or start before starting this container configuration. | | `WithDockerEndpoint` | Sets the Docker daemon socket to connect to. | | `WithAutoRemove` | Will remove the stopped container automatically, similar to `--rm`. | diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index bda8a2366..de3b6fd01 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -307,7 +307,7 @@ await container.StartAsync() var stdout = await stdoutReader.ReadToEndAsync() .ConfigureAwait(false); - using var stderrReader = new StreamReader(consumer.Stdout, leaveOpen: true); + using var stderrReader = new StreamReader(consumer.Stderr, leaveOpen: true); var stderr = await stderrReader.ReadToEndAsync() .ConfigureAwait(false);