From 3fa798629e46b7ccf99dbaa33f177ceef42a8f84 Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Fri, 3 May 2024 21:23:40 +0200
Subject: [PATCH 1/4] feat: Extend ICustomConfiguration interface (add wait
 stragety options)

---
 ...erDesktopEndpointAuthenticationProvider.cs | 18 +++++
 ...ontainersEndpointAuthenticationProvider.cs | 18 +++++
 .../Configurations/CustomConfiguration.cs     | 20 ++++++
 .../EnvironmentConfiguration.cs               | 27 +++++++
 .../Configurations/ICustomConfiguration.cs    | 18 +++++
 .../PropertiesFileConfiguration.cs            | 21 ++++++
 .../WaitStrategies/WaitStrategy.cs            |  4 +-
 .../Configurations/CustomConfigurationTest.cs | 70 +++++++++++++++++++
 8 files changed, 194 insertions(+), 2 deletions(-)

diff --git a/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs
index 362558579..b2e280597 100644
--- a/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs
+++ b/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs
@@ -101,5 +101,23 @@ public string GetHubImageNamePrefix()
     {
       return null;
     }
+
+    /// <inheritdoc />
+    public ushort GetWaitStrategyRetries()
+    {
+      return ushort.MinValue;
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyInterval()
+    {
+      return TimeSpan.Zero;
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyTimeout()
+    {
+      return TimeSpan.Zero;
+    }
   }
 }
diff --git a/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs
index e3078055c..d9efde95d 100644
--- a/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs
+++ b/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs
@@ -129,6 +129,24 @@ public string GetHubImageNamePrefix()
       return _customConfiguration.GetHubImageNamePrefix();
     }
 
+    /// <inheritdoc />
+    public ushort GetWaitStrategyRetries()
+    {
+      return ushort.MinValue;
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyInterval()
+    {
+      return TimeSpan.Zero;
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyTimeout()
+    {
+      return TimeSpan.Zero;
+    }
+
     private sealed class TestcontainersConfiguration : PropertiesFileConfiguration
     {
       public TestcontainersConfiguration()
diff --git a/src/Testcontainers/Configurations/CustomConfiguration.cs b/src/Testcontainers/Configurations/CustomConfiguration.cs
index 155e2dbe2..587db8d7e 100644
--- a/src/Testcontainers/Configurations/CustomConfiguration.cs
+++ b/src/Testcontainers/Configurations/CustomConfiguration.cs
@@ -102,6 +102,21 @@ protected virtual string GetHubImageNamePrefix(string propertyName)
       return GetPropertyValue<string>(propertyName);
     }
 
+    protected virtual ushort GetWaitStrategyRetries(string propertyName)
+    {
+      return GetPropertyValue<ushort>(propertyName);
+    }
+
+    protected virtual TimeSpan GetWaitStrategyInterval(string propertyName)
+    {
+      return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, out var result) && result > TimeSpan.Zero ? result : TimeSpan.FromSeconds(1);
+    }
+
+    protected virtual TimeSpan GetWaitStrategyTimeout(string propertyName)
+    {
+      return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, out var result) && result > TimeSpan.Zero ? result : TimeSpan.FromHours(1);
+    }
+
     private T GetPropertyValue<T>(string propertyName)
     {
       switch (Type.GetTypeCode(typeof(T)))
@@ -111,6 +126,11 @@ private T GetPropertyValue<T>(string propertyName)
           return (T)(object)(_properties.TryGetValue(propertyName, out var propertyValue) && ("1".Equals(propertyValue, StringComparison.Ordinal) || (bool.TryParse(propertyValue, out var result) && result)));
         }
 
