Pin the variant rules a mutant survived or that lost their only test
Reader refusals (v1 header over a variant, payload on a variant, unknown op), a load that finds a variant flattened by another writer, a re-resolution reaching a read-only cache, a base removed in the same batch as its new variant, and plain-record-to-variant refused without the name rule masking it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
33664434f9
commit
b93934f6b4
1 changed files with 97 additions and 2 deletions
|
|
@ -304,6 +304,14 @@ namespace GameCult.Caching.Tests
|
|||
batch.Remove(spare);
|
||||
batch.Remove(BigKey);
|
||||
}), Is.True, "removed in the same batch");
|
||||
|
||||
SeedBase(cache);
|
||||
var removedInTheBatch = Refused(() => cache.Commit(batch =>
|
||||
{
|
||||
batch.Remove(BaseKey);
|
||||
batch.UpsertVariant(BigKey, BaseKey);
|
||||
}));
|
||||
Assert.That(removedInTheBatch.Message, Does.Contain(BigKey.Value).And.Contain(BaseKey.Value));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
@ -371,7 +379,9 @@ namespace GameCult.Caching.Tests
|
|||
Assert.That(Refused(() => cache.FlattenAsync(BaseKey).GetAwaiter().GetResult()).Message, Does.Contain(BaseKey.Value));
|
||||
var spare = new CultRecordKey("spare");
|
||||
cache.Commit(batch => batch.Upsert(typeof(VariantGear), new VariantGear { Name = "spare" }, spare));
|
||||
Assert.That(Refused(() => cache.UpsertVariantAsync(spare, BaseKey).GetAwaiter().GetResult()).Message, Does.Contain(spare.Value));
|
||||
var named = new[] { cache.Override<VariantGear>(nameof(VariantGear.Name), "spare variant") };
|
||||
Assert.That(Refused(() => cache.UpsertVariantAsync(spare, BaseKey, named).GetAwaiter().GetResult()).Message, Does.Contain(spare.Value));
|
||||
Assert.That(cache.GetStored(spare)!.Variant, Is.Null, "the plain record is still plain");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
@ -529,10 +539,16 @@ namespace GameCult.Caching.Tests
|
|||
SeedBig(cache);
|
||||
}
|
||||
|
||||
var before = File.ReadAllBytes(path);
|
||||
using var readOnly = Open(path, readOnly: true);
|
||||
Assert.That(readOnly.Get<VariantGear>(BigKey)!.Power, Is.EqualTo(25));
|
||||
Assert.That(readOnly.Get<VariantGear>(BigKey)!.Code, Is.EqualTo("l1"));
|
||||
|
||||
// A base edit another writer commits reaches the read-only cache's variant when it pulls: a re-resolution is no write.
|
||||
using var writer = Open(path);
|
||||
EditBase(writer, gear => gear.Tags.Add("pierce"));
|
||||
var before = File.ReadAllBytes(path);
|
||||
readOnly.PullAllBackingStoresAsync();
|
||||
Assert.That(readOnly.Get<VariantGear>(BigKey)!.Tags, Is.EqualTo(new[] { "beam", "pierce" }));
|
||||
Assert.That(File.ReadAllBytes(path), Is.EqualTo(before));
|
||||
}
|
||||
|
||||
|
|
@ -553,6 +569,24 @@ namespace GameCult.Caching.Tests
|
|||
"the pull carried only the base; the variant was re-resolved in the admitting hold");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AFlattenFromAnotherWriterReachesACacheHoldingTheVariant()
|
||||
{
|
||||
var path = PathOf("gear.cc");
|
||||
using var a = Open(path);
|
||||
SeedBase(a);
|
||||
SeedBig(a);
|
||||
using var b = Open(path);
|
||||
Assert.That(b.GetStored(BigKey)!.Variant, Is.Not.Null);
|
||||
|
||||
a.FlattenAsync(BigKey).GetAwaiter().GetResult();
|
||||
a.FlushAsync().GetAwaiter().GetResult();
|
||||
b.PullAllBackingStoresAsync();
|
||||
|
||||
Assert.That(b.GetStored(BigKey)!.Variant, Is.Null, "a load reports what is on disk; it is not a plain write refused at a variant key");
|
||||
Assert.That(b.Get<VariantGear>(BigKey)!.Power, Is.EqualTo(25));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ARebaseResolvesFromTheNewBase()
|
||||
{
|
||||
|
|
@ -569,6 +603,67 @@ namespace GameCult.Caching.Tests
|
|||
Assert.That(cache.GetStored(BigKey)!.Variant!.BaseKey, Is.EqualTo(spare.Value));
|
||||
}
|
||||
|
||||
// ---- readers ----
|
||||
|
||||
private string WriteStore(params CultPersistedRecord[] records) => WriteStore(CultPersistedStoreSnapshot.FormatV2, records);
|
||||
|
||||
private string WriteStore(string formatVersion, params CultPersistedRecord[] records)
|
||||
{
|
||||
var path = PathOf($"hand-{Guid.NewGuid():N}.cc");
|
||||
var gear = Registry.GetRequired<VariantGear>();
|
||||
File.WriteAllBytes(path, CultDocumentMessagePackSerialization.SerializeSnapshot(new CultPersistedStoreSnapshot
|
||||
{
|
||||
FormatVersion = formatVersion,
|
||||
SchemaCatalog = new[] { gear.ToCatalogEntry() },
|
||||
Records = records
|
||||
}));
|
||||
return path;
|
||||
}
|
||||
|
||||
private static CultPersistedRecord PlainGear(string key) => new()
|
||||
{
|
||||
Key = key,
|
||||
SchemaId = Registry.GetRequired<VariantGear>().SchemaId,
|
||||
StoredAt = "2026-01-01T00:00:00.0000000Z",
|
||||
Payload = CultDocumentMessagePackSerialization.SerializeUntyped(Laser(), typeof(VariantGear), Registry)
|
||||
};
|
||||
|
||||
private static CultPersistedRecord VariantGearRecord(string key, string baseKey, byte[]? payload = null, CultOverrideOp op = CultOverrideOp.Set) => new()
|
||||
{
|
||||
Key = key,
|
||||
SchemaId = Registry.GetRequired<VariantGear>().SchemaId,
|
||||
StoredAt = "2026-01-01T00:00:00.0000000Z",
|
||||
Payload = payload ?? Array.Empty<byte>(),
|
||||
Variant = new CultVariantDelta(baseKey, new[]
|
||||
{
|
||||
new CultVariantOverride(op, new[] { new CultPathStep(0) }, string.Empty, new byte[] { 0xa1, (byte)'x' })
|
||||
})
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void AVariantRecordUnderAV1HeaderIsRefusedNamingTheRecord()
|
||||
{
|
||||
var path = WriteStore(CultPersistedStoreSnapshot.FormatV1, PlainGear("hand-base"), VariantGearRecord("hand-variant", "hand-base"));
|
||||
var error = Assert.Throws<NotSupportedException>(() => Open(path).Dispose())!;
|
||||
Assert.That(error.Message, Does.Contain("hand-variant").And.Contain("cultcache.store.v2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AVariantRecordWithAPayloadIsRefusedNamingTheRecord()
|
||||
{
|
||||
var path = WriteStore(PlainGear("hand-base"), VariantGearRecord("hand-variant", "hand-base", payload: new byte[] { 0x90 }));
|
||||
var error = Assert.Throws<NotSupportedException>(() => Open(path).Dispose())!;
|
||||
Assert.That(error.Message, Does.Contain("hand-variant").And.Contain("payload"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnOverrideWithAnUnknownOpIsRefusedNamingTheRecord()
|
||||
{
|
||||
var path = WriteStore(PlainGear("hand-base"), VariantGearRecord("hand-variant", "hand-base", op: (CultOverrideOp)7));
|
||||
var error = Assert.Throws<NotSupportedException>(() => Open(path).Dispose())!;
|
||||
Assert.That(error.Message, Does.Contain("hand-variant").And.Contain("op 7"));
|
||||
}
|
||||
|
||||
// ---- the other stores refuse loudly until their own cuts ----
|
||||
|
||||
[Test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue