From 1f698ff225daf099729bfd7bcfcd379adffda24f Mon Sep 17 00:00:00 2001 From: Libby Date: Fri, 13 Nov 2026 16:00:09 +0000 Subject: [PATCH] Caching tests: content-version vector, written by the C# reference store.cc and versions.msgpack pin the content version of a stored record (SHA-256 of its persisted encoding) for other runtimes to match. Co-Authored-By: Claude Sonnet 5.5 --- .../ContentVersionVectorTests.cs | 115 ++++++++++++++++++ tests/vectors/content-version/README.md | 17 +++ tests/vectors/content-version/store.cc | Bin 0 -> 766 bytes .../vectors/content-version/versions.msgpack | 1 + 4 files changed, 133 insertions(+) create mode 100644 tests/GameCult.Caching.Tests/ContentVersionVectorTests.cs create mode 100644 tests/vectors/content-version/README.md create mode 100644 tests/vectors/content-version/store.cc create mode 100644 tests/vectors/content-version/versions.msgpack diff --git a/tests/GameCult.Caching.Tests/ContentVersionVectorTests.cs b/tests/GameCult.Caching.Tests/ContentVersionVectorTests.cs new file mode 100644 index 00000000..7c2cd5a8 --- /dev/null +++ b/tests/GameCult.Caching.Tests/ContentVersionVectorTests.cs @@ -0,0 +1,115 @@ +#nullable enable +using System; +using System.Buffers; +using System.IO; +using System.Linq; +using GameCult.Caching; +using GameCult.Caching.MessagePack; +using MessagePack; +using NUnit.Framework; + +namespace GameCult.Caching.Tests +{ + // tests/vectors/content-version: the content version of a stored record, the lowercase hex SHA-256 of its persisted encoding + // (variants:ruling:write-set-content-version-declared-ownership), as the reference reads it from a single-file store. The C# + // reference is the only writer: under CULTCACHE_WRITE_VECTORS=1 the store is written and the versions read from it are written + // beside it; every other run opens the committed store.cc and compares what the reference reads to versions.msgpack. Another + // runtime computes the version from the same file and must equal these rows. + public class ContentVersionVectorTests + { + private const string Vector = "content-version"; + + private static readonly Type Item = + EmittedDocumentTypes.Emit("CvItem", "tests.cv_item", "tests.cv_item.v1", new[] { new EmittedDocumentTypes.Field("Text", typeof(string), 0) }); + + private static readonly CultDocumentRegistry Registry = CultDocumentRegistry.ForTypes(new[] { Item }); + + private static readonly (string Key, string Text)[] Records = { ("alpha", "first"), ("beta", "second") }; + + private static byte[] StoreBytes() + { + var directory = Path.Combine(Path.GetTempPath(), $"cultlib-cvv-{Guid.NewGuid():N}"); + Directory.CreateDirectory(directory); + try + { + var path = Path.Combine(directory, "store.cc"); + using (var cache = CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry, StoreFlushOnDispose = true })) + { + cache.Clock = () => VectorFiles.StoredAt; + foreach (var (key, text) in Records) + cache.UpsertAsync(Item, EmittedDocumentTypes.New(Item, ("Text", text)), new CultRecordKey(key)).GetAwaiter().GetResult(); + cache.FlushAsync().GetAwaiter().GetResult(); + } + + return File.ReadAllBytes(path); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + // What the reference reads from a store file: [key, version] for each record, sorted by key. + private static (string Key, string Version)[] ReadVersions(byte[] store) + { + var directory = Path.Combine(Path.GetTempPath(), $"cultlib-cvr-{Guid.NewGuid():N}"); + Directory.CreateDirectory(directory); + try + { + var path = Path.Combine(directory, "store.cc"); + File.WriteAllBytes(path, store); + using var cache = CultCacheMessagePack.Create(path, new CultCacheOpenOptions { Registry = Registry }); + return cache.AllStoredDocuments + .Select(stored => (Key: stored.Key.Value, Version: stored.StoredVersion)) + .OrderBy(row => row.Key, StringComparer.Ordinal) + .ToArray(); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + private static byte[] RowsFile((string Key, string Version)[] rows) + { + var buffer = new ArrayBufferWriter(); + var writer = new MessagePackWriter(buffer); + writer.WriteArrayHeader(rows.Length); + foreach (var (key, version) in rows) + { + writer.WriteArrayHeader(2); + writer.Write(key); + writer.Write(version); + } + + writer.Flush(); + return buffer.WrittenSpan.ToArray(); + } + + [Test] + public void TheReferenceReadsTheCommittedVersions() + { + if (VectorFiles.Writing) + { + var store = StoreBytes(); + VectorFiles.Check(Vector, "store.cc", store); + VectorFiles.Check(Vector, "versions.msgpack", RowsFile(ReadVersions(store))); + return; + } + + var committed = VectorFiles.Committed(Vector, "store.cc"); + var rows = ReadVersions(committed); + Assert.That(rows.Select(row => row.Key), Is.EqualTo(Records.Select(record => record.Key)), "the store holds the two records"); + Assert.That(rows.Select(row => row.Version).Distinct().Count(), Is.EqualTo(rows.Length), "two different records, two versions"); + Assert.That(rows.All(row => row.Version.Length == 64 && row.Version == row.Version.ToLowerInvariant()), Is.True, "lowercase hex SHA-256"); + Assert.That(Convert.ToHexString(RowsFile(rows)), Is.EqualTo(Convert.ToHexString(VectorFiles.Committed(Vector, "versions.msgpack"))), "versions.msgpack"); + } + + [Test] + public void TheCommittedStoreIsWhatTheReferenceWrites() + { + if (VectorFiles.Writing) return; + Assert.That(Convert.ToHexString(StoreBytes()), Is.EqualTo(Convert.ToHexString(VectorFiles.Committed(Vector, "store.cc"))), "store.cc"); + } + } +} diff --git a/tests/vectors/content-version/README.md b/tests/vectors/content-version/README.md new file mode 100644 index 00000000..cddd7bff --- /dev/null +++ b/tests/vectors/content-version/README.md @@ -0,0 +1,17 @@ +# content-version vectors + +Written only by the C# reference, `tests/GameCult.Caching.Tests/ContentVersionVectorTests.cs`, with +`CULTCACHE_WRITE_VECTORS=1` (never set in CI). Every other run opens the committed `store.cc`, compares what the reference reads +to `versions.msgpack` and compares the store bytes the reference writes to the file; a changed vector is a reviewed commit. +Other runtimes compute the version of each record of `store.cc` and must equal these rows +(`variants:ruling:write-set-content-version-declared-ownership`). + +RULE: the content version of a stored record is the lowercase hex SHA-256 of its persisted encoding, the MessagePack array +`[key, schemaId, storedAt, payload bytes]` exactly as the store writes the record. + +## Files + +- `store.cc`: a single-file store of one type, `tests.cv_item` (one string slot, `[0: Text]`), holding `alpha` = "first" and + `beta` = "second", stored-at pinned to `2026-01-01T00:00:00Z`. +- `versions.msgpack`: an array of `[key, version]`, sorted by key, the version the reference reads for the record + (`GetStored(key).StoredVersion`). diff --git a/tests/vectors/content-version/store.cc b/tests/vectors/content-version/store.cc new file mode 100644 index 0000000000000000000000000000000000000000..3962430aec5fc575a5c2c70acde762d9a6e046cd GIT binary patch literal 766 zcmcgp!Ait15cQ_NVXtbLq?@Ga&676|u6PlVX)<*SZC9EoF5*X21^oc^CWv3KEdGP+ z-xzmgalI;rxy-zo_vTG*Ud>tM4VnSP&JSCP&sldj-EjltLT13YLC&NUi8hI)lq(IQ zXJwu!(@QPWNdY{!QX?2{QHs=P6(*Gl?J9Zp