+        case TypeCode.UInt16:
+        {
+          return (T)(object)(_properties.TryGetValue(propertyName, out var propertyValue) && ushort.TryParse(propertyValue, out var result) ? result : ushort.MinValue);
+        }
+
         case TypeCode.String:
         {
           _ = _properties.TryGetValue(propertyName, out var propertyValue);
diff --git a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs
index 471cfc9c8..462894a6f 100644
--- a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs
+++ b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs
@@ -34,6 +34,12 @@ internal class EnvironmentConfiguration : CustomConfiguration, ICustomConfigurat
 
     private const string HubImageNamePrefix = "TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX";
 
+    private const string WaitStrategyRetries = "TESTCONTAINERS_WAIT_STRATEGY_RETRIES";
+
+    private const string WaitStrategyInterval = "TESTCONTAINERS_WAIT_STRATEGY_INTERVAL";
+
+    private const string WaitStrategyTimeout = "TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT";
+
     static EnvironmentConfiguration()
     {
     }
@@ -56,6 +62,9 @@ public EnvironmentConfiguration()
           RyukContainerPrivileged,
           RyukContainerImage,
           HubImageNamePrefix,
+          WaitStrategyRetries,
+          WaitStrategyInterval,
+          WaitStrategyTimeout,
         }
         .ToDictionary(key => key, Environment.GetEnvironmentVariable))
     {
@@ -138,5 +147,23 @@ public string GetHubImageNamePrefix()
     {
       return GetHubImageNamePrefix(HubImageNamePrefix);
     }
+
+    /// <inheritdoc />
+    public ushort GetWaitStrategyRetries()
+    {
+      return GetWaitStrategyRetries(WaitStrategyRetries);
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyInterval()
+    {
+      return GetWaitStrategyInterval(WaitStrategyInterval);
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyTimeout()
+    {
+      return GetWaitStrategyTimeout(WaitStrategyTimeout);
+    }
   }
 }
diff --git a/src/Testcontainers/Configurations/ICustomConfiguration.cs b/src/Testcontainers/Configurations/ICustomConfiguration.cs
index 4a9c31ecd..af65a0b69 100644
--- a/src/Testcontainers/Configurations/ICustomConfiguration.cs
+++ b/src/Testcontainers/Configurations/ICustomConfiguration.cs
@@ -101,5 +101,23 @@ internal interface ICustomConfiguration
     /// <remarks>https://dotnet.testcontainers.org/custom_configuration/.</remarks>
     [CanBeNull]
     string GetHubImageNamePrefix();
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <returns></returns>
+    ushort GetWaitStrategyRetries();
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <returns></returns>
+    TimeSpan GetWaitStrategyInterval();
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <returns></returns>
+    TimeSpan GetWaitStrategyTimeout();
   }
 }
diff --git a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs
index 6d0367c1f..71418a5eb 100644
--- a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs
+++ b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs
@@ -139,5 +139,26 @@ public string GetHubImageNamePrefix()
       const string propertyName = "hub.image.name.prefix";
       return GetHubImageNamePrefix(propertyName);
     }
+
+    /// <inheritdoc />
+    public ushort GetWaitStrategyRetries()
+    {
+      const string propertyName = "wait.strategy.retries";
+      return GetWaitStrategyRetries(propertyName);
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyInterval()
+    {
+      const string propertyName = "wait.strategy.interval";
+      return GetWaitStrategyInterval(propertyName);
+    }
+
+    /// <inheritdoc />
+    public TimeSpan GetWaitStrategyTimeout()
+    {
+      const string propertyName = "wait.strategy.timeout";
+      return GetWaitStrategyTimeout(propertyName);
+    }
   }
 }
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
index 75466647d..fe7de8a0a 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
@@ -44,8 +44,8 @@ public WaitStrategy(IWaitUntil waitUntil)
     /// <summary>
     /// Gets the number of retries.
     /// </summary>
-    public int Retries { get; private set; }
-      = -1;
+    public ushort Retries { get; private set; }
+      = ushort.MinValue;
 
     /// <summary>
     /// Gets the interval between retries.
diff --git a/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs
index c30508a30..873e1079d 100644
--- a/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs
+++ b/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs
@@ -27,6 +27,9 @@ static EnvironmentConfigurationTest()
         EnvironmentVariables.Add("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED");
         EnvironmentVariables.Add("TESTCONTAINERS_RYUK_CONTAINER_IMAGE");
         EnvironmentVariables.Add("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX");
+        EnvironmentVariables.Add("TESTCONTAINERS_WAIT_STRATEGY_RETRIES");
+        EnvironmentVariables.Add("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL");
+        EnvironmentVariables.Add("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT");
       }
 
       [Theory]
@@ -177,6 +180,41 @@ public void GetHubImageNamePrefixCustomConfiguration(string propertyName, string
         Assert.Equal(expected, customConfiguration.GetHubImageNamePrefix());
       }
 
