Skip to content

Commit

Permalink
Finalize the callback API (#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk committed Apr 17, 2023
1 parent f661176 commit 6c80878
Show file tree
Hide file tree
Showing 20 changed files with 674 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ public class RetryResilienceStrategyBuilderExtensionsTests
strategy.DelayGenerator!.GenerateAsync(new Polly.Strategy.Outcome<bool>(new InvalidOperationException()), args).Result.Should().Be(TimeSpan.FromMilliseconds(8));
});
},
builder =>
{
builder.AddRetry(new RetryStrategyOptions<int>
{
BackoffType = RetryBackoffType.Exponential,
RetryCount = 3,
BaseDelay = TimeSpan.FromSeconds(2)
});
AssertStrategy(builder, RetryBackoffType.Exponential, 3, TimeSpan.FromSeconds(2));
}
};

[MemberData(nameof(OverloadsData))]
Expand Down Expand Up @@ -79,5 +90,11 @@ public void AddRetry_InvalidOptions_Throws()
.Should()
.Throw<ValidationException>()
.WithMessage("The retry strategy options are invalid.*");

builder
.Invoking(b => b.AddRetry(new RetryStrategyOptions<int> { ShouldRetry = null! }))
.Should()
.Throw<ValidationException>()
.WithMessage("The retry strategy options are invalid.*");
}
}
87 changes: 87 additions & 0 deletions src/Polly.Core.Tests/Retry/RetryStrategyOptionsTResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.ComponentModel.DataAnnotations;
using Polly.Retry;
using Polly.Strategy;
using Polly.Utils;

namespace Polly.Core.Tests.Retry;

public class RetryStrategyOptionsTResultTests
{
[Fact]
public void Ctor_Ok()
{
var options = new RetryStrategyOptions<int>();

options.ShouldRetry.Should().NotBeNull();
options.ShouldRetry.IsEmpty.Should().BeTrue();

options.RetryDelayGenerator.Should().NotBeNull();
options.RetryDelayGenerator.IsEmpty.Should().BeTrue();

options.OnRetry.Should().NotBeNull();
options.OnRetry.IsEmpty.Should().BeTrue();

options.RetryCount.Should().Be(3);
options.BackoffType.Should().Be(RetryBackoffType.Exponential);
options.BaseDelay.Should().Be(TimeSpan.FromSeconds(2));
}

[Fact]
public void InvalidOptions()
{
var options = new RetryStrategyOptions<int>
{
ShouldRetry = null!,
RetryDelayGenerator = null!,
OnRetry = null!,
RetryCount = -3,
BaseDelay = TimeSpan.MinValue
};

options.Invoking(o => ValidationHelper.ValidateObject(o, "Invalid Options"))
.Should()
.Throw<ValidationException>()
.WithMessage("""
Invalid Options

Validation Errors:
The field RetryCount must be between -1 and 100.
The field BaseDelay must be >= to 00:00:00.
The ShouldRetry field is required.
The RetryDelayGenerator field is required.
The OnRetry field is required.
""");
}

[Fact]
public async Task AsNonGenericOptions_Ok()
{
var called = false;
var options = new RetryStrategyOptions<int>
{
BackoffType = RetryBackoffType.Constant,
BaseDelay = TimeSpan.FromMilliseconds(555),
RetryCount = 7,
StrategyName = "my-name",
StrategyType = "my-type",
};

options.ShouldRetry.HandleResult(999);
options.OnRetry.Register(() => called = true);
options.RetryDelayGenerator.SetGenerator((_, _) => TimeSpan.FromSeconds(123));
var nonGenericOptions = options.AsNonGenericOptions();

nonGenericOptions.BackoffType.Should().Be(RetryBackoffType.Constant);
nonGenericOptions.BaseDelay.Should().Be(TimeSpan.FromMilliseconds(555));
nonGenericOptions.RetryCount.Should().Be(7);
nonGenericOptions.StrategyName.Should().Be("my-name");
nonGenericOptions.StrategyType.Should().Be("my-type");

(await nonGenericOptions.ShouldRetry.CreateHandler()!.ShouldHandleAsync(new Outcome<int>(999), default)).Should().BeTrue();
await nonGenericOptions.OnRetry.CreateHandler()!.HandleAsync(new Outcome<int>(999), default);
called.Should().BeTrue();
var args = new RetryDelayArguments(ResilienceContext.Get(), 2, TimeSpan.FromMinutes(1));
(await nonGenericOptions.RetryDelayGenerator.CreateHandler(default, _ => true)!.GenerateAsync(new Outcome<int>(999), args)).Should().Be(TimeSpan.FromSeconds(123));

}
}
62 changes: 62 additions & 0 deletions src/Polly.Core.Tests/Strategy/NoOutcomeGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Polly.Strategy;

namespace Polly.Core.Tests.Timeout;

