Skip to content

Commit

Permalink
Introduce OutcomeEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk committed Apr 4, 2023
1 parent b1ffcb9 commit c21d536
Show file tree
Hide file tree
Showing 13 changed files with 537 additions and 124 deletions.
3 changes: 3 additions & 0 deletions src/Polly.Core.Tests/Retry/RetryStrategyOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ public void Ctor_Ok()

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

options.OnRetry.Should().NotBeNull();
options.OnRetry.IsEmpty.Should().BeTrue();
}
}
15 changes: 15 additions & 0 deletions src/Polly.Core.Tests/Strategy/OnRetryArgumentsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Polly.Retry;

namespace Polly.Core.Tests.Strategy;

public class OnRetryArgumentsTests
{
[Fact]
public void Ctor_Ok()
{
var args = new OnRetryArguments(ResilienceContext.Get(), 2);

args.Context.Should().NotBeNull();
args.Attempt.Should().Be(2);
}
}
125 changes: 125 additions & 0 deletions src/Polly.Core.Tests/Strategy/OutcomeEventTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System;
using System.Threading.Tasks;
using Polly.Strategy;

namespace Polly.Core.Tests.Strategy;

public class OutcomeEventTests
{
private readonly DummyEvent _sut = new();

[Fact]
public void Empty_Ok()
{
_sut.IsEmpty.Should().BeTrue();

_sut.Add<int>(() => { });

_sut.IsEmpty.Should().BeFalse();
}

[Fact]
public void CreateHandler_Empty_ReturnsNull()
{
_sut.CreateHandler().Should().BeNull();
}

public static readonly TheoryData<Action<DummyEvent>> Data = new()
{
sut =>
{
bool called = false;
sut.Add<int>((_, _) => { called = true; });
InvokeHandler(sut, new Outcome<int>(0));
called.Should().BeTrue();
},
sut =>
{
bool called = false;
sut.Add<int>(_ => { called = true; });
InvokeHandler(sut, new Outcome<int>(0));
called.Should().BeTrue();
},
sut =>
{
bool called = false;
sut.Add<int>(() => { called = true; });
InvokeHandler(sut, new Outcome<int>(0));
called.Should().BeTrue();
},
sut =>
{
bool called = false;
sut.Add<int>((_, _) => { called = true; return default; });
InvokeHandler(sut, new Outcome<int>(0));
called.Should().BeTrue();
},
sut =>
{
int calls = 0;
sut.Add<int>((_, _) => { calls++; });
sut.Add<int>((_, _) => { calls++; });

InvokeHandler(sut, new Outcome<int>(0));

calls.Should().Be(2);
},
sut =>
{
bool called = false;
sut.Add<int>((_, _) => { called = true; return default; });
sut.Add<double>((_, _) => { called = true; return default; });
InvokeHandler(sut, new Outcome<bool>(true));
called.Should().BeFalse();
},
sut =>
{
bool called = false;
sut.Add<double>((_, _) => { called = true; return default; });
InvokeHandler(sut, new Outcome<bool>(true));
called.Should().BeFalse();
},
};

[MemberData(nameof(Data))]
[Theory]
public void InvokeCallbacks_Ok(Action<DummyEvent> callback)
{
_sut.Invoking(s => callback(s)).Should().NotThrow();
callback(_sut);
}

[Fact]
public void AddCallback_DifferentResultType_NotInvoked()
{
var callbacks = new List<int>();

for (var i = 0; i < 10; i++)
{
var index = i;

_sut.Add<int>((_, _) =>
{
callbacks.Add(index);
});

_sut.Add<bool>((_, _) =>
{
callbacks.Add(index);
});
}

InvokeHandler(_sut, new Outcome<int>(1));

callbacks.Distinct().Should().HaveCount(10);
}

private static void InvokeHandler<T>(DummyEvent sut, Outcome<T> outcome)
{
sut.CreateHandler()!.Handle(outcome, new TestArguments()).AsTask().Wait();
}

public sealed class DummyEvent : OutcomeEvent<TestArguments, DummyEvent>
{
}
}
68 changes: 68 additions & 0 deletions src/Polly.Core.Tests/Strategy/SimpleEventTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Polly.Timeout;

namespace Polly.Core.Tests.Timeout;

