Artifact e6f6ba27d7aae9bc3ab3d5556231d3c186cd2d7aa28cde28f4150157eb6441dc:
- File
conformance/repostate_commit_test.go
— part of check-in
[7fa0b9b5da]
at
2026-10-11 02:13:19
on branch trunk
— test(conformance): repository-state conformance suite (#262) (#295)
* test(conformance): repository-state conformance suite (#262)
Check go-libfossil against draft-fossil-repo-state-00, requirement by requirement. Each derived-state case is a scripted sequence of artifacts loaded into go-libfossil and into fossil(1); the tables derived from it are compared row by row, joined by artifact hash. Every case runs through manifest.Crosslink, as one sweep and one artifact at a time, and most also run as a ".rebuild" case through verify.Rebuild. fossil's rows must equal each expectation on every run, and each case states what ignoring its requirement would change, so a case cannot pass or fail by accident.
API cases cover name resolution, leaves, the ambiguity test, branch creation and listing, and tagging, each compared with fossil's answer on the same repository. TestRepoStateCorpus compares verify.Rebuild and a Crosslink sweep with fossil rebuild on real repositories named by CONFORMANCE_REPOS.
Known failures are marked with the placeholder Gap #0 until their issues are filed, so TestCoverage refuses them for now.
* test(conformance): repository-state suite review fixes (#262)
- Unsorted T-card cases use a non-hex name, so the ordering rule, not the hex-name rule, is what rejects them. - RS-4.2-7 crosslinks every artifact a second time over the rows the first left (repostate.4.2.relink-unchanged), instead of a sweep that links nothing. - RS-7.3-2, -3 and -4 are checked through manifest.Checkin and branch.Create, which maintain leaves one check-in at a time; a crosslink sweep recomputes the whole table. - retag-branch is built so the retagged check-in's own leaf status changes; branch membership is read through BranchLeaves, and the tagxref case it replaced is reattributed to RS-5.4-5; bad-prefix also compares tagxref. - parentless-untagged follows the draft and marks go-libfossil's crosslink, which follows fossil(1), as a deviation; the unused draft field becomes fossil, checked against fossil's rows. - Every fossil(1) command, in all suites, runs with HOME, FOSSIL_HOME and XDG_CONFIG_HOME in the test's directory, so it no longer records checkouts in the user's global configuration.
* test(conformance): mark repository-state gaps with their issues (#262)
Replace the placeholder gap marks with the issues that track each fix: #278 and #285-#294. (user: [email protected], size: 4223) [annotate] [blame] [check-ins using]
package conformance import ( "path/filepath" "strings" "github.com/danmestas/go-libfossil/internal/branch" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/manifest" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/simio" ) // The incremental leaf procedures of Section 7.3. A crosslink sweep // recomputes the whole leaf table at its end, so it cannot show whether a // single check is right; a local commit maintains leaves one check-in at a // time, so these cases commit through manifest.Checkin and branch.Create. // The reference is `fossil rebuild` on a copy of the result. // commitStep is one commit: its label, its parents by label (primary // first), and, when set, the branch it starts with branch.Create. type commitStep struct { label string parents []string branch string } // committedLeaves commits steps in order and checks the leaf table. func committedLeaves(id, req string, steps []commitStep, want ...string) rsAPICase { return rsAPICase{Case: Case{ID: id, Req: req}, run: func(e *rsEnv) error { path, labels, err := commitAll(e, steps) if err != nil { return err } expected := rsState{"leaf": rows("leaf", want...)} for i, row := range expected["leaf"] { expected["leaf"][i] = strings.TrimPrefix(row, "leaf: ") } if e.ref != nil { rebuilt, wrote, err := e.ref.rebuildCopy(fossilRepo{path: path}) if err != nil || wrote { return expectf("fossil rebuild: %v, stored artifacts of its own: %v", err, wrote) } fossil, err := readState(rebuilt, labels, []string{"leaf"}, nil) if err != nil { return err } if d := diffState(expected, fossil, "want", "fossil rebuild"); d != "" { return expectf("fossil(1) disagrees with the case: %s", d) } } got, err := readState(path, labels, []string{"leaf"}, nil) if err != nil { return err } if d := diffState(expected, got, "want", "commits"); d != "" { return expectf("%s", d) } return nil }} } // commitAll makes each commit in a new repository, which it closes, and // returns its path and the label of every check-in's hash. func commitAll(e *rsEnv, steps []commitStep) (string, map[string]string, error) { path := filepath.Join(e.t.TempDir(), "commits.fossil") r, err := repo.Create(path, "u", simio.CryptoRand{}, "") if err != nil { return "", nil, err } defer r.Close() rids, labels := map[string]libfossil.FslID{}, map[string]string{} for i, step := range steps { rid, uuid, err := commitOne(r, step, rids, i+1) if err != nil { return "", nil, err } rids[step.label], labels[uuid] = rid, step.label } return path, labels, r.Close() } func commitOne( r *repo.Repo, step commitStep, rids map[string]libfossil.FslID, s int, ) (libfossil.FslID, string, error) { var parents []libfossil.FslID for _, label := range step.parents { parents = append(parents, rids[label]) } if step.branch != "" { return branch.Create(r, branch.CreateOpts{ Name: step.branch, Parent: parents[0], User: "u", Time: rsAt(s)}) } opts := manifest.CheckinOpts{ Files: []manifest.File{{Name: "f.txt", Content: []byte(step.label + "\n")}}, Comment: step.label, User: "u", Time: rsAt(s), } if len(parents) > 0 { opts.Parent, opts.MergeParents = parents[0], parents[1:] } return manifest.Checkin(r, opts) } func commitLeafCases() []rsAPICase { return []rsAPICase{ // c1 leaves the table when c2 lands on its branch. committedLeaves("repostate.7.3.incremental", "RS-7.3-2", []commitStep{ {label: "c1"}, {label: "c2", parents: []string{"c1"}}, }, "c2"), // b, a merge parent of m on its own branch, is rechecked too. committedLeaves("repostate.7.3.recheck-parents", "RS-7.3-3", []commitStep{ {label: "c1"}, {label: "a", parents: []string{"c1"}}, {label: "b", parents: []string{"c1"}}, {label: "m", parents: []string{"a", "b"}}, }, "m"), // feat's root is checked only once its branch tag is in place, so // c1, whose only child is on feat, stays a leaf. committedLeaves("repostate.7.3.batch-applied", "RS-7.3-4", []commitStep{ {label: "c1"}, {label: "feat", parents: []string{"c1"}, branch: "feat"}, }, "c1", "feat"), } }