Skip to content

Commit

Permalink
Remove MockTimeProvider
Browse files Browse the repository at this point in the history
- Remove `MockTimeProvider`.
- Simplify `TimeProviderExtensions` to remove code that duplicates what the extensions in `Microsoft.Bcl.TimeProvider` do.
  • Loading branch information
martincostello committed Jun 26, 2023
1 parent f7de3f0 commit 7be0f93
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 134 deletions.
32 changes: 7 additions & 25 deletions src/Polly.Core/Utils/TimeProviderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,23 @@ public static Task DelayAsync(this TimeProvider timeProvider, TimeSpan delay, Re

context.CancellationToken.ThrowIfCancellationRequested();

if (context.IsSynchronous && timeProvider == TimeProvider.System)
if (context.IsSynchronous)
{
// Stryker disable once boolean : no means to test this
if (context.CancellationToken.CanBeCanceled)
{
context.CancellationToken.WaitHandle.WaitOne(delay);
context.CancellationToken.ThrowIfCancellationRequested();
}
else
{
Thread.Sleep(delay);
}

return Task.CompletedTask;
}
else
{
if (context.IsSynchronous)
{
#pragma warning disable CA1849 // For synchronous scenarios we want to return completed task
#if NET8_0_OR_GREATER
Task.Delay(delay, timeProvider, context.CancellationToken).GetAwaiter().GetResult();
Task.Delay(delay, timeProvider, context.CancellationToken).GetAwaiter().GetResult();
#else
timeProvider.Delay(delay, context.CancellationToken).GetAwaiter().GetResult();
timeProvider.Delay(delay, context.CancellationToken).GetAwaiter().GetResult();
#endif
#pragma warning restore CA1849

return Task.CompletedTask;
}
return Task.CompletedTask;
}

#if NET8_0_OR_GREATER
return Task.Delay(delay, timeProvider, context.CancellationToken);
return Task.Delay(delay, timeProvider, context.CancellationToken);
#else
return timeProvider.Delay(delay, context.CancellationToken);
return timeProvider.Delay(delay, context.CancellationToken);
#endif
}
}
}
52 changes: 0 additions & 52 deletions test/Polly.Core.Tests/Helpers/MockTimeProvider.cs

This file was deleted.

4 changes: 2 additions & 2 deletions test/Polly.Core.Tests/Polly.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Polly.Contrib.WaitAndRetry" />
<PackageReference Include="Microsoft.Bcl.TimeProvider"/>
<PackageReference Include="Polly.Contrib.WaitAndRetry" />
</ItemGroup>

<ItemGroup>
Expand All @@ -20,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<Using Include="Polly.TestUtils" />
<Using Include="Polly.Core.Tests.Helpers" />
<Using Include="Polly.TestUtils" />
</ItemGroup>
</Project>
75 changes: 56 additions & 19 deletions test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using FluentAssertions.Execution;
using Microsoft.Extensions.Time.Testing;
using Moq;
using Polly.Retry;
Expand Down Expand Up @@ -158,40 +159,76 @@ public void Retry_Infinite_Respected()
[Fact]
public async Task RetryDelayGenerator_Respected()
{
int calls = 0;
_options.OnRetry = _ => { calls++; return default; };
int retries = 0;
int generatedValues = 0;

var delay = TimeSpan.FromMilliseconds(120);
var provider = TimeProvider.System;

_options.ShouldHandle = _ => PredicateResult.True;
_options.RetryCount = 3;
_options.BackoffType = RetryBackoffType.Constant;
_options.RetryDelayGenerator = _ => new ValueTask<TimeSpan>(TimeSpan.FromMilliseconds(123));
var provider = new MockTimeProvider();
provider.SetupCreateTimer(TimeSpan.FromMilliseconds(123));
provider.Setup(p => p.GetTimestamp()).Returns(0);
provider.Setup(p => p.TimestampFrequency).Returns(10000);

var sut = CreateSut(provider.Object);
_options.OnRetry = _ =>
{
retries++;
return default;
};
_options.RetryDelayGenerator = _ =>
{
generatedValues++;
return new ValueTask<TimeSpan>(delay);
};

var before = provider.GetUtcNow();

var sut = CreateSut(provider);
await sut.ExecuteAsync(_ => default);

provider.VerifyAll();
retries.Should().Be(3);
generatedValues.Should().Be(3);

var after = provider.GetUtcNow();
(after - before).Should().BeGreaterThanOrEqualTo(delay.Add(delay).Add(delay));
}

