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()
{