using XFESystem.Kernel; using Xunit; namespace XFESystem.Core.Tests; public sealed class NetworkBootWaitTests { [Fact] public void AlreadyReadyDoesNotWaitForAnyInterrupt() { Assert.True(NativeNetworkBootWait.WaitUntilReady(() => true, () => 42, 1_000, () => throw new InvalidOperationException("No wait expected."))); } [Fact] public void NeverReadyTimesOutAfterFiveSecondsWithoutPitTicks() { long now = 0; int lapicInterrupts = 0, polls = 0; Assert.False(NativeNetworkBootWait.WaitUntilReady(() => { polls++; return false; }, () => now, 1_000, () => { lapicInterrupts++; now += 10; })); Assert.Equal(5_000, now); Assert.Equal(500, lapicInterrupts); Assert.Equal(500, polls); } [Theory] [InlineData(4_990, true)] [InlineData(5_000, false)] [InlineData(5_010, false)] public void ReadinessMustArriveBeforeTheDeadline(int readyAt, bool expected) { long now = 0; Assert.Equal(expected, NativeNetworkBootWait.WaitUntilReady(() => now >= readyAt, () => now, 1_000, () => now += 10)); Assert.Equal(expected ? readyAt : 5_000, now); } [Fact] public void LongInterruptWaitCountsTowardTheSameBudget() { long now = 0; int polls = 0, waits = 0; Assert.False(NativeNetworkBootWait.WaitUntilReady(() => { polls++; return false; }, () => now, 1_000, () => { waits++; now += 9_000; })); Assert.Equal(1, waits); Assert.Equal(1, polls); } [Theory] [InlineData(true)] [InlineData(false)] public void PollingTimeCannotExtendTheDeadline(bool ready) { long now = 0; Assert.False(NativeNetworkBootWait.WaitUntilReady(() => { now += 5_000; return ready; }, () => now, 1_000, () => throw new InvalidOperationException("An expired poll must not wait."))); } [Theory] [InlineData(1)] [InlineData(1_000)] [InlineData(1_000_000_001)] public void UsesTheSuppliedTimestampFrequency(long frequency) { long now = 0; int waits = 0; Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, frequency, () => { waits++; now += frequency; })); Assert.Equal(5, waits); Assert.Equal(frequency * 5, now); } [Theory] [InlineData(long.MaxValue - 2_000)] [InlineData(-2_000)] public void TimestampWrapDoesNotOverflowOrSkipTheTimeout(long start) { long now = start; int waits = 0; Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, 1_000, () => { waits++; now = unchecked(now + 1_000); })); Assert.Equal(5, waits); Assert.Equal(unchecked(start + 5_000), now); } [Fact] public void LargestSupportedBudgetDoesNotOverflow() { const long frequency = long.MaxValue / 5; long now = 100; int waits = 0; Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, frequency, () => { waits++; now = unchecked(now + frequency * 5); })); Assert.Equal(1, waits); } [Fact] public void BackwardsClockFailsClosed() { long now = 100; int waits = 0; Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, 1_000, () => { waits++; now--; })); Assert.Equal(1, waits); } [Theory] [InlineData(0)] [InlineData(-1)] [InlineData(long.MaxValue / 5 + 1)] [InlineData(long.MaxValue)] public void InvalidOrUnrepresentableFrequencyIsRejectedBeforeWaiting(long frequency) { Assert.Throws(() => NativeNetworkBootWait.WaitUntilReady( () => throw new InvalidOperationException("No poll expected."), () => throw new InvalidOperationException("No timestamp expected."), frequency, () => throw new InvalidOperationException("No wait expected."))); } }