From 9641b20e3ff1ef2b4484e310a94db69652825055 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 25 Jun 2023 17:27:08 -0500 Subject: [PATCH 01/36] * add MonkeyStrategy as the main ChaosStrategy class which all chaos strategies will inherit from * add LatencyChaosStrategy Note: this is still work in progress, I'm just getting familiar with the v8 implemenation. --- src/Polly.Core/Polly.Core.csproj | 5 ++ .../Simmy/Latency/LatencyChaosStrategy.cs | 58 +++++++++++++++++++ .../Simmy/Latency/LatencyConstants.cs | 8 +++ .../Simmy/Latency/LatencyStrategyOptions.cs | 33 +++++++++++ .../Simmy/Latency/OnDelayedArguments.cs | 8 +++ src/Polly.Core/Simmy/MonkeyStrategy.cs | 54 +++++++++++++++++ .../Simmy/MonkeyStrategyConstants.cs | 8 +++ src/Polly.Core/Simmy/MonkeyStrategyOptions.cs | 36 ++++++++++++ src/Polly.Core/Simmy/Utils/GuardExtensions.cs | 16 +++++ 9 files changed, 226 insertions(+) create mode 100644 src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs create mode 100644 src/Polly.Core/Simmy/Latency/LatencyConstants.cs create mode 100644 src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs create mode 100644 src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs create mode 100644 src/Polly.Core/Simmy/MonkeyStrategy.cs create mode 100644 src/Polly.Core/Simmy/MonkeyStrategyConstants.cs create mode 100644 src/Polly.Core/Simmy/MonkeyStrategyOptions.cs create mode 100644 src/Polly.Core/Simmy/Utils/GuardExtensions.cs diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index c9747fcc2c7..30cfe2218d4 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -26,4 +26,9 @@ + + + + + diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs new file mode 100644 index 00000000000..254ef9bdb0d --- /dev/null +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -0,0 +1,58 @@ +using Polly.Telemetry; + +namespace Polly.Simmy.Latency; + +internal sealed class LatencyChaosStrategy : MonkeyStrategy +{ + private readonly RandomUtil _randomUtil; + private readonly TimeProvider _timeProvider; + private readonly ResilienceStrategyTelemetry _telemetry; + + public LatencyChaosStrategy( + LatencyStrategyOptions options, + TimeProvider timeProvider, + ResilienceStrategyTelemetry telemetry) + : base(options.InjectionRate, options.Enabled) + { + Guard.NotNull(telemetry); + Guard.NotNull(timeProvider); + Guard.NotNull(options.LatencyGenerator); + + OnDelayed = options.OnDelayed; + LatencyGenerator = options.LatencyGenerator; + _telemetry = telemetry; + _timeProvider = timeProvider; + _randomUtil = options.RandomUtil; + } + + public Func? OnDelayed { get; } + + public Func> LatencyGenerator { get; } + + protected internal override async ValueTask> ExecuteCoreAsync( + Func>> callback, ResilienceContext context, TState state) + { + if (await ShouldInject(context, _randomUtil).ConfigureAwait(context.ContinueOnCapturedContext)) + { + try + { + var latency = await LatencyGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext); + await _timeProvider.DelayAsync(latency, context).ConfigureAwait(context.ContinueOnCapturedContext); + + var args = new OnDelayedArguments(context, latency); + _telemetry.Report(LatencyConstants.OnDelayedEvent, context, args); + + if (OnDelayed is not null) + { + await OnDelayed(args).ConfigureAwait(context.ContinueOnCapturedContext); + } + } + catch (OperationCanceledException e) + { + return new Outcome(e); + } + } + + return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); + } +} diff --git a/src/Polly.Core/Simmy/Latency/LatencyConstants.cs b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs new file mode 100644 index 00000000000..9c797f28699 --- /dev/null +++ b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs @@ -0,0 +1,8 @@ +namespace Polly.Simmy.Latency; + +internal static class LatencyConstants +{ + public const string StrategyType = "Latency"; + + public const string OnDelayedEvent = "OnDelayed"; +} diff --git a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs new file mode 100644 index 00000000000..a20e6f899e8 --- /dev/null +++ b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; + +namespace Polly.Simmy.Latency; + +#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null + +/// +/// Represents the options for the Latency chaos strategy. +/// +public class LatencyStrategyOptions : MonkeyStrategyOptions +{ + /// + /// Gets the strategy type. + /// + public sealed override string StrategyType => LatencyConstants.StrategyType; + + /// + /// Gets or sets the delegate that's raised when delay occurs. + /// + /// + /// Defaults to . + /// + public Func? OnDelayed { get; set; } + + /// + /// Gets or sets the latency generator that generates the delay for a given execution. + /// + /// + /// Defaults to . This property is required. + /// + [Required] + public Func> LatencyGenerator { get; set; } +} diff --git a/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs b/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs new file mode 100644 index 00000000000..a387b2f186b --- /dev/null +++ b/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs @@ -0,0 +1,8 @@ +namespace Polly.Simmy.Latency; + +/// +/// Arguments used by the latency strategy to notify that a delayed occurred. +/// +/// The context associated with the execution of a user-provided callback. +/// The timeout value assigned. +public readonly record struct OnDelayedArguments(ResilienceContext Context, TimeSpan Latency); diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.cs b/src/Polly.Core/Simmy/MonkeyStrategy.cs new file mode 100644 index 00000000000..4ce465ab1fe --- /dev/null +++ b/src/Polly.Core/Simmy/MonkeyStrategy.cs @@ -0,0 +1,54 @@ +using Polly.Simmy.Utils; + +namespace Polly.Simmy; + +#pragma warning disable CA1031 // Do not catch general exception types + +/// +/// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. +/// +public abstract class MonkeyStrategy : ResilienceStrategy +{ + /// + /// Initializes a new instance of the class. + /// + /// Delegate that determines the injection rate. + /// Delegate that determines whether or not the chaos is enabled. + protected MonkeyStrategy( + Func> injectionRate, Func> enabled) + { + Guard.NotNull(enabled); + Guard.NotNull(injectionRate); + + InjectionRate = injectionRate; + Enabled = enabled; + } + + internal Func> InjectionRate { get; } + + internal Func> Enabled { get; } + + internal async ValueTask ShouldInject(ResilienceContext context, RandomUtil randomUtil) + { + Guard.NotNull(randomUtil); + + // to prevent executing config delegates if token was signaled before to start. + context.CancellationToken.ThrowIfCancellationRequested(); + + if (!await Enabled(context).ConfigureAwait(context.ContinueOnCapturedContext)) + { + return false; + } + + // to prevent executing InjectionRate config delegate if token was signaled on Enabled configuration delegate. + context.CancellationToken.ThrowIfCancellationRequested(); + + double injectionThreshold = await InjectionRate(context).ConfigureAwait(context.ContinueOnCapturedContext); + + // to prevent executing further config delegates if token was signaled on InjectionRate configuration delegate. + context.CancellationToken.ThrowIfCancellationRequested(); + + injectionThreshold.EnsureInjectionThreshold(); + return randomUtil.NextDouble() < injectionThreshold; + } +} diff --git a/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs b/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs new file mode 100644 index 00000000000..db2cbe6bb83 --- /dev/null +++ b/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs @@ -0,0 +1,8 @@ +namespace Polly.Simmy; + +internal static class MonkeyStrategyConstants +{ + public const int MinInjectionThreshold = 0; + + public const int MaxInjectionThreshold = 1; +} diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs new file mode 100644 index 00000000000..6fd66cd90e1 --- /dev/null +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; + +namespace Polly.Simmy; + +#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null + +/// +/// The options associated with the . +/// +public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions +{ + /// + /// Gets or sets the lambda to get injection rate between [0, 1]. + /// + /// + /// Defaults to . This property is required. + /// + [Required] + [Range(MonkeyStrategyConstants.MinInjectionThreshold, MonkeyStrategyConstants.MaxInjectionThreshold)] + public Func> InjectionRate { get; set; } + + /// + /// Gets or sets tge lambda to check if this policy is enabled in current context. + /// + /// + /// Defaults to . This property is required. + /// + [Required] + public Func> Enabled { get; set; } + + /// + /// Gets or sets the instance. + /// + [Required] + internal RandomUtil RandomUtil { get; set; } +} diff --git a/src/Polly.Core/Simmy/Utils/GuardExtensions.cs b/src/Polly.Core/Simmy/Utils/GuardExtensions.cs new file mode 100644 index 00000000000..358961c0dab --- /dev/null +++ b/src/Polly.Core/Simmy/Utils/GuardExtensions.cs @@ -0,0 +1,16 @@ +namespace Polly.Simmy.Utils; + +internal static class GuardExtensions +{ + public static void EnsureInjectionThreshold(this double injectionThreshold) + { + if (injectionThreshold < MonkeyStrategyConstants.MinInjectionThreshold) + { + throw new ArgumentOutOfRangeException(nameof(injectionThreshold), "Injection rate/threshold in Monkey strategies should always be a double between [0, 1]; never a negative number."); + } + if (injectionThreshold > MonkeyStrategyConstants.MaxInjectionThreshold) + { + throw new ArgumentOutOfRangeException(nameof(injectionThreshold), "Injection rate/threshold in Monkey strategies should always be a double between [0, 1]; never a number greater than 1."); + } + } +} From fadd5b29d2b59b0c083646ffd22a3e37e32e7ac7 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 25 Jun 2023 21:04:17 -0500 Subject: [PATCH 02/36] * add LatencyChaosStrategyBuilderExtensions: still need to add the generic options --- .../Simmy/Latency/LatencyChaosStrategy.cs | 2 +- .../LatencyChaosStrategyBuilderExtensions.cs | 48 +++++++++++++++++++ .../Simmy/Latency/LatencyStrategyOptions.cs | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index 254ef9bdb0d..430de7df6a4 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -27,7 +27,7 @@ public LatencyChaosStrategy( public Func? OnDelayed { get; } - public Func> LatencyGenerator { get; } + public Func> LatencyGenerator { get; } protected internal override async ValueTask> ExecuteCoreAsync( Func>> callback, ResilienceContext context, TState state) diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs new file mode 100644 index 00000000000..959be29439a --- /dev/null +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; + +namespace Polly.Simmy.Latency; + +/// +/// Extension methods for adding timeouts to a . +/// +public static class LatencyChaosStrategyBuilderExtensions +{ + /// + /// Adds a latency chaos strategy to the builder. + /// + /// The builder type. + /// The builder instance. + /// The delay value. + /// The same builder instance. + /// Thrown when is . + /// Thrown when the options produced from the arguments are invalid. + public static TBuilder AddLatency(this TBuilder builder, TimeSpan delay) + where TBuilder : ResilienceStrategyBuilderBase + { + Guard.NotNull(builder); + + return builder.AddLatency(new LatencyStrategyOptions + { + LatencyGenerator = (_) => new(delay) + }); + } + + /// + /// Adds a latency chaos strategy to the builder. + /// + /// The builder type. + /// The builder instance. + /// The latency options. + /// The same builder instance. + /// Thrown when or is . + /// Thrown when are invalid. + public static TBuilder AddLatency(this TBuilder builder, LatencyStrategyOptions options) + where TBuilder : ResilienceStrategyBuilderBase + { + Guard.NotNull(builder); + Guard.NotNull(options); + + builder.AddStrategy(context => new LatencyChaosStrategy(options, context.TimeProvider, context.Telemetry), options); + return builder; + } +} diff --git a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs index a20e6f899e8..7957a75e3f0 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs @@ -29,5 +29,5 @@ public class LatencyStrategyOptions : MonkeyStrategyOptions /// Defaults to . This property is required. /// [Required] - public Func> LatencyGenerator { get; set; } + public Func> LatencyGenerator { get; set; } } From 25b15d83f3cc078c91f028c45f6d514cc5dba44b Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 1 Jul 2023 15:14:55 -0500 Subject: [PATCH 03/36] * allows passing both a generator or a primitive value for injection rate and enabled options * refactor MonkeyStrategy so that it is instantiated out of options object rather than individual parameters * expose ShouldInject method in the MonkeyStrategy so that consumers who extend/inherit it can also use it for convenience * add Latency property to the LatencyStrategyOptions as a primitive value to not force consumers to always pass a generator * refactors the LatencyChaosStrategy so that the code it's wrap inside a try/catch to handle canceled operations, since the execution can be also canceled within the ShouldInject method or even within the ExecuteCallbackSafeAsync --- .../Simmy/Latency/LatencyChaosStrategy.cs | 26 ++++----- .../Simmy/Latency/LatencyStrategyOptions.cs | 17 ++++-- src/Polly.Core/Simmy/MonkeyStrategy.cs | 54 +++++++++++++------ src/Polly.Core/Simmy/MonkeyStrategyOptions.cs | 42 +++++++++++---- 4 files changed, 97 insertions(+), 42 deletions(-) diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index 430de7df6a4..bcb89bad280 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -4,7 +4,6 @@ namespace Polly.Simmy.Latency; internal sealed class LatencyChaosStrategy : MonkeyStrategy { - private readonly RandomUtil _randomUtil; private readonly TimeProvider _timeProvider; private readonly ResilienceStrategyTelemetry _telemetry; @@ -12,29 +11,32 @@ public LatencyChaosStrategy( LatencyStrategyOptions options, TimeProvider timeProvider, ResilienceStrategyTelemetry telemetry) - : base(options.InjectionRate, options.Enabled) + : base(options) { Guard.NotNull(telemetry); Guard.NotNull(timeProvider); Guard.NotNull(options.LatencyGenerator); OnDelayed = options.OnDelayed; - LatencyGenerator = options.LatencyGenerator; + Latency = options.Latency; + LatencyGenerator = options.Latency.HasValue ? (_) => new(options.Latency.Value) : options.LatencyGenerator; + _telemetry = telemetry; _timeProvider = timeProvider; - _randomUtil = options.RandomUtil; } public Func? OnDelayed { get; } public Func> LatencyGenerator { get; } + public TimeSpan? Latency { get; } + protected internal override async ValueTask> ExecuteCoreAsync( Func>> callback, ResilienceContext context, TState state) { - if (await ShouldInject(context, _randomUtil).ConfigureAwait(context.ContinueOnCapturedContext)) + try { - try + if (await ShouldInject(context).ConfigureAwait(context.ContinueOnCapturedContext)) { var latency = await LatencyGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext); await _timeProvider.DelayAsync(latency, context).ConfigureAwait(context.ContinueOnCapturedContext); @@ -47,12 +49,12 @@ protected internal override async ValueTask> ExecuteCoreAsync(e); - } - } - return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); + return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); + } + catch (OperationCanceledException e) + { + return new Outcome(e); + } } } diff --git a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs index 7957a75e3f0..7167e111126 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations; - -namespace Polly.Simmy.Latency; +namespace Polly.Simmy.Latency; #pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null @@ -26,8 +24,17 @@ public class LatencyStrategyOptions : MonkeyStrategyOptions /// Gets or sets the latency generator that generates the delay for a given execution. /// /// - /// Defaults to . This property is required. + /// Defaults to . Either or this property is required. + /// When this property is the is used. /// - [Required] public Func> LatencyGenerator { get; set; } + + /// + /// Gets or sets the delay for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public TimeSpan? Latency { get; set; } } diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.cs b/src/Polly.Core/Simmy/MonkeyStrategy.cs index 4ce465ab1fe..caa046d60bd 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.cs @@ -3,39 +3,63 @@ namespace Polly.Simmy; #pragma warning disable CA1031 // Do not catch general exception types +#pragma warning disable S3928 // Custom ArgumentNullException message /// /// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. /// public abstract class MonkeyStrategy : ResilienceStrategy { + private readonly Func _randomizer; + /// /// Initializes a new instance of the class. /// - /// Delegate that determines the injection rate. - /// Delegate that determines whether or not the chaos is enabled. - protected MonkeyStrategy( - Func> injectionRate, Func> enabled) + /// The chaos strategy options. + protected MonkeyStrategy(MonkeyStrategyOptions options) { - Guard.NotNull(enabled); - Guard.NotNull(injectionRate); + Guard.NotNull(options); + Guard.NotNull(options.Randomizer); + + if (options.InjectionRate is null && options.InjectionRateGenerator is null) + { + throw new ArgumentNullException(nameof(options.InjectionRate), "Either InjectionRate or InjectionRateGenerator is required."); + } + + if (options.Enabled is null && options.EnabledGenerator is null) + { + throw new ArgumentNullException(nameof(options.Enabled), "Either Enabled or EnabledGenerator is required."); + } - InjectionRate = injectionRate; - Enabled = enabled; + _randomizer = options.Randomizer; + InjectionRateGenerator = options.InjectionRate.HasValue ? (_) => new(options.InjectionRate.Value) : options.InjectionRateGenerator; + EnabledGenerator = options.Enabled.HasValue ? (_) => new(options.Enabled.Value) : options.EnabledGenerator; } - internal Func> InjectionRate { get; } + /// + /// Gets the injection rate for a given execution, which the value should be between [0, 1]. + /// + public Func> InjectionRateGenerator { get; } - internal Func> Enabled { get; } + /// + /// Gets a value that indicates whether or not the chaos strategy is enabled for a given execution. + /// + public Func> EnabledGenerator { get; } - internal async ValueTask ShouldInject(ResilienceContext context, RandomUtil randomUtil) + /// + /// Determines whether or not the chaos strategy should be injected based on the injection rate and enabled flag. + /// + /// The instance. + /// A boolean value that indicates whether or not the chaos strategy should be injected. + /// Use this method before injecting any chaos strategy to evaluate whether a given chaos strategy needs to be injected during the execution. + public async ValueTask ShouldInject(ResilienceContext context) { - Guard.NotNull(randomUtil); + Guard.NotNull(context); // to prevent executing config delegates if token was signaled before to start. context.CancellationToken.ThrowIfCancellationRequested(); - if (!await Enabled(context).ConfigureAwait(context.ContinueOnCapturedContext)) + if (!await EnabledGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext)) { return false; } @@ -43,12 +67,12 @@ internal async ValueTask ShouldInject(ResilienceContext context, RandomUti // to prevent executing InjectionRate config delegate if token was signaled on Enabled configuration delegate. context.CancellationToken.ThrowIfCancellationRequested(); - double injectionThreshold = await InjectionRate(context).ConfigureAwait(context.ContinueOnCapturedContext); + double injectionThreshold = await InjectionRateGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext); // to prevent executing further config delegates if token was signaled on InjectionRate configuration delegate. context.CancellationToken.ThrowIfCancellationRequested(); injectionThreshold.EnsureInjectionThreshold(); - return randomUtil.NextDouble() < injectionThreshold; + return _randomizer() < injectionThreshold; } } diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs index 6fd66cd90e1..0170d3ac24d 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs @@ -10,27 +10,49 @@ namespace Polly.Simmy; public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions { /// - /// Gets or sets the lambda to get injection rate between [0, 1]. + /// Gets or sets the injection rate for a given execution, which the value should be between [0, 1]. /// /// - /// Defaults to . This property is required. + /// Defaults to . Either or this property is required. + /// When this property is the is used. /// - [Required] [Range(MonkeyStrategyConstants.MinInjectionThreshold, MonkeyStrategyConstants.MaxInjectionThreshold)] - public Func> InjectionRate { get; set; } + public double? InjectionRate { get; set; } /// - /// Gets or sets tge lambda to check if this policy is enabled in current context. + /// Gets or sets the injection rate generator for a given execution, which the value should be between [0, 1]. /// /// - /// Defaults to . This property is required. + /// Defaults to . Either or this property is required. + /// When this property is the is used. /// - [Required] - public Func> Enabled { get; set; } + public Func> InjectionRateGenerator { get; set; } + + /// + /// Gets or sets the enable generator that indicates whether or not the chaos strategy is enabled for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public Func> EnabledGenerator { get; set; } /// - /// Gets or sets the instance. + /// Gets or sets a value that indicates whether or not the chaos strategy is enabled for a given execution. /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public bool? Enabled { get; set; } + + /// + /// Gets or sets the Randomizer generator instance that is used to evaluate the injection rate. + /// + /// + /// The default randomizer is thread safe and returns values between 0.0 and 1.0. + /// [Required] - internal RandomUtil RandomUtil { get; set; } + public Func Randomizer { get; set; } = RandomUtil.Instance.NextDouble; } + From 4d9852446df3f0d51706e789248c662c03584743 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 1 Jul 2023 15:28:22 -0500 Subject: [PATCH 04/36] fixes after synching with the main branch --- src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index bcb89bad280..1457b5bc25f 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -42,7 +42,7 @@ protected internal override async ValueTask> ExecuteCoreAsync Date: Sun, 2 Jul 2023 15:36:04 -0500 Subject: [PATCH 05/36] fate time provider is not working, it gets hung forever --- .../Simmy/Latency/LatencyChaosStrategy.cs | 10 +++- .../Latency/LatencyChaosStrategyTests.cs | 52 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index 1457b5bc25f..de4da974032 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -2,6 +2,8 @@ namespace Polly.Simmy.Latency; +#pragma warning disable S3928 // Custom ArgumentNullException message + internal sealed class LatencyChaosStrategy : MonkeyStrategy { private readonly TimeProvider _timeProvider; @@ -15,11 +17,15 @@ public LatencyChaosStrategy( { Guard.NotNull(telemetry); Guard.NotNull(timeProvider); - Guard.NotNull(options.LatencyGenerator); - OnDelayed = options.OnDelayed; + if (options.Latency is null && options.LatencyGenerator is null) + { + throw new ArgumentNullException(nameof(options.Latency), "Either Latency or LatencyGenerator is required."); + } + Latency = options.Latency; LatencyGenerator = options.Latency.HasValue ? (_) => new(options.Latency.Value) : options.LatencyGenerator; + OnDelayed = options.OnDelayed; _telemetry = telemetry; _timeProvider = timeProvider; diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs new file mode 100644 index 00000000000..ff0d5ab9b3d --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Time.Testing; +using Moq; +using Polly.Simmy.Latency; +using Polly.Telemetry; + +namespace Polly.Core.Tests.Simmy.Latency; + +public class LatencyChaosStrategyTests : IDisposable +{ + private readonly ResilienceStrategyTelemetry _telemetry; + private readonly FakeTimeProvider _timeProvider = new(); + private readonly LatencyStrategyOptions _options; + private readonly CancellationTokenSource _cancellationSource; + private readonly TimeSpan _delay = TimeSpan.FromMilliseconds(500); + + private readonly Mock _diagnosticSource = new(); + + public LatencyChaosStrategyTests() + { + _telemetry = TestUtilities.CreateResilienceTelemetry(_diagnosticSource.Object); + _options = new LatencyStrategyOptions(); + _cancellationSource = new CancellationTokenSource(); + } + + public void Dispose() => _cancellationSource.Dispose(); + + [Fact] + public void InjectLatency_Context_Free_Should_Introduce_Delay_If_Enabled() + { + var executed = false; + + _options.InjectionRate = 0.6; + _options.Enabled = true; + _options.Latency = _delay; + _options.Randomizer = () => 0.5; + + //var sut = CreateSut(); + + //var before = _timeProvider.GetUtcNow(); + + //sut.Execute(_ => executed = true); + + //executed.Should().BeTrue(); + + //var after = _timeProvider.GetUtcNow(); + //(after - before).Should().BeGreaterThanOrEqualTo(_delay); + + executed.Should().BeFalse(); + } + + private LatencyChaosStrategy CreateSut() => new(_options, _timeProvider, _telemetry); +} From af7e4cf5b66a8ba81728787206ccaf58c4691ee9 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 2 Jul 2023 16:20:06 -0500 Subject: [PATCH 06/36] add behavior chaos strategy --- src/Polly.Core/Polly.Core.csproj | 1 - .../Simmy/Behavior/BehaviorChaosStrategy.cs | 51 +++++++++++++++++++ .../BehaviorChaosStrategyBuilderExtensions.cs | 48 +++++++++++++++++ .../Simmy/Behavior/BehaviorConstants.cs | 8 +++ .../Simmy/Behavior/BehaviorStrategyOptions.cs | 30 +++++++++++ .../Behavior/OnBehaviorInjectedArguments.cs | 7 +++ .../LatencyChaosStrategyBuilderExtensions.cs | 2 +- 7 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs create mode 100644 src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs create mode 100644 src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs create mode 100644 src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs create mode 100644 src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index 30cfe2218d4..d03860ecb19 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -27,7 +27,6 @@ - diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs new file mode 100644 index 00000000000..fccbb7c2c45 --- /dev/null +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs @@ -0,0 +1,51 @@ +using Polly.Telemetry; + +namespace Polly.Simmy.Behavior; + +internal sealed class BehaviorChaosStrategy : MonkeyStrategy +{ + private readonly ResilienceStrategyTelemetry _telemetry; + + public BehaviorChaosStrategy( + BehaviorStrategyOptions options, + ResilienceStrategyTelemetry telemetry) + : base(options) + { + Guard.NotNull(telemetry); + Guard.NotNull(options.Behavior); + + _telemetry = telemetry; + OnBehaviorInjected = options.OnBehaviorInjected; + Behavior = options.Behavior; + } + + public Func? OnBehaviorInjected { get; } + + public Func Behavior { get; } + + protected internal override async ValueTask> ExecuteCoreAsync( + Func>> callback, ResilienceContext context, TState state) + { + try + { + if (await ShouldInject(context).ConfigureAwait(context.ContinueOnCapturedContext)) + { + await Behavior(context).ConfigureAwait(context.ContinueOnCapturedContext); + + var args = new OnBehaviorInjectedArguments(context); + _telemetry.Report(new(ResilienceEventSeverity.Warning, BehaviorConstants.OnBehaviorInjectedEvent), context, args); + + if (OnBehaviorInjected is not null) + { + await OnBehaviorInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); + } + } + + return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); + } + catch (OperationCanceledException e) + { + return new Outcome(e); + } + } +} diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs new file mode 100644 index 00000000000..07f98c3a046 --- /dev/null +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; + +namespace Polly.Simmy.Behavior; + +/// +/// Extension methods for adding custom behaviors to a . +/// +public static class BehaviorChaosStrategyBuilderExtensions +{ + /// + /// Adds a latency chaos strategy to the builder. + /// + /// The builder type. + /// The builder instance. + /// The behavior to be injected. + /// The same builder instance. + /// Thrown when is . + /// Thrown when the options produced from the arguments are invalid. + public static TBuilder AddBehavior(this TBuilder builder, Func behavior) + where TBuilder : ResilienceStrategyBuilderBase + { + Guard.NotNull(builder); + + return builder.AddBehavior(new BehaviorStrategyOptions + { + Behavior = (_) => behavior() + }); + } + + /// + /// Adds a behavior chaos strategy to the builder. + /// + /// The builder type. + /// The builder instance. + /// The behavior options. + /// The same builder instance. + /// Thrown when or is . + /// Thrown when are invalid. + public static TBuilder AddBehavior(this TBuilder builder, BehaviorStrategyOptions options) + where TBuilder : ResilienceStrategyBuilderBase + { + Guard.NotNull(builder); + Guard.NotNull(options); + + builder.AddStrategy(context => new BehaviorChaosStrategy(options, context.Telemetry), options); + return builder; + } +} diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs b/src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs new file mode 100644 index 00000000000..8b3dbba2289 --- /dev/null +++ b/src/Polly.Core/Simmy/Behavior/BehaviorConstants.cs @@ -0,0 +1,8 @@ +namespace Polly.Simmy.Behavior; + +internal static class BehaviorConstants +{ + public const string StrategyType = "Behavior"; + + public const string OnBehaviorInjectedEvent = "OnBehaviorInjected"; +} diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs new file mode 100644 index 00000000000..c509ade8dec --- /dev/null +++ b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs @@ -0,0 +1,30 @@ +namespace Polly.Simmy.Behavior; + +#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null + +/// +/// Represents the options for the Behavior chaos strategy. +/// +public class BehaviorStrategyOptions : MonkeyStrategyOptions +{ + /// + /// Gets the strategy type. + /// + public sealed override string StrategyType => BehaviorConstants.StrategyType; + + /// + /// Gets or sets the delegate that's raised when the custom behavior is injected. + /// + /// + /// Defaults to . + /// + public Func? OnBehaviorInjected { get; set; } + + /// + /// Gets or sets the custom behavior that is going to be injected for a given execution. + /// + /// + /// Defaults to . + /// + public Func Behavior { get; set; } +} diff --git a/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs b/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs new file mode 100644 index 00000000000..ca601a58175 --- /dev/null +++ b/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs @@ -0,0 +1,7 @@ +namespace Polly.Simmy.Behavior; + +/// +/// Arguments used by the latency strategy to notify that a delayed occurred. +/// +/// The context associated with the execution of a user-provided callback. +public readonly record struct OnBehaviorInjectedArguments(ResilienceContext Context); diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs index 959be29439a..fc0fc173f62 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs @@ -3,7 +3,7 @@ namespace Polly.Simmy.Latency; /// -/// Extension methods for adding timeouts to a . +/// Extension methods for adding latency to a . /// public static class LatencyChaosStrategyBuilderExtensions { From 17d1bb4f52520a0c65ffba94ebc5871729861e51 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 2 Jul 2023 18:41:36 -0500 Subject: [PATCH 07/36] OutComeChaosStrategy implemenation: WIP --- src/Polly.Core/Polly.Core.csproj | 4 -- .../Behavior/OnBehaviorInjectedArguments.cs | 2 +- .../Simmy/Latency/OnDelayedArguments.cs | 2 +- .../Simmy/MonkeyStrategy.TResult.cs | 17 ++++++ .../Simmy/MonkeyStrategyOptions.TResult.cs | 59 ++++++++++++++++++ src/Polly.Core/Simmy/MonkeyStrategyOptions.cs | 51 +--------------- .../Outcomes/OnOutcomeInjectedArguments.cs | 10 ++++ .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 60 +++++++++++++++++++ .../Simmy/Outcomes/OutcomeConstants.cs | 8 +++ .../OutcomeStrategyOptions.TResult.cs | 41 +++++++++++++ .../Simmy/Outcomes/OutcomeStrategyOptions.cs | 6 ++ 11 files changed, 205 insertions(+), 55 deletions(-) create mode 100644 src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs create mode 100644 src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index d03860ecb19..c9747fcc2c7 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -26,8 +26,4 @@ - - - - diff --git a/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs b/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs index ca601a58175..6b8aad29456 100644 --- a/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs +++ b/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs @@ -1,7 +1,7 @@ namespace Polly.Simmy.Behavior; /// -/// Arguments used by the latency strategy to notify that a delayed occurred. +/// Arguments used by the behavior chaos strategy to notify that a custom behavior was injected. /// /// The context associated with the execution of a user-provided callback. public readonly record struct OnBehaviorInjectedArguments(ResilienceContext Context); diff --git a/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs b/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs index a387b2f186b..c2ae7d2bf1f 100644 --- a/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs +++ b/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs @@ -1,7 +1,7 @@ namespace Polly.Simmy.Latency; /// -/// Arguments used by the latency strategy to notify that a delayed occurred. +/// Arguments used by the latency chaos strategy to notify that a delayed occurred. /// /// The context associated with the execution of a user-provided callback. /// The timeout value assigned. diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs new file mode 100644 index 00000000000..110e010e8ae --- /dev/null +++ b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs @@ -0,0 +1,17 @@ +namespace Polly.Simmy; + +/// +/// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. +/// +/// The type of result this strategy supports. +public abstract class MonkeyStrategy : MonkeyStrategy +{ + /// + /// Initializes a new instance of the class. + /// + /// The chaos strategy options. + protected MonkeyStrategy(MonkeyStrategyOptions options) + : base(options) + { + } +} diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs new file mode 100644 index 00000000000..b4f7d89d9f5 --- /dev/null +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations; + +namespace Polly.Simmy; + +#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null + +/// +/// The options associated with the . +/// +/// The type of result the retry strategy handles. +public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions +{ + /// + /// Gets or sets the injection rate for a given execution, which the value should be between [0, 1]. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + [Range(MonkeyStrategyConstants.MinInjectionThreshold, MonkeyStrategyConstants.MaxInjectionThreshold)] + public double? InjectionRate { get; set; } + + /// + /// Gets or sets the injection rate generator for a given execution, which the value should be between [0, 1]. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public Func> InjectionRateGenerator { get; set; } + + /// + /// Gets or sets the enable generator that indicates whether or not the chaos strategy is enabled for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public Func> EnabledGenerator { get; set; } + + /// + /// Gets or sets a value that indicates whether or not the chaos strategy is enabled for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public bool? Enabled { get; set; } + + /// + /// Gets or sets the Randomizer generator instance that is used to evaluate the injection rate. + /// + /// + /// The default randomizer is thread safe and returns values between 0.0 and 1.0. + /// + [Required] + public Func Randomizer { get; set; } = RandomUtil.Instance.NextDouble; +} + diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs index 0170d3ac24d..72100ea21f9 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs @@ -1,58 +1,11 @@ -using System.ComponentModel.DataAnnotations; - -namespace Polly.Simmy; +namespace Polly.Simmy; #pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null /// /// The options associated with the . /// -public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions +public abstract class MonkeyStrategyOptions : MonkeyStrategyOptions { - /// - /// Gets or sets the injection rate for a given execution, which the value should be between [0, 1]. - /// - /// - /// Defaults to . Either or this property is required. - /// When this property is the is used. - /// - [Range(MonkeyStrategyConstants.MinInjectionThreshold, MonkeyStrategyConstants.MaxInjectionThreshold)] - public double? InjectionRate { get; set; } - - /// - /// Gets or sets the injection rate generator for a given execution, which the value should be between [0, 1]. - /// - /// - /// Defaults to . Either or this property is required. - /// When this property is the is used. - /// - public Func> InjectionRateGenerator { get; set; } - - /// - /// Gets or sets the enable generator that indicates whether or not the chaos strategy is enabled for a given execution. - /// - /// - /// Defaults to . Either or this property is required. - /// When this property is the is used. - /// - public Func> EnabledGenerator { get; set; } - - /// - /// Gets or sets a value that indicates whether or not the chaos strategy is enabled for a given execution. - /// - /// - /// Defaults to . Either or this property is required. - /// When this property is the is used. - /// - public bool? Enabled { get; set; } - - /// - /// Gets or sets the Randomizer generator instance that is used to evaluate the injection rate. - /// - /// - /// The default randomizer is thread safe and returns values between 0.0 and 1.0. - /// - [Required] - public Func Randomizer { get; set; } = RandomUtil.Instance.NextDouble; } diff --git a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs new file mode 100644 index 00000000000..8c47de7fd29 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs @@ -0,0 +1,10 @@ +namespace Polly.Simmy.Outcomes; + +/// +/// Arguments used by the latency chaos strategy to notify that an outcome was injected. +/// +/// The type of the outcome that was injected. +/// The context associated with the execution of a user-provided callback. +/// The outcome that was injected. +/// +public readonly record struct OnOutcomeInjectedArguments(ResilienceContext Context, Outcome Outcome); diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs new file mode 100644 index 00000000000..9ecba6f6c8d --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -0,0 +1,60 @@ +using Polly.Telemetry; + +namespace Polly.Simmy.Outcomes; + +#pragma warning disable S3928 // Custom ArgumentNullException message + +internal sealed class OutcomeChaosStrategy : MonkeyStrategy +{ + private readonly ResilienceStrategyTelemetry _telemetry; + + public OutcomeChaosStrategy(OutcomeStrategyOptions options, + ResilienceStrategyTelemetry telemetry) + : base(options) + { + Guard.NotNull(telemetry); + + if (!options.Outcome.HasResult && options.OutcomeGenerator is null) + { + throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); + } + + _telemetry = telemetry; + Outcome = options.Outcome; + OutcomeGenerator = options.Outcome.HasResult ? (_) => new(options.Outcome) : options.OutcomeGenerator; + OnOutcomeInjected = options.OnOutcomeInjected; + } + + public Func, ValueTask>? OnOutcomeInjected { get; } + + public Func>> OutcomeGenerator { get; } + + public Outcome Outcome { get; } + + protected internal override async ValueTask> ExecuteCoreAsync( + Func>> callback, ResilienceContext context, TState state) + { + try + { + if (await ShouldInject(context).ConfigureAwait(context.ContinueOnCapturedContext)) + { + var outcome = await OutcomeGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext); + var args = new OnOutcomeInjectedArguments(context, outcome); + _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); + + if (OnOutcomeInjected is not null) + { + await OnOutcomeInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); + } + + return outcome.AsOutcome(); + } + + return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); + } + catch (OperationCanceledException e) + { + return new Outcome(e); + } + } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs new file mode 100644 index 00000000000..c82c4e1c363 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs @@ -0,0 +1,8 @@ +namespace Polly.Simmy.Outcomes; + +internal static class OutcomeConstants +{ + public const string StrategyType = "Outcome"; + + public const string OnOutcomeInjectedEvent = "OnOutcomeInjected"; +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs new file mode 100644 index 00000000000..c72eedc7a2a --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs @@ -0,0 +1,41 @@ +namespace Polly.Simmy.Outcomes; + +#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null + +/// +/// Represents the options for the Behavior chaos strategy. +/// +/// The type of the outcome that was injected. +public class OutcomeStrategyOptions : MonkeyStrategyOptions +{ + /// + /// Gets the strategy type. + /// + public sealed override string StrategyType => OutcomeConstants.StrategyType; + + /// + /// Gets or sets the delegate that's raised when the outcome is injected. + /// + /// + /// Defaults to . + /// + public Func, ValueTask>? OnOutcomeInjected { get; set; } + + /// + /// Gets or sets the outcome generator to be injected for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public Func>> OutcomeGenerator { get; set; } + + /// + /// Gets or sets the outcome to be injected for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public Outcome Outcome { get; set; } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs new file mode 100644 index 00000000000..7a29aa8ef5d --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.cs @@ -0,0 +1,6 @@ +namespace Polly.Simmy.Outcomes; + +/// +public class OutcomeStrategyOptions : OutcomeStrategyOptions +{ +} From b03d4cb2cf0e2ce3df57fce750711bc382b86a35 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 2 Jul 2023 21:24:08 -0500 Subject: [PATCH 08/36] improves MonkeyStrategy generic implementation so that it encapsulates the logic to handle a callback of type T vs TResult to make it easier and clear to consumers, and avoid confusion so that they don't have to worry about it. --- .../Simmy/MonkeyStrategy.TResult.cs | 65 ++++++++++++++++++- .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 8 +-- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs index 110e010e8ae..abc2eb00aaa 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs @@ -3,8 +3,8 @@ /// /// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. /// -/// The type of result this strategy supports. -public abstract class MonkeyStrategy : MonkeyStrategy +/// The type of result this strategy supports. +public abstract class MonkeyStrategy : MonkeyStrategy { /// /// Initializes a new instance of the class. @@ -14,4 +14,65 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) : base(options) { } + + /// + /// Executes the specified callback. + /// + /// The type of state associated with the callback. + /// The type of result returned by the callback. + /// The user-provided callback. + /// The context associated with the callback. + /// The state associated with the callback. + /// An instance of that represents an asynchronous callback. + /// + /// This method is called by various methods exposed on . These methods make sure that + /// is properly initialized with details about the execution mode. + /// + /// The provided callback never throws an exception. Instead, the exception is captured and converted to an . + /// + /// + /// Do not throw exceptions from your strategy implementation. Instead, return an exception instance as . + /// + /// + protected internal abstract ValueTask> ExecuteCoreAsync( + Func>> callback, + ResilienceContext context, + TState state); + + /// + protected internal sealed override ValueTask> ExecuteCoreAsync( + Func>> callback, + ResilienceContext context, + TState state) + { + Guard.NotNull(callback); + + if (typeof(TResult) != typeof(T)) + { + return callback(context, state); + } + + // cast is safe here, because TResult and T are the same type + var callbackCasted = (Func>>)(object)callback; + var valueTask = ExecuteCoreAsync(callbackCasted, context, state); + + return ConvertValueTask(valueTask, context); + } + + // TODO: Consider abstract this out as an utility? it's also being used in OutcomeResilienceStrategy + private static ValueTask> ConvertValueTask(ValueTask> valueTask, ResilienceContext resilienceContext) + { + if (valueTask.IsCompletedSuccessfully) + { + return new ValueTask>(valueTask.Result.AsOutcome()); + } + + return ConvertValueTaskAsync(valueTask, resilienceContext); + + static async ValueTask> ConvertValueTaskAsync(ValueTask> valueTask, ResilienceContext resilienceContext) + { + var outcome = await valueTask.ConfigureAwait(resilienceContext.ContinueOnCapturedContext); + return outcome.AsOutcome(); + } + } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index 9ecba6f6c8d..5f3dd6bfa21 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -31,8 +31,8 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, public Outcome Outcome { get; } - protected internal override async ValueTask> ExecuteCoreAsync( - Func>> callback, ResilienceContext context, TState state) + protected internal override async ValueTask> ExecuteCoreAsync( + Func>> callback, ResilienceContext context, TState state) { try { @@ -47,14 +47,14 @@ protected internal override async ValueTask> ExecuteCoreAsync(); + return outcome; } return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); } catch (OperationCanceledException e) { - return new Outcome(e); + return new Outcome(e); } } } From 09fb16393ac6140db55a7b82475847847963ba62 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 2 Jul 2023 21:32:17 -0500 Subject: [PATCH 09/36] fix docs --- src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs index abc2eb00aaa..c50a3909272 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs @@ -19,7 +19,6 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) /// Executes the specified callback. /// /// The type of state associated with the callback. - /// The type of result returned by the callback. /// The user-provided callback. /// The context associated with the callback. /// The state associated with the callback. @@ -28,10 +27,10 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) /// This method is called by various methods exposed on . These methods make sure that /// is properly initialized with details about the execution mode. /// - /// The provided callback never throws an exception. Instead, the exception is captured and converted to an . + /// The provided callback never throws an exception. Instead, the exception is captured and converted to an . /// /// - /// Do not throw exceptions from your strategy implementation. Instead, return an exception instance as . + /// Do not throw exceptions from your strategy implementation. Instead, return an exception instance as . /// /// protected internal abstract ValueTask> ExecuteCoreAsync( @@ -59,7 +58,7 @@ protected internal sealed override ValueTask> ExecuteCoreAsync< return ConvertValueTask(valueTask, context); } - // TODO: Consider abstract this out as an utility? it's also being used in OutcomeResilienceStrategy + // TODO: Consider abstract this out as an utility or in the ResilienceStrategy? it's also being used in OutcomeResilienceStrategy private static ValueTask> ConvertValueTask(ValueTask> valueTask, ResilienceContext resilienceContext) { if (valueTask.IsCompletedSuccessfully) From 090159ddf9fe0bee132feea5c1fee05114506260 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 2 Jul 2023 21:42:07 -0500 Subject: [PATCH 10/36] abstract ConvertValueTask functionality --- .../Simmy/MonkeyStrategy.TResult.cs | 19 +----------------- .../Utils/OutcomeResilienceStrategy.cs | 20 ++----------------- .../Utils/OutcomeResilienceStrategyHelper.cs | 20 +++++++++++++++++++ 3 files changed, 23 insertions(+), 36 deletions(-) create mode 100644 src/Polly.Core/Utils/OutcomeResilienceStrategyHelper.cs diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs index c50a3909272..f2336d90102 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs @@ -55,23 +55,6 @@ protected internal sealed override ValueTask> ExecuteCoreAsync< var callbackCasted = (Func>>)(object)callback; var valueTask = ExecuteCoreAsync(callbackCasted, context, state); - return ConvertValueTask(valueTask, context); - } - - // TODO: Consider abstract this out as an utility or in the ResilienceStrategy? it's also being used in OutcomeResilienceStrategy - private static ValueTask> ConvertValueTask(ValueTask> valueTask, ResilienceContext resilienceContext) - { - if (valueTask.IsCompletedSuccessfully) - { - return new ValueTask>(valueTask.Result.AsOutcome()); - } - - return ConvertValueTaskAsync(valueTask, resilienceContext); - - static async ValueTask> ConvertValueTaskAsync(ValueTask> valueTask, ResilienceContext resilienceContext) - { - var outcome = await valueTask.ConfigureAwait(resilienceContext.ContinueOnCapturedContext); - return outcome.AsOutcome(); - } + return OutcomeResilienceStrategyHelper.ConvertValueTask(valueTask, context); } } diff --git a/src/Polly.Core/Utils/OutcomeResilienceStrategy.cs b/src/Polly.Core/Utils/OutcomeResilienceStrategy.cs index 2112930bc47..50066e918d7 100644 --- a/src/Polly.Core/Utils/OutcomeResilienceStrategy.cs +++ b/src/Polly.Core/Utils/OutcomeResilienceStrategy.cs @@ -38,7 +38,7 @@ protected internal sealed override ValueTask> ExecuteCoreAsync< var callbackCasted = (Func>>)(object)callback; var valueTask = ExecuteCallbackAsync(callbackCasted, context, state); - return ConvertValueTask(valueTask, context); + return OutcomeResilienceStrategyHelper.ConvertValueTask(valueTask, context); } else { @@ -53,7 +53,7 @@ static async (context, state) => context, (callback, state)); - return ConvertValueTask(valueTask, context); + return OutcomeResilienceStrategyHelper.ConvertValueTask(valueTask, context); } } @@ -61,20 +61,4 @@ protected abstract ValueTask> ExecuteCallbackAsync( Func>> callback, ResilienceContext context, TState state); - - private static ValueTask> ConvertValueTask(ValueTask> valueTask, ResilienceContext resilienceContext) - { - if (valueTask.IsCompletedSuccessfully) - { - return new ValueTask>(valueTask.Result.AsOutcome()); - } - - return ConvertValueTaskAsync(valueTask, resilienceContext); - - static async ValueTask> ConvertValueTaskAsync(ValueTask> valueTask, ResilienceContext resilienceContext) - { - var outcome = await valueTask.ConfigureAwait(resilienceContext.ContinueOnCapturedContext); - return outcome.AsOutcome(); - } - } } diff --git a/src/Polly.Core/Utils/OutcomeResilienceStrategyHelper.cs b/src/Polly.Core/Utils/OutcomeResilienceStrategyHelper.cs new file mode 100644 index 00000000000..11e264db73a --- /dev/null +++ b/src/Polly.Core/Utils/OutcomeResilienceStrategyHelper.cs @@ -0,0 +1,20 @@ +namespace Polly.Utils; + +internal static class OutcomeResilienceStrategyHelper +{ + internal static ValueTask> ConvertValueTask(ValueTask> valueTask, ResilienceContext resilienceContext) + { + if (valueTask.IsCompletedSuccessfully) + { + return new ValueTask>(valueTask.Result.AsOutcome()); + } + + return ConvertValueTaskAsync(valueTask, resilienceContext); + + static async ValueTask> ConvertValueTaskAsync(ValueTask> valueTask, ResilienceContext resilienceContext) + { + var outcome = await valueTask.ConfigureAwait(resilienceContext.ContinueOnCapturedContext); + return outcome.AsOutcome(); + } + } +} From 0dcf1212df25e7b8685fb8250826acd868edcb6d Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Mon, 3 Jul 2023 16:45:21 -0500 Subject: [PATCH 11/36] add outcomes buileder extensions --- .../OutcomeChaosStrategyBuilderExtensions.cs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs new file mode 100644 index 00000000000..109f712dbd6 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs @@ -0,0 +1,112 @@ +namespace Polly.Simmy.Outcomes; + +/// +/// Extension methods for adding outcome to a . +/// +public static class OutcomeChaosStrategyBuilderExtensions +{ + /// + /// Adds a fault chaos strategy to the builder. + /// + /// The builder instance. + /// The exception to inject. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, Exception fault) + { + Guard.NotNull(builder); + + return builder.AddOutcomeCore(new OutcomeStrategyOptions + { + Outcome = new(fault) + }); + } + + /// + /// Adds a fault chaos strategy to the builder. + /// + /// The builder instance. + /// The exception generator delegate. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, Func>> faultGenerator) + { + Guard.NotNull(builder); + + return builder.AddOutcomeCore(new OutcomeStrategyOptions + { + OutcomeGenerator = (_) => faultGenerator() + }); + } + + /// + /// Adds a fault chaos strategy to the builder. + /// + /// The builder instance. + /// The fault strategy options. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, OutcomeStrategyOptions options) + { + Guard.NotNull(builder); + Guard.NotNull(options); + + return builder.AddOutcomeCore(options); + } + + /// + /// Adds an outcome chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// The outcome to inject. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, TResult result) + { + Guard.NotNull(builder); + + return builder.AddOutcomeCore(new OutcomeStrategyOptions + { + Outcome = new(result) + }); + } + + /// + /// Adds an outcome chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// The outcome generator delegate. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, Func>> outcomeGenerator) + { + Guard.NotNull(builder); + + return builder.AddOutcomeCore(new OutcomeStrategyOptions + { + OutcomeGenerator = (_) => outcomeGenerator() + }); + } + + /// + /// Adds an outcome chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// The outcome strategy options. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, OutcomeStrategyOptions options) + { + Guard.NotNull(builder); + Guard.NotNull(options); + + return builder.AddOutcomeCore(options); + } + + private static TBuilder AddOutcomeCore(this TBuilder builder, OutcomeStrategyOptions options) + where TBuilder : ResilienceStrategyBuilderBase + { + return builder.AddStrategy(context => + new OutcomeChaosStrategy( + options, + context.Telemetry), + options); + } +} From de989efbc261d8878de41b06f712533d694ca401 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 8 Jul 2023 14:13:37 -0500 Subject: [PATCH 12/36] WIP: adding unit tests --- .../Simmy/MonkeyStrategyConstants.cs | 4 +- .../Behavior/BehaviorChaosStrategyTests.cs | 98 +++++++++++++++ .../Simmy/MonkeyStrategyOptionsTests.cs | 44 +++++++ .../Simmy/MonkeyStrategyTests.cs | 119 ++++++++++++++++++ .../Simmy/TestChaosStrategy.cs | 45 +++++++ .../Simmy/TestChaosStrategyOptions.cs | 8 ++ 6 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs create mode 100644 test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.cs diff --git a/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs b/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs index db2cbe6bb83..cefb170e3f5 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs @@ -2,7 +2,7 @@ internal static class MonkeyStrategyConstants { - public const int MinInjectionThreshold = 0; + public const double MinInjectionThreshold = 0; - public const int MaxInjectionThreshold = 1; + public const double MaxInjectionThreshold = 1; } diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs new file mode 100644 index 00000000000..153875614a6 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs @@ -0,0 +1,98 @@ +using Moq; +using Polly.Simmy.Behavior; +using Polly.Telemetry; + +namespace Polly.Core.Tests.Simmy.Behavior; +public class BehaviorChaosStrategyTests +{ + private readonly ResilienceStrategyTelemetry _telemetry; + private readonly BehaviorStrategyOptions _options; + private readonly CancellationTokenSource _cancellationSource; + private readonly Mock _diagnosticSource = new(); + + public BehaviorChaosStrategyTests() + { + _telemetry = TestUtilities.CreateResilienceTelemetry(_diagnosticSource.Object); + _options = new(); + _cancellationSource = new CancellationTokenSource(); + } + + [Fact] + public void Given_not_enabled_should_not_inject_behaviour() + { + var userDelegateExecuted = false; + var injectedBehaviourExecuted = false; + + _options.InjectionRate = 0.6; + _options.Enabled = false; + _options.Randomizer = () => 0.5; + _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + + var sut = CreateSut(); + sut.Execute(() => { userDelegateExecuted = true; }); + + userDelegateExecuted.Should().BeTrue(); + injectedBehaviourExecuted.Should().BeFalse(); + } + + [Fact] + public async Task Given_enabled_and_randomly_within_threshold_should_inject_behaviour() + { + var userDelegateExecuted = false; + var injectedBehaviourExecuted = false; + + _options.InjectionRate = 0.6; + _options.Enabled = true; + _options.Randomizer = () => 0.5; + _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + + var sut = CreateSut(); + await sut.ExecuteAsync((_) => { userDelegateExecuted = true; return default; }); + + userDelegateExecuted.Should().BeTrue(); + injectedBehaviourExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inject_behaviour() + { + var userDelegateExecuted = false; + var injectedBehaviourExecuted = false; + + _options.InjectionRate = 0.4; + _options.Enabled = false; + _options.Randomizer = () => 0.5; + _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + + var sut = CreateSut(); + await sut.ExecuteAsync((_) => { userDelegateExecuted = true; return default; }); + + userDelegateExecuted.Should().BeTrue(); + injectedBehaviourExecuted.Should().BeFalse(); + } + + [Fact] + public async Task Should_inject_behaviour_before_executing_user_delegate() + { + var userDelegateExecuted = false; + var injectedBehaviourExecuted = false; + + _options.InjectionRate = 0.6; + _options.Enabled = true; + _options.Randomizer = () => 0.5; + _options.Behavior = (_) => + { + userDelegateExecuted.Should().BeFalse(); // Not yet executed at the time the injected behaviour runs. + injectedBehaviourExecuted = true; + return default; + }; + + var sut = CreateSut(); + await sut.ExecuteAsync((_) => { userDelegateExecuted = true; return default; }); + + userDelegateExecuted.Should().BeTrue(); + injectedBehaviourExecuted.Should().BeTrue(); + } + + private BehaviorChaosStrategy CreateSut() => new(_options, _telemetry); +} diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs new file mode 100644 index 00000000000..cd0b796971d --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; +using Polly.Utils; + +namespace Polly.Core.Tests.Simmy; +public class MonkeyStrategyOptionsTests +{ + [Fact] + public void Ctor_Ok() + { + var sut = new TestChaosStrategyOptions(); + + sut.StrategyType.Should().Be("Test"); + + sut.Randomizer.Should().NotBeNull(); + + sut.Enabled.Should().BeNull(); + sut.EnabledGenerator.Should().BeNull(); + + sut.InjectionRate.Should().BeNull(); + sut.InjectionRateGenerator.Should().BeNull(); + } + + [InlineData(-1)] + [InlineData(1.1)] + [Theory] + public void InvalidThreshold(double injectionRate) + { + var sut = new TestChaosStrategyOptions + { + InjectionRate = injectionRate, + }; + + sut + .Invoking(o => ValidationHelper.ValidateObject(o, "Invalid Options")) + .Should() + .Throw() + .WithMessage(""" + Invalid Options + + Validation Errors: + The field InjectionRate must be between 0 and 1. + """); + } +} diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs new file mode 100644 index 00000000000..b37b51d6de8 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs @@ -0,0 +1,119 @@ +namespace Polly.Core.Tests.Simmy; + +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +public class MonkeyStrategyTests +{ + private readonly TestChaosStrategyOptions _options; + + public MonkeyStrategyTests() => _options = new(); + + public static List CtorTestCases => + new() + { + new object[] { null, "Value cannot be null. (Parameter 'options')" }, + new object[] { new TestChaosStrategyOptions(), "Either InjectionRate or InjectionRateGenerator is required. (Parameter 'InjectionRate')" }, + new object[] { new TestChaosStrategyOptions { InjectionRate = 0.5 }, "Either Enabled or EnabledGenerator is required. (Parameter 'Enabled')" } + }; + + [Theory] + [MemberData(nameof(CtorTestCases))] + public void InvalidCtor(TestChaosStrategyOptions options, string message) + { + Action act = () => + { + var _ = new TestChaosStrategy(options); + }; + + act.Should() + .Throw() + .WithMessage(message); + } + + [Fact] + public async Task Ctor_Ok() + { + var context = ResilienceContext.Get(); + _options.EnabledGenerator = (_) => new ValueTask(true); + _options.InjectionRate = 0.5; + + var sut = CreateSut(); + + sut.EnabledGenerator.Should().NotBeNull(); + (await sut.EnabledGenerator(context)).Should().BeTrue(); + + sut.InjectionRateGenerator.Should().NotBeNull(); + (await sut.InjectionRateGenerator(context)).Should().Be(0.5); + } + + [InlineData(-1)] + [InlineData(1.1)] + [Theory] + public async Task Should_throw_error_when_injection_rate_generator_result_is_not_valid(double injectionRate) + { + var wasMonkeyUnleashed = false; + + _options.EnabledGenerator = (_) => new ValueTask(true); + _options.InjectionRateGenerator = (_) => new ValueTask(injectionRate); + _options.Randomizer = () => 0.5; + + var sut = CreateSut(); + + await sut.Invoking(s => s.ExecuteAsync((_) => { return default; }).AsTask()) + .Should() + .ThrowAsync(); + + wasMonkeyUnleashed.Should().BeFalse(); + } + + [Fact] + public async Task Should_inject_chaos() + { + var wasMonkeyUnleashed = false; + + _options.EnabledGenerator = (_) => new ValueTask(true); + _options.InjectionRate = 0.6; + _options.Randomizer = () => 0.5; + + var sut = CreateSut(); + sut.OnExecute = (_, _) => { wasMonkeyUnleashed = true; return Task.CompletedTask; }; + + await sut.ExecuteAsync((_) => { return default; }); + + wasMonkeyUnleashed.Should().BeTrue(); + } + + [Fact] + public async Task Should_not_inject_chaos_when_it_was_cancelled_before_evaluating_strategy() + { + var wasMonkeyUnleashed = false; + var enableGeneratorExecuted = false; + var injectionRateGeneratorExecuted = false; + + _options.Randomizer = () => 0.5; + _options.EnabledGenerator = (_) => + { + enableGeneratorExecuted = true; + return new ValueTask(true); + }; + _options.InjectionRateGenerator = (_) => + { + injectionRateGeneratorExecuted = true; + return new ValueTask(0.6); + }; + + using var cts = new CancellationTokenSource(); + var sut = CreateSut(); + sut.Before = (_, _) => { cts.Cancel(); }; + + await sut.Invoking(s => s.ExecuteAsync(async _ => { await Task.CompletedTask; }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); + + wasMonkeyUnleashed.Should().BeFalse(); + enableGeneratorExecuted.Should().BeFalse(); + injectionRateGeneratorExecuted.Should().BeFalse(); + } + + private TestChaosStrategy CreateSut() => new(_options); +} diff --git a/test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs b/test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs new file mode 100644 index 00000000000..a517cdd836a --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs @@ -0,0 +1,45 @@ +using Polly.Simmy; + +namespace Polly.Core.Tests.Simmy; +public sealed class TestChaosStrategy : MonkeyStrategy +{ + public TestChaosStrategy(TestChaosStrategyOptions options) + : base(options) + { + } + + public Action? Before { get; set; } + + public Action? After { get; set; } + + public Func? OnExecute { get; set; } + + protected internal override async ValueTask> ExecuteCoreAsync( + Func>> callback, ResilienceContext context, TState state) + { + Before?.Invoke(context, state); + + try + { + if (await ShouldInject(context).ConfigureAwait(context.ContinueOnCapturedContext)) + { + if (OnExecute != null) + { + await OnExecute(context, state).ConfigureAwait(false); + } + } + + var result = await callback(context, state).ConfigureAwait(false); + + After?.Invoke(result, null); + + return result; + } + catch (Exception e) + { + After?.Invoke(null, e); + + throw; + } + } +} diff --git a/test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.cs b/test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.cs new file mode 100644 index 00000000000..00482a26f04 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.cs @@ -0,0 +1,8 @@ +using Polly.Simmy; + +namespace Polly.Core.Tests.Simmy; + +public sealed class TestChaosStrategyOptions : MonkeyStrategyOptions +{ + public override string StrategyType => "Test"; +} From 15f2ccb2735bcb4e7969869446daa848c3e391d7 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 8 Jul 2023 19:13:45 -0500 Subject: [PATCH 13/36] wrapping up behavior unit tests --- .../BehaviorChaosStrategyBuilderExtensions.cs | 6 +- .../Simmy/Behavior/BehaviorStrategyOptions.cs | 5 +- .../BehaviorChaosBuilderExtensionsTests.cs | 73 ++++++++++++++++ .../Behavior/BehaviorChaosStrategyTests.cs | 57 ++++++++++++- .../Simmy/Behavior/BehaviorConstantsTests.cs | 13 +++ .../Behavior/BehaviorStrategyOptionsTests.cs | 39 +++++++++ .../OnBehaviorInjectedArgumentsTests.cs | 13 +++ .../Simmy/MonkeyStrategyTests.cs | 84 ++++++++++++++++--- 8 files changed, 276 insertions(+), 14 deletions(-) create mode 100644 test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosBuilderExtensionsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests.cs diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs index 07f98c3a046..fdbb12b1278 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs @@ -12,17 +12,21 @@ public static class BehaviorChaosStrategyBuilderExtensions /// /// The builder type. /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. /// The behavior to be injected. /// The same builder instance. /// Thrown when is . /// Thrown when the options produced from the arguments are invalid. - public static TBuilder AddBehavior(this TBuilder builder, Func behavior) + public static TBuilder AddBehavior(this TBuilder builder, bool enabled, double injectionrate, Func behavior) where TBuilder : ResilienceStrategyBuilderBase { Guard.NotNull(builder); return builder.AddBehavior(new BehaviorStrategyOptions { + Enabled = enabled, + InjectionRate = injectionrate, Behavior = (_) => behavior() }); } diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs index c509ade8dec..8bb4f20a6ee 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs @@ -1,4 +1,6 @@ -namespace Polly.Simmy.Behavior; +using System.ComponentModel.DataAnnotations; + +namespace Polly.Simmy.Behavior; #pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null @@ -26,5 +28,6 @@ public class BehaviorStrategyOptions : MonkeyStrategyOptions /// /// Defaults to . /// + [Required] public Func Behavior { get; set; } } diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosBuilderExtensionsTests.cs new file mode 100644 index 00000000000..a0f7181dd41 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosBuilderExtensionsTests.cs @@ -0,0 +1,73 @@ +using System.ComponentModel.DataAnnotations; +using Polly.Simmy.Behavior; + +namespace Polly.Core.Tests.Simmy.Behavior; + +public class BehaviorChaosBuilderExtensionsTests +{ + public static IEnumerable AddBehavior_Ok_Data() + { + var context = ResilienceContext.Get(); + Func behavior = () => new ValueTask(Task.CompletedTask); + yield return new object[] + { + (ResilienceStrategyBuilder builder) => { builder.AddBehavior(true, 0.5, behavior); }, + (BehaviorChaosStrategy strategy) => + { + strategy.Behavior.Invoke(context).Preserve().GetAwaiter().IsCompleted.Should().BeTrue(); + strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().BeTrue(); + strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(0.5); + } + }; + } + + [Fact] + public void AddBehavior_Shortcut_Option_Ok() + { + var sut = new ResilienceStrategyBuilder().AddBehavior(true, 0.5, () => new ValueTask(Task.CompletedTask)).Build(); + sut.Should().BeOfType(); + } + + [Fact] + public void AddBehavior_Shortcut_Option_Throws() + { + new ResilienceStrategyBuilder() + .Invoking(b => b.AddBehavior(true, -1, () => new ValueTask(Task.CompletedTask))) + .Should() + .Throw(); + } + + [Fact] + public void AddBehavior_InvalidOptions_Throws() + { + new ResilienceStrategyBuilder() + .Invoking(b => b.AddBehavior(new BehaviorStrategyOptions())) + .Should() + .Throw(); + } + + [Fact] + public void AddBehavior_Options_Ok() + { + var sut = new ResilienceStrategyBuilder() + .AddBehavior(new BehaviorStrategyOptions + { + Enabled = true, + InjectionRate = 1, + Behavior = (_) => new ValueTask(Task.CompletedTask) + }) + .Build(); + + sut.Should().BeOfType(); + } + + [MemberData(nameof(AddBehavior_Ok_Data))] + [Theory] + internal void AddBehavior_Generic_Options_Ok(Action> configure, Action assert) + { + var builder = new ResilienceStrategyBuilder(); + configure(builder); + var strategy = builder.Build().Strategy.Should().BeOfType().Subject; + assert(strategy); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs index 153875614a6..6b5adf16543 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs @@ -7,14 +7,12 @@ public class BehaviorChaosStrategyTests { private readonly ResilienceStrategyTelemetry _telemetry; private readonly BehaviorStrategyOptions _options; - private readonly CancellationTokenSource _cancellationSource; private readonly Mock _diagnosticSource = new(); public BehaviorChaosStrategyTests() { _telemetry = TestUtilities.CreateResilienceTelemetry(_diagnosticSource.Object); _options = new(); - _cancellationSource = new CancellationTokenSource(); } [Fact] @@ -53,6 +51,35 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_beha injectedBehaviourExecuted.Should().BeTrue(); } + [Fact] + public async Task Given_enabled_and_randomly_within_threshold_ensure_on_behavior_injected_called() + { + var called = false; + var userDelegateExecuted = false; + var injectedBehaviourExecuted = false; + _diagnosticSource.Setup(v => v.IsEnabled(BehaviorConstants.OnBehaviorInjectedEvent)).Returns(true); + + _options.InjectionRate = 0.6; + _options.Enabled = true; + _options.Randomizer = () => 0.5; + _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + _options.OnBehaviorInjected = args => + { + args.Context.Should().NotBeNull(); + args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); + called = true; + return default; + }; + + var sut = CreateSut(); + await sut.ExecuteAsync((_) => { userDelegateExecuted = true; return default; }); + + called.Should().BeTrue(); + userDelegateExecuted.Should().BeTrue(); + injectedBehaviourExecuted.Should().BeTrue(); + _diagnosticSource.VerifyAll(); + } + [Fact] public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inject_behaviour() { @@ -94,5 +121,31 @@ public async Task Should_inject_behaviour_before_executing_user_delegate() injectedBehaviourExecuted.Should().BeTrue(); } + [Fact] + public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running_the_strategy() + { + var userDelegateExecuted = false; + var injectedBehaviourExecuted = false; + + using var cts = new CancellationTokenSource(); + _options.InjectionRate = 0.6; + _options.Enabled = true; + _options.Randomizer = () => 0.5; + _options.Behavior = (_) => + { + cts.Cancel(); + injectedBehaviourExecuted = true; + return default; + }; + + var sut = CreateSut(); + await sut.Invoking(s => s.ExecuteAsync(async _ => { await Task.CompletedTask; }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); + + userDelegateExecuted.Should().BeFalse(); + injectedBehaviourExecuted.Should().BeTrue(); + } + private BehaviorChaosStrategy CreateSut() => new(_options, _telemetry); } diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs new file mode 100644 index 00000000000..ec825ac903e --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorConstantsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Behavior; + +namespace Polly.Core.Tests.Simmy.Behavior; + +public class BehaviorConstantsTests +{ + [Fact] + public void EnsureDefaults() + { + BehaviorConstants.OnBehaviorInjectedEvent.Should().Be("OnBehaviorInjected"); + BehaviorConstants.StrategyType.Should().Be("Behavior"); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs new file mode 100644 index 00000000000..9f5dda2297b --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; +using Polly.Simmy.Behavior; +using Polly.Utils; + +namespace Polly.Core.Tests.Simmy.Behavior; + +public class BehaviorStrategyOptionsTests +{ + [Fact] + public void Ctor_Ok() + { + var sut = new BehaviorStrategyOptions(); + sut.StrategyType.Should().Be(BehaviorConstants.StrategyType); + sut.Randomizer.Should().NotBeNull(); + sut.Enabled.Should().BeNull(); + sut.EnabledGenerator.Should().BeNull(); + sut.InjectionRate.Should().BeNull(); + sut.InjectionRateGenerator.Should().BeNull(); + sut.Behavior.Should().BeNull(); + sut.OnBehaviorInjected.Should().BeNull(); + } + + [Fact] + public void InvalidOptions() + { + var sut = new BehaviorStrategyOptions(); + + sut + .Invoking(o => ValidationHelper.ValidateObject(o, "Invalid Options")) + .Should() + .Throw() + .WithMessage(""" + Invalid Options + + Validation Errors: + The Behavior field is required. + """); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests.cs new file mode 100644 index 00000000000..c5e39bc3802 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Behavior; + +namespace Polly.Core.Tests.Simmy.Behavior; + +public class OnBehaviorInjectedArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new OnBehaviorInjectedArguments(ResilienceContext.Get()); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs index b37b51d6de8..196a2efe540 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs @@ -67,32 +67,49 @@ public async Task Should_throw_error_when_injection_rate_generator_result_is_not } [Fact] - public async Task Should_inject_chaos() + public async Task Should_not_inject_chaos_when_it_was_cancelled_before_evaluating_strategy() { var wasMonkeyUnleashed = false; + var enableGeneratorExecuted = false; + var injectionRateGeneratorExecuted = false; - _options.EnabledGenerator = (_) => new ValueTask(true); - _options.InjectionRate = 0.6; _options.Randomizer = () => 0.5; + _options.EnabledGenerator = (_) => + { + enableGeneratorExecuted = true; + return new ValueTask(true); + }; + _options.InjectionRateGenerator = (_) => + { + injectionRateGeneratorExecuted = true; + return new ValueTask(0.6); + }; + using var cts = new CancellationTokenSource(); var sut = CreateSut(); - sut.OnExecute = (_, _) => { wasMonkeyUnleashed = true; return Task.CompletedTask; }; + sut.Before = (_, _) => { cts.Cancel(); }; - await sut.ExecuteAsync((_) => { return default; }); + await sut.Invoking(s => s.ExecuteAsync(async _ => { await Task.CompletedTask; }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); - wasMonkeyUnleashed.Should().BeTrue(); + wasMonkeyUnleashed.Should().BeFalse(); + enableGeneratorExecuted.Should().BeFalse(); + injectionRateGeneratorExecuted.Should().BeFalse(); } [Fact] - public async Task Should_not_inject_chaos_when_it_was_cancelled_before_evaluating_strategy() + public async Task Should_not_inject_chaos_when_it_was_cancelled_on_enable_generator() { var wasMonkeyUnleashed = false; var enableGeneratorExecuted = false; var injectionRateGeneratorExecuted = false; + using var cts = new CancellationTokenSource(); _options.Randomizer = () => 0.5; _options.EnabledGenerator = (_) => { + cts.Cancel(); enableGeneratorExecuted = true; return new ValueTask(true); }; @@ -102,18 +119,65 @@ public async Task Should_not_inject_chaos_when_it_was_cancelled_before_evaluatin return new ValueTask(0.6); }; - using var cts = new CancellationTokenSource(); var sut = CreateSut(); - sut.Before = (_, _) => { cts.Cancel(); }; await sut.Invoking(s => s.ExecuteAsync(async _ => { await Task.CompletedTask; }, cts.Token).AsTask()) .Should() .ThrowAsync(); wasMonkeyUnleashed.Should().BeFalse(); - enableGeneratorExecuted.Should().BeFalse(); + enableGeneratorExecuted.Should().BeTrue(); injectionRateGeneratorExecuted.Should().BeFalse(); } + [Fact] + public async Task Should_not_inject_chaos_when_it_was_cancelled_on_injection_rate_generator() + { + var wasMonkeyUnleashed = false; + var enableGeneratorExecuted = false; + var injectionRateGeneratorExecuted = false; + + using var cts = new CancellationTokenSource(); + _options.Randomizer = () => 0.5; + _options.EnabledGenerator = (_) => + { + enableGeneratorExecuted = true; + return new ValueTask(true); + }; + _options.InjectionRateGenerator = (_) => + { + cts.Cancel(); + injectionRateGeneratorExecuted = true; + return new ValueTask(0.6); + }; + + var sut = CreateSut(); + + await sut.Invoking(s => s.ExecuteAsync(async _ => { await Task.CompletedTask; }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); + + wasMonkeyUnleashed.Should().BeFalse(); + enableGeneratorExecuted.Should().BeTrue(); + injectionRateGeneratorExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Should_inject_chaos() + { + var wasMonkeyUnleashed = false; + + _options.EnabledGenerator = (_) => new ValueTask(true); + _options.InjectionRate = 0.6; + _options.Randomizer = () => 0.5; + + var sut = CreateSut(); + sut.OnExecute = (_, _) => { wasMonkeyUnleashed = true; return Task.CompletedTask; }; + + await sut.ExecuteAsync((_) => { return default; }); + + wasMonkeyUnleashed.Should().BeTrue(); + } + private TestChaosStrategy CreateSut() => new(_options); } From 7dac89ee73d85d2943059d1598b952b3765b77f0 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 8 Jul 2023 19:19:32 -0500 Subject: [PATCH 14/36] add missing parameters in builder extensions --- .../BehaviorChaosStrategyBuilderExtensions.cs | 6 ++--- .../LatencyChaosStrategyBuilderExtensions.cs | 6 ++++- .../OutcomeChaosStrategyBuilderExtensions.cs | 25 ++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs index fdbb12b1278..43f8e32aee2 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategyBuilderExtensions.cs @@ -13,12 +13,12 @@ public static class BehaviorChaosStrategyBuilderExtensions /// The builder type. /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1]. /// The behavior to be injected. /// The same builder instance. /// Thrown when is . /// Thrown when the options produced from the arguments are invalid. - public static TBuilder AddBehavior(this TBuilder builder, bool enabled, double injectionrate, Func behavior) + public static TBuilder AddBehavior(this TBuilder builder, bool enabled, double injectionRate, Func behavior) where TBuilder : ResilienceStrategyBuilderBase { Guard.NotNull(builder); @@ -26,7 +26,7 @@ public static TBuilder AddBehavior(this TBuilder builder, bool enabled return builder.AddBehavior(new BehaviorStrategyOptions { Enabled = enabled, - InjectionRate = injectionrate, + InjectionRate = injectionRate, Behavior = (_) => behavior() }); } diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs index fc0fc173f62..5043a85b32b 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategyBuilderExtensions.cs @@ -12,17 +12,21 @@ public static class LatencyChaosStrategyBuilderExtensions /// /// The builder type. /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. /// The delay value. /// The same builder instance. /// Thrown when is . /// Thrown when the options produced from the arguments are invalid. - public static TBuilder AddLatency(this TBuilder builder, TimeSpan delay) + public static TBuilder AddLatency(this TBuilder builder, bool enabled, double injectionRate, TimeSpan delay) where TBuilder : ResilienceStrategyBuilderBase { Guard.NotNull(builder); return builder.AddLatency(new LatencyStrategyOptions { + Enabled = enabled, + InjectionRate = injectionRate, LatencyGenerator = (_) => new(delay) }); } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs index 109f712dbd6..fd6a1dc5e04 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs @@ -9,14 +9,18 @@ public static class OutcomeChaosStrategyBuilderExtensions /// Adds a fault chaos strategy to the builder. /// /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. /// The exception to inject. /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, Exception fault) + public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Exception fault) { Guard.NotNull(builder); return builder.AddOutcomeCore(new OutcomeStrategyOptions { + Enabled = enabled, + InjectionRate = injectionRate, Outcome = new(fault) }); } @@ -25,14 +29,18 @@ public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder /// Adds a fault chaos strategy to the builder. /// /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. /// The exception generator delegate. /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, Func>> faultGenerator) + public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Func>> faultGenerator) { Guard.NotNull(builder); return builder.AddOutcomeCore(new OutcomeStrategyOptions { + Enabled = enabled, + InjectionRate = injectionRate, OutcomeGenerator = (_) => faultGenerator() }); } @@ -56,14 +64,18 @@ public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder /// /// The type of result the retry strategy handles. /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. /// The outcome to inject. /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, TResult result) + public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, TResult result) { Guard.NotNull(builder); return builder.AddOutcomeCore(new OutcomeStrategyOptions { + Enabled = enabled, + InjectionRate = injectionRate, Outcome = new(result) }); } @@ -73,14 +85,19 @@ public static ResilienceStrategyBuilder AddResult(this Resilie /// /// The type of result the retry strategy handles. /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. /// The outcome generator delegate. /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, Func>> outcomeGenerator) + public static ResilienceStrategyBuilder AddResult( + this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Func>> outcomeGenerator) { Guard.NotNull(builder); return builder.AddOutcomeCore(new OutcomeStrategyOptions { + Enabled = enabled, + InjectionRate = injectionRate, OutcomeGenerator = (_) => outcomeGenerator() }); } From df62a1023a8acb791029032e15a0b357c0b4364d Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 9 Jul 2023 17:11:48 -0500 Subject: [PATCH 15/36] * adding unit tests for outcome chaos strategy * refactor and clean up --- .../Simmy/MonkeyStrategy.TResult.cs | 60 -------- .../Outcomes/OutcomeChaosStrategy.TResult.cs | 117 ++++++++++++++ .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 55 +------ ...eChaosStrategyBuilderExtensions.TResult.cs | 145 ++++++++++++++++++ .../OutcomeChaosStrategyBuilderExtensions.cs | 76 ++------- .../Simmy/Outcomes/OutcomeConstants.cs | 2 + .../Simmy/Utils/OutcomeMonkeyStrategy.cs | 66 ++++++++ .../Outcomes/OutcomeChaosStrategyTests.cs | 67 ++++++++ 8 files changed, 410 insertions(+), 178 deletions(-) delete mode 100644 src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.TResult.cs create mode 100644 src/Polly.Core/Simmy/Utils/OutcomeMonkeyStrategy.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs deleted file mode 100644 index f2336d90102..00000000000 --- a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Polly.Simmy; - -/// -/// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. -/// -/// The type of result this strategy supports. -public abstract class MonkeyStrategy : MonkeyStrategy -{ - /// - /// Initializes a new instance of the class. - /// - /// The chaos strategy options. - protected MonkeyStrategy(MonkeyStrategyOptions options) - : base(options) - { - } - - /// - /// Executes the specified 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 that represents an asynchronous callback. - /// - /// This method is called by various methods exposed on . These methods make sure that - /// is properly initialized with details about the execution mode. - /// - /// The provided callback never throws an exception. Instead, the exception is captured and converted to an . - /// - /// - /// Do not throw exceptions from your strategy implementation. Instead, return an exception instance as . - /// - /// - protected internal abstract ValueTask> ExecuteCoreAsync( - Func>> callback, - ResilienceContext context, - TState state); - - /// - protected internal sealed override ValueTask> ExecuteCoreAsync( - Func>> callback, - ResilienceContext context, - TState state) - { - Guard.NotNull(callback); - - if (typeof(TResult) != typeof(T)) - { - return callback(context, state); - } - - // cast is safe here, because TResult and T are the same type - var callbackCasted = (Func>>)(object)callback; - var valueTask = ExecuteCoreAsync(callbackCasted, context, state); - - return OutcomeResilienceStrategyHelper.ConvertValueTask(valueTask, context); - } -} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs new file mode 100644 index 00000000000..2a8fdbf5179 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs @@ -0,0 +1,117 @@ +using Polly.Telemetry; + +namespace Polly.Simmy.Outcomes; + +#pragma warning disable S3928 // Custom ArgumentNullException message + +internal class OutcomeChaosStrategy : OutcomeMonkeyStrategy +{ + private readonly ResilienceStrategyTelemetry _telemetry; + + public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrategyTelemetry telemetry, bool isGeneric) + : base(isGeneric, options) + { + Guard.NotNull(telemetry); + + if (options.Outcome.Exception is null && options.OutcomeGenerator is null) + { + throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); + } + + _telemetry = telemetry; + Fault = options.Outcome; + OnFaultInjected = options.OnOutcomeInjected; + FaultGenerator = options.Outcome.Exception is not null ? (_) => new(options.Outcome) : options.OutcomeGenerator; + } + + public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrategyTelemetry telemetry, bool isGeneric) + : base(isGeneric, options) + { + Guard.NotNull(telemetry); + + if (!options.Outcome.HasResult && options.OutcomeGenerator is null) + { + throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); + } + + _telemetry = telemetry; + Outcome = options.Outcome; + OnOutcomeInjected = options.OnOutcomeInjected; + OutcomeGenerator = options.Outcome.HasResult ? (_) => new(options.Outcome) : options.OutcomeGenerator; + } + + public Func, ValueTask>? OnOutcomeInjected { get; } + + public Func, ValueTask>? OnFaultInjected { get; } + + public Func>>? OutcomeGenerator { get; } + + public Func>>? FaultGenerator { get; } + + public Outcome? Outcome { get; } + + public Outcome? Fault { get; } + + protected override async ValueTask> ExecuteCallbackAsync(Func>> callback, ResilienceContext context, TState state) + { + try + { + if (await ShouldInject(context).ConfigureAwait(context.ContinueOnCapturedContext)) + { + if (FaultGenerator is not null) + { + var fault = await InjectFault(context).ConfigureAwait(context.ContinueOnCapturedContext); + if (fault is not null) + { + return new Outcome(fault); + } + } + else if (OutcomeGenerator is not null) + { + return await InjectOutcome(context).ConfigureAwait(context.ContinueOnCapturedContext); + } + else + { + throw new InvalidOperationException("Either a fault or fake outcome to inject must be defined."); + } + } + + return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); + } + catch (OperationCanceledException e) + { + return new Outcome(e); + } + } + + private async ValueTask> InjectOutcome(ResilienceContext context) + { + var outcome = await OutcomeGenerator!(context).ConfigureAwait(context.ContinueOnCapturedContext); + var args = new OnOutcomeInjectedArguments(context, outcome); + _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); + + if (OnOutcomeInjected is not null) + { + await OnOutcomeInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); + } + + return outcome; + } + + private async ValueTask InjectFault(ResilienceContext context) + { + var fault = await FaultGenerator!(context).ConfigureAwait(context.ContinueOnCapturedContext); + if (fault.Exception is not null) + { + var args = new OnOutcomeInjectedArguments(context, fault); + _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); + + if (OnFaultInjected is not null) + { + await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); + } + } + + return fault.Exception; + } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index 5f3dd6bfa21..70ee98fd233 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -2,59 +2,10 @@ namespace Polly.Simmy.Outcomes; -#pragma warning disable S3928 // Custom ArgumentNullException message - -internal sealed class OutcomeChaosStrategy : MonkeyStrategy +internal sealed class OutcomeChaosStrategy : OutcomeChaosStrategy { - private readonly ResilienceStrategyTelemetry _telemetry; - - public OutcomeChaosStrategy(OutcomeStrategyOptions options, - ResilienceStrategyTelemetry telemetry) - : base(options) + public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrategyTelemetry telemetry, bool isGeneric) + : base(options, telemetry, isGeneric) { - Guard.NotNull(telemetry); - - if (!options.Outcome.HasResult && options.OutcomeGenerator is null) - { - throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); - } - - _telemetry = telemetry; - Outcome = options.Outcome; - OutcomeGenerator = options.Outcome.HasResult ? (_) => new(options.Outcome) : options.OutcomeGenerator; - OnOutcomeInjected = options.OnOutcomeInjected; - } - - public Func, ValueTask>? OnOutcomeInjected { get; } - - public Func>> OutcomeGenerator { get; } - - public Outcome Outcome { get; } - - protected internal override async ValueTask> ExecuteCoreAsync( - Func>> callback, ResilienceContext context, TState state) - { - try - { - if (await ShouldInject(context).ConfigureAwait(context.ContinueOnCapturedContext)) - { - var outcome = await OutcomeGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext); - var args = new OnOutcomeInjectedArguments(context, outcome); - _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); - - if (OnOutcomeInjected is not null) - { - await OnOutcomeInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); - } - - return outcome; - } - - return await ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); - } - catch (OperationCanceledException e) - { - return new Outcome(e); - } } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.TResult.cs new file mode 100644 index 00000000000..874e4dd7fe1 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.TResult.cs @@ -0,0 +1,145 @@ +namespace Polly.Simmy.Outcomes; + +/// +/// Extension methods for adding outcome to a . +/// +public static partial class OutcomeChaosStrategyBuilderExtensions +{ + /// + /// Adds a fault chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The exception to inject. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Exception fault) + { + Guard.NotNull(builder); + + return builder.AddFaultCore, TResult>(new OutcomeStrategyOptions + { + Enabled = enabled, + InjectionRate = injectionRate, + Outcome = new(fault) + }); + } + + /// + /// Adds a fault chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The exception generator delegate. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddFault( + this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Func>> faultGenerator) + { + Guard.NotNull(builder); + + return builder.AddFaultCore, TResult>(new OutcomeStrategyOptions + { + Enabled = enabled, + InjectionRate = injectionRate, + OutcomeGenerator = (_) => faultGenerator() + }); + } + + /// + /// Adds a fault chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// The fault strategy options. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, OutcomeStrategyOptions options) + { + Guard.NotNull(builder); + Guard.NotNull(options); + + return builder.AddFaultCore, TResult>(options); + } + + /// + /// Adds an outcome chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The outcome to inject. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, TResult result) + { + Guard.NotNull(builder); + + return builder.AddOutcomeCore(new OutcomeStrategyOptions + { + Enabled = enabled, + InjectionRate = injectionRate, + Outcome = new(result) + }); + } + + /// + /// Adds an outcome chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// A value that indicates whether or not the chaos strategy is enabled for a given execution. + /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The outcome generator delegate. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddResult( + this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Func>> outcomeGenerator) + { + Guard.NotNull(builder); + + return builder.AddOutcomeCore(new OutcomeStrategyOptions + { + Enabled = enabled, + InjectionRate = injectionRate, + OutcomeGenerator = (_) => outcomeGenerator() + }); + } + + /// + /// Adds an outcome chaos strategy to the builder. + /// + /// The type of result the retry strategy handles. + /// The builder instance. + /// The outcome strategy options. + /// The builder instance with the retry strategy added. + public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, OutcomeStrategyOptions options) + { + Guard.NotNull(builder); + Guard.NotNull(options); + + return builder.AddOutcomeCore(options); + } + + private static TBuilder AddOutcomeCore(this TBuilder builder, OutcomeStrategyOptions options) + where TBuilder : ResilienceStrategyBuilderBase + { + return builder.AddStrategy(context => + new OutcomeChaosStrategy( + options, + context.Telemetry, + context.IsGenericBuilder), + options); + } + + private static TBuilder AddFaultCore(this TBuilder builder, OutcomeStrategyOptions options) + where TBuilder : ResilienceStrategyBuilderBase + { + return builder.AddStrategy(context => + new OutcomeChaosStrategy( + options, + context.Telemetry, + context.IsGenericBuilder), + options); + } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs index fd6a1dc5e04..3ffc8ba13cf 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategyBuilderExtensions.cs @@ -3,7 +3,7 @@ /// /// Extension methods for adding outcome to a . /// -public static class OutcomeChaosStrategyBuilderExtensions +public static partial class OutcomeChaosStrategyBuilderExtensions { /// /// Adds a fault chaos strategy to the builder. @@ -17,7 +17,7 @@ public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder { Guard.NotNull(builder); - return builder.AddOutcomeCore(new OutcomeStrategyOptions + return builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, @@ -33,11 +33,12 @@ public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder /// The injection rate for a given execution, which the value should be between [0, 1]. /// The exception generator delegate. /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Func>> faultGenerator) + public static ResilienceStrategyBuilder AddFault( + this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Func>> faultGenerator) { Guard.NotNull(builder); - return builder.AddOutcomeCore(new OutcomeStrategyOptions + return builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, @@ -56,74 +57,17 @@ public static ResilienceStrategyBuilder AddFault(this ResilienceStrategyBuilder Guard.NotNull(builder); Guard.NotNull(options); - return builder.AddOutcomeCore(options); + return builder.AddFaultCore(options); } - /// - /// Adds an outcome chaos strategy to the builder. - /// - /// The type of result the retry strategy handles. - /// The builder instance. - /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. - /// The outcome to inject. - /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, TResult result) - { - Guard.NotNull(builder); - - return builder.AddOutcomeCore(new OutcomeStrategyOptions - { - Enabled = enabled, - InjectionRate = injectionRate, - Outcome = new(result) - }); - } - - /// - /// Adds an outcome chaos strategy to the builder. - /// - /// The type of result the retry strategy handles. - /// The builder instance. - /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. - /// The outcome generator delegate. - /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddResult( - this ResilienceStrategyBuilder builder, bool enabled, double injectionRate, Func>> outcomeGenerator) - { - Guard.NotNull(builder); - - return builder.AddOutcomeCore(new OutcomeStrategyOptions - { - Enabled = enabled, - InjectionRate = injectionRate, - OutcomeGenerator = (_) => outcomeGenerator() - }); - } - - /// - /// Adds an outcome chaos strategy to the builder. - /// - /// The type of result the retry strategy handles. - /// The builder instance. - /// The outcome strategy options. - /// The builder instance with the retry strategy added. - public static ResilienceStrategyBuilder AddResult(this ResilienceStrategyBuilder builder, OutcomeStrategyOptions options) - { - Guard.NotNull(builder); - Guard.NotNull(options); - - return builder.AddOutcomeCore(options); - } - - private static TBuilder AddOutcomeCore(this TBuilder builder, OutcomeStrategyOptions options) + private static TBuilder AddFaultCore(this TBuilder builder, OutcomeStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { return builder.AddStrategy(context => - new OutcomeChaosStrategy( + new OutcomeChaosStrategy( options, - context.Telemetry), + context.Telemetry, + context.IsGenericBuilder), options); } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs index c82c4e1c363..a6356f486cf 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs @@ -5,4 +5,6 @@ internal static class OutcomeConstants public const string StrategyType = "Outcome"; public const string OnOutcomeInjectedEvent = "OnOutcomeInjected"; + + public const string OnFaultInjectedEvent = "OnFaultInjectedEvent"; } diff --git a/src/Polly.Core/Simmy/Utils/OutcomeMonkeyStrategy.cs b/src/Polly.Core/Simmy/Utils/OutcomeMonkeyStrategy.cs new file mode 100644 index 00000000000..2958a2676e3 --- /dev/null +++ b/src/Polly.Core/Simmy/Utils/OutcomeMonkeyStrategy.cs @@ -0,0 +1,66 @@ +using Polly; +using Polly.Simmy; + +/// +/// This base strategy class is used to simplify the implementation of generic (reactive) +/// strategies by limiting the number of generic types this strategy receives. +/// +/// The type of result this strategy handles. +/// +/// For strategies that handle all result types the generic parameter must be of type . +/// +internal abstract class OutcomeMonkeyStrategy : MonkeyStrategy +{ + private readonly bool _isGeneric; + + protected OutcomeMonkeyStrategy(bool isGeneric, MonkeyStrategyOptions options) + : base(options) + { + if (!isGeneric && typeof(T) != typeof(object)) + { + throw new NotSupportedException("For non-generic strategies the generic parameter should be of type 'object'."); + } + + _isGeneric = isGeneric; + } + + protected internal sealed override ValueTask> ExecuteCoreAsync( + Func>> callback, + ResilienceContext context, + TState state) + { + if (_isGeneric) + { + if (typeof(TResult) != typeof(T)) + { + return callback(context, state); + } + + // cast is safe here, because TResult and T are the same type + var callbackCasted = (Func>>)(object)callback; + var valueTask = ExecuteCallbackAsync(callbackCasted, context, state); + + return OutcomeResilienceStrategyHelper.ConvertValueTask(valueTask, context); + } + else + { + var valueTask = ExecuteCallbackAsync( + static async (context, state) => + { + var outcome = await state.callback(context, state.state).ConfigureAwait(context.ContinueOnCapturedContext); + + // cast the outcome to "object" based on (T) + return outcome.AsOutcome(); + }, + context, + (callback, state)); + + return OutcomeResilienceStrategyHelper.ConvertValueTask(valueTask, context); + } + } + + protected abstract ValueTask> ExecuteCallbackAsync( + Func>> callback, + ResilienceContext context, + TState state); +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs new file mode 100644 index 00000000000..2ca8c4ca0d3 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -0,0 +1,67 @@ +using Moq; +using Polly.Simmy.Outcomes; +using Polly.Telemetry; + +namespace Polly.Core.Tests.Simmy.Outcomes; +public class OutcomeChaosStrategyTests +{ + private readonly ResilienceStrategyTelemetry _telemetry; + private readonly Mock _diagnosticSource = new(); + + public OutcomeChaosStrategyTests() + { + _telemetry = TestUtilities.CreateResilienceTelemetry(_diagnosticSource.Object); + } + + [Fact] + public async Task InjectFault_T_Result_enabled_should_throw_and_should_not_execute_user_delegate_async() + { + var outcome = new Outcome(new InvalidOperationException("Dummy exception")); + var userDelegateExecuted = false; + + // generic tests + + //var options = new OutcomeStrategyOptions + //{ + // InjectionRate = 0.6, + // Enabled = true, + // Randomizer = () => 0.5, + // Outcome = outcome + //}; + + var sut = new ResilienceStrategyBuilder() + .AddFault(true, 1, new InvalidOperationException("Dummy exception")) + .Build(); + + //var sut = new OutcomeChaosStrategy(options, _telemetry); + + await sut.Invoking(s => s.ExecuteAsync(_ => + { + userDelegateExecuted = true; + return default; + }).AsTask()) + .Should() + .ThrowAsync() + .WithMessage("Dummy exception"); + + userDelegateExecuted.Should().BeFalse(); + } + + [Fact] + public async Task InjectFault_enabled_should_throw_and_should_not_execute_user_delegate_async() + { + var userDelegateExecuted = false; + + // non-generic tests + var nonGenericSut = new ResilienceStrategyBuilder() + .AddFault(true, 1, new AggregateException("chimbo")) + .Build(); + + nonGenericSut.Invoking(s => s.Execute(_ => { userDelegateExecuted = true; })) + .Should() + .Throw() + .WithMessage("chimbo"); + + userDelegateExecuted.Should().BeFalse(); + } +} From 60db9d3f4e4b8e2a01ea1a95d71fa0005fe262c0 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 9 Jul 2023 17:30:34 -0500 Subject: [PATCH 16/36] put back MonkeyStrategy generic implemetnation so that people can extend generic chaos strategies? --- .../Simmy/MonkeyStrategy.TResult.cs | 108 ++++++++++++++++++ src/Polly.Core/Simmy/MonkeyStrategy.cs | 2 +- 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs new file mode 100644 index 00000000000..e8d29ea06a5 --- /dev/null +++ b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs @@ -0,0 +1,108 @@ +namespace Polly.Simmy; + +/// +/// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. +/// +/// The type of result this strategy supports. +public partial class MonkeyStrategy +{ + internal MonkeyStrategy(MonkeyStrategy strategy) => Strategy = strategy; + + internal MonkeyStrategy Strategy { get; } + + /// + /// Executes the specified 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. + /// The instance of that represents the asynchronous execution. + /// Thrown when or is . + public ValueTask ExecuteAsync( + Func> callback, + ResilienceContext context, + TState state) + { + Guard.NotNull(callback); + Guard.NotNull(context); + + return Strategy.ExecuteAsync(callback, context, state); + } + + /// + /// Executes the specified callback. + /// + /// The user-provided callback. + /// The context associated with the callback. + /// The instance of that represents the asynchronous execution. + /// Thrown when or is . + public ValueTask ExecuteAsync( + Func> callback, + ResilienceContext context) + { + Guard.NotNull(callback); + Guard.NotNull(context); + + return Strategy.ExecuteAsync(callback, context); + } + + /// + /// Executes the specified callback. + /// + /// The type of state associated with the callback. + /// The user-provided callback. + /// The state associated with the callback. + /// The associated with the callback. + /// The instance of that represents the asynchronous execution. + /// Thrown when is . + public ValueTask ExecuteAsync( + Func> callback, + TState state, + CancellationToken cancellationToken = default) + { + Guard.NotNull(callback); + + return Strategy.ExecuteAsync(callback, state, cancellationToken); + } + + /// + /// Executes the specified callback. + /// + /// The user-provided callback. + /// The associated with the callback. + /// The instance of that represents the asynchronous execution. + /// Thrown when is . + public ValueTask ExecuteAsync( + Func> callback, + CancellationToken cancellationToken = default) + { + Guard.NotNull(callback); + + return Strategy.ExecuteAsync(callback, cancellationToken); + } + + /// + /// Executes the specified outcome-based 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. + /// The instance of that represents the asynchronous execution. + /// Thrown when or is . + /// + /// This method is for advanced and high performance scenarios. The caller must make sure that the + /// does not throw any exceptions. Instead, it converts them to . + /// + public ValueTask> ExecuteOutcomeAsync( + Func>> callback, + ResilienceContext context, + TState state) + { + Guard.NotNull(callback); + Guard.NotNull(context); + + return Strategy.ExecuteOutcomeAsync(callback, context, state); + } +} diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.cs b/src/Polly.Core/Simmy/MonkeyStrategy.cs index caa046d60bd..51f9506ffe7 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.cs @@ -8,7 +8,7 @@ namespace Polly.Simmy; /// /// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. /// -public abstract class MonkeyStrategy : ResilienceStrategy +public abstract partial class MonkeyStrategy : ResilienceStrategy { private readonly Func _randomizer; From 0d6c306b4922b7160891deeee1fc30f880ff5753 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Mon, 7 Aug 2023 00:35:22 +0000 Subject: [PATCH 17/36] fault unit tests --- src/Polly.Core/Outcome.TResult.cs | 8 +- .../Outcomes/OutcomeChaosStrategy.TResult.cs | 37 +-- ...positeStrategyBuilderExtensions.TResult.cs | 32 ++- ...tcomeCompositeStrategyBuilderExtensions.cs | 14 +- .../Simmy/MonkeyStrategyTests.cs | 5 + .../OnOutcomeInjectedArgumentsTests.cs | 13 ++ .../Outcomes/OutcomeChaosStrategyTests.cs | 221 +++++++++++++++--- .../OutcomeCompositeBuilderExtensionsTests.cs | 201 ++++++++++++++++ .../Simmy/Outcomes/OutcomeConstantsTests.cs | 13 ++ .../Outcomes/OutcomeStrategyOptionsTests.cs | 20 ++ 10 files changed, 502 insertions(+), 62 deletions(-) create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs diff --git a/src/Polly.Core/Outcome.TResult.cs b/src/Polly.Core/Outcome.TResult.cs index 21e1c337426..f58cbd540f9 100644 --- a/src/Polly.Core/Outcome.TResult.cs +++ b/src/Polly.Core/Outcome.TResult.cs @@ -15,7 +15,13 @@ namespace Polly; public readonly struct Outcome { internal Outcome(Exception exception) - : this() => ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(Guard.NotNull(exception)); + : this() + { + if (exception != null) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); + } + } internal Outcome(TResult? result) : this() => Result = result; diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs index af44f885a69..6612eb572e2 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs @@ -29,11 +29,6 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg { Guard.NotNull(telemetry); - if (!options.Outcome.HasResult && options.OutcomeGenerator is null) - { - throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); - } - _telemetry = telemetry; Outcome = options.Outcome; OnOutcomeInjected = options.OnOutcomeInjected; @@ -48,7 +43,7 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg public Func>>? FaultGenerator { get; } - public Outcome? Outcome { get; } + public Outcome? Outcome { get; private set; } public Outcome? Fault { get; } @@ -100,18 +95,32 @@ private async ValueTask> InjectOutcome(ResilienceContext context) private async ValueTask InjectFault(ResilienceContext context) { - var fault = await FaultGenerator!(context).ConfigureAwait(context.ContinueOnCapturedContext); - if (fault.Exception is not null) + try { - var args = new OnOutcomeInjectedArguments(context, fault); - _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); + var fault = await FaultGenerator!(context).ConfigureAwait(context.ContinueOnCapturedContext); + + // to prevent injecting the fault if it was cancelled while executing the FaultGenerator + context.CancellationToken.ThrowIfCancellationRequested(); - if (OnFaultInjected is not null) + if (fault.Exception is not null) { - await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); + Outcome = new(fault.Exception); + var args = new OnOutcomeInjectedArguments(context, fault); + _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); + + if (OnFaultInjected is not null) + { + await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); + } } - } - return fault.Exception; + return fault.Exception; + } + catch (OperationCanceledException) + { + // fault injection might be cancelled during FaultGenerator, if so we run the user's delegate normally + context.CancellationToken = CancellationToken.None; + return null; + } } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs index 2538e18c23a..060a5c5c822 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs @@ -20,12 +20,13 @@ public static CompositeStrategyBuilder AddFault(this Composite { Guard.NotNull(builder); - return builder.AddFaultCore, TResult>(new OutcomeStrategyOptions + builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, Outcome = new(fault) }); + return builder; } /// @@ -42,12 +43,13 @@ public static CompositeStrategyBuilder AddFault( { Guard.NotNull(builder); - return builder.AddFaultCore, TResult>(new OutcomeStrategyOptions + builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, OutcomeGenerator = (_) => faultGenerator() }); + return builder; } /// @@ -62,7 +64,8 @@ public static CompositeStrategyBuilder AddFault(this Composite Guard.NotNull(builder); Guard.NotNull(options); - return builder.AddFaultCore, TResult>(options); + builder.AddFaultCore(options); + return builder; } /// @@ -78,12 +81,13 @@ public static CompositeStrategyBuilder AddResult(this Composit { Guard.NotNull(builder); - return builder.AddOutcomeCore(new OutcomeStrategyOptions + builder.AddOutcomeCore>(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, Outcome = new(result) }); + return builder; } /// @@ -100,12 +104,13 @@ public static CompositeStrategyBuilder AddResult( { Guard.NotNull(builder); - return builder.AddOutcomeCore(new OutcomeStrategyOptions + builder.AddOutcomeCore>(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, OutcomeGenerator = (_) => outcomeGenerator() }); + return builder; } /// @@ -120,17 +125,19 @@ public static CompositeStrategyBuilder AddResult(this Composit Guard.NotNull(builder); Guard.NotNull(options); - return builder.AddOutcomeCore(options); + builder.AddOutcomeCore>(options); + return builder; } [UnconditionalSuppressMessage( "Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "All options members preserved.")] - private static TBuilder AddOutcomeCore(this TBuilder builder, OutcomeStrategyOptions options) - where TBuilder : CompositeStrategyBuilderBase + private static void AddOutcomeCore( + this CompositeStrategyBuilderBase builder, + OutcomeStrategyOptions options) { - return builder.AddStrategy(context => + builder.AddStrategy(context => new OutcomeChaosStrategy( options, context.Telemetry), @@ -141,10 +148,11 @@ private static TBuilder AddOutcomeCore(this TBuilder builder, "Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "All options members preserved.")] - private static TBuilder AddFaultCore(this TBuilder builder, OutcomeStrategyOptions options) - where TBuilder : CompositeStrategyBuilderBase + private static void AddFaultCore( + this CompositeStrategyBuilderBase builder, + OutcomeStrategyOptions options) { - return builder.AddStrategy(context => + builder.AddStrategy(context => new OutcomeChaosStrategy( options, context.Telemetry), diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs index b96111b3796..dfde92ec48b 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs @@ -19,12 +19,13 @@ public static CompositeStrategyBuilder AddFault(this CompositeStrategyBuilder bu { Guard.NotNull(builder); - return builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, Outcome = new(fault) }); + return builder; } /// @@ -40,12 +41,13 @@ public static CompositeStrategyBuilder AddFault( { Guard.NotNull(builder); - return builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, OutcomeGenerator = (_) => faultGenerator() }); + return builder; } /// @@ -59,17 +61,17 @@ public static CompositeStrategyBuilder AddFault(this CompositeStrategyBuilder bu Guard.NotNull(builder); Guard.NotNull(options); - return builder.AddFaultCore(options); + builder.AddFaultCore(options); + return builder; } [UnconditionalSuppressMessage( "Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "All options members preserved.")] - private static TBuilder AddFaultCore(this TBuilder builder, OutcomeStrategyOptions options) - where TBuilder : CompositeStrategyBuilderBase + private static void AddFaultCore(this CompositeStrategyBuilderBase builder, OutcomeStrategyOptions options) { - return builder.AddStrategy(context => + builder.AddStrategy(context => new OutcomeChaosStrategy( options, context.Telemetry), diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs index 4cd701aadbb..d5ac5caa8f7 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs @@ -25,9 +25,14 @@ public void InvalidCtor(TestChaosStrategyOptions options, string message) var _ = new TestChaosStrategy(options); }; +#if NET481 +act.Should() + .Throw(); +#else act.Should() .Throw() .WithMessage(message); +#endif } [Fact] diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs new file mode 100644 index 00000000000..f847c89fac2 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class OnOutcomeInjectedArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new OnOutcomeInjectedArguments(ResilienceContextPool.Shared.Get(), new Outcome(1)); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 51b26ffca5d..39027582763 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -3,65 +3,228 @@ using Polly.Telemetry; namespace Polly.Core.Tests.Simmy.Outcomes; + public class OutcomeChaosStrategyTests { private readonly ResilienceStrategyTelemetry _telemetry; private readonly Mock _diagnosticSource = new(); - public OutcomeChaosStrategyTests() + public OutcomeChaosStrategyTests() => _telemetry = TestUtilities.CreateResilienceTelemetry(_diagnosticSource.Object); + + public static List FaultCtorTestCases => + new() + { + new object[] { null!, "Value cannot be null. (Parameter 'options')" }, + new object[] { new OutcomeStrategyOptions + { + InjectionRate = 1, + Enabled = true, + }, "Either Outcome or OutcomeGenerator is required. (Parameter 'Outcome')" }, + }; + + [Theory] + [MemberData(nameof(FaultCtorTestCases))] + public void FaultInvalidCtor(OutcomeStrategyOptions options, string message) { - _telemetry = TestUtilities.CreateResilienceTelemetry(_diagnosticSource.Object); + Action act = () => + { + var _ = new OutcomeChaosStrategy(options, _telemetry); + }; + +#if NET481 +act.Should() + .Throw(); +#else + act.Should() + .Throw() + .WithMessage(message); +#endif } [Fact] - public async Task InjectFault_T_Result_enabled_should_throw_and_should_not_execute_user_delegate_async() + public void Given_not_enabled_should_not_inject_fault() { - var outcome = new Outcome(new InvalidOperationException("Dummy exception")); var userDelegateExecuted = false; + var fault = new InvalidOperationException("Dummy exception"); - // generic tests + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = false, + Randomizer = () => 0.5, + Outcome = new Outcome(fault) + }; - //var options = new OutcomeStrategyOptions - //{ - // InjectionRate = 0.6, - // Enabled = true, - // Randomizer = () => 0.5, - // Outcome = outcome - //}; + var sut = CreateSut(options); + sut.Execute(() => { userDelegateExecuted = true; }); - var sut = new CompositeStrategyBuilder() - .AddFault(true, 1, new InvalidOperationException("Dummy exception")) - .Build(); + userDelegateExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Given_enabled_and_randomly_within_threshold_should_inject_fault() + { + var userDelegateExecuted = false; + var exceptionMessage = "Dummy exception"; + var fault = new InvalidOperationException(exceptionMessage); - //var sut = new OutcomeChaosStrategy(options, _telemetry); + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + Outcome = new Outcome(fault) + }; - await sut.Invoking(s => s.ExecuteAsync(_ => + var sut = CreateSut(options); + await sut.Invoking(s => s.ExecuteAsync(async _ => { userDelegateExecuted = true; - return default; + return await Task.FromResult(new Outcome(200)); }).AsTask()) .Should() .ThrowAsync() - .WithMessage("Dummy exception"); + .WithMessage(exceptionMessage); userDelegateExecuted.Should().BeFalse(); } [Fact] - public async Task InjectFault_enabled_should_throw_and_should_not_execute_user_delegate_async() + public void Given_enabled_and_randomly_not_within_threshold_should_not_inject_fault() { var userDelegateExecuted = false; + var fault = new InvalidOperationException("Dummy exception"); - // non-generic tests - var nonGenericSut = new CompositeStrategyBuilder() - .AddFault(true, 1, new AggregateException("chimbo")) - .Build(); + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.3, + Enabled = true, + Randomizer = () => 0.5, + Outcome = new Outcome(fault) + }; - nonGenericSut.Invoking(s => s.Execute(_ => { userDelegateExecuted = true; })) - .Should() - .Throw() - .WithMessage("chimbo"); + var sut = CreateSut(options); + var result = sut.Execute(_ => + { + userDelegateExecuted = true; + return 200; + }); - userDelegateExecuted.Should().BeFalse(); + result.Should().Be(200); + userDelegateExecuted.Should().BeTrue(); + } + + [Fact] + public void Given_enabled_and_randomly_within_threshold_should_not_inject_fault_when_exception_is_null() + { + var userDelegateExecuted = false; + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + OutcomeGenerator = (_) => new ValueTask>(Task.FromResult(new Outcome(null!))) + }; + + var sut = CreateSut(options); + sut.Execute(_ => + { + userDelegateExecuted = true; + }); + + userDelegateExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Should_not_inject_fault_when_it_was_cancelled_running_the_fault_generator() + { + var userDelegateExecuted = false; + var fault = new InvalidOperationException("Dummy exception"); + + using var cts = new CancellationTokenSource(); + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + OutcomeGenerator = (_) => + { + cts.Cancel(); + return new ValueTask>(new Outcome(fault)); + } + }; + + var sut = CreateSut(options); + var restult = await sut.ExecuteAsync(async _ => + { + userDelegateExecuted = true; + return await Task.FromResult(200); + }, cts.Token); + + restult.Should().Be(200); + userDelegateExecuted.Should().BeTrue(); } + + //[Fact] + //public async Task InjectFault_T_Result_enabled_should_throw_and_should_not_execute_user_delegate_async() + //{ + // // generic tests + + // //var outcome = new Outcome(new InvalidOperationException("Dummy exception")); + // //var options = new OutcomeStrategyOptions + // //{ + // // InjectionRate = 0.6, + // // Enabled = true, + // // Randomizer = () => 0.5, + // // Outcome = outcome + // //}; + + // //var sut = new OutcomeChaosStrategy(options, _telemetry); + // //var sut2 = new CompositeStrategyBuilder() + // // .AddFault(options) + // // .Build(); + + // var userDelegateExecuted = false; + // var sut = new CompositeStrategyBuilder() + // .AddFault(true, 1, new InvalidOperationException("Dummy exception")) + // .Build(); + + // await sut.Invoking(s => s.ExecuteAsync(_ => + // { + // userDelegateExecuted = true; + // return default; + // }).AsTask()) + // .Should() + // .ThrowAsync() + // .WithMessage("Dummy exception"); + + // userDelegateExecuted.Should().BeFalse(); + //} + + //[Fact] + //public async Task InjectFault_enabled_should_throw_and_should_not_execute_user_delegate_async() + //{ + // var userDelegateExecuted = false; + + // // non-generic tests + // var nonGenericSut = new CompositeStrategyBuilder() + // .AddFault(true, 1, new AggregateException("chimbo")) + // .Build(); + + // nonGenericSut.Invoking(s => s.Execute(_ => { userDelegateExecuted = true; })) + // .Should() + // .Throw() + // .WithMessage("chimbo"); + + // userDelegateExecuted.Should().BeFalse(); + //} + + private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions options) => + new(options, _telemetry); + + private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions options) => + new(options, _telemetry); + + private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions options) => + new(options, _telemetry); } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs new file mode 100644 index 00000000000..8da103f0818 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs @@ -0,0 +1,201 @@ +using System.ComponentModel.DataAnnotations; +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class OutcomeCompositeBuilderExtensionsTests +{ + public static readonly TheoryData>> ResultStrategy = new() + { + builder => + { + builder.AddResult(new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + Outcome = new(100) + }); + + AssertResultStrategy(builder, true, 0.6, new(100)); + } + }; + + public static readonly TheoryData>> FaultGenericStrategy = new() + { + builder => + { + builder.AddFault(new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + Outcome = new(new InvalidOperationException("Dummy exception.")) + }); + + AssertFaultStrategy(builder, true, 0.6, new InvalidOperationException("Dummy exception.")); + } + }; + + public static readonly TheoryData> FaultStrategy = new() + { + builder => + { + builder.AddFault(new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + Outcome = new(new InvalidOperationException("Dummy exception.")) + }); + + AssertFaultStrategy(builder, true, 0.6, new InvalidOperationException("Dummy exception.")); + } + }; + + private static void AssertResultStrategy(CompositeStrategyBuilder builder, bool enabled, double injectionRate, Outcome outcome) + { + var context = ResilienceContextPool.Shared.Get(); + var strategy = (OutcomeChaosStrategy)builder.Build().Strategy; + strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.OutcomeGenerator.Should().NotBeNull(); + strategy.OutcomeGenerator!.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(outcome); + strategy.Outcome.Should().Be(outcome); + } + + private static void AssertFaultStrategy(CompositeStrategyBuilder builder, bool enabled, double injectionRate, Exception ex) + where TException : Exception + { + var context = ResilienceContextPool.Shared.Get(); + var strategy = (OutcomeChaosStrategy)builder.Build().Strategy; + strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.FaultGenerator.Should().NotBeNull(); + strategy.Fault.Should().BeOfType(typeof(Outcome)); + strategy.Fault.Should().NotBeNull(); + + // it is supposed that this line should work the same as the try/catch block, but it's not, ideas? + //Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); + + try + { + var _ = strategy.Fault!.Value; + } + catch (Exception e) + { + e.Should().Be(ex); + } + } + + private static void AssertFaultStrategy(CompositeStrategyBuilder builder, bool enabled, double injectionRate, Exception ex) + where TException : Exception + { + var context = ResilienceContextPool.Shared.Get(); + var strategy = (OutcomeChaosStrategy)builder.Build(); + strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.FaultGenerator.Should().NotBeNull(); + strategy.Fault.Should().BeOfType(typeof(Outcome)); + strategy.Fault.Should().NotBeNull(); + + // it is supposed that this line should work the same as the try/catch block, but it's not, ideas? + //Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); + + try + { + var _ = strategy.Fault!.Value; + } + catch (Exception e) + { + e.Should().Be(ex); + } + } + + [MemberData(nameof(ResultStrategy))] + [Theory] + internal void AddResult_Options_Ok(Action> configure) + { + var builder = new CompositeStrategyBuilder(); + builder.Invoking(b => configure(b)).Should().NotThrow(); + } + + [MemberData(nameof(FaultGenericStrategy))] + [Theory] + internal void AddFault_Generic_Options_Ok(Action> configure) + { + var builder = new CompositeStrategyBuilder(); + builder.Invoking(b => configure(b)).Should().NotThrow(); + } + + [MemberData(nameof(FaultStrategy))] + [Theory] + internal void AddFault_Options_Ok(Action configure) + { + var builder = new CompositeStrategyBuilder(); + builder.Invoking(b => configure(b)).Should().NotThrow(); + } + + [Fact] + public void AddResult_Shortcut_Option_Ok() + { + var builder = new CompositeStrategyBuilder(); + builder + .AddResult(true, 0.5, 120) + .Build(); + + AssertResultStrategy(builder, true, 0.5, new(120)); + } + + [Fact] + public void AddResult_Shortcut_Option_Throws() + { + new CompositeStrategyBuilder() + .Invoking(b => b.AddResult(true, -1, () => new ValueTask>(Task.FromResult(new Outcome(120))))) + .Should() + .Throw(); + } + + [Fact] + public void AddFault_Shortcut_Option_Ok() + { + var builder = new CompositeStrategyBuilder(); + builder + .AddFault(true, 0.5, new InvalidOperationException("Dummy exception")) + .Build(); + + AssertFaultStrategy(builder, true, 0.5, new InvalidOperationException("Dummy exception")); + } + + [Fact] + public void AddFault_Shortcut_Option_Throws() + { + new CompositeStrategyBuilder() + .Invoking(b => b.AddFault( + true, + 1.5, + () => new ValueTask>(Task.FromResult(new Outcome(new InvalidOperationException()))))) + .Should() + .Throw(); + } + + [Fact] + public void AddFault_Generic_Shortcut_Option_Ok() + { + var builder = new CompositeStrategyBuilder(); + builder + .AddFault(true, 0.5, new InvalidOperationException("Dummy exception")) + .Build(); + + AssertFaultStrategy(builder, true, 0.5, new InvalidOperationException("Dummy exception")); + } + + [Fact] + public void AddFault_Generic_Shortcut_Option_Throws() + { + new CompositeStrategyBuilder() + .Invoking(b => b.AddFault(true, -1, new InvalidOperationException())) + .Should() + .Throw(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs new file mode 100644 index 00000000000..f0427f90cd5 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeConstantsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class OutcomeConstantsTests +{ + [Fact] + public void EnsureDefaults() + { + OutcomeConstants.OnFaultInjectedEvent.Should().Be("OnFaultInjectedEvent"); + OutcomeConstants.OnOutcomeInjectedEvent.Should().Be("OnOutcomeInjected"); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs new file mode 100644 index 00000000000..ea08db8954f --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs @@ -0,0 +1,20 @@ +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class OutcomeStrategyOptionsTests +{ + [Fact] + public void Ctor_Ok() + { + var sut = new OutcomeStrategyOptions(); + sut.Randomizer.Should().NotBeNull(); + sut.Enabled.Should().BeNull(); + sut.EnabledGenerator.Should().BeNull(); + sut.InjectionRate.Should().BeNull(); + sut.InjectionRateGenerator.Should().BeNull(); + sut.Outcome.Should().Be(default(Outcome)); + sut.OnOutcomeInjected.Should().BeNull(); + sut.OutcomeGenerator.Should().BeNull(); + } +} From adf8120699d35ef428a6123eed867fe5ab5927c8 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Mon, 7 Aug 2023 01:00:05 +0000 Subject: [PATCH 18/36] add result unit tests --- .../Outcomes/OutcomeChaosStrategyTests.cs | 525 ++++++++++++++++-- 1 file changed, 472 insertions(+), 53 deletions(-) diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 39027582763..aa67c2ce44c 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -165,59 +165,76 @@ public async Task Should_not_inject_fault_when_it_was_cancelled_running_the_faul userDelegateExecuted.Should().BeTrue(); } - //[Fact] - //public async Task InjectFault_T_Result_enabled_should_throw_and_should_not_execute_user_delegate_async() - //{ - // // generic tests - - // //var outcome = new Outcome(new InvalidOperationException("Dummy exception")); - // //var options = new OutcomeStrategyOptions - // //{ - // // InjectionRate = 0.6, - // // Enabled = true, - // // Randomizer = () => 0.5, - // // Outcome = outcome - // //}; - - // //var sut = new OutcomeChaosStrategy(options, _telemetry); - // //var sut2 = new CompositeStrategyBuilder() - // // .AddFault(options) - // // .Build(); - - // var userDelegateExecuted = false; - // var sut = new CompositeStrategyBuilder() - // .AddFault(true, 1, new InvalidOperationException("Dummy exception")) - // .Build(); - - // await sut.Invoking(s => s.ExecuteAsync(_ => - // { - // userDelegateExecuted = true; - // return default; - // }).AsTask()) - // .Should() - // .ThrowAsync() - // .WithMessage("Dummy exception"); - - // userDelegateExecuted.Should().BeFalse(); - //} - - //[Fact] - //public async Task InjectFault_enabled_should_throw_and_should_not_execute_user_delegate_async() - //{ - // var userDelegateExecuted = false; - - // // non-generic tests - // var nonGenericSut = new CompositeStrategyBuilder() - // .AddFault(true, 1, new AggregateException("chimbo")) - // .Build(); - - // nonGenericSut.Invoking(s => s.Execute(_ => { userDelegateExecuted = true; })) - // .Should() - // .Throw() - // .WithMessage("chimbo"); - - // userDelegateExecuted.Should().BeFalse(); - //} + [Fact] + public void Given_not_enabled_should_not_inject_result() + { + var userDelegateExecuted = false; + var fakeResult = HttpStatusCode.TooManyRequests; + + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = false, + Randomizer = () => 0.5, + Outcome = new Outcome(fakeResult) + }; + + var sut = CreateSut(options); + var response = sut.Execute(() => { userDelegateExecuted = true; return HttpStatusCode.OK; }); + + response.Should().Be(HttpStatusCode.OK); + userDelegateExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Given_enabled_and_randomly_within_threshold_should_inject_result() + { + var userDelegateExecuted = false; + var fakeResult = HttpStatusCode.TooManyRequests; + + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + Outcome = new Outcome(fakeResult) + }; + + var sut = CreateSut(options); + var response = await sut.ExecuteAsync(async _ => + { + userDelegateExecuted = true; + return await Task.FromResult(HttpStatusCode.OK); + }); + + response.Should().Be(fakeResult); + userDelegateExecuted.Should().BeFalse(); + } + + [Fact] + public void Given_enabled_and_randomly_not_within_threshold_should_not_inject_result() + { + var userDelegateExecuted = false; + var fakeResult = HttpStatusCode.TooManyRequests; + + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.3, + Enabled = false, + Randomizer = () => 0.5, + Outcome = new Outcome(fakeResult) + }; + + var sut = CreateSut(options); + var response = sut.Execute(_ => + { + userDelegateExecuted = true; + return HttpStatusCode.OK; + }); + + response.Should().Be(HttpStatusCode.OK); + userDelegateExecuted.Should().BeTrue(); + } private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions options) => new(options, _telemetry); @@ -228,3 +245,405 @@ private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions< private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions options) => new(options, _telemetry); } + +/// +/// Borrowing this from the actual dotnet standard implementation since it is not available in the net481. +/// +public enum HttpStatusCode +{ + // Summary: + // Equivalent to HTTP status 100. System.Net.HttpStatusCode.Continue indicates that + // the client can continue with its request. + Continue = 100, + + // Summary: + // Equivalent to HTTP status 101. System.Net.HttpStatusCode.SwitchingProtocols indicates + // that the protocol version or protocol is being changed. + SwitchingProtocols = 101, + + // Summary: + // Equivalent to HTTP status 102. System.Net.HttpStatusCode.Processing indicates + // that the server has accepted the complete request but hasn't completed it yet. + Processing = 102, + + // Summary: + // Equivalent to HTTP status 103. System.Net.HttpStatusCode.EarlyHints indicates + // to the client that the server is likely to send a final response with the header + // fields included in the informational response. + EarlyHints = 103, + + // Summary: + // Equivalent to HTTP status 200. System.Net.HttpStatusCode.OK indicates that the + // request succeeded and that the requested information is in the response. This + // is the most common status code to receive. + OK = 200, + + // Summary: + // Equivalent to HTTP status 201. System.Net.HttpStatusCode.Created indicates that + // the request resulted in a new resource created before the response was sent. + Created = 201, + + // Summary: + // Equivalent to HTTP status 202. System.Net.HttpStatusCode.Accepted indicates that + // the request has been accepted for further processing. + Accepted = 202, + + // Summary: + // Equivalent to HTTP status 203. System.Net.HttpStatusCode.NonAuthoritativeInformation + // indicates that the returned meta information is from a cached copy instead of + // the origin server and therefore may be incorrect. + NonAuthoritativeInformation = 203, + + // Summary: + // Equivalent to HTTP status 204. System.Net.HttpStatusCode.NoContent indicates + // that the request has been successfully processed and that the response is intentionally + // blank. + NoContent = 204, + + // Summary: + // Equivalent to HTTP status 205. System.Net.HttpStatusCode.ResetContent indicates + // that the client should reset (not reload) the current resource. + ResetContent = 205, + + // Summary: + // Equivalent to HTTP status 206. System.Net.HttpStatusCode.PartialContent indicates + // that the response is a partial response as requested by a GET request that includes + // a byte range. + PartialContent = 206, + + // Summary: + // Equivalent to HTTP status 207. System.Net.HttpStatusCode.MultiStatus indicates + // multiple status codes for a single response during a Web Distributed Authoring + // and Versioning (WebDAV) operation. The response body contains XML that describes + // the status codes. + MultiStatus = 207, + + // Summary: + // Equivalent to HTTP status 208. System.Net.HttpStatusCode.AlreadyReported indicates + // that the members of a WebDAV binding have already been enumerated in a preceding + // part of the multistatus response, and are not being included again. + AlreadyReported = 208, + + // Summary: + // Equivalent to HTTP status 226. System.Net.HttpStatusCode.IMUsed indicates that + // the server has fulfilled a request for the resource, and the response is a representation + // of the result of one or more instance-manipulations applied to the current instance. + IMUsed = 226, + + // Summary: + // Equivalent to HTTP status 300. System.Net.HttpStatusCode.Ambiguous indicates + // that the requested information has multiple representations. The default action + // is to treat this status as a redirect and follow the contents of the Location + // header associated with this response. Ambiguous is a synonym for MultipleChoices. + Ambiguous = 300, + + // Summary: + // Equivalent to HTTP status 300. System.Net.HttpStatusCode.MultipleChoices indicates + // that the requested information has multiple representations. The default action + // is to treat this status as a redirect and follow the contents of the Location + // header associated with this response. MultipleChoices is a synonym for Ambiguous. + MultipleChoices = 300, + + // Summary: + // Equivalent to HTTP status 301. System.Net.HttpStatusCode.Moved indicates that + // the requested information has been moved to the URI specified in the Location + // header. The default action when this status is received is to follow the Location + // header associated with the response. When the original request method was POST, + // the redirected request will use the GET method. Moved is a synonym for MovedPermanently. + Moved = 301, + + // Summary: + // Equivalent to HTTP status 301. System.Net.HttpStatusCode.MovedPermanently indicates + // that the requested information has been moved to the URI specified in the Location + // header. The default action when this status is received is to follow the Location + // header associated with the response. MovedPermanently is a synonym for Moved. + MovedPermanently = 301, + + // Summary: + // Equivalent to HTTP status 302. System.Net.HttpStatusCode.Found indicates that + // the requested information is located at the URI specified in the Location header. + // The default action when this status is received is to follow the Location header + // associated with the response. When the original request method was POST, the + // redirected request will use the GET method. Found is a synonym for Redirect. + Found = 302, + + // Summary: + // Equivalent to HTTP status 302. System.Net.HttpStatusCode.Redirect indicates that + // the requested information is located at the URI specified in the Location header. + // The default action when this status is received is to follow the Location header + // associated with the response. When the original request method was POST, the + // redirected request will use the GET method. Redirect is a synonym for Found. + Redirect = 302, + + // Summary: + // Equivalent to HTTP status 303. System.Net.HttpStatusCode.RedirectMethod automatically + // redirects the client to the URI specified in the Location header as the result + // of a POST. The request to the resource specified by the Location header will + // be made with a GET. RedirectMethod is a synonym for SeeOther. + RedirectMethod = 303, + + // Summary: + // Equivalent to HTTP status 303. System.Net.HttpStatusCode.SeeOther automatically + // redirects the client to the URI specified in the Location header as the result + // of a POST. The request to the resource specified by the Location header will + // be made with a GET. SeeOther is a synonym for RedirectMethod. + SeeOther = 303, + + // Summary: + // Equivalent to HTTP status 304. System.Net.HttpStatusCode.NotModified indicates + // that the client's cached copy is up to date. The contents of the resource are + // not transferred. + NotModified = 304, + + // Summary: + // Equivalent to HTTP status 305. System.Net.HttpStatusCode.UseProxy indicates that + // the request should use the proxy server at the URI specified in the Location + // header. + UseProxy = 305, + + // Summary: + // Equivalent to HTTP status 306. System.Net.HttpStatusCode.Unused is a proposed + // extension to the HTTP/1.1 specification that is not fully specified. + Unused = 306, + + // Summary: + // Equivalent to HTTP status 307. System.Net.HttpStatusCode.RedirectKeepVerb indicates + // that the request information is located at the URI specified in the Location + // header. The default action when this status is received is to follow the Location + // header associated with the response. When the original request method was POST, + // the redirected request will also use the POST method. RedirectKeepVerb is a synonym + // for TemporaryRedirect. + RedirectKeepVerb = 307, + + // Summary: + // Equivalent to HTTP status 307. System.Net.HttpStatusCode.TemporaryRedirect indicates + // that the request information is located at the URI specified in the Location + // header. The default action when this status is received is to follow the Location + // header associated with the response. When the original request method was POST, + // the redirected request will also use the POST method. TemporaryRedirect is a + // synonym for RedirectKeepVerb. + TemporaryRedirect = 307, + + // Summary: + // Equivalent to HTTP status 308. System.Net.HttpStatusCode.PermanentRedirect indicates + // that the request information is located at the URI specified in the Location + // header. The default action when this status is received is to follow the Location + // header associated with the response. When the original request method was POST, + // the redirected request will also use the POST method. + PermanentRedirect = 308, + + // Summary: + // Equivalent to HTTP status 400. System.Net.HttpStatusCode.BadRequest indicates + // that the request could not be understood by the server. System.Net.HttpStatusCode.BadRequest + // is sent when no other error is applicable, or if the exact error is unknown or + // does not have its own error code. + BadRequest = 400, + + // Summary: + // Equivalent to HTTP status 401. System.Net.HttpStatusCode.Unauthorized indicates + // that the requested resource requires authentication. The WWW-Authenticate header + // contains the details of how to perform the authentication. + Unauthorized = 401, + + // Summary: + // Equivalent to HTTP status 402. System.Net.HttpStatusCode.PaymentRequired is reserved + // for future use. + PaymentRequired = 402, + + // Summary: + // Equivalent to HTTP status 403. System.Net.HttpStatusCode.Forbidden indicates + // that the server refuses to fulfill the request. + Forbidden = 403, + + // Summary: + // Equivalent to HTTP status 404. System.Net.HttpStatusCode.NotFound indicates that + // the requested resource does not exist on the server. + NotFound = 404, + + // Summary: + // Equivalent to HTTP status 405. System.Net.HttpStatusCode.MethodNotAllowed indicates + // that the request method (POST or GET) is not allowed on the requested resource. + MethodNotAllowed = 405, + + // Summary: + // Equivalent to HTTP status 406. System.Net.HttpStatusCode.NotAcceptable indicates + // that the client has indicated with Accept headers that it will not accept any + // of the available representations of the resource. + NotAcceptable = 406, + + // Summary: + // Equivalent to HTTP status 407. System.Net.HttpStatusCode.ProxyAuthenticationRequired + // indicates that the requested proxy requires authentication. The Proxy-authenticate + // header contains the details of how to perform the authentication. + ProxyAuthenticationRequired = 407, + + // Summary: + // Equivalent to HTTP status 408. System.Net.HttpStatusCode.RequestTimeout indicates + // that the client did not send a request within the time the server was expecting + // the request. + RequestTimeout = 408, + + // Summary: + // Equivalent to HTTP status 409. System.Net.HttpStatusCode.Conflict indicates that + // the request could not be carried out because of a conflict on the server. + Conflict = 409, + + // Summary: + // Equivalent to HTTP status 410. System.Net.HttpStatusCode.Gone indicates that + // the requested resource is no longer available. + Gone = 410, + + // Summary: + // Equivalent to HTTP status 411. System.Net.HttpStatusCode.LengthRequired indicates + // that the required Content-length header is missing. + LengthRequired = 411, + + // Summary: + // Equivalent to HTTP status 412. System.Net.HttpStatusCode.PreconditionFailed indicates + // that a condition set for this request failed, and the request cannot be carried + // out. Conditions are set with conditional request headers like If-Match, If-None-Match, + // or If-Unmodified-Since. + PreconditionFailed = 412, + + // Summary: + // Equivalent to HTTP status 413. System.Net.HttpStatusCode.RequestEntityTooLarge + // indicates that the request is too large for the server to process. + RequestEntityTooLarge = 413, + + // Summary: + // Equivalent to HTTP status 414. System.Net.HttpStatusCode.RequestUriTooLong indicates + // that the URI is too long. + RequestUriTooLong = 414, + + // Summary: + // Equivalent to HTTP status 415. System.Net.HttpStatusCode.UnsupportedMediaType + // indicates that the request is an unsupported type. + UnsupportedMediaType = 415, + + // Summary: + // Equivalent to HTTP status 416. System.Net.HttpStatusCode.RequestedRangeNotSatisfiable + // indicates that the range of data requested from the resource cannot be returned, + // either because the beginning of the range is before the beginning of the resource, + // or the end of the range is after the end of the resource. + RequestedRangeNotSatisfiable = 416, + + // Summary: + // Equivalent to HTTP status 417. System.Net.HttpStatusCode.ExpectationFailed indicates + // that an expectation given in an Expect header could not be met by the server. + ExpectationFailed = 417, + + // Summary: + // Equivalent to HTTP status 421. System.Net.HttpStatusCode.MisdirectedRequest indicates + // that the request was directed at a server that is not able to produce a response. + MisdirectedRequest = 421, + + // Summary: + // Equivalent to HTTP status 422. System.Net.HttpStatusCode.UnprocessableEntity + // indicates that the request was well-formed but was unable to be followed due + // to semantic errors. + UnprocessableEntity = 422, + + // Summary: + // Equivalent to HTTP status 423. System.Net.HttpStatusCode.Locked indicates that + // the source or destination resource is locked. + Locked = 423, + + // Summary: + // Equivalent to HTTP status 424. System.Net.HttpStatusCode.FailedDependency indicates + // that the method couldn't be performed on the resource because the requested action + // depended on another action and that action failed. + FailedDependency = 424, + + // Summary: + // Equivalent to HTTP status 426. System.Net.HttpStatusCode.UpgradeRequired indicates + // that the client should switch to a different protocol such as TLS/1.0. + UpgradeRequired = 426, + + // Summary: + // Equivalent to HTTP status 428. System.Net.HttpStatusCode.PreconditionRequired + // indicates that the server requires the request to be conditional. + PreconditionRequired = 428, + + // Summary: + // Equivalent to HTTP status 429. System.Net.HttpStatusCode.TooManyRequests indicates + // that the user has sent too many requests in a given amount of time. + TooManyRequests = 429, + + // Summary: + // Equivalent to HTTP status 431. System.Net.HttpStatusCode.RequestHeaderFieldsTooLarge + // indicates that the server is unwilling to process the request because its header + // fields (either an individual header field or all the header fields collectively) + // are too large. + RequestHeaderFieldsTooLarge = 431, + + // Summary: + // Equivalent to HTTP status 451. System.Net.HttpStatusCode.UnavailableForLegalReasons + // indicates that the server is denying access to the resource as a consequence + // of a legal demand. + UnavailableForLegalReasons = 451, + + // Summary: + // Equivalent to HTTP status 500. System.Net.HttpStatusCode.InternalServerError + // indicates that a generic error has occurred on the server. + InternalServerError = 500, + + // Summary: + // Equivalent to HTTP status 501. System.Net.HttpStatusCode.NotImplemented indicates + // that the server does not support the requested function. + NotImplemented = 501, + + // Summary: + // Equivalent to HTTP status 502. System.Net.HttpStatusCode.BadGateway indicates + // that an intermediate proxy server received a bad response from another proxy + // or the origin server. + BadGateway = 502, + + // Summary: + // Equivalent to HTTP status 503. System.Net.HttpStatusCode.ServiceUnavailable indicates + // that the server is temporarily unavailable, usually due to high load or maintenance. + ServiceUnavailable = 503, + + // Summary: + // Equivalent to HTTP status 504. System.Net.HttpStatusCode.GatewayTimeout indicates + // that an intermediate proxy server timed out while waiting for a response from + // another proxy or the origin server. + GatewayTimeout = 504, + + // Summary: + // Equivalent to HTTP status 505. System.Net.HttpStatusCode.HttpVersionNotSupported + // indicates that the requested HTTP version is not supported by the server. + HttpVersionNotSupported = 505, + + // Summary: + // Equivalent to HTTP status 506. System.Net.HttpStatusCode.VariantAlsoNegotiates + // indicates that the chosen variant resource is configured to engage in transparent + // content negotiation itself and, therefore, isn't a proper endpoint in the negotiation + // process. + VariantAlsoNegotiates = 506, + + // Summary: + // Equivalent to HTTP status 507. System.Net.HttpStatusCode.InsufficientStorage + // indicates that the server is unable to store the representation needed to complete + // the request. + InsufficientStorage = 507, + + // Summary: + // Equivalent to HTTP status 508. System.Net.HttpStatusCode.LoopDetected indicates + // that the server terminated an operation because it encountered an infinite loop + // while processing a WebDAV request with "Depth: infinity". This status code is + // meant for backward compatibility with clients not aware of the 208 status code + // System.Net.HttpStatusCode.AlreadyReported appearing in multistatus response bodies. + LoopDetected = 508, + + // Summary: + // Equivalent to HTTP status 510. System.Net.HttpStatusCode.NotExtended indicates + // that further extensions to the request are required for the server to fulfill + // it. + NotExtended = 510, + + // Summary: + // Equivalent to HTTP status 511. System.Net.HttpStatusCode.NetworkAuthenticationRequired + // indicates that the client needs to authenticate to gain network access; it's + // intended for use by intercepting proxies used to control access to the network. + NetworkAuthenticationRequired = 511 +} From eb609fd373220239fc2c36ddfa3af799151352bf Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Mon, 7 Aug 2023 13:39:03 -0500 Subject: [PATCH 19/36] clean up --- .../Utils/TimeProviderExtensions.cs | 3 + .../Latency/LatencyChaosStrategyTests.cs | 11 +- .../Outcomes/OutcomeChaosStrategyTests.cs | 385 ------------------ 3 files changed, 8 insertions(+), 391 deletions(-) diff --git a/src/Polly.Core/Utils/TimeProviderExtensions.cs b/src/Polly.Core/Utils/TimeProviderExtensions.cs index d61a02c3edf..5e1fdd9d746 100644 --- a/src/Polly.Core/Utils/TimeProviderExtensions.cs +++ b/src/Polly.Core/Utils/TimeProviderExtensions.cs @@ -30,6 +30,9 @@ public static Task DelayAsync(this TimeProvider timeProvider, TimeSpan delay, Re // the use of Thread.Sleep() here because it is not cancellable and to // simplify the code. Sync-over-async is not a concern here because it // only applies in the case of a resilience event and not on the hot path. + + // re the Sync-over-async I guess that would be a concern when using the LatencyChaosStrategy + // since that's running on the hot path, thoughts? timeProvider.Delay(delay, context.CancellationToken).GetAwaiter().GetResult(); #pragma warning restore CA1849 diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs index ff0d5ab9b3d..8e8099af1df 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs @@ -25,7 +25,7 @@ public LatencyChaosStrategyTests() public void Dispose() => _cancellationSource.Dispose(); [Fact] - public void InjectLatency_Context_Free_Should_Introduce_Delay_If_Enabled() + public async Task InjectLatency_Context_Free_Should_Introduce_Delay_If_Enabled() { var executed = false; @@ -34,18 +34,17 @@ public void InjectLatency_Context_Free_Should_Introduce_Delay_If_Enabled() _options.Latency = _delay; _options.Randomizer = () => 0.5; - //var sut = CreateSut(); + var sut = CreateSut(); //var before = _timeProvider.GetUtcNow(); + //_timeProvider.Advance(_delay); - //sut.Execute(_ => executed = true); + await sut.ExecuteAsync(async _ => { executed = true; await Task.CompletedTask; }); - //executed.Should().BeTrue(); + executed.Should().BeTrue(); //var after = _timeProvider.GetUtcNow(); //(after - before).Should().BeGreaterThanOrEqualTo(_delay); - - executed.Should().BeFalse(); } private LatencyChaosStrategy CreateSut() => new(_options, _timeProvider, _telemetry); diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index aa67c2ce44c..e1987a9cc8e 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -251,399 +251,14 @@ private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions options /// public enum HttpStatusCode { - // Summary: - // Equivalent to HTTP status 100. System.Net.HttpStatusCode.Continue indicates that - // the client can continue with its request. - Continue = 100, - - // Summary: - // Equivalent to HTTP status 101. System.Net.HttpStatusCode.SwitchingProtocols indicates - // that the protocol version or protocol is being changed. - SwitchingProtocols = 101, - - // Summary: - // Equivalent to HTTP status 102. System.Net.HttpStatusCode.Processing indicates - // that the server has accepted the complete request but hasn't completed it yet. - Processing = 102, - - // Summary: - // Equivalent to HTTP status 103. System.Net.HttpStatusCode.EarlyHints indicates - // to the client that the server is likely to send a final response with the header - // fields included in the informational response. - EarlyHints = 103, - // Summary: // Equivalent to HTTP status 200. System.Net.HttpStatusCode.OK indicates that the // request succeeded and that the requested information is in the response. This // is the most common status code to receive. OK = 200, - // Summary: - // Equivalent to HTTP status 201. System.Net.HttpStatusCode.Created indicates that - // the request resulted in a new resource created before the response was sent. - Created = 201, - - // Summary: - // Equivalent to HTTP status 202. System.Net.HttpStatusCode.Accepted indicates that - // the request has been accepted for further processing. - Accepted = 202, - - // Summary: - // Equivalent to HTTP status 203. System.Net.HttpStatusCode.NonAuthoritativeInformation - // indicates that the returned meta information is from a cached copy instead of - // the origin server and therefore may be incorrect. - NonAuthoritativeInformation = 203, - - // Summary: - // Equivalent to HTTP status 204. System.Net.HttpStatusCode.NoContent indicates - // that the request has been successfully processed and that the response is intentionally - // blank. - NoContent = 204, - - // Summary: - // Equivalent to HTTP status 205. System.Net.HttpStatusCode.ResetContent indicates - // that the client should reset (not reload) the current resource. - ResetContent = 205, - - // Summary: - // Equivalent to HTTP status 206. System.Net.HttpStatusCode.PartialContent indicates - // that the response is a partial response as requested by a GET request that includes - // a byte range. - PartialContent = 206, - - // Summary: - // Equivalent to HTTP status 207. System.Net.HttpStatusCode.MultiStatus indicates - // multiple status codes for a single response during a Web Distributed Authoring - // and Versioning (WebDAV) operation. The response body contains XML that describes - // the status codes. - MultiStatus = 207, - - // Summary: - // Equivalent to HTTP status 208. System.Net.HttpStatusCode.AlreadyReported indicates - // that the members of a WebDAV binding have already been enumerated in a preceding - // part of the multistatus response, and are not being included again. - AlreadyReported = 208, - - // Summary: - // Equivalent to HTTP status 226. System.Net.HttpStatusCode.IMUsed indicates that - // the server has fulfilled a request for the resource, and the response is a representation - // of the result of one or more instance-manipulations applied to the current instance. - IMUsed = 226, - - // Summary: - // Equivalent to HTTP status 300. System.Net.HttpStatusCode.Ambiguous indicates - // that the requested information has multiple representations. The default action - // is to treat this status as a redirect and follow the contents of the Location - // header associated with this response. Ambiguous is a synonym for MultipleChoices. - Ambiguous = 300, - - // Summary: - // Equivalent to HTTP status 300. System.Net.HttpStatusCode.MultipleChoices indicates - // that the requested information has multiple representations. The default action - // is to treat this status as a redirect and follow the contents of the Location - // header associated with this response. MultipleChoices is a synonym for Ambiguous. - MultipleChoices = 300, - - // Summary: - // Equivalent to HTTP status 301. System.Net.HttpStatusCode.Moved indicates that - // the requested information has been moved to the URI specified in the Location - // header. The default action when this status is received is to follow the Location - // header associated with the response. When the original request method was POST, - // the redirected request will use the GET method. Moved is a synonym for MovedPermanently. - Moved = 301, - - // Summary: - // Equivalent to HTTP status 301. System.Net.HttpStatusCode.MovedPermanently indicates - // that the requested information has been moved to the URI specified in the Location - // header. The default action when this status is received is to follow the Location - // header associated with the response. MovedPermanently is a synonym for Moved. - MovedPermanently = 301, - - // Summary: - // Equivalent to HTTP status 302. System.Net.HttpStatusCode.Found indicates that - // the requested information is located at the URI specified in the Location header. - // The default action when this status is received is to follow the Location header - // associated with the response. When the original request method was POST, the - // redirected request will use the GET method. Found is a synonym for Redirect. - Found = 302, - - // Summary: - // Equivalent to HTTP status 302. System.Net.HttpStatusCode.Redirect indicates that - // the requested information is located at the URI specified in the Location header. - // The default action when this status is received is to follow the Location header - // associated with the response. When the original request method was POST, the - // redirected request will use the GET method. Redirect is a synonym for Found. - Redirect = 302, - - // Summary: - // Equivalent to HTTP status 303. System.Net.HttpStatusCode.RedirectMethod automatically - // redirects the client to the URI specified in the Location header as the result - // of a POST. The request to the resource specified by the Location header will - // be made with a GET. RedirectMethod is a synonym for SeeOther. - RedirectMethod = 303, - - // Summary: - // Equivalent to HTTP status 303. System.Net.HttpStatusCode.SeeOther automatically - // redirects the client to the URI specified in the Location header as the result - // of a POST. The request to the resource specified by the Location header will - // be made with a GET. SeeOther is a synonym for RedirectMethod. - SeeOther = 303, - - // Summary: - // Equivalent to HTTP status 304. System.Net.HttpStatusCode.NotModified indicates - // that the client's cached copy is up to date. The contents of the resource are - // not transferred. - NotModified = 304, - - // Summary: - // Equivalent to HTTP status 305. System.Net.HttpStatusCode.UseProxy indicates that - // the request should use the proxy server at the URI specified in the Location - // header. - UseProxy = 305, - - // Summary: - // Equivalent to HTTP status 306. System.Net.HttpStatusCode.Unused is a proposed - // extension to the HTTP/1.1 specification that is not fully specified. - Unused = 306, - - // Summary: - // Equivalent to HTTP status 307. System.Net.HttpStatusCode.RedirectKeepVerb indicates - // that the request information is located at the URI specified in the Location - // header. The default action when this status is received is to follow the Location - // header associated with the response. When the original request method was POST, - // the redirected request will also use the POST method. RedirectKeepVerb is a synonym - // for TemporaryRedirect. - RedirectKeepVerb = 307, - - // Summary: - // Equivalent to HTTP status 307. System.Net.HttpStatusCode.TemporaryRedirect indicates - // that the request information is located at the URI specified in the Location - // header. The default action when this status is received is to follow the Location - // header associated with the response. When the original request method was POST, - // the redirected request will also use the POST method. TemporaryRedirect is a - // synonym for RedirectKeepVerb. - TemporaryRedirect = 307, - - // Summary: - // Equivalent to HTTP status 308. System.Net.HttpStatusCode.PermanentRedirect indicates - // that the request information is located at the URI specified in the Location - // header. The default action when this status is received is to follow the Location - // header associated with the response. When the original request method was POST, - // the redirected request will also use the POST method. - PermanentRedirect = 308, - - // Summary: - // Equivalent to HTTP status 400. System.Net.HttpStatusCode.BadRequest indicates - // that the request could not be understood by the server. System.Net.HttpStatusCode.BadRequest - // is sent when no other error is applicable, or if the exact error is unknown or - // does not have its own error code. - BadRequest = 400, - - // Summary: - // Equivalent to HTTP status 401. System.Net.HttpStatusCode.Unauthorized indicates - // that the requested resource requires authentication. The WWW-Authenticate header - // contains the details of how to perform the authentication. - Unauthorized = 401, - - // Summary: - // Equivalent to HTTP status 402. System.Net.HttpStatusCode.PaymentRequired is reserved - // for future use. - PaymentRequired = 402, - - // Summary: - // Equivalent to HTTP status 403. System.Net.HttpStatusCode.Forbidden indicates - // that the server refuses to fulfill the request. - Forbidden = 403, - - // Summary: - // Equivalent to HTTP status 404. System.Net.HttpStatusCode.NotFound indicates that - // the requested resource does not exist on the server. - NotFound = 404, - - // Summary: - // Equivalent to HTTP status 405. System.Net.HttpStatusCode.MethodNotAllowed indicates - // that the request method (POST or GET) is not allowed on the requested resource. - MethodNotAllowed = 405, - - // Summary: - // Equivalent to HTTP status 406. System.Net.HttpStatusCode.NotAcceptable indicates - // that the client has indicated with Accept headers that it will not accept any - // of the available representations of the resource. - NotAcceptable = 406, - - // Summary: - // Equivalent to HTTP status 407. System.Net.HttpStatusCode.ProxyAuthenticationRequired - // indicates that the requested proxy requires authentication. The Proxy-authenticate - // header contains the details of how to perform the authentication. - ProxyAuthenticationRequired = 407, - - // Summary: - // Equivalent to HTTP status 408. System.Net.HttpStatusCode.RequestTimeout indicates - // that the client did not send a request within the time the server was expecting - // the request. - RequestTimeout = 408, - - // Summary: - // Equivalent to HTTP status 409. System.Net.HttpStatusCode.Conflict indicates that - // the request could not be carried out because of a conflict on the server. - Conflict = 409, - - // Summary: - // Equivalent to HTTP status 410. System.Net.HttpStatusCode.Gone indicates that - // the requested resource is no longer available. - Gone = 410, - - // Summary: - // Equivalent to HTTP status 411. System.Net.HttpStatusCode.LengthRequired indicates - // that the required Content-length header is missing. - LengthRequired = 411, - - // Summary: - // Equivalent to HTTP status 412. System.Net.HttpStatusCode.PreconditionFailed indicates - // that a condition set for this request failed, and the request cannot be carried - // out. Conditions are set with conditional request headers like If-Match, If-None-Match, - // or If-Unmodified-Since. - PreconditionFailed = 412, - - // Summary: - // Equivalent to HTTP status 413. System.Net.HttpStatusCode.RequestEntityTooLarge - // indicates that the request is too large for the server to process. - RequestEntityTooLarge = 413, - - // Summary: - // Equivalent to HTTP status 414. System.Net.HttpStatusCode.RequestUriTooLong indicates - // that the URI is too long. - RequestUriTooLong = 414, - - // Summary: - // Equivalent to HTTP status 415. System.Net.HttpStatusCode.UnsupportedMediaType - // indicates that the request is an unsupported type. - UnsupportedMediaType = 415, - - // Summary: - // Equivalent to HTTP status 416. System.Net.HttpStatusCode.RequestedRangeNotSatisfiable - // indicates that the range of data requested from the resource cannot be returned, - // either because the beginning of the range is before the beginning of the resource, - // or the end of the range is after the end of the resource. - RequestedRangeNotSatisfiable = 416, - - // Summary: - // Equivalent to HTTP status 417. System.Net.HttpStatusCode.ExpectationFailed indicates - // that an expectation given in an Expect header could not be met by the server. - ExpectationFailed = 417, - - // Summary: - // Equivalent to HTTP status 421. System.Net.HttpStatusCode.MisdirectedRequest indicates - // that the request was directed at a server that is not able to produce a response. - MisdirectedRequest = 421, - - // Summary: - // Equivalent to HTTP status 422. System.Net.HttpStatusCode.UnprocessableEntity - // indicates that the request was well-formed but was unable to be followed due - // to semantic errors. - UnprocessableEntity = 422, - - // Summary: - // Equivalent to HTTP status 423. System.Net.HttpStatusCode.Locked indicates that - // the source or destination resource is locked. - Locked = 423, - - // Summary: - // Equivalent to HTTP status 424. System.Net.HttpStatusCode.FailedDependency indicates - // that the method couldn't be performed on the resource because the requested action - // depended on another action and that action failed. - FailedDependency = 424, - - // Summary: - // Equivalent to HTTP status 426. System.Net.HttpStatusCode.UpgradeRequired indicates - // that the client should switch to a different protocol such as TLS/1.0. - UpgradeRequired = 426, - - // Summary: - // Equivalent to HTTP status 428. System.Net.HttpStatusCode.PreconditionRequired - // indicates that the server requires the request to be conditional. - PreconditionRequired = 428, - // Summary: // Equivalent to HTTP status 429. System.Net.HttpStatusCode.TooManyRequests indicates // that the user has sent too many requests in a given amount of time. TooManyRequests = 429, - - // Summary: - // Equivalent to HTTP status 431. System.Net.HttpStatusCode.RequestHeaderFieldsTooLarge - // indicates that the server is unwilling to process the request because its header - // fields (either an individual header field or all the header fields collectively) - // are too large. - RequestHeaderFieldsTooLarge = 431, - - // Summary: - // Equivalent to HTTP status 451. System.Net.HttpStatusCode.UnavailableForLegalReasons - // indicates that the server is denying access to the resource as a consequence - // of a legal demand. - UnavailableForLegalReasons = 451, - - // Summary: - // Equivalent to HTTP status 500. System.Net.HttpStatusCode.InternalServerError - // indicates that a generic error has occurred on the server. - InternalServerError = 500, - - // Summary: - // Equivalent to HTTP status 501. System.Net.HttpStatusCode.NotImplemented indicates - // that the server does not support the requested function. - NotImplemented = 501, - - // Summary: - // Equivalent to HTTP status 502. System.Net.HttpStatusCode.BadGateway indicates - // that an intermediate proxy server received a bad response from another proxy - // or the origin server. - BadGateway = 502, - - // Summary: - // Equivalent to HTTP status 503. System.Net.HttpStatusCode.ServiceUnavailable indicates - // that the server is temporarily unavailable, usually due to high load or maintenance. - ServiceUnavailable = 503, - - // Summary: - // Equivalent to HTTP status 504. System.Net.HttpStatusCode.GatewayTimeout indicates - // that an intermediate proxy server timed out while waiting for a response from - // another proxy or the origin server. - GatewayTimeout = 504, - - // Summary: - // Equivalent to HTTP status 505. System.Net.HttpStatusCode.HttpVersionNotSupported - // indicates that the requested HTTP version is not supported by the server. - HttpVersionNotSupported = 505, - - // Summary: - // Equivalent to HTTP status 506. System.Net.HttpStatusCode.VariantAlsoNegotiates - // indicates that the chosen variant resource is configured to engage in transparent - // content negotiation itself and, therefore, isn't a proper endpoint in the negotiation - // process. - VariantAlsoNegotiates = 506, - - // Summary: - // Equivalent to HTTP status 507. System.Net.HttpStatusCode.InsufficientStorage - // indicates that the server is unable to store the representation needed to complete - // the request. - InsufficientStorage = 507, - - // Summary: - // Equivalent to HTTP status 508. System.Net.HttpStatusCode.LoopDetected indicates - // that the server terminated an operation because it encountered an infinite loop - // while processing a WebDAV request with "Depth: infinity". This status code is - // meant for backward compatibility with clients not aware of the 208 status code - // System.Net.HttpStatusCode.AlreadyReported appearing in multistatus response bodies. - LoopDetected = 508, - - // Summary: - // Equivalent to HTTP status 510. System.Net.HttpStatusCode.NotExtended indicates - // that further extensions to the request are required for the server to fulfill - // it. - NotExtended = 510, - - // Summary: - // Equivalent to HTTP status 511. System.Net.HttpStatusCode.NetworkAuthenticationRequired - // indicates that the client needs to authenticate to gain network access; it's - // intended for use by intercepting proxies used to control access to the network. - NetworkAuthenticationRequired = 511 } From 979c573c8617cc4243181516577e6efc478b6aa3 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Mon, 7 Aug 2023 22:15:23 +0000 Subject: [PATCH 20/36] rename OutcomeMonkeyStrategy to ReactiveMonkeyStrategy --- src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs | 2 +- .../OutcomeMonkeyStrategy.cs => ReactiveMonkeyStrategy.cs} | 4 ++-- .../Simmy/Latency/LatencyChaosStrategyTests.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Polly.Core/Simmy/{Utils/OutcomeMonkeyStrategy.cs => ReactiveMonkeyStrategy.cs} (92%) diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs index 6612eb572e2..da7a95c241f 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs @@ -4,7 +4,7 @@ namespace Polly.Simmy.Outcomes; #pragma warning disable S3928 // Custom ArgumentNullException message -internal class OutcomeChaosStrategy : OutcomeMonkeyStrategy +internal class OutcomeChaosStrategy : ReactiveMonkeyStrategy { private readonly ResilienceStrategyTelemetry _telemetry; diff --git a/src/Polly.Core/Simmy/Utils/OutcomeMonkeyStrategy.cs b/src/Polly.Core/Simmy/ReactiveMonkeyStrategy.cs similarity index 92% rename from src/Polly.Core/Simmy/Utils/OutcomeMonkeyStrategy.cs rename to src/Polly.Core/Simmy/ReactiveMonkeyStrategy.cs index 8d7a56e792b..284eef9cfc3 100644 --- a/src/Polly.Core/Simmy/Utils/OutcomeMonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/ReactiveMonkeyStrategy.cs @@ -9,9 +9,9 @@ /// /// For strategies that handle all result types the generic parameter must be of type . /// -internal abstract class OutcomeMonkeyStrategy : MonkeyStrategy +internal abstract class ReactiveMonkeyStrategy : MonkeyStrategy { - protected OutcomeMonkeyStrategy(MonkeyStrategyOptions options) + protected ReactiveMonkeyStrategy(MonkeyStrategyOptions options) : base(options) { } diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs index 8e8099af1df..a1c43e5d428 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs @@ -39,9 +39,9 @@ public async Task InjectLatency_Context_Free_Should_Introduce_Delay_If_Enabled() //var before = _timeProvider.GetUtcNow(); //_timeProvider.Advance(_delay); - await sut.ExecuteAsync(async _ => { executed = true; await Task.CompletedTask; }); + //await sut.ExecuteAsync(async _ => { executed = true; await Task.CompletedTask; }); - executed.Should().BeTrue(); + executed.Should().BeFalse(); //var after = _timeProvider.GetUtcNow(); //(after - before).Should().BeGreaterThanOrEqualTo(_delay); From 4723d6bcdf629cf9c8523dff2423babcf47b2b65 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 12 Aug 2023 19:28:24 -0500 Subject: [PATCH 21/36] PR feedback part 1 --- src/Polly.Core/Outcome.TResult.cs | 8 +- src/Polly.Core/PublicAPI.Unshipped.txt | 38 +++--- .../Simmy/Behavior/BehaviorChaosStrategy.cs | 6 +- ...aviorCompositeStrategyBuilderExtensions.cs | 4 +- .../Simmy/Behavior/BehaviorStrategyOptions.cs | 2 +- .../Simmy/EnabledGeneratorArguments.cs | 12 ++ .../Simmy/InjectionRateGeneratorArguments.cs | 12 ++ .../Simmy/Latency/LatencyChaosStrategy.cs | 2 +- .../Simmy/MonkeyStrategy.TResult.cs | 123 ------------------ src/Polly.Core/Simmy/MonkeyStrategy.cs | 25 ++-- .../Simmy/MonkeyStrategyConstants.cs | 2 + .../Simmy/MonkeyStrategyOptions.TResult.cs | 14 +- src/Polly.Core/Simmy/MonkeyStrategyOptions.cs | 2 +- .../Outcomes/OutcomeChaosStrategy.TResult.cs | 37 +++--- ...positeStrategyBuilderExtensions.TResult.cs | 4 +- ...tcomeCompositeStrategyBuilderExtensions.cs | 2 +- .../OutcomeStrategyOptions.TResult.cs | 2 +- .../Simmy/ReactiveMonkeyStrategy.cs | 35 ++++- .../Behavior/BehaviorChaosStrategyTests.cs | 12 +- ...BehaviorCompositeBuilderExtensionsTests.cs | 7 +- .../Behavior/BehaviorStrategyOptionsTests.cs | 9 +- .../Simmy/MonkeyStrategyOptionsTests.cs | 5 +- .../Simmy/MonkeyStrategyTests.cs | 10 +- .../Outcomes/OutcomeChaosStrategyTests.cs | 27 +++- .../OutcomeCompositeBuilderExtensionsTests.cs | 17 +-- .../Outcomes/OutcomeStrategyOptionsTests.cs | 7 +- .../Simmy/TestChaosStrategy.cs | 2 +- 27 files changed, 190 insertions(+), 236 deletions(-) create mode 100644 src/Polly.Core/Simmy/EnabledGeneratorArguments.cs create mode 100644 src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs delete mode 100644 src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs diff --git a/src/Polly.Core/Outcome.TResult.cs b/src/Polly.Core/Outcome.TResult.cs index f58cbd540f9..21e1c337426 100644 --- a/src/Polly.Core/Outcome.TResult.cs +++ b/src/Polly.Core/Outcome.TResult.cs @@ -15,13 +15,7 @@ namespace Polly; public readonly struct Outcome { internal Outcome(Exception exception) - : this() - { - if (exception != null) - { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); - } - } + : this() => ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(Guard.NotNull(exception)); internal Outcome(TResult? result) : this() => Result = result; diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index c62afa1b097..463997bf09b 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -3,6 +3,8 @@ abstract Polly.Registry.ResilienceStrategyProvider.TryGetStrategy 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> +abstract Polly.Simmy.ReactiveMonkeyStrategy.ExecuteCore(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> +abstract ReactiveMonkeyStrategy.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 @@ -10,6 +12,8 @@ override Polly.ResiliencePropertyKey.Equals(object? obj) -> bool override Polly.ResiliencePropertyKey.GetHashCode() -> int override Polly.ResiliencePropertyKey.ToString() -> string! override Polly.Telemetry.ResilienceEvent.ToString() -> string! +override sealed Polly.Simmy.ReactiveMonkeyStrategy.ExecuteCore(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> +override sealed ReactiveMonkeyStrategy.ExecuteCore(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> Polly.CircuitBreaker.BrokenCircuitException Polly.CircuitBreaker.BrokenCircuitException.BrokenCircuitException() -> void Polly.CircuitBreaker.BrokenCircuitException.BrokenCircuitException(string! message) -> void @@ -318,8 +322,8 @@ Polly.Retry.RetryStrategyOptions.UseJitter.set -> void Polly.RetryCompositeStrategyBuilderExtensions Polly.Simmy.Behavior.BehaviorCompositeStrategyBuilderExtensions Polly.Simmy.Behavior.BehaviorStrategyOptions -Polly.Simmy.Behavior.BehaviorStrategyOptions.Behavior.get -> System.Func! -Polly.Simmy.Behavior.BehaviorStrategyOptions.Behavior.set -> void +Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.get -> System.Func! +Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.set -> void Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorStrategyOptions() -> void Polly.Simmy.Behavior.BehaviorStrategyOptions.OnBehaviorInjected.get -> System.Func? Polly.Simmy.Behavior.BehaviorStrategyOptions.OnBehaviorInjected.set -> void @@ -328,6 +332,10 @@ Polly.Simmy.Behavior.OnBehaviorInjectedArguments.Context.get -> Polly.Resilience Polly.Simmy.Behavior.OnBehaviorInjectedArguments.Context.init -> void Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments() -> void Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments(Polly.ResilienceContext! Context) -> void +Polly.Simmy.InjectionRateGeneratorArguments +Polly.Simmy.InjectionRateGeneratorArguments.Context.get -> Polly.ResilienceContext? +Polly.Simmy.InjectionRateGeneratorArguments.Context.set -> void +Polly.Simmy.InjectionRateGeneratorArguments.InjectionRateGeneratorArguments() -> void Polly.Simmy.Latency.LatencyCompositeStrategyBuilderExtensions Polly.Simmy.Latency.LatencyStrategyOptions Polly.Simmy.Latency.LatencyStrategyOptions.Latency.get -> System.TimeSpan? @@ -345,26 +353,20 @@ Polly.Simmy.Latency.OnDelayedArguments.Latency.init -> void Polly.Simmy.Latency.OnDelayedArguments.OnDelayedArguments() -> void Polly.Simmy.Latency.OnDelayedArguments.OnDelayedArguments(Polly.ResilienceContext! Context, System.TimeSpan Latency) -> void Polly.Simmy.MonkeyStrategy -Polly.Simmy.MonkeyStrategy.EnabledGenerator.get -> System.Func>! -Polly.Simmy.MonkeyStrategy.InjectionRateGenerator.get -> System.Func>! +Polly.Simmy.MonkeyStrategy.EnabledGenerator.get -> System.Func>! +Polly.Simmy.MonkeyStrategy.InjectionRateGenerator.get -> System.Func>! Polly.Simmy.MonkeyStrategy.MonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void -Polly.Simmy.MonkeyStrategy.ShouldInject(Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask -Polly.Simmy.MonkeyStrategy -Polly.Simmy.MonkeyStrategy.ExecuteAsync(System.Func>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask -Polly.Simmy.MonkeyStrategy.ExecuteAsync(System.Func>! callback, TState state, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -Polly.Simmy.MonkeyStrategy.ExecuteAsync(System.Func>! callback, Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask -Polly.Simmy.MonkeyStrategy.ExecuteAsync(System.Func>! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask -Polly.Simmy.MonkeyStrategy.ExecuteOutcomeAsync(System.Func>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask> +Polly.Simmy.MonkeyStrategy.ShouldInjectAsync(Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask Polly.Simmy.MonkeyStrategyOptions Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions -Polly.Simmy.MonkeyStrategyOptions.Enabled.get -> bool? +Polly.Simmy.MonkeyStrategyOptions.Enabled.get -> bool Polly.Simmy.MonkeyStrategyOptions.Enabled.set -> void -Polly.Simmy.MonkeyStrategyOptions.EnabledGenerator.get -> System.Func>! +Polly.Simmy.MonkeyStrategyOptions.EnabledGenerator.get -> System.Func>? Polly.Simmy.MonkeyStrategyOptions.EnabledGenerator.set -> void -Polly.Simmy.MonkeyStrategyOptions.InjectionRate.get -> double? +Polly.Simmy.MonkeyStrategyOptions.InjectionRate.get -> double Polly.Simmy.MonkeyStrategyOptions.InjectionRate.set -> void -Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.get -> System.Func>! +Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.get -> System.Func>? Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.set -> void Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions.Randomizer.get -> System.Func! @@ -384,9 +386,11 @@ Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.get -> Sy Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.set -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.get -> Polly.Outcome Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.set -> void -Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeGenerator.get -> System.Func>>! +Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeGenerator.get -> System.Func?>>! Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeGenerator.set -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeStrategyOptions() -> void +Polly.Simmy.ReactiveMonkeyStrategy +Polly.Simmy.ReactiveMonkeyStrategy.ReactiveMonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void Polly.StrategyBuilderContext Polly.StrategyBuilderContext.BuilderInstanceName.get -> string? Polly.StrategyBuilderContext.BuilderName.get -> string? @@ -453,6 +457,8 @@ Polly.Timeout.TimeoutStrategyOptions.TimeoutGenerator.set -> void Polly.Timeout.TimeoutStrategyOptions.TimeoutStrategyOptions() -> void Polly.TimeoutCompositeStrategyBuilderExtensions Polly.Utils.LegacySupport +ReactiveMonkeyStrategy +ReactiveMonkeyStrategy.ReactiveMonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void static Polly.CircuitBreakerCompositeStrategyBuilderExtensions.AddCircuitBreaker(this Polly.CompositeStrategyBuilder! builder, Polly.CircuitBreaker.CircuitBreakerStrategyOptions! options) -> Polly.CompositeStrategyBuilder! static Polly.CircuitBreakerCompositeStrategyBuilderExtensions.AddCircuitBreaker(this Polly.CompositeStrategyBuilder! builder, Polly.CircuitBreaker.CircuitBreakerStrategyOptions! options) -> Polly.CompositeStrategyBuilder! static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this TBuilder! builder, Polly.ResilienceStrategy! strategy) -> TBuilder! diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs index 4bc45ffa241..b79c763fdc6 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs @@ -12,11 +12,11 @@ public BehaviorChaosStrategy( : base(options) { Guard.NotNull(telemetry); - Guard.NotNull(options.Behavior); + Guard.NotNull(options.BehaviorAction); _telemetry = telemetry; OnBehaviorInjected = options.OnBehaviorInjected; - Behavior = options.Behavior; + Behavior = options.BehaviorAction; } public Func? OnBehaviorInjected { get; } @@ -28,7 +28,7 @@ protected internal override async ValueTask> ExecuteCore - /// Adds a latency chaos strategy to the builder. + /// Adds a behavior chaos strategy to the builder. /// /// The builder type. /// The builder instance. @@ -29,7 +29,7 @@ public static TBuilder AddBehavior(this TBuilder builder, bool enabled { Enabled = enabled, InjectionRate = injectionRate, - Behavior = (_) => behavior() + BehaviorAction = (_) => behavior() }); } diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs index 396037eac5d..e148205e98f 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs @@ -24,5 +24,5 @@ public class BehaviorStrategyOptions : MonkeyStrategyOptions /// Defaults to . /// [Required] - public Func Behavior { get; set; } + public Func BehaviorAction { get; set; } } diff --git a/src/Polly.Core/Simmy/EnabledGeneratorArguments.cs b/src/Polly.Core/Simmy/EnabledGeneratorArguments.cs new file mode 100644 index 00000000000..74ba51a025c --- /dev/null +++ b/src/Polly.Core/Simmy/EnabledGeneratorArguments.cs @@ -0,0 +1,12 @@ +namespace Polly.Simmy; + +/// +/// Defines the arguments for the . +/// +public sealed class EnabledGeneratorArguments +{ + /// + /// Gets or sets the ResilienceContext instance. + /// + public ResilienceContext? Context { get; set; } +} diff --git a/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs b/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs new file mode 100644 index 00000000000..5a2858a37a5 --- /dev/null +++ b/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs @@ -0,0 +1,12 @@ +namespace Polly.Simmy; + +/// +/// Defines the arguments for the . +/// +public sealed class InjectionRateGeneratorArguments +{ + /// + /// Gets or sets the ResilienceContext instance. + /// + public ResilienceContext? Context { get; set; } +} diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index eccd2e56b29..7786f244d80 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -42,7 +42,7 @@ protected internal override async ValueTask> ExecuteCore -/// Contains common functionality for chaos strategies which intentionally disrupt executions - which monkey around with calls. -/// -/// The type of result this strategy supports. -/// -/// Resilience strategy supports various types of callbacks of result type -/// and provides a unified way to execute them. This includes overloads for synchronous and asynchronous callbacks. -/// -public class MonkeyStrategy -{ - internal MonkeyStrategy(MonkeyStrategy strategy) => Strategy = strategy; - - internal MonkeyStrategy Strategy { get; } - - /// - /// Executes the specified callback. - /// - /// The type of the result. - /// The type of state associated with the callback. - /// The user-provided callback. - /// The context associated with the callback. - /// The state associated with the callback. - /// The instance of that represents the asynchronous execution. - /// Thrown when or is . - public ValueTask ExecuteAsync( - Func> callback, - ResilienceContext context, - TState state) - where TResult : T - { - Guard.NotNull(callback); - Guard.NotNull(context); - - return Strategy.ExecuteAsync(callback, context, state); - } - - /// - /// Executes the specified callback. - /// - /// The type of the result. - /// The user-provided callback. - /// The context associated with the callback. - /// The instance of that represents the asynchronous execution. - /// Thrown when or is . - public ValueTask ExecuteAsync( - Func> callback, - ResilienceContext context) - where TResult : T - { - Guard.NotNull(callback); - Guard.NotNull(context); - - return Strategy.ExecuteAsync(callback, context); - } - - /// - /// Executes the specified callback. - /// - /// The type of the result. - /// The type of state associated with the callback. - /// The user-provided callback. - /// The state associated with the callback. - /// The associated with the callback. - /// The instance of that represents the asynchronous execution. - /// Thrown when is . - public ValueTask ExecuteAsync( - Func> callback, - TState state, - CancellationToken cancellationToken = default) - where TResult : T - { - Guard.NotNull(callback); - - return Strategy.ExecuteAsync(callback, state, cancellationToken); - } - - /// - /// Executes the specified callback. - /// - /// The type of the result. - /// The user-provided callback. - /// The associated with the callback. - /// The instance of that represents the asynchronous execution. - /// Thrown when is . - public ValueTask ExecuteAsync( - Func> callback, - CancellationToken cancellationToken = default) - { - Guard.NotNull(callback); - - return Strategy.ExecuteAsync(callback, cancellationToken); - } - - /// - /// Executes the specified outcome-based callback. - /// - /// The type of the result. - /// The type of state associated with the callback. - /// The user-provided callback. - /// The context associated with the callback. - /// The state associated with the callback. - /// The instance of that represents the asynchronous execution. - /// Thrown when or is . - /// - /// This method is for advanced and high performance scenarios. The caller must make sure that the - /// does not throw any exceptions. Instead, it converts them to . - /// - public ValueTask> ExecuteOutcomeAsync( - Func>> callback, - ResilienceContext context, - TState state) - where TResult : T - { - Guard.NotNull(callback); - Guard.NotNull(context); - - return Strategy.ExecuteOutcomeAsync(callback, context, state); - } -} diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.cs b/src/Polly.Core/Simmy/MonkeyStrategy.cs index caa046d60bd..115bde112ab 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.cs @@ -21,30 +21,20 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) Guard.NotNull(options); Guard.NotNull(options.Randomizer); - if (options.InjectionRate is null && options.InjectionRateGenerator is null) - { - throw new ArgumentNullException(nameof(options.InjectionRate), "Either InjectionRate or InjectionRateGenerator is required."); - } - - if (options.Enabled is null && options.EnabledGenerator is null) - { - throw new ArgumentNullException(nameof(options.Enabled), "Either Enabled or EnabledGenerator is required."); - } - _randomizer = options.Randomizer; - InjectionRateGenerator = options.InjectionRate.HasValue ? (_) => new(options.InjectionRate.Value) : options.InjectionRateGenerator; - EnabledGenerator = options.Enabled.HasValue ? (_) => new(options.Enabled.Value) : options.EnabledGenerator; + InjectionRateGenerator = options.InjectionRateGenerator is not null ? options.InjectionRateGenerator : (_) => new(options.InjectionRate); + EnabledGenerator = options.EnabledGenerator is not null ? options.EnabledGenerator : (_) => new(options.Enabled); } /// /// Gets the injection rate for a given execution, which the value should be between [0, 1]. /// - public Func> InjectionRateGenerator { get; } + public Func> InjectionRateGenerator { get; } /// /// Gets a value that indicates whether or not the chaos strategy is enabled for a given execution. /// - public Func> EnabledGenerator { get; } + public Func> EnabledGenerator { get; } /// /// Determines whether or not the chaos strategy should be injected based on the injection rate and enabled flag. @@ -52,14 +42,14 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) /// The instance. /// A boolean value that indicates whether or not the chaos strategy should be injected. /// Use this method before injecting any chaos strategy to evaluate whether a given chaos strategy needs to be injected during the execution. - public async ValueTask ShouldInject(ResilienceContext context) + public async ValueTask ShouldInjectAsync(ResilienceContext context) { Guard.NotNull(context); // to prevent executing config delegates if token was signaled before to start. context.CancellationToken.ThrowIfCancellationRequested(); - if (!await EnabledGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext)) + if (!await EnabledGenerator(new EnabledGeneratorArguments { Context = context }).ConfigureAwait(context.ContinueOnCapturedContext)) { return false; } @@ -67,7 +57,8 @@ public async ValueTask ShouldInject(ResilienceContext context) // to prevent executing InjectionRate config delegate if token was signaled on Enabled configuration delegate. context.CancellationToken.ThrowIfCancellationRequested(); - double injectionThreshold = await InjectionRateGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext); + double injectionThreshold = await InjectionRateGenerator(new InjectionRateGeneratorArguments { Context = context }) + .ConfigureAwait(context.ContinueOnCapturedContext); // to prevent executing further config delegates if token was signaled on InjectionRate configuration delegate. context.CancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs b/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs index cefb170e3f5..f736d9e4810 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyConstants.cs @@ -5,4 +5,6 @@ internal static class MonkeyStrategyConstants public const double MinInjectionThreshold = 0; public const double MaxInjectionThreshold = 1; + + public const double DefaultInjectionRate = 0.001; } diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs index b4f7d89d9f5..d56826ed67a 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs @@ -5,9 +5,9 @@ namespace Polly.Simmy; #pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null /// -/// The options associated with the . +/// The options associated with the . /// -/// The type of result the retry strategy handles. +/// The type of result the monkey strategy handles. public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions { /// @@ -18,7 +18,7 @@ public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions /// When this property is the is used. /// [Range(MonkeyStrategyConstants.MinInjectionThreshold, MonkeyStrategyConstants.MaxInjectionThreshold)] - public double? InjectionRate { get; set; } + public double InjectionRate { get; set; } = MonkeyStrategyConstants.DefaultInjectionRate; /// /// Gets or sets the injection rate generator for a given execution, which the value should be between [0, 1]. @@ -27,7 +27,7 @@ public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public Func> InjectionRateGenerator { get; set; } + public Func>? InjectionRateGenerator { get; set; } /// /// Gets or sets the enable generator that indicates whether or not the chaos strategy is enabled for a given execution. @@ -36,16 +36,16 @@ public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public Func> EnabledGenerator { get; set; } + public Func>? EnabledGenerator { get; set; } /// - /// Gets or sets a value that indicates whether or not the chaos strategy is enabled for a given execution. + /// Gets or sets a value indicating whether or not the chaos strategy is enabled for a given execution. /// /// /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public bool? Enabled { get; set; } + public bool Enabled { get; set; } /// /// Gets or sets the Randomizer generator instance that is used to evaluate the injection rate. diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs index 72100ea21f9..74361846962 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.cs @@ -3,7 +3,7 @@ #pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null /// -/// The options associated with the . +/// The options associated with the . /// public abstract class MonkeyStrategyOptions : MonkeyStrategyOptions { diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs index da7a95c241f..613a9d9776b 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs @@ -39,9 +39,9 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg public Func, ValueTask>? OnFaultInjected { get; } - public Func>>? OutcomeGenerator { get; } + public Func?>>? OutcomeGenerator { get; } - public Func>>? FaultGenerator { get; } + public Func?>>? FaultGenerator { get; } public Outcome? Outcome { get; private set; } @@ -51,7 +51,7 @@ protected override async ValueTask> ExecuteCore(Func> ExecuteCore(Func(outcome.Value.Result); + } } else { @@ -79,10 +83,10 @@ protected override async ValueTask> ExecuteCore(Func> InjectOutcome(ResilienceContext context) + private async ValueTask?> InjectOutcome(ResilienceContext context) { var outcome = await OutcomeGenerator!(context).ConfigureAwait(context.ContinueOnCapturedContext); - var args = new OnOutcomeInjectedArguments(context, outcome); + var args = new OnOutcomeInjectedArguments(context, new Outcome(outcome.Value.Result)); _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); if (OnOutcomeInjected is not null) @@ -98,23 +102,24 @@ private async ValueTask> InjectOutcome(ResilienceContext context) try { var fault = await FaultGenerator!(context).ConfigureAwait(context.ContinueOnCapturedContext); + if (!fault.HasValue) + { + return null; + } // to prevent injecting the fault if it was cancelled while executing the FaultGenerator context.CancellationToken.ThrowIfCancellationRequested(); - if (fault.Exception is not null) - { - Outcome = new(fault.Exception); - var args = new OnOutcomeInjectedArguments(context, fault); - _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); + Outcome = new(fault.Value.Exception!); + var args = new OnOutcomeInjectedArguments(context, new Outcome(fault.Value.Exception!)); + _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); - if (OnFaultInjected is not null) - { - await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); - } + if (OnFaultInjected is not null) + { + await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); } - return fault.Exception; + return fault.Value.Exception; } catch (OperationCanceledException) { diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs index 060a5c5c822..ee7137d437a 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs @@ -39,7 +39,7 @@ public static CompositeStrategyBuilder AddFault(this Composite /// The exception generator delegate. /// The builder instance with the retry strategy added. public static CompositeStrategyBuilder AddFault( - this CompositeStrategyBuilder builder, bool enabled, double injectionRate, Func>> faultGenerator) + this CompositeStrategyBuilder builder, bool enabled, double injectionRate, Func?>> faultGenerator) { Guard.NotNull(builder); @@ -100,7 +100,7 @@ public static CompositeStrategyBuilder AddResult(this Composit /// The outcome generator delegate. /// The builder instance with the retry strategy added. public static CompositeStrategyBuilder AddResult( - this CompositeStrategyBuilder builder, bool enabled, double injectionRate, Func>> outcomeGenerator) + this CompositeStrategyBuilder builder, bool enabled, double injectionRate, Func?>> outcomeGenerator) { Guard.NotNull(builder); diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs index dfde92ec48b..7efa73c4573 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs @@ -37,7 +37,7 @@ public static CompositeStrategyBuilder AddFault(this CompositeStrategyBuilder bu /// The exception generator delegate. /// The builder instance with the retry strategy added. public static CompositeStrategyBuilder AddFault( - this CompositeStrategyBuilder builder, bool enabled, double injectionRate, Func>> faultGenerator) + this CompositeStrategyBuilder builder, bool enabled, double injectionRate, Func?>> faultGenerator) { Guard.NotNull(builder); diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs index 7ab95b2d6d9..b6f897ee2c5 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs @@ -23,7 +23,7 @@ public class OutcomeStrategyOptions : MonkeyStrategyOptions /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public Func>> OutcomeGenerator { get; set; } + public Func?>> OutcomeGenerator { get; set; } /// /// Gets or sets the outcome to be injected for a given execution. diff --git a/src/Polly.Core/Simmy/ReactiveMonkeyStrategy.cs b/src/Polly.Core/Simmy/ReactiveMonkeyStrategy.cs index 284eef9cfc3..06671f228c6 100644 --- a/src/Polly.Core/Simmy/ReactiveMonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/ReactiveMonkeyStrategy.cs @@ -1,21 +1,25 @@ -using Polly; -using Polly.Simmy; +namespace Polly.Simmy; /// /// This base strategy class is used to simplify the implementation of generic (reactive) -/// strategies by limiting the number of generic types this strategy receives. +/// strategies by limiting the number of generic types the execute method receives. /// /// The type of result this strategy handles. /// /// For strategies that handle all result types the generic parameter must be of type . /// -internal abstract class ReactiveMonkeyStrategy : MonkeyStrategy +public abstract class ReactiveMonkeyStrategy : MonkeyStrategy { + /// + /// Initializes a new instance of the class. + /// + /// The chaos strategy options. protected ReactiveMonkeyStrategy(MonkeyStrategyOptions options) : base(options) { } + /// protected internal sealed override ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, @@ -43,6 +47,29 @@ static async (context, state) => } } + /// + /// An implementation of resilience strategy that executes the specified . + /// + /// 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 abstract ValueTask> ExecuteCore( Func>> callback, ResilienceContext context, diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs index 6b5adf16543..85658ea4059 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs @@ -24,7 +24,7 @@ public void Given_not_enabled_should_not_inject_behaviour() _options.InjectionRate = 0.6; _options.Enabled = false; _options.Randomizer = () => 0.5; - _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + _options.BehaviorAction = (_) => { injectedBehaviourExecuted = true; return default; }; var sut = CreateSut(); sut.Execute(() => { userDelegateExecuted = true; }); @@ -42,7 +42,7 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_beha _options.InjectionRate = 0.6; _options.Enabled = true; _options.Randomizer = () => 0.5; - _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + _options.BehaviorAction = (_) => { injectedBehaviourExecuted = true; return default; }; var sut = CreateSut(); await sut.ExecuteAsync((_) => { userDelegateExecuted = true; return default; }); @@ -62,7 +62,7 @@ public async Task Given_enabled_and_randomly_within_threshold_ensure_on_behavior _options.InjectionRate = 0.6; _options.Enabled = true; _options.Randomizer = () => 0.5; - _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + _options.BehaviorAction = (_) => { injectedBehaviourExecuted = true; return default; }; _options.OnBehaviorInjected = args => { args.Context.Should().NotBeNull(); @@ -89,7 +89,7 @@ public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inj _options.InjectionRate = 0.4; _options.Enabled = false; _options.Randomizer = () => 0.5; - _options.Behavior = (_) => { injectedBehaviourExecuted = true; return default; }; + _options.BehaviorAction = (_) => { injectedBehaviourExecuted = true; return default; }; var sut = CreateSut(); await sut.ExecuteAsync((_) => { userDelegateExecuted = true; return default; }); @@ -107,7 +107,7 @@ public async Task Should_inject_behaviour_before_executing_user_delegate() _options.InjectionRate = 0.6; _options.Enabled = true; _options.Randomizer = () => 0.5; - _options.Behavior = (_) => + _options.BehaviorAction = (_) => { userDelegateExecuted.Should().BeFalse(); // Not yet executed at the time the injected behaviour runs. injectedBehaviourExecuted = true; @@ -131,7 +131,7 @@ public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running _options.InjectionRate = 0.6; _options.Enabled = true; _options.Randomizer = () => 0.5; - _options.Behavior = (_) => + _options.BehaviorAction = (_) => { cts.Cancel(); injectedBehaviourExecuted = true; diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs index 1bc0119bff6..a6b4f76b534 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Polly.Simmy; using Polly.Simmy.Behavior; namespace Polly.Core.Tests.Simmy.Behavior; @@ -15,8 +16,8 @@ public static IEnumerable AddBehavior_Ok_Data() (BehaviorChaosStrategy strategy) => { strategy.Behavior.Invoke(context).Preserve().GetAwaiter().IsCompleted.Should().BeTrue(); - strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().BeTrue(); - strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(0.5); + strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().BeTrue(); + strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(0.5); } }; } @@ -54,7 +55,7 @@ public void AddBehavior_Options_Ok() { Enabled = true, InjectionRate = 1, - Behavior = (_) => new ValueTask(Task.CompletedTask) + BehaviorAction = (_) => new ValueTask(Task.CompletedTask) }) .Build(); diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs index 9d74b31ab7f..d0b88c9390b 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorStrategyOptionsTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Polly.Simmy; using Polly.Simmy.Behavior; using Polly.Utils; @@ -11,11 +12,11 @@ public void Ctor_Ok() { var sut = new BehaviorStrategyOptions(); sut.Randomizer.Should().NotBeNull(); - sut.Enabled.Should().BeNull(); + sut.Enabled.Should().BeFalse(); sut.EnabledGenerator.Should().BeNull(); - sut.InjectionRate.Should().BeNull(); + sut.InjectionRate.Should().Be(MonkeyStrategyConstants.DefaultInjectionRate); sut.InjectionRateGenerator.Should().BeNull(); - sut.Behavior.Should().BeNull(); + sut.BehaviorAction.Should().BeNull(); sut.OnBehaviorInjected.Should().BeNull(); } @@ -32,7 +33,7 @@ public void InvalidOptions() Invalid Options Validation Errors: - The Behavior field is required. + The BehaviorAction field is required. """); } } diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs index 5fe2826760c..d768e3b7787 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Polly.Simmy; using Polly.Utils; namespace Polly.Core.Tests.Simmy; @@ -10,9 +11,9 @@ public void Ctor_Ok() var sut = new TestChaosStrategyOptions(); sut.Randomizer.Should().NotBeNull(); - sut.Enabled.Should().BeNull(); + sut.Enabled.Should().BeFalse(); sut.EnabledGenerator.Should().BeNull(); - sut.InjectionRate.Should().BeNull(); + sut.InjectionRate.Should().Be(MonkeyStrategyConstants.DefaultInjectionRate); sut.InjectionRateGenerator.Should().BeNull(); } diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs index d5ac5caa8f7..4bd1158d1b2 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs @@ -1,4 +1,6 @@ -namespace Polly.Core.Tests.Simmy; +using Polly.Simmy; + +namespace Polly.Core.Tests.Simmy; #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. @@ -12,8 +14,6 @@ public class MonkeyStrategyTests new() { new object[] { null, "Value cannot be null. (Parameter 'options')" }, - new object[] { new TestChaosStrategyOptions(), "Either InjectionRate or InjectionRateGenerator is required. (Parameter 'InjectionRate')" }, - new object[] { new TestChaosStrategyOptions { InjectionRate = 0.5 }, "Either Enabled or EnabledGenerator is required. (Parameter 'Enabled')" } }; [Theory] @@ -45,10 +45,10 @@ public async Task Ctor_Ok() var sut = CreateSut(); sut.EnabledGenerator.Should().NotBeNull(); - (await sut.EnabledGenerator(context)).Should().BeTrue(); + (await sut.EnabledGenerator(new EnabledGeneratorArguments { Context = context })).Should().BeTrue(); sut.InjectionRateGenerator.Should().NotBeNull(); - (await sut.InjectionRateGenerator(context)).Should().Be(0.5); + (await sut.InjectionRateGenerator(new InjectionRateGeneratorArguments { Context = context })).Should().Be(0.5); } [InlineData(-1)] diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index e1987a9cc8e..28b5d8f1f5b 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -123,7 +123,7 @@ public void Given_enabled_and_randomly_within_threshold_should_not_inject_fault_ InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - OutcomeGenerator = (_) => new ValueTask>(Task.FromResult(new Outcome(null!))) + OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(null)) }; var sut = CreateSut(options); @@ -150,7 +150,7 @@ public async Task Should_not_inject_fault_when_it_was_cancelled_running_the_faul OutcomeGenerator = (_) => { cts.Cancel(); - return new ValueTask>(new Outcome(fault)); + return new ValueTask?>(new Outcome(fault)); } }; @@ -236,6 +236,29 @@ public void Given_enabled_and_randomly_not_within_threshold_should_not_inject_re userDelegateExecuted.Should().BeTrue(); } + [Fact] + public async Task Given_enabled_and_randomly_within_threshold_should_inject_result_even_as_null() + { + var userDelegateExecuted = false; + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + Outcome = Outcome.FromResult(null) + }; + + var sut = CreateSut(options); + var response = await sut.ExecuteAsync(async _ => + { + userDelegateExecuted = true; + return await Task.FromResult(HttpStatusCode.OK); + }); + + response.Should().Be(null); + userDelegateExecuted.Should().BeFalse(); + } + private OutcomeChaosStrategy CreateSut(OutcomeStrategyOptions options) => new(options, _telemetry); diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs index 8da103f0818..21631a4477c 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Polly.Simmy; using Polly.Simmy.Outcomes; namespace Polly.Core.Tests.Simmy.Outcomes; @@ -57,8 +58,8 @@ private static void AssertResultStrategy(CompositeStrategyBuilder builder, { var context = ResilienceContextPool.Shared.Get(); var strategy = (OutcomeChaosStrategy)builder.Build().Strategy; - strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(enabled); - strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.OutcomeGenerator.Should().NotBeNull(); strategy.OutcomeGenerator!.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(outcome); strategy.Outcome.Should().Be(outcome); @@ -69,8 +70,8 @@ private static void AssertFaultStrategy(CompositeStrategyBuilder< { var context = ResilienceContextPool.Shared.Get(); var strategy = (OutcomeChaosStrategy)builder.Build().Strategy; - strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(enabled); - strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.FaultGenerator.Should().NotBeNull(); strategy.Fault.Should().BeOfType(typeof(Outcome)); strategy.Fault.Should().NotBeNull(); @@ -93,8 +94,8 @@ private static void AssertFaultStrategy(CompositeStrategyBuilder bui { var context = ResilienceContextPool.Shared.Get(); var strategy = (OutcomeChaosStrategy)builder.Build(); - strategy.EnabledGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(enabled); - strategy.InjectionRateGenerator.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.FaultGenerator.Should().NotBeNull(); strategy.Fault.Should().BeOfType(typeof(Outcome)); strategy.Fault.Should().NotBeNull(); @@ -151,7 +152,7 @@ public void AddResult_Shortcut_Option_Ok() public void AddResult_Shortcut_Option_Throws() { new CompositeStrategyBuilder() - .Invoking(b => b.AddResult(true, -1, () => new ValueTask>(Task.FromResult(new Outcome(120))))) + .Invoking(b => b.AddResult(true, -1, () => new ValueTask?>(new Outcome(120)))) .Should() .Throw(); } @@ -174,7 +175,7 @@ public void AddFault_Shortcut_Option_Throws() .Invoking(b => b.AddFault( true, 1.5, - () => new ValueTask>(Task.FromResult(new Outcome(new InvalidOperationException()))))) + () => new ValueTask?>(new Outcome(new InvalidOperationException())))) .Should() .Throw(); } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs index ea08db8954f..3d2d6d001a7 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs @@ -1,4 +1,5 @@ -using Polly.Simmy.Outcomes; +using Polly.Simmy; +using Polly.Simmy.Outcomes; namespace Polly.Core.Tests.Simmy.Outcomes; @@ -9,9 +10,9 @@ public void Ctor_Ok() { var sut = new OutcomeStrategyOptions(); sut.Randomizer.Should().NotBeNull(); - sut.Enabled.Should().BeNull(); + sut.Enabled.Should().BeFalse(); sut.EnabledGenerator.Should().BeNull(); - sut.InjectionRate.Should().BeNull(); + sut.InjectionRate.Should().Be(MonkeyStrategyConstants.DefaultInjectionRate); sut.InjectionRateGenerator.Should().BeNull(); sut.Outcome.Should().Be(default(Outcome)); sut.OnOutcomeInjected.Should().BeNull(); diff --git a/test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs b/test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs index 8900f855735..5fab6d3777b 100644 --- a/test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs +++ b/test/Polly.Core.Tests/Simmy/TestChaosStrategy.cs @@ -21,7 +21,7 @@ protected internal override async ValueTask> ExecuteCore Date: Sun, 13 Aug 2023 17:40:36 -0500 Subject: [PATCH 22/36] PR feedback part 2 --- src/Polly.Core/PublicAPI.Unshipped.txt | 72 ++++++++++--------- .../Simmy/Behavior/BehaviorActionArguments.cs | 20 ++++++ .../Simmy/Behavior/BehaviorChaosStrategy.cs | 11 ++- .../Simmy/Behavior/BehaviorStrategyOptions.cs | 4 +- .../Behavior/OnBehaviorInjectedArguments.cs | 17 ++++- .../Simmy/EnabledGeneratorArguments.cs | 14 +++- .../Simmy/InjectionRateGeneratorArguments.cs | 14 +++- .../Simmy/Latency/LatencyChaosStrategy.cs | 24 +++---- .../Simmy/Latency/LatencyConstants.cs | 2 + .../Latency/LatencyGeneratorArguments.cs | 20 ++++++ .../Simmy/Latency/LatencyStrategyOptions.cs | 11 +-- .../Simmy/Latency/OnDelayedArguments.cs | 8 --- .../Simmy/Latency/OnLatencyArguments.cs | 30 ++++++++ src/Polly.Core/Simmy/MonkeyStrategy.cs | 28 +++++--- .../Simmy/MonkeyStrategyOptions.TResult.cs | 7 +- .../Outcomes/OnOutcomeInjectedArguments.cs | 10 --- .../Outcomes/OutcomeChaosStrategy.TResult.cs | 31 ++++---- .../Outcomes/OutcomeGeneratorArguments.cs | 20 ++++++ .../OutcomeStrategyOptions.TResult.cs | 6 +- src/Polly.Core/Simmy/Utils/GuardExtensions.cs | 17 ----- .../Behavior/BehaviorActionArgumentsTests.cs | 13 ++++ ...BehaviorCompositeBuilderExtensionsTests.cs | 7 +- ...nBehaviorInjectedArgumentsTests - Copy.cs} | 0 .../Simmy/EnabledGeneratorArgumentsTests.cs | 13 ++++ .../InjectionRateGeneratorArgumentsTests.cs | 13 ++++ .../Simmy/MonkeyStrategyTests.cs | 23 +++--- .../Outcomes/OutcomeChaosStrategyTests.cs | 56 ++++++++++++--- .../OutcomeCompositeBuilderExtensionsTests.cs | 15 ++-- ...s.cs => OutcomeGeneratorArgumentsTests.cs} | 4 +- .../Outcomes/OutcomeStrategyOptionsTests.cs | 2 +- 30 files changed, 340 insertions(+), 172 deletions(-) create mode 100644 src/Polly.Core/Simmy/Behavior/BehaviorActionArguments.cs create mode 100644 src/Polly.Core/Simmy/Latency/LatencyGeneratorArguments.cs delete mode 100644 src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs create mode 100644 src/Polly.Core/Simmy/Latency/OnLatencyArguments.cs delete mode 100644 src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs delete mode 100644 src/Polly.Core/Simmy/Utils/GuardExtensions.cs create mode 100644 test/Polly.Core.Tests/Simmy/Behavior/BehaviorActionArgumentsTests.cs rename test/Polly.Core.Tests/Simmy/Behavior/{OnBehaviorInjectedArgumentsTests.cs => OnBehaviorInjectedArgumentsTests - Copy.cs} (100%) create mode 100644 test/Polly.Core.Tests/Simmy/EnabledGeneratorArgumentsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/InjectionRateGeneratorArgumentsTests.cs rename test/Polly.Core.Tests/Simmy/Outcomes/{OnOutcomeInjectedArgumentsTests.cs => OutcomeGeneratorArgumentsTests.cs} (51%) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 463997bf09b..9ee730db50e 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -320,41 +320,50 @@ Polly.Retry.RetryStrategyOptions.ShouldHandle.set -> void Polly.Retry.RetryStrategyOptions.UseJitter.get -> bool Polly.Retry.RetryStrategyOptions.UseJitter.set -> void Polly.RetryCompositeStrategyBuilderExtensions +Polly.Simmy.Behavior.BehaviorActionArguments +Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments() -> void +Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments(Polly.ResilienceContext! context) -> void +Polly.Simmy.Behavior.BehaviorActionArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Behavior.BehaviorCompositeStrategyBuilderExtensions Polly.Simmy.Behavior.BehaviorStrategyOptions -Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.get -> System.Func! +Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.get -> System.Func? Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.set -> void Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorStrategyOptions() -> void Polly.Simmy.Behavior.BehaviorStrategyOptions.OnBehaviorInjected.get -> System.Func? Polly.Simmy.Behavior.BehaviorStrategyOptions.OnBehaviorInjected.set -> void Polly.Simmy.Behavior.OnBehaviorInjectedArguments Polly.Simmy.Behavior.OnBehaviorInjectedArguments.Context.get -> Polly.ResilienceContext! -Polly.Simmy.Behavior.OnBehaviorInjectedArguments.Context.init -> void Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments() -> void -Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments(Polly.ResilienceContext! Context) -> void +Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments(Polly.ResilienceContext! context) -> void +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.InjectionRateGeneratorArguments -Polly.Simmy.InjectionRateGeneratorArguments.Context.get -> Polly.ResilienceContext? -Polly.Simmy.InjectionRateGeneratorArguments.Context.set -> void +Polly.Simmy.InjectionRateGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.InjectionRateGeneratorArguments.InjectionRateGeneratorArguments() -> void +Polly.Simmy.InjectionRateGeneratorArguments.InjectionRateGeneratorArguments(Polly.ResilienceContext! context) -> void Polly.Simmy.Latency.LatencyCompositeStrategyBuilderExtensions +Polly.Simmy.Latency.LatencyGeneratorArguments +Polly.Simmy.Latency.LatencyGeneratorArguments.Context.get -> Polly.ResilienceContext! +Polly.Simmy.Latency.LatencyGeneratorArguments.LatencyGeneratorArguments() -> void +Polly.Simmy.Latency.LatencyGeneratorArguments.LatencyGeneratorArguments(Polly.ResilienceContext! context) -> void Polly.Simmy.Latency.LatencyStrategyOptions -Polly.Simmy.Latency.LatencyStrategyOptions.Latency.get -> System.TimeSpan? +Polly.Simmy.Latency.LatencyStrategyOptions.Latency.get -> System.TimeSpan Polly.Simmy.Latency.LatencyStrategyOptions.Latency.set -> void -Polly.Simmy.Latency.LatencyStrategyOptions.LatencyGenerator.get -> System.Func>! +Polly.Simmy.Latency.LatencyStrategyOptions.LatencyGenerator.get -> System.Func>? Polly.Simmy.Latency.LatencyStrategyOptions.LatencyGenerator.set -> void Polly.Simmy.Latency.LatencyStrategyOptions.LatencyStrategyOptions() -> void -Polly.Simmy.Latency.LatencyStrategyOptions.OnDelayed.get -> System.Func? -Polly.Simmy.Latency.LatencyStrategyOptions.OnDelayed.set -> void -Polly.Simmy.Latency.OnDelayedArguments -Polly.Simmy.Latency.OnDelayedArguments.Context.get -> Polly.ResilienceContext! -Polly.Simmy.Latency.OnDelayedArguments.Context.init -> void -Polly.Simmy.Latency.OnDelayedArguments.Latency.get -> System.TimeSpan -Polly.Simmy.Latency.OnDelayedArguments.Latency.init -> void -Polly.Simmy.Latency.OnDelayedArguments.OnDelayedArguments() -> void -Polly.Simmy.Latency.OnDelayedArguments.OnDelayedArguments(Polly.ResilienceContext! Context, System.TimeSpan Latency) -> void +Polly.Simmy.Latency.LatencyStrategyOptions.OnLatency.get -> System.Func? +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.MonkeyStrategy -Polly.Simmy.MonkeyStrategy.EnabledGenerator.get -> System.Func>! -Polly.Simmy.MonkeyStrategy.InjectionRateGenerator.get -> System.Func>! +Polly.Simmy.MonkeyStrategy.EnabledGenerator.get -> System.Func>! +Polly.Simmy.MonkeyStrategy.InjectionRateGenerator.get -> System.Func>! Polly.Simmy.MonkeyStrategy.MonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void Polly.Simmy.MonkeyStrategy.ShouldInjectAsync(Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask Polly.Simmy.MonkeyStrategyOptions @@ -362,31 +371,28 @@ Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions Polly.Simmy.MonkeyStrategyOptions.Enabled.get -> bool Polly.Simmy.MonkeyStrategyOptions.Enabled.set -> void -Polly.Simmy.MonkeyStrategyOptions.EnabledGenerator.get -> System.Func>? +Polly.Simmy.MonkeyStrategyOptions.EnabledGenerator.get -> System.Func>? Polly.Simmy.MonkeyStrategyOptions.EnabledGenerator.set -> void Polly.Simmy.MonkeyStrategyOptions.InjectionRate.get -> double Polly.Simmy.MonkeyStrategyOptions.InjectionRate.set -> void -Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.get -> System.Func>? +Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.get -> System.Func>? Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.set -> void Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions.Randomizer.get -> System.Func! Polly.Simmy.MonkeyStrategyOptions.Randomizer.set -> void -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.get -> Polly.ResilienceContext! -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.init -> void -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments() -> void -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments(Polly.ResilienceContext! Context, Polly.Outcome Outcome) -> void -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Outcome.get -> Polly.Outcome -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Outcome.init -> void Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions +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 -Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? +Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.set -> void -Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.get -> Polly.Outcome +Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.get -> Polly.Outcome? Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.set -> void -Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeGenerator.get -> System.Func?>>! +Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeGenerator.get -> System.Func?>>? Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeGenerator.set -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeStrategyOptions() -> void Polly.Simmy.ReactiveMonkeyStrategy @@ -486,12 +492,12 @@ static Polly.Simmy.Behavior.BehaviorCompositeStrategyBuilderExtensions.AddBehavi static Polly.Simmy.Latency.LatencyCompositeStrategyBuilderExtensions.AddLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! static Polly.Simmy.Latency.LatencyCompositeStrategyBuilderExtensions.AddLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.CompositeStrategyBuilder! -static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Func>>! faultGenerator) -> Polly.CompositeStrategyBuilder! +static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.CompositeStrategyBuilder! static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.CompositeStrategyBuilder! static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.CompositeStrategyBuilder! -static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Func>>! faultGenerator) -> Polly.CompositeStrategyBuilder! +static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.CompositeStrategyBuilder! static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddFault(this Polly.CompositeStrategyBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.CompositeStrategyBuilder! -static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddResult(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Func>>! outcomeGenerator) -> Polly.CompositeStrategyBuilder! +static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddResult(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, System.Func?>>! outcomeGenerator) -> Polly.CompositeStrategyBuilder! static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddResult(this Polly.CompositeStrategyBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.CompositeStrategyBuilder! static Polly.Simmy.Outcomes.OutcomeCompositeStrategyBuilderExtensions.AddResult(this Polly.CompositeStrategyBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.CompositeStrategyBuilder! static Polly.TimeoutCompositeStrategyBuilderExtensions.AddTimeout(this TBuilder! builder, Polly.Timeout.TimeoutStrategyOptions! options) -> TBuilder! diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorActionArguments.cs b/src/Polly.Core/Simmy/Behavior/BehaviorActionArguments.cs new file mode 100644 index 00000000000..2df7cdd1724 --- /dev/null +++ b/src/Polly.Core/Simmy/Behavior/BehaviorActionArguments.cs @@ -0,0 +1,20 @@ +namespace Polly.Simmy.Behavior; + +#pragma warning disable CA1815 // Override equals and operator equals on value types + +/// +/// Arguments used by the behavior chaos strategy to execute a user's delegate custom action. +/// +public readonly struct BehaviorActionArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + public BehaviorActionArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. + /// + public ResilienceContext Context { get; } +} diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs index b79c763fdc6..f85cd241163 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs @@ -11,9 +11,6 @@ public BehaviorChaosStrategy( ResilienceStrategyTelemetry telemetry) : base(options) { - Guard.NotNull(telemetry); - Guard.NotNull(options.BehaviorAction); - _telemetry = telemetry; OnBehaviorInjected = options.OnBehaviorInjected; Behavior = options.BehaviorAction; @@ -21,16 +18,18 @@ public BehaviorChaosStrategy( public Func? OnBehaviorInjected { get; } - public Func Behavior { get; } + public Func? Behavior { get; } protected internal override async ValueTask> ExecuteCore( - Func>> callback, ResilienceContext context, TState state) + Func>> callback, + ResilienceContext context, + TState state) { try { if (await ShouldInjectAsync(context).ConfigureAwait(context.ContinueOnCapturedContext)) { - await Behavior(context).ConfigureAwait(context.ContinueOnCapturedContext); + await Behavior!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); var args = new OnBehaviorInjectedArguments(context); _telemetry.Report(new(ResilienceEventSeverity.Warning, BehaviorConstants.OnBehaviorInjectedEvent), context, args); diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs index e148205e98f..1541fbc9b3f 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorStrategyOptions.cs @@ -2,8 +2,6 @@ namespace Polly.Simmy.Behavior; -#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null - /// /// Represents the options for the Behavior chaos strategy. /// @@ -24,5 +22,5 @@ public class BehaviorStrategyOptions : MonkeyStrategyOptions /// Defaults to . /// [Required] - public Func BehaviorAction { get; set; } + public Func? BehaviorAction { get; set; } } diff --git a/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs b/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs index 6b8aad29456..34e68a505c7 100644 --- a/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs +++ b/src/Polly.Core/Simmy/Behavior/OnBehaviorInjectedArguments.cs @@ -1,7 +1,20 @@ namespace Polly.Simmy.Behavior; +#pragma warning disable CA1815 // Override equals and operator equals on value types + /// /// Arguments used by the behavior chaos strategy to notify that a custom behavior was injected. /// -/// The context associated with the execution of a user-provided callback. -public readonly record struct OnBehaviorInjectedArguments(ResilienceContext Context); +public readonly struct OnBehaviorInjectedArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + public OnBehaviorInjectedArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. + /// + public ResilienceContext Context { get; } +} diff --git a/src/Polly.Core/Simmy/EnabledGeneratorArguments.cs b/src/Polly.Core/Simmy/EnabledGeneratorArguments.cs index 74ba51a025c..57c6cec52d4 100644 --- a/src/Polly.Core/Simmy/EnabledGeneratorArguments.cs +++ b/src/Polly.Core/Simmy/EnabledGeneratorArguments.cs @@ -1,12 +1,20 @@ namespace Polly.Simmy; +#pragma warning disable CA1815 // Override equals and operator equals on value types + /// /// Defines the arguments for the . /// -public sealed class EnabledGeneratorArguments +public readonly struct EnabledGeneratorArguments { /// - /// Gets or sets the ResilienceContext instance. + /// Initializes a new instance of the struct. + /// + /// The resilience context intance. + public EnabledGeneratorArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. /// - public ResilienceContext? Context { get; set; } + public ResilienceContext Context { get; } } diff --git a/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs b/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs index 5a2858a37a5..556299c6d31 100644 --- a/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs +++ b/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs @@ -1,12 +1,20 @@ namespace Polly.Simmy; +#pragma warning disable CA1815 // Override equals and operator equals on value types + /// /// Defines the arguments for the . /// -public sealed class InjectionRateGeneratorArguments +public readonly struct InjectionRateGeneratorArguments { /// - /// Gets or sets the ResilienceContext instance. + /// Initializes a new instance of the struct. + /// + /// The resilience context intance. + public InjectionRateGeneratorArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. /// - public ResilienceContext? Context { get; set; } + public ResilienceContext Context { get; } } diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index 7786f244d80..598cdee8ca8 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -15,39 +15,33 @@ public LatencyChaosStrategy( ResilienceStrategyTelemetry telemetry) : base(options) { - Guard.NotNull(telemetry); - Guard.NotNull(timeProvider); - - if (options.Latency is null && options.LatencyGenerator is null) - { - throw new ArgumentNullException(nameof(options.Latency), "Either Latency or LatencyGenerator is required."); - } - Latency = options.Latency; - LatencyGenerator = options.Latency.HasValue ? (_) => new(options.Latency.Value) : options.LatencyGenerator; - OnDelayed = options.OnDelayed; + LatencyGenerator = options.LatencyGenerator is not null ? options.LatencyGenerator : (_) => new(options.Latency); + OnDelayed = options.OnLatency; _telemetry = telemetry; _timeProvider = timeProvider; } - public Func? OnDelayed { get; } + public Func? OnDelayed { get; } - public Func> LatencyGenerator { get; } + public Func> LatencyGenerator { get; } public TimeSpan? Latency { get; } protected internal override async ValueTask> ExecuteCore( - Func>> callback, ResilienceContext context, TState state) + Func>> callback, + ResilienceContext context, + TState state) { try { if (await ShouldInjectAsync(context).ConfigureAwait(context.ContinueOnCapturedContext)) { - var latency = await LatencyGenerator(context).ConfigureAwait(context.ContinueOnCapturedContext); + var latency = await LatencyGenerator(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); await _timeProvider.DelayAsync(latency, context).ConfigureAwait(context.ContinueOnCapturedContext); - var args = new OnDelayedArguments(context, latency); + var args = new OnLatencyArguments(context, latency); _telemetry.Report(new(ResilienceEventSeverity.Warning, LatencyConstants.OnDelayedEvent), context, args); if (OnDelayed is not null) diff --git a/src/Polly.Core/Simmy/Latency/LatencyConstants.cs b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs index 0bfa872ec89..211542e348f 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyConstants.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyConstants.cs @@ -3,4 +3,6 @@ internal static class LatencyConstants { public const string OnDelayedEvent = "OnDelayed"; + + public static readonly TimeSpan DefaultLatency = TimeSpan.FromSeconds(30); } diff --git a/src/Polly.Core/Simmy/Latency/LatencyGeneratorArguments.cs b/src/Polly.Core/Simmy/Latency/LatencyGeneratorArguments.cs new file mode 100644 index 00000000000..8ef01985fee --- /dev/null +++ b/src/Polly.Core/Simmy/Latency/LatencyGeneratorArguments.cs @@ -0,0 +1,20 @@ +namespace Polly.Simmy.Latency; + +#pragma warning disable CA1815 // Override equals and operator equals on value types + +/// +/// Arguments used by the latency chaos strategy to notify that a delayed occurred. +/// +public readonly struct LatencyGeneratorArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + public LatencyGeneratorArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. + /// + public ResilienceContext Context { get; } +} diff --git a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs index 104e02d701e..84301646684 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyStrategyOptions.cs @@ -7,13 +7,15 @@ /// public class LatencyStrategyOptions : MonkeyStrategyOptions { + internal static readonly TimeSpan DefaultLatency = TimeSpan.FromSeconds(30); + /// /// Gets or sets the delegate that's raised when delay occurs. /// /// /// Defaults to . /// - public Func? OnDelayed { get; set; } + public Func? OnLatency { get; set; } /// /// Gets or sets the latency generator that generates the delay for a given execution. @@ -22,14 +24,13 @@ public class LatencyStrategyOptions : MonkeyStrategyOptions /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public Func> LatencyGenerator { get; set; } + public Func>? LatencyGenerator { get; set; } /// /// Gets or sets the delay for a given execution. /// /// - /// Defaults to . Either or this property is required. - /// When this property is the is used. + /// Defaults to 30 seconds. Either or this property is required. /// - public TimeSpan? Latency { get; set; } + public TimeSpan Latency { get; set; } = LatencyConstants.DefaultLatency; } diff --git a/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs b/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs deleted file mode 100644 index c2ae7d2bf1f..00000000000 --- a/src/Polly.Core/Simmy/Latency/OnDelayedArguments.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Polly.Simmy.Latency; - -/// -/// Arguments used by the latency chaos strategy to notify that a delayed occurred. -/// -/// The context associated with the execution of a user-provided callback. -/// The timeout value assigned. -public readonly record struct OnDelayedArguments(ResilienceContext Context, TimeSpan Latency); diff --git a/src/Polly.Core/Simmy/Latency/OnLatencyArguments.cs b/src/Polly.Core/Simmy/Latency/OnLatencyArguments.cs new file mode 100644 index 00000000000..9a3896d2904 --- /dev/null +++ b/src/Polly.Core/Simmy/Latency/OnLatencyArguments.cs @@ -0,0 +1,30 @@ +namespace Polly.Simmy.Latency; + +#pragma warning disable CA1815 // Override equals and operator equals on value types + +/// +/// Arguments used by the latency chaos strategy to notify that a delayed occurred. +/// +public readonly struct OnLatencyArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + /// The latency that was injected. + public OnLatencyArguments(ResilienceContext context, TimeSpan latency) + { + Context = context; + Latency = latency; + } + + /// + /// Gets the ResilienceContext instance. + /// + public ResilienceContext Context { get; } + + /// + /// Gets the latency that was injected. + /// + public TimeSpan Latency { get; } +} diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.cs b/src/Polly.Core/Simmy/MonkeyStrategy.cs index 115bde112ab..c519cfbe4b7 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.cs @@ -1,6 +1,4 @@ -using Polly.Simmy.Utils; - -namespace Polly.Simmy; +namespace Polly.Simmy; #pragma warning disable CA1031 // Do not catch general exception types #pragma warning disable S3928 // Custom ArgumentNullException message @@ -42,14 +40,14 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) /// The instance. /// A boolean value that indicates whether or not the chaos strategy should be injected. /// Use this method before injecting any chaos strategy to evaluate whether a given chaos strategy needs to be injected during the execution. - public async ValueTask ShouldInjectAsync(ResilienceContext context) + protected async ValueTask ShouldInjectAsync(ResilienceContext context) { Guard.NotNull(context); // to prevent executing config delegates if token was signaled before to start. context.CancellationToken.ThrowIfCancellationRequested(); - if (!await EnabledGenerator(new EnabledGeneratorArguments { Context = context }).ConfigureAwait(context.ContinueOnCapturedContext)) + if (!await EnabledGenerator(new(context)).ConfigureAwait(context.ContinueOnCapturedContext)) { return false; } @@ -57,13 +55,27 @@ public async ValueTask ShouldInjectAsync(ResilienceContext context) // to prevent executing InjectionRate config delegate if token was signaled on Enabled configuration delegate. context.CancellationToken.ThrowIfCancellationRequested(); - double injectionThreshold = await InjectionRateGenerator(new InjectionRateGeneratorArguments { Context = context }) - .ConfigureAwait(context.ContinueOnCapturedContext); + double injectionThreshold = await InjectionRateGenerator(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); // to prevent executing further config delegates if token was signaled on InjectionRate configuration delegate. context.CancellationToken.ThrowIfCancellationRequested(); - injectionThreshold.EnsureInjectionThreshold(); + injectionThreshold = CoerceInjectionThreshold(injectionThreshold); return _randomizer() < injectionThreshold; } + + private static double CoerceInjectionThreshold(double injectionThreshold) + { + if (injectionThreshold < MonkeyStrategyConstants.MinInjectionThreshold) + { + return MonkeyStrategyConstants.MinInjectionThreshold; + } + + if (injectionThreshold > MonkeyStrategyConstants.MaxInjectionThreshold) + { + return MonkeyStrategyConstants.MaxInjectionThreshold; + } + + return injectionThreshold; + } } diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs index d56826ed67a..74fe29dd6d2 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs @@ -14,8 +14,7 @@ public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions /// Gets or sets the injection rate for a given execution, which the value should be between [0, 1]. /// /// - /// Defaults to . Either or this property is required. - /// When this property is the is used. + /// Defaults to 0.001. Either or this property is required. /// [Range(MonkeyStrategyConstants.MinInjectionThreshold, MonkeyStrategyConstants.MaxInjectionThreshold)] public double InjectionRate { get; set; } = MonkeyStrategyConstants.DefaultInjectionRate; @@ -42,8 +41,7 @@ public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions /// Gets or sets a value indicating whether or not the chaos strategy is enabled for a given execution. /// /// - /// Defaults to . Either or this property is required. - /// When this property is the is used. + /// Defaults to . Either or this property is required. /// public bool Enabled { get; set; } @@ -56,4 +54,3 @@ public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions [Required] public Func Randomizer { get; set; } = RandomUtil.Instance.NextDouble; } - diff --git a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs deleted file mode 100644 index 8c47de7fd29..00000000000 --- a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Polly.Simmy.Outcomes; - -/// -/// Arguments used by the latency chaos strategy to notify that an outcome was injected. -/// -/// The type of the outcome that was injected. -/// The context associated with the execution of a user-provided callback. -/// The outcome that was injected. -/// -public readonly record struct OnOutcomeInjectedArguments(ResilienceContext Context, Outcome Outcome); diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs index 613a9d9776b..31e2cdeaa7f 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.TResult.cs @@ -11,9 +11,7 @@ internal class OutcomeChaosStrategy : ReactiveMonkeyStrategy public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrategyTelemetry telemetry) : base(options) { - Guard.NotNull(telemetry); - - if (options.Outcome.Exception is null && options.OutcomeGenerator is null) + if (options.Outcome is null && options.OutcomeGenerator is null) { throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); } @@ -21,27 +19,30 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, Resilienc _telemetry = telemetry; Fault = options.Outcome; OnFaultInjected = options.OnOutcomeInjected; - FaultGenerator = options.Outcome.Exception is not null ? (_) => new(options.Outcome) : options.OutcomeGenerator; + FaultGenerator = options.OutcomeGenerator is not null ? options.OutcomeGenerator : (_) => new(options.Outcome); } public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrategyTelemetry telemetry) : base(options) { - Guard.NotNull(telemetry); + if (options.Outcome is null && options.OutcomeGenerator is null) + { + throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); + } _telemetry = telemetry; Outcome = options.Outcome; OnOutcomeInjected = options.OnOutcomeInjected; - OutcomeGenerator = options.Outcome.HasResult ? (_) => new(options.Outcome) : options.OutcomeGenerator; + OutcomeGenerator = options.OutcomeGenerator is not null ? options.OutcomeGenerator : (_) => new(options.Outcome); } - public Func, ValueTask>? OnOutcomeInjected { get; } + public Func, ValueTask>? OnOutcomeInjected { get; } - public Func, ValueTask>? OnFaultInjected { get; } + public Func, ValueTask>? OnFaultInjected { get; } - public Func?>>? OutcomeGenerator { get; } + public Func?>>? OutcomeGenerator { get; } - public Func?>>? FaultGenerator { get; } + public Func?>>? FaultGenerator { get; } public Outcome? Outcome { get; private set; } @@ -85,8 +86,9 @@ protected override async ValueTask> ExecuteCore(Func?> InjectOutcome(ResilienceContext context) { - var outcome = await OutcomeGenerator!(context).ConfigureAwait(context.ContinueOnCapturedContext); - var args = new OnOutcomeInjectedArguments(context, new Outcome(outcome.Value.Result)); + var outcomeGeneratorArgs = new OutcomeGeneratorArguments(context); + var outcome = await OutcomeGenerator!(outcomeGeneratorArgs).ConfigureAwait(context.ContinueOnCapturedContext); + var args = new OutcomeArguments(context, outcome.Value, outcomeGeneratorArgs); _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); if (OnOutcomeInjected is not null) @@ -101,7 +103,8 @@ protected override async ValueTask> ExecuteCore(Func> ExecuteCore(Func(context, new Outcome(fault.Value.Exception!)); + var args = new OutcomeArguments(context, new Outcome(fault.Value.Exception!), outcomeGeneratorArgs); _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); if (OnFaultInjected is not null) diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs new file mode 100644 index 00000000000..d3ca3322316 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs @@ -0,0 +1,20 @@ +namespace Polly.Simmy.Outcomes; + +#pragma warning disable CA1815 // Override equals and operator equals on value types + +/// +/// Arguments used by the outcome chaos strategy to notify that an outcome was injected. +/// +public readonly struct OutcomeGeneratorArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + public OutcomeGeneratorArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. + /// + public ResilienceContext Context { get; } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs index b6f897ee2c5..09bdcc512bd 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs @@ -14,7 +14,7 @@ public class OutcomeStrategyOptions : MonkeyStrategyOptions /// /// Defaults to . /// - public Func, ValueTask>? OnOutcomeInjected { get; set; } + public Func, ValueTask>? OnOutcomeInjected { get; set; } /// /// Gets or sets the outcome generator to be injected for a given execution. @@ -23,7 +23,7 @@ public class OutcomeStrategyOptions : MonkeyStrategyOptions /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public Func?>> OutcomeGenerator { get; set; } + public Func?>>? OutcomeGenerator { get; set; } /// /// Gets or sets the outcome to be injected for a given execution. @@ -32,5 +32,5 @@ public class OutcomeStrategyOptions : MonkeyStrategyOptions /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public Outcome Outcome { get; set; } + public Outcome? Outcome { get; set; } } diff --git a/src/Polly.Core/Simmy/Utils/GuardExtensions.cs b/src/Polly.Core/Simmy/Utils/GuardExtensions.cs deleted file mode 100644 index f9836ea1221..00000000000 --- a/src/Polly.Core/Simmy/Utils/GuardExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Polly.Simmy.Utils; - -internal static class GuardExtensions -{ - public static void EnsureInjectionThreshold(this double injectionThreshold) - { - if (injectionThreshold < MonkeyStrategyConstants.MinInjectionThreshold) - { - throw new ArgumentOutOfRangeException(nameof(injectionThreshold), "Injection rate/threshold in Monkey strategies should always be a double between [0, 1]; never a negative number."); - } - - if (injectionThreshold > MonkeyStrategyConstants.MaxInjectionThreshold) - { - throw new ArgumentOutOfRangeException(nameof(injectionThreshold), "Injection rate/threshold in Monkey strategies should always be a double between [0, 1]; never a number greater than 1."); - } - } -} diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorActionArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorActionArgumentsTests.cs new file mode 100644 index 00000000000..d357abd4377 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorActionArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Behavior; + +namespace Polly.Core.Tests.Simmy.Behavior; + +public class BehaviorActionArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new BehaviorActionArguments(ResilienceContextPool.Shared.Get()); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs index a6b4f76b534..8315dc52f9e 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using Polly.Simmy; using Polly.Simmy.Behavior; namespace Polly.Core.Tests.Simmy.Behavior; @@ -15,9 +14,9 @@ public static IEnumerable AddBehavior_Ok_Data() (CompositeStrategyBuilder builder) => { builder.AddBehavior(true, 0.5, behavior); }, (BehaviorChaosStrategy strategy) => { - strategy.Behavior.Invoke(context).Preserve().GetAwaiter().IsCompleted.Should().BeTrue(); - strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().BeTrue(); - strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(0.5); + strategy.Behavior!.Invoke(new(context)).Preserve().GetAwaiter().IsCompleted.Should().BeTrue(); + strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().BeTrue(); + strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(0.5); } }; } diff --git a/test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests - Copy.cs similarity index 100% rename from test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests.cs rename to test/Polly.Core.Tests/Simmy/Behavior/OnBehaviorInjectedArgumentsTests - Copy.cs diff --git a/test/Polly.Core.Tests/Simmy/EnabledGeneratorArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/EnabledGeneratorArgumentsTests.cs new file mode 100644 index 00000000000..78825a85ba1 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/EnabledGeneratorArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class EnabledGeneratorArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new EnabledGeneratorArguments(ResilienceContextPool.Shared.Get()); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/InjectionRateGeneratorArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/InjectionRateGeneratorArgumentsTests.cs new file mode 100644 index 00000000000..e5202f2115a --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/InjectionRateGeneratorArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class InjectionRateGeneratorArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new InjectionRateGeneratorArguments(ResilienceContextPool.Shared.Get()); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs index 4bd1158d1b2..125d36a0acf 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs @@ -1,6 +1,4 @@ -using Polly.Simmy; - -namespace Polly.Core.Tests.Simmy; +namespace Polly.Core.Tests.Simmy; #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. @@ -45,30 +43,29 @@ public async Task Ctor_Ok() var sut = CreateSut(); sut.EnabledGenerator.Should().NotBeNull(); - (await sut.EnabledGenerator(new EnabledGeneratorArguments { Context = context })).Should().BeTrue(); + (await sut.EnabledGenerator(new(context))).Should().BeTrue(); sut.InjectionRateGenerator.Should().NotBeNull(); - (await sut.InjectionRateGenerator(new InjectionRateGeneratorArguments { Context = context })).Should().Be(0.5); + (await sut.InjectionRateGenerator(new(context))).Should().Be(0.5); } - [InlineData(-1)] - [InlineData(1.1)] + [InlineData(-1, false)] + [InlineData(1.1, true)] [Theory] - public async Task Should_throw_error_when_injection_rate_generator_result_is_not_valid(double injectionRate) + public async Task Should_coerce_injection_rate_generator_result_is_not_valid(double injectionRateGeneratorResult, bool shouldBeInjected) { var wasMonkeyUnleashed = false; _options.EnabledGenerator = (_) => new ValueTask(true); - _options.InjectionRateGenerator = (_) => new ValueTask(injectionRate); + _options.InjectionRateGenerator = (_) => new ValueTask(injectionRateGeneratorResult); _options.Randomizer = () => 0.5; var sut = CreateSut(); + sut.OnExecute = (_, _) => { wasMonkeyUnleashed = true; return Task.CompletedTask; }; - await sut.Invoking(s => s.ExecuteAsync((_) => { return default; }).AsTask()) - .Should() - .ThrowAsync(); + await sut.ExecuteAsync((_) => { return default; }); - wasMonkeyUnleashed.Should().BeFalse(); + wasMonkeyUnleashed.Should().Be(shouldBeInjected); } [Fact] diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 28b5d8f1f5b..b2f7106cbfc 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -12,15 +12,34 @@ public class OutcomeChaosStrategyTests public OutcomeChaosStrategyTests() => _telemetry = TestUtilities.CreateResilienceTelemetry(_diagnosticSource.Object); public static List FaultCtorTestCases => - new() - { - new object[] { null!, "Value cannot be null. (Parameter 'options')" }, - new object[] { new OutcomeStrategyOptions - { - InjectionRate = 1, - Enabled = true, - }, "Either Outcome or OutcomeGenerator is required. (Parameter 'Outcome')" }, - }; + new() + { + new object[] { null!, "Value cannot be null. (Parameter 'options')" }, + new object[] + { + new OutcomeStrategyOptions + { + InjectionRate = 1, + Enabled = true, + }, + "Either Outcome or OutcomeGenerator is required. (Parameter 'Outcome')" + }, + }; + + public static List ResultCtorTestCases => + new() + { + new object[] { null!, "Value cannot be null. (Parameter 'options')" }, + new object[] + { + new OutcomeStrategyOptions + { + InjectionRate = 1, + Enabled = true, + }, + "Either Outcome or OutcomeGenerator is required. (Parameter 'Outcome')" + }, + }; [Theory] [MemberData(nameof(FaultCtorTestCases))] @@ -31,6 +50,25 @@ public void FaultInvalidCtor(OutcomeStrategyOptions options, string m var _ = new OutcomeChaosStrategy(options, _telemetry); }; +#if NET481 +act.Should() + .Throw(); +#else + act.Should() + .Throw() + .WithMessage(message); +#endif + } + + [Theory] + [MemberData(nameof(ResultCtorTestCases))] + public void ResultInvalidCtor(OutcomeStrategyOptions options, string message) + { + Action act = () => + { + var _ = new OutcomeChaosStrategy(options, _telemetry); + }; + #if NET481 act.Should() .Throw(); diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs index 21631a4477c..d583bfc658a 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using Polly.Simmy; using Polly.Simmy.Outcomes; namespace Polly.Core.Tests.Simmy.Outcomes; @@ -58,10 +57,10 @@ private static void AssertResultStrategy(CompositeStrategyBuilder builder, { var context = ResilienceContextPool.Shared.Get(); var strategy = (OutcomeChaosStrategy)builder.Build().Strategy; - strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(enabled); - strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.OutcomeGenerator.Should().NotBeNull(); - strategy.OutcomeGenerator!.Invoke(context).Preserve().GetAwaiter().GetResult().Should().Be(outcome); + strategy.OutcomeGenerator!.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(outcome); strategy.Outcome.Should().Be(outcome); } @@ -70,8 +69,8 @@ private static void AssertFaultStrategy(CompositeStrategyBuilder< { var context = ResilienceContextPool.Shared.Get(); var strategy = (OutcomeChaosStrategy)builder.Build().Strategy; - strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(enabled); - strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.FaultGenerator.Should().NotBeNull(); strategy.Fault.Should().BeOfType(typeof(Outcome)); strategy.Fault.Should().NotBeNull(); @@ -94,8 +93,8 @@ private static void AssertFaultStrategy(CompositeStrategyBuilder bui { var context = ResilienceContextPool.Shared.Get(); var strategy = (OutcomeChaosStrategy)builder.Build(); - strategy.EnabledGenerator.Invoke(new EnabledGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(enabled); - strategy.InjectionRateGenerator.Invoke(new InjectionRateGeneratorArguments { Context = context }).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); + strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); + strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.FaultGenerator.Should().NotBeNull(); strategy.Fault.Should().BeOfType(typeof(Outcome)); strategy.Fault.Should().NotBeNull(); diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeGeneratorArgumentsTests.cs similarity index 51% rename from test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs rename to test/Polly.Core.Tests/Simmy/Outcomes/OutcomeGeneratorArgumentsTests.cs index f847c89fac2..0f8873eceae 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeGeneratorArgumentsTests.cs @@ -2,12 +2,12 @@ namespace Polly.Core.Tests.Simmy.Outcomes; -public class OnOutcomeInjectedArgumentsTests +public class OutcomeGeneratorArgumentsTests { [Fact] public void Ctor_Ok() { - var args = new OnOutcomeInjectedArguments(ResilienceContextPool.Shared.Get(), new Outcome(1)); + var args = new OutcomeGeneratorArguments(ResilienceContextPool.Shared.Get()); args.Context.Should().NotBeNull(); } } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs index 3d2d6d001a7..d2442bedd6f 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeStrategyOptionsTests.cs @@ -14,7 +14,7 @@ public void Ctor_Ok() sut.EnabledGenerator.Should().BeNull(); sut.InjectionRate.Should().Be(MonkeyStrategyConstants.DefaultInjectionRate); sut.InjectionRateGenerator.Should().BeNull(); - sut.Outcome.Should().Be(default(Outcome)); + sut.Outcome.Should().BeNull(); sut.OnOutcomeInjected.Should().BeNull(); sut.OutcomeGenerator.Should().BeNull(); } From f3a62bb4a45a0e569a7b99214957a4a7fe3c2048 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 19 Aug 2023 19:22:51 -0500 Subject: [PATCH 23/36] update api file --- src/Polly.Core/PublicAPI.Unshipped.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index cb790567d56..fff79f535d4 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -397,7 +397,7 @@ Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeStrategyOptions() -> Polly.Simmy.ReactiveMonkeyStrategy Polly.Simmy.ReactiveMonkeyStrategy.EnabledGenerator.get -> System.Func>! Polly.Simmy.ReactiveMonkeyStrategy.InjectionRateGenerator.get -> System.Func>! -Polly.Simmy.ReactiveMonkeyStrategy.ReactiveMonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void +Polly.Simmy.ReactiveMonkeyStrategy.ReactiveMonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void Polly.Simmy.ReactiveMonkeyStrategy.ShouldInjectAsync(Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask Polly.StrategyBuilderContext Polly.StrategyBuilderContext.BuilderInstanceName.get -> string? From b7c9b3acdd60bad06880f9cdc9bbb367c27c0ab0 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 20 Aug 2023 13:10:05 -0500 Subject: [PATCH 24/36] wrapping up --- src/Polly.Core/PublicAPI.Unshipped.txt | 38 ++++---- ...BehaviorChaosPipelineBuilderExtensions.cs} | 8 +- .../Simmy/Behavior/BehaviorChaosStrategy.cs | 4 +- ... LatencyChaosPipelineBuilderExtensions.cs} | 8 +- .../Simmy/Latency/LatencyChaosStrategy.cs | 13 +-- .../Simmy/Latency/LatencyConstants.cs | 2 +- .../Outcomes/OnOutcomeInjectedArguments.cs | 20 ++++ ...ChaosPipelineBuilderExtensions.TResult.cs} | 14 +-- ... OutcomeChaosPipelineBuilderExtensions.cs} | 8 +- .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 14 ++- .../Outcomes/OutcomeGeneratorArguments.cs | 2 +- .../OutcomeStrategyOptions.TResult.cs | 2 +- ...iorChaosPipelineBuilderExtensionsTests.cs} | 12 +-- ...encyChaosPipelineBuilderExtensionsTests.cs | 56 ++++++++++++ .../Latency/LatencyChaosStrategyTests.cs | 91 +++++++++++++++++-- .../Simmy/Latency/LatencyConstantsTests.cs | 13 +++ .../Latency/LatencyGeneratorArgumentsTests.cs | 13 +++ .../Latency/LatencyStrategyOptionsTests.cs | 21 +++++ .../Simmy/Latency/OnLatencyArgumentsTests.cs | 14 +++ .../OnOutcomeInjectedArgumentsTests.cs | 13 +++ ...omeChaosPipelineBuilderExtensionsTests.cs} | 20 ++-- .../Outcomes/OutcomeChaosStrategyTests.cs | 33 ++++++- 22 files changed, 336 insertions(+), 83 deletions(-) rename src/Polly.Core/Simmy/Behavior/{BehaviorCompositeStrategyBuilderExtensions.cs => BehaviorChaosPipelineBuilderExtensions.cs} (86%) rename src/Polly.Core/Simmy/Latency/{LatencyCompositeStrategyBuilderExtensions.cs => LatencyChaosPipelineBuilderExtensions.cs} (87%) create mode 100644 src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs rename src/Polly.Core/Simmy/Outcomes/{OutcomeCompositeStrategyBuilderExtensions.TResult.cs => OutcomeChaosPipelineBuilderExtensions.TResult.cs} (87%) rename src/Polly.Core/Simmy/Outcomes/{OutcomeCompositeStrategyBuilderExtensions.cs => OutcomeChaosPipelineBuilderExtensions.cs} (87%) rename test/Polly.Core.Tests/Simmy/Behavior/{BehaviorCompositeBuilderExtensionsTests.cs => BehaviorChaosPipelineBuilderExtensionsTests.cs} (83%) create mode 100644 test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Latency/LatencyGeneratorArgumentsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Latency/OnLatencyArgumentsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs rename test/Polly.Core.Tests/Simmy/Outcomes/{OutcomeCompositeBuilderExtensionsTests.cs => OutcomeChaosPipelineBuilderExtensionsTests.cs} (90%) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index a94956f0a54..2cb8243bf76 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -315,7 +315,7 @@ Polly.Simmy.Behavior.BehaviorActionArguments Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments() -> void Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments(Polly.ResilienceContext! context) -> void Polly.Simmy.Behavior.BehaviorActionArguments.Context.get -> Polly.ResilienceContext! -Polly.Simmy.Behavior.BehaviorResiliencePipelineBuilderExtensions +Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions Polly.Simmy.Behavior.BehaviorStrategyOptions Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.get -> System.Func? Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.set -> void @@ -334,11 +334,11 @@ Polly.Simmy.InjectionRateGeneratorArguments Polly.Simmy.InjectionRateGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.InjectionRateGeneratorArguments.InjectionRateGeneratorArguments() -> void Polly.Simmy.InjectionRateGeneratorArguments.InjectionRateGeneratorArguments(Polly.ResilienceContext! context) -> void +Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions Polly.Simmy.Latency.LatencyGeneratorArguments Polly.Simmy.Latency.LatencyGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Latency.LatencyGeneratorArguments.LatencyGeneratorArguments() -> void Polly.Simmy.Latency.LatencyGeneratorArguments.LatencyGeneratorArguments(Polly.ResilienceContext! context) -> void -Polly.Simmy.Latency.LatencyResiliencePipelineBuilderExtensions Polly.Simmy.Latency.LatencyStrategyOptions Polly.Simmy.Latency.LatencyStrategyOptions.Latency.get -> System.TimeSpan Polly.Simmy.Latency.LatencyStrategyOptions.Latency.set -> void @@ -376,15 +376,19 @@ Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.set -> void Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions.Randomizer.get -> System.Func! Polly.Simmy.MonkeyStrategyOptions.Randomizer.set -> void +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.get -> Polly.ResilienceContext! +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments() -> void +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments(Polly.ResilienceContext! context) -> void +Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions 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.OutcomeResiliencePipelineBuilderExtensions Polly.Simmy.Outcomes.OutcomeStrategyOptions Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeStrategyOptions() -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions -Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? +Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.set -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.get -> Polly.Outcome? Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.set -> void @@ -484,19 +488,19 @@ static Polly.ResiliencePipelineBuilderExtensions.AddStrategy(this TBui static Polly.ResiliencePipelineBuilderExtensions.AddStrategy(this Polly.ResiliencePipelineBuilder! builder, System.Func!>! factory, Polly.ResilienceStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Behavior.BehaviorResiliencePipelineBuilderExtensions.AddBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! -static Polly.Simmy.Behavior.BehaviorResiliencePipelineBuilderExtensions.AddBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! -static Polly.Simmy.Latency.LatencyResiliencePipelineBuilderExtensions.AddLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! -static Polly.Simmy.Latency.LatencyResiliencePipelineBuilderExtensions.AddLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeResiliencePipelineBuilderExtensions.AddResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! +static Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! +static Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! +static Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.TimeoutResiliencePipelineBuilderExtensions.AddTimeout(this TBuilder! builder, Polly.Timeout.TimeoutStrategyOptions! options) -> TBuilder! static Polly.TimeoutResiliencePipelineBuilderExtensions.AddTimeout(this TBuilder! builder, System.TimeSpan timeout) -> TBuilder! static readonly Polly.ResiliencePipeline.Null -> Polly.ResiliencePipeline! diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorCompositeStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs similarity index 86% rename from src/Polly.Core/Simmy/Behavior/BehaviorCompositeStrategyBuilderExtensions.cs rename to src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs index 9ce5c1e882b..e5fe8bedb6a 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorCompositeStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs @@ -6,7 +6,7 @@ namespace Polly.Simmy.Behavior; /// /// Extension methods for adding custom behaviors to a . /// -public static class BehaviorResiliencePipelineBuilderExtensions +public static class BehaviorChaosPipelineBuilderExtensions { /// @@ -20,12 +20,12 @@ public static class BehaviorResiliencePipelineBuilderExtensions /// The same builder instance. /// Thrown when is . /// Thrown when the options produced from the arguments are invalid. - public static TBuilder AddBehavior(this TBuilder builder, bool enabled, double injectionRate, Func behavior) + public static TBuilder AddChaosBehavior(this TBuilder builder, bool enabled, double injectionRate, Func behavior) where TBuilder : ResiliencePipelineBuilderBase { Guard.NotNull(builder); - return builder.AddBehavior(new BehaviorStrategyOptions + return builder.AddChaosBehavior(new BehaviorStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, @@ -46,7 +46,7 @@ public static TBuilder AddBehavior(this TBuilder builder, bool enabled "Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "All options members preserved.")] - public static TBuilder AddBehavior(this TBuilder builder, BehaviorStrategyOptions options) + public static TBuilder AddChaosBehavior(this TBuilder builder, BehaviorStrategyOptions options) where TBuilder : ResiliencePipelineBuilderBase { Guard.NotNull(builder); diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs index 233393b8484..30ada086e78 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs @@ -13,12 +13,12 @@ public BehaviorChaosStrategy( { _telemetry = telemetry; OnBehaviorInjected = options.OnBehaviorInjected; - Behavior = options.BehaviorAction; + Behavior = options.BehaviorAction!; } public Func? OnBehaviorInjected { get; } - public Func? Behavior { get; } + public Func Behavior { get; } protected internal override async ValueTask> ExecuteCore( Func>> callback, diff --git a/src/Polly.Core/Simmy/Latency/LatencyCompositeStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs similarity index 87% rename from src/Polly.Core/Simmy/Latency/LatencyCompositeStrategyBuilderExtensions.cs rename to src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs index 70f767d2544..54f3ac73a10 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyCompositeStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs @@ -6,7 +6,7 @@ namespace Polly.Simmy.Latency; /// /// Extension methods for adding latency to a . /// -public static class LatencyResiliencePipelineBuilderExtensions +public static class LatencyChaosPipelineBuilderExtensions { /// /// Adds a latency chaos strategy to the builder. @@ -19,12 +19,12 @@ public static class LatencyResiliencePipelineBuilderExtensions /// The same builder instance. /// Thrown when is . /// Thrown when the options produced from the arguments are invalid. - public static TBuilder AddLatency(this TBuilder builder, bool enabled, double injectionRate, TimeSpan delay) + public static TBuilder AddChaosLatency(this TBuilder builder, bool enabled, double injectionRate, TimeSpan delay) where TBuilder : ResiliencePipelineBuilderBase { Guard.NotNull(builder); - return builder.AddLatency(new LatencyStrategyOptions + return builder.AddChaosLatency(new LatencyStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, @@ -45,7 +45,7 @@ public static TBuilder AddLatency(this TBuilder builder, bool enabled, "Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "All options members preserved.")] - public static TBuilder AddLatency(this TBuilder builder, LatencyStrategyOptions options) + public static TBuilder AddChaosLatency(this TBuilder builder, LatencyStrategyOptions options) where TBuilder : ResiliencePipelineBuilderBase { Guard.NotNull(builder); diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index 66234f07218..544ce9f461d 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -17,13 +17,13 @@ public LatencyChaosStrategy( { Latency = options.Latency; LatencyGenerator = options.LatencyGenerator is not null ? options.LatencyGenerator : (_) => new(options.Latency); - OnDelayed = options.OnLatency; + OnLatency = options.OnLatency; _telemetry = telemetry; _timeProvider = timeProvider; } - public Func? OnDelayed { get; } + public Func? OnLatency { get; } public Func> LatencyGenerator { get; } @@ -39,14 +39,15 @@ protected internal override async ValueTask> ExecuteCore +/// Arguments used by the outcome chaos strategy to notify that an outcome was injected. +/// +public readonly struct OnOutcomeInjectedArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + public OnOutcomeInjectedArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. + /// + public ResilienceContext Context { get; } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs similarity index 87% rename from src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs rename to src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs index b99c662246e..3ab6384b027 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs @@ -5,7 +5,7 @@ namespace Polly.Simmy.Outcomes; /// /// Extension methods for adding outcome to a . /// -public static partial class OutcomeResiliencePipelineBuilderExtensions +public static partial class OutcomeChaosPipelineBuilderExtensions { /// /// Adds a fault chaos strategy to the builder. @@ -16,7 +16,7 @@ public static partial class OutcomeResiliencePipelineBuilderExtensions /// The injection rate for a given execution, which the value should be between [0, 1]. /// The exception to inject. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddFault(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception fault) + public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception fault) { Guard.NotNull(builder); @@ -38,7 +38,7 @@ public static ResiliencePipelineBuilder AddFault(this Resilien /// The injection rate for a given execution, which the value should be between [0, 1]. /// The exception generator delegate. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddFault( + public static ResiliencePipelineBuilder AddChaosFault( this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func?>> faultGenerator) { Guard.NotNull(builder); @@ -59,7 +59,7 @@ public static ResiliencePipelineBuilder AddFault( /// The builder instance. /// The fault strategy options. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddFault(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) + public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) { Guard.NotNull(builder); Guard.NotNull(options); @@ -77,7 +77,7 @@ public static ResiliencePipelineBuilder AddFault(this Resilien /// The injection rate for a given execution, which the value should be between [0, 1]. /// The outcome to inject. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddResult(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, TResult result) + public static ResiliencePipelineBuilder AddChaosResult(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, TResult result) { Guard.NotNull(builder); @@ -99,7 +99,7 @@ public static ResiliencePipelineBuilder AddResult(this Resilie /// The injection rate for a given execution, which the value should be between [0, 1]. /// The outcome generator delegate. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddResult( + public static ResiliencePipelineBuilder AddChaosResult( this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func?>> outcomeGenerator) { Guard.NotNull(builder); @@ -120,7 +120,7 @@ public static ResiliencePipelineBuilder AddResult( /// The builder instance. /// The outcome strategy options. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddResult(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) + public static ResiliencePipelineBuilder AddChaosResult(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) { Guard.NotNull(builder); Guard.NotNull(options); diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs similarity index 87% rename from src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs rename to src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs index 2d6d3306f1a..25d7c463e97 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeCompositeStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs @@ -5,7 +5,7 @@ namespace Polly.Simmy.Outcomes; /// /// Extension methods for adding outcome to a . /// -public static partial class OutcomeResiliencePipelineBuilderExtensions +public static partial class OutcomeChaosPipelineBuilderExtensions { /// /// Adds a fault chaos strategy to the builder. @@ -15,7 +15,7 @@ public static partial class OutcomeResiliencePipelineBuilderExtensions /// The injection rate for a given execution, which the value should be between [0, 1]. /// The exception to inject. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddFault(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception fault) + public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception fault) { Guard.NotNull(builder); @@ -36,7 +36,7 @@ public static ResiliencePipelineBuilder AddFault(this ResiliencePipelineBuilder /// The injection rate for a given execution, which the value should be between [0, 1]. /// The exception generator delegate. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddFault( + public static ResiliencePipelineBuilder AddChaosFault( this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func?>> faultGenerator) { Guard.NotNull(builder); @@ -56,7 +56,7 @@ public static ResiliencePipelineBuilder AddFault( /// The builder instance. /// The fault strategy options. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddFault(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) + public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) { Guard.NotNull(builder); Guard.NotNull(options); diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index 8e4081367df..be28f8f5ee0 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -36,9 +36,9 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg OutcomeGenerator = options.OutcomeGenerator is not null ? options.OutcomeGenerator : (_) => new(options.Outcome); } - public Func, ValueTask>? OnOutcomeInjected { get; } + public Func, ValueTask>? OnOutcomeInjected { get; } - public Func, ValueTask>? OnFaultInjected { get; } + public Func, ValueTask>? OnFaultInjected { get; } public Func?>>? OutcomeGenerator { get; } @@ -86,9 +86,8 @@ protected internal override async ValueTask> ExecuteCore(Func private async ValueTask?> InjectOutcome(ResilienceContext context) { - var outcomeGeneratorArgs = new OutcomeGeneratorArguments(context); - var outcome = await OutcomeGenerator!(outcomeGeneratorArgs).ConfigureAwait(context.ContinueOnCapturedContext); - var args = new OutcomeArguments(context, outcome.Value, outcomeGeneratorArgs); + var outcome = await OutcomeGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); + var args = new OutcomeArguments(context, outcome.Value, new(context)); _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); if (OnOutcomeInjected is not null) @@ -103,8 +102,7 @@ protected internal override async ValueTask> ExecuteCore(Func { try { - var outcomeGeneratorArgs = new OutcomeGeneratorArguments(context); - var fault = await FaultGenerator!(outcomeGeneratorArgs).ConfigureAwait(context.ContinueOnCapturedContext); + var fault = await FaultGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); if (!fault.HasValue) { return null; @@ -114,7 +112,7 @@ protected internal override async ValueTask> ExecuteCore(Func context.CancellationToken.ThrowIfCancellationRequested(); Outcome = new(fault.Value.Exception!); - var args = new OutcomeArguments(context, new Outcome(fault.Value.Exception!), outcomeGeneratorArgs); + var args = new OutcomeArguments(context, new Outcome(fault.Value.Exception!), new(context)); _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); if (OnFaultInjected is not null) diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs index d3ca3322316..28a07d74f4a 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeGeneratorArguments.cs @@ -3,7 +3,7 @@ #pragma warning disable CA1815 // Override equals and operator equals on value types /// -/// Arguments used by the outcome chaos strategy to notify that an outcome was injected. +/// Arguments used by the outcome chaos strategy to ge the outcome that is going to be injected. /// public readonly struct OutcomeGeneratorArguments { diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs index 09bdcc512bd..14035f4cf94 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs @@ -14,7 +14,7 @@ public class OutcomeStrategyOptions : MonkeyStrategyOptions /// /// Defaults to . /// - public Func, ValueTask>? OnOutcomeInjected { get; set; } + public Func, ValueTask>? OnOutcomeInjected { get; set; } /// /// Gets or sets the outcome generator to be injected for a given execution. diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensionsTests.cs similarity index 83% rename from test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs rename to test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensionsTests.cs index 0a8d8acfc31..724e6bd58e1 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorCompositeBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensionsTests.cs @@ -4,7 +4,7 @@ namespace Polly.Core.Tests.Simmy.Behavior; -public class BehaviorCompositeBuilderExtensionsTests +public class BehaviorChaosPipelineBuilderExtensionsTests { public static IEnumerable AddBehavior_Ok_Data() { @@ -12,7 +12,7 @@ public static IEnumerable AddBehavior_Ok_Data() Func behavior = () => new ValueTask(Task.CompletedTask); yield return new object[] { - (ResiliencePipelineBuilder builder) => { builder.AddBehavior(true, 0.5, behavior); }, + (ResiliencePipelineBuilder builder) => { builder.AddChaosBehavior(true, 0.5, behavior); }, (BehaviorChaosStrategy strategy) => { strategy.Behavior!.Invoke(new(context)).Preserve().GetAwaiter().IsCompleted.Should().BeTrue(); @@ -25,7 +25,7 @@ public static IEnumerable AddBehavior_Ok_Data() [Fact] public void AddBehavior_Shortcut_Option_Ok() { - var sut = new ResiliencePipelineBuilder().AddBehavior(true, 0.5, () => new ValueTask(Task.CompletedTask)).Build(); + var sut = new ResiliencePipelineBuilder().AddChaosBehavior(true, 0.5, () => new ValueTask(Task.CompletedTask)).Build(); sut.GetPipelineDescriptor().FirstStrategy.StrategyInstance.Should().BeOfType(); } @@ -33,7 +33,7 @@ public void AddBehavior_Shortcut_Option_Ok() public void AddBehavior_Shortcut_Option_Throws() { new ResiliencePipelineBuilder() - .Invoking(b => b.AddBehavior(true, -1, () => new ValueTask(Task.CompletedTask))) + .Invoking(b => b.AddChaosBehavior(true, -1, () => new ValueTask(Task.CompletedTask))) .Should() .Throw(); } @@ -42,7 +42,7 @@ public void AddBehavior_Shortcut_Option_Throws() public void AddBehavior_InvalidOptions_Throws() { new ResiliencePipelineBuilder() - .Invoking(b => b.AddBehavior(new BehaviorStrategyOptions())) + .Invoking(b => b.AddChaosBehavior(new BehaviorStrategyOptions())) .Should() .Throw(); } @@ -51,7 +51,7 @@ public void AddBehavior_InvalidOptions_Throws() public void AddBehavior_Options_Ok() { var sut = new ResiliencePipelineBuilder() - .AddBehavior(new BehaviorStrategyOptions + .AddChaosBehavior(new BehaviorStrategyOptions { Enabled = true, InjectionRate = 1, diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs new file mode 100644 index 00000000000..28de4a62073 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs @@ -0,0 +1,56 @@ +using Polly.Simmy.Latency; +using Polly.Testing; + +namespace Polly.Core.Tests.Simmy.Latency; + +public class LatencyChaosPipelineBuilderExtensionsTests +{ + public static IEnumerable AddLatency_Ok_Data() + { + var context = ResilienceContextPool.Shared.Get(); + Func behavior = () => new ValueTask(Task.CompletedTask); + yield return new object[] + { + (ResiliencePipelineBuilder builder) => { builder.AddChaosLatency(true, 0.5, TimeSpan.FromSeconds(10)); }, + (LatencyChaosStrategy strategy) => + { + strategy.LatencyGenerator!.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(TimeSpan.FromSeconds(10)); + strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().BeTrue(); + strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(0.5); + } + }; + } + + [Fact] + public void AddLatency_Shortcut_Option_Ok() + { + var sut = new ResiliencePipelineBuilder().AddChaosLatency(true, 0.5, TimeSpan.FromSeconds(10)).Build(); + sut.GetPipelineDescriptor().FirstStrategy.StrategyInstance.Should().BeOfType(); + } + + [Fact] + public void AddLatency_Options_Ok() + { + var sut = new ResiliencePipelineBuilder() + .AddChaosLatency(new LatencyStrategyOptions + { + Enabled = true, + InjectionRate = 1, + LatencyGenerator = (_) => new ValueTask(TimeSpan.FromSeconds(30)) + }) + .Build(); + + sut.GetPipelineDescriptor().FirstStrategy.StrategyInstance.Should().BeOfType(); + } + + [MemberData(nameof(AddLatency_Ok_Data))] + [Theory] + internal void AddLatency_Generic_Options_Ok(Action> configure, Action assert) + { + var builder = new ResiliencePipelineBuilder(); + configure(builder); + + var strategy = builder.Build().GetPipelineDescriptor().FirstStrategy.StrategyInstance.Should().BeOfType().Subject; + assert(strategy); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs index 5541ae57df6..7593e7d75cf 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs @@ -23,26 +23,101 @@ public LatencyChaosStrategyTests() public void Dispose() => _cancellationSource.Dispose(); [Fact] - public async Task InjectLatency_Context_Free_Should_Introduce_Delay_If_Enabled() + public async Task Given_enabled_and_randomly_within_threshold_should_inject_latency() { - var executed = false; + var userDelegateExecuted = false; + var onLatencyExecuted = false; _options.InjectionRate = 0.6; _options.Enabled = true; _options.Latency = _delay; _options.Randomizer = () => 0.5; + _options.OnLatency = args => + { + args.Context.Should().NotBeNull(); + args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); + onLatencyExecuted = true; + return default; + }; + var before = _timeProvider.GetUtcNow(); var sut = CreateSut(); + var task = sut.ExecuteAsync(async _ => { userDelegateExecuted = true; await Task.CompletedTask; }); + _timeProvider.Advance(_delay); + await task; - //var before = _timeProvider.GetUtcNow(); - //_timeProvider.Advance(_delay); + userDelegateExecuted.Should().BeTrue(); + var after = _timeProvider.GetUtcNow(); + (after - before).Should().Be(_delay); - //await sut.ExecuteAsync(async _ => { executed = true; await Task.CompletedTask; }); + _args.Should().HaveCount(1); + _args[0].Arguments.Should().BeOfType(); + onLatencyExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Given_not_enabled_should_not_inject_latency() + { + var userDelegateExecuted = false; + + _options.InjectionRate = 0.6; + _options.Enabled = false; + _options.Latency = _delay; + _options.Randomizer = () => 0.5; + + var before = _timeProvider.GetUtcNow(); + var sut = CreateSut(); + var task = sut.ExecuteAsync(async _ => { userDelegateExecuted = true; await Task.CompletedTask; }); + _timeProvider.Advance(_delay); + await task; + + userDelegateExecuted.Should().BeTrue(); + var after = _timeProvider.GetUtcNow(); + (after - before).Seconds.Should().Be(0); + } + + [Fact] + public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inject_behaviour() + { + var userDelegateExecuted = false; - executed.Should().BeFalse(); + _options.InjectionRate = 0.4; + _options.Enabled = false; + _options.Latency = _delay; + _options.Randomizer = () => 0.5; + + var before = _timeProvider.GetUtcNow(); + var sut = CreateSut(); + var task = sut.ExecuteAsync(async _ => { userDelegateExecuted = true; await Task.CompletedTask; }); + _timeProvider.Advance(_delay); + await task; + + userDelegateExecuted.Should().BeTrue(); + var after = _timeProvider.GetUtcNow(); + (after - before).Seconds.Should().Be(0); + } + + [Fact] + public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running_the_strategy() + { + var userDelegateExecuted = false; + + using var cts = new CancellationTokenSource(); + _options.InjectionRate = 0.6; + _options.Enabled = true; + _options.Randomizer = () => 0.5; + _options.LatencyGenerator = (_) => + { + cts.Cancel(); + return new ValueTask(_delay); + }; + + var sut = CreateSut(); + await sut.Invoking(s => s.ExecuteAsync(async _ => { await Task.CompletedTask; }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); - //var after = _timeProvider.GetUtcNow(); - //(after - before).Should().BeGreaterThanOrEqualTo(_delay); + userDelegateExecuted.Should().BeFalse(); } private ResiliencePipeline CreateSut() => new LatencyChaosStrategy(_options, _timeProvider, _telemetry).AsPipeline(); diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs new file mode 100644 index 00000000000..1d27e28bf8f --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyConstantsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Latency; + +namespace Polly.Core.Tests.Simmy.Latency; + +public class LatencyConstantsTests +{ + [Fact] + public void EnsureDefaults() + { + LatencyConstants.OnLatencyEvent.Should().Be("OnLatency"); + LatencyConstants.DefaultLatency.Should().Be(TimeSpan.FromSeconds(30)); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyGeneratorArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyGeneratorArgumentsTests.cs new file mode 100644 index 00000000000..4220a57459a --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyGeneratorArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Latency; + +namespace Polly.Core.Tests.Simmy.Latency; + +public class LatencyGeneratorArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new LatencyGeneratorArguments(ResilienceContextPool.Shared.Get()); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs new file mode 100644 index 00000000000..571b5c2df7d --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyStrategyOptionsTests.cs @@ -0,0 +1,21 @@ +using Polly.Simmy; +using Polly.Simmy.Latency; + +namespace Polly.Core.Tests.Simmy.Latency; + +public class LatencyStrategyOptionsTests +{ + [Fact] + public void Ctor_Ok() + { + var sut = new LatencyStrategyOptions(); + sut.Randomizer.Should().NotBeNull(); + sut.Enabled.Should().BeFalse(); + sut.EnabledGenerator.Should().BeNull(); + sut.InjectionRate.Should().Be(MonkeyStrategyConstants.DefaultInjectionRate); + sut.InjectionRateGenerator.Should().BeNull(); + sut.Latency.Should().Be(LatencyConstants.DefaultLatency); + sut.LatencyGenerator.Should().BeNull(); + sut.OnLatency.Should().BeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Latency/OnLatencyArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/OnLatencyArgumentsTests.cs new file mode 100644 index 00000000000..5c834634e5f --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Latency/OnLatencyArgumentsTests.cs @@ -0,0 +1,14 @@ +using Polly.Simmy.Latency; + +namespace Polly.Core.Tests.Simmy.Latency; + +public class OnLatencyArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new OnLatencyArguments(ResilienceContextPool.Shared.Get(), TimeSpan.FromSeconds(10)); + args.Context.Should().NotBeNull(); + args.Latency.Should().Be(TimeSpan.FromSeconds(10)); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs new file mode 100644 index 00000000000..1a3b770b3dd --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class OnOutcomeInjectedArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new OnOutcomeInjectedArguments(ResilienceContextPool.Shared.Get()); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs similarity index 90% rename from test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs rename to test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs index 4c4d6964f87..204326f0da4 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeCompositeBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs @@ -4,13 +4,13 @@ namespace Polly.Core.Tests.Simmy.Outcomes; -public class OutcomeCompositeBuilderExtensionsTests +public class OutcomeChaosPipelineBuilderExtensionsTests { public static readonly TheoryData>> ResultStrategy = new() { builder => { - builder.AddResult(new OutcomeStrategyOptions + builder.AddChaosResult(new OutcomeStrategyOptions { InjectionRate = 0.6, Enabled = true, @@ -26,7 +26,7 @@ public class OutcomeCompositeBuilderExtensionsTests { builder => { - builder.AddFault(new OutcomeStrategyOptions + builder.AddChaosFault(new OutcomeStrategyOptions { InjectionRate = 0.6, Enabled = true, @@ -42,7 +42,7 @@ public class OutcomeCompositeBuilderExtensionsTests { builder => { - builder.AddFault(new OutcomeStrategyOptions + builder.AddChaosFault(new OutcomeStrategyOptions { InjectionRate = 0.6, Enabled = true, @@ -145,7 +145,7 @@ public void AddResult_Shortcut_Option_Ok() { var builder = new ResiliencePipelineBuilder(); builder - .AddResult(true, 0.5, 120) + .AddChaosResult(true, 0.5, 120) .Build(); AssertResultStrategy(builder, true, 0.5, new(120)); @@ -155,7 +155,7 @@ public void AddResult_Shortcut_Option_Ok() public void AddResult_Shortcut_Option_Throws() { new ResiliencePipelineBuilder() - .Invoking(b => b.AddResult(true, -1, () => new ValueTask?>(new Outcome(120)))) + .Invoking(b => b.AddChaosResult(true, -1, () => new ValueTask?>(new Outcome(120)))) .Should() .Throw(); } @@ -165,7 +165,7 @@ public void AddFault_Shortcut_Option_Ok() { var builder = new ResiliencePipelineBuilder(); builder - .AddFault(true, 0.5, new InvalidOperationException("Dummy exception")) + .AddChaosFault(true, 0.5, new InvalidOperationException("Dummy exception")) .Build(); AssertFaultStrategy(builder, true, 0.5, new InvalidOperationException("Dummy exception")); @@ -175,7 +175,7 @@ public void AddFault_Shortcut_Option_Ok() public void AddFault_Shortcut_Option_Throws() { new ResiliencePipelineBuilder() - .Invoking(b => b.AddFault( + .Invoking(b => b.AddChaosFault( true, 1.5, () => new ValueTask?>(new Outcome(new InvalidOperationException())))) @@ -188,7 +188,7 @@ public void AddFault_Generic_Shortcut_Option_Ok() { var builder = new ResiliencePipelineBuilder(); builder - .AddFault(true, 0.5, new InvalidOperationException("Dummy exception")) + .AddChaosFault(true, 0.5, new InvalidOperationException("Dummy exception")) .Build(); AssertFaultStrategy(builder, true, 0.5, new InvalidOperationException("Dummy exception")); @@ -198,7 +198,7 @@ public void AddFault_Generic_Shortcut_Option_Ok() public void AddFault_Generic_Shortcut_Option_Throws() { new ResiliencePipelineBuilder() - .Invoking(b => b.AddFault(true, -1, new InvalidOperationException())) + .Invoking(b => b.AddChaosFault(true, -1, new InvalidOperationException())) .Should() .Throw(); } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 0afeb594731..3f735d7b411 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -92,7 +92,7 @@ public void Given_not_enabled_should_not_inject_fault() Outcome = new Outcome(fault) }; - var sut = new ResiliencePipelineBuilder().AddFault(options).Build(); + var sut = new ResiliencePipelineBuilder().AddChaosFault(options).Build(); sut.Execute(() => { userDelegateExecuted = true; }); userDelegateExecuted.Should().BeTrue(); @@ -101,6 +101,7 @@ public void Given_not_enabled_should_not_inject_fault() [Fact] public async Task Given_enabled_and_randomly_within_threshold_should_inject_fault() { + var onFaultInjected = false; var userDelegateExecuted = false; var exceptionMessage = "Dummy exception"; var fault = new InvalidOperationException(exceptionMessage); @@ -110,7 +111,14 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_faul InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - Outcome = new Outcome(fault) + Outcome = new Outcome(fault), + OnOutcomeInjected = args => + { + args.Context.Should().NotBeNull(); + args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); + onFaultInjected = true; + return default; + } }; var sut = CreateSut(options); @@ -124,6 +132,10 @@ await sut.Invoking(s => s.ExecuteAsync(async _ => .WithMessage(exceptionMessage); userDelegateExecuted.Should().BeFalse(); + _args.Should().HaveCount(1); + _args[0].Arguments.Should().BeOfType>(); + _args[0].Event.EventName.Should().Be(OutcomeConstants.OnFaultInjectedEvent); + onFaultInjected.Should().BeTrue(); } [Fact] @@ -163,7 +175,7 @@ public void Given_enabled_and_randomly_within_threshold_should_not_inject_fault_ OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(null)) }; - var sut = new ResiliencePipelineBuilder().AddFault(options).Build(); + var sut = new ResiliencePipelineBuilder().AddChaosFault(options).Build(); sut.Execute(_ => { userDelegateExecuted = true; @@ -226,6 +238,7 @@ public void Given_not_enabled_should_not_inject_result() [Fact] public async Task Given_enabled_and_randomly_within_threshold_should_inject_result() { + var onResultInjected = false; var userDelegateExecuted = false; var fakeResult = HttpStatusCode.TooManyRequests; @@ -234,7 +247,14 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_resu InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - Outcome = new Outcome(fakeResult) + Outcome = new Outcome(fakeResult), + OnOutcomeInjected = args => + { + args.Context.Should().NotBeNull(); + args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); + onResultInjected = true; + return default; + } }; var sut = CreateSut(options); @@ -246,6 +266,11 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_resu response.Should().Be(fakeResult); userDelegateExecuted.Should().BeFalse(); + + _args.Should().HaveCount(1); + _args[0].Arguments.Should().BeOfType>(); + _args[0].Event.EventName.Should().Be(OutcomeConstants.OnOutcomeInjectedEvent); + onResultInjected.Should().BeTrue(); } [Fact] From d054336721d25e1d3831283ff50f46f437541d47 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 26 Aug 2023 17:36:09 -0500 Subject: [PATCH 25/36] add more unit tests --- .../Outcomes/OutcomeChaosStrategyTests.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 3f735d7b411..2ad0c346f99 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -98,6 +98,68 @@ public void Given_not_enabled_should_not_inject_fault() userDelegateExecuted.Should().BeTrue(); } + [Fact] + public async Task Given_not_enabled_should_not_inject_fault_and_return_outcome() + { + var userDelegateExecuted = false; + var fault = new InvalidOperationException("Dummy exception"); + + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = false, + Randomizer = () => 0.5, + Outcome = new Outcome(fault) + }; + + var sut = new ResiliencePipelineBuilder().AddChaosFault(options).Build(); + var response = await sut.ExecuteAsync(async _ => + { + userDelegateExecuted = true; + return await Task.FromResult(HttpStatusCode.OK); + }); + + response.Should().Be(HttpStatusCode.OK); + userDelegateExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Given_enabled_and_randomly_within_threshold_should_inject_fault_instead_returning_outcome() + { + var onFaultInjected = false; + var userDelegateExecuted = false; + var exceptionMessage = "Dummy exception"; + var fault = new InvalidOperationException(exceptionMessage); + + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Enabled = true, + Randomizer = () => 0.5, + Outcome = new Outcome(fault), + OnOutcomeInjected = args => + { + args.Context.Should().NotBeNull(); + args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); + onFaultInjected = true; + return default; + } + }; + + var sut = new ResiliencePipelineBuilder().AddChaosFault(options).Build(); + await sut.Invoking(s => s.ExecuteAsync(async _ => + { + userDelegateExecuted = true; + return await Task.FromResult(HttpStatusCode.OK); + }).AsTask()) + .Should() + .ThrowAsync() + .WithMessage(exceptionMessage); + + userDelegateExecuted.Should().BeFalse(); + onFaultInjected.Should().BeTrue(); + } + [Fact] public async Task Given_enabled_and_randomly_within_threshold_should_inject_fault() { From 28d3b0c85a64d945ba897903c6a689942efb78ea Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sat, 26 Aug 2023 21:18:01 -0500 Subject: [PATCH 26/36] PR feedback --- src/Polly.Core/PublicAPI.Unshipped.txt | 33 ++++++++++++---- .../BehaviorChaosPipelineBuilderExtensions.cs | 6 +-- .../Simmy/Behavior/BehaviorChaosStrategy.cs | 2 +- .../LatencyChaosPipelineBuilderExtensions.cs | 6 +-- .../Outcomes/OnOutcomeInjectedArguments.cs | 17 ++++++-- ...eChaosPipelineBuilderExtensions.TResult.cs | 15 +++---- .../OutcomeChaosPipelineBuilderExtensions.cs | 7 ++-- .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 39 +++++++------------ .../OutcomeStrategyOptions.TResult.cs | 2 +- ...viorChaosPipelineBuilderExtensionsTests.cs | 1 + ...encyChaosPipelineBuilderExtensionsTests.cs | 1 + .../OnOutcomeInjectedArgumentsTests.cs | 2 +- ...comeChaosPipelineBuilderExtensionsTests.cs | 5 ++- .../Outcomes/OutcomeChaosStrategyTests.cs | 37 ++---------------- 14 files changed, 82 insertions(+), 91 deletions(-) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index b0b583fb138..4f5e6cbd09c 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -338,6 +338,7 @@ Polly.Simmy.Behavior.OnBehaviorInjectedArguments Polly.Simmy.Behavior.OnBehaviorInjectedArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments() -> void Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments(Polly.ResilienceContext! context) -> void +Polly.Simmy.BehaviorChaosPipelineBuilderExtensions Polly.Simmy.EnabledGeneratorArguments Polly.Simmy.EnabledGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.EnabledGeneratorArguments.EnabledGeneratorArguments() -> void @@ -364,6 +365,7 @@ 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.LatencyChaosPipelineBuilderExtensions Polly.Simmy.MonkeyStrategy Polly.Simmy.MonkeyStrategy.EnabledGenerator.get -> System.Func>! Polly.Simmy.MonkeyStrategy.InjectionRateGenerator.get -> System.Func>! @@ -388,10 +390,12 @@ Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.set -> void Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions.Randomizer.get -> System.Func! Polly.Simmy.MonkeyStrategyOptions.Randomizer.set -> void -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.get -> Polly.ResilienceContext! -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments() -> void -Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments(Polly.ResilienceContext! context) -> void +Polly.Simmy.OutcomeChaosPipelineBuilderExtensions +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.get -> Polly.ResilienceContext! +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments() -> void +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments(Polly.ResilienceContext! context, Polly.Outcome outcome) -> void +Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Outcome.get -> Polly.Outcome Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions Polly.Simmy.Outcomes.OutcomeGeneratorArguments Polly.Simmy.Outcomes.OutcomeGeneratorArguments.Context.get -> Polly.ResilienceContext! @@ -400,7 +404,7 @@ Polly.Simmy.Outcomes.OutcomeGeneratorArguments.OutcomeGeneratorArguments(Polly.R Polly.Simmy.Outcomes.OutcomeStrategyOptions Polly.Simmy.Outcomes.OutcomeStrategyOptions.OutcomeStrategyOptions() -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions -Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? +Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? Polly.Simmy.Outcomes.OutcomeStrategyOptions.OnOutcomeInjected.set -> void Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.get -> Polly.Outcome? Polly.Simmy.Outcomes.OutcomeStrategyOptions.Outcome.set -> void @@ -502,15 +506,28 @@ static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.Resili static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! static Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! +static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! +static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! static Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! static Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! +static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! +static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func?>>! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.TimeoutResiliencePipelineBuilderExtensions.AddTimeout(this TBuilder! builder, Polly.Timeout.TimeoutStrategyOptions! options) -> TBuilder! diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs index e5fe8bedb6a..b105a01e49e 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs @@ -1,7 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; +using Polly.Simmy.Behavior; -namespace Polly.Simmy.Behavior; +namespace Polly.Simmy; /// /// Extension methods for adding custom behaviors to a . @@ -52,7 +53,6 @@ public static TBuilder AddChaosBehavior(this TBuilder builder, Behavio Guard.NotNull(builder); Guard.NotNull(options); - builder.AddStrategy(context => new BehaviorChaosStrategy(options, context.Telemetry), options); - return builder; + return builder.AddStrategy(context => new BehaviorChaosStrategy(options, context.Telemetry), options); } } diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs index 30ada086e78..3bd50ae73b2 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs @@ -29,7 +29,7 @@ protected internal override async ValueTask> ExecuteCore /// Extension methods for adding latency to a . @@ -51,8 +52,7 @@ public static TBuilder AddChaosLatency(this TBuilder builder, LatencyS Guard.NotNull(builder); Guard.NotNull(options); - builder.AddStrategy(context => new LatencyChaosStrategy(options, context.TimeProvider, context.Telemetry), options); - return builder; + return builder.AddStrategy(context => new LatencyChaosStrategy(options, context.TimeProvider, context.Telemetry), options); } } diff --git a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs index 673b3c1dd53..182708f83c3 100644 --- a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs +++ b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs @@ -5,16 +5,27 @@ /// /// Arguments used by the outcome chaos strategy to notify that an outcome was injected. /// -public readonly struct OnOutcomeInjectedArguments +/// The type of the outcome that was injected. +public readonly struct OnOutcomeInjectedArguments { /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The context associated with the execution of a user-provided callback. - public OnOutcomeInjectedArguments(ResilienceContext context) => Context = context; + /// The outcome that was injected. + public OnOutcomeInjectedArguments(ResilienceContext context, Outcome outcome) + { + Context = context; + Outcome = outcome; + } /// /// Gets the ResilienceContext instance. /// public ResilienceContext Context { get; } + + /// + /// Gets the Outcome that was injeceted. + /// + public Outcome Outcome { get; } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs index 3ab6384b027..a316788d82a 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using Polly.Simmy.Outcomes; -namespace Polly.Simmy.Outcomes; +namespace Polly.Simmy; /// /// Extension methods for adding outcome to a . @@ -20,7 +21,7 @@ public static ResiliencePipelineBuilder AddChaosFault(this Res { Guard.NotNull(builder); - builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, @@ -39,15 +40,15 @@ public static ResiliencePipelineBuilder AddChaosFault(this Res /// The exception generator delegate. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosFault( - this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func?>> faultGenerator) + this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func faultGenerator) { Guard.NotNull(builder); - builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new OutcomeStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, - OutcomeGenerator = (_) => faultGenerator() + OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(Outcome.FromResult(faultGenerator()))) }); return builder; } @@ -100,7 +101,7 @@ public static ResiliencePipelineBuilder AddChaosResult(this Re /// The outcome generator delegate. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosResult( - this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func?>> outcomeGenerator) + this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func outcomeGenerator) { Guard.NotNull(builder); @@ -108,7 +109,7 @@ public static ResiliencePipelineBuilder AddChaosResult( { Enabled = enabled, InjectionRate = injectionRate, - OutcomeGenerator = (_) => outcomeGenerator() + OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(Outcome.FromResult(outcomeGenerator()))) }); return builder; } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs index 25d7c463e97..f7e3c7afd8e 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using Polly.Simmy.Outcomes; -namespace Polly.Simmy.Outcomes; +namespace Polly.Simmy; /// /// Extension methods for adding outcome to a . @@ -37,7 +38,7 @@ public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBui /// The exception generator delegate. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosFault( - this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func?>> faultGenerator) + this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Func faultGenerator) { Guard.NotNull(builder); @@ -45,7 +46,7 @@ public static ResiliencePipelineBuilder AddChaosFault( { Enabled = enabled, InjectionRate = injectionRate, - OutcomeGenerator = (_) => faultGenerator() + OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(Outcome.FromResult(faultGenerator()))) }); return builder; } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index be28f8f5ee0..e512fe172ac 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -36,9 +36,9 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg OutcomeGenerator = options.OutcomeGenerator is not null ? options.OutcomeGenerator : (_) => new(options.Outcome); } - public Func, ValueTask>? OnOutcomeInjected { get; } + public Func, ValueTask>? OnOutcomeInjected { get; } - public Func, ValueTask>? OnFaultInjected { get; } + public Func, ValueTask>? OnFaultInjected { get; } public Func?>>? OutcomeGenerator { get; } @@ -87,7 +87,7 @@ protected internal override async ValueTask> ExecuteCore(Func private async ValueTask?> InjectOutcome(ResilienceContext context) { var outcome = await OutcomeGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); - var args = new OutcomeArguments(context, outcome.Value, new(context)); + var args = new OnOutcomeInjectedArguments(context, outcome.Value); _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); if (OnOutcomeInjected is not null) @@ -100,33 +100,20 @@ protected internal override async ValueTask> ExecuteCore(Func private async ValueTask InjectFault(ResilienceContext context) { - try + var fault = await FaultGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); + if (!fault.HasValue) { - var fault = await FaultGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); - if (!fault.HasValue) - { - return null; - } - - // to prevent injecting the fault if it was cancelled while executing the FaultGenerator - context.CancellationToken.ThrowIfCancellationRequested(); - - Outcome = new(fault.Value.Exception!); - var args = new OutcomeArguments(context, new Outcome(fault.Value.Exception!), new(context)); - _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); + return null; + } - if (OnFaultInjected is not null) - { - await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); - } + var args = new OnOutcomeInjectedArguments(context, new Outcome(fault.Value.Exception!)); + _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); - return fault.Value.Exception; - } - catch (OperationCanceledException) + if (OnFaultInjected is not null) { - // fault injection might be cancelled during FaultGenerator, if so we run the user's delegate normally - context.CancellationToken = CancellationToken.None; - return null; + await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); } + + return fault.Value.Exception; } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs index 14035f4cf94..9f190267723 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs @@ -14,7 +14,7 @@ public class OutcomeStrategyOptions : MonkeyStrategyOptions /// /// Defaults to . /// - public Func, ValueTask>? OnOutcomeInjected { get; set; } + public Func, ValueTask>? OnOutcomeInjected { get; set; } /// /// Gets or sets the outcome generator to be injected for a given execution. diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensionsTests.cs index 724e6bd58e1..ba176a5a1b2 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensionsTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Polly.Simmy; using Polly.Simmy.Behavior; using Polly.Testing; diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs index 28de4a62073..077d6ea104d 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs @@ -1,3 +1,4 @@ +using Polly.Simmy; using Polly.Simmy.Latency; using Polly.Testing; diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs index 1a3b770b3dd..f8c5eed8ae1 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs @@ -7,7 +7,7 @@ public class OnOutcomeInjectedArgumentsTests [Fact] public void Ctor_Ok() { - var args = new OnOutcomeInjectedArguments(ResilienceContextPool.Shared.Get()); + var args = new OnOutcomeInjectedArguments(ResilienceContextPool.Shared.Get(), new(200)); args.Context.Should().NotBeNull(); } } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs index 204326f0da4..db6838f1829 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Polly.Simmy; using Polly.Simmy.Outcomes; using Polly.Testing; @@ -155,7 +156,7 @@ public void AddResult_Shortcut_Option_Ok() public void AddResult_Shortcut_Option_Throws() { new ResiliencePipelineBuilder() - .Invoking(b => b.AddChaosResult(true, -1, () => new ValueTask?>(new Outcome(120)))) + .Invoking(b => b.AddChaosResult(true, -1, () => 120)) .Should() .Throw(); } @@ -178,7 +179,7 @@ public void AddFault_Shortcut_Option_Throws() .Invoking(b => b.AddChaosFault( true, 1.5, - () => new ValueTask?>(new Outcome(new InvalidOperationException())))) + () => new InvalidOperationException())) .Should() .Throw(); } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 2ad0c346f99..9fea3f4c807 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -1,4 +1,5 @@ -using Polly.Simmy.Outcomes; +using Polly.Simmy; +using Polly.Simmy.Outcomes; using Polly.Telemetry; namespace Polly.Core.Tests.Simmy.Outcomes; @@ -195,7 +196,7 @@ await sut.Invoking(s => s.ExecuteAsync(async _ => userDelegateExecuted.Should().BeFalse(); _args.Should().HaveCount(1); - _args[0].Arguments.Should().BeOfType>(); + _args[0].Arguments.Should().BeOfType>(); _args[0].Event.EventName.Should().Be(OutcomeConstants.OnFaultInjectedEvent); onFaultInjected.Should().BeTrue(); } @@ -246,36 +247,6 @@ public void Given_enabled_and_randomly_within_threshold_should_not_inject_fault_ userDelegateExecuted.Should().BeTrue(); } - [Fact] - public async Task Should_not_inject_fault_when_it_was_cancelled_running_the_fault_generator() - { - var userDelegateExecuted = false; - var fault = new InvalidOperationException("Dummy exception"); - - using var cts = new CancellationTokenSource(); - var options = new OutcomeStrategyOptions - { - InjectionRate = 0.6, - Enabled = true, - Randomizer = () => 0.5, - OutcomeGenerator = (_) => - { - cts.Cancel(); - return new ValueTask?>(new Outcome(fault)); - } - }; - - var sut = CreateSut(options); - var restult = await sut.ExecuteAsync(async _ => - { - userDelegateExecuted = true; - return await Task.FromResult(200); - }, cts.Token); - - restult.Should().Be(200); - userDelegateExecuted.Should().BeTrue(); - } - [Fact] public void Given_not_enabled_should_not_inject_result() { @@ -330,7 +301,7 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_resu userDelegateExecuted.Should().BeFalse(); _args.Should().HaveCount(1); - _args[0].Arguments.Should().BeOfType>(); + _args[0].Arguments.Should().BeOfType>(); _args[0].Event.EventName.Should().Be(OutcomeConstants.OnOutcomeInjectedEvent); onResultInjected.Should().BeTrue(); } From d8286a4a0b98d26ba04754b12ec72eafdd675cd0 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 27 Aug 2023 11:42:44 -0500 Subject: [PATCH 27/36] fixes buld erros --- src/Polly.Core/PublicAPI.Unshipped.txt | 16 -------------- .../BehaviorChaosPipelineBuilderExtensions.cs | 1 - .../Utils/TimeProviderExtensions.cs | 2 +- .../Simmy/MonkeyStrategyTests.cs | 22 ++++--------------- ...comeChaosPipelineBuilderExtensionsTests.cs | 12 ++++++++-- .../Outcomes/OutcomeChaosStrategyTests.cs | 10 +++++---- 6 files changed, 21 insertions(+), 42 deletions(-) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 4f5e6cbd09c..ee4a97d6cf8 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -327,7 +327,6 @@ Polly.Simmy.Behavior.BehaviorActionArguments Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments() -> void Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments(Polly.ResilienceContext! context) -> void Polly.Simmy.Behavior.BehaviorActionArguments.Context.get -> Polly.ResilienceContext! -Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions Polly.Simmy.Behavior.BehaviorStrategyOptions Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.get -> System.Func? Polly.Simmy.Behavior.BehaviorStrategyOptions.BehaviorAction.set -> void @@ -347,7 +346,6 @@ Polly.Simmy.InjectionRateGeneratorArguments Polly.Simmy.InjectionRateGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.InjectionRateGeneratorArguments.InjectionRateGeneratorArguments() -> void Polly.Simmy.InjectionRateGeneratorArguments.InjectionRateGeneratorArguments(Polly.ResilienceContext! context) -> void -Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions Polly.Simmy.Latency.LatencyGeneratorArguments Polly.Simmy.Latency.LatencyGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Latency.LatencyGeneratorArguments.LatencyGeneratorArguments() -> void @@ -396,7 +394,6 @@ Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.get -> Polly.Re Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments() -> void Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments(Polly.ResilienceContext! context, Polly.Outcome outcome) -> void Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Outcome.get -> Polly.Outcome -Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions Polly.Simmy.Outcomes.OutcomeGeneratorArguments Polly.Simmy.Outcomes.OutcomeGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Outcomes.OutcomeGeneratorArguments.OutcomeGeneratorArguments() -> void @@ -504,12 +501,8 @@ static Polly.ResiliencePipelineBuilderExtensions.AddStrategy(this TBui static Polly.ResiliencePipelineBuilderExtensions.AddStrategy(this Polly.ResiliencePipelineBuilder! builder, System.Func!>! factory, Polly.ResilienceStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! -static Polly.Simmy.Behavior.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! -static Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! -static Polly.Simmy.Latency.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! @@ -521,15 +514,6 @@ static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault( static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.Outcomes.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.TimeoutResiliencePipelineBuilderExtensions.AddTimeout(this TBuilder! builder, Polly.Timeout.TimeoutStrategyOptions! options) -> TBuilder! static Polly.TimeoutResiliencePipelineBuilderExtensions.AddTimeout(this TBuilder! builder, System.TimeSpan timeout) -> TBuilder! static readonly Polly.ResiliencePipeline.Empty -> Polly.ResiliencePipeline! diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs index b105a01e49e..ed4e565c892 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs @@ -8,7 +8,6 @@ namespace Polly.Simmy; /// Extension methods for adding custom behaviors to a . /// public static class BehaviorChaosPipelineBuilderExtensions - { /// /// Adds a behavior chaos strategy to the builder. diff --git a/src/Polly.Core/Utils/TimeProviderExtensions.cs b/src/Polly.Core/Utils/TimeProviderExtensions.cs index 5e1fdd9d746..ed8e0d0a210 100644 --- a/src/Polly.Core/Utils/TimeProviderExtensions.cs +++ b/src/Polly.Core/Utils/TimeProviderExtensions.cs @@ -32,7 +32,7 @@ public static Task DelayAsync(this TimeProvider timeProvider, TimeSpan delay, Re // only applies in the case of a resilience event and not on the hot path. // re the Sync-over-async I guess that would be a concern when using the LatencyChaosStrategy - // since that's running on the hot path, thoughts? + // since that's running on the hot path, thoughts? timeProvider.Delay(delay, context.CancellationToken).GetAwaiter().GetResult(); #pragma warning restore CA1849 diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs index df795ac0414..7d319321e48 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs @@ -8,29 +8,15 @@ public class MonkeyStrategyTests public MonkeyStrategyTests() => _options = new(); - public static List CtorTestCases => - new() - { - new object[] { null, "Value cannot be null. (Parameter 'options')" }, - }; - - [Theory] - [MemberData(nameof(CtorTestCases))] - public void InvalidCtor(TestChaosStrategyOptions options, string message) + [Fact] + public void InvalidCtor() { Action act = () => { - var _ = new TestChaosStrategy(options); + var _ = new TestChaosStrategy(null); }; -#if NET481 -act.Should() - .Throw(); -#else - act.Should() - .Throw() - .WithMessage(message); -#endif + act.Should().Throw(); } [Fact] diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs index db6838f1829..ec9545b7c43 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs @@ -80,8 +80,10 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder strategy.Fault.Should().NotBeNull(); // it is supposed that this line should work the same as the try/catch block, but it's not, ideas? - //Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); +#pragma warning disable S125 // Sections of code should not be commented out + // Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); +#pragma warning disable CA1031 // Do not catch general exception types try { var _ = strategy.Fault!.Value; @@ -90,6 +92,8 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder { e.Should().Be(ex); } +#pragma warning restore CA1031 // Do not catch general exception types +#pragma warning restore S125 // Sections of code should not be commented out } private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception ex) @@ -105,8 +109,10 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder bu strategy.Fault.Should().NotBeNull(); // it is supposed that this line should work the same as the try/catch block, but it's not, ideas? - //Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); +#pragma warning disable S125 // Sections of code should not be commented out + // Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); +#pragma warning disable CA1031 // Do not catch general exception types try { var _ = strategy.Fault!.Value; @@ -115,6 +121,8 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder bu { e.Should().Be(ex); } +#pragma warning restore CA1031 // Do not catch general exception types +#pragma warning restore S125 // Sections of code should not be commented out } [MemberData(nameof(ResultStrategy))] diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 9fea3f4c807..1deefe848e1 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -43,7 +43,9 @@ public class OutcomeChaosStrategyTests [Theory] [MemberData(nameof(FaultCtorTestCases))] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters public void FaultInvalidCtor(OutcomeStrategyOptions options, string message) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters { Action act = () => { @@ -51,8 +53,7 @@ public void FaultInvalidCtor(OutcomeStrategyOptions options, string m }; #if NET481 - act.Should() - .Throw(); + act.Should().Throw(); #else act.Should() .Throw() @@ -62,7 +63,9 @@ public void FaultInvalidCtor(OutcomeStrategyOptions options, string m [Theory] [MemberData(nameof(ResultCtorTestCases))] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters public void ResultInvalidCtor(OutcomeStrategyOptions options, string message) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters { Action act = () => { @@ -70,8 +73,7 @@ public void ResultInvalidCtor(OutcomeStrategyOptions options, string messag }; #if NET481 - act.Should() - .Throw(); + act.Should().Throw(); #else act.Should() .Throw() From c2ec5a5be5c179141b1d1bba6005ba727723ff03 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 3 Sep 2023 18:02:31 -0500 Subject: [PATCH 28/36] pr feedback --- src/Polly.Core/PublicAPI.Unshipped.txt | 2 +- .../Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs | 6 +++--- src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs | 4 ++-- src/Polly.Core/Simmy/MonkeyStrategy.cs | 4 ++-- src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs | 2 +- src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 6d2fd815f19..e00743778ae 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -501,7 +501,7 @@ static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.Resili static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! -static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan delay) -> TBuilder! +static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan latency) -> TBuilder! static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs index dd333568aef..97790008928 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs @@ -16,11 +16,11 @@ public static class LatencyChaosPipelineBuilderExtensions /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. /// The injection rate for a given execution, which the value should be between [0, 1]. - /// The delay value. + /// The delay value. /// The same builder instance. /// Thrown when is . /// Thrown when the options produced from the arguments are invalid. - public static TBuilder AddChaosLatency(this TBuilder builder, bool enabled, double injectionRate, TimeSpan delay) + public static TBuilder AddChaosLatency(this TBuilder builder, bool enabled, double injectionRate, TimeSpan latency) where TBuilder : ResiliencePipelineBuilderBase { Guard.NotNull(builder); @@ -29,7 +29,7 @@ public static TBuilder AddChaosLatency(this TBuilder builder, bool ena { Enabled = enabled, InjectionRate = injectionRate, - LatencyGenerator = (_) => new(delay) + Latency = latency }); } diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs index d0d2f7f2cb0..e8161bdc071 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs @@ -31,12 +31,12 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) /// /// Gets the injection rate for a given execution, which the value should be between [0, 1]. /// - public Func> InjectionRateGenerator { get; } + internal Func> InjectionRateGenerator { get; } /// /// Gets a value that indicates whether or not the chaos strategy is enabled for a given execution. /// - public Func> EnabledGenerator { get; } + internal Func> EnabledGenerator { get; } /// /// Determines whether or not the chaos strategy should be injected based on the injection rate and enabled flag. diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.cs b/src/Polly.Core/Simmy/MonkeyStrategy.cs index d30ef2a4093..cc9592c60bd 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.cs @@ -29,12 +29,12 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) /// /// Gets the injection rate for a given execution, which the value should be between [0, 1]. /// - public Func> InjectionRateGenerator { get; } + internal Func> InjectionRateGenerator { get; } /// /// Gets a value that indicates whether or not the chaos strategy is enabled for a given execution. /// - public Func> EnabledGenerator { get; } + internal Func> EnabledGenerator { get; } /// /// Determines whether or not the chaos strategy should be injected based on the injection rate and enabled flag. diff --git a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs index 182708f83c3..dbdae43baf7 100644 --- a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs +++ b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs @@ -20,7 +20,7 @@ public OnOutcomeInjectedArguments(ResilienceContext context, Outcome ou } /// - /// Gets the ResilienceContext instance. + /// Gets the context of this event. /// public ResilienceContext Context { get; } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index e512fe172ac..b2f1d4a417d 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -44,7 +44,7 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg public Func?>>? FaultGenerator { get; } - public Outcome? Outcome { get; private set; } + public Outcome? Outcome { get; } public Outcome? Fault { get; } From 1eb39a66090707d74d790173e0d55b4be8d051b0 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 10 Sep 2023 12:27:00 -0500 Subject: [PATCH 29/36] fix PublicAPI.Unshipped.txt --- src/Polly.Core/PublicAPI.Unshipped.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index e00743778ae..8dba65e8a57 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -363,13 +363,9 @@ Polly.Simmy.Latency.OnLatencyArguments.OnLatencyArguments() -> void Polly.Simmy.Latency.OnLatencyArguments.OnLatencyArguments(Polly.ResilienceContext! context, System.TimeSpan latency) -> void Polly.Simmy.LatencyChaosPipelineBuilderExtensions Polly.Simmy.MonkeyStrategy -Polly.Simmy.MonkeyStrategy.EnabledGenerator.get -> System.Func>! -Polly.Simmy.MonkeyStrategy.InjectionRateGenerator.get -> System.Func>! Polly.Simmy.MonkeyStrategy.MonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void Polly.Simmy.MonkeyStrategy.ShouldInjectAsync(Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask Polly.Simmy.MonkeyStrategy -Polly.Simmy.MonkeyStrategy.EnabledGenerator.get -> System.Func>! -Polly.Simmy.MonkeyStrategy.InjectionRateGenerator.get -> System.Func>! Polly.Simmy.MonkeyStrategy.MonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void Polly.Simmy.MonkeyStrategy.ShouldInjectAsync(Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask Polly.Simmy.MonkeyStrategyOptions From 7d959cac2021cb27d454edba69aae0dd7f9e413a Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 10 Sep 2023 13:51:56 -0500 Subject: [PATCH 30/36] PR feedback: use FaultStrategyOptions to separate outcomes from faults and make it cleaner --- src/Polly.Core/PublicAPI.Unshipped.txt | 12 +++- .../Simmy/Outcomes/FaultStrategyOptions.cs | 35 ++++++++++++ ...eChaosPipelineBuilderExtensions.TResult.cs | 12 ++-- .../OutcomeChaosPipelineBuilderExtensions.cs | 12 ++-- .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 22 ++++---- .../OutcomeStrategyOptions.TResult.cs | 2 +- ...comeChaosPipelineBuilderExtensionsTests.cs | 56 ++++--------------- .../Outcomes/OutcomeChaosStrategyTests.cs | 36 ++++++------ 8 files changed, 99 insertions(+), 88 deletions(-) create mode 100644 src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 8dba65e8a57..e778c1d9c0a 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -383,6 +383,14 @@ Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions.Randomizer.get -> System.Func! Polly.Simmy.MonkeyStrategyOptions.Randomizer.set -> void Polly.Simmy.OutcomeChaosPipelineBuilderExtensions +Polly.Simmy.Outcomes.FaultStrategyOptions +Polly.Simmy.Outcomes.FaultStrategyOptions.Fault.get -> System.Exception? +Polly.Simmy.Outcomes.FaultStrategyOptions.Fault.set -> void +Polly.Simmy.Outcomes.FaultStrategyOptions.FaultGenerator.get -> System.Func>? +Polly.Simmy.Outcomes.FaultStrategyOptions.FaultGenerator.set -> void +Polly.Simmy.Outcomes.FaultStrategyOptions.FaultStrategyOptions() -> void +Polly.Simmy.Outcomes.FaultStrategyOptions.OnFaultInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? +Polly.Simmy.Outcomes.FaultStrategyOptions.OnFaultInjected.set -> void Polly.Simmy.Outcomes.OnOutcomeInjectedArguments Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments() -> void @@ -501,10 +509,10 @@ static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.FaultStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.FaultStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! diff --git a/src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs b/src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs new file mode 100644 index 00000000000..3c78d4b4d75 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs @@ -0,0 +1,35 @@ +namespace Polly.Simmy.Outcomes; + +#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null + +/// +/// Represents the options for the Fault chaos strategy. +/// +public class FaultStrategyOptions : MonkeyStrategyOptions +{ + /// + /// Gets or sets the delegate that's raised when the outcome is injected. + /// + /// + /// Defaults to . + /// + public Func, ValueTask>? OnFaultInjected { get; set; } + + /// + /// Gets or sets the outcome generator to be injected for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public Func>? FaultGenerator { get; set; } + + /// + /// Gets or sets the outcome to be injected for a given execution. + /// + /// + /// Defaults to . Either or this property is required. + /// When this property is the is used. + /// + public Exception? Fault { get; set; } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs index a316788d82a..439db0abebc 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs @@ -21,11 +21,11 @@ public static ResiliencePipelineBuilder AddChaosFault(this Res { Guard.NotNull(builder); - builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new FaultStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, - Outcome = new(fault) + Fault = fault }); return builder; } @@ -44,11 +44,11 @@ public static ResiliencePipelineBuilder AddChaosFault( { Guard.NotNull(builder); - builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new FaultStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, - OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(Outcome.FromResult(faultGenerator()))) + FaultGenerator = (_) => new ValueTask(Task.FromResult(faultGenerator())) }); return builder; } @@ -60,7 +60,7 @@ public static ResiliencePipelineBuilder AddChaosFault( /// The builder instance. /// The fault strategy options. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) + public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, FaultStrategyOptions options) { Guard.NotNull(builder); Guard.NotNull(options); @@ -149,7 +149,7 @@ public static ResiliencePipelineBuilder AddChaosResult(this Re Justification = "All options members preserved.")] private static void AddFaultCore( this ResiliencePipelineBuilder builder, - OutcomeStrategyOptions options) + FaultStrategyOptions options) { builder.AddStrategy( context => new OutcomeChaosStrategy(options, context.Telemetry), diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs index f7e3c7afd8e..0698a9188f0 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs @@ -20,11 +20,11 @@ public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBui { Guard.NotNull(builder); - builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new FaultStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, - Outcome = new(fault) + Fault = fault }); return builder; } @@ -42,11 +42,11 @@ public static ResiliencePipelineBuilder AddChaosFault( { Guard.NotNull(builder); - builder.AddFaultCore(new OutcomeStrategyOptions + builder.AddFaultCore(new FaultStrategyOptions { Enabled = enabled, InjectionRate = injectionRate, - OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(Outcome.FromResult(faultGenerator()))) + FaultGenerator = (_) => new ValueTask(Task.FromResult(faultGenerator())) }); return builder; } @@ -57,7 +57,7 @@ public static ResiliencePipelineBuilder AddChaosFault( /// The builder instance. /// The fault strategy options. /// The builder instance with the retry strategy added. - public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) + public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, FaultStrategyOptions options) { Guard.NotNull(builder); Guard.NotNull(options); @@ -70,7 +70,7 @@ public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBui "Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "All options members preserved.")] - private static void AddFaultCore(this ResiliencePipelineBuilder builder, OutcomeStrategyOptions options) + private static void AddFaultCore(this ResiliencePipelineBuilder builder, FaultStrategyOptions options) { builder.AddStrategy(context => new OutcomeChaosStrategy( diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index b2f1d4a417d..6667087fc1d 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -8,18 +8,18 @@ internal class OutcomeChaosStrategy : MonkeyStrategy { private readonly ResilienceStrategyTelemetry _telemetry; - public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrategyTelemetry telemetry) + public OutcomeChaosStrategy(FaultStrategyOptions options, ResilienceStrategyTelemetry telemetry) : base(options) { - if (options.Outcome is null && options.OutcomeGenerator is null) + if (options.Fault is null && options.FaultGenerator is null) { - throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); + throw new ArgumentNullException(nameof(options.Fault), "Either Fault or FaultGenerator is required."); } _telemetry = telemetry; - Fault = options.Outcome; - OnFaultInjected = options.OnOutcomeInjected; - FaultGenerator = options.OutcomeGenerator is not null ? options.OutcomeGenerator : (_) => new(options.Outcome); + Fault = options.Fault; + OnFaultInjected = options.OnFaultInjected; + FaultGenerator = options.FaultGenerator is not null ? options.FaultGenerator : (_) => new(options.Fault); } public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrategyTelemetry telemetry) @@ -42,11 +42,11 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg public Func?>>? OutcomeGenerator { get; } - public Func?>>? FaultGenerator { get; } + public Func>? FaultGenerator { get; } public Outcome? Outcome { get; } - public Outcome? Fault { get; } + public Exception? Fault { get; } protected internal override async ValueTask> ExecuteCore(Func>> callback, ResilienceContext context, TState state) { @@ -101,12 +101,12 @@ protected internal override async ValueTask> ExecuteCore(Func private async ValueTask InjectFault(ResilienceContext context) { var fault = await FaultGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); - if (!fault.HasValue) + if (fault is null) { return null; } - var args = new OnOutcomeInjectedArguments(context, new Outcome(fault.Value.Exception!)); + var args = new OnOutcomeInjectedArguments(context, new Outcome(fault)); _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); if (OnFaultInjected is not null) @@ -114,6 +114,6 @@ protected internal override async ValueTask> ExecuteCore(Func await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext); } - return fault.Value.Exception; + return fault; } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs index 9f190267723..3194e63b7d0 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeStrategyOptions.TResult.cs @@ -3,7 +3,7 @@ #pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null /// -/// Represents the options for the Behavior chaos strategy. +/// Represents the options for the Outcome chaos strategy. /// /// The type of the outcome that was injected. public class OutcomeStrategyOptions : MonkeyStrategyOptions diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs index ec9545b7c43..52cffdb662a 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs @@ -27,15 +27,15 @@ public class OutcomeChaosPipelineBuilderExtensionsTests { builder => { - builder.AddChaosFault(new OutcomeStrategyOptions + builder.AddChaosFault(new FaultStrategyOptions { InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - Outcome = new(new InvalidOperationException("Dummy exception.")) + Fault = new InvalidOperationException("Dummy exception.") }); - AssertFaultStrategy(builder, true, 0.6, new InvalidOperationException("Dummy exception.")); + AssertFaultStrategy(builder, true, 0.6); } }; @@ -43,15 +43,15 @@ public class OutcomeChaosPipelineBuilderExtensionsTests { builder => { - builder.AddChaosFault(new OutcomeStrategyOptions + builder.AddChaosFault(new FaultStrategyOptions { InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - Outcome = new(new InvalidOperationException("Dummy exception.")) + Fault = new InvalidOperationException("Dummy exception.") }); - AssertFaultStrategy(builder, true, 0.6, new InvalidOperationException("Dummy exception.")); + AssertFaultStrategy(builder, true, 0.6); } }; @@ -67,7 +67,7 @@ private static void AssertResultStrategy(ResiliencePipelineBuilder builder strategy.Outcome.Should().Be(outcome); } - private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception ex) + private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate) where TException : Exception { var context = ResilienceContextPool.Shared.Get(); @@ -76,27 +76,11 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.FaultGenerator.Should().NotBeNull(); - strategy.Fault.Should().BeOfType(typeof(Outcome)); + strategy.Fault.Should().BeOfType(typeof(TException)); strategy.Fault.Should().NotBeNull(); - - // it is supposed that this line should work the same as the try/catch block, but it's not, ideas? -#pragma warning disable S125 // Sections of code should not be commented out - // Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); - -#pragma warning disable CA1031 // Do not catch general exception types - try - { - var _ = strategy.Fault!.Value; - } - catch (Exception e) - { - e.Should().Be(ex); - } -#pragma warning restore CA1031 // Do not catch general exception types -#pragma warning restore S125 // Sections of code should not be commented out } - private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception ex) + private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate) where TException : Exception { var context = ResilienceContextPool.Shared.Get(); @@ -105,24 +89,8 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder bu strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); strategy.FaultGenerator.Should().NotBeNull(); - strategy.Fault.Should().BeOfType(typeof(Outcome)); + strategy.Fault.Should().BeOfType(typeof(TException)); strategy.Fault.Should().NotBeNull(); - - // it is supposed that this line should work the same as the try/catch block, but it's not, ideas? -#pragma warning disable S125 // Sections of code should not be commented out - // Assert.Throws(() => { var _ = strategy.Fault.Value; }).Should().Be(ex); - -#pragma warning disable CA1031 // Do not catch general exception types - try - { - var _ = strategy.Fault!.Value; - } - catch (Exception e) - { - e.Should().Be(ex); - } -#pragma warning restore CA1031 // Do not catch general exception types -#pragma warning restore S125 // Sections of code should not be commented out } [MemberData(nameof(ResultStrategy))] @@ -177,7 +145,7 @@ public void AddFault_Shortcut_Option_Ok() .AddChaosFault(true, 0.5, new InvalidOperationException("Dummy exception")) .Build(); - AssertFaultStrategy(builder, true, 0.5, new InvalidOperationException("Dummy exception")); + AssertFaultStrategy(builder, true, 0.5); } [Fact] @@ -200,7 +168,7 @@ public void AddFault_Generic_Shortcut_Option_Ok() .AddChaosFault(true, 0.5, new InvalidOperationException("Dummy exception")) .Build(); - AssertFaultStrategy(builder, true, 0.5, new InvalidOperationException("Dummy exception")); + AssertFaultStrategy(builder, true, 0.5); } [Fact] diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 1deefe848e1..9d8d129cd78 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -17,12 +17,12 @@ public class OutcomeChaosStrategyTests new object[] { null!, "Value cannot be null. (Parameter 'options')" }, new object[] { - new OutcomeStrategyOptions + new FaultStrategyOptions { InjectionRate = 1, Enabled = true, }, - "Either Outcome or OutcomeGenerator is required. (Parameter 'Outcome')" + "Either Fault or FaultGenerator is required. (Parameter 'Fault')" }, }; @@ -44,7 +44,7 @@ public class OutcomeChaosStrategyTests [Theory] [MemberData(nameof(FaultCtorTestCases))] #pragma warning disable xUnit1026 // Theory methods should use all of their parameters - public void FaultInvalidCtor(OutcomeStrategyOptions options, string message) + public void FaultInvalidCtor(FaultStrategyOptions options, string message) #pragma warning restore xUnit1026 // Theory methods should use all of their parameters { Action act = () => @@ -87,12 +87,12 @@ public void Given_not_enabled_should_not_inject_fault() var userDelegateExecuted = false; var fault = new InvalidOperationException("Dummy exception"); - var options = new OutcomeStrategyOptions + var options = new FaultStrategyOptions { InjectionRate = 0.6, Enabled = false, Randomizer = () => 0.5, - Outcome = new Outcome(fault) + Fault = fault }; var sut = new ResiliencePipelineBuilder().AddChaosFault(options).Build(); @@ -107,12 +107,12 @@ public async Task Given_not_enabled_should_not_inject_fault_and_return_outcome() var userDelegateExecuted = false; var fault = new InvalidOperationException("Dummy exception"); - var options = new OutcomeStrategyOptions + var options = new FaultStrategyOptions { InjectionRate = 0.6, Enabled = false, Randomizer = () => 0.5, - Outcome = new Outcome(fault) + Fault = fault }; var sut = new ResiliencePipelineBuilder().AddChaosFault(options).Build(); @@ -134,13 +134,13 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_faul var exceptionMessage = "Dummy exception"; var fault = new InvalidOperationException(exceptionMessage); - var options = new OutcomeStrategyOptions + var options = new FaultStrategyOptions { InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - Outcome = new Outcome(fault), - OnOutcomeInjected = args => + Fault = fault, + OnFaultInjected = args => { args.Context.Should().NotBeNull(); args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); @@ -171,13 +171,13 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_faul var exceptionMessage = "Dummy exception"; var fault = new InvalidOperationException(exceptionMessage); - var options = new OutcomeStrategyOptions + var options = new FaultStrategyOptions { InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - Outcome = new Outcome(fault), - OnOutcomeInjected = args => + Fault = fault, + OnFaultInjected = args => { args.Context.Should().NotBeNull(); args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); @@ -209,12 +209,12 @@ public void Given_enabled_and_randomly_not_within_threshold_should_not_inject_fa var userDelegateExecuted = false; var fault = new InvalidOperationException("Dummy exception"); - var options = new OutcomeStrategyOptions + var options = new FaultStrategyOptions { InjectionRate = 0.3, Enabled = true, Randomizer = () => 0.5, - Outcome = new Outcome(fault) + Fault = fault }; var sut = CreateSut(options); @@ -232,12 +232,12 @@ public void Given_enabled_and_randomly_not_within_threshold_should_not_inject_fa public void Given_enabled_and_randomly_within_threshold_should_not_inject_fault_when_exception_is_null() { var userDelegateExecuted = false; - var options = new OutcomeStrategyOptions + var options = new FaultStrategyOptions { InjectionRate = 0.6, Enabled = true, Randomizer = () => 0.5, - OutcomeGenerator = (_) => new ValueTask?>(Task.FromResult?>(null)) + FaultGenerator = (_) => new ValueTask(Task.FromResult(null)) }; var sut = new ResiliencePipelineBuilder().AddChaosFault(options).Build(); @@ -359,7 +359,7 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_resu private ResiliencePipeline CreateSut(OutcomeStrategyOptions options) => new OutcomeChaosStrategy(options, _telemetry).AsPipeline(); - private ResiliencePipeline CreateSut(OutcomeStrategyOptions options) => + private ResiliencePipeline CreateSut(FaultStrategyOptions options) => new OutcomeChaosStrategy(options, _telemetry).AsPipeline(); } From 5959d835f17dd95727dff4893245936e984ad5f1 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 10 Sep 2023 15:15:41 -0500 Subject: [PATCH 31/36] more PR feedback --- src/Polly.Core/PublicAPI.Unshipped.txt | 13 ++++++-- .../BehaviorChaosPipelineBuilderExtensions.cs | 2 +- .../Simmy/Behavior/BehaviorChaosStrategy.cs | 2 +- .../Simmy/EnabledGeneratorArguments.cs | 2 +- .../Simmy/InjectionRateGeneratorArguments.cs | 2 +- .../LatencyChaosPipelineBuilderExtensions.cs | 2 +- .../Simmy/Latency/LatencyChaosStrategy.cs | 7 +++- .../Simmy/Latency/LatencyStrategyOptions.cs | 4 +-- .../Simmy/MonkeyStrategy.TResult.cs | 2 +- src/Polly.Core/Simmy/MonkeyStrategy.cs | 2 +- .../Simmy/MonkeyStrategyOptions.TResult.cs | 6 ++-- .../Simmy/Outcomes/FaultGeneratorArguments.cs | 20 +++++++++++ .../Simmy/Outcomes/FaultStrategyOptions.cs | 4 +-- .../Outcomes/OnFaultInjectedArguments.cs | 30 +++++++++++++++++ .../Outcomes/OnOutcomeInjectedArguments.cs | 2 +- ...eChaosPipelineBuilderExtensions.TResult.cs | 8 ++--- .../OutcomeChaosPipelineBuilderExtensions.cs | 4 +-- .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 14 +++----- .../Latency/LatencyChaosStrategyTests.cs | 33 ++++++++++++++++++- .../Outcomes/FaultGeneratorArgumentsTests.cs | 13 ++++++++ .../Outcomes/OnFaultInjectedArgumentsTests.cs | 14 ++++++++ .../Outcomes/OutcomeChaosStrategyTests.cs | 2 +- 22 files changed, 152 insertions(+), 36 deletions(-) create mode 100644 src/Polly.Core/Simmy/Outcomes/FaultGeneratorArguments.cs create mode 100644 src/Polly.Core/Simmy/Outcomes/OnFaultInjectedArguments.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/FaultGeneratorArgumentsTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/Outcomes/OnFaultInjectedArgumentsTests.cs diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index e778c1d9c0a..19f1ec2cd44 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -383,14 +383,23 @@ Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions.Randomizer.get -> System.Func! Polly.Simmy.MonkeyStrategyOptions.Randomizer.set -> void Polly.Simmy.OutcomeChaosPipelineBuilderExtensions +Polly.Simmy.Outcomes.FaultGeneratorArguments +Polly.Simmy.Outcomes.FaultGeneratorArguments.Context.get -> Polly.ResilienceContext! +Polly.Simmy.Outcomes.FaultGeneratorArguments.FaultGeneratorArguments() -> void +Polly.Simmy.Outcomes.FaultGeneratorArguments.FaultGeneratorArguments(Polly.ResilienceContext! context) -> void Polly.Simmy.Outcomes.FaultStrategyOptions Polly.Simmy.Outcomes.FaultStrategyOptions.Fault.get -> System.Exception? Polly.Simmy.Outcomes.FaultStrategyOptions.Fault.set -> void -Polly.Simmy.Outcomes.FaultStrategyOptions.FaultGenerator.get -> System.Func>? +Polly.Simmy.Outcomes.FaultStrategyOptions.FaultGenerator.get -> System.Func>? Polly.Simmy.Outcomes.FaultStrategyOptions.FaultGenerator.set -> void Polly.Simmy.Outcomes.FaultStrategyOptions.FaultStrategyOptions() -> void -Polly.Simmy.Outcomes.FaultStrategyOptions.OnFaultInjected.get -> System.Func, System.Threading.Tasks.ValueTask>? +Polly.Simmy.Outcomes.FaultStrategyOptions.OnFaultInjected.get -> System.Func? Polly.Simmy.Outcomes.FaultStrategyOptions.OnFaultInjected.set -> void +Polly.Simmy.Outcomes.OnFaultInjectedArguments +Polly.Simmy.Outcomes.OnFaultInjectedArguments.Context.get -> Polly.ResilienceContext! +Polly.Simmy.Outcomes.OnFaultInjectedArguments.Fault.get -> System.Exception! +Polly.Simmy.Outcomes.OnFaultInjectedArguments.OnFaultInjectedArguments() -> void +Polly.Simmy.Outcomes.OnFaultInjectedArguments.OnFaultInjectedArguments(Polly.ResilienceContext! context, System.Exception! fault) -> void Polly.Simmy.Outcomes.OnOutcomeInjectedArguments Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Outcomes.OnOutcomeInjectedArguments.OnOutcomeInjectedArguments() -> void diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs index ed4e565c892..b235f209a8b 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs @@ -15,7 +15,7 @@ public static class BehaviorChaosPipelineBuilderExtensions /// The builder type. /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The behavior to be injected. /// The same builder instance. /// Thrown when is . diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs index 3bd50ae73b2..654fb93f45f 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorChaosStrategy.cs @@ -32,7 +32,7 @@ protected internal override async ValueTask> ExecuteCore /// Initializes a new instance of the struct. /// - /// The resilience context intance. + /// The resilience context instance. public EnabledGeneratorArguments(ResilienceContext context) => Context = context; /// diff --git a/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs b/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs index 556299c6d31..7af162378e5 100644 --- a/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs +++ b/src/Polly.Core/Simmy/InjectionRateGeneratorArguments.cs @@ -10,7 +10,7 @@ public readonly struct InjectionRateGeneratorArguments /// /// Initializes a new instance of the struct. /// - /// The resilience context intance. + /// The resilience context instance. public InjectionRateGeneratorArguments(ResilienceContext context) => Context = context; /// diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs index 97790008928..426c97f8a18 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs @@ -15,7 +15,7 @@ public static class LatencyChaosPipelineBuilderExtensions /// The builder type. /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The delay value. /// The same builder instance. /// Thrown when is . diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index 544ce9f461d..e0c154b0a1f 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -39,9 +39,14 @@ protected internal override async ValueTask> ExecuteCore public class LatencyStrategyOptions : MonkeyStrategyOptions { - internal static readonly TimeSpan DefaultLatency = TimeSpan.FromSeconds(30); - /// - /// Gets or sets the delegate that's raised when delay occurs. + /// Gets or sets the delegate that's raised when a delay occurs. /// /// /// Defaults to . diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs index e8161bdc071..a73e0d2e040 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.TResult.cs @@ -29,7 +29,7 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) } /// - /// Gets the injection rate for a given execution, which the value should be between [0, 1]. + /// Gets the injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// internal Func> InjectionRateGenerator { get; } diff --git a/src/Polly.Core/Simmy/MonkeyStrategy.cs b/src/Polly.Core/Simmy/MonkeyStrategy.cs index cc9592c60bd..173c0462625 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategy.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategy.cs @@ -27,7 +27,7 @@ protected MonkeyStrategy(MonkeyStrategyOptions options) } /// - /// Gets the injection rate for a given execution, which the value should be between [0, 1]. + /// Gets the injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// internal Func> InjectionRateGenerator { get; } diff --git a/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs index 74fe29dd6d2..6c7335ada58 100644 --- a/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs +++ b/src/Polly.Core/Simmy/MonkeyStrategyOptions.TResult.cs @@ -11,16 +11,16 @@ namespace Polly.Simmy; public abstract class MonkeyStrategyOptions : ResilienceStrategyOptions { /// - /// Gets or sets the injection rate for a given execution, which the value should be between [0, 1]. + /// Gets or sets the injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// /// - /// Defaults to 0.001. Either or this property is required. + /// Defaults to 0.001, meaning one in a thousand executions/0.1%. Either or this property is required. /// [Range(MonkeyStrategyConstants.MinInjectionThreshold, MonkeyStrategyConstants.MaxInjectionThreshold)] public double InjectionRate { get; set; } = MonkeyStrategyConstants.DefaultInjectionRate; /// - /// Gets or sets the injection rate generator for a given execution, which the value should be between [0, 1]. + /// Gets or sets the injection rate generator for a given execution, which the value should be between [0, 1] (inclusive). /// /// /// Defaults to . Either or this property is required. diff --git a/src/Polly.Core/Simmy/Outcomes/FaultGeneratorArguments.cs b/src/Polly.Core/Simmy/Outcomes/FaultGeneratorArguments.cs new file mode 100644 index 00000000000..31bf773dac1 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/FaultGeneratorArguments.cs @@ -0,0 +1,20 @@ +namespace Polly.Simmy.Outcomes; + +#pragma warning disable CA1815 // Override equals and operator equals on value types + +/// +/// Arguments used by the fault chaos strategy to ge the fault that is going to be injected. +/// +public readonly struct FaultGeneratorArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + public FaultGeneratorArguments(ResilienceContext context) => Context = context; + + /// + /// Gets the ResilienceContext instance. + /// + public ResilienceContext Context { get; } +} diff --git a/src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs b/src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs index 3c78d4b4d75..902c4155b7d 100644 --- a/src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Outcomes/FaultStrategyOptions.cs @@ -13,7 +13,7 @@ public class FaultStrategyOptions : MonkeyStrategyOptions /// /// Defaults to . /// - public Func, ValueTask>? OnFaultInjected { get; set; } + public Func? OnFaultInjected { get; set; } /// /// Gets or sets the outcome generator to be injected for a given execution. @@ -22,7 +22,7 @@ public class FaultStrategyOptions : MonkeyStrategyOptions /// Defaults to . Either or this property is required. /// When this property is the is used. /// - public Func>? FaultGenerator { get; set; } + public Func>? FaultGenerator { get; set; } /// /// Gets or sets the outcome to be injected for a given execution. diff --git a/src/Polly.Core/Simmy/Outcomes/OnFaultInjectedArguments.cs b/src/Polly.Core/Simmy/Outcomes/OnFaultInjectedArguments.cs new file mode 100644 index 00000000000..93513504c57 --- /dev/null +++ b/src/Polly.Core/Simmy/Outcomes/OnFaultInjectedArguments.cs @@ -0,0 +1,30 @@ +namespace Polly.Simmy.Outcomes; + +#pragma warning disable CA1815 // Override equals and operator equals on value types + +/// +/// Arguments used by the fault chaos strategy to notify that an fault was injected. +/// +public readonly struct OnFaultInjectedArguments +{ + /// + /// Initializes a new instance of the struct. + /// + /// The context associated with the execution of a user-provided callback. + /// The fault that was injected. + public OnFaultInjectedArguments(ResilienceContext context, Exception fault) + { + Context = context; + Fault = fault; + } + + /// + /// Gets the context of this event. + /// + public ResilienceContext Context { get; } + + /// + /// Gets the Outcome that was injected. + /// + public Exception Fault { get; } +} diff --git a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs index dbdae43baf7..6a202560b43 100644 --- a/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs +++ b/src/Polly.Core/Simmy/Outcomes/OnOutcomeInjectedArguments.cs @@ -25,7 +25,7 @@ public OnOutcomeInjectedArguments(ResilienceContext context, Outcome ou public ResilienceContext Context { get; } /// - /// Gets the Outcome that was injeceted. + /// Gets the Outcome that was injected. /// public Outcome Outcome { get; } } diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs index 439db0abebc..c96740f3fd0 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs @@ -14,7 +14,7 @@ public static partial class OutcomeChaosPipelineBuilderExtensions /// The type of result the retry strategy handles. /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The exception to inject. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception fault) @@ -36,7 +36,7 @@ public static ResiliencePipelineBuilder AddChaosFault(this Res /// The type of result the retry strategy handles. /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The exception generator delegate. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosFault( @@ -75,7 +75,7 @@ public static ResiliencePipelineBuilder AddChaosFault(this Res /// The type of result the retry strategy handles. /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The outcome to inject. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosResult(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, TResult result) @@ -97,7 +97,7 @@ public static ResiliencePipelineBuilder AddChaosResult(this Re /// The type of result the retry strategy handles. /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The outcome generator delegate. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosResult( diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs index 0698a9188f0..00b71f98f14 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs @@ -13,7 +13,7 @@ public static partial class OutcomeChaosPipelineBuilderExtensions /// /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The exception to inject. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Exception fault) @@ -34,7 +34,7 @@ public static ResiliencePipelineBuilder AddChaosFault(this ResiliencePipelineBui /// /// The builder instance. /// A value that indicates whether or not the chaos strategy is enabled for a given execution. - /// The injection rate for a given execution, which the value should be between [0, 1]. + /// The injection rate for a given execution, which the value should be between [0, 1] (inclusive). /// The exception generator delegate. /// The builder instance with the retry strategy added. public static ResiliencePipelineBuilder AddChaosFault( diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index 6667087fc1d..9995363bfb2 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -38,11 +38,11 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg public Func, ValueTask>? OnOutcomeInjected { get; } - public Func, ValueTask>? OnFaultInjected { get; } + public Func? OnFaultInjected { get; } public Func?>>? OutcomeGenerator { get; } - public Func>? FaultGenerator { get; } + public Func>? FaultGenerator { get; } public Outcome? Outcome { get; } @@ -70,10 +70,6 @@ protected internal override async ValueTask> ExecuteCore(Func return new Outcome(outcome.Value.Result); } } - else - { - throw new InvalidOperationException("Either a fault or fake outcome to inject must be defined."); - } } return await StrategyHelper.ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); @@ -88,7 +84,7 @@ protected internal override async ValueTask> ExecuteCore(Func { var outcome = await OutcomeGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext); var args = new OnOutcomeInjectedArguments(context, outcome.Value); - _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnOutcomeInjectedEvent), context, args); + _telemetry.Report(new(ResilienceEventSeverity.Information, OutcomeConstants.OnOutcomeInjectedEvent), context, args); if (OnOutcomeInjected is not null) { @@ -106,8 +102,8 @@ protected internal override async ValueTask> ExecuteCore(Func return null; } - var args = new OnOutcomeInjectedArguments(context, new Outcome(fault)); - _telemetry.Report(new(ResilienceEventSeverity.Warning, OutcomeConstants.OnFaultInjectedEvent), context, args); + var args = new OnFaultInjectedArguments(context, fault); + _telemetry.Report(new(ResilienceEventSeverity.Information, OutcomeConstants.OnFaultInjectedEvent), context, args); if (OnFaultInjected is not null) { diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs index 7593e7d75cf..655260909d4 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs @@ -77,7 +77,7 @@ public async Task Given_not_enabled_should_not_inject_latency() } [Fact] - public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inject_behaviour() + public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inject_latency() { var userDelegateExecuted = false; @@ -97,6 +97,37 @@ public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inj (after - before).Seconds.Should().Be(0); } + [Fact] + public async Task Given_latency_is_negative_should_not_inject_latency() + { + var onLatencyExecuted = false; + var userDelegateExecuted = false; + + _options.InjectionRate = 0.6; + _options.Enabled = true; + _options.Latency = TimeSpan.FromSeconds(-1000); + _options.Randomizer = () => 0.5; + + _options.OnLatency = args => + { + args.Context.Should().NotBeNull(); + args.Context.CancellationToken.IsCancellationRequested.Should().BeFalse(); + onLatencyExecuted = true; + return default; + }; + + var before = _timeProvider.GetUtcNow(); + var sut = CreateSut(); + var task = sut.ExecuteAsync(async _ => { userDelegateExecuted = true; await Task.CompletedTask; }); + _timeProvider.Advance(_delay); + await task; + + userDelegateExecuted.Should().BeTrue(); + var after = _timeProvider.GetUtcNow(); + (after - before).Seconds.Should().Be(0); + onLatencyExecuted.Should().BeFalse(); + } + [Fact] public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running_the_strategy() { diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/FaultGeneratorArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/FaultGeneratorArgumentsTests.cs new file mode 100644 index 00000000000..6d05a240390 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/FaultGeneratorArgumentsTests.cs @@ -0,0 +1,13 @@ +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class FaultGeneratorArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new FaultGeneratorArguments(ResilienceContextPool.Shared.Get()); + args.Context.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OnFaultInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OnFaultInjectedArgumentsTests.cs new file mode 100644 index 00000000000..52fec6411ee --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OnFaultInjectedArgumentsTests.cs @@ -0,0 +1,14 @@ +using Polly.Simmy.Outcomes; + +namespace Polly.Core.Tests.Simmy.Outcomes; + +public class OnFaultInjectedArgumentsTests +{ + [Fact] + public void Ctor_Ok() + { + var args = new OnFaultInjectedArguments(ResilienceContextPool.Shared.Get(), new InvalidCastException()); + args.Context.Should().NotBeNull(); + args.Fault.Should().NotBeNull(); + } +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 9d8d129cd78..4d354e5dbd6 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -198,7 +198,7 @@ await sut.Invoking(s => s.ExecuteAsync(async _ => userDelegateExecuted.Should().BeFalse(); _args.Should().HaveCount(1); - _args[0].Arguments.Should().BeOfType>(); + _args[0].Arguments.Should().BeOfType(); _args[0].Event.EventName.Should().Be(OutcomeConstants.OnFaultInjectedEvent); onFaultInjected.Should().BeTrue(); } From 7845c519355e34e34814c3b07c1d420cfa66bbe5 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Mon, 11 Sep 2023 00:43:48 +0000 Subject: [PATCH 32/36] code coverage unit tests almost done --- .../Behavior/BehaviorChaosStrategyTests.cs | 27 ++++++- ...encyChaosPipelineBuilderExtensionsTests.cs | 1 + .../OnOutcomeInjectedArgumentsTests.cs | 1 + ...comeChaosPipelineBuilderExtensionsTests.cs | 78 +++++++++++++++---- .../Outcomes/OutcomeChaosStrategyTests.cs | 31 ++++++++ 5 files changed, 122 insertions(+), 16 deletions(-) diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs index 6f78da81627..64947e22a85 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs @@ -121,7 +121,7 @@ public async Task Should_inject_behaviour_before_executing_user_delegate() } [Fact] - public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running_the_strategy() + public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running_the_injected_behavior() { var userDelegateExecuted = false; var injectedBehaviourExecuted = false; @@ -146,5 +146,30 @@ public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running injectedBehaviourExecuted.Should().BeTrue(); } + [Fact] + public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running_the_strategy() + { + var userDelegateExecuted = false; + var enabledGeneratorExecuted = false; + + using var cts = new CancellationTokenSource(); + _options.InjectionRate = 0.6; + _options.Randomizer = () => 0.5; + _options.EnabledGenerator = (_) => + { + cts.Cancel(); + enabledGeneratorExecuted = true; + return new ValueTask(true); + }; + + var sut = CreateSut(); + await sut.Invoking(s => s.ExecuteAsync(async _ => { await Task.CompletedTask; }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); + + userDelegateExecuted.Should().BeFalse(); + enabledGeneratorExecuted.Should().BeTrue(); + } + private ResiliencePipeline CreateSut() => new BehaviorChaosStrategy(_options, _telemetry).AsPipeline(); } diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs index 077d6ea104d..57be2dbcde7 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosPipelineBuilderExtensionsTests.cs @@ -15,6 +15,7 @@ public static IEnumerable AddLatency_Ok_Data() (ResiliencePipelineBuilder builder) => { builder.AddChaosLatency(true, 0.5, TimeSpan.FromSeconds(10)); }, (LatencyChaosStrategy strategy) => { + strategy.Latency.Should().Be(TimeSpan.FromSeconds(10)); strategy.LatencyGenerator!.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(TimeSpan.FromSeconds(10)); strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().BeTrue(); strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(0.5); diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs index f8c5eed8ae1..c2a4bef4549 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OnOutcomeInjectedArgumentsTests.cs @@ -9,5 +9,6 @@ public void Ctor_Ok() { var args = new OnOutcomeInjectedArguments(ResilienceContextPool.Shared.Get(), new(200)); args.Context.Should().NotBeNull(); + args.Outcome.Should().NotBeNull(); } } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs index 52cffdb662a..cb3f5ea3d2a 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs @@ -19,7 +19,8 @@ public class OutcomeChaosPipelineBuilderExtensionsTests Outcome = new(100) }); - AssertResultStrategy(builder, true, 0.6, new(100)); + AssertResultStrategy(builder, true, 0.6, new(100)) + .Outcome.Should().Be(new Outcome(100)); } }; @@ -35,7 +36,8 @@ public class OutcomeChaosPipelineBuilderExtensionsTests Fault = new InvalidOperationException("Dummy exception.") }); - AssertFaultStrategy(builder, true, 0.6); + AssertFaultStrategy(builder, true, 0.6) + .Fault.Should().BeOfType(typeof(InvalidOperationException)); } }; @@ -51,23 +53,24 @@ public class OutcomeChaosPipelineBuilderExtensionsTests Fault = new InvalidOperationException("Dummy exception.") }); - AssertFaultStrategy(builder, true, 0.6); + AssertFaultStrategy(builder, true, 0.6) + .Fault.Should().BeOfType(typeof(InvalidOperationException)); } }; - private static void AssertResultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Outcome outcome) + private static OutcomeChaosStrategy AssertResultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Outcome outcome) { var context = ResilienceContextPool.Shared.Get(); var strategy = builder.Build().GetPipelineDescriptor().FirstStrategy.StrategyInstance.Should().BeOfType>().Subject; strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); - strategy.OutcomeGenerator.Should().NotBeNull(); strategy.OutcomeGenerator!.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(outcome); - strategy.Outcome.Should().Be(outcome); + + return strategy; } - private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate) + private static OutcomeChaosStrategy AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate) where TException : Exception { var context = ResilienceContextPool.Shared.Get(); @@ -75,12 +78,12 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); - strategy.FaultGenerator.Should().NotBeNull(); - strategy.Fault.Should().BeOfType(typeof(TException)); - strategy.Fault.Should().NotBeNull(); + strategy.FaultGenerator!.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().BeOfType(typeof(TException)); + + return strategy; } - private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate) + private static OutcomeChaosStrategy AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate) where TException : Exception { var context = ResilienceContextPool.Shared.Get(); @@ -88,9 +91,9 @@ private static void AssertFaultStrategy(ResiliencePipelineBuilder bu strategy.EnabledGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(enabled); strategy.InjectionRateGenerator.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().Be(injectionRate); - strategy.FaultGenerator.Should().NotBeNull(); - strategy.Fault.Should().BeOfType(typeof(TException)); - strategy.Fault.Should().NotBeNull(); + strategy.FaultGenerator!.Invoke(new(context)).Preserve().GetAwaiter().GetResult().Should().BeOfType(typeof(TException)); + + return strategy; } [MemberData(nameof(ResultStrategy))] @@ -128,6 +131,17 @@ public void AddResult_Shortcut_Option_Ok() AssertResultStrategy(builder, true, 0.5, new(120)); } + [Fact] + public void AddResult_Shortcut_Generator_Option_Ok() + { + var builder = new ResiliencePipelineBuilder(); + builder + .AddChaosResult(true, 0.5, () => 120) + .Build(); + + AssertResultStrategy(builder, true, 0.5, new(120)); + } + [Fact] public void AddResult_Shortcut_Option_Throws() { @@ -149,7 +163,7 @@ public void AddFault_Shortcut_Option_Ok() } [Fact] - public void AddFault_Shortcut_Option_Throws() + public void AddFault_Generic_Shortcut_Generator_Option_Throws() { new ResiliencePipelineBuilder() .Invoking(b => b.AddChaosFault( @@ -160,6 +174,29 @@ public void AddFault_Shortcut_Option_Throws() .Throw(); } + [Fact] + public void AddFault_Shortcut_Generator_Option_Throws() + { + new ResiliencePipelineBuilder() + .Invoking(b => b.AddChaosFault( + true, + 1.5, + () => new InvalidOperationException())) + .Should() + .Throw(); + } + + [Fact] + public void AddFault_Shortcut_Generator_Option_Ok() + { + var builder = new ResiliencePipelineBuilder(); + builder + .AddChaosFault(true, 0.5, () => new InvalidOperationException("Dummy exception")) + .Build(); + + AssertFaultStrategy(builder, true, 0.5); + } + [Fact] public void AddFault_Generic_Shortcut_Option_Ok() { @@ -179,4 +216,15 @@ public void AddFault_Generic_Shortcut_Option_Throws() .Should() .Throw(); } + + [Fact] + public void AddFault_Generic_Shortcut_Generator_Option_Ok() + { + var builder = new ResiliencePipelineBuilder(); + builder + .AddChaosFault(true, 0.5, () => new InvalidOperationException("Dummy exception")) + .Build(); + + AssertFaultStrategy(builder, true, 0.5); + } } diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 4d354e5dbd6..6e0075c0873 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -356,6 +356,37 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_resu userDelegateExecuted.Should().BeFalse(); } + [Fact] + public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running_the_strategy() + { + var userDelegateExecuted = false; + + using var cts = new CancellationTokenSource(); + var options = new OutcomeStrategyOptions + { + InjectionRate = 0.6, + Randomizer = () => 0.5, + EnabledGenerator = (_) => + { + cts.Cancel(); + return new ValueTask(true); + }, + Outcome = Outcome.FromResult(HttpStatusCode.TooManyRequests) + }; + + var sut = CreateSut(options); + await sut.Invoking(s => s.ExecuteAsync(async _ => + { + userDelegateExecuted = true; + return await Task.FromResult(HttpStatusCode.OK); + }, cts.Token) + .AsTask()) + .Should() + .ThrowAsync(); + + userDelegateExecuted.Should().BeFalse(); + } + private ResiliencePipeline CreateSut(OutcomeStrategyOptions options) => new OutcomeChaosStrategy(options, _telemetry).AsPipeline(); From 1428577a97003e5a220e3f77ffba65085c63d23a Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 17 Sep 2023 22:06:47 -0500 Subject: [PATCH 33/36] * pr feedback * add more CC --- src/Polly.Core/PublicAPI.Unshipped.txt | 32 ++-- ...s => BehaviorPipelineBuilderExtensions.cs} | 2 +- ...cs => LatencyPipelineBuilderExtensions.cs} | 2 +- .../Simmy/Outcomes/OutcomeChaosStrategy.cs | 4 +- ...tcomePipelineBuilderExtensions.TResult.cs} | 2 +- ...cs => OutcomePipelineBuilderExtensions.cs} | 2 +- .../Simmy/MonkeyStrategyOptionsTTests.cs | 41 +++++ .../Simmy/MonkeyStrategyTTests.cs | 171 ++++++++++++++++++ .../Outcomes/OutcomeChaosStrategyTests.cs | 52 +++--- .../Simmy/TestChaosStrategy.TResult.cs | 45 +++++ .../Simmy/TestChaosStrategyOptions.TResult.cs | 7 + 11 files changed, 314 insertions(+), 46 deletions(-) rename src/Polly.Core/Simmy/Behavior/{BehaviorChaosPipelineBuilderExtensions.cs => BehaviorPipelineBuilderExtensions.cs} (97%) rename src/Polly.Core/Simmy/Latency/{LatencyChaosPipelineBuilderExtensions.cs => LatencyPipelineBuilderExtensions.cs} (97%) rename src/Polly.Core/Simmy/Outcomes/{OutcomeChaosPipelineBuilderExtensions.TResult.cs => OutcomePipelineBuilderExtensions.TResult.cs} (99%) rename src/Polly.Core/Simmy/Outcomes/{OutcomeChaosPipelineBuilderExtensions.cs => OutcomePipelineBuilderExtensions.cs} (98%) create mode 100644 test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs create mode 100644 test/Polly.Core.Tests/Simmy/TestChaosStrategy.TResult.cs create mode 100644 test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.TResult.cs diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 19f1ec2cd44..1ce0be49a51 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -335,7 +335,7 @@ Polly.Simmy.Behavior.OnBehaviorInjectedArguments Polly.Simmy.Behavior.OnBehaviorInjectedArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments() -> void Polly.Simmy.Behavior.OnBehaviorInjectedArguments.OnBehaviorInjectedArguments(Polly.ResilienceContext! context) -> void -Polly.Simmy.BehaviorChaosPipelineBuilderExtensions +Polly.Simmy.BehaviorPipelineBuilderExtensions Polly.Simmy.EnabledGeneratorArguments Polly.Simmy.EnabledGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.EnabledGeneratorArguments.EnabledGeneratorArguments() -> void @@ -361,7 +361,7 @@ 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.LatencyChaosPipelineBuilderExtensions +Polly.Simmy.LatencyPipelineBuilderExtensions Polly.Simmy.MonkeyStrategy Polly.Simmy.MonkeyStrategy.MonkeyStrategy(Polly.Simmy.MonkeyStrategyOptions! options) -> void Polly.Simmy.MonkeyStrategy.ShouldInjectAsync(Polly.ResilienceContext! context) -> System.Threading.Tasks.ValueTask @@ -382,7 +382,7 @@ Polly.Simmy.MonkeyStrategyOptions.InjectionRateGenerator.set -> void Polly.Simmy.MonkeyStrategyOptions.MonkeyStrategyOptions() -> void Polly.Simmy.MonkeyStrategyOptions.Randomizer.get -> System.Func! Polly.Simmy.MonkeyStrategyOptions.Randomizer.set -> void -Polly.Simmy.OutcomeChaosPipelineBuilderExtensions +Polly.Simmy.OutcomePipelineBuilderExtensions Polly.Simmy.Outcomes.FaultGeneratorArguments Polly.Simmy.Outcomes.FaultGeneratorArguments.Context.get -> Polly.ResilienceContext! Polly.Simmy.Outcomes.FaultGeneratorArguments.FaultGeneratorArguments() -> void @@ -512,19 +512,19 @@ static Polly.ResiliencePipelineBuilderExtensions.AddStrategy(this TBui static Polly.ResiliencePipelineBuilderExtensions.AddStrategy(this Polly.ResiliencePipelineBuilder! builder, System.Func!>! factory, Polly.ResilienceStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.RetryResiliencePipelineBuilderExtensions.AddRetry(this Polly.ResiliencePipelineBuilder! builder, Polly.Retry.RetryStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! -static Polly.Simmy.BehaviorChaosPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! -static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan latency) -> TBuilder! -static Polly.Simmy.LatencyChaosPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.FaultStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.FaultStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! -static Polly.Simmy.OutcomeChaosPipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.BehaviorPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, bool enabled, double injectionRate, System.Func! behavior) -> TBuilder! +static Polly.Simmy.BehaviorPipelineBuilderExtensions.AddChaosBehavior(this TBuilder! builder, Polly.Simmy.Behavior.BehaviorStrategyOptions! options) -> TBuilder! +static Polly.Simmy.LatencyPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, bool enabled, double injectionRate, System.TimeSpan latency) -> TBuilder! +static Polly.Simmy.LatencyPipelineBuilderExtensions.AddChaosLatency(this TBuilder! builder, Polly.Simmy.Latency.LatencyStrategyOptions! options) -> TBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.FaultStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Exception! fault) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! faultGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosFault(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.FaultStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, System.Func! outcomeGenerator) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, bool enabled, double injectionRate, TResult result) -> Polly.ResiliencePipelineBuilder! +static Polly.Simmy.OutcomePipelineBuilderExtensions.AddChaosResult(this Polly.ResiliencePipelineBuilder! builder, Polly.Simmy.Outcomes.OutcomeStrategyOptions! options) -> Polly.ResiliencePipelineBuilder! static Polly.TimeoutResiliencePipelineBuilderExtensions.AddTimeout(this TBuilder! builder, Polly.Timeout.TimeoutStrategyOptions! options) -> TBuilder! static Polly.TimeoutResiliencePipelineBuilderExtensions.AddTimeout(this TBuilder! builder, System.TimeSpan timeout) -> TBuilder! static readonly Polly.ResiliencePipeline.Empty -> Polly.ResiliencePipeline! diff --git a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Behavior/BehaviorPipelineBuilderExtensions.cs similarity index 97% rename from src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs rename to src/Polly.Core/Simmy/Behavior/BehaviorPipelineBuilderExtensions.cs index b235f209a8b..80b0173da55 100644 --- a/src/Polly.Core/Simmy/Behavior/BehaviorChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Behavior/BehaviorPipelineBuilderExtensions.cs @@ -7,7 +7,7 @@ namespace Polly.Simmy; /// /// Extension methods for adding custom behaviors to a . /// -public static class BehaviorChaosPipelineBuilderExtensions +public static class BehaviorPipelineBuilderExtensions { /// /// Adds a behavior chaos strategy to the builder. diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Latency/LatencyPipelineBuilderExtensions.cs similarity index 97% rename from src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs rename to src/Polly.Core/Simmy/Latency/LatencyPipelineBuilderExtensions.cs index 426c97f8a18..6c2eeb909a3 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyPipelineBuilderExtensions.cs @@ -7,7 +7,7 @@ namespace Polly.Simmy; /// /// Extension methods for adding latency to a . /// -public static class LatencyChaosPipelineBuilderExtensions +public static class LatencyPipelineBuilderExtensions { /// /// Adds a latency chaos strategy to the builder. diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index 9995363bfb2..d7ef1c476f8 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -13,7 +13,7 @@ public OutcomeChaosStrategy(FaultStrategyOptions options, ResilienceStrategyTele { if (options.Fault is null && options.FaultGenerator is null) { - throw new ArgumentNullException(nameof(options.Fault), "Either Fault or FaultGenerator is required."); + throw new InvalidOperationException("Either Fault or FaultGenerator is required."); } _telemetry = telemetry; @@ -27,7 +27,7 @@ public OutcomeChaosStrategy(OutcomeStrategyOptions options, ResilienceStrateg { if (options.Outcome is null && options.OutcomeGenerator is null) { - throw new ArgumentNullException(nameof(options.Outcome), "Either Outcome or OutcomeGenerator is required."); + throw new InvalidOperationException("Either Outcome or OutcomeGenerator is required."); } _telemetry = telemetry; diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs b/src/Polly.Core/Simmy/Outcomes/OutcomePipelineBuilderExtensions.TResult.cs similarity index 99% rename from src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs rename to src/Polly.Core/Simmy/Outcomes/OutcomePipelineBuilderExtensions.TResult.cs index c96740f3fd0..483ccc27164 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.TResult.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomePipelineBuilderExtensions.TResult.cs @@ -6,7 +6,7 @@ namespace Polly.Simmy; /// /// Extension methods for adding outcome to a . /// -public static partial class OutcomeChaosPipelineBuilderExtensions +public static partial class OutcomePipelineBuilderExtensions { /// /// Adds a fault chaos strategy to the builder. diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs b/src/Polly.Core/Simmy/Outcomes/OutcomePipelineBuilderExtensions.cs similarity index 98% rename from src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs rename to src/Polly.Core/Simmy/Outcomes/OutcomePipelineBuilderExtensions.cs index 00b71f98f14..091ad3a3dfe 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensions.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomePipelineBuilderExtensions.cs @@ -6,7 +6,7 @@ namespace Polly.Simmy; /// /// Extension methods for adding outcome to a . /// -public static partial class OutcomeChaosPipelineBuilderExtensions +public static partial class OutcomePipelineBuilderExtensions { /// /// Adds a fault chaos strategy to the builder. diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTTests.cs new file mode 100644 index 00000000000..265bdd5d391 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyOptionsTTests.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; +using Polly.Simmy; +using Polly.Utils; + +namespace Polly.Core.Tests.Simmy; +public class MonkeyStrategyOptionsTTests +{ + [Fact] + public void Ctor_Ok() + { + var sut = new TestChaosStrategyOptions(); + + sut.Randomizer.Should().NotBeNull(); + sut.Enabled.Should().BeFalse(); + sut.EnabledGenerator.Should().BeNull(); + sut.InjectionRate.Should().Be(MonkeyStrategyConstants.DefaultInjectionRate); + sut.InjectionRateGenerator.Should().BeNull(); + } + + [InlineData(-1)] + [InlineData(1.1)] + [Theory] + public void InvalidThreshold(double injectionRate) + { + var sut = new TestChaosStrategyOptions + { + InjectionRate = injectionRate, + }; + + sut + .Invoking(o => ValidationHelper.ValidateObject(new(o, "Invalid Options"))) + .Should() + .Throw() + .WithMessage(""" + Invalid Options + + Validation Errors: + The field InjectionRate must be between 0 and 1. + """); + } +} diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs new file mode 100644 index 00000000000..9202b1a9718 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs @@ -0,0 +1,171 @@ +namespace Polly.Core.Tests.Simmy; + +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +public class MonkeyStrategyTTests +{ + private readonly TestChaosStrategyOptions _options; + + public MonkeyStrategyTTests() => _options = new(); + + [Fact] + public void InvalidCtor() + { + Action act = () => + { + var _ = new TestChaosStrategy(null); + }; + + act.Should().Throw(); + } + + [Fact] + public async Task Ctor_Ok() + { + var context = ResilienceContextPool.Shared.Get(); + _options.EnabledGenerator = (_) => new ValueTask(true); + _options.InjectionRate = 0.5; + + var sut = CreateSut(); + + sut.EnabledGenerator.Should().NotBeNull(); + (await sut.EnabledGenerator(new(context))).Should().BeTrue(); + + sut.InjectionRateGenerator.Should().NotBeNull(); + (await sut.InjectionRateGenerator(new(context))).Should().Be(0.5); + } + + [InlineData(-1, false)] + [InlineData(1.1, true)] + [Theory] + public async Task Should_coerce_injection_rate_generator_result_is_not_valid(double injectionRateGeneratorResult, bool shouldBeInjected) + { + var wasMonkeyUnleashed = false; + + _options.EnabledGenerator = (_) => new ValueTask(true); + _options.InjectionRateGenerator = (_) => new ValueTask(injectionRateGeneratorResult); + _options.Randomizer = () => 0.5; + + var sut = CreateSut(); + sut.OnExecute = (_, _) => { wasMonkeyUnleashed = true; return Task.CompletedTask; }; + + await sut.AsPipeline().ExecuteAsync((_) => { return default; }); + + wasMonkeyUnleashed.Should().Be(shouldBeInjected); + } + + [Fact] + public async Task Should_not_inject_chaos_when_it_was_cancelled_before_evaluating_strategy() + { + var wasMonkeyUnleashed = false; + var enableGeneratorExecuted = false; + var injectionRateGeneratorExecuted = false; + + _options.Randomizer = () => 0.5; + _options.EnabledGenerator = (_) => + { + enableGeneratorExecuted = true; + return new ValueTask(true); + }; + _options.InjectionRateGenerator = (_) => + { + injectionRateGeneratorExecuted = true; + return new ValueTask(0.6); + }; + + using var cts = new CancellationTokenSource(); + var sut = CreateSut(); + sut.Before = (_, _) => { cts.Cancel(); }; + + await sut.Invoking(s => s.AsPipeline().ExecuteAsync(async _ => { return await Task.FromResult(5); }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); + + wasMonkeyUnleashed.Should().BeFalse(); + enableGeneratorExecuted.Should().BeFalse(); + injectionRateGeneratorExecuted.Should().BeFalse(); + } + + [Fact] + public async Task Should_not_inject_chaos_when_it_was_cancelled_on_enable_generator() + { + var wasMonkeyUnleashed = false; + var enableGeneratorExecuted = false; + var injectionRateGeneratorExecuted = false; + + using var cts = new CancellationTokenSource(); + _options.Randomizer = () => 0.5; + _options.EnabledGenerator = (_) => + { + cts.Cancel(); + enableGeneratorExecuted = true; + return new ValueTask(true); + }; + _options.InjectionRateGenerator = (_) => + { + injectionRateGeneratorExecuted = true; + return new ValueTask(0.6); + }; + + var sut = CreateSut(); + + await sut.Invoking(s => s.AsPipeline().ExecuteAsync(async _ => { return await Task.FromResult(5); }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); + + wasMonkeyUnleashed.Should().BeFalse(); + enableGeneratorExecuted.Should().BeTrue(); + injectionRateGeneratorExecuted.Should().BeFalse(); + } + + [Fact] + public async Task Should_not_inject_chaos_when_it_was_cancelled_on_injection_rate_generator() + { + var wasMonkeyUnleashed = false; + var enableGeneratorExecuted = false; + var injectionRateGeneratorExecuted = false; + + using var cts = new CancellationTokenSource(); + _options.Randomizer = () => 0.5; + _options.EnabledGenerator = (_) => + { + enableGeneratorExecuted = true; + return new ValueTask(true); + }; + _options.InjectionRateGenerator = (_) => + { + cts.Cancel(); + injectionRateGeneratorExecuted = true; + return new ValueTask(0.6); + }; + + var sut = CreateSut(); + + await sut.Invoking(s => s.AsPipeline().ExecuteAsync(async _ => { return await Task.FromResult(5); }, cts.Token).AsTask()) + .Should() + .ThrowAsync(); + + wasMonkeyUnleashed.Should().BeFalse(); + enableGeneratorExecuted.Should().BeTrue(); + injectionRateGeneratorExecuted.Should().BeTrue(); + } + + [Fact] + public async Task Should_inject_chaos() + { + var wasMonkeyUnleashed = false; + + _options.EnabledGenerator = (_) => new ValueTask(true); + _options.InjectionRate = 0.6; + _options.Randomizer = () => 0.5; + + var sut = CreateSut(); + sut.OnExecute = (_, _) => { wasMonkeyUnleashed = true; return Task.CompletedTask; }; + + await sut.AsPipeline().ExecuteAsync((_) => { return default; }); + + wasMonkeyUnleashed.Should().BeTrue(); + } + + private TestChaosStrategy CreateSut() => new(_options); +} diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 6e0075c0873..d5f53072285 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -14,7 +14,7 @@ public class OutcomeChaosStrategyTests public static List FaultCtorTestCases => new() { - new object[] { null!, "Value cannot be null. (Parameter 'options')" }, + new object[] { null!, "Value cannot be null. (Parameter 'options')", typeof(ArgumentNullException) }, new object[] { new FaultStrategyOptions @@ -22,14 +22,15 @@ public class OutcomeChaosStrategyTests InjectionRate = 1, Enabled = true, }, - "Either Fault or FaultGenerator is required. (Parameter 'Fault')" + "Either Fault or FaultGenerator is required.", + typeof(InvalidOperationException) }, }; public static List ResultCtorTestCases => new() { - new object[] { null!, "Value cannot be null. (Parameter 'options')" }, + new object[] { null!, "Value cannot be null. (Parameter 'options')", typeof(ArgumentNullException) }, new object[] { new OutcomeStrategyOptions @@ -37,48 +38,51 @@ public class OutcomeChaosStrategyTests InjectionRate = 1, Enabled = true, }, - "Either Outcome or OutcomeGenerator is required. (Parameter 'Outcome')" + "Either Outcome or OutcomeGenerator is required.", + typeof(InvalidOperationException) }, }; [Theory] [MemberData(nameof(FaultCtorTestCases))] #pragma warning disable xUnit1026 // Theory methods should use all of their parameters - public void FaultInvalidCtor(FaultStrategyOptions options, string message) + public void FaultInvalidCtor(FaultStrategyOptions options, string expectedMessage, Type expectedException) #pragma warning restore xUnit1026 // Theory methods should use all of their parameters { - Action act = () => +#pragma warning disable CA1031 // Do not catch general exception types + try { var _ = new OutcomeChaosStrategy(options, _telemetry); - }; - -#if NET481 - act.Should().Throw(); -#else - act.Should() - .Throw() - .WithMessage(message); + } + catch (Exception ex) + { + Assert.IsType(expectedException, ex); +#if !NET481 + Assert.Equal(expectedMessage, ex.Message); #endif + } +#pragma warning restore CA1031 // Do not catch general exception types } [Theory] [MemberData(nameof(ResultCtorTestCases))] #pragma warning disable xUnit1026 // Theory methods should use all of their parameters - public void ResultInvalidCtor(OutcomeStrategyOptions options, string message) + public void ResultInvalidCtor(OutcomeStrategyOptions options, string expectedMessage, Type expectedException) #pragma warning restore xUnit1026 // Theory methods should use all of their parameters { - Action act = () => +#pragma warning disable CA1031 // Do not catch general exception types + try { var _ = new OutcomeChaosStrategy(options, _telemetry); - }; - -#if NET481 - act.Should().Throw(); -#else - act.Should() - .Throw() - .WithMessage(message); + } + catch (Exception ex) + { + Assert.IsType(expectedException, ex); +#if !NET481 + Assert.Equal(expectedMessage, ex.Message); #endif + } +#pragma warning restore CA1031 // Do not catch general exception types } [Fact] diff --git a/test/Polly.Core.Tests/Simmy/TestChaosStrategy.TResult.cs b/test/Polly.Core.Tests/Simmy/TestChaosStrategy.TResult.cs new file mode 100644 index 00000000000..2b6b6c0f73e --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/TestChaosStrategy.TResult.cs @@ -0,0 +1,45 @@ +using Polly.Simmy; + +namespace Polly.Core.Tests.Simmy; +public sealed class TestChaosStrategy : MonkeyStrategy +{ + public TestChaosStrategy(TestChaosStrategyOptions options) + : base(options) + { + } + + public Action? Before { get; set; } + + public Action? After { get; set; } + + public Func? OnExecute { get; set; } + + protected internal override async ValueTask> ExecuteCore( + Func>> callback, ResilienceContext context, TState state) + { + Before?.Invoke(context, state); + + try + { + if (await ShouldInjectAsync(context).ConfigureAwait(context.ContinueOnCapturedContext)) + { + if (OnExecute != null) + { + await OnExecute(context, state).ConfigureAwait(false); + } + } + + var result = await callback(context, state).ConfigureAwait(false); + + After?.Invoke(result, null); + + return result; + } + catch (Exception e) + { + After?.Invoke(null, e); + + throw; + } + } +} diff --git a/test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.TResult.cs b/test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.TResult.cs new file mode 100644 index 00000000000..e6058581220 --- /dev/null +++ b/test/Polly.Core.Tests/Simmy/TestChaosStrategyOptions.TResult.cs @@ -0,0 +1,7 @@ +using Polly.Simmy; + +namespace Polly.Core.Tests.Simmy; + +public sealed class TestChaosStrategyOptions : MonkeyStrategyOptions +{ +} From f6bee7bb3ecf3637231a66ac28a14acd75f27c79 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 17 Sep 2023 22:17:33 -0500 Subject: [PATCH 34/36] 100% coverage! --- src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs index d7ef1c476f8..06a13223107 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs @@ -65,10 +65,7 @@ protected internal override async ValueTask> ExecuteCore(Func else if (OutcomeGenerator is not null) { var outcome = await InjectOutcome(context).ConfigureAwait(context.ContinueOnCapturedContext); - if (outcome.HasValue) - { - return new Outcome(outcome.Value.Result); - } + return new Outcome(outcome.Value.Result); } } From 48bda7c0dee48647e5615fee4cba607a0f8f6a78 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Mon, 18 Sep 2023 19:23:40 -0500 Subject: [PATCH 35/36] add missing unit tests to kill survivor mutants --- src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs | 2 +- .../Simmy/Latency/LatencyChaosStrategyTests.cs | 8 +++++--- test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs | 3 +++ test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs | 3 +++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs index e0c154b0a1f..1c814acc597 100644 --- a/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs +++ b/src/Polly.Core/Simmy/Latency/LatencyChaosStrategy.cs @@ -39,7 +39,7 @@ protected internal override async ValueTask> ExecuteCore 0.5; _options.OnLatency = args => diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs index 9202b1a9718..880d1b600b0 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTTests.cs @@ -35,7 +35,10 @@ public async Task Ctor_Ok() (await sut.InjectionRateGenerator(new(context))).Should().Be(0.5); } + [InlineData(0, false)] + [InlineData(0.5, false)] [InlineData(-1, false)] + [InlineData(1, true)] [InlineData(1.1, true)] [Theory] public async Task Should_coerce_injection_rate_generator_result_is_not_valid(double injectionRateGeneratorResult, bool shouldBeInjected) diff --git a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs index 7d319321e48..1d40f8875c9 100644 --- a/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/MonkeyStrategyTests.cs @@ -35,7 +35,10 @@ public async Task Ctor_Ok() (await sut.InjectionRateGenerator(new(context))).Should().Be(0.5); } + [InlineData(0, false)] + [InlineData(0.5, false)] [InlineData(-1, false)] + [InlineData(1, true)] [InlineData(1.1, true)] [Theory] public async Task Should_coerce_injection_rate_generator_result_is_not_valid(double injectionRateGeneratorResult, bool shouldBeInjected) From ac0bba601fc22ce0ae80083cb8fd4d5b2bfa9bbb Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Tue, 19 Sep 2023 09:51:13 -0500 Subject: [PATCH 36/36] disable "once equality" for mutants that cannot be killed --- src/Polly.Core/Simmy/Utils/MonkeyStrategyHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Polly.Core/Simmy/Utils/MonkeyStrategyHelper.cs b/src/Polly.Core/Simmy/Utils/MonkeyStrategyHelper.cs index 57697daeb83..13e8e4e3420 100644 --- a/src/Polly.Core/Simmy/Utils/MonkeyStrategyHelper.cs +++ b/src/Polly.Core/Simmy/Utils/MonkeyStrategyHelper.cs @@ -32,11 +32,13 @@ public static async ValueTask ShouldInjectAsync( private static double CoerceInjectionThreshold(double injectionThreshold) { + // stryker disable once equality : no means to test this if (injectionThreshold < MonkeyStrategyConstants.MinInjectionThreshold) { return MonkeyStrategyConstants.MinInjectionThreshold; } + // stryker disable once equality : no means to test this if (injectionThreshold > MonkeyStrategyConstants.MaxInjectionThreshold) { return MonkeyStrategyConstants.MaxInjectionThreshold;