[Fact]
public async Task RetryDelayGenerator_ZeroDelay_NoTimeProviderCalls()
{
int calls = 0;
_options.OnRetry = _ => { calls++; return default; };
int retries = 0;
int generatedValues = 0;

var delay = TimeSpan.Zero;
var provider = new ThrowingFakeTimeProvider();

_options.ShouldHandle = _ => PredicateResult.True;
_options.RetryCount = 1;
_options.RetryDelayGenerator = _ => new ValueTask<TimeSpan>(TimeSpan.FromMilliseconds(0));
var provider = new MockTimeProvider();
provider.Setup(p => p.GetTimestamp()).Returns(0);
provider.Setup(p => p.TimestampFrequency).Returns(10000);
_options.RetryCount = 3;
_options.BackoffType = RetryBackoffType.Constant;

var sut = CreateSut(provider.Object);
_options.OnRetry = _ =>
{
retries++;
return default;
};
_options.RetryDelayGenerator = _ =>
{
generatedValues++;
return new ValueTask<TimeSpan>(delay);
};

var sut = CreateSut(provider);
await sut.ExecuteAsync(_ => default);

provider.VerifyAll();
provider.VerifyNoOtherCalls();
retries.Should().Be(3);
generatedValues.Should().Be(3);
}

private sealed class ThrowingFakeTimeProvider : FakeTimeProvider
{
public override DateTimeOffset GetUtcNow() => throw new AssertionFailedException("TimeProvider should not be used.");

public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
=> throw new AssertionFailedException("TimeProvider should not be used.");
}

[Fact]
Expand Down
49 changes: 15 additions & 34 deletions test/Polly.Core.Tests/Utils/TimeProviderExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,27 @@ 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)]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
[Theory]
public async Task DelayAsync_System_Ok(bool synchronous, bool mocked, bool hasCancellation)
public async Task DelayAsync_System_Ok(bool synchronous, bool hasCancellation)
{
using var tcs = new CancellationTokenSource();
var token = hasCancellation ? tcs.Token : default;
var delay = TimeSpan.FromMilliseconds(10);
var mock = new MockTimeProvider();
var timeProvider = mocked ? mock.Object : TimeProvider.System;
var timeProvider = TimeProvider.System;
var context = ResilienceContext.Get();
context.Initialize<VoidResult>(isSynchronous: synchronous);
context.CancellationToken = token;
mock.SetupCreateTimer(delay);

await TestUtilities.AssertWithTimeoutAsync(async () =>
{
var task = timeProvider.DelayAsync(delay, context);
task.IsCompleted.Should().Be(synchronous || mocked);
task.IsCompleted.Should().Be(synchronous);
await task;
});

if (mocked)
{
mock.VerifyAll();
}
}

[Fact]
Expand Down Expand Up @@ -72,46 +61,38 @@ await TimeProvider.System
});
}

[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
[InlineData(false)]
[InlineData(true)]
[Theory]
public async Task DelayAsync_CancellationRequestedBefore_Throws(bool synchronous, bool mocked)
public async Task DelayAsync_CancellationRequestedBefore_Throws(bool synchronous)
{
using var tcs = new CancellationTokenSource();
tcs.Cancel();
var token = tcs.Token;
var delay = TimeSpan.FromMilliseconds(10);
var mock = new MockTimeProvider();
var timeProvider = mocked ? mock.Object : TimeProvider.System;
var timeProvider = TimeProvider.System;
var context = ResilienceContext.Get();
context.Initialize<VoidResult>(isSynchronous: synchronous);
context.CancellationToken = token;
mock.SetupCreateTimer(delay);

await Assert.ThrowsAsync<OperationCanceledException>(() => timeProvider.DelayAsync(delay, context));
}

[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
[InlineData(false)]
[InlineData(true)]
[Theory]
public async Task DelayAsync_CancellationAfter_Throws(bool synchronous, bool mocked)
public async Task DelayAsync_CancellationAfter_Throws(bool synchronous)
{
var delay = TimeSpan.FromMilliseconds(20);

await TestUtilities.AssertWithTimeoutAsync(async () =>
{
var mock = new MockTimeProvider();
using var tcs = new CancellationTokenSource();
var token = tcs.Token;
var timeProvider = mocked ? mock.Object : TimeProvider.System;
var timeProvider = TimeProvider.System;
var context = ResilienceContext.Get();
context.Initialize<VoidResult>(isSynchronous: synchronous);
context.CancellationToken = token;
mock.SetupCreateTimerException(delay, new OperationCanceledException());
tcs.CancelAfter(TimeSpan.FromMilliseconds(5));
Expand Down
1 change: 0 additions & 1 deletion test/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Polly.Core.Tests\Helpers\MockTimeProvider.cs" Link="Utils\MockTimeProvider.cs" />
<Compile Include="..\Polly.Core.Tests\Utils\ObjectPoolTests.cs" Link="Utils\ObjectPoolTests.cs" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion test/Polly.TestUtils/Polly.TestUtils.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<ItemGroup>
<Using Remove="System.Net.Http" />
<ProjectReference Include="..\..\src\Polly.Core\Polly.Core.csproj" />
<PackageReference Include="Moq" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Moq" />
</ItemGroup>
</Project>

0 comments on commit 7be0f93

Please sign in to comment.