Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Replace output consumer with IContainer.GetLogsAsync(DateTime, DateTime, bool, CancellationToken) #793

Merged
merged 6 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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