CultLib/tests/GameCult.Mesh.Tests/CultMeshSingleFileReadabilityTests.cs
Meta Vens a7114a1f9f C# write set: tests for copy-forward, raw catalog carry, header rule and the inverted lay-back rules
New WriteSetTests (single-file and directory stores, flush and commit): another writer's write and
removal survive, a store that appeared after open is kept, an unstaged in-place change is not
written, a copied record keeps the catalog entry the store holds now, an entry's unknown trailing
fields survive byte for byte, conditional and unconditional commits write the same bytes, the
header drops only when every copied record is known id-free, v2 exactly with a variant. Mesh: a
write copies neighbours forward and replaces only its key. The lay-back and whole-view tests and
the flush-restamp tests are rewritten or removed with the rules they pinned.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 03:16:49 +02:00

115 lines
5 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FluentAssertions;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
using NUnit.Framework;
#nullable enable
namespace GameCult.Mesh.Tests;
// A single-file write replaces a file exactly when the runtime's own reader opens it, as a cache's flush does. The bytes and
// the verdict are shared with every runtime: tests/vectors/document-variants-c2a/readability.
public sealed class CultMeshSingleFileReadabilityTests
{
private const int CSharp = 1;
private string _root = null!;
[SetUp]
public void SetUp()
{
_root = Path.Combine(Path.GetTempPath(), "cultmesh-single-file-readability", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_root);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(_root)) Directory.Delete(_root, true);
}
private static string VectorRoot()
{
for (var dir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); dir != null; dir = dir.Parent)
{
var candidate = Path.Combine(dir.FullName, "tests", "vectors", "document-variants-c2a", "readability");
if (Directory.Exists(candidate)) return candidate;
}
throw new DirectoryNotFoundException($"Shared readability vectors not found above {TestContext.CurrentContext.TestDirectory}.");
}
public static IEnumerable<TestCaseData> Vectors() =>
File.ReadAllLines(Path.Combine(VectorRoot(), "manifest.txt"))
.Where(line => line.Length > 0 && line[0] != '#')
.Select(line => line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries))
.Select(cells => new TestCaseData(cells[0], cells[CSharp] == "reads").SetName($"{cells[0]} {cells[CSharp]}"));
private static CultMeshBodyPublicationDocument Publication() => new()
{
BodyId = "aetheria:entities",
ProducerId = "aetheria",
SchemaId = "eve.entity_soa.v1"
};
[TestCaseSource(nameof(Vectors))]
public void AWriteReplacesAFileExactlyWhenItReads(string vector, bool reads)
{
var raw = new CultMeshSingleFileDocumentSchema("raw:schema", "RawSchema", "1");
var writes = new (string Name, Action<string> Write)[]
{
("typed", path => CultMesh.WriteSingleFileDocument(path, new CultRecordKey("publication"), Publication())),
("raw payload", path => CultMesh.WriteSingleFileDocumentPayload(path, new CultRecordKey("publication"), raw, null, new byte[] { 0x90 }))
};
foreach (var (name, write) in writes)
{
var path = Path.Combine(_root, name.Replace(' ', '-') + ".cc");
var bytes = File.ReadAllBytes(Path.Combine(VectorRoot(), vector));
File.WriteAllBytes(path, bytes);
if (reads)
{
var before = CultDocumentMessagePackSerialization.DeserializeSnapshot(bytes);
write(path);
var after = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(path));
after.Records.Where(record => record.Key == "publication").Should().ContainSingle(name);
var others = before.Records.Where(record => record.Key != "publication").ToArray();
after.Records.Should().HaveCount(others.Length + 1, name);
foreach (var record in others)
{
var kept = after.Records.Single(candidate => candidate.Key == record.Key);
kept.SchemaId.Should().Be(record.SchemaId, name);
kept.StoredAt.Should().Be(record.StoredAt, name);
kept.Payload.Should().Equal(record.Payload, name + " copies the file's other records as they are");
}
continue;
}
Assert.That(() => write(path), Throws.TypeOf<CultStoreUnreadableException>(), name);
File.ReadAllBytes(path).Should().Equal(bytes, name + " rewrote a file this runtime cannot read");
}
}
// A read asks the same reader as a write: a file this runtime cannot read, a zero-byte one included, is refused as unreadable
// and never reported as a store that lacks the record.
[Test]
public void AReadOfAZeroByteFileIsRefusedAsUnreadableNotAsAMissingRecord()
{
var path = Path.Combine(_root, "zero.cc");
File.WriteAllBytes(path, Array.Empty<byte>());
Assert.That(() => CultMesh.ReadSingleFileDocumentPayload(path, new CultRecordKey("publication"), "raw:schema"), Throws.TypeOf<CultStoreUnreadableException>());
}
[Test]
public void EveryVectorInTheFolderHasAManifestRow()
{
var rows = Vectors().Select(row => (string)row.Arguments[0]).Where(name => !name.StartsWith("..", StringComparison.Ordinal)).OrderBy(name => name, StringComparer.Ordinal);
var files = Directory.GetFiles(VectorRoot(), "*.bin").Select(Path.GetFileName).OrderBy(name => name, StringComparer.Ordinal);
rows.Should().Equal(files);
}
}