Artifact c9944c25ea2dde9bf4d3fe171a33d9e10b22c4b15f4730c7204f9175400dc194:
- File
internal/deck/submanifest_test.go
— part of check-in
[245a0ed306]
at
2026-10-20 02:08:49
on branch feat/submanifest-reader
— conformance: re-pin the three drafts to their -00 names
zit check-in 68a3f76cb7 renames the artifact-format, repo-state and sync-protocol drafts from -01 to -00 in their headers, cross-references and reference entries, and retitles each Changes appendix "Changes in This Revision". No normative text changed and no section number moved.
The pinned copies take zit's file names, the requirement tables pin the new hashes, and the comments, tests and docs that cited the -01 drafts cite the -00 ones. The refresh script copies every draft under its own name. The sparse-cone copy is re-pinned to d497c2432f, whose text is unchanged. Coverage counts are identical before and after. (user: [email protected], size: 7758) [annotate] [blame] [check-ins using] [more...]
package deck import ( "strings" "testing" ) // These tests pin the card-level half of draft-fossil-submanifests-00: the S // card (§3), the SUBMANIFEST type and its reassignment rule (§4.3), the // legality table (§4.2) and the per-type semantic checks (§4.4). Each input // differs from an accepted one by the single property under test, so nothing // but that property can explain a rejection. const ( smH1 = "aa9d404c14fe4dadd2ab398961fb0dfe650a877c061f650d8233a5888e920acf" smH2 = "a50a722b465ed67b42ee0fbe61e4a19d6b1d22940ba5740e99e450921e3f52ea" smH3 = "0123456789abcdef0123456789abcdef01234567" ) func mustParse(t *testing.T, body string) *Deck { t.Helper() d, err := Parse(withZCard(body)) if err != nil { t.Fatalf("Parse(%q): %v", body, err) } return d } func mustReject(t *testing.T, body, wantSubstring string) { t.Helper() _, err := Parse(withZCard(body)) if err == nil { t.Fatalf("Parse(%q) accepted, want rejection", body) } if wantSubstring != "" && !strings.Contains(err.Error(), wantSubstring) { t.Fatalf("Parse(%q) error %q, want it to mention %q", body, err, wantSubstring) } } func TestTreeManifestParsesSCards(t *testing.T) { d := mustParse(t, "C tree\nD 2026-07-26T00:00:00\nS README.md "+smH1+ "\nS src/node11.c "+smH2+"\nU drh\n") if d.Type != Checkin { t.Fatalf("type %v, want Checkin", d.Type) } if len(d.S) != 2 { t.Fatalf("len(S) = %d, want 2", len(d.S)) } if d.S[1].Name != "src/node11.c" || d.S[1].UUID != smH2 { t.Fatalf("S[1] = %+v", d.S[1]) } } func TestSubmanifestTypeByReassignment(t *testing.T) { leaf := mustParse(t, "F README.md "+smH1+"\nF src/main.c "+smH2+"\n") if leaf.Type != Submanifest { t.Fatalf("leaf type %v, want Submanifest", leaf.Type) } interior := mustParse(t, "S a "+smH1+"\nS b "+smH2+"\n") if interior.Type != Submanifest { t.Fatalf("interior type %v, want Submanifest", interior.Type) } } func TestSCardDecodesFilenameNotHash(t *testing.T) { d := mustParse(t, `S a\sb `+smH1+"\n") if d.S[0].Name != "a b" { t.Fatalf("S name %q, want decoded %q", d.S[0].Name, "a b") } mustReject(t, `S a `+`aa9d404c14fe4dadd2ab398961fb0dfe650a877c061f650d8233a5888e920ac\s`+"\n", "hash") } func TestSCardArityAndShape(t *testing.T) { mustReject(t, "S a\n", "S-card") mustReject(t, "S a "+smH1+" x\n", "S-card") mustReject(t, "S a 0123\n", "hash") mustReject(t, "S ../a "+smH1+"\n", "filename") } func TestSRunStrictlyAscending(t *testing.T) { mustReject(t, "S b "+smH1+"\nS a "+smH2+"\n", "order") mustReject(t, "S a "+smH1+"\nS a "+smH2+"\n", "order") } func TestSAcceptsSha1AndUppercaseHash(t *testing.T) { mustParse(t, "S a "+smH3+"\n") mustParse(t, "S a "+strings.ToUpper(smH1)+"\n") } func TestManifestRejectsFAndS(t *testing.T) { mustReject(t, "D 2026-07-26T00:00:00\nF a "+smH3+"\nS b "+smH1+"\n", "") } func TestManifestRejectsBAndS(t *testing.T) { mustReject(t, "B "+smH1+"\nD 2026-07-26T00:00:00\nS b "+smH2+"\n", "") } func TestSubmanifestRejectsFAndS(t *testing.T) { mustReject(t, "F a "+smH3+"\nS b "+smH1+"\n", "") } func TestSubmanifestRejectsOtherCards(t *testing.T) { mustReject(t, "C x\nF a "+smH3+"\n", "not allowed") mustReject(t, "F a "+smH3+"\nU drh\n", "not allowed") } func TestSubmanifestRejectsDeleteMarker(t *testing.T) { mustReject(t, "F a\n", "hash") } func TestDLessManifestStillRejected(t *testing.T) { // A C-only, D-less artifact was a manifest missing its D card under // artifact-format-00; the reassignment makes it a sub-manifest with an // illegal C card. Either way it is rejected. mustReject(t, "C x\nP "+smH3+"\n", "") } func TestSCardOrderAgainstTAndR(t *testing.T) { // §3.4: S falls between R and T. mustReject(t, "D 2026-07-26T00:00:00\nT +x *\nS a "+smH1+"\nU u\n", "out of order") mustParse(t, "D 2026-07-26T00:00:00\nR 0123456789abcdef0123456789abcdef\nS a "+smH1+"\nT +x *\nU u\n") } func TestTreeManifestRoundTrips(t *testing.T) { body := "C tree\nD 2026-07-26T00:00:00.000\nS README.md " + smH1 + "\nS src/node11.c " + smH2 + "\nU drh\n" d := mustParse(t, body) out, err := d.Marshal() if err != nil { t.Fatal(err) } if string(out) != string(withZCard(body)) { t.Fatalf("round trip:\n got %q\nwant %q", out, withZCard(body)) } leafBody := "F a\\sb " + smH1 + "\nF c " + smH2 + "\n" leaf := mustParse(t, leafBody) out, err = leaf.Marshal() if err != nil { t.Fatal(err) } if string(out) != string(withZCard(leafBody)) { t.Fatalf("leaf round trip:\n got %q\nwant %q", out, withZCard(leafBody)) } } func TestUndefinedLettersStillRejected(t *testing.T) { for _, letter := range []string{"O", "V", "X", "Y"} { mustReject(t, "D 2026-07-26T00:00:00\n"+letter+" x\n", "") } } // §6.2: canonical emission escapes exactly SP, LF and backslash; the // decoder's other escape octets are written raw. func TestCanonicalEncodeIsMinimal(t *testing.T) { cases := map[string]string{ "a b": `a\sb`, "a\nb": `a\nb`, `a\b`: `a\\b`, "a\tb\rc": "a\tb\rc", "v\vf\f\x00": "v\vf\f\x00", "plain.c": "plain.c", } for in, want := range cases { if got := CanonicalEncode(in); got != want { t.Errorf("CanonicalEncode(%q) = %q, want %q", in, got, want) } if back := FossilDecode(CanonicalEncode(in)); back != in { t.Errorf("FossilDecode(CanonicalEncode(%q)) = %q", in, back) } } } // Appendix A.2: the first leaf of the example tree, byte for byte. func TestMarshalLeafReproducesAppendixA2(t *testing.T) { got := MarshalLeaf([]FileCard{ {Name: "README.md", UUID: "a4ab3d6bba47168d231f8ebdfaeb9bce15e134d7150bf20800a42551409bfbfb"}, {Name: "src/main.c", UUID: "ef30bf8ab404da88c777979d0eeb8729f59cff33423382cfb212ed7b70a7f31c"}, }) want := "F README.md a4ab3d6bba47168d231f8ebdfaeb9bce15e134d7150bf20800a42551409bfbfb\n" + "F src/main.c ef30bf8ab404da88c777979d0eeb8729f59cff33423382cfb212ed7b70a7f31c\n" + "Z bdbb5692981c3a9e28c8320434534bf1\n" if string(got) != want { t.Fatalf("MarshalLeaf:\n got %q\nwant %q", got, want) } } // §6.2: a leaf writes its filename and old-name tokens canonically, keeps // the permission token, and forces the "w" placeholder on a rename. func TestMarshalLeafCanonicalTokens(t *testing.T) { got := MarshalLeaf([]FileCard{ {Name: "a b\tc", UUID: smH1, Perm: "x"}, {Name: "d", UUID: smH2, OldName: "old name"}, }) body := "F a\\sb\tc " + smH1 + " x\nF d " + smH2 + " w old\\sname\n" if string(got) != string(withZCard(body)) { t.Fatalf("MarshalLeaf:\n got %q\nwant %q", got, withZCard(body)) } d := mustParse(t, body) if d.Type != Submanifest || d.F[0].Name != "a b\tc" || d.F[1].OldName != "old name" { t.Fatalf("leaf parses as %+v", d) } } // §6.2 step 3: an interior sub-manifest is one S card per member and a Z // card; S filenames are canonical. func TestMarshalInterior(t *testing.T) { got := MarshalInterior([]SubmanifestCard{{Name: "a\tb", UUID: smH1}, {Name: "c d", UUID: smH2}}) body := "S a\tb " + smH1 + "\nS c\\sd " + smH2 + "\n" if string(got) != string(withZCard(body)) { t.Fatalf("MarshalInterior:\n got %q\nwant %q", got, withZCard(body)) } if d := mustParse(t, body); d.Type != Submanifest || d.S[0].Name != "a\tb" { t.Fatalf("interior parses as %+v", d) } } // A tree manifest's S filenames are canonical too (§6.2), so a manifest the // writer builds and the one it marshals agree byte for byte. func TestManifestSCardsMarshalCanonically(t *testing.T) { d := &Deck{D: mustParse(t, "D 2026-07-26T00:00:00\nS a "+smH1+"\nS b "+smH2+"\n").D, S: []SubmanifestCard{{Name: "a\tb", UUID: smH1}, {Name: "c d", UUID: smH2}}} out, err := d.Marshal() if err != nil { t.Fatal(err) } if !strings.Contains(string(out), "S a\tb "+smH1+"\nS c\\sd "+smH2+"\n") { t.Fatalf("manifest S cards not canonical: %q", out) } }