public class NoOutcomeGeneratorTests
{
[Fact]
public void Default_EnsureCorrectValue()
{
var generator = new NoOutcomeGenerator<TestArguments, TimeSpan>();

generator.CreateHandler(default, _ => true).Should().BeNull();
}

[Fact]
public async Task SetGenerator_Callback_Ok()
{
var generator = new NoOutcomeGenerator<TestArguments, TimeSpan>();
var delay = TimeSpan.FromSeconds(1);

generator.SetGenerator(args => delay);

var actualDelay = await generator.CreateHandler(default, _ => true)!(new TestArguments());

actualDelay.Should().Be(delay);
}

[Fact]
public async Task SetGenerator_AsyncCallback_Ok()
{
var generator = new NoOutcomeGenerator<TestArguments, TimeSpan>();
var delay = TimeSpan.FromSeconds(1);

generator.SetGenerator(args => new ValueTask<TimeSpan>(delay));

var actualDelay = await generator.CreateHandler(default, _ => true)!(new TestArguments());

actualDelay.Should().Be(delay);
}

[Fact]
public async Task CreateHandler_EnsureValidation()
{
var generator = new NoOutcomeGenerator<TestArguments, TimeSpan>();
var delay = TimeSpan.FromSeconds(1);

generator.SetGenerator(args => new ValueTask<TimeSpan>(delay));

var actualDelay = await generator.CreateHandler(TimeSpan.FromMilliseconds(123), _ => false)!(new TestArguments());

actualDelay.Should().Be(TimeSpan.FromMilliseconds(123));
}

[Fact]
public void ArgValidation_EnsureThrows()
{
var generator = new NoOutcomeGenerator<TestArguments, TimeSpan>();

Assert.Throws<ArgumentNullException>(() => generator.SetGenerator((Func<TestArguments, TimeSpan>)null!));
Assert.Throws<ArgumentNullException>(() => generator.SetGenerator((Func<TestArguments, ValueTask<TimeSpan>>)null!));
}
}
15 changes: 14 additions & 1 deletion src/Polly.Core.Tests/Strategy/OutcomeEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ public void CreateHandler_Empty_ReturnsNull()
InvokeHandler(sut, new Outcome<bool>(true));
called.Should().BeFalse();
},
sut =>
{
sut.ConfigureCallbacks<double>(callbacks => callbacks.IsEmpty.Should().BeTrue());
InvokeHandler(sut, new Outcome<double>(1.0));
},
sut =>
{
bool called = false;
sut.Register<double>((_, _) => { called = true; return default; });
sut.SetCallbacks(new OutcomeEvent<TestArguments, double>());
InvokeHandler(sut, new Outcome<double>(1.0));
called.Should().BeFalse();
},
};

[MemberData(nameof(Data))]
Expand Down Expand Up @@ -114,6 +127,6 @@ public void Register_DifferentResultType_NotInvoked()

private static void InvokeHandler<T>(OutcomeEvent<TestArguments> sut, Outcome<T> outcome)
{
sut.CreateHandler()!.HandleAsync(outcome, new TestArguments()).AsTask().Wait();
sut.CreateHandler()?.HandleAsync(outcome, new TestArguments()).AsTask().Wait();
}
}
26 changes: 25 additions & 1 deletion src/Polly.Core.Tests/Strategy/OutcomeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ public void CreateHandler_Empty_ReturnsNull()
});
InvokeHandler(sut, new Outcome<int>(10), GeneratedValue.Valid1);
},
sut =>
{
sut.ConfigureGenerator<int>(generator => generator.IsEmpty.Should().BeTrue());
InvokeHandler(sut, new Outcome<int>(10), GeneratedValue.Default);
},
sut =>
{
sut.ConfigureGenerator<int>(generator => generator.SetGenerator((_, _) => GeneratedValue.Valid1));
sut.SetGenerator(new OutcomeGenerator<TestArguments, GeneratedValue, int>());
InvokeHandler(sut, new Outcome<int>(10), GeneratedValue.Default);
},
sut =>
{
sut.SetGenerator((_, _) => new ValueTask<GeneratedValue>(GeneratedValue.Valid1));
InvokeHandler(sut, new Outcome<int>(10), GeneratedValue.Valid1);
},
};

[MemberData(nameof(Data))]
Expand Down Expand Up @@ -155,7 +171,15 @@ public void AddResultHandlers_DifferentResultType_NotInvoked()

private static void InvokeHandler<T>(OutcomeGenerator<TestArguments, GeneratedValue> sut, Outcome<T> outcome, GeneratedValue expectedResult)
{
CreateHandler(sut)!.GenerateAsync(outcome, new TestArguments()).AsTask().Result.Should().Be(expectedResult);
OutcomeGenerator<TestArguments, GeneratedValue>.Handler? handler = CreateHandler(sut);

if (handler == null)
{
expectedResult.Should().Be(GeneratedValue.Default);
return;
}

handler.GenerateAsync(outcome, new TestArguments()).AsTask().Result.Should().Be(expectedResult);
}

