Skip to content

Commit

Permalink
feat: Replace output consumer with `IContainer.GetLogsAsync(DateTime,…
Browse files Browse the repository at this point in the history
… DateTime, bool, CancellationToken)` (#793)
  • Loading branch information
HofmeisterAn authored Feb 19, 2023
1 parent eaa6068 commit d3ae00f
Show file tree
Hide file tree
Showing 21 changed files with 138 additions and 143 deletions.
1 change: 0 additions & 1 deletion docs/api/create_docker_container.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ Assert.Equal(MagicNumber, magicNumber);
| `WithNetwork` | Assigns a network to the container e.g. `--network "bridge"`. |
| `WithNetworkAliases` | Assigns a network-scoped aliases to the container e.g. `--network-alias "alias"`. |
| `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. |
Expand Down
2 changes: 1 addition & 1 deletion src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private sealed class WaitUntil : IWaitUntil
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var (stdout, _) = await container.GetLogs()
var (stdout, _) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return _pattern.IsMatch(stdout);
Expand Down
2 changes: 2 additions & 0 deletions src/Testcontainers.EventStoreDb/EventStoreDbBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace Testcontainers.EventStoreDb;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class EventStoreDbBuilder : ContainerBuilder<EventStoreDbBuilder, EventStoreDbContainer, EventStoreDbConfiguration>
{
public const string EventStoreDbImage = "eventstore/eventstore:22.10.1-buster-slim";
Expand Down
4 changes: 2 additions & 2 deletions src/Testcontainers.MongoDb/MongoDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private sealed class WaitUntil : IWaitUntil
/// <param name="configuration">The container configuration.</param>
public WaitUntil(MongoDbConfiguration configuration)
{
const string js = "db.runCommand({ping:1})";
const string js = "db.runCommand({hello:1}).isWritablePrimary";
_mongoDbShellCommand = new MongoDbShellCommand(js, configuration.Username, configuration.Password);
}

Expand All @@ -124,7 +124,7 @@ public async Task<bool> UntilAsync(IContainer container)
var execResult = await container.ExecAsync(_mongoDbShellCommand)
.ConfigureAwait(false);

return 0L.Equals(execResult.ExitCode);
return 0L.Equals(execResult.ExitCode) && "true\n".Equals(execResult.Stdout, StringComparison.OrdinalIgnoreCase);
}
}
}
2 changes: 1 addition & 1 deletion src/Testcontainers.Oracle/OracleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private sealed class WaitUntil : IWaitUntil
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var (stdout, _) = await container.GetLogs()
var (stdout, _) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return stdout.Contains("DATABASE IS READY TO USE!");
Expand Down
2 changes: 1 addition & 1 deletion src/Testcontainers.RabbitMq/RabbitMqBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private sealed class WaitUntil : IWaitUntil
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var (stdout, _) = await container.GetLogs()
var (stdout, _) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return stdout.Contains("Server startup complete");
Expand Down
2 changes: 1 addition & 1 deletion src/Testcontainers.RavenDb/RavenDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private sealed class WaitUntil : IWaitUntil
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var (stdout, _) = await container.GetLogs()
var (stdout, _) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return stdout.Contains("Server started");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,15 @@ public interface IDockerContainer : IAsyncDisposable

ushort GetMappedPublicPort(string containerPort);

[Obsolete("Use IContainer.GetExitCodeAsync(CancellationToken) instead.")]
Task<long> GetExitCode(CancellationToken ct = default);

Task<(string Stdout, string Stderr)> GetLogs(DateTime since = default, DateTime until = default, CancellationToken ct = default);
Task<long> GetExitCodeAsync(CancellationToken ct = default);

[Obsolete("Use IContainer.GetLogsAsync(DateTime, DateTime, bool, CancellationToken) instead.")]
Task<(string Stdout, string Stderr)> GetLogs(DateTime since = default, DateTime until = default, bool timestampsEnabled = true, CancellationToken ct = default);

Task<(string Stdout, string Stderr)> GetLogsAsync(DateTime since = default, DateTime until = default, bool timestampsEnabled = true, CancellationToken ct = default);

Task StartAsync(CancellationToken ct = default);

Expand Down
3 changes: 1 addition & 2 deletions src/Testcontainers/Builders/IContainerBuilder`2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity> : I
/// <param name="outputConsumer">The output consumer.</param>
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
[PublicAPI]
[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.")]
TBuilderEntity WithOutputConsumer(IOutputConsumer outputConsumer);

/// <summary>
Expand All @@ -355,8 +356,6 @@ public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity> : I
/// <param name="startupCallback">The callback method to invoke.</param>
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
[PublicAPI]

// TODO: Set this methode to obsolete and replace it with TAP.
TBuilderEntity WithStartupCallback(Func<IContainer, CancellationToken, Task> startupCallback);

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/Testcontainers/Clients/DockerContainerOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,21 @@ public async Task<bool> ExistsWithNameAsync(string name, CancellationToken ct =
.ConfigureAwait(false) != null;
}

public async Task<long> GetExitCode(string id, CancellationToken ct = default)
public async Task<long> GetExitCodeAsync(string id, CancellationToken ct = default)
{
return (await this.Docker.Containers.WaitContainerAsync(id, ct)
.ConfigureAwait(false)).StatusCode;
}

public async Task<(string Stdout, string Stderr)> GetLogs(string id, TimeSpan since, TimeSpan until, CancellationToken ct = default)
public async Task<(string Stdout, string Stderr)> GetLogsAsync(string id, TimeSpan since, TimeSpan until, bool timestampsEnabled = true, CancellationToken ct = default)
{
var logsParameters = new ContainerLogsParameters
{
ShowStdout = true,
ShowStderr = true,
Since = since.TotalSeconds.ToString("0", CultureInfo.InvariantCulture),
Until = until.TotalSeconds.ToString("0", CultureInfo.InvariantCulture),
Timestamps = true,
Timestamps = timestampsEnabled,
};

using (var stdOutAndErrStream = await this.Docker.Containers.GetContainerLogsAsync(id, false, logsParameters, ct)
Expand Down
4 changes: 2 additions & 2 deletions src/Testcontainers/Clients/IDockerContainerOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ namespace DotNet.Testcontainers.Clients

internal interface IDockerContainerOperations : IHasListOperations<ContainerListResponse>
{
Task<long> GetExitCode(string id, CancellationToken ct = default);
Task<long> GetExitCodeAsync(string id, CancellationToken ct = default);

Task<(string Stdout, string Stderr)> GetLogs(string id, TimeSpan since, TimeSpan until, CancellationToken ct = default);
Task<(string Stdout, string Stderr)> GetLogsAsync(string id, TimeSpan since, TimeSpan until, bool timestampsEnabled = true, CancellationToken ct = default);

Task StartAsync(string id, CancellationToken ct = default);

Expand Down
14 changes: 4 additions & 10 deletions src/Testcontainers/Clients/ITestcontainersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,24 @@ internal interface ITestcontainersClient
/// </summary>
bool IsRunningInsideDocker { get; }

/// <summary>
/// Returns true if the Docker Windows engine is enabled, otherwise false.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Task that returns true if the Docker Windows engine is enabled, otherwise false.</returns>
Task<bool> GetIsWindowsEngineEnabled(CancellationToken ct = default);

/// <summary>
/// Gets the Testcontainers exit code.
/// </summary>
/// <param name="id">Docker container id.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Task that gets the Testcontainers exit code.</returns>
Task<long> GetContainerExitCode(string id, CancellationToken ct = default);
Task<long> GetContainerExitCodeAsync(string id, CancellationToken ct = default);

/// <summary>
/// Gets the Testcontainers logs.
/// </summary>
/// <param name="id">Docker container id.</param>
/// <param name="since">Only logs since this time.</param>
/// <param name="until">Only logs until this time.</param>
/// <param name="timestampsEnabled">Determines whether every log line contains a timestamp or not.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Task that gets the Testcontainers logs.</returns>
Task<(string Stdout, string Stderr)> GetContainerLogs(string id, DateTime since = default, DateTime until = default, CancellationToken ct = default);
Task<(string Stdout, string Stderr)> GetContainerLogsAsync(string id, DateTime since = default, DateTime until = default, bool timestampsEnabled = true, CancellationToken ct = default);

/// <summary>
/// Gets the Testcontainers inspect information.
Expand All @@ -55,7 +49,7 @@ internal interface ITestcontainersClient
/// <exception cref="DockerApiException">The daemon experienced an error.</exception>
/// <exception cref="DockerContainerNotFoundException">No such container was found.</exception>
/// <exception cref="HttpRequestException">The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.</exception>
Task<ContainerInspectResponse> InspectContainer(string id, CancellationToken ct = default);
Task<ContainerInspectResponse> InspectContainerAsync(string id, CancellationToken ct = default);

/// <summary>
/// Starts a container.
Expand Down
26 changes: 10 additions & 16 deletions src/Testcontainers/Clients/TestcontainersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,13 @@ public bool IsRunningInsideDocker
}

/// <inheritdoc />
public Task<bool> GetIsWindowsEngineEnabled(CancellationToken ct = default)
public Task<long> GetContainerExitCodeAsync(string id, CancellationToken ct = default)
{
return this.system.GetIsWindowsEngineEnabled(ct);
return this.containers.GetExitCodeAsync(id, ct);
}

/// <inheritdoc />
public Task<ContainerInspectResponse> InspectContainer(string id, CancellationToken ct = default)
{
return this.containers.InspectAsync(id, ct);
}

/// <inheritdoc />
public Task<long> GetContainerExitCode(string id, CancellationToken ct = default)
{
return this.containers.GetExitCode(id, ct);
}

/// <inheritdoc />
public Task<(string Stdout, string Stderr)> GetContainerLogs(string id, DateTime since = default, DateTime until = default, CancellationToken ct = default)
public Task<(string Stdout, string Stderr)> GetContainerLogsAsync(string id, DateTime since = default, DateTime until = default, bool timestampsEnabled = true, CancellationToken ct = default)
{
var unixEpoch = new DateTime(1970, 1, 1);

Expand All @@ -114,7 +102,13 @@ public Task<long> GetContainerExitCode(string id, CancellationToken ct = default
until = DateTime.MaxValue;
}

return this.containers.GetLogs(id, since.ToUniversalTime().Subtract(unixEpoch), until.ToUniversalTime().Subtract(unixEpoch), ct);
return this.containers.GetLogsAsync(id, since.ToUniversalTime().Subtract(unixEpoch), until.ToUniversalTime().Subtract(unixEpoch), timestampsEnabled, ct);
}

/// <inheritdoc />
public Task<ContainerInspectResponse> InspectContainerAsync(string id, CancellationToken ct = default)
{
return this.containers.InspectAsync(id, ct);
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace DotNet.Testcontainers.Configurations
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using JetBrains.Annotations;

/// <summary>
Expand Down Expand Up @@ -57,13 +58,30 @@ public interface IWaitForContainerOS
[PublicAPI]
IWaitForContainerOS UntilFileExists(string file);

/// <summary>
/// Waits until the message is logged.
/// </summary>
/// <param name="pattern">The regular expression that matches the log message.</param>
/// <returns>A configured instance of <see cref="IWaitForContainerOS" />.</returns>
[PublicAPI]
IWaitForContainerOS UntilMessageIsLogged(string pattern);

/// <summary>
/// Waits until the message is logged.
/// </summary>
/// <param name="pattern">The regular expression that matches the log message.</param>
/// <returns>A configured instance of <see cref="IWaitForContainerOS" />.</returns>
[PublicAPI]
IWaitForContainerOS UntilMessageIsLogged(Regex pattern);

/// <summary>
/// Waits until the message is logged in the steam.
/// </summary>
/// <param name="stream">The stream to be searched.</param>
/// <param name="message">The message to be checked.</param>
/// <returns>A configured instance of <see cref="IWaitForContainerOS" />.</returns>
[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);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,30 @@
namespace DotNet.Testcontainers.Configurations
{
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DotNet.Testcontainers.Containers;

internal class UntilMessageIsLogged : IWaitUntil
{
private readonly string message;
private readonly Regex pattern;

private readonly Stream stream;
public UntilMessageIsLogged(string pattern)
: this(new Regex(pattern, RegexOptions.None, TimeSpan.FromSeconds(5)))
{
}

public UntilMessageIsLogged(Stream stream, string message)
public UntilMessageIsLogged(Regex pattern)
{
this.stream = stream;
this.message = message;
this.pattern = pattern;
}

public async Task<bool> UntilAsync(IContainer container)
{
this.stream.Seek(0, SeekOrigin.Begin);

using (var streamReader = new StreamReader(this.stream, Encoding.UTF8, false, 4096, true))
{
var output = await streamReader.ReadToEndAsync()
.ConfigureAwait(false);
var (stdout, stderr) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return Regex.IsMatch(output, this.message, RegexOptions.None, TimeSpan.FromSeconds(1));
}
return this.pattern.IsMatch(stdout) || this.pattern.IsMatch(stderr);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace DotNet.Testcontainers.Configurations
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

/// <inheritdoc cref="IWaitForContainerOS" />
internal abstract class WaitForContainerOS : IWaitForContainerOS
Expand Down Expand Up @@ -39,10 +40,22 @@ public virtual IWaitForContainerOS UntilFileExists(string file)
return this.AddCustomWaitStrategy(new UntilFilesExists(file));
}

/// <inheritdoc />
public IWaitForContainerOS UntilMessageIsLogged(string pattern)
{
return this.AddCustomWaitStrategy(new UntilMessageIsLogged(pattern));
}

/// <inheritdoc />
public IWaitForContainerOS UntilMessageIsLogged(Regex pattern)
{
return this.AddCustomWaitStrategy(new UntilMessageIsLogged(pattern));
}

/// <inheritdoc />
public virtual IWaitForContainerOS UntilMessageIsLogged(Stream stream, string message)
{
return this.AddCustomWaitStrategy(new UntilMessageIsLogged(stream, message));
return this.AddCustomWaitStrategy(new UntilMessageIsLogged(message));
}

/// <inheritdoc />
Expand Down
Loading

0 comments on commit d3ae00f

Please sign in to comment.