+      [Theory]
+      [InlineData("", "", 0)]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_RETRIES", "", 0)]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_RETRIES", "1", 1)]
+      public void GetWaitStrategyRetriesCustomConfiguration(string propertyName, string propertyValue, ushort expected)
+      {
+        SetEnvironmentVariable(propertyName, propertyValue);
+        ICustomConfiguration customConfiguration = new EnvironmentConfiguration();
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyRetries());
+      }
+
+      [Theory]
+      [InlineData("", "", "00:00:01")]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "", "00:00:01")]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "-00:00:00.001", "00:00:01")]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "00:00:01", "00:00:01")]
+      public void GetWaitStrategyIntervalCustomConfiguration(string propertyName, string propertyValue, string expected)
+      {
+        SetEnvironmentVariable(propertyName, propertyValue);
+        ICustomConfiguration customConfiguration = new EnvironmentConfiguration();
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyInterval().ToString());
+      }
+
+      [Theory]
+      [InlineData("", "", "01:00:00")]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "", "01:00:00")]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "-00:00:00.001", "01:00:00")]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "00:00:01", "00:00:01")]
+      public void GetWaitStrategyTimeoutCustomConfiguration(string propertyName, string propertyValue, string expected)
+      {
+        SetEnvironmentVariable(propertyName, propertyValue);
+        ICustomConfiguration customConfiguration = new EnvironmentConfiguration();
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout().ToString());
+      }
+
       public void Dispose()
       {
         foreach (var propertyName in EnvironmentVariables)
@@ -331,6 +369,38 @@ public void GetHubImageNamePrefixCustomConfiguration(string configuration, strin
         ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
         Assert.Equal(expected, customConfiguration.GetHubImageNamePrefix());
       }
+
+      [Theory]
+      [InlineData("", 0)]
+      [InlineData("wait.strategy.retries=", 0)]
+      [InlineData("wait.strategy.retries=1", 1)]
+      public void GetWaitStrategyRetriesCustomConfiguration(string configuration, ushort expected)
+      {
+        ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyRetries());
+      }
+
+      [Theory]
+      [InlineData("", "00:00:01")]
+      [InlineData("wait.strategy.interval=", "00:00:01")]
+      [InlineData("wait.strategy.interval=-00:00:00.001", "00:00:01")]
+      [InlineData("wait.strategy.interval=00:00:01", "00:00:01")]
+      public void GetWaitStrategyIntervalCustomConfiguration(string configuration, string expected)
+      {
+        ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyInterval().ToString());
+      }
+
+      [Theory]
+      [InlineData("", "01:00:00")]
+      [InlineData("wait.strategy.timeout=", "01:00:00")]
+      [InlineData("wait.strategy.timeout=-00:00:00.001", "01:00:00")]
+      [InlineData("wait.strategy.timeout=00:00:01", "00:00:01")]
+      public void GetWaitStrategyTimeoutCustomConfiguration(string configuration, string expected)
+      {
+        ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout().ToString());
+      }
     }
   }
 }

From c5ea618297ba0c5080d571c520f4f7c245c117fe Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Sat, 4 May 2024 10:50:22 +0200
Subject: [PATCH 2/4] feat: Add custom confis to settings

---
 ...erDesktopEndpointAuthenticationProvider.cs | 12 ++---
 ...ontainersEndpointAuthenticationProvider.cs | 12 ++---
 .../Configurations/CustomConfiguration.cs     | 18 ++++----
 .../EnvironmentConfiguration.cs               |  6 +--
 .../Configurations/ICustomConfiguration.cs    | 24 ++++++----
 .../PropertiesFileConfiguration.cs            |  6 +--
 .../Configurations/TestcontainersSettings.cs  | 36 +++++++++++++++
 .../WaitStrategies/WaitStrategy.cs            |  6 +--
 .../Configurations/CustomConfigurationTest.cs | 44 +++++++++----------
 9 files changed, 104 insertions(+), 60 deletions(-)

diff --git a/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs
index b2e280597..5ec60e648 100644
--- a/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs
+++ b/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs
@@ -103,21 +103,21 @@ public string GetHubImageNamePrefix()
     }
 
     /// <inheritdoc />
-    public ushort GetWaitStrategyRetries()
+    public ushort? GetWaitStrategyRetries()
     {
-      return ushort.MinValue;
+      return null;
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyInterval()
+    public TimeSpan? GetWaitStrategyInterval()
     {
-      return TimeSpan.Zero;
+      return null;
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyTimeout()
+    public TimeSpan? GetWaitStrategyTimeout()
     {
-      return TimeSpan.Zero;
+      return null;
     }
   }
 }
diff --git a/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs
index d9efde95d..a043d2e4e 100644
--- a/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs
+++ b/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs
@@ -130,21 +130,21 @@ public string GetHubImageNamePrefix()
     }
 
     /// <inheritdoc />
