test(vt): round 3 item 8 — nav byte-identity coverage + s-cell strip + .ast round-trip

The already-committed nav_briennecarlus.af/nav_empyrean.af/nav_lockandkeyjaw.af
fixtures (checkpoint/recall/portal2 node coverage, clean metaf headers)
were exercised by the model-equality proofs (parse, parse-write-parse,
binary-import-matches-af) but never by the REAL byte-identical writer-output
theory, which only ever covered nav_ab.af. Converted
WriterOutputMatchesMetafCanonicalEmissionNavOnly into a Theory over all
four nav-only fixtures.

Added two more coverage gaps this round's audit named:
- StringCellStripsEmbeddedNewlineAtConstructionNotJustOnWrite pins
  VtankCell.String's gy.cs:84 newline strip (construction-time, not just
  at WriteTo).
- RealAstFixturesRoundTripByteIdentical extends the existing schema-only
  owner-{a,b,c}.ast coverage to a real parse -> Render() byte-identity
  round trip, the same proof-4 rigor MetafSerializerTests already applies
  to .af fixtures.

No writer bug surfaced in the extended nav theory or the .ast round trip —
both already passed; these are coverage-closing additions, not fixes.
Verified each new test has teeth via a temporary synthetic mutation rather
than a real revert (there is no production fix to revert here): the
string-cell test was checked against a temporarily-unstripped VtankCell.String
(failed, restored), and the .ast round-trip was checked against a
temporarily LF-only VtankWriter.AppendLine (all three .ast cases failed,
the four nav cases were unaffected since MetafSerializer's .af writer is a
separate code path from VtankUsdDocument's "y" grammar writer — restored).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 01:38:56 +02:00
parent d98bf4a139
commit 2566dad1d3
3 changed files with 66 additions and 5 deletions

View file

@ -234,12 +234,30 @@ public sealed class MetafSerializerTests
// Proof (4), nav-only case: SaveNav's own header (navHeader) and
// single-Nav fold-marker wrap are byte-identical to a real nav-only
// .af fixture. nav_ab.af's header is metaf's own fresh banner (first
// bytes "~~ {\n~~ ", matching every clean meta fixture above).
[Fact]
public void WriterOutputMatchesMetafCanonicalEmissionNavOnly()
// .af fixture. Every listed fixture carries metaf's own fresh banner
// (first bytes "~~ {\n~~ "); round 3 item 8 extended this from a
// single fixture (nav_ab.af) to the three checkpoint/recall/portal2
// fixtures copied from the metas repo (nav_briennecarlus.af carries
// chk+jmp, nav_empyrean.af carries rcl+ptl, nav_lockandkeyjaw.af
// carries rcl+chk) so the theory actually exercises every node kind
// metaf's own grammar supports, not just plain point waypoints.
public static TheoryData<string> NavByteIdenticalFixtureData()
{
var data = new TheoryData<string>();
foreach (string name in new[]
{
"nav_ab", "nav_briennecarlus", "nav_empyrean", "nav_lockandkeyjaw",
})
{
data.Add(Path.Combine(FixturesRoot, "af", name + ".af"));
}
return data;
}
[Theory]
[MemberData(nameof(NavByteIdenticalFixtureData))]
public void WriterOutputMatchesMetafCanonicalEmissionNavOnly(string path)
{
string path = Path.Combine(FixturesRoot, "af", "nav_ab.af");
string original = File.ReadAllText(path);
var settings = new NavigationSettings();
Assert.True(

View file

@ -324,6 +324,28 @@ public sealed class VtankProfileDirectoryTests
spells!.ColumnNames);
}
/// <summary>
/// Round 3 item 8: the shared "y" database grammar's writer must be
/// REAL byte-identical against real installed data, not just the
/// hand-built synthetic fixtures in VtankUsdDocumentTests — the three
/// real owner-*.ast per-character spell-tracking caches round-trip
/// through parse -> Render() unchanged.
/// </summary>
[Theory]
[InlineData("owner-a.ast")]
[InlineData("owner-b.ast")]
[InlineData("owner-c.ast")]
public void RealAstFixturesRoundTripByteIdentical(string fileName)
{
string original = File.ReadAllText(
Path.Combine(AppContext.BaseDirectory, "Fixtures", "vtank", fileName));
VtankDatabase database = VtankDatabase.Parse(original);
string rewritten = database.Render();
Assert.Equal(original, rewritten);
}
private sealed class MemoryStorage : IPluginStorage
{
private readonly Dictionary<string, string> _text = new(StringComparer.Ordinal);

View file

@ -110,4 +110,25 @@ public sealed class VtankUsdDocumentTests
["Apple", "Mango", "Zebra"],
reparsed.Tables.Select(static entry => entry.Name).ToArray());
}
/// <summary>
/// Round 3 item 8: pins <c>VtankCell.String</c>'s embedded-newline strip
/// (<c>gy.cs:84</c>: <c>text.Replace("\n", "")</c> — VTank's own writer
/// never emits a multi-line "s" cell; only a <c>ba</c> blob can carry
/// embedded newlines, see <see cref="BaBlobWithEmbeddedCrlfRoundTripsExactCharacterCount"/>).
/// The strip happens at construction, not only at write time, so a
/// caller reading the cell back via <see cref="VtankCell.AsString"/>
/// never sees the stripped character either.
/// </summary>
[Fact]
public void StringCellStripsEmbeddedNewlineAtConstructionNotJustOnWrite()
{
VtankCell cell = VtankCell.String("line1\nline2");
Assert.Equal("line1line2", cell.AsString());
var sb = new System.Text.StringBuilder();
cell.WriteTo(sb);
Assert.Equal("s\r\nline1line2\r\n", sb.ToString());
}
}