diff --git a/tests/DodoSSH.Client.App.Tests/ProbedDeviceKeyStoreTests.cs b/tests/DodoSSH.Client.App.Tests/ProbedDeviceKeyStoreTests.cs index 0ea8c0b..b299062 100644 --- a/tests/DodoSSH.Client.App.Tests/ProbedDeviceKeyStoreTests.cs +++ b/tests/DodoSSH.Client.App.Tests/ProbedDeviceKeyStoreTests.cs @@ -15,28 +15,39 @@ public sealed class ProbedDeviceKeyStoreTests { private static CancellationToken Token => TestContext.Current.CancellationToken; + /// + /// "Off the caller's thread" is proved by blocking, not by comparing thread ids. The probe waits for a + /// release the test only gives once construction has returned, so a probe run on the thread that + /// constructed the store could never be released and times out instead. Thread ids proved nothing: the + /// test itself runs on a pool thread, and that same thread is free to pick up the queued probe the + /// moment the test awaits, which is what failed on CI with the code doing exactly the right thing. + /// [Fact] public async Task TheChoiceIsMadeOnce_OffTheCallersThread() { - var asking = Environment.CurrentManagedThreadId; var probes = 0; - var probedOn = 0; + var releasedInTime = false; + using var release = new ManualResetEventSlim(); var inner = Substitute.For(); var store = new ProbedDeviceKeyStore(() => { Interlocked.Increment(ref probes); - probedOn = Environment.CurrentManagedThreadId; + releasedInTime = release.Wait(TimeSpan.FromSeconds(10)); return inner; }); - (await store.IsAvailableAsync(Token)).ShouldBeTrue(); + var available = store.IsAvailableAsync(Token); + available.IsCompleted.ShouldBeFalse("nothing has released the probe yet"); + release.Set(); + + (await available).ShouldBeTrue(); (await store.IsAvailableAsync(Token)).ShouldBeTrue(); await store.TryLoadAsync(Token); + releasedInTime.ShouldBeTrue("the probe ran on the thread that asked, so nothing could release it"); probes.ShouldBe(1); - probedOn.ShouldNotBe(asking); // Availability is the choice, not a second probe through the store it chose. await inner.DidNotReceive().IsAvailableAsync(Arg.Any());