-    public ushort GetWaitStrategyRetries()
+    public ushort? GetWaitStrategyRetries()
     {
-      return ushort.MinValue;
+      return _customConfiguration.GetWaitStrategyRetries();
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyInterval()
+    public TimeSpan? GetWaitStrategyInterval()
     {
-      return TimeSpan.Zero;
+      return _customConfiguration.GetWaitStrategyInterval();
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyTimeout()
+    public TimeSpan? GetWaitStrategyTimeout()
     {
-      return TimeSpan.Zero;
+      return _customConfiguration.GetWaitStrategyTimeout();
     }
 
     private sealed class TestcontainersConfiguration : PropertiesFileConfiguration
diff --git a/src/Testcontainers/Configurations/CustomConfiguration.cs b/src/Testcontainers/Configurations/CustomConfiguration.cs
index 587db8d7e..e5a45b361 100644
--- a/src/Testcontainers/Configurations/CustomConfiguration.cs
+++ b/src/Testcontainers/Configurations/CustomConfiguration.cs
@@ -102,24 +102,26 @@ protected virtual string GetHubImageNamePrefix(string propertyName)
       return GetPropertyValue<string>(propertyName);
     }
 
-    protected virtual ushort GetWaitStrategyRetries(string propertyName)
+    protected virtual ushort? GetWaitStrategyRetries(string propertyName)
     {
-      return GetPropertyValue<ushort>(propertyName);
+      return GetPropertyValue<ushort?>(propertyName);
     }
 
-    protected virtual TimeSpan GetWaitStrategyInterval(string propertyName)
+    protected virtual TimeSpan? GetWaitStrategyInterval(string propertyName)
     {
-      return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, out var result) && result > TimeSpan.Zero ? result : TimeSpan.FromSeconds(1);
+      return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, out var result) && result > TimeSpan.Zero ? result : (TimeSpan?)null;
     }
 
