Skip to content

Commit

Permalink
Introduce public API for Hedging Resilience Strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk committed Apr 26, 2023
1 parent 6cc8329 commit a8b977e
Show file tree
Hide file tree
Showing 27 changed files with 970 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.*");
}
}
58 changes: 58 additions & 0 deletions src/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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();
}

[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);
}
85 changes: 85 additions & 0 deletions src/Polly.Core.Tests/Hedging/HedgingStrategyOptionsTResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.ComponentModel.DataAnnotations;
using Polly.Hedging;
using Polly.Strategy;
using Polly.Utils;

namespace Polly.Core.Tests.Hedging;

public class HedgingStrategyOptionsTResultTests
{
[Fact]
public void Ctor_EnsureDefaults()
{
var options = new HedgingStrategyOptions<int>();

options.StrategyType.Should().Be("Hedging");
options.ShouldHandle.Should().NotBeNull();
options.ShouldHandle.IsEmpty.Should().BeTrue();
options.HedgingActionGenerator.Should().BeNull();
options.HedgingDelay.Should().Be(TimeSpan.FromSeconds(2));
options.MaxHedgedAttempts.Should().Be(2);
}

[Fact]
public void Validation()
{
var options = new HedgingStrategyOptions<int>
{
HedgingDelayGenerator = null!,
ShouldHandle = null!,
MaxHedgedAttempts = -1,
};

options
.Invoking(o => ValidationHelper.ValidateObject(o, "Invalid."))
.Should()
.Throw<ValidationException>()
.WithMessage("""
Invalid.
Validation Errors:
The field MaxHedgedAttempts must be between 2 and 10.
The ShouldHandle field is required.
The HedgingActionGenerator field is required.
The HedgingDelayGenerator field is required.
""");
}

[Fact]
public async Task AsNonGenericOptions_Ok()
{
var options = new HedgingStrategyOptions<int>
{
HedgingDelayGenerator = new NoOutcomeGenerator<HedgingDelayArguments, TimeSpan>().SetGenerator(args => TimeSpan.FromSeconds(123)),
ShouldHandle = new OutcomePredicate<HandleHedgingArguments, int>().HandleResult(-1),
StrategyName = "Dummy",
StrategyType = "Dummy-Hedging",
HedgingDelay = TimeSpan.FromSeconds(3),
MaxHedgedAttempts = 4,
HedgingActionGenerator = args => () => Task.FromResult(555)
};

var nonGeneric = options.AsNonGenericOptions();

nonGeneric.Should().NotBeNull();
nonGeneric.StrategyType.Should().Be("Dummy-Hedging");
nonGeneric.StrategyName.Should().Be("Dummy");
nonGeneric.Handler.IsEmpty.Should().BeFalse();
nonGeneric.MaxHedgedAttempts.Should().Be(4);
nonGeneric.HedgingDelay.Should().Be(TimeSpan.FromSeconds(3));

var handler = nonGeneric.Handler.CreateHandler();
handler.Should().NotBeNull();

(await handler!.TryCreateHedgedAction<int>(ResilienceContext.Get())!()).Should().Be(555);

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

result = await handler!.ShouldHandleAsync(new Outcome<int>(0), new HandleHedgingArguments(ResilienceContext.Get()));
result.Should().BeFalse();

var delay = await nonGeneric.HedgingDelayGenerator.CreateHandler(TimeSpan.Zero, _ => true)!(new HedgingDelayArguments(ResilienceContext.Get(), 4));
delay.Should().Be(TimeSpan.FromSeconds(123));
}
}
Loading

0 comments on commit a8b977e

Please sign in to comment.