CachelessSingleFileTests: null arguments, the lock wait, and the registry deciding the header for copied records
Stryker on WriteRecord left three NoCoverage statements (the two null guards, AttachRegistry) and a survivor (AcquireLock wait: true to false). Each now has a test that fails when it is mutated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bd40c05dff
commit
6463f34203
1 changed files with 56 additions and 0 deletions
|
|
@ -1,8 +1,10 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using GameCult.Caching.MessagePack;
|
||||
using NUnit.Framework;
|
||||
using static GameCult.Caching.Tests.ElementIdTests;
|
||||
|
||||
namespace GameCult.Caching.Tests
|
||||
{
|
||||
|
|
@ -92,5 +94,59 @@ namespace GameCult.Caching.Tests
|
|||
|
||||
Assert.That(new SingleFileMessagePackBackingStore(_path).ReadStoreSnapshot()!.FormatVersion, Is.EqualTo(CultPersistedStoreSnapshot.FormatV3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRecordRefusesNullArguments()
|
||||
{
|
||||
var store = new SingleFileMessagePackBackingStore(_path);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => store.WriteRecord(null!, One));
|
||||
Assert.Throws<ArgumentNullException>(() => store.WriteRecord(Record("a", One, 1), null!));
|
||||
Assert.That(File.Exists(_path), Is.False);
|
||||
}
|
||||
|
||||
// The write is under the store's lock and waits for it: a write that finds the lock held lands after it is released, and
|
||||
// never without it.
|
||||
[Test]
|
||||
public async Task WriteRecordWaitsForTheStoreLock()
|
||||
{
|
||||
var write = default(Task);
|
||||
using (new FileStream(_path + ".lock", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
|
||||
{
|
||||
write = Task.Run(() => new SingleFileMessagePackBackingStore(_path).WriteRecord(Record("a", One, 1), One));
|
||||
await Task.Delay(500);
|
||||
Assert.That(write.IsCompleted, Is.False, "the write waits while another holder has the lock");
|
||||
Assert.That(File.Exists(_path), Is.False);
|
||||
}
|
||||
|
||||
await write;
|
||||
|
||||
Assert.That(new SingleFileMessagePackBackingStore(_path).ReadStoreSnapshot()!.Records, Has.Length.EqualTo(1));
|
||||
}
|
||||
|
||||
// The registry the payload was encoded with lets the write decide the header for the records it copies: another runtime may
|
||||
// have written ids under an unmarked header, and a write that can read them marks the store. Without a registry it decides
|
||||
// nothing for them.
|
||||
[TestCase(true, CultPersistedStoreSnapshot.FormatV3)]
|
||||
[TestCase(false, CultPersistedStoreSnapshot.FormatV1)]
|
||||
public async Task WriteRecordGivenARegistryMarksAStoreWhoseCopiedRecordHoldsIds(bool withRegistry, string expected)
|
||||
{
|
||||
var registry = CultDocumentRegistry.ForTypes(new[] { typeof(IdDeck) });
|
||||
using (var cache = new CultCache(registry))
|
||||
{
|
||||
cache.AddBackingStore(new SingleFileMessagePackBackingStore(_path));
|
||||
await cache.AddAsync(new IdDeck { Name = "deck", Reels = { new IdReel { Label = "reel" } } });
|
||||
cache.FlushAllBackingStores();
|
||||
}
|
||||
|
||||
var written = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(_path));
|
||||
Assert.That(written.FormatVersion, Is.EqualTo(CultPersistedStoreSnapshot.FormatV3), "the cache marked the store it wrote ids into");
|
||||
written.FormatVersion = CultPersistedStoreSnapshot.FormatV1;
|
||||
File.WriteAllBytes(_path, CultDocumentMessagePackSerialization.SerializeSnapshot(written));
|
||||
|
||||
new SingleFileMessagePackBackingStore(_path).WriteRecord(Record("other", One, 1), One, holdsIds: false, registry: withRegistry ? registry : null);
|
||||
|
||||
Assert.That(new SingleFileMessagePackBackingStore(_path).ReadStoreSnapshot()!.FormatVersion, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue