From 208e5dc5c924ed73a7c692a07bf07fa3a4e255e5 Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 09:52:53 +0100 Subject: [PATCH 01/12] Rename OnLatency to OnLatencyInjected --- docs/chaos/latency.md | 16 ++++++++-------- .../Simmy/Latency/LatencyChaosStrategy.cs | 12 ++++++------ src/Polly.Core/Simmy/Latency/LatencyConstants.cs | 2 +- .../Simmy/Latency/LatencyStrategyOptions.cs | 2 +- ...rguments.cs => OnLatencyInjectedArguments.cs} | 6 +++--- src/Snippets/Docs/Chaos.Latency.cs | 6 +++--- .../Simmy/Latency/LatencyChaosStrategyTests.cs | 6 +++--- .../Simmy/Latency/LatencyConstantsTests.cs | 2 +- .../Simmy/Latency/LatencyStrategyOptionsTests.cs | 2 +- ...sts.cs => OnLatencyInjectedArgumentsTests.cs} | 4 ++-- 10 files changed, 29 insertions(+), 29 deletions(-) rename src/Polly.Core/Simmy/Latency/{OnLatencyArguments.cs => OnLatencyInjectedArguments.cs} (77%) rename test/Polly.Core.Tests/Simmy/Latency/{OnLatencyArgumentsTests.cs => OnLatencyInjectedArgumentsTests.cs} (58%) diff --git a/docs/chaos/latency.md b/docs/chaos/latency.md index 8503acd1c4f..b3595e17a48 100644 --- a/docs/chaos/latency.md +++ b/docs/chaos/latency.md @@ -45,14 +45,14 @@ var optionsWithLatencyGenerator = new LatencyStrategyOptions }; // To get notifications when a delay is injected -var optionsOnBehaviorInjected = new LatencyStrategyOptions +var optionsOnLatencyInjected = new LatencyStrategyOptions { Latency = TimeSpan.FromSeconds(30), Enabled = true, InjectionRate = 0.1, - OnLatency = static args => + OnLatencyInjected = static args => { - Console.WriteLine($"OnLatency, Latency: {args.Latency}, Operation: {args.Context.OperationKey}."); + Console.WriteLine($"OnLatencyInjected, Latency: {args.Latency}, Operation: {args.Context.OperationKey}."); return default; } }; @@ -92,11 +92,11 @@ var pipeline = new ResiliencePipelineBuilder() ## Defaults -| Property | Default Value | Description | -|--------------------|---------------|--------------------------------------------------------| -| `Latency` | `30 seconds` | A `TimeSpan` indicating the delay to be injected. | -| `LatencyGenerator` | `null` | Generates the latency to inject for a given execution. | -| `OnLatency` | `null` | Action executed when latency is injected. | +| Property | Default Value | Description | +|---------------------|---------------|--------------------------------------------------------| +| `Latency` | `30 seconds` | A `TimeSpan` indicating the delay to be injected. | +| `LatencyGenerator` | `null` | Generates the latency to inject for a given execution. | +| `OnLatencyInjected` | `null` | Action executed when latency is injected. | ## Diagrams diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index 409c9a4881f..cdeb5b8efcb 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -15,13 +15,13 @@ public LatencyChaosStrategy( { Latency = options.Latency; LatencyGenerator = options.LatencyGenerator is not null ? options.LatencyGenerator : (_) => new(options.Latency); - OnLatency = options.OnLatency; + OnLatencyInjected = options.OnLatencyInjected; _telemetry = telemetry; _timeProvider = timeProvider; } - public Func<OnLatencyArguments, ValueTask>? OnLatency { get; } + public Func<OnLatencyInjectedArguments, ValueTask>? OnLatencyInjected { get; } public Func<LatencyGeneratorArguments, ValueTask<TimeSpan>> LatencyGenerator { get; } @@ -43,14 +43,14 @@ protected internal override async ValueTask<Outcome<TResult>> ExecuteCore<TResul return await StrategyHelper.ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); } - var args = new OnLatencyArguments(context, latency); - _telemetry.Report(new(ResilienceEventSeverity.Information, LatencyConstants.OnLatencyEvent), context, args); + var args = new OnLatencyInjectedArguments(context, latency); + _telemetry.Report(new(ResilienceEventSeverity.Information, LatencyConstants.OnLatencyInjectedEvent), context, args); await _timeProvider.DelayAsync(latency, context).ConfigureAwait(context.ContinueOnCapturedContext); - if (OnLatency is not null) + if (OnLatencyInjected is not null) { - await OnLatency(args).ConfigureAwait(context.ContinueOnCapturedContext); + await OnLatencyInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); } } diff --git a/src/Polly.Core/Simmy/Latency/LatencyConstants.cs b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs index a74b6a58a0e..fdb1a5ab6c3 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyConstants.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs @@ -2,7 +2,7 @@ internal static class LatencyConstants { - public const string OnLatencyEvent = "OnLatency"; + public const string OnLatencyInjectedEvent = "OnLatencyInjected"; public static readonly TimeSpan DefaultLatency = TimeSpan.FromSeconds(30); } diff --git a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs index 534741efa2a..ec7327e878b 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs @@ -13,7 +13,7 @@ public class LatencyStrategyOptions : MonkeyStrategyOptions /// <remarks> /// Defaults to <see langword="null"/>. /// </remarks> - public Func<OnLatencyArguments, ValueTask>? OnLatency { get; set; } + public Func<OnLatencyInjectedArguments, ValueTask>? OnLatencyInjected { get; set; } /// <summary> /// Gets or sets the latency generator that generates the delay for a given execution. diff --git a/src/Polly.Core/Simmy/Latency/OnLatencyArguments.cs b/src/Polly.Core/Simmy/Latency/OnLatencyInjectedArguments.cs similarity index 77% rename from src/Polly.Core/Simmy/Latency/OnLatencyArguments.cs rename to src/Polly.Core/Simmy/Latency/OnLatencyInjectedArguments.cs index 9a3896d2904..b82423ede42 100644 --- a/src/Polly.Core/Simmy/Latency/OnLatencyArguments.cs +++ b/src/Polly.Core/Simmy/Latency/OnLatencyInjectedArguments.cs @@ -5,14 +5,14 @@ /// <summary> /// Arguments used by the latency chaos strategy to notify that a delayed occurred. /// </summary> -public readonly struct OnLatencyArguments +public readonly struct OnLatencyInjectedArguments { /// <summary> - /// Initializes a new instance of the <see cref="OnLatencyArguments"/> struct. + /// Initializes a new instance of the <see cref="OnLatencyInjectedArguments"/> struct. /// </summary> /// <param name="context">The context associated with the execution of a user-provided callback.</param> /// <param name="latency">The latency that was injected.</param> - public OnLatencyArguments(ResilienceContext context, TimeSpan latency) + public OnLatencyInjectedArguments(ResilienceContext context, TimeSpan latency) { Context = context; Latency = latency; diff --git a/src/Snippets/Docs/Chaos.Latency.cs b/src/Snippets/Docs/Chaos.Latency.cs index 75fdf174d9e..e1cd6d49f30 100644 --- a/src/Snippets/Docs/Chaos.Latency.cs +++ b/src/Snippets/Docs/Chaos.Latency.cs @@ -42,14 +42,14 @@ public static void LatencyUsage() }; // To get notifications when a delay is injected - var optionsOnBehaviorInjected = new LatencyStrategyOptions + var optionsOnLatencyInjected = new LatencyStrategyOptions { Latency = TimeSpan.FromSeconds(30), Enabled = true, InjectionRate = 0.1, - OnLatency = static args => + OnLatencyInjected = static args => { - Console.WriteLine($"OnLatency, Latency: {args.Latency}, Operation: {args.Context.OperationKey}."); + Console.WriteLine($"OnLatencyInjected, Latency: {args.Latency}, Operation: {args.Context.OperationKey}."); return default; } }; diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs index 361ab2d75ba..b44aa136e2e 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs @@ -32,7 +32,7 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_late _options.Enabled = true; _options.Latency = _delay; _options.Randomizer = () => 0.5; - _options.OnLatency = args => + _options.OnLatencyInjected = args => { args.Context.Should().NotBeNull(); args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); @@ -51,7 +51,7 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_late (after - before).Should().Be(_delay); _args.Should().HaveCount(1); - _args[0].Arguments.Should().BeOfType<OnLatencyArguments>(); + _args[0].Arguments.Should().BeOfType<OnLatencyInjectedArguments>(); onLatencyExecuted.Should().BeTrue(); } @@ -110,7 +110,7 @@ public async Task Given_latency_is_negative_should_not_inject_latency(double lat _options.Latency = TimeSpan.FromSeconds(latency); _options.Randomizer = () => 0.5; - _options.OnLatency = args => + _options.OnLatencyInjected = args => { args.Context.Should().NotBeNull(); args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs index 1d27e28bf8f..ea5e02f513e 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs @@ -7,7 +7,7 @@ public class LatencyConstantsTests [Fact] public void EnsureDefaults() { - LatencyConstants.OnLatencyEvent.Should().Be("OnLatency"); + LatencyConstants.OnLatencyInjectedEvent.Should().Be("OnLatencyInjected"); LatencyConstants.DefaultLatency.Should().Be(TimeSpan.FromSeconds(30)); } } diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs index 571b5c2df7d..2999b798082 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs @@ -16,6 +16,6 @@ public void Ctor_Ok() sut.InjectionRateGenerator.Should().BeNull(); sut.Latency.Should().Be(LatencyConstants.DefaultLatency); sut.LatencyGenerator.Should().BeNull(); - sut.OnLatency.Should().BeNull(); + sut.OnLatencyInjected.Should().BeNull(); } } diff --git a/test/Polly.Core.Tests/Simmy/Latency/OnLatencyArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/OnLatencyInjectedArgumentsTests.cs similarity index 58% rename from test/Polly.Core.Tests/Simmy/Latency/OnLatencyArgumentsTests.cs rename to test/Polly.Core.Tests/Simmy/Latency/OnLatencyInjectedArgumentsTests.cs index 5c834634e5f..838ea641690 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/OnLatencyArgumentsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/OnLatencyInjectedArgumentsTests.cs @@ -2,12 +2,12 @@ namespace Polly.Core.Tests.Simmy.Latency; -public class OnLatencyArgumentsTests +public class OnLatencyInjectedArgumentsTests { [Fact] public void Ctor_Ok() { - var args = new OnLatencyArguments(ResilienceContextPool.Shared.Get(), TimeSpan.FromSeconds(10)); + var args = new OnLatencyInjectedArguments(ResilienceContextPool.Shared.Get(), TimeSpan.FromSeconds(10)); args.Context.Should().NotBeNull(); args.Latency.Should().Be(TimeSpan.FromSeconds(10)); } From 61cbae6e3cb8c347f41b3bbec91becfab3808f0a Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 09:53:51 +0100 Subject: [PATCH 02/12] Remove Event suffix from the OnFaultInjectedEvent constant --- src/Polly.Core/Simmy/Fault/FaultConstants.cs | 2 +- test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/Simmy/Fault/FaultConstants.cs b/src/Polly.Core/Simmy/Fault/FaultConstants.cs index 1e8f325af5b..0f7dc2c93c8 100644 --- a/src/Polly.Core/Simmy/Fault/FaultConstants.cs +++ b/src/Polly.Core/Simmy/Fault/FaultConstants.cs @@ -2,5 +2,5 @@ internal static class FaultConstants { - public const string OnFaultInjectedEvent = "OnFaultInjectedEvent"; + public const string OnFaultInjectedEvent = "OnFaultInjected"; } diff --git a/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs index ffc1cfd1ff4..647bccc67ff 100644 --- a/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs @@ -7,6 +7,6 @@ public class FaultConstantsTests [Fact] public void EnsureDefaults() { - FaultConstants.OnFaultInjectedEvent.Should().Be("OnFaultInjectedEvent"); + FaultConstants.OnFaultInjectedEvent.Should().Be("OnFaultInjected"); } } From d4f39adb08590f43c67252910490690efbfd279f Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 09:54:15 +0100 Subject: [PATCH 03/12] Remove obsolete documentation comment --- src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs b/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs index deb8d61a602..0cf8f9b9199 100644 --- a/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs @@ -19,8 +19,7 @@ public class FaultStrategyOptions : MonkeyStrategyOptions /// Gets or sets the fault generator to be injected for a given execution. /// </summary> /// <remarks> - /// Defaults to <see langword="null"/>. Either <see cref="Fault"/> or this property is required. - /// When this property is <see langword="null"/> the <see cref="Fault"/> is used. + /// Defaults to <see langword="null"/>. /// </remarks> [Required] public Func<FaultGeneratorArguments, ValueTask<Exception?>>? FaultGenerator { get; set; } = default!; From 0e1a3a853e6f730137b7ddbc22739269eca36c7e Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 09:54:32 +0100 Subject: [PATCH 04/12] Fix markdown table --- docs/chaos/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/chaos/index.md b/docs/chaos/index.md index d9be339cd1f..2882f58f296 100644 --- a/docs/chaos/index.md +++ b/docs/chaos/index.md @@ -48,6 +48,6 @@ All the strategies' options implement the [`MonkeyStrategyOptions`](xref:Polly.S | `InjectionRate` | 0.001 ms | A decimal between 0 and 1 inclusive. The strategy will inject the chaos, randomly, that proportion of the time, e.g.: if 0.2, twenty percent of calls will be randomly affected; if 0.01, one percent of calls; if 1, all calls. | | `InjectionRateGenerator` | `null` | Generates the injection rate for a given execution, which the value should be between [0, 1] (inclusive). | | `Enabled` | `false` | Determines whether the strategy is enabled or not. | -| `EnabledGenerator` | `null` | The generator that indicates whether the chaos strategy is enabled for a given execution. | +| `EnabledGenerator` | `null` | The generator that indicates whether the chaos strategy is enabled for a given execution. | [simmy]: https://github.com/Polly-Contrib/Simmy From 1c6b603b2374a0a9bddfcc198014c5ef85af8870 Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 10:11:23 +0100 Subject: [PATCH 05/12] Update unshipped --- src/Polly.Core/PublicAPI.Unshipped.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 97bb9afd8e9..68d606407b8 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -50,13 +50,13 @@ Polly.Simmy.Latency.LatencyStrategyOptions.Latency.set -> void Polly.Simmy.Latency.LatencyStrategyOptions.LatencyGenerator.get -> System.Func<Polly.Simmy.Latency.LatencyGeneratorArguments, System.Threading.Tasks.ValueTask<System.TimeSpan>>? Polly.Simmy.Latency.LatencyStrategyOptions.LatencyGenerator.set -> void Polly.Simmy.Latency.LatencyStrategyOptions.LatencyStrategyOptions() -> void -Polly.Simmy.Latency.LatencyStrategyOptions.OnLatency.get -> System.Func<Polly.Simmy.Latency.OnLatencyArguments, System.Threading.Tasks.ValueTask>? -Polly.Simmy.Latency.LatencyStrategyOptions.OnLatency.set -> void -Polly.Simmy.Latency.OnLatencyArguments -Polly.Simmy.Latency.OnLatencyArguments.Context.get -> Polly.ResilienceContext! -Polly.Simmy.Latency.OnLatencyArguments.Latency.get -> System.TimeSpan -Polly.Simmy.Latency.OnLatencyArguments.OnLatencyArguments() -> void -Polly.Simmy.Latency.OnLatencyArguments.OnLatencyArguments(Polly.ResilienceContext! context, System.TimeSpan latency) -> void +Polly.Simmy.Latency.LatencyStrategyOptions.OnLatencyInjected.get -> System.Func<Polly.Simmy.Latency.OnLatencyInjectedArguments, System.Threading.Tasks.ValueTask>? +Polly.Simmy.Latency.LatencyStrategyOptions.OnLatencyInjected.set -> void +Polly.Simmy.Latency.OnLatencyInjectedArguments +Polly.Simmy.Latency.OnLatencyInjectedArguments.Context.get -> Polly.ResilienceContext! +Polly.Simmy.Latency.OnLatencyInjectedArguments.Latency.get -> System.TimeSpan +Polly.Simmy.Latency.OnLatencyInjectedArguments.OnLatencyInjectedArguments() -> void +Polly.Simmy.Latency.OnLatencyInjectedArguments.OnLatencyInjectedArguments(Polly.ResilienceContext! context, System.TimeSpan latency) -> void Polly.Simmy.LatencyPipelineBuilderExtensions Polly.Simmy.MonkeyStrategy Polly.Simmy.MonkeyStrategy.MonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void From c5064714475918f19977243b022dc226eea97c62 Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 11:25:24 +0100 Subject: [PATCH 06/12] Apply suggestions for Fault chaos strategy --- src/Polly.Core/Simmy/Fault/FaultConstants.cs | 4 +++- src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs | 5 +++++ test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/Simmy/Fault/FaultConstants.cs b/src/Polly.Core/Simmy/Fault/FaultConstants.cs index 0f7dc2c93c8..a38c84a9fcc 100644 --- a/src/Polly.Core/Simmy/Fault/FaultConstants.cs +++ b/src/Polly.Core/Simmy/Fault/FaultConstants.cs @@ -2,5 +2,7 @@ internal static class FaultConstants { - public const string OnFaultInjectedEvent = "OnFaultInjected"; + public const string DefaultName = "Chaos.Fault"; + + public const string OnFaultInjectedEvent = "Chaos.OnFault"; } diff --git a/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs b/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs index 0cf8f9b9199..abb10ebd639 100644 --- a/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs @@ -7,6 +7,11 @@ namespace Polly.Simmy.Fault; /// </summary> public class FaultStrategyOptions : MonkeyStrategyOptions { + /// <summary> + /// Initializes a new instance of the <see cref="FaultStrategyOptions"/> class. + /// </summary> + public FaultStrategyOptions() => Name = FaultConstants.DefaultName; + /// <summary> /// Gets or sets the delegate that's raised when the outcome is injected. /// </summary> diff --git a/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs index 647bccc67ff..2a52b8cd2d1 100644 --- a/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Fault/FaultConstantsTests.cs @@ -7,6 +7,7 @@ public class FaultConstantsTests [Fact] public void EnsureDefaults() { - FaultConstants.OnFaultInjectedEvent.Should().Be("OnFaultInjected"); + FaultConstants.DefaultName.Should().Be("Chaos.Fault"); + FaultConstants.OnFaultInjectedEvent.Should().Be("Chaos.OnFault"); } } From 324f4c145dc52b4d149440075ac6ed8d688d669d Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 11:28:56 +0100 Subject: [PATCH 07/12] Apply suggestions for Latency chaos strategy --- src/Polly.Core/Simmy/Latency/LatencyConstants.cs | 4 +++- src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs | 5 +++++ test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/Simmy/Latency/LatencyConstants.cs b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs index fdb1a5ab6c3..e226d8d4062 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyConstants.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs @@ -2,7 +2,9 @@ internal static class LatencyConstants { - public const string OnLatencyInjectedEvent = "OnLatencyInjected"; + public const string DefaultName = "Chaos.Latency"; + + public const string OnLatencyInjectedEvent = "Chaos.OnLatency"; public static readonly TimeSpan DefaultLatency = TimeSpan.FromSeconds(30); } diff --git a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs index ec7327e878b..364c6625aca 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs @@ -7,6 +7,11 @@ /// </summary> public class LatencyStrategyOptions : MonkeyStrategyOptions { + /// <summary> + /// Initializes a new instance of the <see cref="LatencyStrategyOptions"/> class. + /// </summary> + public LatencyStrategyOptions() => Name = LatencyConstants.DefaultName; + /// <summary> /// Gets or sets the delegate that's raised when a delay occurs. /// </summary> diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs index ea5e02f513e..b1c3b4462b3 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs @@ -7,7 +7,8 @@ public class LatencyConstantsTests [Fact] public void EnsureDefaults() { - LatencyConstants.OnLatencyInjectedEvent.Should().Be("OnLatencyInjected"); + LatencyConstants.DefaultName.Should().Be("Chaos.Latency"); + LatencyConstants.OnLatencyInjectedEvent.Should().Be("Chaos.OnLatency"); LatencyConstants.DefaultLatency.Should().Be(TimeSpan.FromSeconds(30)); } } From 3f12fbff2c07be4918b33cbb66b0177ee7407914 Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 11:32:51 +0100 Subject: [PATCH 08/12] Apply suggestions for Behavior chaos strategy --- src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs | 4 +++- src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs | 5 +++++ .../Simmy/Behavior/BehaviorConstantsTests.cs | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs b/src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs index 91341bdb948..188e392f32b 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs @@ -2,5 +2,7 @@ internal static class BehaviorConstants { - public const string OnBehaviorInjectedEvent = "OnBehaviorInjected"; + public const string DefaultName = "Chaos.Behavior"; + + public const string OnBehaviorInjectedEvent = "Chaos.OnBehavior"; } diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs index 1541fbc9b3f..84fcb3703ab 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs @@ -7,6 +7,11 @@ namespace Polly.Simmy.Behavior; /// </summary> public class BehaviorStrategyOptions : MonkeyStrategyOptions { + /// <summary> + /// Initializes a new instance of the <see cref="BehaviorStrategyOptions"/> class. + /// </summary> + public BehaviorStrategyOptions() => Name = BehaviorConstants.DefaultName; + /// <summary> /// Gets or sets the delegate that's raised when the custom behavior is injected. /// </summary> diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs index e25fffb3fe0..13f0d4a6578 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs @@ -7,6 +7,7 @@ public class BehaviorConstantsTests [Fact] public void EnsureDefaults() { - BehaviorConstants.OnBehaviorInjectedEvent.Should().Be("OnBehaviorInjected"); + BehaviorConstants.DefaultName.Should().Be("Chaos.Behavior"); + BehaviorConstants.OnBehaviorInjectedEvent.Should().Be("Chaos.OnBehavior"); } } From 1b6f696572e0b843e69501ffde6024ff6bcb89c3 Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 11:34:23 +0100 Subject: [PATCH 09/12] Apply suggestions for Outcome chaos strategy --- src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs | 4 +++- .../Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs | 5 +++++ .../Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs index d061a0579f1..1ac0882db04 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs @@ -2,5 +2,7 @@ internal static class OutcomeConstants { - public const string OnOutcomeInjectedEvent = "OnOutcomeInjected"; + public const string DefaultName = "Chaos.Outcome"; + + public const string OnOutcomeInjectedEvent = "Chaos.OnOutcome"; } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs index 4236d870254..44a2ba1e9bb 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs @@ -8,6 +8,11 @@ namespace Polly.Simmy.Outcomes; /// <typeparam name="TResult">The type of the outcome that was injected.</typeparam> public class OutcomeStrategyOptions<TResult> : MonkeyStrategyOptions { + /// <summary> + /// Initializes a new instance of the <see cref="OutcomeStrategyOptions{TResult}"/> class. + /// </summary> + public OutcomeStrategyOptions() => Name = OutcomeConstants.DefaultName; + /// <summary> /// Gets or sets the delegate that's raised when the outcome is injected. /// </summary> diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs index dcd92910518..f2c085bdd01 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs @@ -7,6 +7,7 @@ public class OutcomeConstantsTests [Fact] public void EnsureDefaults() { - OutcomeConstants.OnOutcomeInjectedEvent.Should().Be("OnOutcomeInjected"); + OutcomeConstants.DefaultName.Should().Be("Chaos.Outcome"); + OutcomeConstants.OnOutcomeInjectedEvent.Should().Be("Chaos.OnOutcome"); } } From 7732a076a3c092bb6aeabd8cda98defa16c055de Mon Sep 17 00:00:00 2001 From: peter-csala <57183693+peter-csala@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:49:28 +0100 Subject: [PATCH 10/12] [Docs] Add banner to chaos docs (#1910) --- docs/chaos/behavior.md | 3 +++ docs/chaos/fault.md | 3 +++ docs/chaos/index.md | 3 +++ docs/chaos/latency.md | 3 +++ docs/chaos/result.md | 3 +++ 5 files changed, 15 insertions(+) diff --git a/docs/chaos/behavior.md b/docs/chaos/behavior.md index 777de831800..31a39d38328 100644 --- a/docs/chaos/behavior.md +++ b/docs/chaos/behavior.md @@ -1,5 +1,8 @@ # Behavior chaos strategy +> [!IMPORTANT] +> This documentation page describes an upcoming feature of Polly. + ## About - **Options**: [`BehaviorStrategyOptions`](xref:Polly.Simmy.Behavior.BehaviorStrategyOptions) diff --git a/docs/chaos/fault.md b/docs/chaos/fault.md index 8f4c2deb3cf..1f7f4182083 100644 --- a/docs/chaos/fault.md +++ b/docs/chaos/fault.md @@ -1,5 +1,8 @@ # Fault chaos strategy +> [!IMPORTANT] +> This documentation page describes an upcoming feature of Polly. + ## About - **Options**: [`FaultStrategyOptions`](xref:Polly.Simmy.Fault.FaultStrategyOptions) diff --git a/docs/chaos/index.md b/docs/chaos/index.md index 2882f58f296..484abccfcff 100644 --- a/docs/chaos/index.md +++ b/docs/chaos/index.md @@ -1,5 +1,8 @@ # Chaos engineering with Simmy +> [!IMPORTANT] +> This documentation page describes an upcoming feature of Polly. + [Simmy][simmy] is a major new addition to Polly library, adding a chaos engineering and fault-injection dimension to Polly, through the provision of strategies to selectively inject faults, latency, custom behavior or fake results. ![Simmy](../media/simmy-logo.png) diff --git a/docs/chaos/latency.md b/docs/chaos/latency.md index b3595e17a48..9fcf430852d 100644 --- a/docs/chaos/latency.md +++ b/docs/chaos/latency.md @@ -1,5 +1,8 @@ # Latency chaos strategy +> [!IMPORTANT] +> This documentation page describes an upcoming feature of Polly. + ## About - **Options**: [`LatencyStrategyOptions`](xref:Polly.Simmy.Latency.LatencyStrategyOptions) diff --git a/docs/chaos/result.md b/docs/chaos/result.md index 0ec40f41cbe..c2ccffdf12c 100644 --- a/docs/chaos/result.md +++ b/docs/chaos/result.md @@ -1,5 +1,8 @@ # Outcome chaos strategy +> [!IMPORTANT] +> This documentation page describes an upcoming feature of Polly. + ## About - **Options**: From 8f6813e366b1504b590adfa86592fe8edd370d78 Mon Sep 17 00:00:00 2001 From: martintmk <103487740+martintmk@users.noreply.github.com> Date: Sun, 21 Jan 2024 21:09:18 +0100 Subject: [PATCH 11/12] Introduce `FaultGenerator` and `OutcomeGenerator<T>` (#1911) --- src/Polly.Core/PublicAPI.Unshipped.txt | 16 ++- src/Polly.Core/Simmy/Fault/FaultGenerator.cs | 84 ++++++++++++ .../Simmy/Outcomes/OutcomeGenerator.cs | 120 ++++++++++++++++++ .../OutcomeStrategyOptions.TResult.cs | 29 ----- .../Simmy/Outcomes/OutcomeStrategyOptions.cs | 24 +++- src/Polly.Core/Simmy/Utils/GeneratorHelper.cs | 52 ++++++++ src/Polly.Core/Utils/RandomUtil.cs | 2 + .../Simmy/Fault/FaultGeneratorTests.cs | 49 +++++++ .../Simmy/Outcomes/OutcomeGeneratorTests.cs | 77 +++++++++++ .../Simmy/Utils/GeneratorHelperTests.cs | 56 ++++++++ 10 files changed, 475 insertions(+), 34 deletions(-) create mode 100644 src/Polly.Core/Simmy/Fault/FaultGenerator.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs delete mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs create mode 100644 src/Polly.Core/Simmy/Utils/GeneratorHelper.cs create mode 100644 test/Polly.Core.Tests/Simmy/Fault/FaultGeneratorTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OutcomeGeneratorTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Utils/GeneratorHelperTests.cs diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 68d606407b8..af7eab4a0e0 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -20,6 +20,11 @@ Polly.Simmy.EnabledGeneratorArguments Polly.Simmy.EnabledGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.EnabledGeneratorArguments.EnabledGeneratorArguments() -> void Polly.Simmy.EnabledGeneratorArguments.EnabledGeneratorArguments(Polly.ResilienceContext! context) -> void +Polly.Simmy.Fault.FaultGenerator +Polly.Simmy.Fault.FaultGenerator.AddException(System.Func<Polly.ResilienceContext!, System.Exception!>! generator, int weight = 100) -> Polly.Simmy.Fault.FaultGenerator! +Polly.Simmy.Fault.FaultGenerator.AddException(System.Func<System.Exception!>! generator, int weight = 100) -> Polly.Simmy.Fault.FaultGenerator! +Polly.Simmy.Fault.FaultGenerator.AddException<TException>(int weight = 100) -> Polly.Simmy.Fault.FaultGenerator! +Polly.Simmy.Fault.FaultGenerator.FaultGenerator() -> void Polly.Simmy.Fault.FaultGeneratorArguments Polly.Simmy.Fault.FaultGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Fault.FaultGeneratorArguments.FaultGeneratorArguments() -> void @@ -84,12 +89,17 @@ Polly.Simmy.Outcomes.OnOutcomeInjectedArguments<TResult>.Context.get -> Polly.Re Polly.Simmy.Outcomes.OnOutcomeInjectedArguments<TResult>.OnOutcomeInjectedArguments() -> void Polly.Simmy.Outcomes.OnOutcomeInjectedArguments<TResult>.OnOutcomeInjectedArguments(Polly.ResilienceContext! context, Polly.Outcome<TResult> outcome) -> void Polly.Simmy.Outcomes.OnOutcomeInjectedArguments<TResult>.Outcome.get -> Polly.Outcome<TResult> +Polly.Simmy.Outcomes.OutcomeGenerator<TResult> +Polly.Simmy.Outcomes.OutcomeGenerator<TResult>.AddException(System.Func<Polly.ResilienceContext!, System.Exception!>! generator, int weight = 100) -> Polly.Simmy.Outcomes.OutcomeGenerator<TResult>! +Polly.Simmy.Outcomes.OutcomeGenerator<TResult>.AddException(System.Func<System.Exception!>! generator, int weight = 100) -> Polly.Simmy.Outcomes.OutcomeGenerator<TResult>! +Polly.Simmy.Outcomes.OutcomeGenerator<TResult>.AddException<TException>(int weight = 100) -> Polly.Simmy.Outcomes.OutcomeGenerator<TResult>! +Polly.Simmy.Outcomes.OutcomeGenerator<TResult>.AddResult(System.Func<Polly.ResilienceContext!, TResult>! generator, int weight = 100) -> Polly.Simmy.Outcomes.OutcomeGenerator<TResult>! +Polly.Simmy.Outcomes.OutcomeGenerator<TResult>.AddResult(System.Func<TResult>! generator, int weight = 100) -> Polly.Simmy.Outcomes.OutcomeGenerator<TResult>! +Polly.Simmy.Outcomes.OutcomeGenerator<TResult>.OutcomeGenerator() -> void Polly.Simmy.Outcomes.OutcomeGeneratorArguments Polly.Simmy.Outcomes.OutcomeGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Outcomes.OutcomeGeneratorArguments.OutcomeGeneratorArguments() -> void Polly.Simmy.Outcomes.OutcomeGeneratorArguments.OutcomeGeneratorArguments(Polly.ResilienceContext! context) -> void -Polly.Simmy.Outcomes.OutcomeStrategyOptions -Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeStrategyOptions() -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions<TResult> Polly.Simmy.Outcomes.OutcomeStrategyOptions<TResult>.OnOutcomeInjected.get -> System.Func<Polly.Simmy.Outcomes.OnOutcomeInjectedArguments<TResult>, System.Threading.Tasks.ValueTask>? Polly.Simmy.Outcomes.OutcomeStrategyOptions<TResult>.OnOutcomeInjected.set -> void @@ -98,9 +108,11 @@ Polly.Simmy.Outcomes.OutcomeStrategyOptions<TResult>.OutcomeGenerator.set -> voi Polly.Simmy.Outcomes.OutcomeStrategyOptions<TResult>.OutcomeStrategyOptions() -> void static Polly.Simmy.BehaviorPipelineBuilderExtensions.AddChaosBehavior<TBuilder>(this TBuilder! builder, double injectionRate, System.Func<System.Threading.Tasks.ValueTask>! behavior) -> TBuilder! static Polly.Simmy.BehaviorPipelineBuilderExtensions.AddChaosBehavior<TBuilder>(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! +static Polly.Simmy.Fault.FaultGenerator.implicit operator System.Func<Polly.Simmy.Fault.FaultGeneratorArguments, System.Threading.Tasks.ValueTask<System.Exception?>>!(Polly.Simmy.Fault.FaultGenerator! generator) -> System.Func<Polly.Simmy.Fault.FaultGeneratorArguments, System.Threading.Tasks.ValueTask<System.Exception?>>! static Polly.Simmy.FaultPipelineBuilderExtensions.AddChaosFault<TBuilder>(this TBuilder! builder, double injectionRate, System.Func<System.Exception?>! faultGenerator) -> TBuilder! static Polly.Simmy.FaultPipelineBuilderExtensions.AddChaosFault<TBuilder>(this TBuilder! builder, Polly.Simmy.Fault.FaultStrategyOptions! options) -> TBuilder! static Polly.Simmy.LatencyPipelineBuilderExtensions.AddChaosLatency<TBuilder>(this TBuilder! builder, double injectionRate, System.TimeSpan latency) -> TBuilder! static Polly.Simmy.LatencyPipelineBuilderExtensions.AddChaosLatency<TBuilder>(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosResult<TResult>(this Polly.ResiliencePipelineBuilder<TResult>! builder, double injectionRate, System.Func<TResult?>! resultGenerator) -> Polly.ResiliencePipelineBuilder<TResult>! static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosResult<TResult>(this Polly.ResiliencePipelineBuilder<TResult>! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions<TResult>! options) -> Polly.ResiliencePipelineBuilder<TResult>! +static Polly.Simmy.Outcomes.OutcomeGenerator<TResult>.implicit operator System.Func<Polly.Simmy.Outcomes.OutcomeGeneratorArguments, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>?>>!(Polly.Simmy.Outcomes.OutcomeGenerator<TResult>! generator) -> System.Func<Polly.Simmy.Outcomes.OutcomeGeneratorArguments, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>?>>! diff --git a/src/Polly.Core/Simmy/Fault/FaultGenerator.cs b/src/Polly.Core/Simmy/Fault/FaultGenerator.cs new file mode 100644 index 00000000000..374faee6a70 --- /dev/null +++ b/src/Polly.Core/Simmy/Fault/FaultGenerator.cs @@ -0,0 +1,84 @@ +using System.ComponentModel; +using Polly.Simmy.Utils; + +namespace Polly.Simmy.Fault; + +#pragma warning disable CA2225 // Operator overloads have named alternates +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + +/// <summary> +/// A generator for creating faults (exceptions) using registered delegate functions. +/// </summary> +/// <remarks> +/// An instance of this class can be assigned to the <see cref="FaultStrategyOptions.FaultGenerator"/> property. +/// </remarks> +public sealed class FaultGenerator +{ + private const int DefaultWeight = 100; + + private readonly GeneratorHelper<VoidResult> _helper; + + /// <summary> + /// Initializes a new instance of the <see cref="FaultGenerator"/> class. + /// </summary> + public FaultGenerator() + => _helper = new GeneratorHelper<VoidResult>(RandomUtil.Instance.Next); + + /// <summary> + /// Registers an exception generator delegate. + /// </summary> + /// <param name="generator">The delegate that generates the exception.</param> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="FaultGenerator"/>.</returns> + public FaultGenerator AddException(Func<Exception> generator, int weight = DefaultWeight) + { + Guard.NotNull(generator); + + _helper.AddOutcome(_ => Outcome.FromException<VoidResult>(generator()), weight); + + return this; + } + + /// <summary> + /// Registers an exception generator delegate that accepts a <see cref="ResilienceContext"/>. + /// </summary> + /// <param name="generator">The delegate that generates the exception, accepting a <see cref="ResilienceContext"/>.</param> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="FaultGenerator"/>.</returns> + public FaultGenerator AddException(Func<ResilienceContext, Exception> generator, int weight = DefaultWeight) + { + Guard.NotNull(generator); + + _helper.AddOutcome(context => Outcome.FromException<VoidResult>(generator(context)), weight); + + return this; + } + + /// <summary> + /// Registers an exception generator for a specific exception type, using the default constructor of that exception. + /// </summary> + /// <typeparam name="TException">The type of the exception to generate.</typeparam> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="FaultGenerator"/>.</returns> + public FaultGenerator AddException<TException>(int weight = DefaultWeight) + where TException : Exception, new() + { + _helper.AddOutcome(_ => Outcome.FromException<VoidResult>(new TException()), weight); + + return this; + } + + /// <summary> + /// Provides an implicit conversion from <see cref="FaultGenerator"/> to a delegate compatible with <see cref="FaultStrategyOptions.FaultGenerator"/>. + /// </summary> + /// <param name="generator">The instance of <see cref="FaultGenerator"/>.</param> + [EditorBrowsable(EditorBrowsableState.Never)] + public static implicit operator Func<FaultGeneratorArguments, ValueTask<Exception?>>(FaultGenerator generator) + { + Guard.NotNull(generator); + + var generatorDelegate = generator._helper.CreateGenerator(); + + return args => new ValueTask<Exception?>(generatorDelegate(args.Context)!.Value.Exception); + } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs new file mode 100644 index 00000000000..3d1439ab80f --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs @@ -0,0 +1,120 @@ +using System.ComponentModel; +using Polly.Simmy.Utils; + +namespace Polly.Simmy.Outcomes; + +#pragma warning disable CA2225 // Operator overloads have named alternates +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + +/// <summary> +/// Generator that produces faults such as exceptions or results. +/// </summary> +/// <typeparam name="TResult">The type of the result.</typeparam> +/// <remarks> +/// An instance of this class is assignable to <see cref="OutcomeStrategyOptions{TResult}.OutcomeGenerator"/>. +/// </remarks> +public sealed class OutcomeGenerator<TResult> +{ + private const int DefaultWeight = 100; + private readonly GeneratorHelper<TResult> _helper; + + /// <summary> + /// Initializes a new instance of the <see cref="OutcomeGenerator{TResult}"/> class. + /// </summary> + public OutcomeGenerator() + : this(RandomUtil.Instance.Next) + { + } + + internal OutcomeGenerator(Func<int, int> weightGenerator) + => _helper = new GeneratorHelper<TResult>(weightGenerator); + + /// <summary> + /// Registers an exception generator delegate. + /// </summary> + /// <param name="generator">The delegate that generates the exception.</param> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="OutcomeGenerator{TResult}"/>.</returns> + public OutcomeGenerator<TResult> AddException(Func<Exception> generator, int weight = DefaultWeight) + { + Guard.NotNull(generator); + + _helper.AddOutcome(_ => Outcome.FromException<TResult>(generator()), weight); + + return this; + } + + /// <summary> + /// Registers an exception generator delegate that accepts a <see cref="ResilienceContext"/>. + /// </summary> + /// <param name="generator">The delegate that generates the exception, accepting a <see cref="ResilienceContext"/>.</param> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="OutcomeGenerator{TResult}"/>.</returns> + public OutcomeGenerator<TResult> AddException(Func<ResilienceContext, Exception> generator, int weight = DefaultWeight) + { + Guard.NotNull(generator); + + _helper.AddOutcome(context => Outcome.FromException<TResult>(generator(context)), weight); + + return this; + } + + /// <summary> + /// Registers an exception generator for a specific exception type, using the default constructor of that exception. + /// </summary> + /// <typeparam name="TException">The type of the exception to generate.</typeparam> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="OutcomeGenerator{TResult}"/>.</returns> + public OutcomeGenerator<TResult> AddException<TException>(int weight = DefaultWeight) + where TException : Exception, new() + { + _helper.AddOutcome(_ => Outcome.FromException<TResult>(new TException()), weight); + + return this; + } + + /// <summary> + /// Registers a result generator. + /// </summary> + /// <param name="generator">The delegate that generates the result.</param> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="OutcomeGenerator{TResult}"/>.</returns> + public OutcomeGenerator<TResult> AddResult(Func<TResult> generator, int weight = DefaultWeight) + { + Guard.NotNull(generator); + + _helper.AddOutcome(_ => Outcome.FromResult(generator()), weight); + + return this; + } + + /// <summary> + /// Registers a result generator. + /// </summary> + /// <param name="generator">The delegate that generates the result, accepting a <see cref="ResilienceContext"/>.</param> + /// <param name="weight">The weight assigned to this generator. Defaults to <c>100</c>.</param> + /// <returns>The current instance of <see cref="OutcomeGenerator{TResult}"/>.</returns> + public OutcomeGenerator<TResult> AddResult(Func<ResilienceContext, TResult> generator, int weight = DefaultWeight) + { + Guard.NotNull(generator); + + _helper.AddOutcome(context => Outcome.FromResult(generator(context)), weight); + + return this; + } + + /// <summary> + /// Implicit conversion to <see cref="OutcomeStrategyOptions{TResult}.OutcomeGenerator"/>. + /// </summary> + /// <param name="generator">The generator instance.</param> + [EditorBrowsable(EditorBrowsableState.Never)] + public static implicit operator Func<OutcomeGeneratorArguments, ValueTask<Outcome<TResult>?>>(OutcomeGenerator<TResult> generator) + { + Guard.NotNull(generator); + + var generatorDelegate = generator._helper.CreateGenerator(); + + return args => new ValueTask<Outcome<TResult>?>(generatorDelegate(args.Context)); + } +} + diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs deleted file mode 100644 index 44a2ba1e9bb..00000000000 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Polly.Simmy.Outcomes; - -/// <summary> -/// Represents the options for the Outcome chaos strategy. -/// </summary> -/// <typeparam name="TResult">The type of the outcome that was injected.</typeparam> -public class OutcomeStrategyOptions<TResult> : MonkeyStrategyOptions -{ - /// <summary> - /// Initializes a new instance of the <see cref="OutcomeStrategyOptions{TResult}"/> class. - /// </summary> - public OutcomeStrategyOptions() => Name = OutcomeConstants.DefaultName; - - /// <summary> - /// Gets or sets the delegate that's raised when the outcome is injected. - /// </summary> - /// <remarks> - /// Defaults to <see langword="null"/>. - /// </remarks> - public Func<OnOutcomeInjectedArguments<TResult>, ValueTask>? OnOutcomeInjected { get; set; } - - /// <summary> - /// Gets or sets the outcome generator to be injected for a given execution. - /// </summary> - [Required] - public Func<OutcomeGeneratorArguments, ValueTask<Outcome<TResult>?>> OutcomeGenerator { get; set; } = default!; -} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs index 7a29aa8ef5d..fa8f21fddad 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs @@ -1,6 +1,24 @@ -namespace Polly.Simmy.Outcomes; +using System.ComponentModel.DataAnnotations; -/// <inheritdoc/> -public class OutcomeStrategyOptions : OutcomeStrategyOptions<object> +namespace Polly.Simmy.Outcomes; + +/// <summary> +/// Represents the options for the Outcome chaos strategy. +/// </summary> +/// <typeparam name="TResult">The type of the outcome that was injected.</typeparam> +public class OutcomeStrategyOptions<TResult> : MonkeyStrategyOptions { + /// <summary> + /// Gets or sets the delegate that's invoked when the outcome is injected. + /// </summary> + /// <remarks> + /// Defaults to <see langword="null"/>. + /// </remarks> + public Func<OnOutcomeInjectedArguments<TResult>, ValueTask>? OnOutcomeInjected { get; set; } + + /// <summary> + /// Gets or sets the outcome generator to be injected for a given execution. + /// </summary> + [Required] + public Func<OutcomeGeneratorArguments, ValueTask<Outcome<TResult>?>> OutcomeGenerator { get; set; } = default!; } diff --git a/src/Polly.Core/Simmy/Utils/GeneratorHelper.cs b/src/Polly.Core/Simmy/Utils/GeneratorHelper.cs new file mode 100644 index 00000000000..6ec4090a0f2 --- /dev/null +++ b/src/Polly.Core/Simmy/Utils/GeneratorHelper.cs @@ -0,0 +1,52 @@ +namespace Polly.Simmy.Utils; + +internal sealed class GeneratorHelper<TResult> +{ + private readonly Func<int, int> _weightGenerator; + + private readonly List<int> _weights = []; + private readonly List<Func<ResilienceContext, Outcome<TResult>>> _factories = []; + private int _totalWeight; + + public GeneratorHelper(Func<int, int> weightGenerator) => _weightGenerator = weightGenerator; + + public void AddOutcome(Func<ResilienceContext, Outcome<TResult>> generator, int weight) + { + Guard.NotNull(generator); + + _totalWeight += weight; + _factories.Add(generator); + _weights.Add(weight); + } + + internal Func<ResilienceContext, Outcome<TResult>?> CreateGenerator() + { + if (_factories.Count == 0) + { + return _ => null; + } + + var totalWeight = _totalWeight; + var factories = _factories.ToArray(); + var weights = _weights.ToArray(); + var generator = _weightGenerator; + + return context => + { + var generatedWeight = generator(totalWeight); + var weight = 0; + + for (var i = 0; i < factories.Length; i++) + { + weight += weights[i]; + if (generatedWeight < weight) + { + return factories[i](context); + } + } + + return null; + }; + } +} + diff --git a/src/Polly.Core/Utils/RandomUtil.cs b/src/Polly.Core/Utils/RandomUtil.cs index aac063fcc7a..de7bc6ff6ce 100644 --- a/src/Polly.Core/Utils/RandomUtil.cs +++ b/src/Polly.Core/Utils/RandomUtil.cs @@ -13,4 +13,6 @@ internal sealed class RandomUtil public RandomUtil(int? seed) => _random = new ThreadLocal<Random>(() => seed == null ? new Random() : new Random(seed.Value)); public double NextDouble() => _random.Value!.NextDouble(); + + public int Next(int maxValue) => _random.Value!.Next(maxValue); } diff --git a/test/Polly.Core.Tests/Simmy/Fault/FaultGeneratorTests.cs b/test/Polly.Core.Tests/Simmy/Fault/FaultGeneratorTests.cs new file mode 100644 index 00000000000..0ffe3c121cf --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Fault/FaultGeneratorTests.cs @@ -0,0 +1,49 @@ +using System; +using Polly.Simmy.Fault; + +namespace Polly.Core.Tests.Simmy.Fault; + +public class FaultGeneratorTests +{ + [Fact] + public void AddException_Generic_Ok() + { + var generator = new FaultGenerator(); + + generator.AddException<InvalidOperationException>(); + + Generate(generator).Should().BeOfType<InvalidOperationException>(); + } + + [Fact] + public void AddException_Factory_Ok() + { + var generator = new FaultGenerator(); + + generator.AddException(() => new InvalidOperationException()); + + Generate(generator).Should().BeOfType<InvalidOperationException>(); + } + + [Fact] + public void AddException_FactoryWithResilienceContext_Ok() + { + var generator = new FaultGenerator(); + + generator.AddException(context => + { + context.Should().NotBeNull(); + + return new InvalidOperationException(); + }); + + Generate(generator).Should().BeOfType<InvalidOperationException>(); + } + + private static Exception? Generate(FaultGenerator generator) + { + Func<FaultGeneratorArguments, ValueTask<Exception?>> func = generator; + + return func(new FaultGeneratorArguments(ResilienceContextPool.Shared.Get())).AsTask().Result; + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeGeneratorTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeGeneratorTests.cs new file mode 100644 index 00000000000..924de797690 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeGeneratorTests.cs @@ -0,0 +1,77 @@ +using System; +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class OutcomeGeneratorTests +{ + [Fact] + public void AddException_Generic_Ok() + { + var generator = new OutcomeGenerator<string>(); + + generator.AddException<InvalidOperationException>(); + + Generate(generator)!.Value.Exception.Should().BeOfType<InvalidOperationException>(); + } + + [Fact] + public void AddException_Factory_Ok() + { + var generator = new OutcomeGenerator<string>(); + + generator.AddException(() => new InvalidOperationException()); + + Generate(generator)!.Value.Exception.Should().BeOfType<InvalidOperationException>(); + } + + [Fact] + public void AddException_FactoryWithResilienceContext_Ok() + { + var generator = new OutcomeGenerator<string>(); + + generator.AddException(context => + { + context.Should().NotBeNull(); + + return new InvalidOperationException(); + }); + + Generate(generator)!.Value.Exception.Should().BeOfType<InvalidOperationException>(); + } + + [Fact] + public void AddResult_Factory_Ok() + { + var generator = new OutcomeGenerator<string>(); + + generator.AddResult(() => + { + return "dummy"; + }); + + Generate(generator)!.Value.Result.Should().Be("dummy"); + } + + [Fact] + public void AddResult_FactoryWithResilienceContext_Ok() + { + var generator = new OutcomeGenerator<string>(); + + generator.AddResult(context => + { + context.Should().NotBeNull(); + + return "dummy"; + }); + + Generate(generator)!.Value.Result.Should().Be("dummy"); + } + + private static Outcome<string>? Generate(OutcomeGenerator<string> generator) + { + Func<OutcomeGeneratorArguments, ValueTask<Outcome<string>?>> func = generator; + + return func(new OutcomeGeneratorArguments(ResilienceContextPool.Shared.Get())).AsTask().Result; + } +} diff --git a/test/Polly.Core.Tests/Simmy/Utils/GeneratorHelperTests.cs b/test/Polly.Core.Tests/Simmy/Utils/GeneratorHelperTests.cs new file mode 100644 index 00000000000..258124cec77 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Utils/GeneratorHelperTests.cs @@ -0,0 +1,56 @@ +using System; +using Polly.Simmy.Utils; + +namespace Polly.Core.Tests.Simmy.Utils; + +public class GeneratorHelperTests +{ + [Fact] + public void CreateGenerator_NoGenerators_Ok() + { + var helper = new GeneratorHelper<int>(_ => 10); + + helper.CreateGenerator()(ResilienceContextPool.Shared.Get()).Should().BeNull(); + } + + [Fact] + public void AddOutcome_EnsureWeightRespected() + { + int weight = 0; + int maxWeight = 0; + var context = ResilienceContextPool.Shared.Get(); + + var helper = new GeneratorHelper<int>(max => + { + maxWeight = max; + return weight; + }); + + helper.AddOutcome(_ => Outcome.FromResult(1), 40); + helper.AddOutcome(_ => Outcome.FromResult(2), 80); + + var generator = helper.CreateGenerator(); + + weight = 0; + generator(context)!.Value.Result.Should().Be(1); + weight = 39; + generator(context)!.Value.Result.Should().Be(1); + + weight = 40; + generator(context)!.Value.Result.Should().Be(2); + + maxWeight.Should().Be(120); + } + + [Fact] + public void Generator_OutsideRange_ReturnsNull() + { + var context = ResilienceContextPool.Shared.Get(); + var helper = new GeneratorHelper<int>(_ => 1000); + + helper.AddOutcome(_ => Outcome.FromResult(1), 40); + + var generator = helper.CreateGenerator(); + generator(context).Should().BeNull(); + } +} From ad256009957ebfb3ab5a03c66a2dc3157868e8fd Mon Sep 17 00:00:00 2001 From: peter-csala <csalasoft@hotmail.com> Date: Fri, 19 Jan 2024 11:34:23 +0100 Subject: [PATCH 12/12] Apply suggestions for Outcome chaos strategy --- src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs index fa8f21fddad..31867aafbf2 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs @@ -8,6 +8,11 @@ namespace Polly.Simmy.Outcomes; /// <typeparam name="TResult">The type of the outcome that was injected.</typeparam> public class OutcomeStrategyOptions<TResult> : MonkeyStrategyOptions { + /// <summary> + /// Initializes a new instance of the <see cref="OutcomeStrategyOptions{TResult}"/> class. + /// </summary> + public OutcomeStrategyOptions() => Name = OutcomeConstants.DefaultName; + /// <summary> /// Gets or sets the delegate that's invoked when the outcome is injected. /// </summary>