-    protected virtual TimeSpan GetWaitStrategyTimeout(string propertyName)
+    protected virtual TimeSpan? GetWaitStrategyTimeout(string propertyName)
     {
-      return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, out var result) && result > TimeSpan.Zero ? result : TimeSpan.FromHours(1);
+      return _properties.TryGetValue(propertyName, out var propertyValue) && TimeSpan.TryParse(propertyValue, out var result) && result > TimeSpan.Zero ? result : (TimeSpan?)null;
     }
 
     private T GetPropertyValue<T>(string propertyName)
     {
-      switch (Type.GetTypeCode(typeof(T)))
+      var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
+
+      switch (Type.GetTypeCode(type))
       {
         case TypeCode.Boolean:
         {
@@ -128,7 +130,7 @@ private T GetPropertyValue<T>(string propertyName)
 
         case TypeCode.UInt16:
         {
-          return (T)(object)(_properties.TryGetValue(propertyName, out var propertyValue) && ushort.TryParse(propertyValue, out var result) ? result : ushort.MinValue);
+          return (T)(object)(_properties.TryGetValue(propertyName, out var propertyValue) && ushort.TryParse(propertyValue, out var result) ? result : (ushort?)null);
         }
 
         case TypeCode.String:
diff --git a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs
index 462894a6f..ebc2dbcad 100644
--- a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs
+++ b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs
@@ -149,19 +149,19 @@ public string GetHubImageNamePrefix()
     }
 
     /// <inheritdoc />
-    public ushort GetWaitStrategyRetries()
+    public ushort? GetWaitStrategyRetries()
     {
       return GetWaitStrategyRetries(WaitStrategyRetries);
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyInterval()
+    public TimeSpan? GetWaitStrategyInterval()
     {
       return GetWaitStrategyInterval(WaitStrategyInterval);
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyTimeout()
+    public TimeSpan? GetWaitStrategyTimeout()
     {
       return GetWaitStrategyTimeout(WaitStrategyTimeout);
     }
diff --git a/src/Testcontainers/Configurations/ICustomConfiguration.cs b/src/Testcontainers/Configurations/ICustomConfiguration.cs
index af65a0b69..4cbd51285 100644
--- a/src/Testcontainers/Configurations/ICustomConfiguration.cs
+++ b/src/Testcontainers/Configurations/ICustomConfiguration.cs
@@ -103,21 +103,27 @@ internal interface ICustomConfiguration
     string GetHubImageNamePrefix();
 
     /// <summary>
-    ///
+    /// Gets the wait strategy retries custom configuration.
     /// </summary>
-    /// <returns></returns>
-    ushort GetWaitStrategyRetries();
+    /// <returns>The wait strategy retries custom configuration.</returns>
+    /// <remarks>https://dotnet.testcontainers.org/custom_configuration/.</remarks>
+    [CanBeNull]
+    ushort? GetWaitStrategyRetries();
 
     /// <summary>
-    ///
+    /// Gets the wait strategy interval custom configuration.
     /// </summary>
-    /// <returns></returns>
-    TimeSpan GetWaitStrategyInterval();
+    /// <returns>The wait strategy interval custom configuration.</returns>
+    /// <remarks>https://dotnet.testcontainers.org/custom_configuration/.</remarks>
+    [CanBeNull]
+    TimeSpan? GetWaitStrategyInterval();
 
     /// <summary>
-    ///
+    /// Gets the wait strategy timeout custom configuration.
     /// </summary>
-    /// <returns></returns>
-    TimeSpan GetWaitStrategyTimeout();
+    /// <returns>The wait strategy timeout custom configuration.</returns>
+    /// <remarks>https://dotnet.testcontainers.org/custom_configuration/.</remarks>
+    [CanBeNull]
+    TimeSpan? GetWaitStrategyTimeout();
   }
 }
diff --git a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs
index 71418a5eb..86d1dc67c 100644
--- a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs
+++ b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs
@@ -141,21 +141,21 @@ public string GetHubImageNamePrefix()
     }
 
     /// <inheritdoc />
-    public ushort GetWaitStrategyRetries()
+    public ushort? GetWaitStrategyRetries()
     {
       const string propertyName = "wait.strategy.retries";
       return GetWaitStrategyRetries(propertyName);
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyInterval()
+    public TimeSpan? GetWaitStrategyInterval()
     {
       const string propertyName = "wait.strategy.interval";
       return GetWaitStrategyInterval(propertyName);
     }
 
     /// <inheritdoc />
-    public TimeSpan GetWaitStrategyTimeout()
+    public TimeSpan? GetWaitStrategyTimeout()
     {
       const string propertyName = "wait.strategy.timeout";
       return GetWaitStrategyTimeout(propertyName);
diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs
index 59dbc57d1..c2b5146b8 100644
--- a/src/Testcontainers/Configurations/TestcontainersSettings.cs
+++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs
@@ -100,6 +100,42 @@ static TestcontainersSettings()
     public static string HubImageNamePrefix { get; set; }
       = EnvironmentConfiguration.Instance.GetHubImageNamePrefix() ?? PropertiesFileConfiguration.Instance.GetHubImageNamePrefix();
 
+    /// <summary>
+    /// Gets or sets the wait strategy retry count.
+    /// </summary>
+    /// <remarks>
+    /// This property represents the default value and applies to all wait strategies.
+    /// Wait strategies can be configured individually using the wait strategy option callback:
+    /// https://dotnet.testcontainers.org/api/wait_strategies/.
+    /// </remarks>
+    [CanBeNull]
+    public static ushort? WaitStrategyRetries { get; set; }
+      = EnvironmentConfiguration.Instance.GetWaitStrategyRetries() ?? PropertiesFileConfiguration.Instance.GetWaitStrategyRetries();
+
+    /// <summary>
+    /// Gets or sets the wait strategy interval.
+    /// </summary>
+    /// <remarks>
+    /// This property represents the default value and applies to all wait strategies.
+    /// Wait strategies can be configured individually using the wait strategy option callback:
+    /// https://dotnet.testcontainers.org/api/wait_strategies/.
+    /// </remarks>
+    [CanBeNull]
+    public static TimeSpan? WaitStrategyInterval { get; set; }
+      = EnvironmentConfiguration.Instance.GetWaitStrategyInterval() ?? PropertiesFileConfiguration.Instance.GetWaitStrategyInterval();
+
+    /// <summary>
+    /// Gets or sets the wait strategy timeout.
+    /// </summary>
+    /// <remarks>
+    /// This property represents the default value and applies to all wait strategies.
+    /// Wait strategies can be configured individually using the wait strategy option callback:
+    /// https://dotnet.testcontainers.org/api/wait_strategies/.
+    /// </remarks>
+    [CanBeNull]
+    public static TimeSpan? WaitStrategyTimeout { get; set; }
+      = EnvironmentConfiguration.Instance.GetWaitStrategyTimeout() ?? PropertiesFileConfiguration.Instance.GetWaitStrategyTimeout();
+
     /// <summary>
     /// Gets or sets the host operating system.
     /// </summary>
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
index fe7de8a0a..6ac8f3cde 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
@@ -45,19 +45,19 @@ public WaitStrategy(IWaitUntil waitUntil)
     /// Gets the number of retries.
     /// </summary>
     public ushort Retries { get; private set; }
-      = ushort.MinValue;
+      = TestcontainersSettings.WaitStrategyRetries ?? 0;
 
     /// <summary>
     /// Gets the interval between retries.
     /// </summary>
     public TimeSpan Interval { get; private set; }
-      = TimeSpan.FromSeconds(1);
+      = TestcontainersSettings.WaitStrategyInterval ?? TimeSpan.FromSeconds(1);
 
     /// <summary>
     /// Gets the timeout.
     /// </summary>
     public TimeSpan Timeout { get; private set; }
-      = TimeSpan.FromHours(1);
+      = TestcontainersSettings.WaitStrategyTimeout ?? TimeSpan.FromHours(1);
 
     /// <inheritdoc />
     public IWaitStrategy WithRetries(ushort retries)
diff --git a/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs
index 873e1079d..72c6ecbd9 100644
--- a/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs
+++ b/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs
@@ -181,10 +181,10 @@ public void GetHubImageNamePrefixCustomConfiguration(string propertyName, string
       }
 
       [Theory]
-      [InlineData("", "", 0)]
-      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_RETRIES", "", 0)]
+      [InlineData("", "", null)]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_RETRIES", "", null)]
       [InlineData("TESTCONTAINERS_WAIT_STRATEGY_RETRIES", "1", 1)]
-      public void GetWaitStrategyRetriesCustomConfiguration(string propertyName, string propertyValue, ushort expected)
+      public void GetWaitStrategyRetriesCustomConfiguration(string propertyName, string propertyValue, int? expected)
       {
         SetEnvironmentVariable(propertyName, propertyValue);
         ICustomConfiguration customConfiguration = new EnvironmentConfiguration();
@@ -192,27 +192,27 @@ public void GetWaitStrategyRetriesCustomConfiguration(string propertyName, strin
       }
 
       [Theory]
-      [InlineData("", "", "00:00:01")]
-      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "", "00:00:01")]
-      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "-00:00:00.001", "00:00:01")]
+      [InlineData("", "", null)]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "", null)]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "-00:00:00.001", null)]
       [InlineData("TESTCONTAINERS_WAIT_STRATEGY_INTERVAL", "00:00:01", "00:00:01")]
       public void GetWaitStrategyIntervalCustomConfiguration(string propertyName, string propertyValue, string expected)
       {
         SetEnvironmentVariable(propertyName, propertyValue);
         ICustomConfiguration customConfiguration = new EnvironmentConfiguration();
-        Assert.Equal(expected, customConfiguration.GetWaitStrategyInterval().ToString());
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyInterval()?.ToString());
       }
 
       [Theory]
