Caching tests: pin the variant refusal message and ReadDurable leaving the store view alone; start TwoWriters rounds together
Some checks failed
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 20m27s

The directory race test no longer collided once each attempt reads fresh, so each round
now starts both writers at a barrier.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Libby 2026-11-13 16:06:57 +00:00
commit d3edf80ed3

View file

@ -78,16 +78,22 @@ namespace GameCult.Caching.Tests
using var a = Open(path, directory);
using var b = Open(path, directory);
var applied = 0;
// Each round starts both writers together: an attempt reads fresh from the store, so only overlapping read-commit windows lose.
var round = new Barrier(2);
var racers = new[] { a, b }.Select(cache => Task.Run(() =>
{
var results = new List<CultUpdateOutcome>();
for (var i = 0; i < n; i++)
{
round.SignalAndWait();
results.Add(cache.Update<UpdateCounter>(Key, counter =>
{
Interlocked.Increment(ref applied);
return Plus(counter, 1);
}, maxAttempts: 200).Outcome);
}
return results;
})).ToArray();
Assert.That(Task.WaitAll(racers, TimeSpan.FromMinutes(2)), Is.True, "the racers did not finish");
@ -252,7 +258,7 @@ namespace GameCult.Caching.Tests
before = File.ReadAllBytes(path);
var gearCalls = 0;
using (var cache = Open(path, false))
Assert.That(() => cache.Update<UpdateGear>(new CultRecordKey("gear-big"), gear => { gearCalls++; return gear; }), Throws.InvalidOperationException);
Assert.That(() => cache.Update<UpdateGear>(new CultRecordKey("gear-big"), gear => { gearCalls++; return gear; }), Throws.InvalidOperationException.With.Message.Contains("Update cannot copy it"));
Assert.That(gearCalls, Is.EqualTo(0), "apply ran for a variant");
Assert.That(calls, Is.EqualTo(0), "apply ran for a refused call");
Assert.That(File.ReadAllBytes(path), Is.EqualTo(before));
@ -509,6 +515,25 @@ namespace GameCult.Caching.Tests
Assert.That(StoredValue(path, directory), Is.EqualTo(8));
}
// The durable read is not a load: it leaves the store's view of what the cache holds alone, so a later pull still delivers the rival's write.
[TestCase(false)]
[TestCase(true)]
public void ADeclinedUpdateDoesNotHideTheRivalsWriteFromALaterPull(bool directory)
{
var path = PathOf("hidden.cc");
Seed(path, directory, 1);
using var a = Open(path, directory);
using var b = Open(path, directory);
Assert.That(b.Get<UpdateCounter>(Key)!.Value, Is.EqualTo(1));
Rival(a, 7);
Assert.That(b.Update<UpdateCounter>(Key, _ => null).Outcome, Is.EqualTo(CultUpdateOutcome.Declined));
Assert.That(b.Get<UpdateCounter>(Key)!.Value, Is.EqualTo(1), "a declined Update changed what the cache holds");
b.PullAllBackingStoresAsync().GetAwaiter().GetResult();
Assert.That(b.Get<UpdateCounter>(Key)!.Value, Is.EqualTo(7));
}
[Test]
public void CodecLessStoreBackedCacheUpdates()
{