Skip to content

Commit 0229729

Browse files
authored
Introduce IResilienceStrategy and core primitives for V8 (App-vNext#1056)
1 parent c4ebb24 commit 0229729

31 files changed

+1559
-15
lines changed

eng/analyzers/Stylecop.globalconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1529,7 +1529,7 @@ dotnet_diagnostic.SA1600.severity = warning
15291529
# Title : Partial elements should be documented
15301530
# Category : StyleCop.CSharp.DocumentationRules
15311531
# Help Link: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1601.md
1532-
dotnet_diagnostic.SA1601.severity = warning
1532+
dotnet_diagnostic.SA1601.severity = none
15331533

15341534
# Title : Enumeration items should be documented
15351535
# Category : StyleCop.CSharp.DocumentationRules

eng/stryker-config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"ConfigureAwait",
1212
"Dispose",
1313
"LogError",
14+
"Debug.Assert",
1415
"LogInformation"
1516
],
1617
"ignore-mutations": [
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using FluentAssertions;
2+
using Polly.Core.Tests.Utils;
3+
using Xunit;
4+
5+
namespace Polly.Core.Tests;
6+
7+
public class DelegatingResilienceStrategyTests
8+
{
9+
[Fact]
10+
public void Next_Change_Ok()
11+
{
12+
var next = new TestResilienceStrategy();
13+
var strategy = new TestResilienceStrategy
14+
{
15+
Next = next
16+
};
17+
18+
strategy.Next.Should().Be(next);
19+
}
20+
21+
[Fact]
22+
public void Next_ChangeToNull_Throws()
23+
{
24+
var strategy = new TestResilienceStrategy();
25+
26+
strategy.Invoking(s => s.Next = null!).Should().Throw<ArgumentNullException>();
27+
}
28+
29+
[Fact]
30+
public void Next_ChangeAfterExecuted_Throws()
31+
{
32+
var strategy = new TestResilienceStrategy();
33+
34+
strategy.Execute(_ => { }, default);
35+
36+
strategy
37+
.Invoking(s => s.Next = NullResilienceStrategy.Instance)
38+
.Should()
39+
.Throw<InvalidOperationException>()
40+
.WithMessage("The delegating resilience strategy has already been executed and changing the value of 'Next' property is not allowed.");
41+
}
42+
43+
[Fact]
44+
public void Execute_HasNext_EnsureExecuteOrder()
45+
{
46+
List<int> executions = new();
47+
48+
var strategy = new TestResilienceStrategy
49+
{
50+
Before = (_, _) => executions.Add(1),
51+
After = (_, _) => executions.Add(5),
52+
};
53+
54+
var next = new TestResilienceStrategy
55+
{
56+
Before = (_, _) => executions.Add(2),
57+
After = (_, _) => executions.Add(4),
58+
};
59+
60+
strategy.Next = next;
61+
strategy.Execute(_ => executions.Add(3), default);
62+
63+
executions.Should().BeInAscendingOrder();
64+
executions.Should().HaveCount(5);
65+
}
66+
}

src/Polly.Core.Tests/DummyTest.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using FluentAssertions;
2+
using Xunit;
3+
4+
namespace Polly.Core.Tests;
5+
6+
public class NullResilienceStrategyTests
7+
{
8+
[Fact]
9+
public void Instance_ShouldNotBeNull()
10+
{
11+
NullResilienceStrategy.Instance.Should().NotBeNull();
12+
}
13+
14+
[Fact]
15+
public void Execute_Ok()
16+
{
17+
bool executed = false;
18+
NullResilienceStrategy.Instance.Execute(_ => executed = true);
19+
20+
executed.Should().BeTrue();
21+
}
22+
}

src/Polly.Core.Tests/Polly.Core.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<Nullable>enable</Nullable>
88
<SkipPollyUsings>true</SkipPollyUsings>
99
<Threshold>100</Threshold>
10+
<NoWarn>$(NoWarn);SA1600</NoWarn>
1011
</PropertyGroup>
1112

1213
<ItemGroup>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using FluentAssertions;
2+
using Xunit;
3+
4+
namespace Polly.Core.Tests;
5+
6+
public class ResilienceContextTests
7+
{
8+
[Fact]
9+
public void Get_EnsureNotNull()
10+
{
11+
ResilienceContext.Get().Should().NotBeNull();
12+
}
13+
14+
[Fact]
15+
public void Get_EnsureDefaults()
16+
{
17+
var context = ResilienceContext.Get();
18+
19+
AssertDefaults(context);
20+
}
21+
22+
[Fact]
23+
public void Return_Null_Throws()
24+
{
25+
Assert.Throws<ArgumentNullException>(() => ResilienceContext.Return(null!));
26+
}
27+
28+
[Fact]
29+
public void Return_EnsureDefaults()
30+
{
31+
using var cts = new CancellationTokenSource();
32+
var context = ResilienceContext.Get();
33+
context.CancellationToken = cts.Token;
34+
context.Initialize<bool>(true);
35+
context.CancellationToken.Should().Be(cts.Token);
36+
37+
ResilienceContext.Return(context);
38+
39+
AssertDefaults(context);
40+
}
41+
42+
[InlineData(true)]
43+
[InlineData(false)]
44+
[Theory]
45+
public void Initialize_Typed_Ok(bool synchronous)
46+
{
47+
var context = ResilienceContext.Get();
48+
context.Initialize<bool>(synchronous);
49+
50+
context.ResultType.Should().Be(typeof(bool));
51+
context.IsVoid.Should().BeFalse();
52+
context.IsInitialized.Should().BeTrue();
53+
context.IsSynchronous.Should().Be(synchronous);
54+
context.ContinueOnCapturedContext.Should().BeFalse();
55+
}
56+
57+
[InlineData(true)]
58+
[InlineData(false)]
59+
[Theory]
60+
public void Initialize_Void_Ok(bool synchronous)
61+
{
62+
var context = ResilienceContext.Get();
63+
context.Initialize<VoidResult>(synchronous);
64+
65+
context.ResultType.Should().Be(typeof(VoidResult));
66+
context.IsVoid.Should().BeTrue();
67+
context.IsInitialized.Should().BeTrue();
68+
context.IsSynchronous.Should().Be(synchronous);
69+
context.ContinueOnCapturedContext.Should().BeFalse();
70+
}
71+
72+
private static void AssertDefaults(ResilienceContext context)
73+
{
74+
context.IsInitialized.Should().BeFalse();
75+
context.ContinueOnCapturedContext.Should().BeFalse();
76+
context.IsVoid.Should().BeFalse();
77+
context.ResultType.Name.Should().Be("UnknownResult");
78+
context.IsSynchronous.Should().BeFalse();
79+
context.CancellationToken.Should().Be(CancellationToken.None);
80+
}
81+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using FluentAssertions;
2+
using Polly.Core.Tests.Utils;
3+
using Xunit;
4+
5+
namespace Polly.Core.Tests;
6+
7+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
8+
9+
public partial class ResilienceStrategyExtensionsTests
10+
{
11+
public static IEnumerable<object[]> ExecuteAsTaskAsync_EnsureCorrectBehavior_Data()
12+
{
13+
return ConvertExecuteParameters(ExecuteAsTaskAsync_EnsureCorrectBehavior_ExecuteParameters);
14+
}
15+
16+
private static IEnumerable<ExecuteParameters> ExecuteAsTaskAsync_EnsureCorrectBehavior_ExecuteParameters()
17+
{
18+
yield return new ExecuteParameters(r => r.ExecuteAsTaskAsync(async _ => { }))
19+
{
20+
Caption = "ExecuteAsTaskAsync_NoCancellation",
21+
AssertContext = AssertResilienceContext,
22+
AssertContextAfter = AssertContextNotInitialized,
23+
};
24+
25+
yield return new ExecuteParameters(r => r.ExecuteAsTaskAsync(async t => { t.Should().Be(CancellationToken); }, CancellationToken))
26+
{
27+
Caption = "ExecuteAsTaskAsync_Cancellation",
28+
AssertContext = AssertResilienceContextAndToken,
29+
AssertContextAfter = AssertContextNotInitialized,
30+
};
31+
32+
yield return new ExecuteParameters(r => r.ExecuteAsTaskAsync(async (_, s) => { s.Should().Be("dummy-state"); }, ResilienceContext.Get(), "dummy-state"))
33+
{
34+
Caption = "ExecuteAsTaskAsync_ResilienceContextAndState",
35+
AssertContext = AssertResilienceContext,
36+
AssertContextAfter = AssertContextInitialized,
37+
};
38+
39+
static void AssertResilienceContext(ResilienceContext context)
40+
{
41+
context.IsSynchronous.Should().BeFalse();
42+
context.IsVoid.Should().BeTrue();
43+
context.ContinueOnCapturedContext.Should().BeFalse();
44+
}
45+
46+
static void AssertResilienceContextAndToken(ResilienceContext context)
47+
{
48+
AssertResilienceContext(context);
49+
context.CancellationToken.Should().Be(CancellationToken);
50+
}
51+
52+
static void AssertContextNotInitialized(ResilienceContext context) => context.IsInitialized.Should().BeFalse();
53+
54+
static void AssertContextInitialized(ResilienceContext context) => context.IsInitialized.Should().BeTrue();
55+
}
56+
57+
[MemberData(nameof(ExecuteAsTaskAsync_EnsureCorrectBehavior_Data))]
58+
[Theory]
59+
public async Task ExecuteAsTaskAsync_Ok(ExecuteParameters parameters)
60+
{
61+
ResilienceContext? context = null;
62+
63+
var strategy = new TestResilienceStrategy
64+
{
65+
Before = (c, _) =>
66+
{
67+
context = c;
68+
parameters.AssertContext(c);
69+
},
70+
};
71+
72+
var result = await parameters.Execute(strategy);
73+
74+
parameters.AssertContextAfter(context!);
75+
parameters.AssertResult(result);
76+
}
77+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using FluentAssertions;
2+
using Polly.Core.Tests.Utils;
3+
using Xunit;
4+
5+
namespace Polly.Core.Tests;
6+
7+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
8+
9+
public partial class ResilienceStrategyExtensionsTests
10+
{
11+
public static IEnumerable<object[]> ExecuteAsTaskAsyncT_EnsureCorrectBehavior_Data()
12+
{
13+
return ConvertExecuteParameters(ExecuteAsTaskAsyncT_EnsureCorrectBehavior_ExecuteParameters);
14+
}
15+
16+
private static IEnumerable<ExecuteParameters> ExecuteAsTaskAsyncT_EnsureCorrectBehavior_ExecuteParameters()
17+
{
18+
long result = 12345;
19+
20+
yield return new ExecuteParameters<long>(r => r.ExecuteAsTaskAsync(async t => result), result)
21+
{
22+
Caption = "ExecuteAsTaskAsyncT_NoCancellation",
23+
AssertContext = AssertResilienceContext,
24+
AssertContextAfter = AssertContextNotInitialized,
25+
};
26+
27+
yield return new ExecuteParameters<long>(r => r.ExecuteAsTaskAsync(async t => { t.Should().Be(CancellationToken); return result; }, CancellationToken), result)
28+
{
29+
Caption = "ExecuteAsTaskAsyncT_Cancellation",
30+
AssertContext = AssertResilienceContextAndToken,
31+
AssertContextAfter = AssertContextNotInitialized,
32+
};
33+
34+
yield return new ExecuteParameters<long>(r => r.ExecuteAsTaskAsync(async (_, s) => { s.Should().Be("dummy-state"); return result; }, ResilienceContext.Get(), "dummy-state"), result)
35+
{
36+
Caption = "ExecuteAsTaskAsyncT_ResilienceContextAndState",
37+
AssertContext = AssertResilienceContext,
38+
AssertContextAfter = AssertContextInitialized,
39+
};
40+
41+
static void AssertResilienceContext(ResilienceContext context)
42+
{
43+
context.IsSynchronous.Should().BeFalse();
44+
context.IsVoid.Should().BeFalse();
45+
context.ResultType.Should().Be(typeof(long));
46+
context.ContinueOnCapturedContext.Should().BeFalse();
47+
}
48+
49+
static void AssertResilienceContextAndToken(ResilienceContext context)
50+
{
51+
AssertResilienceContext(context);
52+
context.CancellationToken.Should().Be(CancellationToken);
53+
}
54+
55+
static void AssertContextNotInitialized(ResilienceContext context) => context.IsInitialized.Should().BeFalse();
56+
57+
static void AssertContextInitialized(ResilienceContext context) => context.IsInitialized.Should().BeTrue();
58+
}
59+
60+
[MemberData(nameof(ExecuteAsTaskAsyncT_EnsureCorrectBehavior_Data))]
61+
[Theory]
62+
public async Task ExecuteAsTaskAsyncT_Ok(ExecuteParameters parameters)
63+
{
64+
ResilienceContext? context = null;
65+
66+
var strategy = new TestResilienceStrategy
67+
{
68+
Before = (c, _) =>
69+
{
70+
context = c;
71+
parameters.AssertContext(c);
72+
},
73+
};
74+
75+
var result = await parameters.Execute(strategy);
76+
77+
parameters.AssertContextAfter(context!);
78+
parameters.AssertResult(result);
79+
}
80+
}

0 commit comments

Comments
 (0)