-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
384 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using Moq; | ||
using Polly.Utils; | ||
|
||
namespace Polly.Core.Tests.Utils; | ||
|
||
internal class FakeTimeProvider : Mock<TimeProvider> | ||
{ | ||
public FakeTimeProvider(long frequency) | ||
: base(MockBehavior.Strict, frequency) | ||
{ | ||
} | ||
|
||
public FakeTimeProvider() | ||
: this(Stopwatch.Frequency) | ||
{ | ||
} | ||
|
||
public FakeTimeProvider SetupDelay(TimeSpan delay, CancellationToken cancellationToken = default) | ||
{ | ||
Setup(x => x.Delay(delay, cancellationToken)).Returns(Task.CompletedTask); | ||
return this; | ||
} | ||
|
||
public FakeTimeProvider SetupDelayCancelled(TimeSpan delay, CancellationToken cancellationToken = default) | ||
{ | ||
Setup(x => x.Delay(delay, cancellationToken)).ThrowsAsync(new OperationCanceledException()); | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
using FluentAssertions; | ||
using Polly.Utils; | ||
using Xunit; | ||
|
||
namespace Polly.Core.Tests.Utils; | ||
|
||
public class SystemTimeProviderTests | ||
{ | ||
[Fact] | ||
public void TimestampFrequency_Ok() | ||
{ | ||
TimeProvider.System.TimestampFrequency.Should().Be(Stopwatch.Frequency); | ||
} | ||
|
||
[Fact] | ||
public async Task CancelAfter_Ok() | ||
{ | ||
await TestUtils.AssertWithTimeoutAsync(async () => | ||
{ | ||
using var cts = new CancellationTokenSource(); | ||
TimeProvider.System.CancelAfter(cts, TimeSpan.FromMilliseconds(10)); | ||
cts.IsCancellationRequested.Should().BeFalse(); | ||
await Task.Delay(10); | ||
cts.Token.IsCancellationRequested.Should().BeTrue(); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public async Task Delay_Ok() | ||
{ | ||
using var cts = new CancellationTokenSource(); | ||
|
||
await TestUtils.AssertWithTimeoutAsync(() => | ||
{ | ||
TimeProvider.System.Delay(TimeSpan.FromMilliseconds(10)).IsCompleted.Should().BeFalse(); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void Delay_NoDelay_Ok() | ||
{ | ||
using var cts = new CancellationTokenSource(); | ||
|
||
TimeProvider.System.Delay(TimeSpan.Zero).IsCompleted.Should().BeTrue(); | ||
} | ||
|
||
[Fact] | ||
public async Task GetElapsedTime_Ok() | ||
{ | ||
var delay = TimeSpan.FromMilliseconds(10); | ||
var delayWithTolerance = TimeSpan.FromMilliseconds(30); | ||
|
||
await TestUtils.AssertWithTimeoutAsync(async () => | ||
{ | ||
var stamp1 = TimeProvider.System.GetTimestamp(); | ||
await Task.Delay(10); | ||
var stamp2 = TimeProvider.System.GetTimestamp(); | ||
var elapsed = TimeProvider.System.GetElapsedTime(stamp1, stamp2); | ||
elapsed.Should().BeGreaterThanOrEqualTo(delay); | ||
elapsed.Should().BeLessThan(delayWithTolerance); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void GetElapsedTime_Mocked_Ok() | ||
{ | ||
var provider = new FakeTimeProvider(40); | ||
provider.SetupSequence(v => v.GetTimestamp()).Returns(120000).Returns(480000); | ||
|
||
var stamp1 = provider.Object.GetTimestamp(); | ||
var stamp2 = provider.Object.GetTimestamp(); | ||
|
||
var delay = provider.Object.GetElapsedTime(stamp1, stamp2); | ||
|
||
var tickFrequency = (double)TimeSpan.TicksPerSecond / 40; | ||
var expected = new TimeSpan((long)((stamp2 - stamp1) * tickFrequency)); | ||
|
||
delay.Should().Be(expected); | ||
} | ||
|
||
[Fact] | ||
public async Task UtcNow_Ok() | ||
{ | ||
await TestUtils.AssertWithTimeoutAsync(() => | ||
{ | ||
var now = TimeProvider.System.UtcNow; | ||
(DateTimeOffset.UtcNow - now).Should().BeLessThanOrEqualTo(TimeSpan.FromMilliseconds(10)); | ||
}); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System; | ||
|
||
namespace Polly.Core.Tests.Utils; | ||
|
||
#pragma warning disable CA1031 // Do not catch general exception types | ||
|
||
public static class TestUtils | ||
{ | ||
public static Task AssertWithTimeoutAsync(Func<Task> assertion) => AssertWithTimeoutAsync(assertion, TimeSpan.FromSeconds(60)); | ||
|
||
public static Task AssertWithTimeoutAsync(Action assertion) => AssertWithTimeoutAsync( | ||
() => | ||
{ | ||
assertion(); | ||
return Task.CompletedTask; | ||
}, | ||
TimeSpan.FromSeconds(60)); | ||
|
||
public static async Task AssertWithTimeoutAsync(Func<Task> assertion, TimeSpan timeout) | ||
{ | ||
var watch = Stopwatch.StartNew(); | ||
|
||
while (true) | ||
{ | ||
try | ||
{ | ||
await assertion(); | ||
return; | ||
} | ||
catch (Exception) when (watch.Elapsed < timeout) | ||
{ | ||
await Task.Delay(5); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using FluentAssertions; | ||
using Polly.Utils; | ||
using Xunit; | ||
|
||
namespace Polly.Core.Tests.Utils; | ||
|
||
public class TimeProviderExtensionsTests | ||
{ | ||
[InlineData(false, false, false)] | ||
[InlineData(false, false, true)] | ||
[InlineData(false, true, true)] | ||
[InlineData(false, true, false)] | ||
[InlineData(true, false, false)] | ||
[InlineData(true, false, true)] | ||
[InlineData(true, true, true)] | ||
[InlineData(true, true, false)] | ||
[Theory] | ||
public async Task DelayAsync_System_Ok(bool synchronous, bool mocked, bool hasCancellation) | ||
{ | ||
using var tcs = new CancellationTokenSource(); | ||
var token = hasCancellation ? tcs.Token : default; | ||
var delay = TimeSpan.FromMilliseconds(10); | ||
var mock = new FakeTimeProvider(); | ||
var timeProvider = mocked ? mock.Object : TimeProvider.System; | ||
var context = ResilienceContext.Get(); | ||
context.Initialize<VoidResult>(isSynchronous: synchronous); | ||
context.CancellationToken = token; | ||
mock.SetupDelay(delay, token); | ||
|
||
await TestUtils.AssertWithTimeoutAsync(async () => | ||
{ | ||
var task = timeProvider.DelayAsync(delay, context); | ||
task.IsCompleted.Should().Be(synchronous || mocked); | ||
await task; | ||
}); | ||
|
||
if (mocked) | ||
{ | ||
mock.VerifyAll(); | ||
} | ||
} | ||
|
||
[InlineData(false, false)] | ||
[InlineData(false, true)] | ||
[InlineData(true, false)] | ||
[InlineData(true, true)] | ||
[Theory] | ||
public async Task DelayAsync_CancellationRequestedbefore_Throws(bool synchronous, bool mocked) | ||
{ | ||
using var tcs = new CancellationTokenSource(); | ||
tcs.Cancel(); | ||
var token = tcs.Token; | ||
var delay = TimeSpan.FromMilliseconds(10); | ||
var mock = new FakeTimeProvider(); | ||
var timeProvider = mocked ? mock.Object : TimeProvider.System; | ||
var context = ResilienceContext.Get(); | ||
context.Initialize<VoidResult>(isSynchronous: synchronous); | ||
context.CancellationToken = token; | ||
mock.SetupDelayCancelled(delay, token); | ||
|
||
await Assert.ThrowsAsync<OperationCanceledException>(() => timeProvider.DelayAsync(delay, context)); | ||
} | ||
|
||
[InlineData(false, false)] | ||
[InlineData(false, true)] | ||
[InlineData(true, false)] | ||
[InlineData(true, true)] | ||
[Theory] | ||
public async Task DelayAsync_CancellationAfter_Throws(bool synchronous, bool mocked) | ||
{ | ||
var delay = TimeSpan.FromMilliseconds(20); | ||
|
||
await TestUtils.AssertWithTimeoutAsync(async () => | ||
{ | ||
var mock = new FakeTimeProvider(); | ||
using var tcs = new CancellationTokenSource(); | ||
var token = tcs.Token; | ||
var timeProvider = mocked ? mock.Object : TimeProvider.System; | ||
var context = ResilienceContext.Get(); | ||
context.Initialize<VoidResult>(isSynchronous: synchronous); | ||
context.CancellationToken = token; | ||
mock.SetupDelayCancelled(delay, token); | ||
tcs.CancelAfter(TimeSpan.FromMilliseconds(5)); | ||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => timeProvider.DelayAsync(delay, context)); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
using System.Threading; | ||
|
||
namespace Polly.Utils; | ||
|
||
#pragma warning disable S3872 // Parameter names should not duplicate the names of their methods | ||
|
||
/// <summary> | ||
/// TEMPORARY ONLY, to be replaced with System.TimeProvider - https://github.com/dotnet/runtime/issues/36617 later. | ||
/// </summary> | ||
/// <remarks>We trimmed some of the API that's not relevant for us too.</remarks> | ||
internal abstract class TimeProvider | ||
{ | ||
private readonly double _tickFrequency; | ||
|
||
public static TimeProvider System { get; } = new SystemTimeProvider(); | ||
|
||
protected TimeProvider(long timestampFrequency) | ||
{ | ||
TimestampFrequency = timestampFrequency; | ||
_tickFrequency = (double)TimeSpan.TicksPerSecond / TimestampFrequency; | ||
} | ||
|
||
public abstract DateTimeOffset UtcNow { get; } | ||
|
||
public long TimestampFrequency { get; } | ||
|
||
public abstract long GetTimestamp(); | ||
|
||
public TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) => new((long)((endingTimestamp - startingTimestamp) * _tickFrequency)); | ||
|
||
public abstract Task Delay(TimeSpan delay, CancellationToken cancellationToken = default); | ||
|
||
public abstract void CancelAfter(CancellationTokenSource source, TimeSpan delay); | ||
|
||
private sealed class SystemTimeProvider : TimeProvider | ||
{ | ||
public SystemTimeProvider() | ||
: base(Stopwatch.Frequency) | ||
{ | ||
} | ||
|
||
public override long GetTimestamp() => Stopwatch.GetTimestamp(); | ||
|
||
public override Task Delay(TimeSpan delay, CancellationToken cancellationToken = default) => Task.Delay(delay, cancellationToken); | ||
|
||
public override void CancelAfter(CancellationTokenSource source, TimeSpan delay) => source.CancelAfter(delay); | ||
|
||
public override DateTimeOffset UtcNow => DateTimeOffset.UtcNow; | ||
} | ||
} |
Oops, something went wrong.