From 4b93c7fd6df5f8a373a72b9854665630f2acae5a Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 10 Aug 2023 11:28:36 +0200 Subject: [PATCH 1/6] Introduce `NonReactiveResilienceStrategy` --- .../CompositeStrategyBuilderExtensions.cs | 7 +- .../NonReactiveResilienceStrategy.cs | 36 +++++++++++ src/Polly.Core/NullResilienceStrategy.cs | 2 +- src/Polly.Core/PublicAPI.Unshipped.txt | 9 +-- src/Polly.Core/ResilienceStrategy.cs | 34 ++-------- .../Timeout/TimeoutResilienceStrategy.cs | 2 +- .../Utils/CompositeResilienceStrategy.cs | 4 +- .../NonReactiveResilienceStrategyBridge.cs | 14 ++++ .../Utils/ReactiveResilienceStrategyBridge.cs | 2 +- .../Utils/ReloadableResilienceStrategy.cs | 2 +- .../RateLimiterResilienceStrategy.cs | 2 +- .../ResilienceStrategyExtensions.cs | 5 ++ .../CompositeStrategyBuilderTests.cs | 38 +++++------ .../GenericCompositeStrategyBuilderTests.cs | 2 +- .../ResilienceStrategyProviderTests.cs | 2 +- .../ResilienceStrategyRegistryTests.cs | 21 +++--- .../ResilienceStrategyTTests.Async.cs | 2 +- .../ResilienceStrategyTTests.Sync.cs | 2 +- .../ResilienceStrategyTests.Async.cs | 4 +- .../ResilienceStrategyTests.AsyncT.cs | 4 +- .../ResilienceStrategyTests.Sync.cs | 4 +- .../ResilienceStrategyTests.SyncT.cs | 4 +- .../ResilienceStrategyTests.cs | 4 +- ...CompositeStrategyBuilderExtensionsTests.cs | 6 +- .../Timeout/TimeoutResilienceStrategyTests.cs | 2 +- .../Utils/CompositeResilienceStrategyTests.cs | 38 +++++------ .../ReloadableResilienceStrategyTests.cs | 10 +-- .../Polly.RateLimiting.Tests.csproj | 1 + ...CompositeStrategyBuilderExtensionsTests.cs | 64 +++++-------------- .../RateLimiterResilienceStrategyTests.cs | 15 ++--- .../NonReactiveStrategyExtensions.cs | 14 ++++ .../TestResilienceStrategy.TResult.cs | 2 +- .../Polly.TestUtils/TestResilienceStrategy.cs | 2 +- 33 files changed, 190 insertions(+), 170 deletions(-) create mode 100644 src/Polly.Core/NonReactiveResilienceStrategy.cs create mode 100644 src/Polly.Core/Utils/NonReactiveResilienceStrategyBridge.cs create mode 100644 test/Polly.TestUtils/NonReactiveStrategyExtensions.cs diff --git a/src/Polly.Core/CompositeStrategyBuilderExtensions.cs b/src/Polly.Core/CompositeStrategyBuilderExtensions.cs index a55625d9c6f..0417afcb0a5 100644 --- a/src/Polly.Core/CompositeStrategyBuilderExtensions.cs +++ b/src/Polly.Core/CompositeStrategyBuilderExtensions.cs @@ -27,7 +27,8 @@ public static TBuilder AddStrategy(this TBuilder builder, ResilienceSt Guard.NotNull(builder); Guard.NotNull(strategy); - return builder.AddStrategy(_ => strategy, EmptyOptions.Instance); + builder.AddStrategyCore(_ => strategy, EmptyOptions.Instance); + return builder; } /// @@ -59,14 +60,14 @@ public static CompositeStrategyBuilder AddStrategy(this Compos /// Thrown when this builder was already used to create a strategy. The builder cannot be modified after it has been used. /// Thrown when is invalid. [RequiresUnreferencedCode(Constants.OptionsValidation)] - public static TBuilder AddStrategy(this TBuilder builder, Func factory, ResilienceStrategyOptions options) + public static TBuilder AddStrategy(this TBuilder builder, Func factory, ResilienceStrategyOptions options) where TBuilder : CompositeStrategyBuilderBase { Guard.NotNull(builder); Guard.NotNull(factory); Guard.NotNull(options); - builder.AddStrategyCore(factory, options); + builder.AddStrategyCore(context => new NonReactiveResilienceStrategyBridge(factory(context)), options); return builder; } diff --git a/src/Polly.Core/NonReactiveResilienceStrategy.cs b/src/Polly.Core/NonReactiveResilienceStrategy.cs new file mode 100644 index 00000000000..1de2f132967 --- /dev/null +++ b/src/Polly.Core/NonReactiveResilienceStrategy.cs @@ -0,0 +1,36 @@ +namespace Polly; + +/// +/// This base class for all non-reactive resilience strategies. +/// +public abstract class NonReactiveResilienceStrategy +{ + /// + /// An implementation of resilience strategy that executes the specified . + /// + /// The type of result returned by the callback. + /// The type of state associated with the callback. + /// The user-provided callback. + /// The context associated with the callback. + /// The state associated with the callback. + /// + /// An instance of a pending for asynchronous executions or a completed task for synchronous executions. + /// + /// + /// This method is called for both synchronous and asynchronous execution flows. + /// + /// You can use to determine whether is synchronous or asynchronous. + /// This is useful when the custom strategy behaves differently in each execution flow. In general, for most strategies, the implementation + /// is the same for both execution flows. + /// See for more details. + /// + /// + /// The provided callback never throws an exception. Instead, the exception is captured and converted to an . + /// Similarly, do not throw exceptions from your strategy implementation. Instead, return an exception instance as . + /// + /// + protected internal abstract ValueTask> ExecuteCore( + Func>> callback, + ResilienceContext context, + TState state); +} diff --git a/src/Polly.Core/NullResilienceStrategy.cs b/src/Polly.Core/NullResilienceStrategy.cs index 66513428cd8..65e32bebe13 100644 --- a/src/Polly.Core/NullResilienceStrategy.cs +++ b/src/Polly.Core/NullResilienceStrategy.cs @@ -15,7 +15,7 @@ private NullResilienceStrategy() } /// - protected internal override ValueTask> ExecuteCore( + internal override ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, TState state) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 742f6604a2c..8da2c7a3d08 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -1,9 +1,9 @@ -abstract Polly.ReactiveResilienceStrategy.ExecuteCore(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> +abstract Polly.NonReactiveResilienceStrategy.ExecuteCore(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> +abstract Polly.ReactiveResilienceStrategy.ExecuteCore(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> abstract Polly.Registry.ResilienceStrategyProvider.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool abstract Polly.Registry.ResilienceStrategyProvider.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool abstract Polly.ResilienceContextPool.Get(string? operationKey, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Polly.ResilienceContext! abstract Polly.ResilienceContextPool.Return(Polly.ResilienceContext! context) -> void -abstract Polly.ResilienceStrategy.ExecuteCore(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> override Polly.Outcome.ToString() -> string! override Polly.Registry.ResilienceStrategyRegistry.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool override Polly.Registry.ResilienceStrategyRegistry.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool @@ -147,6 +147,8 @@ Polly.Hedging.OnHedgingArguments.HasOutcome.get -> bool Polly.Hedging.OnHedgingArguments.OnHedgingArguments(int attemptNumber, bool hasOutcome, System.TimeSpan executionTime) -> void Polly.HedgingCompositeStrategyBuilderExtensions Polly.LegacySupport +Polly.NonReactiveResilienceStrategy +Polly.NonReactiveResilienceStrategy.NonReactiveResilienceStrategy() -> void Polly.NullResilienceStrategy Polly.NullResilienceStrategy Polly.Outcome @@ -262,7 +264,6 @@ Polly.ResilienceStrategy.ExecuteAsync(System.Func(System.Func! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask Polly.ResilienceStrategy.ExecuteAsync(System.Func! callback, TState state, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Polly.ResilienceStrategy.ExecuteOutcomeAsync(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> -Polly.ResilienceStrategy.ResilienceStrategy() -> void Polly.ResilienceStrategy Polly.ResilienceStrategy.Execute(System.Func! callback, Polly.ResilienceContext! context, TState state) -> TResult Polly.ResilienceStrategy.Execute(System.Func! callback, TState state, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> TResult @@ -389,7 +390,7 @@ static Polly.CircuitBreakerCompositeStrategyBuilderExtensions.AddCircuitBreaker( static Polly.CircuitBreakerCompositeStrategyBuilderExtensions.AddCircuitBreaker(this Polly.CompositeStrategyBuilder! builder, Polly.CircuitBreaker.CircuitBreakerStrategyOptions! options) -> Polly.CompositeStrategyBuilder! static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this Polly.CompositeStrategyBuilder! builder, System.Func!>! factory, Polly.ResilienceStrategyOptions! options) -> Polly.CompositeStrategyBuilder! static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this TBuilder! builder, Polly.ResilienceStrategy! strategy) -> TBuilder! -static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this TBuilder! builder, System.Func! factory, Polly.ResilienceStrategyOptions! options) -> TBuilder! +static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this TBuilder! builder, System.Func! factory, Polly.ResilienceStrategyOptions! options) -> TBuilder! static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this Polly.CompositeStrategyBuilder! builder, Polly.ResilienceStrategy! strategy) -> Polly.CompositeStrategyBuilder! static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this Polly.CompositeStrategyBuilder! builder, System.Func!>! factory, Polly.ResilienceStrategyOptions! options) -> Polly.CompositeStrategyBuilder! static Polly.FallbackCompositeStrategyBuilderExtensions.AddFallback(this Polly.CompositeStrategyBuilder! builder, Polly.Fallback.FallbackStrategyOptions! options) -> Polly.CompositeStrategyBuilder! diff --git a/src/Polly.Core/ResilienceStrategy.cs b/src/Polly.Core/ResilienceStrategy.cs index 2ce226716a6..5989b5b865d 100644 --- a/src/Polly.Core/ResilienceStrategy.cs +++ b/src/Polly.Core/ResilienceStrategy.cs @@ -1,7 +1,5 @@ namespace Polly; -#pragma warning disable CA1031 // Do not catch general exception types - /// /// Resilience strategy is used to execute the user-provided callbacks. /// @@ -9,37 +7,17 @@ namespace Polly; /// Resilience strategy supports various types of callbacks and provides a unified way to execute them. /// This includes overloads for synchronous and asynchronous callbacks, generic and non-generic callbacks. /// -public abstract partial class ResilienceStrategy +public partial class ResilienceStrategy { internal static ResilienceContextPool Pool => ResilienceContextPool.Shared; internal ResilienceStrategyOptions? Options { get; set; } - /// - /// An implementation of resilience strategy that executes the specified . - /// - /// The type of result returned by the callback. - /// The type of state associated with the callback. - /// The user-provided callback. - /// The context associated with the callback. - /// The state associated with the callback. - /// - /// An instance of a pending for asynchronous executions or a completed task for synchronous executions. - /// - /// - /// This method is called for both synchronous and asynchronous execution flows. - /// - /// You can use to determine whether is synchronous or asynchronous. - /// This is useful when the custom strategy behaves differently in each execution flow. In general, for most strategies, the implementation - /// is the same for both execution flows. - /// See for more details. - /// - /// - /// The provided callback never throws an exception. Instead, the exception is captured and converted to an . - /// Similarly, do not throw exceptions from your strategy implementation. Instead, return an exception instance as . - /// - /// - protected internal abstract ValueTask> ExecuteCore( + internal ResilienceStrategy() + { + } + + internal abstract ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, TState state); diff --git a/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs b/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs index db8975d8bed..d0252768a62 100644 --- a/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs +++ b/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs @@ -2,7 +2,7 @@ namespace Polly.Timeout; -internal sealed class TimeoutResilienceStrategy : ResilienceStrategy +internal sealed class TimeoutResilienceStrategy : NonReactiveResilienceStrategy { private readonly ResilienceStrategyTelemetry _telemetry; private readonly CancellationTokenSourcePool _cancellationTokenSourcePool; diff --git a/src/Polly.Core/Utils/CompositeResilienceStrategy.cs b/src/Polly.Core/Utils/CompositeResilienceStrategy.cs index b6fe104994b..d620791ea8e 100644 --- a/src/Polly.Core/Utils/CompositeResilienceStrategy.cs +++ b/src/Polly.Core/Utils/CompositeResilienceStrategy.cs @@ -55,7 +55,7 @@ private CompositeResilienceStrategy(ResilienceStrategy first, IReadOnlyList Strategies { get; } - protected internal override ValueTask> ExecuteCore( + internal override ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, TState state) { @@ -78,7 +78,7 @@ private sealed class DelegatingResilienceStrategy : ResilienceStrategy public ResilienceStrategy? Next { get; set; } - protected internal override ValueTask> ExecuteCore( + internal override ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, TState state) diff --git a/src/Polly.Core/Utils/NonReactiveResilienceStrategyBridge.cs b/src/Polly.Core/Utils/NonReactiveResilienceStrategyBridge.cs new file mode 100644 index 00000000000..82db403244d --- /dev/null +++ b/src/Polly.Core/Utils/NonReactiveResilienceStrategyBridge.cs @@ -0,0 +1,14 @@ +namespace Polly.Utils; + +[DebuggerDisplay("{Strategy}")] +internal sealed class NonReactiveResilienceStrategyBridge : ResilienceStrategy +{ + public NonReactiveResilienceStrategyBridge(NonReactiveResilienceStrategy strategy) => Strategy = strategy; + + public NonReactiveResilienceStrategy Strategy { get; } + + internal override ValueTask> ExecuteCore( + Func>> callback, + ResilienceContext context, + TState state) => Strategy.ExecuteCore(callback, context, state); +} diff --git a/src/Polly.Core/Utils/ReactiveResilienceStrategyBridge.cs b/src/Polly.Core/Utils/ReactiveResilienceStrategyBridge.cs index de38ad664df..c76d82fab4a 100644 --- a/src/Polly.Core/Utils/ReactiveResilienceStrategyBridge.cs +++ b/src/Polly.Core/Utils/ReactiveResilienceStrategyBridge.cs @@ -7,7 +7,7 @@ internal sealed class ReactiveResilienceStrategyBridge : ResilienceStrategy public ReactiveResilienceStrategy Strategy { get; } - protected internal override ValueTask> ExecuteCore( + internal override ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, TState state) diff --git a/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs b/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs index dc33a17766a..90dcd420582 100644 --- a/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs +++ b/src/Polly.Core/Utils/ReloadableResilienceStrategy.cs @@ -30,7 +30,7 @@ public ReloadableResilienceStrategy( public ResilienceStrategy Strategy { get; private set; } - protected internal override ValueTask> ExecuteCore( + internal override ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, TState state) diff --git a/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs b/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs index 0508d2c9160..e87120f188e 100644 --- a/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs +++ b/src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs @@ -3,7 +3,7 @@ namespace Polly.RateLimiting; -internal sealed class RateLimiterResilienceStrategy : ResilienceStrategy +internal sealed class RateLimiterResilienceStrategy : NonReactiveResilienceStrategy { private readonly ResilienceStrategyTelemetry _telemetry; diff --git a/src/Polly.Testing/ResilienceStrategyExtensions.cs b/src/Polly.Testing/ResilienceStrategyExtensions.cs index e94b161bfa4..beaa77ee08b 100644 --- a/src/Polly.Testing/ResilienceStrategyExtensions.cs +++ b/src/Polly.Testing/ResilienceStrategyExtensions.cs @@ -56,6 +56,11 @@ private static Type GetStrategyType(ResilienceStrategy strategy) return bridge.Strategy.GetType(); } + if (strategy is NonReactiveResilienceStrategyBridge bridge2) + { + return bridge2.Strategy.GetType(); + } + return strategy.GetType(); } diff --git a/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs b/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs index 2ef3d786dc6..8856e8ed167 100644 --- a/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs +++ b/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs @@ -54,14 +54,14 @@ public void AddStrategy_Single_Ok() After = (_, _) => executions.Add(3), }; - builder.AddStrategy(first); + builder.AddStrategy(first.AsStrategy()); // act var strategy = builder.Build(); // assert strategy.Execute(_ => executions.Add(2)); - strategy.Should().BeOfType(); + ((NonReactiveResilienceStrategyBridge)strategy).Strategy.Should().BeOfType(); executions.Should().BeInAscendingOrder(); executions.Should().HaveCount(3); } @@ -88,9 +88,9 @@ public void AddStrategy_Multiple_Ok() After = (_, _) => executions.Add(5), }; - builder.AddStrategy(first); - builder.AddStrategy(second); - builder.AddStrategy(third); + builder.AddStrategy(first.AsStrategy()); + builder.AddStrategy(second.AsStrategy()); + builder.AddStrategy(third.AsStrategy()); // act var strategy = builder.Build(); @@ -164,9 +164,9 @@ public void AddStrategy_MultipleNonDelegating_Ok() After = () => executions.Add(5), }; - builder.AddStrategy(first); - builder.AddStrategy(second); - builder.AddStrategy(third); + builder.AddStrategy(first.AsStrategy()); + builder.AddStrategy(second.AsStrategy()); + builder.AddStrategy(third.AsStrategy()); // act var strategy = builder.Build(); @@ -218,7 +218,7 @@ public void AddStrategy_InvalidOptions_Throws() var builder = new CompositeStrategyBuilder(); builder - .Invoking(b => b.AddStrategy(_ => NullResilienceStrategy.Instance, new InvalidResilienceStrategyOptions())) + .Invoking(b => b.AddStrategy(_ => new TestResilienceStrategy(), new InvalidResilienceStrategyOptions())) .Should() .Throw() .WithMessage( @@ -236,7 +236,7 @@ public void AddStrategy_NullFactory_Throws() var builder = new CompositeStrategyBuilder(); builder - .Invoking(b => b.AddStrategy((Func)null!, new TestResilienceStrategyOptions())) + .Invoking(b => b.AddStrategy((Func)null!, new TestResilienceStrategyOptions())) .Should() .Throw() .And.ParamName @@ -268,14 +268,14 @@ public void AddStrategy_CombinePipelines_Ok() After = (_, _) => executions.Add(6), }; - var pipeline1 = new CompositeStrategyBuilder().AddStrategy(first).AddStrategy(second).Build(); + var pipeline1 = new CompositeStrategyBuilder().AddStrategy(first.AsStrategy()).AddStrategy(second.AsStrategy()).Build(); var third = new TestResilienceStrategy { Before = (_, _) => executions.Add(3), After = (_, _) => executions.Add(5), }; - var pipeline2 = new CompositeStrategyBuilder().AddStrategy(third).Build(); + var pipeline2 = new CompositeStrategyBuilder().AddStrategy(third.AsStrategy()).Build(); // act var strategy = new CompositeStrategyBuilder().AddStrategy(pipeline1).AddStrategy(pipeline2).Build(); @@ -341,13 +341,13 @@ public void BuildStrategy_EnsureCorrectContext() public void Build_OnCreatingStrategy_EnsureRespected() { // arrange - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var builder = new CompositeStrategyBuilder { OnCreatingStrategy = strategies => { strategies.Should().ContainSingle(s => s == strategy); - strategies.Insert(0, new TestResilienceStrategy()); + strategies.Insert(0, new TestResilienceStrategy().AsStrategy()); } }; @@ -372,19 +372,19 @@ public void ExecuteAsync_EnsureReceivedCallbackExecutesNextStrategy() { Before = (_, _) => executions.Add("first-start"), After = (_, _) => executions.Add("first-end"), - }; + }.AsStrategy(); var second = new ExecuteCallbackTwiceStrategy { Before = () => executions.Add("second-start"), After = () => executions.Add("second-end"), - }; + }.AsStrategy(); var third = new TestResilienceStrategy { Before = (_, _) => executions.Add("third-start"), After = (_, _) => executions.Add("third-end"), - }; + }.AsStrategy(); var strategy = new CompositeStrategyBuilder().AddStrategy(first).AddStrategy(second).AddStrategy(third).Build(); @@ -409,7 +409,7 @@ public void ExecuteAsync_EnsureReceivedCallbackExecutesNextStrategy() .BeTrue(); } - private class Strategy : ResilienceStrategy + private class Strategy : NonReactiveResilienceStrategy { public Action? Before { get; set; } @@ -432,7 +432,7 @@ protected internal override async ValueTask> ExecuteCore(new TestResilienceStrategy()); + var testStrategy = new ResilienceStrategy(new TestResilienceStrategy().AsStrategy()); _builder.AddStrategy(testStrategy); // act diff --git a/test/Polly.Core.Tests/Registry/ResilienceStrategyProviderTests.cs b/test/Polly.Core.Tests/Registry/ResilienceStrategyProviderTests.cs index 74525e49288..d5abfe3aac2 100644 --- a/test/Polly.Core.Tests/Registry/ResilienceStrategyProviderTests.cs +++ b/test/Polly.Core.Tests/Registry/ResilienceStrategyProviderTests.cs @@ -29,7 +29,7 @@ public void Get_GenericDoesNotExist_Throws() [Fact] public void Get_Exist_Ok() { - var provider = new Provider { Strategy = new TestResilienceStrategy() }; + var provider = new Provider { Strategy = new TestResilienceStrategy().AsStrategy() }; provider.GetStrategy("exists").Should().Be(provider.Strategy); } diff --git a/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs b/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs index 69fc6477b4c..ae18cef3d75 100644 --- a/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs +++ b/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs @@ -2,6 +2,7 @@ using Polly.Registry; using Polly.Retry; using Polly.Timeout; +using Polly.Utils; namespace Polly.Core.Tests.Registry; @@ -44,9 +45,9 @@ public void Clear_Ok() registry.TryAddBuilder("C", (b, _) => b.AddStrategy(new TestResilienceStrategy())); - registry.TryAddStrategy("A", new TestResilienceStrategy()); - registry.TryAddStrategy("B", new TestResilienceStrategy()); - registry.TryAddStrategy("C", new TestResilienceStrategy()); + registry.TryAddStrategy("A", new TestResilienceStrategy().AsStrategy()); + registry.TryAddStrategy("B", new TestResilienceStrategy().AsStrategy()); + registry.TryAddStrategy("C", new TestResilienceStrategy().AsStrategy()); registry.ClearStrategies(); @@ -78,8 +79,8 @@ public void Remove_Ok() { var registry = new ResilienceStrategyRegistry(); - registry.TryAddStrategy("A", new TestResilienceStrategy()); - registry.TryAddStrategy("B", new TestResilienceStrategy()); + registry.TryAddStrategy("A", new TestResilienceStrategy().AsStrategy()); + registry.TryAddStrategy("B", new TestResilienceStrategy().AsStrategy()); registry.RemoveStrategy("A").Should().BeTrue(); registry.RemoveStrategy("A").Should().BeFalse(); @@ -302,7 +303,7 @@ public void TryGet_GenericNoBuilder_Null() [Fact] public void TryGet_ExplicitStrategyAdded_Ok() { - var expectedStrategy = new TestResilienceStrategy(); + var expectedStrategy = new TestResilienceStrategy().AsStrategy(); var registry = CreateRegistry(); var key = StrategyId.Create("A", "Instance"); registry.TryAddStrategy(key, expectedStrategy).Should().BeTrue(); @@ -328,12 +329,12 @@ public void TryGet_GenericExplicitStrategyAdded_Ok() [Fact] public void TryAdd_Twice_SecondNotAdded() { - var expectedStrategy = new TestResilienceStrategy(); + var expectedStrategy = new TestResilienceStrategy().AsStrategy(); var registry = CreateRegistry(); var key = StrategyId.Create("A", "Instance"); registry.TryAddStrategy(key, expectedStrategy).Should().BeTrue(); - registry.TryAddStrategy(key, new TestResilienceStrategy()).Should().BeFalse(); + registry.TryAddStrategy(key, new TestResilienceStrategy().AsStrategy()).Should().BeFalse(); registry.TryGetStrategy(key, out var strategy).Should().BeTrue(); strategy.Should().BeSameAs(expectedStrategy); @@ -435,7 +436,7 @@ public void GetOrAddStrategy_Ok() var strategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; }); var otherStrategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; }); - strategy.Should().BeOfType(); + ((NonReactiveResilienceStrategyBridge)strategy).Strategy.Should().BeOfType(); strategy.Should().BeSameAs(otherStrategy); called.Should().Be(1); } @@ -450,7 +451,7 @@ public void GetOrAddStrategy_Generic_Ok() var strategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; }); var otherStrategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; }); - strategy.Strategy.Should().BeOfType(); + ((NonReactiveResilienceStrategyBridge)strategy.Strategy).Strategy.Should().BeOfType(); strategy.Should().BeSameAs(otherStrategy); } diff --git a/test/Polly.Core.Tests/ResilienceStrategyTTests.Async.cs b/test/Polly.Core.Tests/ResilienceStrategyTTests.Async.cs index c586252c469..e1a3f88d278 100644 --- a/test/Polly.Core.Tests/ResilienceStrategyTTests.Async.cs +++ b/test/Polly.Core.Tests/ResilienceStrategyTTests.Async.cs @@ -71,7 +71,7 @@ public async Task ExecuteAsync_GenericStrategy_Ok(Func> execut c.IsSynchronous.Should().BeTrue(); c.ResultType.Should().Be(typeof(string)); }, - }); + }.AsStrategy()); execute(strategy); } diff --git a/test/Polly.Core.Tests/ResilienceStrategyTests.Async.cs b/test/Polly.Core.Tests/ResilienceStrategyTests.Async.cs index 7f360bbaec3..190fb5b2a87 100644 --- a/test/Polly.Core.Tests/ResilienceStrategyTests.Async.cs +++ b/test/Polly.Core.Tests/ResilienceStrategyTests.Async.cs @@ -78,7 +78,7 @@ public async Task ExecuteAsync_Ok(ExecuteParameters parameters) context = c; parameters.AssertContext(c); }, - }; + }.AsStrategy(); var result = await parameters.Execute(strategy); @@ -96,7 +96,7 @@ public async Task ExecuteAsync_EnsureCallStackPreserved() static async ValueTask AssertStackTrace(Func execute) { - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var error = await strategy .Invoking(s => diff --git a/test/Polly.Core.Tests/ResilienceStrategyTests.AsyncT.cs b/test/Polly.Core.Tests/ResilienceStrategyTests.AsyncT.cs index edf8a4724eb..cca7b3c2b7c 100644 --- a/test/Polly.Core.Tests/ResilienceStrategyTests.AsyncT.cs +++ b/test/Polly.Core.Tests/ResilienceStrategyTests.AsyncT.cs @@ -82,7 +82,7 @@ public async Task ExecuteAsyncT_Ok(ExecuteParameters parameters) }, }; - var result = await parameters.Execute(strategy); + var result = await parameters.Execute(strategy.AsStrategy()); parameters.AssertContextAfter(context!); parameters.AssertResult(result); @@ -98,7 +98,7 @@ public async Task ExecuteAsync_T_EnsureCallStackPreserved() static async ValueTask AssertStackTrace(Func> execute) { - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var error = await strategy .Invoking(s => diff --git a/test/Polly.Core.Tests/ResilienceStrategyTests.Sync.cs b/test/Polly.Core.Tests/ResilienceStrategyTests.Sync.cs index 461638d4238..004d4638bc3 100644 --- a/test/Polly.Core.Tests/ResilienceStrategyTests.Sync.cs +++ b/test/Polly.Core.Tests/ResilienceStrategyTests.Sync.cs @@ -82,7 +82,7 @@ public async Task Execute_Ok(ExecuteParameters parameters) context = c; parameters.AssertContext(c); }, - }; + }.AsStrategy(); var result = await parameters.Execute(strategy); @@ -103,7 +103,7 @@ public void Execute_EnsureCallStackPreserved() static void AssertStackTrace(Action execute) { - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var error = strategy .Invoking(s => execute(s)) diff --git a/test/Polly.Core.Tests/ResilienceStrategyTests.SyncT.cs b/test/Polly.Core.Tests/ResilienceStrategyTests.SyncT.cs index 4cd89f7294e..670cc7600e7 100644 --- a/test/Polly.Core.Tests/ResilienceStrategyTests.SyncT.cs +++ b/test/Polly.Core.Tests/ResilienceStrategyTests.SyncT.cs @@ -84,7 +84,7 @@ public async Task ExecuteT_Ok(ExecuteParameters parameters) context = c; parameters.AssertContext(c); }, - }; + }.AsStrategy(); var result = await parameters.Execute(strategy); @@ -105,7 +105,7 @@ public void Execute_T_EnsureCallStackPreserved() static void AssertStackTrace(Func execute) { - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var error = strategy .Invoking(s => execute(s)) diff --git a/test/Polly.Core.Tests/ResilienceStrategyTests.cs b/test/Polly.Core.Tests/ResilienceStrategyTests.cs index a0036bc17a7..b0e867942d4 100644 --- a/test/Polly.Core.Tests/ResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/ResilienceStrategyTests.cs @@ -11,8 +11,8 @@ public void DebuggerProxy_Ok() { var pipeline = CompositeResilienceStrategy.Create(new[] { - new TestResilienceStrategy(), - new TestResilienceStrategy() + new TestResilienceStrategy().AsStrategy(), + new TestResilienceStrategy().AsStrategy() }); new CompositeResilienceStrategy.DebuggerProxy(pipeline).Strategies.Should().HaveCount(2); diff --git a/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs index c913b9b2129..1e61f5bc7ad 100644 --- a/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using Polly.Timeout; +using Polly.Utils; namespace Polly.Core.Tests.Timeout; @@ -31,7 +32,8 @@ internal void AddTimeout_Ok(TimeSpan timeout, Action(); configure(builder); - var strategy = builder.Build().Strategy.Should().BeOfType().Subject; + + var strategy = ((NonReactiveResilienceStrategyBridge)builder.Build().Strategy).Strategy.Should().BeOfType().Subject; assert(strategy); GetTimeout(strategy).Should().Be(timeout); @@ -42,7 +44,7 @@ public void AddTimeout_Options_Ok() { var strategy = new CompositeStrategyBuilder().AddTimeout(new TimeoutStrategyOptions()).Build(); - strategy.Should().BeOfType(); + ((NonReactiveResilienceStrategyBridge)strategy).Strategy.Should().BeOfType(); } [Fact] diff --git a/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs b/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs index 978bdfb199a..20f07e0684a 100644 --- a/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs @@ -234,5 +234,5 @@ await sut.ExecuteAsync(async token => private void SetTimeout(TimeSpan timeout) => _options.TimeoutGenerator = args => new ValueTask(timeout); - private TimeoutResilienceStrategy CreateSut() => new(_options, _timeProvider, _telemetry); + private ResilienceStrategy CreateSut() => new TimeoutResilienceStrategy(_options, _timeProvider, _telemetry).AsStrategy(); } diff --git a/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs b/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs index ac1c16d80a3..d35ebb67bef 100644 --- a/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs @@ -9,7 +9,7 @@ public void Create_ArgValidation() { Assert.Throws(() => CompositeResilienceStrategy.Create(null!)); Assert.Throws(() => CompositeResilienceStrategy.Create(Array.Empty())); - Assert.Throws(() => CompositeResilienceStrategy.Create(new ResilienceStrategy[] { new TestResilienceStrategy() })); + Assert.Throws(() => CompositeResilienceStrategy.Create(new[] { new TestResilienceStrategy().AsStrategy() })); Assert.Throws(() => CompositeResilienceStrategy.Create(new ResilienceStrategy[] { NullResilienceStrategy.Instance, @@ -20,11 +20,11 @@ public void Create_ArgValidation() [Fact] public void Create_EnsureOriginalStrategiesPreserved() { - var strategies = new ResilienceStrategy[] + var strategies = new[] { - new TestResilienceStrategy(), - new Strategy(), - new TestResilienceStrategy(), + new TestResilienceStrategy().AsStrategy(), + new Strategy().AsStrategy(), + new TestResilienceStrategy().AsStrategy(), }; var pipeline = CompositeResilienceStrategy.Create(strategies); @@ -40,10 +40,10 @@ public void Create_EnsureOriginalStrategiesPreserved() [Fact] public async Task Create_EnsureExceptionsNotWrapped() { - var strategies = new ResilienceStrategy[] + var strategies = new[] { - new Strategy(), - new Strategy(), + new Strategy().AsStrategy(), + new Strategy().AsStrategy(), }; var pipeline = CompositeResilienceStrategy.Create(strategies); @@ -56,11 +56,11 @@ await pipeline [Fact] public void Create_EnsurePipelineReusableAcrossDifferentPipelines() { - var strategies = new ResilienceStrategy[] + var strategies = new[] { - new TestResilienceStrategy(), - new Strategy(), - new TestResilienceStrategy(), + new TestResilienceStrategy().AsStrategy(), + new Strategy().AsStrategy(), + new TestResilienceStrategy().AsStrategy(), }; var pipeline = CompositeResilienceStrategy.Create(strategies); @@ -77,10 +77,10 @@ public async Task Create_Cancelled_EnsureNoExecution() { using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); - var strategies = new ResilienceStrategy[] + var strategies = new[] { - new TestResilienceStrategy(), - new TestResilienceStrategy(), + new TestResilienceStrategy().AsStrategy(), + new TestResilienceStrategy().AsStrategy(), }; var pipeline = CompositeResilienceStrategy.Create(strategies); @@ -96,10 +96,10 @@ public async Task Create_CancelledLater_EnsureNoExecution() { var executed = false; using var cancellation = new CancellationTokenSource(); - var strategies = new ResilienceStrategy[] + var strategies = new[] { - new TestResilienceStrategy { Before = (_, _) => { executed = true; cancellation.Cancel(); } }, - new TestResilienceStrategy(), + new TestResilienceStrategy { Before = (_, _) => { executed = true; cancellation.Cancel(); } }.AsStrategy(), + new TestResilienceStrategy().AsStrategy(), }; var pipeline = CompositeResilienceStrategy.Create(strategies); @@ -111,7 +111,7 @@ public async Task Create_CancelledLater_EnsureNoExecution() executed.Should().BeTrue(); } - private class Strategy : ResilienceStrategy + private class Strategy : NonReactiveResilienceStrategy { protected internal override async ValueTask> ExecuteCore( Func>> callback, diff --git a/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs b/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs index a5c22f99145..ff735890cd3 100644 --- a/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Utils/ReloadableResilienceStrategyTests.cs @@ -24,7 +24,7 @@ public ReloadableResilienceStrategyTests() [Fact] public void Ctor_Ok() { - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var sut = CreateSut(strategy); sut.Strategy.Should().Be(strategy); @@ -35,7 +35,7 @@ public void Ctor_Ok() [Fact] public void ChangeTriggered_StrategyReloaded() { - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var sut = CreateSut(strategy); for (var i = 0; i < 10; i++) @@ -54,7 +54,7 @@ public void ChangeTriggered_StrategyReloaded() [Fact] public void ChangeTriggered_FactoryError_LastStrategyUsedAndErrorReported() { - var strategy = new TestResilienceStrategy(); + var strategy = new TestResilienceStrategy().AsStrategy(); var sut = CreateSut(strategy, () => throw new InvalidOperationException()); _cancellationTokenSource.Cancel(); @@ -78,10 +78,10 @@ public void ChangeTriggered_FactoryError_LastStrategyUsedAndErrorReported() private ReloadableResilienceStrategy CreateSut(ResilienceStrategy? initial = null, Func? factory = null) { - factory ??= () => new TestResilienceStrategy(); + factory ??= () => new TestResilienceStrategy().AsStrategy(); return new( - initial ?? new TestResilienceStrategy(), + initial ?? new TestResilienceStrategy().AsStrategy(), () => _cancellationTokenSource.Token, factory, _telemetry); diff --git a/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj b/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj index eee95b966cc..2b2c7807796 100644 --- a/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj +++ b/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj @@ -14,5 +14,6 @@ + diff --git a/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs index c9d6689d4dc..875757ee90e 100644 --- a/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs +++ b/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.RateLimiting; using NSubstitute; +using Polly.Testing; namespace Polly.RateLimiting.Tests; @@ -11,7 +12,7 @@ public class RateLimiterCompositeStrategyBuilderExtensionsTests builder => { builder.AddConcurrencyLimiter(2, 2); - AssertConcurrencyLimiter(builder, hasEvents: false); + AssertRateLimiterStrategy(builder); }, builder => { @@ -22,13 +23,13 @@ public class RateLimiterCompositeStrategyBuilderExtensionsTests QueueLimit = 2 }); - AssertConcurrencyLimiter(builder, hasEvents: false); + AssertRateLimiterStrategy(builder); }, builder => { var expected = Substitute.For(); builder.AddRateLimiter(expected); - AssertRateLimiter(builder, hasEvents: false, limiter => limiter.Should().Be(expected)); + AssertRateLimiterStrategy(builder); } }; @@ -66,7 +67,7 @@ public void AddRateLimiter_AllExtensions_Ok() configure(builder); - GetResilienceStrategy(builder.Build()).Should().BeOfType(); + builder.Build().GetInnerStrategies().Strategies.Single().StrategyType.Should().Be(typeof(RateLimiterResilienceStrategy)); } } @@ -85,8 +86,10 @@ public void AddRateLimiter_Ok() RateLimiter = ResilienceRateLimiter.Create(limiter) }) .Build() + .GetInnerStrategies().Strategies.Single() + .StrategyType .Should() - .BeOfType(); + .Be(); } [Fact] @@ -125,51 +128,16 @@ public void AddRateLimiter_Options_Ok() { RateLimiter = ResilienceRateLimiter.Create(Substitute.For()) }) - .Build(); - - strategy.Should().BeOfType(); - } - - private static void AssertRateLimiter(CompositeStrategyBuilder builder, bool hasEvents, Action? assertLimiter = null) - { - var strategy = GetResilienceStrategy(builder.Build()); - strategy.Limiter.Should().NotBeNull(); - - if (hasEvents) - { - strategy.OnLeaseRejected.Should().NotBeNull(); - strategy - .OnLeaseRejected!(new OnRateLimiterRejectedArguments(ResilienceContextPool.Shared.Get(), Substitute.For(), null)) - .Preserve().GetAwaiter().GetResult(); - } - else - { - strategy.OnLeaseRejected.Should().BeNull(); - } - - assertLimiter?.Invoke(strategy.Limiter.Limiter!); - } - - private static void AssertConcurrencyLimiter(CompositeStrategyBuilder builder, bool hasEvents) - { - var strategy = GetResilienceStrategy(builder.Build()); - strategy.Limiter.Limiter.Should().BeOfType(); - - if (hasEvents) - { - strategy.OnLeaseRejected.Should().NotBeNull(); - strategy - .OnLeaseRejected!(new OnRateLimiterRejectedArguments(ResilienceContextPool.Shared.Get(), Substitute.For(), null)) - .Preserve().GetAwaiter().GetResult(); - } - else - { - strategy.OnLeaseRejected.Should().BeNull(); - } + .Build() + .GetInnerStrategies().Strategies + .Single() + .StrategyType + .Should() + .Be(); } - private static RateLimiterResilienceStrategy GetResilienceStrategy(ResilienceStrategy strategy) + private static void AssertRateLimiterStrategy(CompositeStrategyBuilder builder) { - return (RateLimiterResilienceStrategy)strategy.GetType().GetProperty("Strategy", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(strategy)!; + builder.Build().GetInnerStrategies().Strategies.Single().StrategyType.Should().Be(typeof(RateLimiterResilienceStrategy)); } } diff --git a/test/Polly.RateLimiting.Tests/RateLimiterResilienceStrategyTests.cs b/test/Polly.RateLimiting.Tests/RateLimiterResilienceStrategyTests.cs index 1dae568a25e..c65e9351b51 100644 --- a/test/Polly.RateLimiting.Tests/RateLimiterResilienceStrategyTests.cs +++ b/test/Polly.RateLimiting.Tests/RateLimiterResilienceStrategyTests.cs @@ -99,19 +99,18 @@ private void SetupLimiter(CancellationToken token) .Returns(result); } - private RateLimiterResilienceStrategy Create() + private ResilienceStrategy Create() { var builder = new CompositeStrategyBuilder { DiagnosticSource = _diagnosticSource }; - return (RateLimiterResilienceStrategy)builder - .AddRateLimiter(new RateLimiterStrategyOptions - { - RateLimiter = ResilienceRateLimiter.Create(_limiter), - OnRejected = _event - }) - .Build(); + return builder.AddRateLimiter(new RateLimiterStrategyOptions + { + RateLimiter = ResilienceRateLimiter.Create(_limiter), + OnRejected = _event + }) + .Build(); } } diff --git a/test/Polly.TestUtils/NonReactiveStrategyExtensions.cs b/test/Polly.TestUtils/NonReactiveStrategyExtensions.cs new file mode 100644 index 00000000000..c1c2f6786b9 --- /dev/null +++ b/test/Polly.TestUtils/NonReactiveStrategyExtensions.cs @@ -0,0 +1,14 @@ +using Polly.Utils; + +namespace Polly.TestUtils; + +public static class NonReactiveStrategyExtensions +{ + public static ResilienceStrategy AsStrategy(this NonReactiveResilienceStrategy strategy) => new NonReactiveResilienceStrategyBridge(strategy); + + public static TBuilder AddStrategy(this TBuilder builder, NonReactiveResilienceStrategy strategy) + where TBuilder : CompositeStrategyBuilderBase + { + return builder.AddStrategy(strategy.AsStrategy()); + } +} diff --git a/test/Polly.TestUtils/TestResilienceStrategy.TResult.cs b/test/Polly.TestUtils/TestResilienceStrategy.TResult.cs index 35a443a57ff..12ed008e15a 100644 --- a/test/Polly.TestUtils/TestResilienceStrategy.TResult.cs +++ b/test/Polly.TestUtils/TestResilienceStrategy.TResult.cs @@ -3,7 +3,7 @@ namespace Polly.TestUtils; public class TestResilienceStrategy : ResilienceStrategy { public TestResilienceStrategy() - : base(new TestResilienceStrategy()) + : base(new TestResilienceStrategy().AsStrategy()) { } } diff --git a/test/Polly.TestUtils/TestResilienceStrategy.cs b/test/Polly.TestUtils/TestResilienceStrategy.cs index 56629a3d7a9..f84f0201a86 100644 --- a/test/Polly.TestUtils/TestResilienceStrategy.cs +++ b/test/Polly.TestUtils/TestResilienceStrategy.cs @@ -1,6 +1,6 @@ namespace Polly.TestUtils; -public class TestResilienceStrategy : ResilienceStrategy +public class TestResilienceStrategy : NonReactiveResilienceStrategy { public Action? Before { get; set; } From a4d253468fd184f62ca70c4052fea23127b075de Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 10 Aug 2023 11:33:12 +0200 Subject: [PATCH 2/6] fixes --- src/Polly.Core/NonReactiveResilienceStrategy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Polly.Core/NonReactiveResilienceStrategy.cs b/src/Polly.Core/NonReactiveResilienceStrategy.cs index 1de2f132967..aa2e47dc587 100644 --- a/src/Polly.Core/NonReactiveResilienceStrategy.cs +++ b/src/Polly.Core/NonReactiveResilienceStrategy.cs @@ -1,7 +1,7 @@ namespace Polly; /// -/// This base class for all non-reactive resilience strategies. +/// Base class for all non-reactive resilience strategies. /// public abstract class NonReactiveResilienceStrategy { From 7a197429debc969e0ece15d07e7772dca2ab771a Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 10 Aug 2023 11:47:52 +0200 Subject: [PATCH 3/6] Fixes --- bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs | 2 +- .../Utils/EmptyResilienceStrategy.cs | 2 +- .../Utils/Helper.CircuitBreaker.cs | 4 ++-- .../Utils/Helper.StrategyPipeline.cs | 2 +- src/Polly.Core/NonReactiveResilienceStrategy.cs | 2 +- .../TelemetryCompositeStrategyBuilderExtensions.cs | 9 ++++++++- .../Telemetry/TelemetryResilienceStrategy.cs | 2 +- .../PollyServiceCollectionExtensionTests.cs | 2 +- ...uesTests.OnCircuitBreakWithServiceProvider_796.cs | 2 +- .../ReloadableResilienceStrategyTests.cs | 2 +- .../Telemetry/TelemetryResilienceStrategyTests.cs | 10 ++++++++-- .../ResilienceStrategyExtensionsTests.cs | 12 ++++++++---- 12 files changed, 34 insertions(+), 17 deletions(-) diff --git a/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs b/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs index f62e05293d2..b98f59d91c1 100644 --- a/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs +++ b/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs @@ -66,7 +66,7 @@ private ResilienceStrategy Build(CompositeStrategyBuilder builder) return builder.Build(); } - private class TelemetryEventStrategy : ResilienceStrategy + private class TelemetryEventStrategy : NonReactiveResilienceStrategy { private readonly ResilienceStrategyTelemetry _telemetry; diff --git a/bench/Polly.Core.Benchmarks/Utils/EmptyResilienceStrategy.cs b/bench/Polly.Core.Benchmarks/Utils/EmptyResilienceStrategy.cs index 69c4c9df0ad..3ae5f25b972 100644 --- a/bench/Polly.Core.Benchmarks/Utils/EmptyResilienceStrategy.cs +++ b/bench/Polly.Core.Benchmarks/Utils/EmptyResilienceStrategy.cs @@ -1,6 +1,6 @@ namespace Polly.Core.Benchmarks.Utils; -internal class EmptyResilienceStrategy : ResilienceStrategy +internal class EmptyResilienceStrategy : NonReactiveResilienceStrategy { protected override ValueTask> ExecuteCore( Func>> callback, diff --git a/bench/Polly.Core.Benchmarks/Utils/Helper.CircuitBreaker.cs b/bench/Polly.Core.Benchmarks/Utils/Helper.CircuitBreaker.cs index 43af5ba582b..de6d0340caf 100644 --- a/bench/Polly.Core.Benchmarks/Utils/Helper.CircuitBreaker.cs +++ b/bench/Polly.Core.Benchmarks/Utils/Helper.CircuitBreaker.cs @@ -17,7 +17,7 @@ public static object CreateOpenedCircuitBreaker(PollyVersion version, bool handl if (handleOutcome) { - builder.AddStrategy(new OutcomeHandlingStrategy()); + builder.AddStrategy(_ => new OutcomeHandlingStrategy(), new EmptyResilienceOptions()); } var strategy = builder.AddCircuitBreaker(options).Build(); @@ -64,7 +64,7 @@ public static object CreateCircuitBreaker(PollyVersion technology) }; } - private class OutcomeHandlingStrategy : ResilienceStrategy + private class OutcomeHandlingStrategy : NonReactiveResilienceStrategy { protected override async ValueTask> ExecuteCore( Func>> callback, diff --git a/bench/Polly.Core.Benchmarks/Utils/Helper.StrategyPipeline.cs b/bench/Polly.Core.Benchmarks/Utils/Helper.StrategyPipeline.cs index 8156caadd94..bc0d1b3c802 100644 --- a/bench/Polly.Core.Benchmarks/Utils/Helper.StrategyPipeline.cs +++ b/bench/Polly.Core.Benchmarks/Utils/Helper.StrategyPipeline.cs @@ -10,7 +10,7 @@ internal static partial class Helper { for (var i = 0; i < count; i++) { - builder.AddStrategy(new EmptyResilienceStrategy()); + builder.AddStrategy(_ => new EmptyResilienceStrategy(), new EmptyResilienceOptions()); } }), _ => throw new NotSupportedException() diff --git a/src/Polly.Core/NonReactiveResilienceStrategy.cs b/src/Polly.Core/NonReactiveResilienceStrategy.cs index aa2e47dc587..e5be19ad608 100644 --- a/src/Polly.Core/NonReactiveResilienceStrategy.cs +++ b/src/Polly.Core/NonReactiveResilienceStrategy.cs @@ -6,7 +6,7 @@ public abstract class NonReactiveResilienceStrategy { /// - /// An implementation of resilience strategy that executes the specified . + /// An implementation of non-reactive resilience strategy that executes the specified . /// /// The type of result returned by the callback. /// The type of state associated with the callback. diff --git a/src/Polly.Extensions/Telemetry/TelemetryCompositeStrategyBuilderExtensions.cs b/src/Polly.Extensions/Telemetry/TelemetryCompositeStrategyBuilderExtensions.cs index 10cf5f21422..a501033f189 100644 --- a/src/Polly.Extensions/Telemetry/TelemetryCompositeStrategyBuilderExtensions.cs +++ b/src/Polly.Extensions/Telemetry/TelemetryCompositeStrategyBuilderExtensions.cs @@ -44,6 +44,7 @@ public static TBuilder ConfigureTelemetry(this TBuilder builder, ILogg /// /// Thrown when or is . [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TelemetryOptions))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TelemetryStrategyOptions))] public static TBuilder ConfigureTelemetry(this TBuilder builder, TelemetryOptions options) where TBuilder : CompositeStrategyBuilderBase { @@ -62,9 +63,15 @@ public static TBuilder ConfigureTelemetry(this TBuilder builder, Telem options.ResultFormatter, options.Enrichers.ToList()); - strategies.Insert(0, telemetryStrategy); +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + strategies.Insert(0, new CompositeStrategyBuilder().AddStrategy(_ => telemetryStrategy, new TelemetryStrategyOptions()).Build()); +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code }; return builder; } + + private sealed class TelemetryStrategyOptions : ResilienceStrategyOptions + { + } } diff --git a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs index c52f6867ba1..2c090a4614e 100644 --- a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs +++ b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs @@ -3,7 +3,7 @@ namespace Polly.Telemetry; -internal sealed class TelemetryResilienceStrategy : ResilienceStrategy +internal sealed class TelemetryResilienceStrategy : NonReactiveResilienceStrategy { private readonly TimeProvider _timeProvider; private readonly string? _builderName; diff --git a/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs b/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs index 07a08d349f4..88d082229e5 100644 --- a/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs +++ b/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs @@ -304,7 +304,7 @@ private ResilienceStrategyProvider CreateProvider() return _services.BuildServiceProvider().GetRequiredService>(); } - private class TestStrategy : ResilienceStrategy + private class TestStrategy : NonReactiveResilienceStrategy { protected override ValueTask> ExecuteCore( Func>> callback, diff --git a/test/Polly.Extensions.Tests/Issues/IssuesTests.OnCircuitBreakWithServiceProvider_796.cs b/test/Polly.Extensions.Tests/Issues/IssuesTests.OnCircuitBreakWithServiceProvider_796.cs index 54579ada880..292002536dd 100644 --- a/test/Polly.Extensions.Tests/Issues/IssuesTests.OnCircuitBreakWithServiceProvider_796.cs +++ b/test/Polly.Extensions.Tests/Issues/IssuesTests.OnCircuitBreakWithServiceProvider_796.cs @@ -54,7 +54,7 @@ public async Task OnCircuitBreakWithServiceProvider_796() contextChecked.Should().BeTrue(); } - private class ServiceProviderStrategy : ResilienceStrategy + private class ServiceProviderStrategy : NonReactiveResilienceStrategy { private readonly IServiceProvider _serviceProvider; diff --git a/test/Polly.Extensions.Tests/ReloadableResilienceStrategyTests.cs b/test/Polly.Extensions.Tests/ReloadableResilienceStrategyTests.cs index 25c8382ef2f..ad7a7179648 100644 --- a/test/Polly.Extensions.Tests/ReloadableResilienceStrategyTests.cs +++ b/test/Polly.Extensions.Tests/ReloadableResilienceStrategyTests.cs @@ -55,7 +55,7 @@ public void AddResilienceStrategy_EnsureReloadable(string? name) } } - public class ReloadableStrategy : ResilienceStrategy + public class ReloadableStrategy : NonReactiveResilienceStrategy { public ReloadableStrategy(string tag) => Tag = tag; diff --git a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs index f39fc59c97c..1fc6d37976e 100644 --- a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs @@ -23,7 +23,8 @@ public TelemetryResilienceStrategyTests() [Fact] public void Ctor_Ok() { - var duration = CreateStrategy().ExecutionDuration; + var strategy = CreateStrategy(); + var duration = ((TelemetryResilienceStrategy)strategy.GetType().GetRuntimeProperty("Strategy")!.GetValue(strategy)!).ExecutionDuration; duration.Unit.Should().Be("ms"); duration.Description.Should().Be("The execution duration and execution results of resilience strategies."); @@ -216,7 +217,12 @@ public void Execute_ExecutionHealth(bool healthy) } } - private TelemetryResilienceStrategy CreateStrategy() => new("my-builder", "my-instance", _loggerFactory, (_, r) => r, new List> { c => _enricher?.Invoke(c) }); + private ResilienceStrategy CreateStrategy() + { + return new CompositeStrategyBuilder() + .AddStrategy(_ => new TelemetryResilienceStrategy("my-builder", "my-instance", _loggerFactory, (_, r) => r, new List> { c => _enricher?.Invoke(c) }), new TestResilienceStrategyOptions()) + .Build(); + } public void Dispose() { diff --git a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs index 65f6016bfa2..baf4f8c12cd 100644 --- a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs +++ b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs @@ -25,7 +25,7 @@ public void GetInnerStrategies_Generic_Ok() .AddTimeout(TimeSpan.FromSeconds(1)) .AddHedging(new()) .AddConcurrencyLimiter(10) - .AddStrategy(new CustomStrategy()) + .AddStrategy(_ => new CustomStrategy(), new TestOptions()) .ConfigureTelemetry(NullLoggerFactory.Instance) .Build(); @@ -65,7 +65,7 @@ public void GetInnerStrategies_NonGeneric_Ok() .AddCircuitBreaker(new()) .AddTimeout(TimeSpan.FromSeconds(1)) .AddConcurrencyLimiter(10) - .AddStrategy(new CustomStrategy()) + .AddStrategy(_ => new CustomStrategy(), new TestOptions()) .ConfigureTelemetry(NullLoggerFactory.Instance) .Build(); @@ -120,7 +120,7 @@ public void GetInnerStrategies_Reloadable_Ok() builder .AddConcurrencyLimiter(10) - .AddStrategy(new CustomStrategy()); + .AddStrategy(_ => new CustomStrategy(), new TestOptions()); }); // act @@ -134,9 +134,13 @@ public void GetInnerStrategies_Reloadable_Ok() descriptor.Strategies[1].StrategyType.Should().Be(typeof(CustomStrategy)); } - private sealed class CustomStrategy : ResilienceStrategy + private sealed class CustomStrategy : NonReactiveResilienceStrategy { protected override ValueTask> ExecuteCore(Func>> callback, ResilienceContext context, TState state) => throw new NotSupportedException(); } + + private class TestOptions : ResilienceStrategyOptions + { + } } From 268a8c4401583eb8c38f954204171c82084ca3b1 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 10 Aug 2023 13:56:23 +0200 Subject: [PATCH 4/6] Fixes --- src/Polly.Testing/ResilienceStrategyExtensions.cs | 8 ++++---- .../ResilienceStrategyConversionExtensionsTests.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Polly.Testing/ResilienceStrategyExtensions.cs b/src/Polly.Testing/ResilienceStrategyExtensions.cs index beaa77ee08b..5a115f5c66f 100644 --- a/src/Polly.Testing/ResilienceStrategyExtensions.cs +++ b/src/Polly.Testing/ResilienceStrategyExtensions.cs @@ -51,14 +51,14 @@ private static InnerStrategiesDescriptor GetInnerStrategiesCore(ResilienceStr private static Type GetStrategyType(ResilienceStrategy strategy) { - if (strategy is ReactiveResilienceStrategyBridge bridge) + if (strategy is ReactiveResilienceStrategyBridge reactiveBridge) { - return bridge.Strategy.GetType(); + return reactiveBridge.Strategy.GetType(); } - if (strategy is NonReactiveResilienceStrategyBridge bridge2) + if (strategy is NonReactiveResilienceStrategyBridge nonReactiveBridge) { - return bridge2.Strategy.GetType(); + return nonReactiveBridge.Strategy.GetType(); } return strategy.GetType(); diff --git a/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs b/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs index 685acee397a..e153d914436 100644 --- a/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs +++ b/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs @@ -50,7 +50,7 @@ public void AsSyncPolicy_Ok() [Incoming.Key] = "incoming-value" }; - _strategy.AsSyncPolicy().Execute(_ => + _strategy.AsStrategy().AsSyncPolicy().Execute(_ => { context[Executing.Key] = "executing-value"; }, @@ -86,7 +86,7 @@ public void AsSyncPolicy_Result_Ok() [Incoming.Key] = "incoming-value" }; - var result = _strategy.AsSyncPolicy().Execute(_ => { context[Executing.Key] = "executing-value"; return "dummy"; }, context); + var result = _strategy.AsStrategy().AsSyncPolicy().Execute(_ => { context[Executing.Key] = "executing-value"; return "dummy"; }, context); AssertContext(context); result.Should().Be("dummy"); @@ -102,7 +102,7 @@ public async Task AsAsyncPolicy_Ok() [Incoming.Key] = "incoming-value" }; - await _strategy.AsAsyncPolicy().ExecuteAsync(_ => + await _strategy.AsStrategy().AsAsyncPolicy().ExecuteAsync(_ => { context[Executing.Key] = "executing-value"; return Task.CompletedTask; @@ -144,7 +144,7 @@ public async Task AsAsyncPolicy_Result_Ok() [Incoming.Key] = "incoming-value" }; - var result = await _strategy.AsAsyncPolicy().ExecuteAsync(_ => + var result = await _strategy.AsStrategy().AsAsyncPolicy().ExecuteAsync(_ => { context[Executing.Key] = "executing-value"; return Task.FromResult("dummy"); From 3130cc43e4a5adab6e7fa063609c768eed835aac Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 10 Aug 2023 13:58:41 +0200 Subject: [PATCH 5/6] PR comments --- src/Polly.Core/NonReactiveResilienceStrategy.cs | 2 +- src/Polly.Core/ReactiveResilienceStrategy.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/NonReactiveResilienceStrategy.cs b/src/Polly.Core/NonReactiveResilienceStrategy.cs index e5be19ad608..110a091d7fe 100644 --- a/src/Polly.Core/NonReactiveResilienceStrategy.cs +++ b/src/Polly.Core/NonReactiveResilienceStrategy.cs @@ -6,7 +6,7 @@ public abstract class NonReactiveResilienceStrategy { /// - /// An implementation of non-reactive resilience strategy that executes the specified . + /// An implementation of a non-reactive resilience strategy that executes the specified . /// /// The type of result returned by the callback. /// The type of state associated with the callback. diff --git a/src/Polly.Core/ReactiveResilienceStrategy.cs b/src/Polly.Core/ReactiveResilienceStrategy.cs index 7eeec7911f0..5a8a4e1886e 100644 --- a/src/Polly.Core/ReactiveResilienceStrategy.cs +++ b/src/Polly.Core/ReactiveResilienceStrategy.cs @@ -11,7 +11,7 @@ public abstract class ReactiveResilienceStrategy { /// - /// An implementation of resilience strategy that executes the specified . + /// An implementation of a reactive resilience strategy that executes the specified . /// /// The type of state associated with the callback. /// The user-provided callback. From 7c6cc041aa7a384d82c55bf2429d260255d2f922 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 10 Aug 2023 15:13:53 +0200 Subject: [PATCH 6/6] kill mutants --- ...CompositeStrategyBuilderExtensionsTests.cs | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs index 875757ee90e..ae9582e54f0 100644 --- a/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs +++ b/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs @@ -7,12 +7,12 @@ namespace Polly.RateLimiting.Tests; public class RateLimiterCompositeStrategyBuilderExtensionsTests { - public static readonly TheoryData>> Data = new() + public static readonly TheoryData> Data = new() { builder => { builder.AddConcurrencyLimiter(2, 2); - AssertRateLimiterStrategy(builder); + AssertRateLimiterStrategy(builder, strategy => strategy.Limiter.Limiter.Should().BeOfType()); }, builder => { @@ -23,21 +23,21 @@ public class RateLimiterCompositeStrategyBuilderExtensionsTests QueueLimit = 2 }); - AssertRateLimiterStrategy(builder); + AssertRateLimiterStrategy(builder, strategy => strategy.Limiter.Limiter.Should().BeOfType()); }, builder => { var expected = Substitute.For(); builder.AddRateLimiter(expected); - AssertRateLimiterStrategy(builder); + AssertRateLimiterStrategy(builder, strategy => strategy.Limiter.Limiter.Should().Be(expected)); } }; [MemberData(nameof(Data))] [Theory(Skip = "https://github.com/stryker-mutator/stryker-net/issues/2144")] - public void AddRateLimiter_Extensions_Ok(Action> configure) + public void AddRateLimiter_Extensions_Ok(Action configure) { - var builder = new CompositeStrategyBuilder(); + var builder = new CompositeStrategyBuilder(); configure(builder); @@ -61,13 +61,13 @@ public void AddConcurrencyLimiter_InvalidOptions_Throws() [Fact] public void AddRateLimiter_AllExtensions_Ok() { - foreach (var configure in Data.Select(v => v[0]).Cast>>()) + foreach (var configure in Data.Select(v => v[0]).Cast>()) { - var builder = new CompositeStrategyBuilder(); + var builder = new CompositeStrategyBuilder(); configure(builder); - builder.Build().GetInnerStrategies().Strategies.Single().StrategyType.Should().Be(typeof(RateLimiterResilienceStrategy)); + AssertRateLimiterStrategy(builder); } } @@ -136,8 +136,29 @@ public void AddRateLimiter_Options_Ok() .Be(); } - private static void AssertRateLimiterStrategy(CompositeStrategyBuilder builder) + private static void AssertRateLimiterStrategy(CompositeStrategyBuilder builder, Action? assert = null, bool hasEvents = false) + { + ResilienceStrategy strategy = builder.Build(); + var limiterStrategy = GetResilienceStrategy(strategy); + assert?.Invoke(limiterStrategy); + + if (hasEvents) + { + limiterStrategy.OnLeaseRejected.Should().NotBeNull(); + limiterStrategy + .OnLeaseRejected!(new OnRateLimiterRejectedArguments(ResilienceContextPool.Shared.Get(), Substitute.For(), null)) + .Preserve().GetAwaiter().GetResult(); + } + else + { + limiterStrategy.OnLeaseRejected.Should().BeNull(); + } + + strategy.GetInnerStrategies().Strategies.Single().StrategyType.Should().Be(typeof(RateLimiterResilienceStrategy)); + } + + private static RateLimiterResilienceStrategy GetResilienceStrategy(ResilienceStrategy strategy) { - builder.Build().GetInnerStrategies().Strategies.Single().StrategyType.Should().Be(typeof(RateLimiterResilienceStrategy)); + return (RateLimiterResilienceStrategy)strategy.GetType().GetRuntimeProperty("Strategy")!.GetValue(strategy)!; } }