-      [InlineData("", "", "01:00:00")]
-      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "", "01:00:00")]
-      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "-00:00:00.001", "01:00:00")]
+      [InlineData("", "", null)]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "", null)]
+      [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "-00:00:00.001", null)]
       [InlineData("TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT", "00:00:01", "00:00:01")]
       public void GetWaitStrategyTimeoutCustomConfiguration(string propertyName, string propertyValue, string expected)
       {
         SetEnvironmentVariable(propertyName, propertyValue);
         ICustomConfiguration customConfiguration = new EnvironmentConfiguration();
-        Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout().ToString());
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout()?.ToString());
       }
 
       public void Dispose()
@@ -371,35 +371,35 @@ public void GetHubImageNamePrefixCustomConfiguration(string configuration, strin
       }
 
       [Theory]
-      [InlineData("", 0)]
-      [InlineData("wait.strategy.retries=", 0)]
+      [InlineData("", null)]
+      [InlineData("wait.strategy.retries=", null)]
       [InlineData("wait.strategy.retries=1", 1)]
-      public void GetWaitStrategyRetriesCustomConfiguration(string configuration, ushort expected)
+      public void GetWaitStrategyRetriesCustomConfiguration(string configuration, int? expected)
       {
         ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
         Assert.Equal(expected, customConfiguration.GetWaitStrategyRetries());
       }
 
       [Theory]
-      [InlineData("", "00:00:01")]
-      [InlineData("wait.strategy.interval=", "00:00:01")]
-      [InlineData("wait.strategy.interval=-00:00:00.001", "00:00:01")]
+      [InlineData("", null)]
+      [InlineData("wait.strategy.interval=", null)]
+      [InlineData("wait.strategy.interval=-00:00:00.001", null)]
       [InlineData("wait.strategy.interval=00:00:01", "00:00:01")]
       public void GetWaitStrategyIntervalCustomConfiguration(string configuration, string expected)
       {
         ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
-        Assert.Equal(expected, customConfiguration.GetWaitStrategyInterval().ToString());
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyInterval()?.ToString());
       }
 
       [Theory]
-      [InlineData("", "01:00:00")]
-      [InlineData("wait.strategy.timeout=", "01:00:00")]
-      [InlineData("wait.strategy.timeout=-00:00:00.001", "01:00:00")]
+      [InlineData("", null)]
+      [InlineData("wait.strategy.timeout=", null)]
+      [InlineData("wait.strategy.timeout=-00:00:00.001", null)]
       [InlineData("wait.strategy.timeout=00:00:01", "00:00:01")]
       public void GetWaitStrategyTimeoutCustomConfiguration(string configuration, string expected)
       {
         ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration });
