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