diff --git a/src/Polly.Core/CompositeStrategyBuilderBase.cs b/src/Polly.Core/CompositeStrategyBuilderBase.cs
index 768ff29a062..a2017cc5eba 100644
--- a/src/Polly.Core/CompositeStrategyBuilderBase.cs
+++ b/src/Polly.Core/CompositeStrategyBuilderBase.cs
@@ -2,6 +2,7 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
+using Polly.Telemetry;
namespace Polly;
@@ -27,7 +28,6 @@ private protected CompositeStrategyBuilderBase(CompositeStrategyBuilderBase othe
Name = other.Name;
Properties = other.Properties;
TimeProvider = other.TimeProvider;
- OnCreatingStrategy = other.OnCreatingStrategy;
Randomizer = other.Randomizer;
DiagnosticSource = other.DiagnosticSource;
}
@@ -75,18 +75,6 @@ private protected CompositeStrategyBuilderBase(CompositeStrategyBuilderBase othe
[Required]
internal TimeProvider TimeProvider { get; set; } = TimeProvider.System;
- ///
- /// Gets or sets the callback that is invoked just before the final resilience strategy is being created.
- ///
- ///
- /// This property is used by the telemetry infrastructure and should not be used directly by user code.
- ///
- ///
- /// The default value is .
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Action>? OnCreatingStrategy { get; set; }
-
///
/// Gets or sets the that is used by Polly to report resilience events.
///
@@ -143,19 +131,16 @@ internal ResilienceStrategy BuildStrategy()
_used = true;
var strategies = _entries.Select(CreateResilienceStrategy).ToList();
- OnCreatingStrategy?.Invoke(strategies);
if (strategies.Count == 0)
{
return NullResilienceStrategy.Instance;
}
- if (strategies.Count == 1)
- {
- return strategies[0];
- }
-
- return CompositeResilienceStrategy.Create(strategies);
+ return CompositeResilienceStrategy.Create(
+ strategies,
+ TelemetryUtil.CreateTelemetry(DiagnosticSource, Name, InstanceName, Properties, null),
+ TimeProvider);
}
private ResilienceStrategy CreateResilienceStrategy(Entry entry)
diff --git a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs
index f8c9cc90efe..b384d3ecb9d 100644
--- a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs
+++ b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs
@@ -97,7 +97,7 @@ private async ValueTask> ExecuteCoreAsync(
await HandleOnHedgingAsync(
context,
Outcome.FromResult(default),
- new OnHedgingArguments(attempt, hasOutcome: false, executionTime: delay)).ConfigureAwait(context.ContinueOnCapturedContext);
+ new OnHedgingArguments(attempt, hasOutcome: false, duration: delay)).ConfigureAwait(context.ContinueOnCapturedContext);
continue;
}
diff --git a/src/Polly.Core/Hedging/OnHedgingArguments.cs b/src/Polly.Core/Hedging/OnHedgingArguments.cs
index 2b2db37b08e..04ab9d94045 100644
--- a/src/Polly.Core/Hedging/OnHedgingArguments.cs
+++ b/src/Polly.Core/Hedging/OnHedgingArguments.cs
@@ -10,12 +10,12 @@ public sealed class OnHedgingArguments
///
/// The zero-based hedging attempt number.
/// Indicates whether outcome is available.
- /// The execution time of hedging attempt or the hedging delay in case the attempt was not finished in time.
- public OnHedgingArguments(int attemptNumber, bool hasOutcome, TimeSpan executionTime)
+ /// The execution duration of hedging attempt or the hedging delay in case the attempt was not finished in time.
+ public OnHedgingArguments(int attemptNumber, bool hasOutcome, TimeSpan duration)
{
AttemptNumber = attemptNumber;
HasOutcome = hasOutcome;
- ExecutionTime = executionTime;
+ Duration = duration;
}
///
@@ -32,7 +32,7 @@ public OnHedgingArguments(int attemptNumber, bool hasOutcome, TimeSpan execution
public bool HasOutcome { get; }
///
- /// Gets the execution time of hedging attempt or the hedging delay in case the attempt was not finished in time.
+ /// Gets the execution duration of hedging attempt or the hedging delay in case the attempt was not finished in time.
///
- public TimeSpan ExecutionTime { get; }
+ public TimeSpan Duration { get; }
}
diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt
index 8da2c7a3d08..8e29facb138 100644
--- a/src/Polly.Core/PublicAPI.Unshipped.txt
+++ b/src/Polly.Core/PublicAPI.Unshipped.txt
@@ -88,8 +88,6 @@ Polly.CompositeStrategyBuilderBase.InstanceName.get -> string?
Polly.CompositeStrategyBuilderBase.InstanceName.set -> void
Polly.CompositeStrategyBuilderBase.Name.get -> string?
Polly.CompositeStrategyBuilderBase.Name.set -> void
-Polly.CompositeStrategyBuilderBase.OnCreatingStrategy.get -> System.Action!>?
-Polly.CompositeStrategyBuilderBase.OnCreatingStrategy.set -> void
Polly.CompositeStrategyBuilderBase.Properties.get -> Polly.ResilienceProperties!
Polly.CompositeStrategyBuilderBase.Randomizer.get -> System.Func!
Polly.CompositeStrategyBuilderBase.Randomizer.set -> void
@@ -142,9 +140,9 @@ Polly.Hedging.HedgingStrategyOptions.ShouldHandle.get -> System.Func.ShouldHandle.set -> void
Polly.Hedging.OnHedgingArguments
Polly.Hedging.OnHedgingArguments.AttemptNumber.get -> int
-Polly.Hedging.OnHedgingArguments.ExecutionTime.get -> System.TimeSpan
+Polly.Hedging.OnHedgingArguments.Duration.get -> System.TimeSpan
Polly.Hedging.OnHedgingArguments.HasOutcome.get -> bool
-Polly.Hedging.OnHedgingArguments.OnHedgingArguments(int attemptNumber, bool hasOutcome, System.TimeSpan executionTime) -> void
+Polly.Hedging.OnHedgingArguments.OnHedgingArguments(int attemptNumber, bool hasOutcome, System.TimeSpan duration) -> void
Polly.HedgingCompositeStrategyBuilderExtensions
Polly.LegacySupport
Polly.NonReactiveResilienceStrategy
@@ -329,9 +327,14 @@ Polly.StrategyBuilderContext.StrategyName.get -> string?
Polly.StrategyBuilderContext.Telemetry.get -> Polly.Telemetry.ResilienceStrategyTelemetry!
Polly.Telemetry.ExecutionAttemptArguments
Polly.Telemetry.ExecutionAttemptArguments.AttemptNumber.get -> int
-Polly.Telemetry.ExecutionAttemptArguments.ExecutionAttemptArguments(int attemptNumber, System.TimeSpan executionTime, bool handled) -> void
-Polly.Telemetry.ExecutionAttemptArguments.ExecutionTime.get -> System.TimeSpan
+Polly.Telemetry.ExecutionAttemptArguments.Duration.get -> System.TimeSpan
+Polly.Telemetry.ExecutionAttemptArguments.ExecutionAttemptArguments(int attemptNumber, System.TimeSpan duration, bool handled) -> void
Polly.Telemetry.ExecutionAttemptArguments.Handled.get -> bool
+Polly.Telemetry.PipelineExecutedArguments
+Polly.Telemetry.PipelineExecutedArguments.Duration.get -> System.TimeSpan
+Polly.Telemetry.PipelineExecutedArguments.PipelineExecutedArguments(System.TimeSpan duration) -> void
+Polly.Telemetry.PipelineExecutingArguments
+Polly.Telemetry.PipelineExecutingArguments.PipelineExecutingArguments() -> void
Polly.Telemetry.ResilienceEvent
Polly.Telemetry.ResilienceEvent.EventName.get -> string!
Polly.Telemetry.ResilienceEvent.ResilienceEvent() -> void
diff --git a/src/Polly.Core/Telemetry/ExecutionAttemptArguments.Pool.cs b/src/Polly.Core/Telemetry/ExecutionAttemptArguments.Pool.cs
index 07d1ad22c25..752fd7f277e 100644
--- a/src/Polly.Core/Telemetry/ExecutionAttemptArguments.Pool.cs
+++ b/src/Polly.Core/Telemetry/ExecutionAttemptArguments.Pool.cs
@@ -4,16 +4,16 @@ public partial class ExecutionAttemptArguments
{
private static readonly ObjectPool Pool = new(() => new ExecutionAttemptArguments(), args =>
{
- args.ExecutionTime = TimeSpan.Zero;
+ args.Duration = TimeSpan.Zero;
args.AttemptNumber = 0;
args.Handled = false;
});
- internal static ExecutionAttemptArguments Get(int attempt, TimeSpan executionTime, bool handled)
+ internal static ExecutionAttemptArguments Get(int attempt, TimeSpan duration, bool handled)
{
var args = Pool.Get();
args.AttemptNumber = attempt;
- args.ExecutionTime = executionTime;
+ args.Duration = duration;
args.Handled = handled;
return args;
}
diff --git a/src/Polly.Core/Telemetry/ExecutionAttemptArguments.cs b/src/Polly.Core/Telemetry/ExecutionAttemptArguments.cs
index a1376f7af3a..2126e1fa302 100644
--- a/src/Polly.Core/Telemetry/ExecutionAttemptArguments.cs
+++ b/src/Polly.Core/Telemetry/ExecutionAttemptArguments.cs
@@ -3,18 +3,18 @@
///
/// Arguments that encapsulate the execution attempt for retries or hedging.
///
-public partial class ExecutionAttemptArguments
+public sealed partial class ExecutionAttemptArguments
{
///
/// Initializes a new instance of the class.
///
/// The execution attempt number.
- /// The execution time.
+ /// The execution duration.
/// Determines whether the attempt was handled by the strategy.
- public ExecutionAttemptArguments(int attemptNumber, TimeSpan executionTime, bool handled)
+ public ExecutionAttemptArguments(int attemptNumber, TimeSpan duration, bool handled)
{
AttemptNumber = attemptNumber;
- ExecutionTime = executionTime;
+ Duration = duration;
Handled = handled;
}
@@ -28,9 +28,9 @@ private ExecutionAttemptArguments()
public int AttemptNumber { get; private set; }
///
- /// Gets the execution time of the attempt.
+ /// Gets the execution duration of the attempt.
///
- public TimeSpan ExecutionTime { get; private set; }
+ public TimeSpan Duration { get; private set; }
///
/// Gets a value indicating whether the outcome was handled by retry or hedging strategy.
diff --git a/src/Polly.Core/Telemetry/PipelineExecutedArguments.Pool.cs b/src/Polly.Core/Telemetry/PipelineExecutedArguments.Pool.cs
new file mode 100644
index 00000000000..e71018369dc
--- /dev/null
+++ b/src/Polly.Core/Telemetry/PipelineExecutedArguments.Pool.cs
@@ -0,0 +1,18 @@
+namespace Polly.Telemetry;
+
+public sealed partial class PipelineExecutedArguments
+{
+ private static readonly ObjectPool Pool = new(() => new PipelineExecutedArguments(), args =>
+ {
+ args.Duration = TimeSpan.Zero;
+ });
+
+ internal static PipelineExecutedArguments Get(TimeSpan duration)
+ {
+ var args = Pool.Get();
+ args.Duration = duration;
+ return args;
+ }
+
+ internal static void Return(PipelineExecutedArguments args) => Pool.Return(args);
+}
diff --git a/src/Polly.Core/Telemetry/PipelineExecutedArguments.cs b/src/Polly.Core/Telemetry/PipelineExecutedArguments.cs
new file mode 100644
index 00000000000..a49c8b504fd
--- /dev/null
+++ b/src/Polly.Core/Telemetry/PipelineExecutedArguments.cs
@@ -0,0 +1,22 @@
+namespace Polly.Telemetry;
+
+///
+/// Arguments that indicate the pipeline execution started.
+///
+public sealed partial class PipelineExecutedArguments
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The pipeline execution duration.
+ public PipelineExecutedArguments(TimeSpan duration) => Duration = duration;
+
+ internal PipelineExecutedArguments()
+ {
+ }
+
+ ///
+ /// Gets the pipeline execution duration.
+ ///
+ public TimeSpan Duration { get; internal set; }
+}
diff --git a/src/Polly.Core/Telemetry/PipelineExecutingArguments.cs b/src/Polly.Core/Telemetry/PipelineExecutingArguments.cs
new file mode 100644
index 00000000000..de6f4cc83fa
--- /dev/null
+++ b/src/Polly.Core/Telemetry/PipelineExecutingArguments.cs
@@ -0,0 +1,9 @@
+namespace Polly.Telemetry;
+
+///
+/// Arguments that indicate the pipeline execution started.
+///
+public sealed class PipelineExecutingArguments
+{
+ internal static readonly PipelineExecutingArguments Instance = new();
+}
diff --git a/src/Polly.Core/Telemetry/TelemetryUtil.cs b/src/Polly.Core/Telemetry/TelemetryUtil.cs
index 07c7d02bd86..af44bed2d8e 100644
--- a/src/Polly.Core/Telemetry/TelemetryUtil.cs
+++ b/src/Polly.Core/Telemetry/TelemetryUtil.cs
@@ -6,6 +6,10 @@ internal static class TelemetryUtil
internal const string ExecutionAttempt = "ExecutionAttempt";
+ internal const string PipelineExecuting = "PipelineExecuting";
+
+ internal const string PipelineExecuted = "PipelineExecuted";
+
public static ResilienceStrategyTelemetry CreateTelemetry(
DiagnosticSource? diagnosticSource,
string? builderName,
diff --git a/src/Polly.Core/Utils/CompositeResilienceStrategy.cs b/src/Polly.Core/Utils/CompositeResilienceStrategy.cs
index d620791ea8e..0c5508c6c35 100644
--- a/src/Polly.Core/Utils/CompositeResilienceStrategy.cs
+++ b/src/Polly.Core/Utils/CompositeResilienceStrategy.cs
@@ -1,3 +1,5 @@
+using Polly.Telemetry;
+
namespace Polly.Utils;
#pragma warning disable S2302 // "nameof" should be used
@@ -10,14 +12,16 @@ namespace Polly.Utils;
internal sealed partial class CompositeResilienceStrategy : ResilienceStrategy
{
private readonly ResilienceStrategy _firstStrategy;
+ private readonly ResilienceStrategyTelemetry _telemetry;
+ private readonly TimeProvider _timeProvider;
- public static CompositeResilienceStrategy Create(IReadOnlyList strategies)
+ public static CompositeResilienceStrategy Create(IReadOnlyList strategies, ResilienceStrategyTelemetry telemetry, TimeProvider timeProvider)
{
Guard.NotNull(strategies);
- if (strategies.Count < 2)
+ if (strategies.Count == 0)
{
- throw new InvalidOperationException("The composite resilience strategy must contain at least two resilience strategies.");
+ throw new InvalidOperationException("The composite resilience strategy must contain at least one resilience strategy.");
}
if (strategies.Distinct().Count() != strategies.Count)
@@ -25,6 +29,11 @@ public static CompositeResilienceStrategy Create(IReadOnlyList strategies)
+ private CompositeResilienceStrategy(ResilienceStrategy first, IReadOnlyList strategies, ResilienceStrategyTelemetry telemetry, TimeProvider timeProvider)
{
Strategies = strategies;
+
+ _telemetry = telemetry;
+ _timeProvider = timeProvider;
_firstStrategy = first;
}
public IReadOnlyList Strategies { get; }
- internal override ValueTask> ExecuteCore(
+ internal override async ValueTask> ExecuteCore(
Func>> callback,
- ResilienceContext context, TState state)
+ ResilienceContext context,
+ TState state)
{
+ var timeStamp = _timeProvider.GetTimestamp();
+ _telemetry.Report(new ResilienceEvent(ResilienceEventSeverity.Debug, TelemetryUtil.PipelineExecuting), context, PipelineExecutingArguments.Instance);
+
+ Outcome outcome;
+
if (context.CancellationToken.IsCancellationRequested)
{
- return Outcome.FromExceptionAsTask(new OperationCanceledException(context.CancellationToken).TrySetStackTrace());
+ outcome = Outcome.FromException(new OperationCanceledException(context.CancellationToken).TrySetStackTrace());
}
+ else
+ {
+ outcome = await _firstStrategy.ExecuteCore(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext);
+ }
+
+ var durationArgs = PipelineExecutedArguments.Get(_timeProvider.GetElapsedTime(timeStamp));
+ _telemetry.Report(
+ new ResilienceEvent(ResilienceEventSeverity.Information, TelemetryUtil.PipelineExecuted),
+ new OutcomeArguments(context, outcome, durationArgs));
+ PipelineExecutedArguments.Return(durationArgs);
- return _firstStrategy.ExecuteCore(callback, context, state);
+ return outcome;
}
///
diff --git a/src/Polly.Extensions/README.md b/src/Polly.Extensions/README.md
index 0215b86124b..839d3862a41 100644
--- a/src/Polly.Extensions/README.md
+++ b/src/Polly.Extensions/README.md
@@ -118,11 +118,11 @@ Dimensions:
|`attempt-number`| The execution attempt number, starting at 0 (0, 1, 2). |
|`attempt-handled`| Indicates if the execution outcome was handled. A handled outcome indicates execution failure and the need for retry (`true`, `false`). |
-#### strategy-execution-duration
+#### pipeline-execution-duration
- Type: *Histogram*
- Unit: *milliseconds*
-- Description: Measures the duration and results of resilience strategy executions.
+- Description: Measures the duration and results of resilience pipelines.
Dimensions:
diff --git a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs
index 2469d28fe23..0e5e4ef7973 100644
--- a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs
+++ b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs
@@ -27,12 +27,19 @@ public ResilienceTelemetryDiagnosticSource(TelemetryOptions options)
"execution-attempt-duration",
unit: "ms",
description: "Tracks the duration of execution attempts.");
+
+ ExecutionDuration = Meter.CreateHistogram(
+ "pipeline-execution-duration",
+ unit: "ms",
+ description: "The execution duration and execution results of resilience pipelines.");
}
public Counter Counter { get; }
public Histogram AttemptDuration { get; }
+ public Histogram ExecutionDuration { get; }
+
public override bool IsEnabled(string name) => true;
#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
@@ -89,7 +96,22 @@ private void MeterEvent(TelemetryEventArguments args)
{
var source = args.Source;
- if (args.Arguments is ExecutionAttemptArguments executionAttempt)
+ if (args.Arguments is PipelineExecutedArguments executionFinishedArguments)
+ {
+ if (!ExecutionDuration.Enabled)
+ {
+ return;
+ }
+
+ var enrichmentContext = EnrichmentContext.Get(args.Context, null, args.Outcome);
+ AddCommonTags(args, source, enrichmentContext);
+ enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExecutionHealth, args.Context.GetExecutionHealth()));
+ EnrichmentUtil.Enrich(enrichmentContext, _enrichers);
+
+ ExecutionDuration.Record(executionFinishedArguments.Duration.TotalMilliseconds, enrichmentContext.TagsSpan);
+ EnrichmentContext.Return(enrichmentContext);
+ }
+ else if (args.Arguments is ExecutionAttemptArguments executionAttempt)
{
if (!AttemptDuration.Enabled)
{
@@ -101,7 +123,7 @@ private void MeterEvent(TelemetryEventArguments args)
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.AttemptNumber, executionAttempt.AttemptNumber.AsBoxedInt()));
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.AttemptHandled, executionAttempt.Handled.AsBoxedBool()));
EnrichmentUtil.Enrich(enrichmentContext, _enrichers);
- AttemptDuration.Record(executionAttempt.ExecutionTime.TotalMilliseconds, enrichmentContext.TagsSpan);
+ AttemptDuration.Record(executionAttempt.Duration.TotalMilliseconds, enrichmentContext.TagsSpan);
EnrichmentContext.Return(enrichmentContext);
}
else if (Counter.Enabled)
@@ -128,7 +150,30 @@ private void LogEvent(TelemetryEventArguments args)
var level = args.Event.Severity.AsLogLevel();
- if (args.Arguments is ExecutionAttemptArguments executionAttempt)
+ if (args.Arguments is PipelineExecutingArguments pipelineExecutionStarted)
+ {
+ _logger.ExecutingStrategy(
+ args.Source.BuilderName.GetValueOrPlaceholder(),
+ args.Source.BuilderInstanceName.GetValueOrPlaceholder(),
+ args.Context.OperationKey,
+ args.Context.GetResultType());
+ }
+ else if (args.Arguments is PipelineExecutedArguments pipelineExecutionFinished)
+ {
+ var logLevel = args.Context.IsExecutionHealthy() ? LogLevel.Debug : LogLevel.Warning;
+
+ _logger.StrategyExecuted(
+ logLevel,
+ args.Source.BuilderName.GetValueOrPlaceholder(),
+ args.Source.BuilderInstanceName.GetValueOrPlaceholder(),
+ args.Context.OperationKey,
+ args.Context.GetResultType(),
+ ExpandOutcome(args.Context, args.Outcome),
+ args.Context.GetExecutionHealth(),
+ pipelineExecutionFinished.Duration.TotalMilliseconds,
+ args.Outcome?.Exception);
+ }
+ else if (args.Arguments is ExecutionAttemptArguments executionAttempt)
{
if (_logger.IsEnabled(level))
{
@@ -141,7 +186,7 @@ private void LogEvent(TelemetryEventArguments args)
result,
executionAttempt.Handled,
executionAttempt.AttemptNumber,
- executionAttempt.ExecutionTime.TotalMilliseconds,
+ executionAttempt.Duration.TotalMilliseconds,
args.Outcome?.Exception);
}
}
@@ -158,4 +203,15 @@ private void LogEvent(TelemetryEventArguments args)
args.Outcome?.Exception);
}
}
+
+ private object? ExpandOutcome(ResilienceContext context, Outcome
/// The strategies the pipeline is composed of.
- /// Determines whether the pipeline has telemetry enabled.
/// Determines whether the resilience strategy is reloadable.
- public InnerStrategiesDescriptor(IReadOnlyList strategies, bool hasTelemetry, bool isReloadable)
+ public InnerStrategiesDescriptor(IReadOnlyList strategies, bool isReloadable)
{
Strategies = strategies;
- HasTelemetry = hasTelemetry;
IsReloadable = isReloadable;
}
@@ -24,9 +22,9 @@ public InnerStrategiesDescriptor(IReadOnlyList str
public IReadOnlyList Strategies { get; }
///
- /// Gets a value indicating whether the pipeline has telemetry enabled.
+ /// Gets the first strategy of the pipeline.
///
- public bool HasTelemetry { get; }
+ public ResilienceStrategyDescriptor FirstStrategy => Strategies[0];
///
/// Gets a value indicating whether the resilience strategy is reloadable.
diff --git a/src/Polly.Testing/PublicAPI.Unshipped.txt b/src/Polly.Testing/PublicAPI.Unshipped.txt
index 932466695a8..86b0886af86 100644
--- a/src/Polly.Testing/PublicAPI.Unshipped.txt
+++ b/src/Polly.Testing/PublicAPI.Unshipped.txt
@@ -1,13 +1,13 @@
#nullable enable
Polly.Testing.InnerStrategiesDescriptor
-Polly.Testing.InnerStrategiesDescriptor.HasTelemetry.get -> bool
-Polly.Testing.InnerStrategiesDescriptor.InnerStrategiesDescriptor(System.Collections.Generic.IReadOnlyList! strategies, bool hasTelemetry, bool isReloadable) -> void
+Polly.Testing.InnerStrategiesDescriptor.FirstStrategy.get -> Polly.Testing.ResilienceStrategyDescriptor!
+Polly.Testing.InnerStrategiesDescriptor.InnerStrategiesDescriptor(System.Collections.Generic.IReadOnlyList! strategies, bool isReloadable) -> void
Polly.Testing.InnerStrategiesDescriptor.IsReloadable.get -> bool
Polly.Testing.InnerStrategiesDescriptor.Strategies.get -> System.Collections.Generic.IReadOnlyList!
Polly.Testing.ResilienceStrategyDescriptor
Polly.Testing.ResilienceStrategyDescriptor.Options.get -> Polly.ResilienceStrategyOptions?
-Polly.Testing.ResilienceStrategyDescriptor.ResilienceStrategyDescriptor(Polly.ResilienceStrategyOptions? options, System.Type! strategyType) -> void
-Polly.Testing.ResilienceStrategyDescriptor.StrategyType.get -> System.Type!
+Polly.Testing.ResilienceStrategyDescriptor.ResilienceStrategyDescriptor(Polly.ResilienceStrategyOptions? options, object! strategyInstance) -> void
+Polly.Testing.ResilienceStrategyDescriptor.StrategyInstance.get -> object!
Polly.Testing.ResilienceStrategyExtensions
static Polly.Testing.ResilienceStrategyExtensions.GetInnerStrategies(this Polly.ResilienceStrategy! strategy) -> Polly.Testing.InnerStrategiesDescriptor!
static Polly.Testing.ResilienceStrategyExtensions.GetInnerStrategies(this Polly.ResilienceStrategy! strategy) -> Polly.Testing.InnerStrategiesDescriptor!
diff --git a/src/Polly.Testing/ResilienceStrategyDescriptor.cs b/src/Polly.Testing/ResilienceStrategyDescriptor.cs
index dbba00cfeeb..129c6f9af27 100644
--- a/src/Polly.Testing/ResilienceStrategyDescriptor.cs
+++ b/src/Polly.Testing/ResilienceStrategyDescriptor.cs
@@ -9,11 +9,11 @@ public sealed class ResilienceStrategyDescriptor
/// Initializes a new instance of the class.
///
/// The options used by the resilience strategy, if any.
- /// The type of the strategy.
- public ResilienceStrategyDescriptor(ResilienceStrategyOptions? options, Type strategyType)
+ /// The strategy instance.
+ public ResilienceStrategyDescriptor(ResilienceStrategyOptions? options, object strategyInstance)
{
Options = options;
- StrategyType = strategyType;
+ StrategyInstance = strategyInstance;
}
///
@@ -22,7 +22,7 @@ public ResilienceStrategyDescriptor(ResilienceStrategyOptions? options, Type str
public ResilienceStrategyOptions? Options { get; }
///
- /// Gets the type of the strategy.
+ /// Gets the strategy instance.
///
- public Type StrategyType { get; }
+ public object StrategyInstance { get; }
}
diff --git a/src/Polly.Testing/ResilienceStrategyExtensions.cs b/src/Polly.Testing/ResilienceStrategyExtensions.cs
index 5a115f5c66f..4b94b70933e 100644
--- a/src/Polly.Testing/ResilienceStrategyExtensions.cs
+++ b/src/Polly.Testing/ResilienceStrategyExtensions.cs
@@ -7,8 +7,6 @@ namespace Polly.Testing;
///
public static class ResilienceStrategyExtensions
{
- private const string TelemetryResilienceStrategy = "Polly.Telemetry.TelemetryResilienceStrategy";
-
///
/// Gets the inner strategies the is composed of.
///
@@ -41,30 +39,29 @@ private static InnerStrategiesDescriptor GetInnerStrategiesCore(ResilienceStr
var strategies = new List();
strategy.ExpandStrategies(strategies);
- var innerStrategies = strategies.Select(s => new ResilienceStrategyDescriptor(s.Options, GetStrategyType(s))).ToList();
+ var innerStrategies = strategies.Select(s => new ResilienceStrategyDescriptor(s.Options, GetStrategyInstance(s))).ToList();
return new InnerStrategiesDescriptor(
- innerStrategies.Where(s => !ShouldSkip(s.StrategyType)).ToList().AsReadOnly(),
- hasTelemetry: innerStrategies.Exists(s => s.StrategyType.FullName == TelemetryResilienceStrategy),
- isReloadable: innerStrategies.Exists(s => s.StrategyType == typeof(ReloadableResilienceStrategy)));
+ innerStrategies.Where(s => !ShouldSkip(s.StrategyInstance)).ToList().AsReadOnly(),
+ isReloadable: innerStrategies.Exists(s => s.StrategyInstance is ReloadableResilienceStrategy));
}
- private static Type GetStrategyType(ResilienceStrategy strategy)
+ private static object GetStrategyInstance(ResilienceStrategy strategy)
{
if (strategy is ReactiveResilienceStrategyBridge reactiveBridge)
{
- return reactiveBridge.Strategy.GetType();
+ return reactiveBridge.Strategy;
}
if (strategy is NonReactiveResilienceStrategyBridge nonReactiveBridge)
{
- return nonReactiveBridge.Strategy.GetType();
+ return nonReactiveBridge.Strategy;
}
- return strategy.GetType();
+ return strategy;
}
- private static bool ShouldSkip(Type type) => type == typeof(ReloadableResilienceStrategy) || type.FullName == TelemetryResilienceStrategy;
+ private static bool ShouldSkip(object instance) => instance is ReloadableResilienceStrategy;
private static void ExpandStrategies(this ResilienceStrategy strategy, List strategies)
{
diff --git a/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerCompositeStrategyBuilderTests.cs b/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerCompositeStrategyBuilderTests.cs
index 95628ed9bd4..575febe550d 100644
--- a/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerCompositeStrategyBuilderTests.cs
+++ b/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerCompositeStrategyBuilderTests.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Time.Testing;
using Polly.CircuitBreaker;
-using Polly.Utils;
+using Polly.Testing;
namespace Polly.Core.Tests.CircuitBreaker;
@@ -31,12 +31,7 @@ public void AddCircuitBreaker_Configure(Action builder
builderAction(builder);
- var strategy = builder.Build();
-
- strategy
- .Should().BeOfType>().Subject
- .Strategy
- .Should().BeOfType>();
+ builder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType>();
}
[MemberData(nameof(ConfigureDataGeneric))]
@@ -47,12 +42,7 @@ public void AddCircuitBreaker_Generic_Configure(Action>().Subject
- .Strategy
- .Should().BeOfType>();
+ builder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType>();
}
[Fact]
diff --git a/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs b/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs
index 8856e8ed167..e6e50ba898c 100644
--- a/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs
+++ b/test/Polly.Core.Tests/CompositeStrategyBuilderTests.cs
@@ -2,6 +2,7 @@
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Polly.Retry;
+using Polly.Testing;
using Polly.Utils;
namespace Polly.Core.Tests;
@@ -28,7 +29,6 @@ public void CopyCtor_Ok()
Name = "dummy",
Randomizer = () => 0.0,
DiagnosticSource = Substitute.For(),
- OnCreatingStrategy = _ => { },
};
builder.Properties.Set(new ResiliencePropertyKey("dummy"), "dummy");
@@ -38,7 +38,6 @@ public void CopyCtor_Ok()
other.TimeProvider.Should().Be(builder.TimeProvider);
other.Randomizer.Should().BeSameAs(builder.Randomizer);
other.DiagnosticSource.Should().BeSameAs(builder.DiagnosticSource);
- other.OnCreatingStrategy.Should().BeSameAs(builder.OnCreatingStrategy);
other.Properties.GetValue(new ResiliencePropertyKey("dummy"), "").Should().Be("dummy");
}
@@ -61,7 +60,8 @@ public void AddStrategy_Single_Ok()
// assert
strategy.Execute(_ => executions.Add(2));
- ((NonReactiveResilienceStrategyBridge)strategy).Strategy.Should().BeOfType();
+
+ strategy.GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType();
executions.Should().BeInAscendingOrder();
executions.Should().HaveCount(3);
}
@@ -337,29 +337,6 @@ public void BuildStrategy_EnsureCorrectContext()
verified2.Should().BeTrue();
}
- [Fact]
- public void Build_OnCreatingStrategy_EnsureRespected()
- {
- // arrange
- var strategy = new TestResilienceStrategy().AsStrategy();
- var builder = new CompositeStrategyBuilder
- {
- OnCreatingStrategy = strategies =>
- {
- strategies.Should().ContainSingle(s => s == strategy);
- strategies.Insert(0, new TestResilienceStrategy().AsStrategy());
- }
- };
-
- builder.AddStrategy(strategy);
-
- // act
- var finalStrategy = builder.Build();
-
- // assert
- finalStrategy.Should().BeOfType();
- }
-
[Fact]
public void EmptyOptions_Ok() => CompositeStrategyBuilderExtensions.EmptyOptions.Instance.Name.Should().BeNull();
diff --git a/test/Polly.Core.Tests/Fallback/FallbackCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Fallback/FallbackCompositeStrategyBuilderExtensionsTests.cs
index 0b86d91e255..d428655818c 100644
--- a/test/Polly.Core.Tests/Fallback/FallbackCompositeStrategyBuilderExtensionsTests.cs
+++ b/test/Polly.Core.Tests/Fallback/FallbackCompositeStrategyBuilderExtensionsTests.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Polly.Fallback;
-using Polly.Utils;
+using Polly.Testing;
namespace Polly.Core.Tests.Fallback;
@@ -25,10 +25,7 @@ public void AddFallback_Generic_Ok(Action> configu
var builder = new CompositeStrategyBuilder();
configure(builder);
- builder.Build().Strategy
- .Should().BeOfType>().Subject
- .Strategy
- .Should().BeOfType>();
+ builder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType(typeof(FallbackResilienceStrategy));
}
[Fact]
diff --git a/test/Polly.Core.Tests/GenericCompositeStrategyBuilderTests.cs b/test/Polly.Core.Tests/GenericCompositeStrategyBuilderTests.cs
index d3eb4504025..6cc3b69bcac 100644
--- a/test/Polly.Core.Tests/GenericCompositeStrategyBuilderTests.cs
+++ b/test/Polly.Core.Tests/GenericCompositeStrategyBuilderTests.cs
@@ -13,7 +13,6 @@ public void Ctor_EnsureDefaults()
_builder.Name.Should().BeNull();
_builder.Properties.Should().NotBeNull();
_builder.TimeProvider.Should().Be(TimeProvider.System);
- _builder.OnCreatingStrategy.Should().BeNull();
}
[Fact]
@@ -31,9 +30,6 @@ public void Properties_GetSet_Ok()
var timeProvider = new FakeTimeProvider();
_builder.TimeProvider = timeProvider;
_builder.TimeProvider.Should().Be(timeProvider);
-
- _builder.OnCreatingStrategy = s => { };
- _builder.OnCreatingStrategy.Should().NotBeNull();
}
[Fact]
@@ -63,6 +59,6 @@ public void AddGenericStrategy_Ok()
// assert
strategy.Should().NotBeNull();
- strategy.Strategy.Should().Be(testStrategy.Strategy);
+ ((CompositeResilienceStrategy)strategy.Strategy).Strategies[0].Should().Be(testStrategy.Strategy);
}
}
diff --git a/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs b/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs
index c7214cffef5..7ed4b7cdf2b 100644
--- a/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs
+++ b/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs
@@ -23,7 +23,7 @@ public TaskExecutionTests()
{
if (args.Arguments is ExecutionAttemptArguments attempt)
{
- _args.Add(ExecutionAttemptArguments.Get(attempt.AttemptNumber, attempt.ExecutionTime, attempt.Handled));
+ _args.Add(ExecutionAttemptArguments.Get(attempt.AttemptNumber, attempt.Duration, attempt.Handled));
}
});
diff --git a/test/Polly.Core.Tests/Hedging/HedgingCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Hedging/HedgingCompositeStrategyBuilderExtensionsTests.cs
index a4c99ac3644..3afda1956bc 100644
--- a/test/Polly.Core.Tests/Hedging/HedgingCompositeStrategyBuilderExtensionsTests.cs
+++ b/test/Polly.Core.Tests/Hedging/HedgingCompositeStrategyBuilderExtensionsTests.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Polly.Hedging;
-using Polly.Utils;
+using Polly.Testing;
namespace Polly.Core.Tests.Hedging;
@@ -14,11 +14,9 @@ public void AddHedging_Ok()
{
_builder.AddHedging(new HedgingStrategyOptions { ShouldHandle = _ => PredicateResult.True });
- _builder.Build()
- .Should().BeOfType>().Subject
- .Strategy
- .Should().BeOfType>()
- .Subject.HedgingHandler.IsGeneric.Should().BeFalse();
+ _builder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance
+ .Should().BeOfType>().Subject
+ .HedgingHandler.IsGeneric.Should().BeFalse();
}
[Fact]
@@ -30,11 +28,9 @@ public void AddHedging_Generic_Ok()
ShouldHandle = _ => PredicateResult.True
});
- _genericBuilder.Build().Strategy
- .Should().BeOfType>().Subject
- .Strategy
- .Should().BeOfType>()
- .Subject.HedgingHandler.IsGeneric.Should().BeTrue();
+ _genericBuilder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance
+ .Should().BeOfType>().Subject
+ .HedgingHandler.IsGeneric.Should().BeTrue();
}
[Fact]
diff --git a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs
index 7331e4d2871..a6aabcccfb5 100644
--- a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs
+++ b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs
@@ -88,11 +88,11 @@ public void ExecutePrimaryAndSecondary_EnsureAttemptReported()
var attempts = _events.Select(v => v.Arguments).OfType().ToArray();
attempts[0].Handled.Should().BeTrue();
- attempts[0].ExecutionTime.Should().BeGreaterThan(TimeSpan.Zero);
+ attempts[0].Duration.Should().BeGreaterThan(TimeSpan.Zero);
attempts[0].AttemptNumber.Should().Be(0);
attempts[1].Handled.Should().BeTrue();
- attempts[1].ExecutionTime.Should().BeGreaterThan(TimeSpan.Zero);
+ attempts[1].Duration.Should().BeGreaterThan(TimeSpan.Zero);
attempts[1].AttemptNumber.Should().Be(1);
}
diff --git a/test/Polly.Core.Tests/Hedging/OnHedgingArgumentsTests.cs b/test/Polly.Core.Tests/Hedging/OnHedgingArgumentsTests.cs
index 35e83a47086..69f0f6c813f 100644
--- a/test/Polly.Core.Tests/Hedging/OnHedgingArgumentsTests.cs
+++ b/test/Polly.Core.Tests/Hedging/OnHedgingArgumentsTests.cs
@@ -11,6 +11,6 @@ public void Ctor_Ok()
args.AttemptNumber.Should().Be(1);
args.HasOutcome.Should().BeTrue();
- args.ExecutionTime.Should().Be(TimeSpan.FromSeconds(1));
+ args.Duration.Should().Be(TimeSpan.FromSeconds(1));
}
}
diff --git a/test/Polly.Core.Tests/Polly.Core.Tests.csproj b/test/Polly.Core.Tests/Polly.Core.Tests.csproj
index 0c709602b04..51e78af5d4b 100644
--- a/test/Polly.Core.Tests/Polly.Core.Tests.csproj
+++ b/test/Polly.Core.Tests/Polly.Core.Tests.csproj
@@ -15,6 +15,7 @@
+
diff --git a/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs b/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs
index ae18cef3d75..da6b19f8dae 100644
--- a/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs
+++ b/test/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs
@@ -1,8 +1,8 @@
using System.Globalization;
using Polly.Registry;
using Polly.Retry;
+using Polly.Testing;
using Polly.Timeout;
-using Polly.Utils;
namespace Polly.Core.Tests.Registry;
@@ -436,8 +436,8 @@ public void GetOrAddStrategy_Ok()
var strategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; });
var otherStrategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; });
- ((NonReactiveResilienceStrategyBridge)strategy).Strategy.Should().BeOfType();
- strategy.Should().BeSameAs(otherStrategy);
+ strategy.GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType();
+
called.Should().Be(1);
}
@@ -451,8 +451,7 @@ public void GetOrAddStrategy_Generic_Ok()
var strategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; });
var otherStrategy = registry.GetOrAddStrategy(id, builder => { builder.AddTimeout(TimeSpan.FromSeconds(1)); called++; });
- ((NonReactiveResilienceStrategyBridge)strategy.Strategy).Strategy.Should().BeOfType();
- strategy.Should().BeSameAs(otherStrategy);
+ strategy.GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType();
}
private ResilienceStrategyRegistry CreateRegistry() => new(_options);
diff --git a/test/Polly.Core.Tests/ResilienceStrategyTests.cs b/test/Polly.Core.Tests/ResilienceStrategyTests.cs
index b0e867942d4..9e64afdc4df 100644
--- a/test/Polly.Core.Tests/ResilienceStrategyTests.cs
+++ b/test/Polly.Core.Tests/ResilienceStrategyTests.cs
@@ -13,7 +13,7 @@ public void DebuggerProxy_Ok()
{
new TestResilienceStrategy().AsStrategy(),
new TestResilienceStrategy().AsStrategy()
- });
+ }, null!, null!);
new CompositeResilienceStrategy.DebuggerProxy(pipeline).Strategies.Should().HaveCount(2);
}
diff --git a/test/Polly.Core.Tests/Retry/RetryCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Retry/RetryCompositeStrategyBuilderExtensionsTests.cs
index fe1598cee34..54f4aa8ea75 100644
--- a/test/Polly.Core.Tests/Retry/RetryCompositeStrategyBuilderExtensionsTests.cs
+++ b/test/Polly.Core.Tests/Retry/RetryCompositeStrategyBuilderExtensionsTests.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Polly.Retry;
-using Polly.Utils;
+using Polly.Testing;
namespace Polly.Core.Tests.Retry;
@@ -71,7 +71,7 @@ public void AddRetry_DefaultOptions_Ok()
private static void AssertStrategy(CompositeStrategyBuilder builder, RetryBackoffType type, int retries, TimeSpan delay, Action>? assert = null)
{
- var strategy = (RetryResilienceStrategy)((ReactiveResilienceStrategyBridge)builder.Build()).Strategy;
+ var strategy = builder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType>().Subject;
strategy.BackoffType.Should().Be(type);
strategy.RetryCount.Should().Be(retries);
@@ -80,9 +80,14 @@ private static void AssertStrategy(CompositeStrategyBuilder builder, RetryBackof
assert?.Invoke(strategy);
}
- private static void AssertStrategy(CompositeStrategyBuilder builder, RetryBackoffType type, int retries, TimeSpan delay, Action>? assert = null)
+ private static void AssertStrategy(
+ CompositeStrategyBuilder builder,
+ RetryBackoffType type,
+ int retries,
+ TimeSpan delay,
+ Action>? assert = null)
{
- var strategy = (RetryResilienceStrategy)((ReactiveResilienceStrategyBridge)builder.Build().Strategy).Strategy;
+ var strategy = builder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType>().Subject;
strategy.BackoffType.Should().Be(type);
strategy.RetryCount.Should().Be(retries);
diff --git a/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs b/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs
index 6ef57f1e4b2..1bbadbf297b 100644
--- a/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs
+++ b/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs
@@ -269,7 +269,7 @@ public void Execute_EnsureAttemptReported()
attempt.Handled.Should().BeFalse();
attempt.AttemptNumber.Should().Be(0);
- attempt.ExecutionTime.Should().Be(TimeSpan.FromSeconds(1));
+ attempt.Duration.Should().Be(TimeSpan.FromSeconds(1));
called = true;
});
diff --git a/test/Polly.Core.Tests/Telemetry/ExecutionAttemptArgumentsTests.cs b/test/Polly.Core.Tests/Telemetry/ExecutionAttemptArgumentsTests.cs
index 001bdeadbe3..1042b4574aa 100644
--- a/test/Polly.Core.Tests/Telemetry/ExecutionAttemptArgumentsTests.cs
+++ b/test/Polly.Core.Tests/Telemetry/ExecutionAttemptArgumentsTests.cs
@@ -10,7 +10,7 @@ public void Ctor_Ok()
var args = new ExecutionAttemptArguments(99, TimeSpan.MaxValue, true);
Assert.NotNull(args);
args.AttemptNumber.Should().Be(99);
- args.ExecutionTime.Should().Be(TimeSpan.MaxValue);
+ args.Duration.Should().Be(TimeSpan.MaxValue);
args.Handled.Should().BeTrue();
}
@@ -20,7 +20,7 @@ public void Get_Ok()
var args = ExecutionAttemptArguments.Get(99, TimeSpan.MaxValue, true);
Assert.NotNull(args);
args.AttemptNumber.Should().Be(99);
- args.ExecutionTime.Should().Be(TimeSpan.MaxValue);
+ args.Duration.Should().Be(TimeSpan.MaxValue);
args.Handled.Should().BeTrue();
}
@@ -32,7 +32,7 @@ public void Return_EnsurePropertiesCleared()
ExecutionAttemptArguments.Return(args);
args.AttemptNumber.Should().Be(0);
- args.ExecutionTime.Should().Be(TimeSpan.Zero);
+ args.Duration.Should().Be(TimeSpan.Zero);
args.Handled.Should().BeFalse();
}
}
diff --git a/test/Polly.Core.Tests/Telemetry/PipelineExecutedArgumentsTests.cs b/test/Polly.Core.Tests/Telemetry/PipelineExecutedArgumentsTests.cs
new file mode 100644
index 00000000000..50a04240285
--- /dev/null
+++ b/test/Polly.Core.Tests/Telemetry/PipelineExecutedArgumentsTests.cs
@@ -0,0 +1,31 @@
+using Polly.Telemetry;
+
+namespace Polly.Extensions.Tests.Telemetry;
+
+public class PipelineExecutedArgumentsTests
+{
+ [Fact]
+ public void Ctor_Ok()
+ {
+ var args = new PipelineExecutedArguments(TimeSpan.MaxValue);
+ args.Duration.Should().Be(TimeSpan.MaxValue);
+ }
+
+ [Fact]
+ public void Get_Ok()
+ {
+ var args = PipelineExecutedArguments.Get(TimeSpan.MaxValue);
+ Assert.NotNull(args);
+ args.Duration.Should().Be(TimeSpan.MaxValue);
+ }
+
+ [Fact]
+ public void Return_EnsurePropertiesCleared()
+ {
+ var args = PipelineExecutedArguments.Get(TimeSpan.MaxValue);
+
+ PipelineExecutedArguments.Return(args);
+
+ args.Duration.Should().Be(TimeSpan.Zero);
+ }
+}
diff --git a/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs
index 1e61f5bc7ad..7a34b55376f 100644
--- a/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs
+++ b/test/Polly.Core.Tests/Timeout/TimeoutCompositeStrategyBuilderExtensionsTests.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
+using Polly.Testing;
using Polly.Timeout;
-using Polly.Utils;
namespace Polly.Core.Tests.Timeout;
@@ -33,9 +33,8 @@ internal void AddTimeout_Ok(TimeSpan timeout, Action();
configure(builder);
- var strategy = ((NonReactiveResilienceStrategyBridge)builder.Build().Strategy).Strategy.Should().BeOfType().Subject;
+ var strategy = builder.Build().GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType().Subject;
assert(strategy);
-
GetTimeout(strategy).Should().Be(timeout);
}
@@ -44,7 +43,7 @@ public void AddTimeout_Options_Ok()
{
var strategy = new CompositeStrategyBuilder().AddTimeout(new TimeoutStrategyOptions()).Build();
- ((NonReactiveResilienceStrategyBridge)strategy).Strategy.Should().BeOfType();
+ strategy.GetInnerStrategies().FirstStrategy.StrategyInstance.Should().BeOfType();
}
[Fact]
diff --git a/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs b/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs
index d35ebb67bef..5a6e56183f4 100644
--- a/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs
+++ b/test/Polly.Core.Tests/Utils/CompositeResilienceStrategyTests.cs
@@ -1,20 +1,28 @@
+using Microsoft.Extensions.Time.Testing;
+using NSubstitute;
+using Polly.Telemetry;
using Polly.Utils;
namespace Polly.Core.Tests.Utils;
public class CompositeResilienceStrategyTests
{
+ private readonly ResilienceStrategyTelemetry _telemetry;
+ private Action? _onTelemetry;
+
+ public CompositeResilienceStrategyTests()
+ => _telemetry = TestUtilities.CreateResilienceTelemetry(args => _onTelemetry?.Invoke(args));
+
[Fact]
public void Create_ArgValidation()
{
- Assert.Throws(() => CompositeResilienceStrategy.Create(null!));
- Assert.Throws(() => CompositeResilienceStrategy.Create(Array.Empty()));
- Assert.Throws(() => CompositeResilienceStrategy.Create(new[] { new TestResilienceStrategy().AsStrategy() }));
+ Assert.Throws(() => CompositeResilienceStrategy.Create(null!, null!, null!));
+ Assert.Throws(() => CompositeResilienceStrategy.Create(Array.Empty(), null!, null!));
Assert.Throws(() => CompositeResilienceStrategy.Create(new ResilienceStrategy[]
{
NullResilienceStrategy.Instance,
NullResilienceStrategy.Instance
- }));
+ }, null!, null!));
}
[Fact]
@@ -27,7 +35,7 @@ public void Create_EnsureOriginalStrategiesPreserved()
new TestResilienceStrategy().AsStrategy(),
};
- var pipeline = CompositeResilienceStrategy.Create(strategies);
+ var pipeline = CreateSut(strategies);
for (var i = 0; i < strategies.Length; i++)
{
@@ -46,7 +54,7 @@ public async Task Create_EnsureExceptionsNotWrapped()
new Strategy().AsStrategy(),
};
- var pipeline = CompositeResilienceStrategy.Create(strategies);
+ var pipeline = CreateSut(strategies);
await pipeline
.Invoking(p => p.ExecuteCore((_, _) => Outcome.FromResultAsTask(10), ResilienceContextPool.Shared.Get(), "state").AsTask())
.Should()
@@ -63,11 +71,11 @@ public void Create_EnsurePipelineReusableAcrossDifferentPipelines()
new TestResilienceStrategy().AsStrategy(),
};
- var pipeline = CompositeResilienceStrategy.Create(strategies);
+ var pipeline = CreateSut(strategies);
- CompositeResilienceStrategy.Create(new ResilienceStrategy[] { NullResilienceStrategy.Instance, pipeline });
+ CreateSut(new ResilienceStrategy[] { NullResilienceStrategy.Instance, pipeline });
- this.Invoking(_ => CompositeResilienceStrategy.Create(new ResilienceStrategy[] { NullResilienceStrategy.Instance, pipeline }))
+ this.Invoking(_ => CreateSut(new ResilienceStrategy[] { NullResilienceStrategy.Instance, pipeline }))
.Should()
.NotThrow();
}
@@ -83,7 +91,7 @@ public async Task Create_Cancelled_EnsureNoExecution()
new TestResilienceStrategy().AsStrategy(),
};
- var pipeline = CompositeResilienceStrategy.Create(strategies);
+ var pipeline = CreateSut(strategies, new FakeTimeProvider());
var context = ResilienceContextPool.Shared.Get();
context.CancellationToken = cancellation.Token;
@@ -101,8 +109,7 @@ public async Task Create_CancelledLater_EnsureNoExecution()
new TestResilienceStrategy { Before = (_, _) => { executed = true; cancellation.Cancel(); } }.AsStrategy(),
new TestResilienceStrategy().AsStrategy(),
};
-
- var pipeline = CompositeResilienceStrategy.Create(strategies);
+ var pipeline = CreateSut(strategies, new FakeTimeProvider());
var context = ResilienceContextPool.Shared.Get();
context.CancellationToken = cancellation.Token;
@@ -111,6 +118,35 @@ public async Task Create_CancelledLater_EnsureNoExecution()
executed.Should().BeTrue();
}
+ [Fact]
+ public void ExecuptePipeline_EnsureTelemetryArgumentsReported()
+ {
+ var items = new List();
+ var timeProvider = new FakeTimeProvider();
+
+ _onTelemetry = args =>
+ {
+ if (args.Arguments is PipelineExecutedArguments executed)
+ {
+ executed.Duration.Should().Be(TimeSpan.FromHours(1));
+ }
+
+ items.Add(args.Arguments);
+ };
+
+ var pipeline = CreateSut(new[] { new TestResilienceStrategy().AsStrategy() }, timeProvider);
+ pipeline.Execute(() => { timeProvider.Advance(TimeSpan.FromHours(1)); });
+
+ items.Should().HaveCount(2);
+ items[0].Should().Be(PipelineExecutingArguments.Instance);
+ items[1].Should().BeOfType();
+ }
+
+ private CompositeResilienceStrategy CreateSut(ResilienceStrategy[] strategies, TimeProvider? timeProvider = null)
+ {
+ return CompositeResilienceStrategy.Create(strategies, _telemetry, timeProvider ?? Substitute.For());
+ }
+
private class Strategy : NonReactiveResilienceStrategy
{
protected internal override async ValueTask> ExecuteCore(
diff --git a/test/Polly.Extensions.Tests/Issues/IssuesTests.StrategiesPerEndpoint_1365.cs b/test/Polly.Extensions.Tests/Issues/IssuesTests.StrategiesPerEndpoint_1365.cs
index 39fbe6a4589..c40961b0c6d 100644
--- a/test/Polly.Extensions.Tests/Issues/IssuesTests.StrategiesPerEndpoint_1365.cs
+++ b/test/Polly.Extensions.Tests/Issues/IssuesTests.StrategiesPerEndpoint_1365.cs
@@ -98,7 +98,7 @@ public void StrategiesPerEndpoint_1365()
provider.GetStrategy(resource2Key).Should().BeSameAs(strategy2);
strategy1.Execute(() => { });
- events.Should().HaveCount(3);
+ events.Should().HaveCount(5);
events[0].Tags["builder-name"].Should().Be("endpoint-pipeline");
events[0].Tags["builder-instance"].Should().Be("Endpoint 1/Resource 1");
}
diff --git a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs
index 1268e5c3925..8b96cb873c2 100644
--- a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs
+++ b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs
@@ -37,6 +37,9 @@ public void Meter_Ok()
source.Counter.Description.Should().Be("Tracks the number of resilience events that occurred in resilience strategies.");
source.AttemptDuration.Description.Should().Be("Tracks the duration of execution attempts.");
source.AttemptDuration.Unit.Should().Be("ms");
+
+ source.ExecutionDuration.Description.Should().Be("The execution duration and execution results of resilience pipelines.");
+ source.ExecutionDuration.Unit.Should().Be("ms");
}
[Fact]
@@ -365,6 +368,137 @@ public void OnTelemetryEvent_Ok(bool hasCallback)
called.Should().Be(hasCallback);
}
+ [InlineData(true, false)]
+ [InlineData(false, false)]
+ [InlineData(true, true)]
+ [InlineData(false, true)]
+ [Theory]
+ public void PipelineExecution_Logged(bool healthy, bool exception)
+ {
+ var healthString = healthy ? "Healthy" : "Unhealthy";
+ var context = ResilienceContextPool.Shared.Get("op-key").WithResultType();
+ var telemetry = Create();
+ var outcome = exception ? Outcome.FromException(new InvalidOperationException("dummy message")) : Outcome.FromResult((object)10);
+ var result = exception ? "dummy message" : "10";
+
+ if (!healthy)
+ {
+ ((List)context.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy"));
+ }
+
+ ReportEvent(telemetry, outcome: outcome, arg: new PipelineExecutingArguments(), context: context);
+ ReportEvent(telemetry, outcome: outcome, arg: new PipelineExecutedArguments(TimeSpan.FromSeconds(10)), context: context);
+
+ var messages = _logger.GetRecords(new EventId(1, "StrategyExecuting")).ToList();
+ messages.Should().HaveCount(1);
+ messages[0].Message.Should().Be("Resilience strategy executing. Source: 'my-builder/builder-instance', Operation Key: 'op-key', Result Type: 'Int32'");
+ messages = _logger.GetRecords(new EventId(2, "StrategyExecuted")).ToList();
+ messages.Should().HaveCount(1);
+ messages[0].Message.Should().Match($"Resilience strategy executed. Source: 'my-builder/builder-instance', Operation Key: 'op-key', Result Type: 'Int32', Result: '{result}', Execution Health: '{healthString}', Execution Time: 10000ms");
+ messages[0].LogLevel.Should().Be(healthy ? LogLevel.Debug : LogLevel.Warning);
+ }
+
+ [Fact]
+ public void PipelineExecution_VoidResult_Ok()
+ {
+ var context = ResilienceContextPool.Shared.Get("op-key").WithVoidResultType();
+ var telemetry = Create();
+ ReportEvent(telemetry, outcome: null, arg: new PipelineExecutingArguments(), context: context);
+
+ var messages = _logger.GetRecords(new EventId(1, "StrategyExecuting")).ToList();
+ messages.Should().HaveCount(1);
+ messages[0].Message.Should().Be("Resilience strategy executing. Source: 'my-builder/builder-instance', Operation Key: 'op-key', Result Type: 'void'");
+ }
+
+ [Fact]
+ public void PipelineExecution_NoOutcome_Logged()
+ {
+ var context = ResilienceContextPool.Shared.Get("op-key").WithResultType();
+ var telemetry = Create();
+
+ ReportEvent(telemetry, outcome: null, arg: new PipelineExecutedArguments(TimeSpan.FromSeconds(10)), context: context);
+
+ var messages = _logger.GetRecords(new EventId(2, "StrategyExecuted")).ToList();
+ messages[0].Message.Should().Match($"Resilience strategy executed. Source: 'my-builder/builder-instance', Operation Key: 'op-key', Result Type: 'Int32', Result: '', Execution Health: 'Healthy', Execution Time: 10000ms");
+ }
+
+ [InlineData(true, false)]
+ [InlineData(false, false)]
+ [InlineData(true, true)]
+ [InlineData(false, true)]
+ [Theory]
+ public void PipelineExecution_Metered(bool healthy, bool exception)
+ {
+ var healthString = healthy ? "Healthy" : "Unhealthy";
+ var context = ResilienceContextPool.Shared.Get("op-key").WithResultType();
+ var outcome = exception ? Outcome.FromException(new InvalidOperationException("dummy message")) : Outcome.FromResult((object)10);
+ var result = exception ? "dummy message" : "10";
+
+ if (!healthy)
+ {
+ ((List)context.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy"));
+ }
+
+ var telemetry = Create(enrichers =>
+ {
+ enrichers.Add(context =>
+ {
+ if (exception)
+ {
+ context.Outcome!.Value.Exception.Should().BeOfType();
+ }
+
+ context.Tags.Add(new("custom-tag", "custom-tag-value"));
+ });
+ });
+
+ ReportEvent(telemetry, outcome: outcome, arg: new PipelineExecutedArguments(TimeSpan.FromSeconds(10)), context: context);
+
+ var ev = _events.Single(v => v.Name == "pipeline-execution-duration").Tags;
+
+ ev.Count.Should().Be(exception ? 10 : 9);
+ ev["builder-instance"].Should().Be("builder-instance");
+ ev["operation-key"].Should().Be("op-key");
+ ev["builder-name"].Should().Be("my-builder");
+ ev["result-type"].Should().Be("Int32");
+ ev["event-name"].Should().Be("my-event");
+ ev["event-severity"].Should().Be("Warning");
+ ev["strategy-name"].Should().Be("my-strategy");
+ ev["custom-tag"].Should().Be("custom-tag-value");
+
+ if (exception)
+ {
+ ev["exception-name"].Should().Be("System.InvalidOperationException");
+ }
+ else
+ {
+ ev.Should().NotContainKey("exception-name");
+ }
+
+ if (healthy)
+ {
+ ev["execution-health"].Should().Be("Healthy");
+ }
+ else
+ {
+ ev["execution-health"].Should().Be("Unhealthy");
+ }
+ }
+
+ [Fact]
+ public void PipelineExecuted_ShouldBeSkipped()
+ {
+ _metering.Dispose();
+ _metering = TestUtilities.EnablePollyMetering(_events, _ => false);
+
+ var telemetry = Create();
+ var attemptArg = new PipelineExecutedArguments(TimeSpan.FromSeconds(50));
+ ReportEvent(telemetry, Outcome.FromResult(true), context: ResilienceContextPool.Shared.Get("op-key").WithResultType(), arg: attemptArg);
+
+ var events = GetEvents("pipeline-execution-duration");
+ events.Should().HaveCount(0);
+ }
+
private List> GetEvents(string eventName) => _events.Where(e => e.Name == eventName).Select(v => v.Tags).ToList();
private ResilienceTelemetryDiagnosticSource Create(Action>>? configureEnrichers = null)
diff --git a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs
deleted file mode 100644
index 1fc6d37976e..00000000000
--- a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs
+++ /dev/null
@@ -1,232 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Polly.Telemetry;
-
-namespace Polly.Extensions.Tests.Telemetry;
-
-#pragma warning disable S103 // Lines should not be too long
-
-[Collection(nameof(NonParallelizableCollection))]
-public class TelemetryResilienceStrategyTests : IDisposable
-{
- private readonly FakeLogger _logger;
- private readonly ILoggerFactory _loggerFactory;
- private readonly IDisposable _metering;
- private readonly List _events = new();
- private Action _enricher = _ => { };
-
- public TelemetryResilienceStrategyTests()
- {
- _loggerFactory = TestUtilities.CreateLoggerFactory(out _logger);
- _metering = TestUtilities.EnablePollyMetering(_events);
- }
-
- [Fact]
- public void Ctor_Ok()
- {
- var strategy = CreateStrategy();
- var duration = ((TelemetryResilienceStrategy)strategy.GetType().GetRuntimeProperty("Strategy")!.GetValue(strategy)!).ExecutionDuration;
-
- duration.Unit.Should().Be("ms");
- duration.Description.Should().Be("The execution duration and execution results of resilience strategies.");
- }
-
- [InlineData(true)]
- [InlineData(false)]
- [Theory]
- public void Execute_EnsureLogged(bool healthy)
- {
- var healthString = healthy ? "Healthy" : "Unhealthy";
- var strategy = CreateStrategy();
-
- strategy.Execute(
- (c, _) =>
- {
- if (!healthy)
- {
- ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy"));
- }
- },
- ResilienceContextPool.Shared.Get("op-key"), string.Empty);
-
- var messages = _logger.GetRecords(new EventId(1, "StrategyExecuting")).ToList();
- messages.Should().HaveCount(1);
- messages[0].Message.Should().Be("Resilience strategy executing. Source: 'my-builder/my-instance', Operation Key: 'op-key', Result Type: 'void'");
- messages = _logger.GetRecords(new EventId(2, "StrategyExecuted")).ToList();
- messages.Should().HaveCount(1);
- messages[0].Message.Should().Match($"Resilience strategy executed. Source: 'my-builder/my-instance', Operation Key: 'op-key', Result Type: 'void', Result: 'void', Execution Health: '{healthString}', Execution Time: *ms");
- messages[0].LogLevel.Should().Be(healthy ? LogLevel.Debug : LogLevel.Warning);
-
- // verify reported state
- var coll = messages[0].State.Should().BeAssignableTo>>().Subject;
- coll.Count.Should().Be(8);
- coll.AsEnumerable().Should().HaveCount(8);
- (coll as IEnumerable).GetEnumerator().Should().NotBeNull();
-
- for (int i = 0; i < coll.Count; i++)
- {
- coll[i].Value.Should().NotBeNull();
- }
-
- coll.Invoking(c => c[coll.Count + 1]).Should().Throw();
- }
-
- [Fact]
- public void Execute_WithException_EnsureLogged()
- {
- var strategy = CreateStrategy();
- strategy.Invoking(s => s.Execute(_ => throw new InvalidOperationException("Dummy message."), ResilienceContextPool.Shared.Get("op-key"))).Should().Throw();
-
- var messages = _logger.GetRecords(new EventId(1, "StrategyExecuting")).ToList();
- messages.Should().HaveCount(1);
- messages[0].Message.Should().Be("Resilience strategy executing. Source: 'my-builder/my-instance', Operation Key: 'op-key', Result Type: 'void'");
-
- messages = _logger.GetRecords(new EventId(2, "StrategyExecuted")).ToList();
- messages.Should().HaveCount(1);
- messages[0].Message.Should().Match($"Resilience strategy executed. Source: 'my-builder/my-instance', Operation Key: 'op-key', Result Type: 'void', Result: 'Dummy message.', Execution Health: 'Healthy', Execution Time: *ms");
- messages[0].Exception.Should().BeOfType();
- }
-
- [Fact]
- public void Execute_WithException_EnsureEnrichmentContextWithCorrectOutcome()
- {
- var strategy = CreateStrategy();
-
- _enricher = c =>
- {
- c.Outcome!.Value.Exception.Should().BeOfType();
- };
- strategy.Invoking(s => s.Execute(_ => throw new InvalidOperationException("Dummy message."))).Should().Throw();
- }
-
- [Fact]
- public void Execute_WithResult_EnsureEnrichmentContextWithCorrectOutcome()
- {
- var strategy = CreateStrategy();
-
- _enricher = c =>
- {
- c.Outcome!.Value.Result.Should().Be("dummy");
- };
-
- strategy.Execute(_ => "dummy");
- }
-
- [Fact]
- public void Execute_WithException_EnsureMetered()
- {
- var strategy = CreateStrategy();
- strategy.Invoking(s => s.Execute(_ => throw new InvalidOperationException("Dummy message."), ResilienceContextPool.Shared.Get("op-key"))).Should().Throw();
-
- var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags;
-
- ev.Count.Should().Be(6);
- ev["builder-instance"].Should().Be("my-instance");
- ev["operation-key"].Should().Be("op-key");
- ev["builder-name"].Should().Be("my-builder");
- ev["result-type"].Should().Be("void");
- ev["exception-name"].Should().Be("System.InvalidOperationException");
- ev["execution-health"].Should().Be("Healthy");
- }
-
- [Fact]
- public void Execute_Enrichers_Ok()
- {
- _enricher = context =>
- {
- context.Tags.Add(new KeyValuePair("my-custom-tag", "my-tag-value"));
- };
- var strategy = CreateStrategy();
- strategy.Execute(_ => true);
-
- var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags;
-
- ev.Count.Should().Be(5);
- ev["my-custom-tag"].Should().Be("my-tag-value");
- }
-
- [InlineData(true)]
- [InlineData(false)]
- [Theory]
- public void Execute_WithResult_EnsureMetered(bool healthy)
- {
- var strategy = CreateStrategy();
- strategy.Execute(
- (c, _) =>
- {
- if (!healthy)
- {
- ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy"));
- }
-
- return true;
- },
- ResilienceContextPool.Shared.Get("op-key"), string.Empty);
-
- var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags;
-
- ev.Count.Should().Be(5);
- ev["builder-instance"].Should().Be("my-instance");
- ev["operation-key"].Should().Be("op-key");
- ev["builder-name"].Should().Be("my-builder");
- ev["result-type"].Should().Be("Boolean");
- ev.Should().NotContainKey("exception-name");
-
- if (healthy)
- {
- ev["execution-health"].Should().Be("Healthy");
- }
- else
- {
- ev["execution-health"].Should().Be("Unhealthy");
- }
- }
-
- [InlineData(true)]
- [InlineData(false)]
- [Theory]
- public void Execute_ExecutionHealth(bool healthy)
- {
- var strategy = CreateStrategy();
- strategy.Execute(
- (c, _) =>
- {
- if (healthy)
- {
- ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy"));
- ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy"));
- }
- else
- {
- ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy"));
- ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy"));
- }
-
- return true;
- },
- ResilienceContextPool.Shared.Get(), string.Empty);
-
- var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags;
-
- if (healthy)
- {
- ev["execution-health"].Should().Be("Healthy");
- }
- else
- {
- ev["execution-health"].Should().Be("Unhealthy");
- }
- }
-
- private ResilienceStrategy CreateStrategy()
- {
- return new CompositeStrategyBuilder()
- .AddStrategy(_ => new TelemetryResilienceStrategy("my-builder", "my-instance", _loggerFactory, (_, r) => r, new List> { c => _enricher?.Invoke(c) }), new TestResilienceStrategyOptions())
- .Build();
- }
-
- public void Dispose()
- {
- _metering.Dispose();
- _loggerFactory.Dispose();
- }
-}
diff --git a/test/Polly.Extensions.Tests/Utils/ResilienceContextExtensionsTests.cs b/test/Polly.Extensions.Tests/Utils/ResilienceContextExtensionsTests.cs
new file mode 100644
index 00000000000..e6209416c64
--- /dev/null
+++ b/test/Polly.Extensions.Tests/Utils/ResilienceContextExtensionsTests.cs
@@ -0,0 +1,31 @@
+using Polly.Telemetry;
+
+namespace Polly.Extensions.Tests.Utils;
+
+public class ResilienceContextExtensionsTests
+{
+ [Fact]
+ public void IsHealthy_Ok()
+ {
+ var context = ResilienceContextPool.Shared.Get();
+ AddEvent(context, ResilienceEventSeverity.Warning);
+ context.IsExecutionHealthy().Should().BeFalse();
+
+ context = ResilienceContextPool.Shared.Get();
+ context.IsExecutionHealthy().Should().BeTrue();
+
+ context = ResilienceContextPool.Shared.Get();
+ AddEvent(context, ResilienceEventSeverity.Information);
+ context.IsExecutionHealthy().Should().BeTrue();
+
+ context = ResilienceContextPool.Shared.Get();
+ AddEvent(context, ResilienceEventSeverity.Information);
+ AddEvent(context, ResilienceEventSeverity.Warning);
+ context.IsExecutionHealthy().Should().BeFalse();
+ }
+
+ private static void AddEvent(ResilienceContext context, ResilienceEventSeverity severity)
+ {
+ ((List)context.ResilienceEvents).Add(new ResilienceEvent(severity, "dummy"));
+ }
+}
diff --git a/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs b/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs
index ae9582e54f0..7abb60271cf 100644
--- a/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs
+++ b/test/Polly.RateLimiting.Tests/RateLimiterCompositeStrategyBuilderExtensionsTests.cs
@@ -86,10 +86,11 @@ public void AddRateLimiter_Ok()
RateLimiter = ResilienceRateLimiter.Create(limiter)
})
.Build()
- .GetInnerStrategies().Strategies.Single()
- .StrategyType
+ .GetInnerStrategies()
+ .FirstStrategy
+ .StrategyInstance
.Should()
- .Be();
+ .BeOfType();
}
[Fact]
@@ -129,17 +130,19 @@ public void AddRateLimiter_Options_Ok()
RateLimiter = ResilienceRateLimiter.Create(Substitute.For())
})
.Build()
- .GetInnerStrategies().Strategies
- .Single()
- .StrategyType
+ .GetInnerStrategies()
+ .FirstStrategy
+ .StrategyInstance
.Should()
- .Be();
+ .BeOfType();
}
private static void AssertRateLimiterStrategy(CompositeStrategyBuilder builder, Action? assert = null, bool hasEvents = false)
{
ResilienceStrategy strategy = builder.Build();
- var limiterStrategy = GetResilienceStrategy(strategy);
+
+ var limiterStrategy = (RateLimiterResilienceStrategy)strategy.GetInnerStrategies().FirstStrategy.StrategyInstance;
+
assert?.Invoke(limiterStrategy);
if (hasEvents)
@@ -154,11 +157,11 @@ private static void AssertRateLimiterStrategy(CompositeStrategyBuilder builder,
limiterStrategy.OnLeaseRejected.Should().BeNull();
}
- strategy.GetInnerStrategies().Strategies.Single().StrategyType.Should().Be(typeof(RateLimiterResilienceStrategy));
- }
-
- private static RateLimiterResilienceStrategy GetResilienceStrategy(ResilienceStrategy strategy)
- {
- return (RateLimiterResilienceStrategy)strategy.GetType().GetRuntimeProperty("Strategy")!.GetValue(strategy)!;
+ strategy
+ .GetInnerStrategies()
+ .FirstStrategy
+ .StrategyInstance
+ .Should()
+ .BeOfType();
}
}
diff --git a/test/Polly.TestUtils/TestUtilities.cs b/test/Polly.TestUtils/TestUtilities.cs
index 1ea4cdd3ca1..48db532993b 100644
--- a/test/Polly.TestUtils/TestUtilities.cs
+++ b/test/Polly.TestUtils/TestUtilities.cs
@@ -112,6 +112,12 @@ public static ResilienceContext WithResultType(this ResilienceContext context
return context;
}
+ public static ResilienceContext WithVoidResultType(this ResilienceContext context)
+ {
+ context.Initialize(true);
+ return context;
+ }
+
private sealed class CallbackDiagnosticSource : DiagnosticSource
{
private readonly Action _callback;
@@ -128,7 +134,7 @@ public override void Write(string name, object? value)
if (arguments is ExecutionAttemptArguments attempt)
{
- arguments = ExecutionAttemptArguments.Get(attempt.AttemptNumber, attempt.ExecutionTime, attempt.Handled);
+ arguments = ExecutionAttemptArguments.Get(attempt.AttemptNumber, attempt.Duration, attempt.Handled);
}
// copy the args because these are pooled and in tests we want to preserve them
diff --git a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs
index baf4f8c12cd..097290e7eb0 100644
--- a/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs
+++ b/test/Polly.Testing.Tests/ResilienceStrategyExtensionsTests.cs
@@ -33,27 +33,27 @@ public void GetInnerStrategies_Generic_Ok()
var descriptor = strategy.GetInnerStrategies();
// assert
- descriptor.HasTelemetry.Should().BeTrue();
descriptor.IsReloadable.Should().BeFalse();
descriptor.Strategies.Should().HaveCount(7);
+ descriptor.FirstStrategy.Options.Should().BeOfType>();
descriptor.Strategies[0].Options.Should().BeOfType>();
- descriptor.Strategies[0].StrategyType.FullName.Should().Contain("Fallback");
+ descriptor.Strategies[0].StrategyInstance.GetType().FullName.Should().Contain("Fallback");
descriptor.Strategies[1].Options.Should().BeOfType>();
- descriptor.Strategies[1].StrategyType.FullName.Should().Contain("Retry");
+ descriptor.Strategies[1].StrategyInstance.GetType().FullName.Should().Contain("Retry");
descriptor.Strategies[2].Options.Should().BeOfType>();
- descriptor.Strategies[2].StrategyType.FullName.Should().Contain("CircuitBreaker");
+ descriptor.Strategies[2].StrategyInstance.GetType().FullName.Should().Contain("CircuitBreaker");
descriptor.Strategies[3].Options.Should().BeOfType();
- descriptor.Strategies[3].StrategyType.FullName.Should().Contain("Timeout");
+ descriptor.Strategies[3].StrategyInstance.GetType().FullName.Should().Contain("Timeout");
descriptor.Strategies[3].Options
.Should()
.BeOfType().Subject.Timeout
.Should().Be(TimeSpan.FromSeconds(1));
descriptor.Strategies[4].Options.Should().BeOfType>();
- descriptor.Strategies[4].StrategyType.FullName.Should().Contain("Hedging");
+ descriptor.Strategies[4].StrategyInstance.GetType().FullName.Should().Contain("Hedging");
descriptor.Strategies[5].Options.Should().BeOfType();
- descriptor.Strategies[5].StrategyType.FullName.Should().Contain("RateLimiter");
- descriptor.Strategies[6].StrategyType.Should().Be(typeof(CustomStrategy));
+ descriptor.Strategies[5].StrategyInstance.GetType().FullName.Should().Contain("RateLimiter");
+ descriptor.Strategies[6].StrategyInstance.GetType().Should().Be(typeof(CustomStrategy));
}
[Fact]
@@ -73,23 +73,22 @@ public void GetInnerStrategies_NonGeneric_Ok()
var descriptor = strategy.GetInnerStrategies();
// assert
- descriptor.HasTelemetry.Should().BeTrue();
descriptor.IsReloadable.Should().BeFalse();
descriptor.Strategies.Should().HaveCount(5);
descriptor.Strategies[0].Options.Should().BeOfType();
- descriptor.Strategies[0].StrategyType.FullName.Should().Contain("Retry");
+ descriptor.Strategies[0].StrategyInstance.GetType().FullName.Should().Contain("Retry");
descriptor.Strategies[1].Options.Should().BeOfType();
- descriptor.Strategies[1].StrategyType.FullName.Should().Contain("CircuitBreaker");
+ descriptor.Strategies[1].StrategyInstance.GetType().FullName.Should().Contain("CircuitBreaker");
descriptor.Strategies[2].Options.Should().BeOfType();
- descriptor.Strategies[2].StrategyType.FullName.Should().Contain("Timeout");
+ descriptor.Strategies[2].StrategyInstance.GetType().FullName.Should().Contain("Timeout");
descriptor.Strategies[2].Options
.Should()
.BeOfType().Subject.Timeout
.Should().Be(TimeSpan.FromSeconds(1));
descriptor.Strategies[3].Options.Should().BeOfType();
- descriptor.Strategies[3].StrategyType.FullName.Should().Contain("RateLimiter");
- descriptor.Strategies[4].StrategyType.Should().Be(typeof(CustomStrategy));
+ descriptor.Strategies[3].StrategyInstance.GetType().FullName.Should().Contain("RateLimiter");
+ descriptor.Strategies[4].StrategyInstance.GetType().Should().Be(typeof(CustomStrategy));
}
[Fact]
@@ -104,7 +103,6 @@ public void GetInnerStrategies_SingleStrategy_Ok()
var descriptor = strategy.GetInnerStrategies();
// assert
- descriptor.HasTelemetry.Should().BeFalse();
descriptor.IsReloadable.Should().BeFalse();
descriptor.Strategies.Should().HaveCount(1);
descriptor.Strategies[0].Options.Should().BeOfType();
@@ -127,11 +125,10 @@ public void GetInnerStrategies_Reloadable_Ok()
var descriptor = strategy.GetInnerStrategies();
// assert
- descriptor.HasTelemetry.Should().BeFalse();
descriptor.IsReloadable.Should().BeTrue();
descriptor.Strategies.Should().HaveCount(2);
descriptor.Strategies[0].Options.Should().BeOfType();
- descriptor.Strategies[1].StrategyType.Should().Be(typeof(CustomStrategy));
+ descriptor.Strategies[1].StrategyInstance.GetType().Should().Be(typeof(CustomStrategy));
}
private sealed class CustomStrategy : NonReactiveResilienceStrategy