From c8b0c4d976b91d6982777723317e85f079576daf Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Mon, 10 Jul 2023 12:45:34 +0200 Subject: [PATCH 01/10] Introduce `Polly.Testing` package --- Polly.sln | 14 ++++ src/Polly.Core/Polly.Core.csproj | 1 + src/Polly.Core/ResilienceStrategy.cs | 2 + .../ResilienceStrategyBuilderBase.cs | 10 ++- src/Polly.Testing/Polly.Testing.csproj | 17 ++++ src/Polly.Testing/README.md | 3 + .../ResilienceStrategyDescriptor.cs | 12 +++ .../ResilienceStrategyExtensions.cs | 78 +++++++++++++++++++ src/Polly.Testing/ResilienceStrategyType.cs | 52 +++++++++++++ .../Polly.Testing.Tests.csproj | 17 ++++ .../ResilienceStrategyExtensionsTests.cs | 55 +++++++++++++ 11 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 src/Polly.Testing/Polly.Testing.csproj create mode 100644 src/Polly.Testing/README.md create mode 100644 src/Polly.Testing/ResilienceStrategyDescriptor.cs create mode 100644 src/Polly.Testing/ResilienceStrategyExtensions.cs create mode 100644 src/Polly.Testing/ResilienceStrategyType.cs create mode 100644 test/Polly.Testing.Tests/Polly.Testing.Tests.csproj create mode 100644 test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs diff --git a/Polly.sln b/Polly.sln index dde4741a53..97851b37fd 100644 --- a/Polly.sln +++ b/Polly.sln @@ -52,6 +52,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A6CC41B9-E EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B7BF406B-B06F-4025-83E6-7219C53196A6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Testing", "src\Polly.Testing\Polly.Testing.csproj", "{9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Testing.Tests", "test\Polly.Testing.Tests\Polly.Testing.Tests.csproj", "{D333B5CE-982D-4C11-BDAF-4217AA02306E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -102,6 +106,14 @@ Global {C04DEE61-C1EA-4028-B457-CDBD304B8ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU {C04DEE61-C1EA-4028-B457-CDBD304B8ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU {C04DEE61-C1EA-4028-B457-CDBD304B8ED9}.Release|Any CPU.Build.0 = Release|Any CPU + {9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9}.Release|Any CPU.Build.0 = Release|Any CPU + {D333B5CE-982D-4C11-BDAF-4217AA02306E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D333B5CE-982D-4C11-BDAF-4217AA02306E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D333B5CE-982D-4C11-BDAF-4217AA02306E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D333B5CE-982D-4C11-BDAF-4217AA02306E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -119,6 +131,8 @@ Global {BCA09595-A4D3-4D74-AC80-3E7017E51B24} = {B7BF406B-B06F-4025-83E6-7219C53196A6} {06070F42-6738-4D0B-8D7E-9400B4030193} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB} {C04DEE61-C1EA-4028-B457-CDBD304B8ED9} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB} + {9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9} = {B7BF406B-B06F-4025-83E6-7219C53196A6} + {D333B5CE-982D-4C11-BDAF-4217AA02306E} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E5D54CD-770A-4345-B585-1848FC2EA6F4} diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index c9747fcc2c..c4a3fbde39 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Polly.Core/ResilienceStrategy.cs b/src/Polly.Core/ResilienceStrategy.cs index 6ab5449f12..717269aaa1 100644 --- a/src/Polly.Core/ResilienceStrategy.cs +++ b/src/Polly.Core/ResilienceStrategy.cs @@ -11,6 +11,8 @@ namespace Polly; /// public abstract partial class ResilienceStrategy { + internal ResilienceStrategyOptions? Options { get; set; } + /// /// Executes the specified callback. /// diff --git a/src/Polly.Core/ResilienceStrategyBuilderBase.cs b/src/Polly.Core/ResilienceStrategyBuilderBase.cs index c7083f1603..4aeb59ed87 100644 --- a/src/Polly.Core/ResilienceStrategyBuilderBase.cs +++ b/src/Polly.Core/ResilienceStrategyBuilderBase.cs @@ -137,15 +137,17 @@ private ResilienceStrategy CreateResilienceStrategy(Entry entry) builderName: BuilderName, builderInstanceName: InstanceName, builderProperties: Properties, - strategyName: entry.Properties.StrategyName, - strategyType: entry.Properties.StrategyType, + strategyName: entry.Options.StrategyName, + strategyType: entry.Options.StrategyType, timeProvider: TimeProvider, isGenericBuilder: IsGenericBuilder, diagnosticSource: DiagnosticSource, randomizer: Randomizer); - return entry.Factory(context); + var strategy = entry.Factory(context); + strategy.Options = entry.Options; + return strategy; } - private sealed record Entry(Func Factory, ResilienceStrategyOptions Properties); + private sealed record Entry(Func Factory, ResilienceStrategyOptions Options); } diff --git a/src/Polly.Testing/Polly.Testing.csproj b/src/Polly.Testing/Polly.Testing.csproj new file mode 100644 index 0000000000..857e3081f1 --- /dev/null +++ b/src/Polly.Testing/Polly.Testing.csproj @@ -0,0 +1,17 @@ + + + + net7.0;net6.0;netstandard2.0;net472;net462 + Polly.Testing + Polly.Testing + enable + true + Library + 100 + + + + + + + diff --git a/src/Polly.Testing/README.md b/src/Polly.Testing/README.md new file mode 100644 index 0000000000..7423de748b --- /dev/null +++ b/src/Polly.Testing/README.md @@ -0,0 +1,3 @@ +# About Polly.Testing + + diff --git a/src/Polly.Testing/ResilienceStrategyDescriptor.cs b/src/Polly.Testing/ResilienceStrategyDescriptor.cs new file mode 100644 index 0000000000..634d7370aa --- /dev/null +++ b/src/Polly.Testing/ResilienceStrategyDescriptor.cs @@ -0,0 +1,12 @@ +namespace Polly.Testing; + +/// +/// This class provides additional information about . +/// +/// The options used by the resilience strategy, if any. +/// The type of strategy as an enum. +/// The type of the strategy. +public record ResilienceStrategyDescriptor(ResilienceStrategyOptions? Options, ResilienceStrategyType Type, Type StrategyType) +{ +} + diff --git a/src/Polly.Testing/ResilienceStrategyExtensions.cs b/src/Polly.Testing/ResilienceStrategyExtensions.cs new file mode 100644 index 0000000000..0d86e004bf --- /dev/null +++ b/src/Polly.Testing/ResilienceStrategyExtensions.cs @@ -0,0 +1,78 @@ +using Polly.CircuitBreaker; +using Polly.Fallback; +using Polly.Hedging; +using Polly.Retry; +using Polly.Timeout; +using Polly.Utils; + +namespace Polly.Testing; + +/// +/// The test-related extensions for and . +/// +public static class ResilienceStrategyExtensions +{ + /// + /// Gets the inner strategies the is composed of. + /// + /// The type of result. + /// The strategy instance. + /// A list of inner strategies. + /// Thrown when is . + public static IReadOnlyList GetInnerStrategies(this ResilienceStrategy strategy) + { + Guard.NotNull(strategy); + + return strategy.Strategy.GetInnerStrategies(); + } + + /// + /// Gets the inner strategies the is composed of. + /// + /// The strategy instance. + /// A list of inner strategies. + /// Thrown when is . + public static IReadOnlyList GetInnerStrategies(this ResilienceStrategy strategy) + { + Guard.NotNull(strategy); + + var strategies = new List(); + strategy.ExpandStrategies(strategies); + + return strategies.Select(s => new ResilienceStrategyDescriptor(s.Options, GetType(s), s.GetType())).ToList(); + } + + private static void ExpandStrategies(this ResilienceStrategy strategy, List strategies) + { + if (strategy is ResilienceStrategyPipeline pipeline) + { + foreach (var inner in pipeline.Strategies) + { + inner.ExpandStrategies(strategies); + } + } + else if (strategy is ReloadableResilienceStrategy reloadable) + { + strategies.Add(reloadable); + ExpandStrategies(reloadable.Strategy, strategies); + } + else + { + strategies.Add(strategy); + } + } + + private static ResilienceStrategyType GetType(ResilienceStrategy strategy) => strategy switch + { + TimeoutResilienceStrategy => ResilienceStrategyType.Timeout, + ReloadableResilienceStrategy => ResilienceStrategyType.Reload, + _ when strategy.GetType().FullName == "Polly.RateLimiting.RateLimiterResilienceStrategy" => ResilienceStrategyType.RateLimiter, + _ when strategy.GetType().FullName == "Polly.Extensions.Telemetry.TelemetryResilienceStrategy" => ResilienceStrategyType.Telemetry, + _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(RetryResilienceStrategy<>) => ResilienceStrategyType.Retry, + _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(CircuitBreakerResilienceStrategy<>) => ResilienceStrategyType.CircuitBreaker, + _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(HedgingResilienceStrategy<>) => ResilienceStrategyType.Hedging, + _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(FallbackResilienceStrategy<>) => ResilienceStrategyType.Fallback, + + _ => ResilienceStrategyType.Custom + }; +} diff --git a/src/Polly.Testing/ResilienceStrategyType.cs b/src/Polly.Testing/ResilienceStrategyType.cs new file mode 100644 index 0000000000..4b4533b307 --- /dev/null +++ b/src/Polly.Testing/ResilienceStrategyType.cs @@ -0,0 +1,52 @@ +namespace Polly.Testing; + +/// +/// The type of resilience strategy. +/// +public enum ResilienceStrategyType +{ + /// + /// The strategy is custome one, i.e. defined in the external library and not built-in into Polly. + /// + Custom, + + /// + /// The retry strategy. + /// + Retry, + + /// + /// The timeout strategy. + /// + Timeout, + + /// + /// The hedging strategy. + /// + Hedging, + + /// + /// The circuit breaker strategy. + /// + CircuitBreaker, + + /// + /// The fallback strategy. + /// + Fallback, + + /// + /// The rate limiter strategy. + /// + RateLimiter, + + /// + /// The telemetry strategy. + /// + Telemetry, + + /// + /// The reload strategy. + /// + Reload +} diff --git a/test/Polly.Testing.Tests/Polly.Testing.Tests.csproj b/test/Polly.Testing.Tests/Polly.Testing.Tests.csproj new file mode 100644 index 0000000000..2469343b97 --- /dev/null +++ b/test/Polly.Testing.Tests/Polly.Testing.Tests.csproj @@ -0,0 +1,17 @@ + + + net7.0;net6.0 + $(TargetFrameworks);net481 + Test + enable + 100 + $(NoWarn);SA1600;SA1204 + [Polly.Testing]* + + + + + + + + diff --git a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs new file mode 100644 index 0000000000..a0be73408d --- /dev/null +++ b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.Extensions.Logging.Abstractions; +using Polly.Timeout; + +namespace Polly.Testing.Tests; + +public class ResilienceStrategyExtensionsTests +{ + [Fact] + public void GetInnerStrategies_Ok() + { + // arrange + var strategy = new ResilienceStrategyBuilder() + .AddFallback(new() + { + FallbackAction = _ => Outcome.FromResultAsTask("dummy"), + }) + .AddRetry(new()) + .AddAdvancedCircuitBreaker(new()) + .AddTimeout(TimeSpan.FromSeconds(1)) + .AddHedging(new()) + .AddConcurrencyLimiter(10) + .AddStrategy(new CustomStrategy()) + .ConfigureTelemetry(NullLoggerFactory.Instance) + .Build(); + + // act + var strategies = strategy.GetInnerStrategies(); + + // assert + strategies.Should().HaveCount(8); + strategies[0].Type.Should().Be(ResilienceStrategyType.Telemetry); + strategies[1].Type.Should().Be(ResilienceStrategyType.Fallback); + strategies[2].Type.Should().Be(ResilienceStrategyType.Retry); + strategies[3].Type.Should().Be(ResilienceStrategyType.CircuitBreaker); + strategies[4].Type.Should().Be(ResilienceStrategyType.Timeout); + strategies[4].Options + .Should() + .BeOfType().Subject.Timeout + .Should().Be(TimeSpan.FromSeconds(1)); + + strategies[5].Type.Should().Be(ResilienceStrategyType.Hedging); + strategies[6].Type.Should().Be(ResilienceStrategyType.RateLimiter); + strategies[7].Type.Should().Be(ResilienceStrategyType.Custom); + strategies[7].StrategyType.Should().Be(typeof(CustomStrategy)); + } + + private sealed class CustomStrategy : ResilienceStrategy + { + protected override ValueTask> ExecuteCoreAsync(Func>> callback, ResilienceContext context, TState state) + => throw new NotSupportedException(); + } +} From 653cfa288986618bf42bb5ea91ec830f007c138f Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 13:22:25 +0200 Subject: [PATCH 02/10] Cleanup --- .../InnerStrategiesDescriptor.cs | 9 ++++ src/Polly.Testing/Polly.Testing.csproj | 2 +- .../ResilienceStrategyDescriptor.cs | 4 +- .../ResilienceStrategyExtensions.cs | 34 ++++-------- src/Polly.Testing/ResilienceStrategyType.cs | 52 ------------------- .../ResilienceStrategyExtensionsTests.cs | 52 ++++++++++++++----- 6 files changed, 62 insertions(+), 91 deletions(-) create mode 100644 src/Polly.Testing/InnerStrategiesDescriptor.cs delete mode 100644 src/Polly.Testing/ResilienceStrategyType.cs diff --git a/src/Polly.Testing/InnerStrategiesDescriptor.cs b/src/Polly.Testing/InnerStrategiesDescriptor.cs new file mode 100644 index 0000000000..de313cb2fa --- /dev/null +++ b/src/Polly.Testing/InnerStrategiesDescriptor.cs @@ -0,0 +1,9 @@ +namespace Polly.Testing; + +/// +/// Describes the pipeline of resilience strategy. +/// +/// The strategies the pipeline is composed of. +/// Gets a value indicating whether the pipeline has telemetry enabled. +/// Gets a value indicating whether the resilience strategy is reloadable. +public record class InnerStrategiesDescriptor(IReadOnlyList Strategies, bool HasTelemetry, bool IsReloadable); diff --git a/src/Polly.Testing/Polly.Testing.csproj b/src/Polly.Testing/Polly.Testing.csproj index 857e3081f1..d85ec8fba2 100644 --- a/src/Polly.Testing/Polly.Testing.csproj +++ b/src/Polly.Testing/Polly.Testing.csproj @@ -1,7 +1,7 @@  - net7.0;net6.0;netstandard2.0;net472;net462 + netstandard2.0 Polly.Testing Polly.Testing enable diff --git a/src/Polly.Testing/ResilienceStrategyDescriptor.cs b/src/Polly.Testing/ResilienceStrategyDescriptor.cs index 634d7370aa..4edf0c636b 100644 --- a/src/Polly.Testing/ResilienceStrategyDescriptor.cs +++ b/src/Polly.Testing/ResilienceStrategyDescriptor.cs @@ -4,9 +4,7 @@ /// This class provides additional information about . /// /// The options used by the resilience strategy, if any. -/// The type of strategy as an enum. /// The type of the strategy. -public record ResilienceStrategyDescriptor(ResilienceStrategyOptions? Options, ResilienceStrategyType Type, Type StrategyType) +public record ResilienceStrategyDescriptor(ResilienceStrategyOptions? Options, Type StrategyType) { } - diff --git a/src/Polly.Testing/ResilienceStrategyExtensions.cs b/src/Polly.Testing/ResilienceStrategyExtensions.cs index 0d86e004bf..6761a3c4d1 100644 --- a/src/Polly.Testing/ResilienceStrategyExtensions.cs +++ b/src/Polly.Testing/ResilienceStrategyExtensions.cs @@ -1,9 +1,4 @@ -using Polly.CircuitBreaker; -using Polly.Fallback; -using Polly.Hedging; -using Polly.Retry; -using Polly.Timeout; -using Polly.Utils; +using Polly.Utils; namespace Polly.Testing; @@ -19,7 +14,7 @@ public static class ResilienceStrategyExtensions /// The strategy instance. /// A list of inner strategies. /// Thrown when is . - public static IReadOnlyList GetInnerStrategies(this ResilienceStrategy strategy) + public static InnerStrategiesDescriptor GetInnerStrategies(this ResilienceStrategy strategy) { Guard.NotNull(strategy); @@ -32,16 +27,23 @@ public static IReadOnlyList GetInnerStrategiesThe strategy instance. /// A list of inner strategies. /// Thrown when is . - public static IReadOnlyList GetInnerStrategies(this ResilienceStrategy strategy) + public static InnerStrategiesDescriptor GetInnerStrategies(this ResilienceStrategy strategy) { Guard.NotNull(strategy); var strategies = new List(); strategy.ExpandStrategies(strategies); - return strategies.Select(s => new ResilienceStrategyDescriptor(s.Options, GetType(s), s.GetType())).ToList(); + var innerStrategies = strategies.Select(s => new ResilienceStrategyDescriptor(s.Options, s.GetType())).ToList(); + + return new InnerStrategiesDescriptor( + innerStrategies.Where(s => !ShouldSkip(s.StrategyType)).ToList().AsReadOnly(), + HasTelemetry: innerStrategies.Exists(s => s.StrategyType.FullName == "Polly.Extensions.Telemetry.TelemetryResilienceStrategy"), + IsReloadable: innerStrategies.Exists(s => s.StrategyType == typeof(ReloadableResilienceStrategy))); } + private static bool ShouldSkip(Type type) => type == typeof(ReloadableResilienceStrategy) || type.FullName == "Polly.Extensions.Telemetry.TelemetryResilienceStrategy"; + private static void ExpandStrategies(this ResilienceStrategy strategy, List strategies) { if (strategy is ResilienceStrategyPipeline pipeline) @@ -61,18 +63,4 @@ private static void ExpandStrategies(this ResilienceStrategy strategy, List strategy switch - { - TimeoutResilienceStrategy => ResilienceStrategyType.Timeout, - ReloadableResilienceStrategy => ResilienceStrategyType.Reload, - _ when strategy.GetType().FullName == "Polly.RateLimiting.RateLimiterResilienceStrategy" => ResilienceStrategyType.RateLimiter, - _ when strategy.GetType().FullName == "Polly.Extensions.Telemetry.TelemetryResilienceStrategy" => ResilienceStrategyType.Telemetry, - _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(RetryResilienceStrategy<>) => ResilienceStrategyType.Retry, - _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(CircuitBreakerResilienceStrategy<>) => ResilienceStrategyType.CircuitBreaker, - _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(HedgingResilienceStrategy<>) => ResilienceStrategyType.Hedging, - _ when strategy.GetType().IsGenericType && strategy.GetType().GetGenericTypeDefinition() == typeof(FallbackResilienceStrategy<>) => ResilienceStrategyType.Fallback, - - _ => ResilienceStrategyType.Custom - }; } diff --git a/src/Polly.Testing/ResilienceStrategyType.cs b/src/Polly.Testing/ResilienceStrategyType.cs deleted file mode 100644 index 4b4533b307..0000000000 --- a/src/Polly.Testing/ResilienceStrategyType.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Polly.Testing; - -/// -/// The type of resilience strategy. -/// -public enum ResilienceStrategyType -{ - /// - /// The strategy is custome one, i.e. defined in the external library and not built-in into Polly. - /// - Custom, - - /// - /// The retry strategy. - /// - Retry, - - /// - /// The timeout strategy. - /// - Timeout, - - /// - /// The hedging strategy. - /// - Hedging, - - /// - /// The circuit breaker strategy. - /// - CircuitBreaker, - - /// - /// The fallback strategy. - /// - Fallback, - - /// - /// The rate limiter strategy. - /// - RateLimiter, - - /// - /// The telemetry strategy. - /// - Telemetry, - - /// - /// The reload strategy. - /// - Reload -} diff --git a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs index a0be73408d..9ae6b82af7 100644 --- a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs +++ b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs @@ -1,7 +1,12 @@ using System; -using System.Collections.Generic; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; +using Polly.CircuitBreaker; +using Polly.Fallback; +using Polly.Hedging; +using Polly.RateLimiting; +using Polly.Registry; +using Polly.Retry; using Polly.Timeout; namespace Polly.Testing.Tests; @@ -30,21 +35,44 @@ public void GetInnerStrategies_Ok() var strategies = strategy.GetInnerStrategies(); // assert - strategies.Should().HaveCount(8); - strategies[0].Type.Should().Be(ResilienceStrategyType.Telemetry); - strategies[1].Type.Should().Be(ResilienceStrategyType.Fallback); - strategies[2].Type.Should().Be(ResilienceStrategyType.Retry); - strategies[3].Type.Should().Be(ResilienceStrategyType.CircuitBreaker); - strategies[4].Type.Should().Be(ResilienceStrategyType.Timeout); - strategies[4].Options + strategies.HasTelemetry.Should().BeTrue(); + strategies.Strategies.Should().HaveCount(7); + strategies.Strategies[0].Options.Should().BeOfType>(); + strategies.Strategies[1].Options.Should().BeOfType>(); + strategies.Strategies[2].Options.Should().BeOfType>(); + strategies.Strategies[3].Options.Should().BeOfType(); + strategies.Strategies[3].Options .Should() .BeOfType().Subject.Timeout .Should().Be(TimeSpan.FromSeconds(1)); - strategies[5].Type.Should().Be(ResilienceStrategyType.Hedging); - strategies[6].Type.Should().Be(ResilienceStrategyType.RateLimiter); - strategies[7].Type.Should().Be(ResilienceStrategyType.Custom); - strategies[7].StrategyType.Should().Be(typeof(CustomStrategy)); + strategies.Strategies[4].Options.Should().BeOfType>(); + strategies.Strategies[5].Options.Should().BeOfType(); + strategies.Strategies[6].StrategyType.Should().Be(typeof(CustomStrategy)); + } + + [Fact] + public void GetInnerStrategies_Reloadable_Ok() + { + // arrange + var strategy = new ResilienceStrategyRegistry().GetOrAddStrategy("dummy", (builder, context) => + { + context.EnableReloads(() => () => CancellationToken.None); + + builder + .AddConcurrencyLimiter(10) + .AddStrategy(new CustomStrategy()); + }); + + // act + var strategies = strategy.GetInnerStrategies(); + + // assert + strategies.IsReloadable.Should().BeTrue(); + strategies.HasTelemetry.Should().BeFalse(); + strategies.Strategies.Should().HaveCount(2); + strategies.Strategies[0].Options.Should().BeOfType(); + strategies.Strategies[1].StrategyType.Should().Be(typeof(CustomStrategy)); } private sealed class CustomStrategy : ResilienceStrategy From 7bb65ca0bd6c7059e3ce257e981a5b9770a1d54a Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 13:24:44 +0200 Subject: [PATCH 03/10] cleanup --- .../ResilienceStrategyExtensionsTests.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs index 9ae6b82af7..e700a813d2 100644 --- a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs +++ b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs @@ -32,23 +32,23 @@ public void GetInnerStrategies_Ok() .Build(); // act - var strategies = strategy.GetInnerStrategies(); + var descriptor = strategy.GetInnerStrategies(); // assert - strategies.HasTelemetry.Should().BeTrue(); - strategies.Strategies.Should().HaveCount(7); - strategies.Strategies[0].Options.Should().BeOfType>(); - strategies.Strategies[1].Options.Should().BeOfType>(); - strategies.Strategies[2].Options.Should().BeOfType>(); - strategies.Strategies[3].Options.Should().BeOfType(); - strategies.Strategies[3].Options + descriptor.HasTelemetry.Should().BeTrue(); + descriptor.Strategies.Should().HaveCount(7); + descriptor.Strategies[0].Options.Should().BeOfType>(); + descriptor.Strategies[1].Options.Should().BeOfType>(); + descriptor.Strategies[2].Options.Should().BeOfType>(); + descriptor.Strategies[3].Options.Should().BeOfType(); + descriptor.Strategies[3].Options .Should() .BeOfType().Subject.Timeout .Should().Be(TimeSpan.FromSeconds(1)); - strategies.Strategies[4].Options.Should().BeOfType>(); - strategies.Strategies[5].Options.Should().BeOfType(); - strategies.Strategies[6].StrategyType.Should().Be(typeof(CustomStrategy)); + descriptor.Strategies[4].Options.Should().BeOfType>(); + descriptor.Strategies[5].Options.Should().BeOfType(); + descriptor.Strategies[6].StrategyType.Should().Be(typeof(CustomStrategy)); } [Fact] @@ -65,14 +65,14 @@ public void GetInnerStrategies_Reloadable_Ok() }); // act - var strategies = strategy.GetInnerStrategies(); + var descriptor = strategy.GetInnerStrategies(); // assert - strategies.IsReloadable.Should().BeTrue(); - strategies.HasTelemetry.Should().BeFalse(); - strategies.Strategies.Should().HaveCount(2); - strategies.Strategies[0].Options.Should().BeOfType(); - strategies.Strategies[1].StrategyType.Should().Be(typeof(CustomStrategy)); + descriptor.IsReloadable.Should().BeTrue(); + descriptor.HasTelemetry.Should().BeFalse(); + descriptor.Strategies.Should().HaveCount(2); + descriptor.Strategies[0].Options.Should().BeOfType(); + descriptor.Strategies[1].StrategyType.Should().Be(typeof(CustomStrategy)); } private sealed class CustomStrategy : ResilienceStrategy From adff8b699752a1f7bfbcf3727d38fbee3e056819 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 13:26:39 +0200 Subject: [PATCH 04/10] Fix docs --- src/Polly.Testing/ResilienceStrategyDescriptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Polly.Testing/ResilienceStrategyDescriptor.cs b/src/Polly.Testing/ResilienceStrategyDescriptor.cs index 4edf0c636b..04005d3249 100644 --- a/src/Polly.Testing/ResilienceStrategyDescriptor.cs +++ b/src/Polly.Testing/ResilienceStrategyDescriptor.cs @@ -1,7 +1,7 @@ namespace Polly.Testing; /// -/// This class provides additional information about . +/// This class provides additional information about a . /// /// The options used by the resilience strategy, if any. /// The type of the strategy. From 96d8647bf254880c7b5f0269b8732fabb8a01117 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 14:00:11 +0200 Subject: [PATCH 05/10] Cleanup --- .../InnerStrategiesDescriptor.cs | 2 +- src/Polly.Testing/README.md | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Polly.Testing/InnerStrategiesDescriptor.cs b/src/Polly.Testing/InnerStrategiesDescriptor.cs index de313cb2fa..14f171a73a 100644 --- a/src/Polly.Testing/InnerStrategiesDescriptor.cs +++ b/src/Polly.Testing/InnerStrategiesDescriptor.cs @@ -1,7 +1,7 @@ namespace Polly.Testing; /// -/// Describes the pipeline of resilience strategy. +/// Describes the pipeline of a resilience strategy. /// /// The strategies the pipeline is composed of. /// Gets a value indicating whether the pipeline has telemetry enabled. diff --git a/src/Polly.Testing/README.md b/src/Polly.Testing/README.md index 7423de748b..4b4b081f88 100644 --- a/src/Polly.Testing/README.md +++ b/src/Polly.Testing/README.md @@ -1,3 +1,28 @@ # About Polly.Testing +This package exposes API and utilities that can be used to assert the composition of resilience strategies. +``` csharp +// Build your resilience strategy. +ResilienceStrategy strategy = new ResilienceStrategyBuilder() + .AddRetry(new RetryStrategyOptions + { + RetryCount = 4 + }) + .AddTimeout(TimeSpan.FromSeconds(1)) + .ConfigureTelemetry(NullLoggerFactory.Instance) + .Build(); + +// Retrieve inner strategies. +InnerStrategiesDescriptor descriptor = strategy.GetInnerStrategies(); + +// Assert the composition. +Assert.True(descriptor.HasTelemetry); +Assert.Equal(2, descriptor.Strategies.Count); + +var retryOptions = Assert.IsType(descriptor.Strategies[0]); +Assert.Equal(4, retryOptions.RetryCount); + +var timeoutOptions = Assert.IsType(descriptor.Strategies[0]); +Assert.Equal(TimeSpan.FromSeconds(1), timeoutOptions.Timeout); +``` From 197c2adde29da7f60387c5e568d9e75d149b7233 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 14:03:02 +0200 Subject: [PATCH 06/10] Update the build --- .github/workflows/build.yml | 2 +- build.cake | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3cd86dbe6a..6310531a85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 with: - files: ./artifacts/coverage-reports/Polly.Core.Tests/Cobertura.xml,./artifacts/coverage-reports/Polly.Specs/Cobertura.xml,./artifacts/coverage-reports/Polly.RateLimiting.Tests/Cobertura.xml,./artifacts/coverage-reports/Polly.Extensions.Tests/Cobertura.xml, + files: ./artifacts/coverage-reports/Polly.Core.Tests/Cobertura.xml,./artifacts/coverage-reports/Polly.Specs/Cobertura.xml,./artifacts/coverage-reports/Polly.RateLimiting.Tests/Cobertura.xml,./artifacts/coverage-reports/Polly.Extensions.Tests/Cobertura.xml,./artifacts/coverage-reports/Polly.Testing.Tests/Cobertura.xml, flags: ${{ matrix.os_name }} - name: Upload Mutation Report diff --git a/build.cake b/build.cake index 48abc0bf9a..c029e84de3 100644 --- a/build.cake +++ b/build.cake @@ -170,6 +170,7 @@ Task("__RunMutationTests") TestProject(File("../src/Polly.Core/Polly.Core.csproj"), File("./Polly.Core.Tests/Polly.Core.Tests.csproj"), "Polly.Core.csproj"); TestProject(File("../src/Polly.RateLimiting/Polly.RateLimiting.csproj"), File("./Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj"), "Polly.RateLimiting.csproj"); TestProject(File("../src/Polly.Extensions/Polly.Extensions.csproj"), File("./Polly.Extensions.Tests/Polly.Extensions.Tests.csproj"), "Polly.Extensions.csproj"); + TestProject(File("../src/Polly.Testing/Polly.Testing.csproj"), File("./Polly.Testing.Tests/Polly.Testing.Tests.csproj"), "Polly.Testing.csproj"); TestProject(File("../src/Polly/Polly.csproj"), File("./Polly.Specs/Polly.Specs.csproj"), "Polly.csproj"); context.Environment.WorkingDirectory = oldDirectory; @@ -221,6 +222,7 @@ Task("__CreateNuGetPackages") System.IO.Path.Combine(srcDir, "Polly", "Polly.csproj"), System.IO.Path.Combine(srcDir, "Polly.RateLimiting", "Polly.RateLimiting.csproj"), System.IO.Path.Combine(srcDir, "Polly.Extensions", "Polly.Extensions.csproj"), + System.IO.Path.Combine(srcDir, "Polly.Testing", "Polly.Testing.csproj"), }; Information("Building NuGet packages"); From 04836a646d50f5669cc909671297c1b84c00bb1c Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 14:04:03 +0200 Subject: [PATCH 07/10] PR comments --- src/Polly.Testing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Polly.Testing/README.md b/src/Polly.Testing/README.md index 4b4b081f88..fa1b42f68d 100644 --- a/src/Polly.Testing/README.md +++ b/src/Polly.Testing/README.md @@ -1,6 +1,6 @@ # About Polly.Testing -This package exposes API and utilities that can be used to assert the composition of resilience strategies. +This package exposes APIs and utilities that can be used to assert on the composition of resilience strategies. ``` csharp // Build your resilience strategy. From 6af3c08c64131c57839a16a2f7664bab486b1e96 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 14:33:01 +0200 Subject: [PATCH 08/10] Kill mutants --- test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs index e700a813d2..9f982c9423 100644 --- a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs +++ b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs @@ -36,6 +36,7 @@ public void GetInnerStrategies_Ok() // assert descriptor.HasTelemetry.Should().BeTrue(); + descriptor.IsReloadable.Should().BeFalse(); descriptor.Strategies.Should().HaveCount(7); descriptor.Strategies[0].Options.Should().BeOfType>(); descriptor.Strategies[1].Options.Should().BeOfType>(); @@ -68,8 +69,8 @@ public void GetInnerStrategies_Reloadable_Ok() var descriptor = strategy.GetInnerStrategies(); // assert - descriptor.IsReloadable.Should().BeTrue(); descriptor.HasTelemetry.Should().BeFalse(); + descriptor.IsReloadable.Should().BeTrue(); descriptor.Strategies.Should().HaveCount(2); descriptor.Strategies[0].Options.Should().BeOfType(); descriptor.Strategies[1].StrategyType.Should().Be(typeof(CustomStrategy)); From 5b8fa65262fa92aea0eacdb73aea6103d8918e66 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 14:42:55 +0200 Subject: [PATCH 09/10] Add new test --- .../ResilienceStrategyExtensionsTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs index 9f982c9423..f4ba0477b7 100644 --- a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs +++ b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs @@ -52,6 +52,24 @@ public void GetInnerStrategies_Ok() descriptor.Strategies[6].StrategyType.Should().Be(typeof(CustomStrategy)); } + [Fact] + public void GetInnerStrategies_SingleStrategy_Ok() + { + // arrange + var strategy = new ResilienceStrategyBuilder() + .AddTimeout(TimeSpan.FromSeconds(1)) + .Build(); + + // act + var descriptor = strategy.GetInnerStrategies(); + + // assert + descriptor.HasTelemetry.Should().BeFalse(); + descriptor.IsReloadable.Should().BeFalse(); + descriptor.Strategies.Should().HaveCount(1); + descriptor.Strategies[0].Options.Should().BeOfType(); + } + [Fact] public void GetInnerStrategies_Reloadable_Ok() { From adb42c24c5384fa1f22e9951c895e016ab54b2e1 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 11 Jul 2023 15:16:06 +0200 Subject: [PATCH 10/10] cleanup --- src/Polly.Testing/ResilienceStrategyExtensions.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Polly.Testing/ResilienceStrategyExtensions.cs b/src/Polly.Testing/ResilienceStrategyExtensions.cs index 6761a3c4d1..bf8ca6a1c8 100644 --- a/src/Polly.Testing/ResilienceStrategyExtensions.cs +++ b/src/Polly.Testing/ResilienceStrategyExtensions.cs @@ -7,6 +7,8 @@ namespace Polly.Testing; /// public static class ResilienceStrategyExtensions { + private const string TelemetryResilienceStrategy = "Polly.Extensions.Telemetry.TelemetryResilienceStrategy"; + /// /// Gets the inner strategies the is composed of. /// @@ -38,11 +40,11 @@ public static InnerStrategiesDescriptor GetInnerStrategies(this ResilienceStrate return new InnerStrategiesDescriptor( innerStrategies.Where(s => !ShouldSkip(s.StrategyType)).ToList().AsReadOnly(), - HasTelemetry: innerStrategies.Exists(s => s.StrategyType.FullName == "Polly.Extensions.Telemetry.TelemetryResilienceStrategy"), + HasTelemetry: innerStrategies.Exists(s => s.StrategyType.FullName == TelemetryResilienceStrategy), IsReloadable: innerStrategies.Exists(s => s.StrategyType == typeof(ReloadableResilienceStrategy))); } - private static bool ShouldSkip(Type type) => type == typeof(ReloadableResilienceStrategy) || type.FullName == "Polly.Extensions.Telemetry.TelemetryResilienceStrategy"; + private static bool ShouldSkip(Type type) => type == typeof(ReloadableResilienceStrategy) || type.FullName == TelemetryResilienceStrategy; private static void ExpandStrategies(this ResilienceStrategy strategy, List strategies) {