From 6baeb832187c357f5f23d18710a8da3d09eaa3dc Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Wed, 24 Apr 2024 19:18:18 +0200
Subject: [PATCH 1/2] feat: Add wait strategy options
Signed-off-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
---
docs/api/wait_strategies.md | 9 +
.../CouchbaseBuilder.cs | 4 +-
.../Containers/ContainerConfiguration.cs | 6 +-
.../Containers/IContainerConfiguration.cs | 2 +-
.../WaitStrategies/IWaitForContainerOS.cs | 47 +++--
.../WaitStrategies/IWaitStrategy.cs | 33 ++++
.../WaitStrategies/WaitForContainerOS.cs | 50 +++--
.../WaitStrategies/WaitForContainerUnix.cs | 20 +-
.../WaitStrategies/WaitForContainerWindows.cs | 20 +-
.../WaitStrategies/WaitStrategy.cs | 171 ++++++++++++++++--
.../Containers/DockerContainer.cs | 81 ++++++---
11 files changed, 355 insertions(+), 88 deletions(-)
create mode 100644 src/Testcontainers/Configurations/WaitStrategies/IWaitStrategy.cs
diff --git a/docs/api/wait_strategies.md b/docs/api/wait_strategies.md
index 6b38ba2e3..5c293e8d5 100644
--- a/docs/api/wait_strategies.md
+++ b/docs/api/wait_strategies.md
@@ -11,6 +11,15 @@ _ = Wait.ForUnixContainer()
.AddCustomWaitStrategy(new MyCustomWaitStrategy());
```
+
+
## Wait until an HTTP(S) endpoint is available
You can choose to wait for an HTTP(S) endpoint to return a particular HTTP response status code or to match a predicate. The default configuration tries to access the HTTP endpoint running inside the container. Chose `ForPort(ushort)` or `ForPath(string)` to adjust the endpoint or `UsingTls()` to switch to HTTPS. When using `UsingTls()` port 443 is used as a default. If your container exposes a different HTTPS port, make sure that the correct waiting port is configured accordingly.
diff --git a/src/Testcontainers.Couchbase/CouchbaseBuilder.cs b/src/Testcontainers.Couchbase/CouchbaseBuilder.cs
index b41a63b99..4d6e98105 100644
--- a/src/Testcontainers.Couchbase/CouchbaseBuilder.cs
+++ b/src/Testcontainers.Couchbase/CouchbaseBuilder.cs
@@ -183,7 +183,7 @@ private CouchbaseBuilder WithBucket(params CouchbaseBucket[] bucket)
/// Cancellation token.
private async Task ConfigureCouchbaseAsync(IContainer container, CancellationToken ct = default)
{
- await WaitStrategy.WaitUntilAsync(() => WaitUntilNodeIsReady.UntilAsync(container), TimeSpan.FromSeconds(2), TimeSpan.FromMinutes(5), ct)
+ await WaitStrategy.WaitUntilAsync(() => WaitUntilNodeIsReady.UntilAsync(container), TimeSpan.FromSeconds(2), TimeSpan.FromMinutes(5), -1, ct)
.ConfigureAwait(false);
using (var httpClient = new HttpClient(new RetryHandler()))
@@ -269,7 +269,7 @@ await EnsureSuccessStatusCodeAsync(response)
.Build()
.Last();
- await WaitStrategy.WaitUntilAsync(() => waitUntilBucketIsCreated.UntilAsync(container), TimeSpan.FromSeconds(2), TimeSpan.FromMinutes(5), ct)
+ await WaitStrategy.WaitUntilAsync(() => waitUntilBucketIsCreated.UntilAsync(container), TimeSpan.FromSeconds(2), TimeSpan.FromMinutes(5), -1, ct)
.ConfigureAwait(false);
}
diff --git a/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs b/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs
index 6fc4da4d7..b2a26a0d2 100644
--- a/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs
+++ b/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs
@@ -60,7 +60,7 @@ public ContainerConfiguration(
IEnumerable networkAliases = null,
IEnumerable extraHosts = null,
IOutputConsumer outputConsumer = null,
- IEnumerable waitStrategies = null,
+ IEnumerable waitStrategies = null,
Func startupCallback = null,
bool? autoRemove = null,
bool? privileged = null)
@@ -133,7 +133,7 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
NetworkAliases = BuildConfiguration.Combine(oldValue.NetworkAliases, newValue.NetworkAliases);
ExtraHosts = BuildConfiguration.Combine(oldValue.ExtraHosts, newValue.ExtraHosts);
OutputConsumer = BuildConfiguration.Combine(oldValue.OutputConsumer, newValue.OutputConsumer);
- WaitStrategies = BuildConfiguration.Combine>(oldValue.WaitStrategies, newValue.WaitStrategies);
+ WaitStrategies = BuildConfiguration.Combine>(oldValue.WaitStrategies, newValue.WaitStrategies);
StartupCallback = BuildConfiguration.Combine(oldValue.StartupCallback, newValue.StartupCallback);
AutoRemove = (oldValue.AutoRemove.HasValue && oldValue.AutoRemove.Value) || (newValue.AutoRemove.HasValue && newValue.AutoRemove.Value);
Privileged = (oldValue.Privileged.HasValue && oldValue.Privileged.Value) || (newValue.Privileged.HasValue && newValue.Privileged.Value);
@@ -212,7 +212,7 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
///
[JsonIgnore]
- public IEnumerable WaitStrategies { get; }
+ public IEnumerable WaitStrategies { get; }
///
[JsonIgnore]
diff --git a/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs b/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs
index 1093e8480..8966f48d3 100644
--- a/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs
+++ b/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs
@@ -119,7 +119,7 @@ public interface IContainerConfiguration : IResourceConfiguration
/// Gets the wait strategies.
///
- IEnumerable WaitStrategies { get; }
+ IEnumerable WaitStrategies { get; }
///
/// Gets the startup callback.
diff --git a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs
index f4e3d2df5..341a3976f 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs
@@ -14,98 +14,121 @@ public interface IWaitForContainerOS
///
/// Adds a custom wait strategy to the wait strategies collection.
///
- /// The wait strategy until the container is ready.
+ /// The wait strategy until the container is ready.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
/// Already contains as default wait strategy.
[PublicAPI]
- IWaitForContainerOS AddCustomWaitStrategy(IWaitUntil waitStrategy);
+ IWaitForContainerOS AddCustomWaitStrategy(IWaitUntil waitUntil, Action waitStrategyModifier = null);
///
/// Waits until the command is completed successfully.
///
/// The command to be executed.
/// A configured instance of .
+ ///
+ /// Does not invoke the operating system command shell.
+ /// Normal shell processing does not happen. Expects the exit code to be 0.
+ ///
+ [PublicAPI]
+ IWaitForContainerOS UntilCommandIsCompleted(params string[] command);
+
+ ///
+ /// Waits until the command is completed successfully.
+ ///
+ /// The command to be executed.
+ /// The wait strategy modifier to cancel the readiness check.
+ /// A configured instance of .
/// Invokes the operating system command shell. Expects the exit code to be 0.
[PublicAPI]
- IWaitForContainerOS UntilCommandIsCompleted(string command);
+ IWaitForContainerOS UntilCommandIsCompleted(string command, Action waitStrategyModifier = null);
///
/// Waits until the command is completed successfully.
///
/// The command to be executed.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
///
/// Does not invoke the operating system command shell.
/// Normal shell processing does not happen. Expects the exit code to be 0.
///
[PublicAPI]
- IWaitForContainerOS UntilCommandIsCompleted(params string[] command);
+ IWaitForContainerOS UntilCommandIsCompleted(IEnumerable command, Action waitStrategyModifier = null);
///
/// Waits until the port is available.
///
/// The port to be checked.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
[PublicAPI]
- IWaitForContainerOS UntilPortIsAvailable(int port);
+ IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null);
///
/// Waits until the file exists.
///
/// The file path to be checked.
/// The file system to be checked.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
[PublicAPI]
- IWaitForContainerOS UntilFileExists(string filePath, FileSystem fileSystem = FileSystem.Host);
+ IWaitForContainerOS UntilFileExists(string filePath, FileSystem fileSystem = FileSystem.Host, Action waitStrategyModifier = null);
///
/// Waits until the message is logged.
///
/// The regular expression that matches the log message.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
[PublicAPI]
- IWaitForContainerOS UntilMessageIsLogged(string pattern);
+ IWaitForContainerOS UntilMessageIsLogged(string pattern, Action waitStrategyModifier = null);
///
/// Waits until the message is logged.
///
/// The regular expression that matches the log message.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
[PublicAPI]
- IWaitForContainerOS UntilMessageIsLogged(Regex pattern);
+ IWaitForContainerOS UntilMessageIsLogged(Regex pattern, Action waitStrategyModifier = null);
///
/// Waits until the operation is completed successfully.
///
/// The operation to be executed.
/// The number of attempts before an exception is thrown.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
/// Thrown when number of failed operations exceeded .
[PublicAPI]
- IWaitForContainerOS UntilOperationIsSucceeded(Func operation, int maxCallCount);
+ [Obsolete("Use one of the other wait strategies in combination with the `Action` argument, and set the number of retries.")]
+ IWaitForContainerOS UntilOperationIsSucceeded(Func operation, int maxCallCount, Action waitStrategyModifier = null);
///
/// Waits until the http request is completed successfully.
///
/// The http request to be executed.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
[PublicAPI]
- IWaitForContainerOS UntilHttpRequestIsSucceeded(Func request);
+ IWaitForContainerOS UntilHttpRequestIsSucceeded(Func request, Action waitStrategyModifier = null);
///
/// Waits until the container is healthy.
///
/// The number of attempts before an exception is thrown.
+ /// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
/// Thrown when number of failed operations exceeded .
[PublicAPI]
- IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3);
+ IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3, Action waitStrategyModifier = null);
///
/// Returns a collection with all configured wait strategies.
///
/// Returns a list with all configured wait strategies.
[PublicAPI]
- IEnumerable Build();
+ IEnumerable Build();
}
}
diff --git a/src/Testcontainers/Configurations/WaitStrategies/IWaitStrategy.cs b/src/Testcontainers/Configurations/WaitStrategies/IWaitStrategy.cs
new file mode 100644
index 000000000..d245412a9
--- /dev/null
+++ b/src/Testcontainers/Configurations/WaitStrategies/IWaitStrategy.cs
@@ -0,0 +1,33 @@
+namespace DotNet.Testcontainers.Configurations
+{
+ using System;
+ using JetBrains.Annotations;
+
+ ///
+ /// Represents a wait strategy configuration.
+ ///
+ [PublicAPI]
+ public interface IWaitStrategy
+ {
+ ///
+ /// Sets the number of retries for the wait strategy.
+ ///
+ /// The number of retries.
+ /// The updated instance of the wait strategy.
+ IWaitStrategy WithRetries(ushort retries);
+
+ ///
+ /// Sets the interval between retries for the wait strategy.
+ ///
+ /// The interval between retries.
+ /// The updated instance of the wait strategy.
+ IWaitStrategy WithInterval(TimeSpan interval);
+
+ ///
+ /// Sets the timeout for the wait strategy.
+ ///
+ /// The timeout duration.
+ /// The updated instance of the wait strategy.
+ IWaitStrategy WithTimeout(TimeSpan timeout);
+ }
+}
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs
index 669c5e062..94b48e183 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs
@@ -7,77 +7,87 @@ namespace DotNet.Testcontainers.Configurations
///
internal abstract class WaitForContainerOS : IWaitForContainerOS
{
- private readonly ICollection _waitStrategies = new List();
+ private readonly ICollection _waitStrategies = new List();
///
/// Initializes a new instance of the class.
///
protected WaitForContainerOS()
{
- _waitStrategies.Add(new UntilContainerIsRunning());
+ _waitStrategies.Add(new WaitStrategy(new UntilContainerIsRunning()));
}
///
- public abstract IWaitForContainerOS UntilCommandIsCompleted(string command);
+ public abstract IWaitForContainerOS UntilCommandIsCompleted(params string[] command);
///
- public abstract IWaitForContainerOS UntilCommandIsCompleted(params string[] command);
+ public abstract IWaitForContainerOS UntilCommandIsCompleted(string command, Action waitStrategyModifier = null);
+
+ ///
+ public abstract IWaitForContainerOS UntilCommandIsCompleted(IEnumerable command, Action waitStrategyModifier = null);
///
- public abstract IWaitForContainerOS UntilPortIsAvailable(int port);
+ public abstract IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null);
///
- public virtual IWaitForContainerOS AddCustomWaitStrategy(IWaitUntil waitStrategy)
+ public virtual IWaitForContainerOS AddCustomWaitStrategy(IWaitUntil waitUntil, Action waitStrategyModifier = null)
{
+ var waitStrategy = new WaitStrategy(waitUntil);
+
+ if (waitStrategyModifier != null)
+ {
+ waitStrategyModifier(waitStrategy);
+ }
+
_waitStrategies.Add(waitStrategy);
return this;
}
///
- public virtual IWaitForContainerOS UntilFileExists(string filePath, FileSystem fileSystem = FileSystem.Host)
+ public virtual IWaitForContainerOS UntilFileExists(string filePath, FileSystem fileSystem = FileSystem.Host, Action waitStrategyModifier = null)
{
switch (fileSystem)
{
case FileSystem.Container:
- return AddCustomWaitStrategy(new UntilFileExistsInContainer(filePath));
+ return AddCustomWaitStrategy(new UntilFileExistsInContainer(filePath), waitStrategyModifier);
case FileSystem.Host:
default:
- return AddCustomWaitStrategy(new UntilFileExistsOnHost(filePath));
+ return AddCustomWaitStrategy(new UntilFileExistsOnHost(filePath), waitStrategyModifier);
}
}
///
- public IWaitForContainerOS UntilMessageIsLogged(string pattern)
+ public IWaitForContainerOS UntilMessageIsLogged(string pattern, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilMessageIsLogged(pattern));
+ return AddCustomWaitStrategy(new UntilMessageIsLogged(pattern), waitStrategyModifier);
}
///
- public IWaitForContainerOS UntilMessageIsLogged(Regex pattern)
+ public IWaitForContainerOS UntilMessageIsLogged(Regex pattern, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilMessageIsLogged(pattern));
+ return AddCustomWaitStrategy(new UntilMessageIsLogged(pattern), waitStrategyModifier);
}
///
- public virtual IWaitForContainerOS UntilOperationIsSucceeded(Func operation, int maxCallCount)
+ public virtual IWaitForContainerOS UntilOperationIsSucceeded(Func operation, int maxCallCount, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilOperationIsSucceeded(operation, maxCallCount));
+ return AddCustomWaitStrategy(new UntilOperationIsSucceeded(operation, maxCallCount), waitStrategyModifier);
}
///
- public virtual IWaitForContainerOS UntilHttpRequestIsSucceeded(Func request)
+ public virtual IWaitForContainerOS UntilHttpRequestIsSucceeded(Func request, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(request.Invoke(new HttpWaitStrategy()));
+ return AddCustomWaitStrategy(request.Invoke(new HttpWaitStrategy()), waitStrategyModifier);
}
///
- public virtual IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3)
+ public virtual IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilContainerIsHealthy(failingStreak));
+ return AddCustomWaitStrategy(new UntilContainerIsHealthy(failingStreak), waitStrategyModifier);
}
///
- public IEnumerable Build()
+ public IEnumerable Build()
{
return _waitStrategies;
}
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs
index fb1272984..e4c9f4933 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs
@@ -1,24 +1,34 @@
namespace DotNet.Testcontainers.Configurations
{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
///
internal sealed class WaitForContainerUnix : WaitForContainerOS
{
///
- public override IWaitForContainerOS UntilCommandIsCompleted(string command)
+ public override IWaitForContainerOS UntilCommandIsCompleted(params string[] command)
{
return AddCustomWaitStrategy(new UntilUnixCommandIsCompleted(command));
}
///
- public override IWaitForContainerOS UntilCommandIsCompleted(params string[] command)
+ public override IWaitForContainerOS UntilCommandIsCompleted(string command, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilUnixCommandIsCompleted(command));
+ return AddCustomWaitStrategy(new UntilUnixCommandIsCompleted(command), waitStrategyModifier);
+ }
+
+ ///
+ public override IWaitForContainerOS UntilCommandIsCompleted(IEnumerable command, Action waitStrategyModifier = null)
+ {
+ return AddCustomWaitStrategy(new UntilUnixCommandIsCompleted(command.ToArray()), waitStrategyModifier);
}
///
- public override IWaitForContainerOS UntilPortIsAvailable(int port)
+ public override IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilUnixPortIsAvailable(port));
+ return AddCustomWaitStrategy(new UntilUnixPortIsAvailable(port), waitStrategyModifier);
}
}
}
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs
index d4e6f0719..7e2205904 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs
@@ -1,24 +1,34 @@
namespace DotNet.Testcontainers.Configurations
{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
///
internal sealed class WaitForContainerWindows : WaitForContainerOS
{
///
- public override IWaitForContainerOS UntilCommandIsCompleted(string command)
+ public override IWaitForContainerOS UntilCommandIsCompleted(params string[] command)
{
return AddCustomWaitStrategy(new UntilWindowsCommandIsCompleted(command));
}
///
- public override IWaitForContainerOS UntilCommandIsCompleted(params string[] command)
+ public override IWaitForContainerOS UntilCommandIsCompleted(string command, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilWindowsCommandIsCompleted(command));
+ return AddCustomWaitStrategy(new UntilWindowsCommandIsCompleted(command), waitStrategyModifier);
+ }
+
+ ///
+ public override IWaitForContainerOS UntilCommandIsCompleted(IEnumerable command, Action waitStrategyModifier = null)
+ {
+ return AddCustomWaitStrategy(new UntilWindowsCommandIsCompleted(command.ToArray()), waitStrategyModifier);
}
///
- public override IWaitForContainerOS UntilPortIsAvailable(int port)
+ public override IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null)
{
- return AddCustomWaitStrategy(new UntilWindowsPortIsAvailable(port));
+ return AddCustomWaitStrategy(new UntilWindowsPortIsAvailable(port), waitStrategyModifier);
}
}
}
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
index 137576867..75466647d 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
@@ -3,22 +3,124 @@ namespace DotNet.Testcontainers.Configurations
using System;
using System.Threading;
using System.Threading.Tasks;
+ using DotNet.Testcontainers.Containers;
using JetBrains.Annotations;
- public static class WaitStrategy
+ ///
+ [PublicAPI]
+ public class WaitStrategy : IWaitStrategy
{
+ private const string MaximumRetryExceededException = "The maximum number of retries has been exceeded.";
+
+ private IWaitWhile _waitWhile;
+
+ private IWaitUntil _waitUntil;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WaitStrategy()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The wait while condition to be used in the strategy.
+ public WaitStrategy(IWaitWhile waitWhile)
+ {
+ _ = WithStrategy(waitWhile);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The wait until condition to be used in the strategy.
+ public WaitStrategy(IWaitUntil waitUntil)
+ {
+ _ = WithStrategy(waitUntil);
+ }
+
+ ///
+ /// Gets the number of retries.
+ ///
+ public int Retries { get; private set; }
+ = -1;
+
+ ///
+ /// Gets the interval between retries.
+ ///
+ public TimeSpan Interval { get; private set; }
+ = TimeSpan.FromSeconds(1);
+
+ ///
+ /// Gets the timeout.
+ ///
+ public TimeSpan Timeout { get; private set; }
+ = TimeSpan.FromHours(1);
+
+ ///
+ public IWaitStrategy WithRetries(ushort retries)
+ {
+ Retries = retries;
+ return this;
+ }
+
+ ///
+ public IWaitStrategy WithInterval(TimeSpan interval)
+ {
+ Interval = interval;
+ return this;
+ }
+
+ ///
+ public IWaitStrategy WithTimeout(TimeSpan timeout)
+ {
+ Timeout = timeout;
+ return this;
+ }
+
+ ///
+ /// Executes the wait strategy while the container satisfies the condition.
+ ///
+ /// The container to check the condition for.
+ /// Cancellation token.
+ /// A task representing the asynchronous operation, returning false if the container satisfies the condition; otherwise, true.
+ public virtual Task WhileAsync(IContainer container, CancellationToken ct = default)
+ {
+ return _waitWhile.WhileAsync(container);
+ }
+
+ ///
+ /// Executes the wait strategy until the container satisfies the condition.
+ ///
+ /// The container to check the condition for.
+ /// Cancellation token.
+ /// A task representing the asynchronous operation, returning true if the container satisfies the condition; otherwise, false.
+ public virtual Task UntilAsync(IContainer container, CancellationToken ct = default)
+ {
+ return _waitUntil.UntilAsync(container);
+ }
+
///
- /// Blocks while condition is true or timeout occurs.
+ /// Waits asynchronously until the specified condition returns false or until a timeout occurs.
///
- /// Function to block execution.
- /// The frequency in milliseconds to check the condition.
- /// Timeout in milliseconds.
- /// Propagates notification that operations should be canceled.
- /// Thrown as soon as the timeout expires.
+ ///
+ /// Zero or a negative value for will run the readiness check infinitely until it becomes false.
+ ///
+ /// A function that represents the asynchronous condition to wait for.
+ /// The time interval between consecutive evaluations of the condition.
+ /// The maximum duration to wait for the condition to become false.
+ /// The number of retries to run for the condition to become false.
+ /// The optional cancellation token to cancel the waiting operation.
+ /// Thrown when the timeout expires.
+ /// Thrown when the number of retries is exceeded.
/// A task that represents the asynchronous block operation.
[PublicAPI]
- public static async Task WaitWhileAsync(Func> wait, TimeSpan frequency, TimeSpan timeout, CancellationToken ct = default)
+ public static async Task WaitWhileAsync(Func> wait, TimeSpan interval, TimeSpan timeout, int retries = -1, CancellationToken ct = default)
{
+ ushort actualRetries = 0;
+
async Task WhileAsync()
{
while (!ct.IsCancellationRequested)
@@ -31,7 +133,10 @@ async Task WhileAsync()
break;
}
- await Task.Delay(frequency, ct)
+ _ = Guard.Argument(retries, nameof(retries))
+ .ThrowIf(_ => retries > 0 && ++actualRetries > retries, _ => throw new ArgumentException(MaximumRetryExceededException));
+
+ await Task.Delay(interval, ct)
.ConfigureAwait(false);
}
}
@@ -54,17 +159,24 @@ await waitTask
}
///
- /// Blocks until condition is true or timeout occurs.
+ /// Waits asynchronously until the specified condition returns true or until a timeout occurs.
///
- /// Function to block execution.
- /// The frequency in milliseconds to check the condition.
- /// The timeout in milliseconds.
- /// Propagates notification that operations should be canceled.
- /// Thrown as soon as the timeout expires.
+ ///
+ /// Zero or a negative value for will run the readiness check infinitely until it becomes true.
+ ///
+ /// A function that represents the asynchronous condition to wait for.
+ /// The time interval between consecutive evaluations of the condition.
+ /// The maximum duration to wait for the condition to become true.
+ /// The number of retries to run for the condition to become true.
+ /// The optional cancellation token to cancel the waiting operation.
+ /// Thrown when the timeout expires.
+ /// Thrown when the number of retries is exceeded.
/// A task that represents the asynchronous block operation.
[PublicAPI]
- public static async Task WaitUntilAsync(Func> wait, TimeSpan frequency, TimeSpan timeout, CancellationToken ct = default)
+ public static async Task WaitUntilAsync(Func> wait, TimeSpan interval, TimeSpan timeout, int retries = -1, CancellationToken ct = default)
{
+ ushort actualRetries = 0;
+
async Task UntilAsync()
{
while (!ct.IsCancellationRequested)
@@ -77,7 +189,10 @@ async Task UntilAsync()
break;
}
- await Task.Delay(frequency, ct)
+ _ = Guard.Argument(retries, nameof(retries))
+ .ThrowIf(_ => retries > 0 && ++actualRetries > retries, _ => throw new ArgumentException(MaximumRetryExceededException));
+
+ await Task.Delay(interval, ct)
.ConfigureAwait(false);
}
}
@@ -98,5 +213,27 @@ await Task.Delay(frequency, ct)
await waitTask
.ConfigureAwait(false);
}
+
+ ///
+ /// Sets the wait while condition.
+ ///
+ /// The wait while condition to be used in the strategy.
+ /// The updated instance of the wait strategy.
+ private WaitStrategy WithStrategy(IWaitWhile waitWhile)
+ {
+ _waitWhile = waitWhile;
+ return this;
+ }
+
+ ///
+ /// Sets the wait until condition.
+ ///
+ /// The wait until condition to be used in the strategy.
+ /// The updated instance of the wait strategy.
+ private WaitStrategy WithStrategy(IWaitUntil waitUntil)
+ {
+ _waitUntil = waitUntil;
+ return this;
+ }
}
}
diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs
index 1343fce58..d18658eec 100644
--- a/src/Testcontainers/Containers/DockerContainer.cs
+++ b/src/Testcontainers/Containers/DockerContainer.cs
@@ -455,23 +455,7 @@ protected virtual async Task UnsafeStartAsync(CancellationToken ct = default)
{
ThrowIfLockNotAcquired();
- async Task CheckPortBindingsAsync()
- {
- _container = await _client.Container.ByIdAsync(_container.ID, ct)
- .ConfigureAwait(false);
-
- var boundPorts = _container.NetworkSettings.Ports.Values.Where(portBindings => portBindings != null).SelectMany(portBinding => portBinding).Count(portBinding => !string.IsNullOrEmpty(portBinding.HostPort));
- return _configuration.PortBindings == null || /* IPv4 or IPv6 */ _configuration.PortBindings.Count == boundPorts || /* IPv4 and IPv6 */ 2 * _configuration.PortBindings.Count == boundPorts;
- }
-
- async Task CheckWaitStrategyAsync(IWaitUntil wait)
- {
- _container = await _client.Container.ByIdAsync(_container.ID, ct)
- .ConfigureAwait(false);
-
- return await wait.UntilAsync(this)
- .ConfigureAwait(false);
- }
+ WaitStrategy portBindingsMapped = new WaitUntilPortBindingsMapped(this);
await _client.AttachAsync(_container.ID, _configuration.OutputConsumer, ct)
.ConfigureAwait(false);
@@ -479,7 +463,7 @@ await _client.AttachAsync(_container.ID, _configuration.OutputConsumer, ct)
await _client.StartAsync(_container.ID, ct)
.ConfigureAwait(false);
- await WaitStrategy.WaitUntilAsync(CheckPortBindingsAsync, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(15), ct)
+ _ = await CheckReadinessAsync(new [] { portBindingsMapped }, ct)
.ConfigureAwait(false);
Starting?.Invoke(this, EventArgs.Empty);
@@ -489,11 +473,8 @@ await _configuration.StartupCallback(this, ct)
Logger.StartReadinessCheck(_container.ID);
- foreach (var waitStrategy in _configuration.WaitStrategies)
- {
- await WaitStrategy.WaitUntilAsync(() => CheckWaitStrategyAsync(waitStrategy), TimeSpan.FromSeconds(1), Timeout.InfiniteTimeSpan, ct)
- .ConfigureAwait(false);
- }
+ _ = await CheckReadinessAsync(_configuration.WaitStrategies, ct)
+ .ConfigureAwait(false);
Logger.CompleteReadinessCheck(_container.ID);
@@ -535,5 +516,59 @@ protected override bool Exists()
{
return _container != null && ContainerHasBeenCreatedStates.HasFlag(State);
}
+
+ ///
+ /// Updates the internal container field and checks whether the wait strategy indicates readiness or not.
+ ///
+ /// The wait strategy to execute.
+ /// Cancellation token.
+ /// A task representing the asynchronous operation, returning true if the wait strategy indicates readiness; otherwise, false.
+ private async Task CheckReadinessAsync(WaitStrategy waitStrategy, CancellationToken ct = default)
+ {
+ _container = await _client.Container.ByIdAsync(_container.ID, ct)
+ .ConfigureAwait(false);
+
+ return await waitStrategy.UntilAsync(this, ct)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Updates the internal container field and checks whether the wait strategy indicates readiness or not.
+ ///
+ ///
+ /// To cancel the readiness check, each wait strategy can be configured using the
+ /// members, utilizing the wait strategy modifier.
+ ///
+ /// The wait strategies to execute.
+ /// Cancellation token.
+ /// A task representing the asynchronous operation, returning true if the wait strategies indicate readiness; otherwise, false.
+ private async Task CheckReadinessAsync(IEnumerable waitStrategies, CancellationToken ct = default)
+ {
+ foreach (var waitStrategy in waitStrategies)
+ {
+ await WaitStrategy.WaitUntilAsync(() => CheckReadinessAsync(waitStrategy, ct), waitStrategy.Interval, waitStrategy.Timeout, waitStrategy.Retries, ct)
+ .ConfigureAwait(false);
+ }
+
+ return true;
+ }
+
+ private sealed class WaitUntilPortBindingsMapped : WaitStrategy
+ {
+ private readonly DockerContainer _parent;
+
+ public WaitUntilPortBindingsMapped(DockerContainer parent)
+ {
+ _parent = parent;
+ _ = WithInterval(TimeSpan.FromSeconds(1));
+ _ = WithTimeout(TimeSpan.FromSeconds(15));
+ }
+
+ public override Task UntilAsync(IContainer _, CancellationToken ct = default)
+ {
+ var boundPorts = _parent._container.NetworkSettings.Ports.Values.Where(portBindings => portBindings != null).SelectMany(portBinding => portBinding).Count(portBinding => !string.IsNullOrEmpty(portBinding.HostPort));
+ return Task.FromResult(_parent._configuration.PortBindings == null || /* IPv4 or IPv6 */ _parent._configuration.PortBindings.Count == boundPorts || /* IPv4 and IPv6 */ 2 * _parent._configuration.PortBindings.Count == boundPorts);
+ }
+ }
}
}
From acac3070fbc3a452e574d767235ec1d07535ea0d Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Fri, 3 May 2024 15:57:59 +0200
Subject: [PATCH 2/2] chore: Change signature port arg ushort to int
---
.../Configurations/WaitStrategies/IWaitForContainerOS.cs | 2 +-
.../Configurations/WaitStrategies/WaitForContainerOS.cs | 2 +-
.../Configurations/WaitStrategies/WaitForContainerUnix.cs | 2 +-
.../Configurations/WaitStrategies/WaitForContainerWindows.cs | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs
index 341a3976f..615ccbb17 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs
@@ -63,7 +63,7 @@ public interface IWaitForContainerOS
/// The wait strategy modifier to cancel the readiness check.
/// A configured instance of .
[PublicAPI]
- IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null);
+ IWaitForContainerOS UntilPortIsAvailable(int port, Action waitStrategyModifier = null);
///
/// Waits until the file exists.
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs
index 94b48e183..4a6646c47 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs
@@ -27,7 +27,7 @@ protected WaitForContainerOS()
public abstract IWaitForContainerOS UntilCommandIsCompleted(IEnumerable command, Action waitStrategyModifier = null);
///
- public abstract IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null);
+ public abstract IWaitForContainerOS UntilPortIsAvailable(int port, Action waitStrategyModifier = null);
///
public virtual IWaitForContainerOS AddCustomWaitStrategy(IWaitUntil waitUntil, Action waitStrategyModifier = null)
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs
index e4c9f4933..e1e132a51 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerUnix.cs
@@ -26,7 +26,7 @@ public override IWaitForContainerOS UntilCommandIsCompleted(IEnumerable
}
///
- public override IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null)
+ public override IWaitForContainerOS UntilPortIsAvailable(int port, Action waitStrategyModifier = null)
{
return AddCustomWaitStrategy(new UntilUnixPortIsAvailable(port), waitStrategyModifier);
}
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs
index 7e2205904..756063f11 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerWindows.cs
@@ -26,7 +26,7 @@ public override IWaitForContainerOS UntilCommandIsCompleted(IEnumerable
}
///
- public override IWaitForContainerOS UntilPortIsAvailable(ushort port, Action waitStrategyModifier = null)
+ public override IWaitForContainerOS UntilPortIsAvailable(int port, Action waitStrategyModifier = null)
{
return AddCustomWaitStrategy(new UntilWindowsPortIsAvailable(port), waitStrategyModifier);
}