Skip to content

Commit 298aad4

Browse files
feat: Restore output consumer support (#959)
Co-authored-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
1 parent 05514c0 commit 298aad4

File tree

9 files changed

+63
-24
lines changed

9 files changed

+63
-24
lines changed

docs/api/create_docker_container.md

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ Assert.Equal(MagicNumber, magicNumber);
141141
| `WithNetworkAliases` | Assigns a network-scoped aliases to the container e.g. `--network-alias "alias"`. |
142142
| `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"`. |
143143
| `WithPrivileged` | Sets the `--privileged` flag. |
144+
| `WithOutputConsumer` | Redirects `stdout` and `stderr` to capture the container output. |
144145
| `WithWaitStrategy` | Sets the wait strategy to complete the container start and indicates when it is ready. |
145146
| `WithStartupCallback` | Sets the startup callback to invoke after the container start. |
146147
| `WithCreateParameterModifier` | Allows low level modifications of the Docker container create parameter. |

src/Testcontainers/Builders/ContainerBuilder`3.cs

-1
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ public TBuilderEntity WithPrivileged(bool privileged)
328328
}
329329

330330
/// <inheritdoc />
331-
[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.")]
332331
public TBuilderEntity WithOutputConsumer(IOutputConsumer outputConsumer)
333332
{
334333
return Clone(new ContainerConfiguration(outputConsumer: outputConsumer));

src/Testcontainers/Clients/ITestcontainersClient.cs

+9
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ internal interface ITestcontainersClient
9595
/// <returns>Task that completes when the container has been removed.</returns>
9696
Task RemoveAsync(string id, CancellationToken ct = default);
9797

98+
/// <summary>
99+
/// Attaches to the container and copies the output to the <see cref="IOutputConsumer" />.
100+
/// </summary>
101+
/// <param name="id">The container id.</param>
102+
/// <param name="outputConsumer">The stdout and stderr consumer.</param>
103+
/// <param name="ct">Cancellation token.</param>
104+
/// <returns>Task that completes when the container's stdout and stderr has been copied to the consumer.</returns>
105+
Task AttachAsync(string id, IOutputConsumer outputConsumer, CancellationToken ct = default);
106+
98107
/// <summary>
99108
/// Executes a command in the container.
100109
/// </summary>

src/Testcontainers/Clients/TestcontainersClient.cs

+6
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ await Container.RemoveAsync(id, ct)
164164
}
165165
}
166166

167+
/// <inheritdoc />
168+
public Task AttachAsync(string id, IOutputConsumer outputConsumer, CancellationToken ct = default)
169+
{
170+
return Container.AttachAsync(id, outputConsumer, ct);
171+
}
172+
167173
/// <inheritdoc />
168174
public Task<ExecResult> ExecAsync(string id, IList<string> command, CancellationToken ct = default)
169175
{

src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs

-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Configurations
22
{
33
using System;
44
using System.Collections.Generic;
5-
using System.IO;
65
using System.Text.RegularExpressions;
76
using JetBrains.Annotations;
87

@@ -74,16 +73,6 @@ public interface IWaitForContainerOS
7473
[PublicAPI]
7574
IWaitForContainerOS UntilMessageIsLogged(Regex pattern);
7675

77-
/// <summary>
78-
/// Waits until the message is logged in the steam.
79-
/// </summary>
80-
/// <param name="stream">The stream to be searched.</param>
81-
/// <param name="message">The message to be checked.</param>
82-
/// <returns>A configured instance of <see cref="IWaitForContainerOS" />.</returns>
83-
[PublicAPI]
84-
[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.")]
85-
IWaitForContainerOS UntilMessageIsLogged(Stream stream, string message);
86-
8776
/// <summary>
8877
/// Waits until the operation is completed successfully.
8978
/// </summary>

src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs

-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Configurations
22
{
33
using System;
44
using System.Collections.Generic;
5-
using System.IO;
65
using System.Text.RegularExpressions;
76

87
/// <inheritdoc cref="IWaitForContainerOS" />
@@ -52,12 +51,6 @@ public IWaitForContainerOS UntilMessageIsLogged(Regex pattern)
5251
return AddCustomWaitStrategy(new UntilMessageIsLogged(pattern));
5352
}
5453

55-
/// <inheritdoc />
56-
public virtual IWaitForContainerOS UntilMessageIsLogged(Stream stream, string message)
57-
{
58-
return AddCustomWaitStrategy(new UntilMessageIsLogged(message));
59-
}
60-
6154
/// <inheritdoc />
6255
public virtual IWaitForContainerOS UntilOperationIsSucceeded(Func<bool> operation, int maxCallCount)
6356
{

src/Testcontainers/Containers/DockerContainer.cs

+3
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,9 @@ async Task<bool> CheckWaitStrategyAsync(IWaitUntil wait)
423423
.ConfigureAwait(false);
424424
}
425425

426+
await _client.AttachAsync(_container.ID, _configuration.OutputConsumer, ct)
427+
.ConfigureAwait(false);
428+
426429
await _client.StartAsync(_container.ID, ct)
427430
.ConfigureAwait(false);
428431

tests/Testcontainers.Tests/Unit/Configurations/WaitUntilHttpRequestIsSucceededTest.cs

+7-5
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ public async Task HttpWaitStrategyUsesCustomHttpClientHandler()
9090
var cookieContainer = new CookieContainer();
9191
cookieContainer.Add(new Cookie("Key1", "Value1", "/", _container.Hostname));
9292

93-
var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(new HttpClientHandler
94-
{
95-
CookieContainer = cookieContainer
96-
});
93+
using var httpMessageHandler = new HttpClientHandler();
94+
httpMessageHandler.CookieContainer = cookieContainer;
95+
96+
var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(httpMessageHandler);
9797

9898
// When
9999
var succeeded = await httpWaitStrategy.UntilAsync(_container)
@@ -115,7 +115,9 @@ await Task.Delay(TimeSpan.FromSeconds(1))
115115
public async Task HttpWaitStrategyCanReuseCustomHttpClientHandler()
116116
{
117117
// Given
118-
var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(new HttpClientHandler());
118+
using var httpMessageHandler = new HttpClientHandler();
119+
120+
var httpWaitStrategy = new HttpWaitStrategy().UsingHttpMessageHandler(httpMessageHandler);
119121

120122
// When
121123
await httpWaitStrategy.UntilAsync(_container)

tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs

+37
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace DotNet.Testcontainers.Tests.Unit
22
{
33
using System;
4+
using System.Globalization;
45
using System.IO;
56
using System.Net;
67
using System.Net.Sockets;
@@ -279,6 +280,42 @@ public async Task HostnameShouldMatchDockerGatewayAddress(string expectedHostnam
279280
Assert.Equal(expectedHostname, container.Hostname);
280281
}
281282

283+
[Fact]
284+
public async Task OutputConsumer()
285+
{
286+
// Given
287+
using var consumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream());
288+
289+
var unixTimeInMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
290+
291+
// When
292+
await using var container = new ContainerBuilder()
293+
.WithImage(CommonImages.Alpine)
294+
.WithEntrypoint("/bin/sh", "-c")
295+
.WithCommand($"printf \"%s\" \"{unixTimeInMilliseconds}\" | tee /dev/stderr")
296+
.WithOutputConsumer(consumer)
297+
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(unixTimeInMilliseconds))
298+
.Build();
299+
300+
await container.StartAsync()
301+
.ConfigureAwait(false);
302+
303+
consumer.Stdout.Seek(0, SeekOrigin.Begin);
304+
consumer.Stderr.Seek(0, SeekOrigin.Begin);
305+
306+
using var stdoutReader = new StreamReader(consumer.Stdout, leaveOpen: true);
307+
var stdout = await stdoutReader.ReadToEndAsync()
308+
.ConfigureAwait(false);
309+
310+
using var stderrReader = new StreamReader(consumer.Stderr, leaveOpen: true);
311+
var stderr = await stderrReader.ReadToEndAsync()
312+
.ConfigureAwait(false);
313+
314+
// Then
315+
Assert.Equal(unixTimeInMilliseconds, stdout);
316+
Assert.Equal(unixTimeInMilliseconds, stderr);
317+
}
318+
282319
[Fact]
283320
public async Task WaitStrategy()
284321
{

0 commit comments

Comments
 (0)