public class SimpleEventTests
{
[Fact]
public async Task Add_EnsureOrdering()
{
var ev = new DummyEvent();
List<int> raisedEvents = new List<int>();

ev.Add(() => raisedEvents.Add(1));
ev.Add(args => raisedEvents.Add(2));
ev.Add(args => { raisedEvents.Add(3); return default; });

var handler = ev.CreateHandler()!;

await handler(new TestArguments());

raisedEvents.Should().HaveCount(3);
raisedEvents.Should().BeInAscendingOrder();
}

[Fact]
public void Add_EnsureValidation()
{
var ev = new DummyEvent();

Assert.Throws<ArgumentNullException>(() => ev.Add((Action)null!));
Assert.Throws<ArgumentNullException>(() => ev.Add((Action<TestArguments>)null!));
Assert.Throws<ArgumentNullException>(() => ev.Add(null!));
}

[InlineData(1)]
[InlineData(2)]
[InlineData(100)]
[Theory]
public async Task CreateHandler_Ok(int count)
{
var ev = new DummyEvent();
var events = new List<int>();

for (int i = 0; i < count; i++)
{
ev.Add(() => events.Add(i));
}

await ev.CreateHandler()!(new TestArguments());

events.Should().HaveCount(count);
events.Should().BeInAscendingOrder();
}

[Fact]
public void CreateHandler_NoEvents_Null()
{
var ev = new DummyEvent();

var handler = ev.CreateHandler();

handler.Should().BeNull();
}

private class DummyEvent : SimpleEvent<TestArguments, DummyEvent>
{
}
}
56 changes: 2 additions & 54 deletions src/Polly.Core.Tests/Timeout/OnTimeoutEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,8 @@ namespace Polly.Core.Tests.Timeout;
public class OnTimeoutEventTests
{
[Fact]
public async Task Add_EnsureOrdering()
public void Ctor_Ok()
{
var ev = new OnTimeoutEvent();
List<int> raisedEvents = new List<int>();

ev.Add(() => raisedEvents.Add(1));
ev.Add(args => raisedEvents.Add(2));
ev.Add(args => { raisedEvents.Add(3); return default; });

var handler = ev.CreateHandler()!;

await handler(TimeoutTestUtils.OnTimeoutArguments());

raisedEvents.Should().HaveCount(3);
raisedEvents.Should().BeInAscendingOrder();
}

[Fact]
public void Add_EnsureValidation()
{
var ev = new OnTimeoutEvent();

Assert.Throws<ArgumentNullException>(() => ev.Add((Action)null!));
Assert.Throws<ArgumentNullException>(() => ev.Add((Action<OnTimeoutArguments>)null!));
Assert.Throws<ArgumentNullException>(() => ev.Add(null!));
}

[InlineData(1)]
[InlineData(2)]
[InlineData(100)]
[Theory]
public async Task CreateHandler_Ok(int count)
{
var ev = new OnTimeoutEvent();
var events = new List<int>();

for (int i = 0; i < count; i++)
{
ev.Add(() => events.Add(i));
}

await ev.CreateHandler()!(TimeoutTestUtils.OnTimeoutArguments());

events.Should().HaveCount(count);
events.Should().BeInAscendingOrder();
}

[Fact]
public void CreateHandler_NoEvents_Null()
{
var ev = new OnTimeoutEvent();

var handler = ev.CreateHandler();

handler.Should().BeNull();
this.Invoking(_ => new OnTimeoutEvent()).Should().NotThrow();
}
}
10 changes: 10 additions & 0 deletions src/Polly.Core.Tests/Utils/TestArguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Polly.Strategy;

namespace Polly.Core.Tests.Utils;

public class TestArguments : IResilienceArguments
{
public TestArguments() => Context = ResilienceContext.Get();

public ResilienceContext Context { get; }
}
28 changes: 28 additions & 0 deletions src/Polly.Core/Retry/OnRetryArguments.cs
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="OnRetryEvent"/> for handling the retry event.
/// </summary>
public readonly struct OnRetryArguments : IResilienceArguments
{
internal OnRetryArguments(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; }
}
10 changes: 10 additions & 0 deletions src/Polly.Core/Retry/OnRetryEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Polly.Strategy;

namespace Polly.Retry;

/// <summary>
/// This class holds the user-callbacks that are invoked on retries.
/// </summary>
public sealed class OnRetryEvent : OutcomeEvent<OnRetryArguments, OnRetryEvent>
{
}
9 changes: 9 additions & 0 deletions src/Polly.Core/Retry/RetryStrategyOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,13 @@ public class RetryStrategyOptions
/// </remarks>
[Required]
public RetryDelayGenerator RetryDelayGenerator { get; set; } = new();

/// <summary>
/// Gets or sets the <see cref="OnRetryEvent"/> instance that is invoked when retry happens.
/// </summary>
/// <remarks>
/// By default, the event is empty and no callbacks are registered.
/// </remarks>
[Required]
public OnRetryEvent OnRetry { get; set; } = new();
}
Loading

0 comments on commit c21d536

Please sign in to comment.