-
-
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.
Introduce API for Circuit Breaker Strategy (#1145)
- Loading branch information
Showing
36 changed files
with
1,579 additions
and
153 deletions.
There are no files selected for viewing
142 changes: 142 additions & 0 deletions
142
src/Polly.Core.Tests/CircuitBreaker/AdvancedCircuitBreakerOptionsTests.cs
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,142 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using Polly.CircuitBreaker; | ||
using Polly.Strategy; | ||
using Polly.Utils; | ||
using Xunit; | ||
|
||
namespace Polly.Core.Tests.CircuitBreaker; | ||
|
||
public class AdvancedCircuitBreakerOptionsTests | ||
{ | ||
[Fact] | ||
public void Ctor_Defaults() | ||
{ | ||
var options = new AdvancedCircuitBreakerStrategyOptions(); | ||
|
||
options.BreakDuration.Should().Be(TimeSpan.FromSeconds(5)); | ||
options.FailureThreshold.Should().Be(0.1); | ||
options.MinimumThroughput.Should().Be(100); | ||
options.SamplingDuration.Should().Be(TimeSpan.FromSeconds(30)); | ||
options.OnOpened.IsEmpty.Should().BeTrue(); | ||
options.OnClosed.IsEmpty.Should().BeTrue(); | ||
options.OnHalfOpened.IsEmpty.Should().BeTrue(); | ||
options.ShouldHandle.IsEmpty.Should().BeTrue(); | ||
options.StrategyType.Should().Be("CircuitBreaker"); | ||
options.StrategyName.Should().BeEmpty(); | ||
|
||
// now set to min values | ||
options.FailureThreshold = 0.001; | ||
options.BreakDuration = TimeSpan.FromMilliseconds(500); | ||
options.MinimumThroughput = 2; | ||
options.SamplingDuration = TimeSpan.FromMilliseconds(500); | ||
|
||
ValidationHelper.ValidateObject(options, "Dummy."); | ||
} | ||
|
||
[Fact] | ||
public void Ctor_Generic_Defaults() | ||
{ | ||
var options = new AdvancedCircuitBreakerStrategyOptions<int>(); | ||
|
||
options.BreakDuration.Should().Be(TimeSpan.FromSeconds(5)); | ||
options.FailureThreshold.Should().Be(0.1); | ||
options.MinimumThroughput.Should().Be(100); | ||
options.SamplingDuration.Should().Be(TimeSpan.FromSeconds(30)); | ||
options.OnOpened.IsEmpty.Should().BeTrue(); | ||
options.OnClosed.IsEmpty.Should().BeTrue(); | ||
options.OnHalfOpened.IsEmpty.Should().BeTrue(); | ||
options.ShouldHandle.IsEmpty.Should().BeTrue(); | ||
options.StrategyType.Should().Be("CircuitBreaker"); | ||
options.StrategyName.Should().BeEmpty(); | ||
|
||
// now set to min values | ||
options.FailureThreshold = 0.001; | ||
options.BreakDuration = TimeSpan.FromMilliseconds(500); | ||
options.MinimumThroughput = 2; | ||
options.SamplingDuration = TimeSpan.FromMilliseconds(500); | ||
|
||
ValidationHelper.ValidateObject(options, "Dummy."); | ||
} | ||
|
||
[Fact] | ||
public async Task AsNonGenericOptions_Ok() | ||
{ | ||
bool onBreakCalled = false; | ||
bool onResetCalled = false; | ||
bool onHalfOpenCalled = false; | ||
|
||
var options = new AdvancedCircuitBreakerStrategyOptions<int> | ||
{ | ||
BreakDuration = TimeSpan.FromSeconds(123), | ||
FailureThreshold = 23, | ||
SamplingDuration = TimeSpan.FromSeconds(124), | ||
MinimumThroughput = 6, | ||
StrategyType = "dummy-type", | ||
StrategyName = "dummy-name", | ||
OnOpened = new OutcomeEvent<OnCircuitOpenedArguments, int>().Register(() => onBreakCalled = true), | ||
OnClosed = new OutcomeEvent<OnCircuitClosedArguments, int>().Register(() => onResetCalled = true), | ||
OnHalfOpened = new NoOutcomeEvent<OnCircuitHalfOpenedArguments>().Register(() => onHalfOpenCalled = true), | ||
ShouldHandle = new OutcomePredicate<CircuitBreakerPredicateArguments, int>().HandleException<InvalidOperationException>(), | ||
ManualControl = new CircuitBreakerManualControl(), | ||
StateProvider = new CircuitBreakerStateProvider() | ||
}; | ||
|
||
var converted = options.AsNonGenericOptions(); | ||
|
||
// assert converted options | ||
converted.StrategyType.Should().Be("dummy-type"); | ||
converted.StrategyName.Should().Be("dummy-name"); | ||
converted.FailureThreshold.Should().Be(23); | ||
converted.BreakDuration.Should().Be(TimeSpan.FromSeconds(123)); | ||
converted.SamplingDuration.Should().Be(TimeSpan.FromSeconds(124)); | ||
converted.MinimumThroughput.Should().Be(6); | ||
converted.ManualControl.Should().Be(options.ManualControl); | ||
converted.StateProvider.Should().Be(options.StateProvider); | ||
|
||
var context = ResilienceContext.Get(); | ||
|
||
(await converted.ShouldHandle.CreateHandler()!.ShouldHandleAsync(new Outcome<int>(new InvalidOperationException()), new CircuitBreakerPredicateArguments(context))).Should().BeTrue(); | ||
|
||
await converted.OnClosed.CreateHandler()!.HandleAsync(new Outcome<int>(new InvalidOperationException()), new OnCircuitClosedArguments(context)); | ||
onResetCalled.Should().BeTrue(); | ||
|
||
await converted.OnOpened.CreateHandler()!.HandleAsync(new Outcome<int>(new InvalidOperationException()), new OnCircuitOpenedArguments(context, TimeSpan.Zero)); | ||
onBreakCalled.Should().BeTrue(); | ||
|
||
await converted.OnHalfOpened.CreateHandler()!(new OnCircuitHalfOpenedArguments(context)); | ||
onHalfOpenCalled.Should().BeTrue(); | ||
} | ||
|
||
[Fact] | ||
public void InvalidOptions_Validate() | ||
{ | ||
var options = new AdvancedCircuitBreakerStrategyOptions<int> | ||
{ | ||
BreakDuration = TimeSpan.FromMilliseconds(299), | ||
FailureThreshold = 0, | ||
SamplingDuration = TimeSpan.Zero, | ||
MinimumThroughput = 0, | ||
OnOpened = null!, | ||
OnClosed = null!, | ||
OnHalfOpened = null!, | ||
ShouldHandle = null!, | ||
}; | ||
|
||
options | ||
.Invoking(o => ValidationHelper.ValidateObject(o, "Dummy.")) | ||
.Should() | ||
.Throw<ValidationException>() | ||
.WithMessage(""" | ||
Dummy. | ||
|
||
Validation Errors: | ||
The field MinimumThroughput must be between 2 and 2147483647. | ||
The field SamplingDuration must be >= to 00:00:00.5000000. | ||
The field BreakDuration must be >= to 00:00:00.5000000. | ||
The ShouldHandle field is required. | ||
The OnClosed field is required. | ||
The OnOpened field is required. | ||
The OnHalfOpened field is required. | ||
"""); | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/Polly.Core.Tests/CircuitBreaker/BrokenCircuitExceptionTests.cs
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,51 @@ | ||
using Polly.CircuitBreaker; | ||
|
||
namespace Polly.Core.Tests.CircuitBreaker; | ||
|
||
public class BrokenCircuitExceptionTests | ||
{ | ||
[Fact] | ||
public void Ctor_Ok() | ||
{ | ||
var brokenCircuit = new BrokenCircuitException(); | ||
new BrokenCircuitException("Dummy.").Message.Should().Be("Dummy."); | ||
new BrokenCircuitException("Dummy.", new InvalidOperationException()).Message.Should().Be("Dummy."); | ||
new BrokenCircuitException("Dummy.", new InvalidOperationException()).InnerException.Should().BeOfType<InvalidOperationException>(); | ||
} | ||
|
||
[Fact] | ||
public void Ctor_Generic_Ok() | ||
{ | ||
var exception = new BrokenCircuitException<int>(10); | ||
exception.Result.Should().Be(10); | ||
|
||
exception = new BrokenCircuitException<int>("Dummy.", 10); | ||
exception.Message.Should().Be("Dummy."); | ||
exception.Result.Should().Be(10); | ||
|
||
exception = new BrokenCircuitException<int>("Dummy.", new InvalidOperationException(), 10); | ||
exception.Message.Should().Be("Dummy."); | ||
exception.Result.Should().Be(10); | ||
exception.InnerException.Should().BeOfType<InvalidOperationException>(); | ||
} | ||
|
||
#if !NETCOREAPP | ||
[Fact] | ||
public void BinarySerialization_Ok() | ||
{ | ||
BinarySerializationUtil.SerializeAndDeserializeException(new BrokenCircuitException()).Should().NotBeNull(); | ||
} | ||
|
||
[Fact] | ||
public void BinarySerialization_Generic_Ok() | ||
{ | ||
var result = BinarySerializationUtil | ||
.SerializeAndDeserializeException(new BrokenCircuitException<int>(123)); | ||
|
||
result.Should().NotBeNull(); | ||
|
||
// default | ||
result.Result.Should().Be(0); | ||
} | ||
#endif | ||
} |
79 changes: 79 additions & 0 deletions
79
src/Polly.Core.Tests/CircuitBreaker/CircuitBreakerManualControlTests.cs
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,79 @@ | ||
using System; | ||
using Polly.CircuitBreaker; | ||
|
||
namespace Polly.Core.Tests.CircuitBreaker; | ||
|
||
public class CircuitBreakerManualControlTests | ||
{ | ||
[Fact] | ||
public void Ctor_Ok() | ||
{ | ||
var control = new CircuitBreakerManualControl(); | ||
|
||
control.IsInitialized.Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public async Task IsolateAsync_NotInitialized_Throws() | ||
{ | ||
var control = new CircuitBreakerManualControl(); | ||
|
||
await control | ||
.Invoking(c => c.IsolateAsync(CancellationToken.None)) | ||
.Should() | ||
.ThrowAsync<InvalidOperationException>(); | ||
} | ||
|
||
[Fact] | ||
public async Task ResetAsync_NotInitialized_Throws() | ||
{ | ||
var control = new CircuitBreakerManualControl(); | ||
|
||
await control | ||
.Invoking(c => c.ResetAsync(CancellationToken.None)) | ||
.Should() | ||
.ThrowAsync<InvalidOperationException>(); | ||
} | ||
|
||
[Fact] | ||
public void Initialize_Twice_Throws() | ||
{ | ||
var control = new CircuitBreakerManualControl(); | ||
control.Initialize(_ => Task.CompletedTask, _ => Task.CompletedTask); | ||
|
||
control | ||
.Invoking(c => c.Initialize(_ => Task.CompletedTask, _ => Task.CompletedTask)) | ||
.Should() | ||
.Throw<InvalidOperationException>(); | ||
} | ||
|
||
[Fact] | ||
public async Task Initialize_Ok() | ||
{ | ||
var control = new CircuitBreakerManualControl(); | ||
var isolateCalled = false; | ||
var resetCalled = false; | ||
|
||
control.Initialize( | ||
context => | ||
{ | ||
context.IsVoid.Should().BeTrue(); | ||
context.IsSynchronous.Should().BeFalse(); | ||
isolateCalled = true; | ||
return Task.CompletedTask; | ||
}, | ||
context => | ||
{ | ||
context.IsVoid.Should().BeTrue(); | ||
context.IsSynchronous.Should().BeFalse(); | ||
resetCalled = true; | ||
return Task.CompletedTask; | ||
}); | ||
|
||
await control.IsolateAsync(CancellationToken.None); | ||
await control.ResetAsync(CancellationToken.None); | ||
|
||
isolateCalled.Should().BeTrue(); | ||
resetCalled.Should().BeTrue(); | ||
} | ||
} |
Oops, something went wrong.