diff --git a/docs/api/create_docker_container.md b/docs/api/create_docker_container.md index 63322f439..82679a07a 100644 --- a/docs/api/create_docker_container.md +++ b/docs/api/create_docker_container.md @@ -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. | 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/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 1d36df8e8..3127a9962 100644 --- a/src/Testcontainers/Containers/DockerContainer.cs +++ b/src/Testcontainers/Containers/DockerContainer.cs @@ -423,6 +423,9 @@ async Task CheckWaitStrategyAsync(IWaitUntil wait) .ConfigureAwait(false); } + 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 1353148e8..de3b6fd01 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; @@ -279,6 +280,42 @@ public async Task HostnameShouldMatchDockerGatewayAddress(string expectedHostnam Assert.Equal(expectedHostname, container.Hostname); } + [Fact] + public async Task OutputConsumer() + { + // Given + using var consumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream()); + + var unixTimeInMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture); + + // When + await using var container = new ContainerBuilder() + .WithImage(CommonImages.Alpine) + .WithEntrypoint("/bin/sh", "-c") + .WithCommand($"printf \"%s\" \"{unixTimeInMilliseconds}\" | tee /dev/stderr") + .WithOutputConsumer(consumer) + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(unixTimeInMilliseconds)) + .Build(); + + await container.StartAsync() + .ConfigureAwait(false); + + consumer.Stdout.Seek(0, SeekOrigin.Begin); + consumer.Stderr.Seek(0, SeekOrigin.Begin); + + using var stdoutReader = new StreamReader(consumer.Stdout, leaveOpen: true); + var stdout = await stdoutReader.ReadToEndAsync() + .ConfigureAwait(false); + + using var stderrReader = new StreamReader(consumer.Stderr, leaveOpen: true); + var stderr = await stderrReader.ReadToEndAsync() + .ConfigureAwait(false); + + // Then + Assert.Equal(unixTimeInMilliseconds, stdout); + Assert.Equal(unixTimeInMilliseconds, stderr); + } + [Fact] public async Task WaitStrategy() {