Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce IResilienceStrategy and core primitives for V8 #1056

Merged
merged 6 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/analyzers/Stylecop.globalconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1529,7 +1529,7 @@ dotnet_diagnostic.SA1600.severity = warning
# Title : Partial elements should be documented
# Category : StyleCop.CSharp.DocumentationRules
# Help Link: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1601.md
dotnet_diagnostic.SA1601.severity = warning
dotnet_diagnostic.SA1601.severity = none

# Title : Enumeration items should be documented
# Category : StyleCop.CSharp.DocumentationRules
Expand Down
1 change: 1 addition & 0 deletions eng/stryker-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"ConfigureAwait",
"Dispose",
"LogError",
"Debug.Assert",
"LogInformation"
],
"ignore-mutations": [
Expand Down
66 changes: 66 additions & 0 deletions src/Polly.Core.Tests/DelegatingResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using FluentAssertions;
using Polly.Core.Tests.Utils;
using Xunit;

namespace Polly.Core.Tests;

public class DelegatingResilienceStrategyTests
{
[Fact]
public void Next_Change_Ok()
{
var next = new TestResilienceStrategy();
var strategy = new TestResilienceStrategy
{
Next = next
};

strategy.Next.Should().Be(next);
}

[Fact]
public void Next_ChangeToNull_Throws()
{
var strategy = new TestResilienceStrategy();

strategy.Invoking(s => s.Next = null!).Should().Throw<ArgumentNullException>();
}

[Fact]
public void Next_ChangeAfterExecuted_Throws()
{
var strategy = new TestResilienceStrategy();

strategy.Execute(_ => { }, default);

strategy
.Invoking(s => s.Next = NullResilienceStrategy.Instance)
.Should()
.Throw<InvalidOperationException>()
.WithMessage("The delegating resilience strategy has already been executed and changing the value of 'Next' property is not allowed.");
}

[Fact]
public void Execute_HasNext_EnsureExecuteOrder()
{
List<int> executions = new();

var strategy = new TestResilienceStrategy
{
Before = (_, _) => executions.Add(1),
After = (_, _) => executions.Add(5),
};

var next = new TestResilienceStrategy
{
Before = (_, _) => executions.Add(2),
After = (_, _) => executions.Add(4),
};

strategy.Next = next;
strategy.Execute(_ => executions.Add(3), default);

executions.Should().BeInAscendingOrder();
executions.Should().HaveCount(5);
}
}
12 changes: 0 additions & 12 deletions src/Polly.Core.Tests/DummyTest.cs

This file was deleted.

22 changes: 22 additions & 0 deletions src/Polly.Core.Tests/NullResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FluentAssertions;
using Xunit;

namespace Polly.Core.Tests;

public class NullResilienceStrategyTests
{
[Fact]
public void Instance_ShouldNotBeNull()
{
NullResilienceStrategy.Instance.Should().NotBeNull();
}

[Fact]
public void Execute_Ok()
{
bool executed = false;
NullResilienceStrategy.Instance.Execute(_ => executed = true);

executed.Should().BeTrue();
}
}
1 change: 1 addition & 0 deletions src/Polly.Core.Tests/Polly.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Nullable>enable</Nullable>
<SkipPollyUsings>true</SkipPollyUsings>
<Threshold>100</Threshold>
<NoWarn>$(NoWarn);SA1600</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
81 changes: 81 additions & 0 deletions src/Polly.Core.Tests/ResilienceContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using FluentAssertions;
using Xunit;

namespace Polly.Core.Tests;

public class ResilienceContextTests
{
[Fact]
public void Get_EnsureNotNull()
{
ResilienceContext.Get().Should().NotBeNull();
}

[Fact]
public void Get_EnsureDefaults()
{
var context = ResilienceContext.Get();

AssertDefaults(context);
}

[Fact]
public void Return_Null_Throws()
{
Assert.Throws<ArgumentNullException>(() => ResilienceContext.Return(null!));
}

[Fact]
public void Return_EnsureDefaults()
{
using var cts = new CancellationTokenSource();
var context = ResilienceContext.Get();
context.CancellationToken = cts.Token;
context.Initialize<bool>(true);
context.CancellationToken.Should().Be(cts.Token);

ResilienceContext.Return(context);

AssertDefaults(context);
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public void Initialize_Typed_Ok(bool synchronous)
{
var context = ResilienceContext.Get();
context.Initialize<bool>(synchronous);

context.ResultType.Should().Be(typeof(bool));
context.IsVoid.Should().BeFalse();
context.IsInitialized.Should().BeTrue();
context.IsSynchronous.Should().Be(synchronous);
context.ContinueOnCapturedContext.Should().BeFalse();
}

[InlineData(true)]
[InlineData(false)]
[Theory]
public void Initialize_Void_Ok(bool synchronous)
{
var context = ResilienceContext.Get();
context.Initialize<VoidResult>(synchronous);

context.ResultType.Should().Be(typeof(VoidResult));
context.IsVoid.Should().BeTrue();
context.IsInitialized.Should().BeTrue();
context.IsSynchronous.Should().Be(synchronous);
context.ContinueOnCapturedContext.Should().BeFalse();
}

private static void AssertDefaults(ResilienceContext context)
{
context.IsInitialized.Should().BeFalse();
context.ContinueOnCapturedContext.Should().BeFalse();
context.IsVoid.Should().BeFalse();
context.ResultType.Name.Should().Be("UnknownResult");
context.IsSynchronous.Should().BeFalse();
context.CancellationToken.Should().Be(CancellationToken.None);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using FluentAssertions;
using Polly.Core.Tests.Utils;
using Xunit;

namespace Polly.Core.Tests;

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously

public partial class ResilienceStrategyExtensionsTests
{
public static IEnumerable<object[]> ExecuteAsTaskAsync_EnsureCorrectBehavior_Data()
{
return ConvertExecuteParameters(ExecuteAsTaskAsync_EnsureCorrectBehavior_ExecuteParameters);
}

private static IEnumerable<ExecuteParameters> ExecuteAsTaskAsync_EnsureCorrectBehavior_ExecuteParameters()
{
yield return new ExecuteParameters(r => r.ExecuteAsTaskAsync(async _ => { }))
{
Caption = "ExecuteAsTaskAsync_NoCancellation",
AssertContext = AssertResilienceContext,
AssertContextAfter = AssertContextNotInitialized,
};

yield return new ExecuteParameters(r => r.ExecuteAsTaskAsync(async t => { t.Should().Be(CancellationToken); }, CancellationToken))
{
Caption = "ExecuteAsTaskAsync_Cancellation",
AssertContext = AssertResilienceContextAndToken,
AssertContextAfter = AssertContextNotInitialized,
};

yield return new ExecuteParameters(r => r.ExecuteAsTaskAsync(async (_, s) => { s.Should().Be("dummy-state"); }, ResilienceContext.Get(), "dummy-state"))
{
Caption = "ExecuteAsTaskAsync_ResilienceContextAndState",
AssertContext = AssertResilienceContext,
AssertContextAfter = AssertContextInitialized,
};

static void AssertResilienceContext(ResilienceContext context)
{
context.IsSynchronous.Should().BeFalse();
context.IsVoid.Should().BeTrue();
context.ContinueOnCapturedContext.Should().BeFalse();
}

static void AssertResilienceContextAndToken(ResilienceContext context)
{
AssertResilienceContext(context);
context.CancellationToken.Should().Be(CancellationToken);
}

static void AssertContextNotInitialized(ResilienceContext context) => context.IsInitialized.Should().BeFalse();

static void AssertContextInitialized(ResilienceContext context) => context.IsInitialized.Should().BeTrue();
}

[MemberData(nameof(ExecuteAsTaskAsync_EnsureCorrectBehavior_Data))]
[Theory]
public async Task ExecuteAsTaskAsync_Ok(ExecuteParameters parameters)
{
ResilienceContext? context = null;

var strategy = new TestResilienceStrategy
{
Before = (c, _) =>
{
context = c;
parameters.AssertContext(c);
},
};

var result = await parameters.Execute(strategy);

parameters.AssertContextAfter(context!);
parameters.AssertResult(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using FluentAssertions;
using Polly.Core.Tests.Utils;
using Xunit;

namespace Polly.Core.Tests;

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously

public partial class ResilienceStrategyExtensionsTests
{
public static IEnumerable<object[]> ExecuteAsTaskAsyncT_EnsureCorrectBehavior_Data()
{
return ConvertExecuteParameters(ExecuteAsTaskAsyncT_EnsureCorrectBehavior_ExecuteParameters);
}

private static IEnumerable<ExecuteParameters> ExecuteAsTaskAsyncT_EnsureCorrectBehavior_ExecuteParameters()
{
long result = 12345;

yield return new ExecuteParameters<long>(r => r.ExecuteAsTaskAsync(async t => result), result)
{
Caption = "ExecuteAsTaskAsyncT_NoCancellation",
AssertContext = AssertResilienceContext,
AssertContextAfter = AssertContextNotInitialized,
};

yield return new ExecuteParameters<long>(r => r.ExecuteAsTaskAsync(async t => { t.Should().Be(CancellationToken); return result; }, CancellationToken), result)
{
Caption = "ExecuteAsTaskAsyncT_Cancellation",
AssertContext = AssertResilienceContextAndToken,
AssertContextAfter = AssertContextNotInitialized,
};

yield return new ExecuteParameters<long>(r => r.ExecuteAsTaskAsync(async (_, s) => { s.Should().Be("dummy-state"); return result; }, ResilienceContext.Get(), "dummy-state"), result)
{
Caption = "ExecuteAsTaskAsyncT_ResilienceContextAndState",
AssertContext = AssertResilienceContext,
AssertContextAfter = AssertContextInitialized,
};

static void AssertResilienceContext(ResilienceContext context)
{
context.IsSynchronous.Should().BeFalse();
context.IsVoid.Should().BeFalse();
context.ResultType.Should().Be(typeof(long));
context.ContinueOnCapturedContext.Should().BeFalse();
}

static void AssertResilienceContextAndToken(ResilienceContext context)
{
AssertResilienceContext(context);
context.CancellationToken.Should().Be(CancellationToken);
}

static void AssertContextNotInitialized(ResilienceContext context) => context.IsInitialized.Should().BeFalse();

static void AssertContextInitialized(ResilienceContext context) => context.IsInitialized.Should().BeTrue();
}

[MemberData(nameof(ExecuteAsTaskAsyncT_EnsureCorrectBehavior_Data))]
[Theory]
public async Task ExecuteAsTaskAsyncT_Ok(ExecuteParameters parameters)
{
ResilienceContext? context = null;

var strategy = new TestResilienceStrategy
{
Before = (c, _) =>
{
context = c;
parameters.AssertContext(c);
},
};

var result = await parameters.Execute(strategy);

parameters.AssertContextAfter(context!);
parameters.AssertResult(result);
}
}
Loading