private static OutcomeGenerator<TestArguments, GeneratedValue>.Handler? CreateHandler(OutcomeGenerator<TestArguments, GeneratedValue> generator)
Expand Down
49 changes: 0 additions & 49 deletions src/Polly.Core.Tests/Timeout/TimeoutGeneratorTests.cs

This file was deleted.

14 changes: 7 additions & 7 deletions src/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static TheoryData<TimeSpan> Execute_NoTimeout_Data() => new()
public void Execute_EnsureTimeoutGeneratorCalled()
{
var called = false;
_options.TimeoutGenerator.SetTimeout(args =>
_options.TimeoutGenerator.SetGenerator(args =>
{
args.Context.Should().NotBeNull();
called = true;
Expand All @@ -55,7 +55,7 @@ public async Task Execute_EnsureOnTimeoutCalled()
_diagnosticSource.Setup(v => v.IsEnabled("OnTimeout")).Returns(true);

var called = false;
_options.TimeoutGenerator.SetTimeout(args => _delay);
_options.TimeoutGenerator.SetGenerator(args => _delay);
_options.OnTimeout.Register(args =>
{
args.Exception.Should().BeAssignableTo<OperationCanceledException>();
Expand All @@ -80,7 +80,7 @@ public void Execute_NoTimeout(TimeSpan timeout)
{
var called = false;
var sut = CreateSut();
_options.TimeoutGenerator.SetTimeout(args =>
_options.TimeoutGenerator.SetGenerator(args =>
{
called = true;
return timeout;
Expand All @@ -97,7 +97,7 @@ public async Task Execute_Timeout(bool defaultCancellationToken)
{
using var cts = new CancellationTokenSource();
CancellationToken token = defaultCancellationToken ? default : cts.Token;
_options.TimeoutGenerator.SetTimeout(args => TimeSpan.FromSeconds(2));
_options.TimeoutGenerator.SetGenerator(args => TimeSpan.FromSeconds(2));
_timeProvider.SetupCancelAfterNow(TimeSpan.FromSeconds(2));
var sut = CreateSut();

Expand All @@ -116,7 +116,7 @@ public async Task Execute_Cancelled_EnsureNoTimeout()

var onTimeoutCalled = false;
using var cts = new CancellationTokenSource();
_options.TimeoutGenerator.SetTimeout(args => TimeSpan.FromSeconds(10));
_options.TimeoutGenerator.SetGenerator(args => TimeSpan.FromSeconds(10));
_options.OnTimeout.Register(() => onTimeoutCalled = true);
_timeProvider.Setup(v => v.CancelAfter(It.IsAny<CancellationTokenSource>(), delay));

Expand All @@ -138,7 +138,7 @@ public async Task Execute_NoTimeoutOrCancellation_EnsureCancellationTokenRestore
var delay = TimeSpan.FromSeconds(10);

using var cts = new CancellationTokenSource();
_options.TimeoutGenerator.SetTimeout(args => TimeSpan.FromSeconds(10));
_options.TimeoutGenerator.SetGenerator(args => TimeSpan.FromSeconds(10));
_timeProvider.Setup(v => v.CancelAfter(It.IsAny<CancellationTokenSource>(), delay));

var sut = CreateSut();
Expand All @@ -163,7 +163,7 @@ public async Task Execute_EnsureCancellationTokenRegistrationNotExecutedOnSynchr
{
// Arrange
using var cts = new CancellationTokenSource();
_options.TimeoutGenerator.SetTimeout(args => TimeSpan.FromSeconds(10));
_options.TimeoutGenerator.SetGenerator(args => TimeSpan.FromSeconds(10));
_timeProvider.Setup(v => v.CancelAfter(It.IsAny<CancellationTokenSource>(), TimeSpan.FromSeconds(10)));

var sut = CreateSut();
Expand Down
17 changes: 17 additions & 0 deletions src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,23 @@ public static class RetryResilienceStrategyBuilderExtensions
return builder.AddRetry(options);
}

/// <summary>
/// Adds a retry strategy to the builder.
/// </summary>
/// <typeparam name="TResult">The type of result the retry strategy handles.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="options">The retry strategy options.</param>
/// <returns>The builder instance with the retry strategy added.</returns>
public static ResilienceStrategyBuilder AddRetry<TResult>(this ResilienceStrategyBuilder builder, RetryStrategyOptions<TResult> options)
{
Guard.NotNull(builder);
Guard.NotNull(options);

ValidationHelper.ValidateObject(options, "The retry strategy options are invalid.");

return builder.AddRetry(options.AsNonGenericOptions());
}

/// <summary>
/// Adds a retry strategy to the builder.
/// </summary>
Expand Down

0 comments on commit 6c80878

Please sign in to comment.