Artifact bfa6bf0b9ecd78e47c24c20a1350fc633dae741aa1cc1a78700a6fbb4a7501e0:
- File
conformance/repostate_cases_test.go
— part of check-in
[8cc3d7c5e4]
at
2026-10-13 02:23:50
on branch test/conformance-01-drafts
— test(conformance): check the -01 drafts of artifact, repo-state and sync
Re-pin the conformance suite to draft-fossil-artifact-format-01, draft-fossil-repo-state-01 and draft-fossil-sync-protocol-01, copied byte for byte from zit check-in 100351e26c. The delta format has no -01 and stays on -00. The -00 errata are folded into -01 and are no longer pinned; the six -01 errata are pinned for the fossil 2.28 behavior they record. conformance/rfcs/README.md records where each copy came from.
The requirement tables are re-read against -01, and new cases cover the date tag's event time (repo-state ยง4.2 step 6), the Julian-day grammar, a delta manifest naming a delta, dropped reserved F cards, the trailing-punctuation retry, truncated payloads, errors N, a file card after a refused push, the end of a request after a failed login, and the answer to a gimme for private content.
No library behavior changes. Where -01 departs from fossil 2.28 and go-libfossil follows fossil (a cancelled, unparsable or 0 date tag), the cases are marked as deviations. The non-conformances the re-pin exposes are marked as gaps, tracked in #370, #371, #372, #373, #374 and #375. (user: [email protected], size: 9230) [annotate] [blame] [check-ins using] [more...]
package conformance // The derived-state cases, by draft section. Rows read: // // plink parent>child isprim @mtime baseline // tagxref tag rid tagtype srcid origid value @mtime // leaf rid // phantom rid // event objid type @mtime // // with every artifact named by its label and srcid 0 written 0. func repostateCases() []rsCase { var all []rsCase for _, table := range [][]rsCase{ dagCases(), crosslinkCases(), rejectionCases(), tagTypeCases(), propagationCases(), blockingCases(), branchCases(), leafCases(), dateTagCases(), } { all = append(all, table...) } return all } func dagCases() []rsCase { return []rsCase{{ Case: Case{ID: "repostate.3.2.parents-order", Req: "RS-3.2-1"}, arts: merged(), tables: []string{"plink"}, want: rows("plink", "c1>c2 1 @2 -", "c1>b1 1 @3 -", "c2>m 1 @4 -", "b1>m 0 @4 -"), ignoring: []string{"-plink: b1>m 0 @4 -", "+plink: b1>m 1 @4 -"}, rebuild: true, }, { // plink's unique key on (pid, cid) makes a second row for c1 an // error, so a crosslink that ignored the rule would lose the link. Case: Case{ID: "repostate.3.1.duplicate-parent", Req: "RS-3.1-2"}, arts: with(chain()[:2], checkin("m", 3, of("c2", "c1", "c1")), checkin("n", 4, of("m", "m"))), tables: []string{"plink"}, want: rows("plink", "c1>c2 1 @2 -", "c2>m 1 @3 -", "c1>m 0 @3 -", "m>n 1 @4 -"), ignoring: []string{"-plink: c1>m 0 @3 -"}, rebuild: true, }, { // n names m twice; the second occurrence must not clear the flag // the first set. Case: Case{ID: "repostate.3.2.duplicate-primary", Req: "RS-3.2-2"}, arts: with(chain()[:2], checkin("m", 3, of("c2", "c1", "c1")), checkin("n", 4, of("m", "m"))), tables: []string{"plink"}, want: rows("plink", "c1>c2 1 @2 -", "c2>m 1 @3 -", "c1>m 0 @3 -", "m>n 1 @4 -"), ignoring: []string{"-plink: m>n 1 @4 -", "+plink: m>n 0 @4 -"}, rebuild: true, }, { // c2 is a delta manifest: its link carries c1 as the baseline and // c2's own time. The files are loaded too: go-libfossil defers a // check-in whose files are missing. Case: Case{ID: "repostate.3.1.link-metadata", Req: "RS-3.1-1"}, arts: []rsArt{ file("fa", "a\n"), file("fb", "b\n"), literal("c1", "C c1\nD "+rsTime(1)+"\nF a.txt {fa}\n"+ "T *branch * trunk\nT *sym-trunk *\nU u\n"), literal("c2", "B {c1}\nC c2\nD "+rsTime(2)+"\nF b.txt {fb}\nP {c1}\nU u\n"), }, tables: []string{"plink"}, want: rows("plink", "c1>c2 1 @2 c1"), ignoring: []string{"-plink: c1>c2 1 @2 c1", "+plink: c1>c2 1 @2 -"}, rebuild: true, }} } func crosslinkCases() []rsCase { return []rsCase{{ Case: Case{ID: "repostate.4.2.phantom-parent", Req: "RS-4.2-2"}, arts: []rsArt{checkin("c2", 2, of("c1"))}, tables: []string{"phantom", "plink"}, want: join(rows("phantom", "c1"), rows("plink", "c1>c2 1 @2 -")), ignoring: []string{"-phantom: c1", "-plink: c1>c2 1 @2 -"}, rebuild: true, rebuildWrites: "the phantom root has no branch tag, so fossil rebuild tags it trunk", }, { Case: Case{ID: "repostate.4.2.phantom-target", Req: "RS-4.2-1"}, arts: with(chain()[:2], control("t", 9, "*foo {c3} v")), tables: []string{"phantom", "tagxref"}, tags: []string{"foo"}, want: join(rows("phantom", "c3"), rows("tagxref", "foo c3 2 t c3 'v' @9")), ignoring: []string{"-phantom: c3", "-tagxref: foo c3 2 t c3 'v' @9"}, rebuild: true, }, { // The tag waits on phantom c3, then reaches c4 once both arrive. Case: Case{ID: "repostate.4.2.phantom-late-target", Req: "RS-4.2-1"}, arts: with(chain()[:2], control("t", 9, "*foo {c3} v"), chain()[2], chain()[3]), tables: []string{"phantom", "plink", "tagxref"}, tags: []string{"foo"}, want: join(chainPlink, rows("tagxref", "foo c3 2 t c3 'v' @9", "foo c4 2 0 c3 'v' @9")), ignoring: []string{"-tagxref: foo c4 2 0 c3 'v' @9", "+phantom: c3"}, rebuild: true, }, { Case: Case{ID: "repostate.4.2.technote-tags", Req: "RS-4.2-3"}, arts: with(chain()[:2], technote("e", 6, "+bgcolor * red")), tables: []string{"tagxref"}, tags: []string{"bgcolor"}, want: rows("tagxref", "bgcolor e 1 e e 'red' @6"), ignoring: []string{"-tagxref: bgcolor e 1 e e 'red' @6"}, rebuild: true, }, { // fossil(1) dates the cluster tag with the wall clock, so its // mtime is not compared. Case: Case{ID: "repostate.4.2.cluster", Req: "RS-4.2-4"}, arts: with(chain()[:2], literal("k", "M {c1}\nM {c2}\n")), tables: []string{"tagxref"}, tags: []string{"cluster"}, want: rows("tagxref", "cluster k 1 k k NULL *"), ignoring: []string{"-tagxref: cluster k 1 k k NULL *"}, rebuild: true, }, { // c3 and c4 arrive after the tag on c1 and inherit it. Case: Case{ID: "repostate.4.2.late-child", Req: "RS-4.2-5"}, arts: with(chain()[:2], control("t", 9, "*foo {c1} v"), chain()[2], chain()[3]), tables: []string{"tagxref"}, tags: []string{"foo"}, want: rows("tagxref", "foo c1 2 t c1 'v' @9", "foo c2 2 0 c1 'v' @9", "foo c3 2 0 c1 'v' @9", "foo c4 2 0 c1 'v' @9"), ignoring: []string{"-tagxref: foo c3 2 0 c1 'v' @9", "-tagxref: foo c4 2 0 c1 'v' @9"}, rebuild: true, }, { Case: Case{ID: "repostate.4.2.propagate-on-tag", Req: "RS-4.2-6"}, arts: with(chain(), control("t", 9, "*foo {c2} v")), tables: []string{"tagxref"}, tags: []string{"foo"}, want: rows("tagxref", "foo c2 2 t c2 'v' @9", "foo c3 2 0 c2 'v' @9", "foo c4 2 0 c2 'v' @9"), ignoring: []string{"-tagxref: foo c3 2 0 c2 'v' @9"}, rebuild: true, }, { // A singleton walks as a cancel: c4 loses the foo it inherited. Case: Case{ID: "repostate.4.2.singleton-walks-as-cancel", Req: "RS-4.2-6"}, arts: with(chain(), control("t1", 8, "*foo {c1} v"), control("t2", 9, "+foo {c3} s")), tables: []string{"tagxref"}, tags: []string{"foo"}, want: rows("tagxref", "foo c1 2 t1 c1 'v' @8", "foo c2 2 0 c1 'v' @8", "foo c3 1 t2 c3 's' @9"), ignoring: []string{"+tagxref: foo c4 2 0 c1 'v' @8"}, rebuild: true, }, reparentCase()} } // reparentCase moves c3 from c2 to c1 with a parent tag, which leaves c2 // without a child, so a leaf. func reparentCase() rsCase { return rsCase{ Case: Case{ID: "repostate.4.2.reparent", Req: "RS-4.2-8"}, arts: with(chain(), control("t", 9, "+parent {c3} {c1}")), tables: []string{"leaf", "plink", "tagxref"}, tags: []string{"parent"}, want: join(rows("plink", "c1>c2 1 @2 -", "c1>c3 1 @3 -", "c3>c4 1 @4 -"), rows("leaf", "c2", "c4"), rows("tagxref", "parent c3 1 t c3 '{c1}' @9")), ignoring: []string{"-plink: c1>c3 1 @3 -", "+plink: c2>c3 1 @3 -", "-leaf: c2"}, rebuild: true, // fossil's per-artifact crosslink rewrites plink but does not // recheck c2, the old parent; its rebuild does, as Section 7.3 // requires. incremental: "fossil's per-artifact crosslink leaves c2, the old parent, out of leaf", } } // rejectionCases are artifacts the T-card rules reject: they derive // nothing, so the rows are those of the rest of the script. func rejectionCases() []rsCase { twoLinks := rows("plink", "c1>c2 1 @2 -") rejected := func(id, req string, x rsArt, tags ...string) rsCase { return rsCase{ Case: Case{ID: id, Req: req}, arts: with(chain()[:2], x), tables: []string{"plink", "tagxref"}, tags: tags, want: twoLinks, ignoring: []string{"+plink: c2>x 1 @5 -"}, rebuild: true, } } unsortedCheckin := literal("x", "C x\nD "+rsTime(5)+"\nP {c2}\nT *zz *\nT *ax *\nU u\n") unsortedControl := literal("x", "D "+rsTime(5)+"\nT *zz {c1} 1\nT *ax {c1} 1\nU u\n") cases := []rsCase{ rejected("repostate.4.1.no-prefix", "RS-4.1-1", literal("x", "C x\nD "+rsTime(5)+"\nP {c2}\nT foo *\nU u\n"), "foo"), rejected("repostate.4.1.hex-name", "RS-4.1-2", literal("x", "C x\nD "+rsTime(5)+"\nP {c2}\nT *beef *\nU u\n"), "beef"), rejected("repostate.4.1.unsorted", "RS-4.1-3", unsortedCheckin, "ax", "zz"), rejected("repostate.4.3.no-effect", "RS-4.3-1", unsortedCheckin, "ax", "zz"), rejected("repostate.4.1.no-partial", "RS-4.1-4", unsortedControl, "ax", "zz"), rejected("repostate.4.4.opaque-control", "RS-4.4-2", literal("x", "D "+rsTime(9)+"\nT *foo {c1} v\n"), "foo"), } // A control artifact derives no link; ignoring the rules shows as tags. cases[4].ignoring = []string{"+tagxref: ax c1 2 x c1 '1' @5"} cases[5].ignoring = []string{"+tagxref: foo c1 2 x c1 'v' @9"} return append(cases, rsCase{ // Assembly goes on past the opaque artifact: c3 and c4 still link. Case: Case{ID: "repostate.4.4.opaque-load-succeeds", Req: "RS-4.4-1"}, arts: with(chain()[:2], literal("x", "D "+rsTime(5)+"\nT *foo {c1} v\n"), chain()[2], chain()[3]), tables: []string{"leaf", "plink"}, want: join(chainPlink, chainLeaf), ignoring: []string{"-leaf: c4"}, rebuild: true, }, rsCase{ Case: Case{ID: "repostate.12.technote-non-self", Req: "RS-12-1"}, arts: with(chain()[:2], technote("e", 6, "*foo {c1} v")), tables: []string{"event", "tagxref"}, tags: []string{"foo"}, want: rows("event", "c1 ci @1", "c2 ci @2"), ignoring: []string{"+event: e e @6", "+tagxref: foo c1 2 e c1 'v' @6"}, rebuild: true, }) }