Skip to content

Commit

Permalink
Introduce public API for Hedging Resilience Strategy (#1160)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored Apr 26, 2023
1 parent 6cc8329 commit 38ae8fd
Show file tree
Hide file tree
Showing 28 changed files with 1,029 additions and 4 deletions.
14 changes: 14 additions & 0 deletions src/Polly.Core.Tests/Hedging/HandleHedgingArgumentsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Polly.Hedging;

namespace Polly.Core.Tests.Hedging;

public class HandleHedgingArgumentsTests
{
[Fact]
public void Ctor_Ok()
{
var args = new HandleHedgingArguments(ResilienceContext.Get());

args.Context.Should().NotBeNull();
}
}
15 changes: 15 additions & 0 deletions src/Polly.Core.Tests/Hedging/HedgingDelayArgumentsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Polly.Hedging;

namespace Polly.Core.Tests.Hedging;

public class HedgingDelayArgumentsTests
{
[Fact]
public void Ctor_Ok()
{
var args = new HedgingDelayArguments(ResilienceContext.Get(), 5);

args.Context.Should().NotBeNull();
args.Attempt.Should().Be(5);
}
}
160 changes: 160 additions & 0 deletions src/Polly.Core.Tests/Hedging/HedgingHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System.ComponentModel.DataAnnotations;
using Polly.Hedging;
using Polly.Strategy;

namespace Polly.Core.Tests.Hedging;

public class HedgingHandlerTests
{
[Fact]
public void SetHedging_ConfigureAsInvalid_Throws()
{
var handler = new HedgingHandler();

handler
.Invoking(h => h.SetHedging<int>(handler =>
{
handler.HedgingActionGenerator = null!;
handler.ShouldHandle = null!;
}))
.Should()
.Throw<ValidationException>()
.WithMessage("""
The hedging handler configuration is invalid.
Validation Errors:
The ShouldHandle field is required.
The HedgingActionGenerator field is required.
""");
}

[Fact]
public void SetVoidHedging_ConfigureAsInvalid_Throws()
{
var handler = new HedgingHandler();

handler
.Invoking(h => h.SetVoidHedging(handler =>
{
handler.HedgingActionGenerator = null!;
handler.ShouldHandle = null!;
}))
.Should()
.Throw<ValidationException>()
.WithMessage("""
The hedging handler configuration is invalid.
Validation Errors:
The ShouldHandle field is required.
The HedgingActionGenerator field is required.
""");
}

[Fact]
public void SetHedging_Empty_Discarded()
{
var handler = new HedgingHandler()
.SetHedging<int>(handler =>
{
handler.HedgingActionGenerator = args => () => Task.FromResult(10);
})
.SetVoidHedging(handler =>
{
handler.HedgingActionGenerator = args => () => Task.CompletedTask;
});

handler.IsEmpty.Should().BeTrue();
handler.CreateHandler().Should().BeNull();
}

[Fact]
public async Task SetHedging_Ok()
{
var handler = new HedgingHandler()
.SetHedging<int>(handler =>
{
handler.HedgingActionGenerator = args => () => Task.FromResult(0);
handler.ShouldHandle.HandleResult(-1);
})
.CreateHandler();

var args = new HandleHedgingArguments(ResilienceContext.Get());
handler.Should().NotBeNull();
var result = await handler!.ShouldHandleAsync(new Outcome<int>(-1), args);
result.Should().BeTrue();

handler.HandlesHedging<int>().Should().BeTrue();

var action = handler.TryCreateHedgedAction<int>(ResilienceContext.Get());
action.Should().NotBeNull();
(await (action!()!)).Should().Be(0);

handler.TryCreateHedgedAction<double>(ResilienceContext.Get()).Should().BeNull();
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public async Task SetVoidHedging_Ok(bool returnsNullAction)
{
var handler = new HedgingHandler()
.SetVoidHedging(handler =>
{
handler.HedgingActionGenerator = args =>
{
args.Context.Should().NotBeNull();
if (returnsNullAction)
{
return null;
}

return () => Task.CompletedTask;
};
handler.ShouldHandle.HandleException<InvalidOperationException>();
})
.CreateHandler();

var args = new HandleHedgingArguments(ResilienceContext.Get());
handler.Should().NotBeNull();
var result = await handler!.ShouldHandleAsync(new Outcome<VoidResult>(new InvalidOperationException()), args);
result.Should().BeTrue();

handler.HandlesHedging<VoidResult>().Should().BeTrue();

var action = handler.TryCreateHedgedAction<VoidResult>(ResilienceContext.Get());
if (returnsNullAction)
{
action.Should().BeNull();
}
else
{
action.Should().NotBeNull();
(await (action!()!)).Should().Be(VoidResult.Instance);
}
}

[Fact]
public async Task ShouldHandleAsync_UnknownResultType_Null()
{
var handler = new HedgingHandler()
.SetHedging<int>(handler =>
{
handler.HedgingActionGenerator = args => () => Task.FromResult(0);
handler.ShouldHandle.HandleException<InvalidOperationException>();
})
.SetHedging<string>(handler =>
{
handler.HedgingActionGenerator = args =>
{
args.Context.Should().NotBeNull();
return () => Task.FromResult("dummy");
};
})
.CreateHandler();

var args = new HandleHedgingArguments(ResilienceContext.Get());
(await handler!.ShouldHandleAsync(new Outcome<double>(new InvalidOperationException()), args)).Should().BeFalse();
handler.HandlesHedging<double>().Should().BeFalse();
handler.TryCreateHedgedAction<double>(ResilienceContext.Get()).Should().BeNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Polly.Hedging;

namespace Polly.Core.Tests.Hedging;

public class HedgingResilienceStrategyBuilderExtensionsTests
{
private readonly ResilienceStrategyBuilder _builder = new();

public static readonly TheoryData<Action<ResilienceStrategyBuilder>> HedgingCases = new()
{
builder =>
{
builder.AddHedging(new HedgingStrategyOptions());
},
builder =>
{
builder.AddHedging(new HedgingStrategyOptions<double>
{
HedgingActionGenerator = args => () => Task.FromResult<double>(0)
});
},
};

[MemberData(nameof(HedgingCases))]
[Theory]
public void AddHedging_Ok(Action<ResilienceStrategyBuilder> configure)
{
configure(_builder);
_builder.Build().Should().BeOfType<HedgingResilienceStrategy>();
}

[Fact]
public void AddHedging_InvalidOptions_Throws()
{
_builder
.Invoking(b => b.AddHedging(new HedgingStrategyOptions { Handler = null! }))
.Should()
.Throw<ValidationException>()
.WithMessage("The hedging strategy options are invalid.*");
}

[Fact]
public void AddHedgingT_InvalidOptions_Throws()
{
_builder
.Invoking(b => b.AddHedging(new HedgingStrategyOptions<double> { ShouldHandle = null! }))
.Should()
.Throw<ValidationException>()
.WithMessage("The hedging strategy options are invalid.*");
}
}
59 changes: 59 additions & 0 deletions src/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Polly.Hedging;

namespace Polly.Core.Tests.Hedging;

public class HedgingResilienceStrategyTests
{
private readonly HedgingStrategyOptions _options = new();

[Fact]
public void Ctor_EnsureDefaults()
{
var strategy = Create();

strategy.MaxHedgedAttempts.Should().Be(_options.MaxHedgedAttempts);
strategy.HedgingDelay.Should().Be(_options.HedgingDelay);
strategy.HedgingDelayGenerator.Should().BeNull();
strategy.HedgingHandler.Should().BeNull();
strategy.HedgingHandler.Should().BeNull();
}

[Fact]
public void Execute_Skipped_Ok()
{
var strategy = Create();

strategy.Execute(_ => 10).Should().Be(10);
}

[InlineData(-1)]
[InlineData(-1000)]
[InlineData(0)]
[InlineData(1)]
[InlineData(1000)]
[Theory]
public async Task GetHedgingDelayAsync_GeneratorSet_EnsureCorrectGeneratedValue(int seconds)
{
_options.HedgingDelayGenerator.SetGenerator(args => TimeSpan.FromSeconds(seconds));

var strategy = Create();

var result = await strategy.GetHedgingDelayAsync(ResilienceContext.Get(), 0);

result.Should().Be(TimeSpan.FromSeconds(seconds));
}

[Fact]
public async Task GetHedgingDelayAsync_NoGeneratorSet_EnsureCorrectValue()
{
_options.HedgingDelay = TimeSpan.FromMilliseconds(123);

var strategy = Create();

var result = await strategy.GetHedgingDelayAsync(ResilienceContext.Get(), 0);

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

private HedgingResilienceStrategy Create() => new(_options);
}
Loading

0 comments on commit 38ae8fd

Please sign in to comment.