From ceef33072c03771f1a87e56904ee8756b827cb2a Mon Sep 17 00:00:00 2001 From: vibesoftwarecoder Date: Fri, 9 Oct 2026 11:23:18 -0500 Subject: [PATCH] test(updates): make the atomic-save test prove its reader overlapped the writer The test asserted the reader had read at least once after a fixed 40 saves. On a busy CI runner the reader thread had not run by the time the writer finished (PR #106, line 178), so the assertion failed, and a passing run could have proved nothing about atomicity. The writer now starts only after the reader has read the file, and keeps saving until the reader has seen it 200 times (60 s limit). A non-atomic save still turns it red. Co-Authored-By: Claude Sonnet 5.5 --- .../Updates/UpdateStateStoreTests.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/MultiSeat.Tests/Updates/UpdateStateStoreTests.cs b/src/MultiSeat.Tests/Updates/UpdateStateStoreTests.cs index 70c752a..f6c19d1 100644 --- a/src/MultiSeat.Tests/Updates/UpdateStateStoreTests.cs +++ b/src/MultiSeat.Tests/Updates/UpdateStateStoreTests.cs @@ -151,7 +151,16 @@ public async Task Save_IsAtomic_AReaderNeverSeesAHalfWrittenFile() Assert.True(store.Save(big)); var fullLength = new FileInfo(FilePath).Length; + // The reader and the writer race, so the test must PROVE they overlapped instead of hoping + // the scheduler lets them. A first version wrote a fixed 40 times and then asserted the + // reader had read at least once; on a busy CI runner the reader thread had not run by the + // time the writer finished, so the assertion failed (and a passing run could have proved + // nothing). Now: the writer starts only once the reader has read the file, and keeps + // saving until the reader has seen it many times, with a time limit as the safety net. + const int MinReads = 200; + const int MinSaves = 40; using var stop = new CancellationTokenSource(); + using var readerRunning = new ManualResetEventSlim(); var torn = 0; var reads = 0; var reader = Task.Run(() => @@ -163,19 +172,28 @@ public async Task Save_IsAtomic_AReaderNeverSeesAHalfWrittenFile() using var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using var ms = new MemoryStream(); fs.CopyTo(ms); - reads++; - if (ms.Length != fullLength) torn++; + if (ms.Length != fullLength) Interlocked.Increment(ref torn); + if (Interlocked.Increment(ref reads) == 1) readerRunning.Set(); } catch (IOException) { /* the file was being replaced at that instant: not a torn read */ } catch (UnauthorizedAccessException) { } } }); - for (var i = 0; i < 40; i++) store.Save(big); + Assert.True(readerRunning.Wait(TimeSpan.FromSeconds(30)), "the reader never managed a single read before the writer started"); + + var saves = 0; + var clock = System.Diagnostics.Stopwatch.StartNew(); + while ((saves < MinSaves || Volatile.Read(ref reads) < MinReads) && clock.Elapsed < TimeSpan.FromSeconds(60)) + { + store.Save(big); + saves++; + } stop.Cancel(); await reader; - Assert.True(reads > 0); + Assert.True(Volatile.Read(ref reads) >= MinReads, + $"the reader saw the file only {reads} times in {saves} saves; the race was not exercised, so this run proves nothing"); Assert.Equal(0, torn); }