fix: cap automatic playout refreshes
This commit is contained in:
@@ -181,11 +181,145 @@ public sealed class LegacyRefreshSchedulerTests
|
||||
Assert.Equal(0, delays.CallCount);
|
||||
}
|
||||
|
||||
private static LegacyRefreshScheduler CreateScheduler(ControlledDelay delays) =>
|
||||
[Fact]
|
||||
public async Task MaximumOne_StopsBeforeASecondDelayOrRefreshCanBeScheduled()
|
||||
{
|
||||
var delays = new ControlledDelay();
|
||||
var scheduler = CreateScheduler(delays, maximumRefreshCount: 1);
|
||||
var untrackedCalls = 0;
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
scheduler.ExecuteRefreshAsync(
|
||||
_ =>
|
||||
{
|
||||
untrackedCalls++;
|
||||
return Task.FromResult(true);
|
||||
},
|
||||
CancellationToken.None));
|
||||
Assert.Equal(0, untrackedCalls);
|
||||
Assert.Throws<InvalidOperationException>(scheduler.MarkRefreshSucceeded);
|
||||
|
||||
var firstOpportunity = scheduler.WaitForNextRefreshAsync(
|
||||
_ => Task.FromResult(true),
|
||||
null,
|
||||
CancellationToken.None);
|
||||
Assert.Equal(TimeSpan.FromSeconds(2), await delays.WaitForCallAsync(0));
|
||||
delays.Complete(0);
|
||||
Assert.True(await firstOpportunity);
|
||||
|
||||
Assert.True(await scheduler.ExecuteRefreshAndTrackSuccessAsync(
|
||||
_ => Task.FromResult(true),
|
||||
static result => result,
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Equal(1, scheduler.CompletedRefreshCount);
|
||||
Assert.Equal(1, scheduler.MaximumRefreshCount);
|
||||
Assert.True(scheduler.HasReachedMaximum);
|
||||
Assert.False(scheduler.IsMaximumCallbackDrained);
|
||||
var finalCallback = NewCompletion<bool>();
|
||||
var drain = scheduler.DrainMaximumCallbackAsync(
|
||||
cancellationToken => finalCallback.Task.WaitAsync(cancellationToken),
|
||||
CancellationToken.None);
|
||||
await Task.Yield();
|
||||
Assert.False(drain.IsCompleted);
|
||||
Assert.False(scheduler.IsMaximumCallbackDrained);
|
||||
finalCallback.SetResult(true);
|
||||
Assert.True(await drain);
|
||||
Assert.True(scheduler.IsMaximumCallbackDrained);
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
scheduler.WaitForNextRefreshAsync(
|
||||
_ => Task.FromResult(true),
|
||||
null,
|
||||
CancellationToken.None));
|
||||
var unexpectedRefreshCalls = 0;
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
scheduler.ExecuteRefreshAsync(
|
||||
_ =>
|
||||
{
|
||||
unexpectedRefreshCalls++;
|
||||
return Task.FromResult(true);
|
||||
},
|
||||
CancellationToken.None));
|
||||
Assert.Equal(0, unexpectedRefreshCalls);
|
||||
Assert.Equal(1, delays.CallCount);
|
||||
Assert.Throws<InvalidOperationException>(scheduler.MarkRefreshSucceeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullMaximum_PreservesUnlimitedLegacyScheduling()
|
||||
{
|
||||
var scheduler = CreateScheduler(new ControlledDelay());
|
||||
|
||||
for (var index = 0; index < 10_000; index++)
|
||||
{
|
||||
scheduler.MarkRefreshSucceeded();
|
||||
}
|
||||
|
||||
Assert.Null(scheduler.MaximumRefreshCount);
|
||||
Assert.Equal(10_000, scheduler.CompletedRefreshCount);
|
||||
Assert.False(scheduler.HasReachedMaximum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroMaximum_DisablesAutomaticRefreshBeforeTheFirstDelay()
|
||||
{
|
||||
var scheduler = CreateScheduler(new ControlledDelay(), maximumRefreshCount: 0);
|
||||
|
||||
Assert.Equal(0, scheduler.CompletedRefreshCount);
|
||||
Assert.Equal(0, scheduler.MaximumRefreshCount);
|
||||
Assert.True(scheduler.HasReachedMaximum);
|
||||
Assert.True(scheduler.IsMaximumCallbackDrained);
|
||||
Assert.Throws<InvalidOperationException>(scheduler.MarkRefreshSucceeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaximumCallbackTimeout_DoesNotConfirmTheLimit()
|
||||
{
|
||||
var scheduler = CreateScheduler(new ControlledDelay(), maximumRefreshCount: 1);
|
||||
Assert.True(await scheduler.ExecuteRefreshAndTrackSuccessAsync(
|
||||
_ => Task.FromResult(true),
|
||||
static result => result,
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.False(await scheduler.DrainMaximumCallbackAsync(
|
||||
_ => Task.FromResult(false),
|
||||
CancellationToken.None));
|
||||
Assert.True(scheduler.HasReachedMaximum);
|
||||
Assert.False(scheduler.IsMaximumCallbackDrained);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FailedTrackedRefresh_DoesNotConsumeTheMaximum()
|
||||
{
|
||||
var scheduler = CreateScheduler(new ControlledDelay(), maximumRefreshCount: 1);
|
||||
|
||||
Assert.False(await scheduler.ExecuteRefreshAndTrackSuccessAsync(
|
||||
_ => Task.FromResult(false),
|
||||
static result => result,
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Equal(0, scheduler.CompletedRefreshCount);
|
||||
Assert.False(scheduler.HasReachedMaximum);
|
||||
Assert.False(scheduler.IsMaximumCallbackDrained);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NegativeMaximum_IsRejected()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
new LegacyRefreshScheduler(
|
||||
TimeSpan.FromSeconds(2),
|
||||
TimeSpan.FromSeconds(3),
|
||||
maximumRefreshCount: -1));
|
||||
}
|
||||
|
||||
private static LegacyRefreshScheduler CreateScheduler(
|
||||
ControlledDelay delays,
|
||||
int? maximumRefreshCount = null) =>
|
||||
new(
|
||||
TimeSpan.FromSeconds(2),
|
||||
TimeSpan.FromSeconds(3),
|
||||
delays.DelayAsync);
|
||||
delays.DelayAsync,
|
||||
maximumRefreshCount);
|
||||
|
||||
private static async Task<bool> RunOneRefreshAsync(
|
||||
LegacyRefreshScheduler scheduler,
|
||||
|
||||
Reference in New Issue
Block a user