-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
432 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using Polly.Retry; | ||
|
||
namespace Polly.Core.Tests.Retry; | ||
|
||
public class RetryDelayArgumentsTests | ||
{ | ||
[Fact] | ||
public void Ctor_Ok() | ||
{ | ||
var args = new RetryDelayArguments(ResilienceContext.Get(), 2); | ||
|
||
args.Context.Should().NotBeNull(); | ||
args.Attempt.Should().Be(2); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Polly.Retry; | ||
using Polly.Strategy; | ||
using Xunit; | ||
|
||
namespace Polly.Core.Tests.Retry; | ||
|
||
public class RetryDelayGeneratorTests | ||
{ | ||
[Fact] | ||
public async Task NoGeneratorRegisteredForType_EnsureDefaultValue() | ||
{ | ||
var result = await new RetryDelayGenerator() | ||
.SetGenerator<int>((_, _) => TimeSpan.Zero) | ||
.CreateHandler()! | ||
.Generate(new Outcome<bool>(true), new RetryDelayArguments(ResilienceContext.Get(), 0)); | ||
|
||
result.Should().Be(TimeSpan.MinValue); | ||
} | ||
|
||
public static readonly TheoryData<TimeSpan> ValidDelays = new() { TimeSpan.Zero, TimeSpan.FromMilliseconds(123) }; | ||
|
||
[MemberData(nameof(ValidDelays))] | ||
[Theory] | ||
public async Task GeneratorRegistered_EnsureValueNotIgnored(TimeSpan delay) | ||
{ | ||
var result = await new RetryDelayGenerator() | ||
.SetGenerator<int>((_, _) => delay) | ||
.CreateHandler()! | ||
.Generate(new Outcome<int>(0), new RetryDelayArguments(ResilienceContext.Get(), 0)); | ||
|
||
result.Should().Be(delay); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Polly.Strategy; | ||
|
||
namespace Polly.Core.Tests.Strategy; | ||
|
||
public class OutcomeGeneratorTests | ||
{ | ||
private readonly DummyGenerator _sut = new(); | ||
|
||
[Fact] | ||
public void Empty_Ok() | ||
{ | ||
_sut.IsEmpty.Should().BeTrue(); | ||
|
||
_sut.SetGenerator<int>((_, _) => GeneratedValue.Invalid); | ||
|
||
_sut.IsEmpty.Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public void CreateHandler_Empty_ReturnsNull() | ||
{ | ||
_sut.CreateHandler().Should().BeNull(); | ||
} | ||
|
||
public static readonly TheoryData<Action<DummyGenerator>> Data = new() | ||
{ | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => GeneratedValue.Valid1); | ||
InvokeHandler(sut, new Outcome<int>(0), GeneratedValue.Valid1); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => new ValueTask<GeneratedValue>(GeneratedValue.Valid1)); | ||
InvokeHandler(sut, new Outcome<int>(0), GeneratedValue.Valid1); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => GeneratedValue.Valid1); | ||
InvokeHandler(sut, new Outcome<bool>(true), GeneratedValue.Default); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => new ValueTask<GeneratedValue>(GeneratedValue.Valid1)); | ||
InvokeHandler(sut, new Outcome<bool>(true), GeneratedValue.Default); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => GeneratedValue.Invalid); | ||
InvokeHandler(sut, new Outcome<int>(0), GeneratedValue.Default); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => new ValueTask<GeneratedValue>(GeneratedValue.Invalid)); | ||
InvokeHandler(sut, new Outcome<int>(0), GeneratedValue.Default); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => GeneratedValue.Valid1); | ||
sut.SetGenerator<int>((_, _) => GeneratedValue.Valid2); | ||
InvokeHandler(sut, new Outcome<int>(0), GeneratedValue.Valid2); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => new ValueTask<GeneratedValue>(GeneratedValue.Valid1)); | ||
sut.SetGenerator<int>((_, _) => new ValueTask<GeneratedValue>(GeneratedValue.Valid2)); | ||
InvokeHandler(sut, new Outcome<int>(0), GeneratedValue.Valid2); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => GeneratedValue.Valid1); | ||
sut.SetGenerator<bool>((_, _) => GeneratedValue.Valid2); | ||
InvokeHandler(sut, new Outcome<int>(0), GeneratedValue.Valid1); | ||
InvokeHandler(sut, new Outcome<bool>(true), GeneratedValue.Valid2); | ||
}, | ||
sut => | ||
{ | ||
sut.SetGenerator<int>((_, _) => GeneratedValue.Valid1); | ||
sut.SetGenerator<double>((_, _) => GeneratedValue.Valid1); | ||
InvokeHandler(sut, new Outcome<bool>(true), GeneratedValue.Default); | ||
}, | ||
}; | ||
|
||
[MemberData(nameof(Data))] | ||
[Theory] | ||
public void ResultHandler_SinglePredicate_Ok(Action<DummyGenerator> callback) | ||
{ | ||
_sut.Invoking(s => callback(s)).Should().NotThrow(); | ||
callback(_sut); | ||
} | ||
|
||
[Fact] | ||
public void AddResultHandlers_DifferentResultType_NotInvoked() | ||
{ | ||
var callbacks = new List<int>(); | ||
|
||
for (var i = 0; i < 10; i++) | ||
{ | ||
var index = i; | ||
|
||
_sut.SetGenerator<int>((_, _) => | ||
{ | ||
callbacks.Add(index); | ||
return GeneratedValue.Valid1; | ||
}); | ||
|
||
_sut.SetGenerator<bool>((_, _) => | ||
{ | ||
callbacks.Add(index); | ||
return GeneratedValue.Valid1; | ||
}); | ||
} | ||
|
||
InvokeHandler(_sut, new Outcome<int>(1), GeneratedValue.Valid1); | ||
|
||
callbacks.Distinct().Should().HaveCount(1); | ||
} | ||
|
||
private static void InvokeHandler<T>(DummyGenerator sut, Outcome<T> outcome, GeneratedValue expectedResult) | ||
{ | ||
var args = new Args(); | ||
sut.CreateHandler()!.Generate(outcome, args).AsTask().Result.Should().Be(expectedResult); | ||
} | ||
|
||
public sealed class DummyGenerator : OutcomeGenerator<GeneratedValue, Args, DummyGenerator> | ||
{ | ||
protected override GeneratedValue DefaultValue => GeneratedValue.Default; | ||
|
||
protected override bool IsValid(GeneratedValue value) => value == GeneratedValue.Valid1 || value == GeneratedValue.Valid2; | ||
} | ||
|
||
public enum GeneratedValue | ||
{ | ||
Default, | ||
Valid1, | ||
Valid2, | ||
Invalid | ||
} | ||
|
||
public class Args : IResilienceArguments | ||
{ | ||
public Args() => Context = ResilienceContext.Get(); | ||
|
||
public ResilienceContext Context { get; private set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using Polly.Strategy; | ||
|
||
namespace Polly.Retry; | ||
|
||
#pragma warning disable CA1815 // Override equals and operator equals on value types | ||
|
||
/// <summary> | ||
/// Represents the arguments used in <see cref="RetryDelayGenerator"/> for generating the next retry delay. | ||
/// </summary> | ||
public readonly struct RetryDelayArguments : IResilienceArguments | ||
{ | ||
internal RetryDelayArguments(ResilienceContext context, int attempt) | ||
{ | ||
Attempt = attempt; | ||
Context = context; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the zero-based attempt number. | ||
/// </summary> | ||
/// <remarks> | ||
/// The first attempt is 0, the second attempt is 1, and so on. | ||
/// </remarks> | ||
public int Attempt { get; } | ||
|
||
/// <inheritdoc/> | ||
public ResilienceContext Context { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Polly.Strategy; | ||
|
||
namespace Polly.Retry; | ||
|
||
/// <summary> | ||
/// This class generates the customized retries used in retry strategy. | ||
/// </summary> | ||
/// <remarks> | ||
/// If the generator returns a negative value, it's value is ignored. | ||
/// </remarks> | ||
public sealed class RetryDelayGenerator : OutcomeGenerator<TimeSpan, RetryDelayArguments, RetryDelayGenerator> | ||
{ | ||
/// <inheritdoc/> | ||
protected override TimeSpan DefaultValue => TimeSpan.MinValue; | ||
|
||
/// <inheritdoc/> | ||
protected override bool IsValid(TimeSpan value) => value >= TimeSpan.Zero; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace Polly.Strategy; | ||
|
||
#pragma warning disable CA1034 // Nested types should not be visible | ||
#pragma warning disable CA1005 // Avoid excessive parameters on generic types | ||
#pragma warning disable S2436 // Types and methods should not have too many generic parameters | ||
|
||
public abstract partial class OutcomeGenerator<TGeneratedValue, TArgs, TSelf> | ||
{ | ||
/// <summary> | ||
/// The resulting handler for the outcome. | ||
/// </summary> | ||
public abstract class Handler | ||
{ | ||
private protected Handler(TGeneratedValue defaultValue, Predicate<TGeneratedValue> isValid) | ||
{ | ||
DefaultValue = defaultValue; | ||
IsValid = isValid; | ||
} | ||
|
||
internal TGeneratedValue DefaultValue { get; } | ||
|
||
internal Predicate<TGeneratedValue> IsValid { get; } | ||
|
||
/// <summary> | ||
/// Determines if the handler should handle the outcome. | ||
/// </summary> | ||
/// <typeparam name="TResult">The result type to add a predicate for.</typeparam> | ||
/// <param name="outcome">The operation outcome.</param> | ||
/// <param name="args">The arguments.</param> | ||
/// <returns>The result of the handle operation.</returns> | ||
public abstract ValueTask<TGeneratedValue> Generate<TResult>(Outcome<TResult> outcome, TArgs args); | ||
} | ||
|
||
private sealed class TypeHandler : Handler | ||
{ | ||
private readonly Type _type; | ||
private readonly object _generator; | ||
|
||
public TypeHandler( | ||
Type type, | ||
object generator, | ||
TGeneratedValue defaultValue, | ||
Predicate<TGeneratedValue> isValid) | ||
: base(defaultValue, isValid) | ||
{ | ||
_type = type; | ||
_generator = generator; | ||
} | ||
|
||
public override async ValueTask<TGeneratedValue> Generate<TResult>(Outcome<TResult> outcome, TArgs args) | ||
{ | ||
if (typeof(TResult) == _type) | ||
{ | ||
var value = await ((Func<Outcome<TResult>, TArgs, ValueTask<TGeneratedValue>>)_generator)(outcome, args).ConfigureAwait(args.Context.ContinueOnCapturedContext); | ||
|
||
if (IsValid(value)) | ||
{ | ||
return value; | ||
} | ||
|
||
return DefaultValue; | ||
} | ||
|
||
return DefaultValue; | ||
} | ||
} | ||
|
||
private sealed class TypesHandler : Handler | ||
{ | ||
private readonly Dictionary<Type, TypeHandler> _generators; | ||
|
||
public TypesHandler( | ||
IEnumerable<KeyValuePair<Type, object>> generators, | ||
TGeneratedValue defaultValue, | ||
Predicate<TGeneratedValue> isValid) | ||
: base(defaultValue, isValid) | ||
=> _generators = generators.ToDictionary(v => v.Key, v => new TypeHandler(v.Key, v.Value, defaultValue, isValid)); | ||
|
||
public override ValueTask<TGeneratedValue> Generate<TResult>(Outcome<TResult> outcome, TArgs args) | ||
{ | ||
if (_generators.TryGetValue(typeof(TResult), out var handler)) | ||
{ | ||
return handler.Generate(outcome, args); | ||
} | ||
|
||
return new ValueTask<TGeneratedValue>(DefaultValue); | ||
} | ||
} | ||
} |
Oops, something went wrong.