-        Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout().ToString());
+        Assert.Equal(expected, customConfiguration.GetWaitStrategyTimeout()?.ToString());
       }
     }
   }

From f50d38cfbb75aee7c31cb16097d6aeba579e7c12 Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Sat, 4 May 2024 11:06:40 +0200
Subject: [PATCH 3/4] docs: Add custom configs

---
 docs/custom_configuration/index.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/docs/custom_configuration/index.md b/docs/custom_configuration/index.md
index 81eae7bb7..5c3f60634 100644
--- a/docs/custom_configuration/index.md
+++ b/docs/custom_configuration/index.md
@@ -16,6 +16,11 @@ Testcontainers supports various configurations to set up your test environment.
 | `ryuk.container.privileged` | `TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED` | Runs Ryuk (resource reaper) in privileged mode.                                                                           | `false`                     |
 | `ryuk.container.image`      | `TESTCONTAINERS_RYUK_CONTAINER_IMAGE`      | The Ryuk (resource reaper) Docker image.                                                                                  | `testcontainers/ryuk:0.5.1` |
 | `hub.image.name.prefix`     | `TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX`     | The name to use for substituting the Docker Hub registry part of the image name.                                          | -                           |
+<!-- | `wait.strategy.retries`     | `TESTCONTAINERS_WAIT_STRATEGY_RETRIES`     | The wait strategy retry count.                                                                                            | `infinite`                  |
+| `wait.strategy.interval`    | `TESTCONTAINERS_WAIT_STRATEGY_INTERVAL`    | The wait strategy interval<sup>1</sup>.                                                                                   | `00:00:01`                  |
+| `wait.strategy.timeout`     | `TESTCONTAINERS_WAIT_STRATEGY_TIMEOUT`     | The wait strategy timeout<sup>1</sup>.                                                                                    | `01:00:00`                  |
+
+1) The value represent the string representation of a [TimeSpan](https://learn.microsoft.com/en-us/dotnet/api/system.timespan), for example, `00:00:01` for 1 second. -->
 
 ## Configure remote container runtime
 

From 175635b05a4e5dd6ce24a45bcfc31c0bc05e0e37 Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Sat, 4 May 2024 13:41:33 +0200
Subject: [PATCH 4/4] chore: Add RetryLimitExceededException

---
 docs/api/resource_reuse.md                    |  5 ++-
 .../RetryLimitExceededException.cs            | 21 +++++++++
 .../WaitStrategies/WaitStrategy.cs            | 12 ++---
 .../WaitStrategyTest.cs                       | 45 +++++++++++++++++++
 4 files changed, 75 insertions(+), 8 deletions(-)
 create mode 100644 src/Testcontainers/Configurations/WaitStrategies/RetryLimitExceededException.cs
 create mode 100644 tests/Testcontainers.Platform.Linux.Tests/WaitStrategyTest.cs

diff --git a/docs/api/resource_reuse.md b/docs/api/resource_reuse.md
index f99f4c06b..2165add78 100644
--- a/docs/api/resource_reuse.md
+++ b/docs/api/resource_reuse.md
@@ -17,8 +17,9 @@ _ = new ContainerBuilder()
 
 The current implementation considers the following resource configurations and their corresponding builder APIs when calculating the hash value.
 
-> [!NOTE]  
-> Version 3.8.0 did not include the container configuration's name in the hash value.
+!!!note
+
+    Version 3.8.0 did not include the container configuration's name in the hash value.
 
 - [ContainerConfiguration](https://github.com/testcontainers/testcontainers-dotnet/blob/develop/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs)
     - Image
diff --git a/src/Testcontainers/Configurations/WaitStrategies/RetryLimitExceededException.cs b/src/Testcontainers/Configurations/WaitStrategies/RetryLimitExceededException.cs
new file mode 100644
index 000000000..17b3621c0
--- /dev/null
+++ b/src/Testcontainers/Configurations/WaitStrategies/RetryLimitExceededException.cs
@@ -0,0 +1,21 @@
+namespace DotNet.Testcontainers.Configurations
+{
+  using System;
+
+  public sealed class RetryLimitExceededException : Exception
+  {
+    public RetryLimitExceededException()
+    {
+    }
+
+    public RetryLimitExceededException(string message)
+      : base(message)
+    {
+    }
+
+    public RetryLimitExceededException(string message, Exception inner)
+      : base(message, inner)
+    {
+    }
+  }
+}
diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
index 6ac8f3cde..48c74d36f 100644
--- a/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
+++ b/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs
@@ -114,10 +114,10 @@ public virtual Task<bool> UntilAsync(IContainer container, CancellationToken ct
     /// <param name="retries">The number of retries to run for the condition to become false.</param>
     /// <param name="ct">The optional cancellation token to cancel the waiting operation.</param>
     /// <exception cref="TimeoutException">Thrown when the timeout expires.</exception>
-    /// <exception cref="ArgumentException">Thrown when the number of retries is exceeded.</exception>
+    /// <exception cref="RetryLimitExceededException">Thrown when the number of retries is exceeded.</exception>
     /// <returns>A task that represents the asynchronous block operation.</returns>
     [PublicAPI]
-    public static async Task WaitWhileAsync(Func<Task<bool>> wait, TimeSpan interval, TimeSpan timeout, int retries = -1, CancellationToken ct = default)
+    public static async Task WaitWhileAsync(Func<Task<bool>> wait, TimeSpan interval, TimeSpan timeout, int retries = 0, CancellationToken ct = default)
     {
       ushort actualRetries = 0;
 
@@ -134,7 +134,7 @@ async Task WhileAsync()
           }
 
           _ = Guard.Argument(retries, nameof(retries))
-            .ThrowIf(_ => retries > 0 && ++actualRetries > retries, _ => throw new ArgumentException(MaximumRetryExceededException));
+            .ThrowIf(_ => retries > 0 && ++actualRetries > retries, _ => throw new RetryLimitExceededException(MaximumRetryExceededException));
 
           await Task.Delay(interval, ct)
             .ConfigureAwait(false);
@@ -170,10 +170,10 @@ await waitTask
     /// <param name="retries">The number of retries to run for the condition to become true.</param>
     /// <param name="ct">The optional cancellation token to cancel the waiting operation.</param>
     /// <exception cref="TimeoutException">Thrown when the timeout expires.</exception>
-    /// <exception cref="ArgumentException">Thrown when the number of retries is exceeded.</exception>
+    /// <exception cref="RetryLimitExceededException">Thrown when the number of retries is exceeded.</exception>
     /// <returns>A task that represents the asynchronous block operation.</returns>
     [PublicAPI]
-    public static async Task WaitUntilAsync(Func<Task<bool>> wait, TimeSpan interval, TimeSpan timeout, int retries = -1, CancellationToken ct = default)
+    public static async Task WaitUntilAsync(Func<Task<bool>> wait, TimeSpan interval, TimeSpan timeout, int retries = 0, CancellationToken ct = default)
     {
       ushort actualRetries = 0;
 
@@ -190,7 +190,7 @@ async Task UntilAsync()
           }
 
           _ = Guard.Argument(retries, nameof(retries))
-            .ThrowIf(_ => retries > 0 && ++actualRetries > retries, _ => throw new ArgumentException(MaximumRetryExceededException));
+            .ThrowIf(_ => retries > 0 && ++actualRetries > retries, _ => throw new RetryLimitExceededException(MaximumRetryExceededException));
 
           await Task.Delay(interval, ct)
             .ConfigureAwait(false);
diff --git a/tests/Testcontainers.Platform.Linux.Tests/WaitStrategyTest.cs b/tests/Testcontainers.Platform.Linux.Tests/WaitStrategyTest.cs
new file mode 100644
index 000000000..0bcddbb26
--- /dev/null
+++ b/tests/Testcontainers.Platform.Linux.Tests/WaitStrategyTest.cs
@@ -0,0 +1,45 @@
+namespace Testcontainers.Tests;
+
+public sealed class WaitStrategyTest
+{
+    [Fact]
+    public Task WithTimeout()
+    {
+        return Assert.ThrowsAsync<TimeoutException>(() => new ContainerBuilder()
+            .WithImage(CommonImages.Alpine)
+            .WithEntrypoint(CommonCommands.SleepInfinity)
+            .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(FailingWaitStrategy.Instance, o => o.WithTimeout(TimeSpan.FromSeconds(1))))
+            .Build()
+            .StartAsync());
+    }
+
+    [Fact]
+    public Task WithRetries()
+    {
+        return Assert.ThrowsAsync<RetryLimitExceededException>(() => new ContainerBuilder()
+            .WithImage(CommonImages.Alpine)
+            .WithEntrypoint(CommonCommands.SleepInfinity)
+            .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(FailingWaitStrategy.Instance, o => o.WithRetries(1)))
+            .Build()
+            .StartAsync());
+    }
+
+    private sealed class FailingWaitStrategy : IWaitUntil
+    {
+        static FailingWaitStrategy()
+        {
+        }
+
+        private FailingWaitStrategy()
+        {
+        }
+
+        public static IWaitUntil Instance { get; }
+            = new FailingWaitStrategy();
+
+        public Task<bool> UntilAsync(IContainer container)
+        {
+            return Task.FromResult(false);
+        }
+    }
+}
\ No newline at end of file