Skip to content
Merged
Changes from all 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
38 changes: 31 additions & 7 deletions tests/CodeShellManager.Tests/ThrottledSignalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,36 @@ public class ThrottledSignalTests
{
private static readonly TimeSpan Delay = TimeSpan.FromMilliseconds(50);

// Wait for an EVENT, never for a fixed amount of wall clock. These used to sleep 500ms and
// then assert, which failed on a loaded CI runner (main, after #160 merged: "Expected: 1,
// Actual: 0") — xunit runs test classes in parallel, and a 50ms Task.Delay continuation on
// a starved thread pool can land well past 500ms. The deadline below is only a ceiling for
// a genuinely broken implementation; a healthy one returns in ~50ms.
private static readonly TimeSpan Deadline = TimeSpan.FromSeconds(10);

// How long to keep watching after the expected count is reached, to catch an EXTRA run.
// Several times Delay, so a wrongly scheduled second run would have fired by then.
private static readonly TimeSpan Settle = TimeSpan.FromMilliseconds(300);

private static async Task WaitForRuns(Func<int> runs, int atLeast)
{
var until = DateTime.UtcNow + Deadline;
while (runs() < atLeast && DateTime.UtcNow < until)
await Task.Delay(10);
Assert.True(runs() >= atLeast, $"expected at least {atLeast} run(s) within {Deadline.TotalSeconds}s, saw {runs()}");
}

[Fact]
public async Task A_burst_of_signals_runs_the_action_once()
{
int runs = 0;
var signal = new ThrottledSignal(Delay, a => a(), () => Interlocked.Increment(ref runs));

for (int i = 0; i < 1000; i++) signal.Signal();
await Task.Delay(500);
await WaitForRuns(() => Volatile.Read(ref runs), 1);
await Task.Delay(Settle);

Assert.Equal(1, runs);
Assert.Equal(1, Volatile.Read(ref runs));
}

[Fact]
Expand All @@ -34,11 +54,14 @@ public async Task A_signal_after_the_action_ran_schedules_another()
var signal = new ThrottledSignal(Delay, a => a(), () => Interlocked.Increment(ref runs));

signal.Signal();
await Task.Delay(500);
// Must wait for the first run itself: signalling again while it is still pending
// piggybacks on it, which is the burst case above, not this one.
await WaitForRuns(() => Volatile.Read(ref runs), 1);
signal.Signal();
await Task.Delay(500);
await WaitForRuns(() => Volatile.Read(ref runs), 2);
await Task.Delay(Settle);

Assert.Equal(2, runs);
Assert.Equal(2, Volatile.Read(ref runs));
}

[Fact]
Expand All @@ -54,9 +77,10 @@ public async Task A_signal_during_the_action_is_not_lost()
});

signal.Signal();
await Task.Delay(500);
await WaitForRuns(() => Volatile.Read(ref runs), 2);
await Task.Delay(Settle);

Assert.Equal(2, runs);
Assert.Equal(2, Volatile.Read(ref runs));
}

[Fact]
Expand Down