Artifact 2dc6012b45889570072dc86c333f9f0a36321cd87fffb161fa65ca3e1649eaac:
- File
conformance/repostate_working_test.go
— part of check-in
[0889d8b777]
at
2026-10-13 13:36:12
on branch main
— feat(resolve): checkout-inclusive request where ckout names the working files (#420)
* feat(resolve): checkout-inclusive request where ckout names the working files
Checkout.ResolveWorking is fossil's "ci+" request (repo-state-01 8.3): while the checkout has a version, "ckout" answers the working files, reported as WorkingVersion.Working rather than as a pseudo-RID. Every other name goes through the same resolver as Checkout.ResolveVersion, restricted to check-ins as fossil's ci+ is. The draft also makes ckout a keyword in a plain "ci" request; this follows fossil, where only ci+ has it.
New repostate.8.3 cases run the request against fossil test-name-to-id --type ci+, and RS-8.3-4 loses its scope note. Tracked in #419.
* fix(resolve): record the ckout retry as a fossil deviation; tighten tests (user: [email protected], size: 5309) [annotate] [blame] [check-ins using]
package conformance import ( "database/sql" "errors" "fmt" "path/filepath" "strings" gofossil "github.com/danmestas/go-libfossil" "github.com/danmestas/go-libfossil/db" ) // The checkout-inclusive request (Section 8.3), fossil's "ci+" type, which // Checkout.ResolveWorking is. "ckout" names the working files there while // the checkout has a version. The draft also makes it a keyword in a plain // check-in request; fossil does not (symbolic_name_to_rid, src/name.c), and // the cases follow fossil, as an erratum to the draft is pending. func workingAPICases() []rsAPICase { twoChildren := with(chain()[:3], checkin("m", 4, of("c3", "c2"))) // A tag named ckout, on c1: inside the inclusive request the keyword // wins; everywhere else the name is the tag. ckoutTag := with(twoChildren, control("t", 9, "+sym-ckout {c1}")) // A technote tagged sym-note: the inclusive request is a check-in // request, so only a check-in can answer it. noted := with(twoChildren, technote("e", 9, "+sym-note *")) return []rsAPICase{ workingResolves("repostate.8.3.ckout-inclusive", twoChildren, "c2", "ckout", "c2", true), workingResolves("repostate.8.3.ckout-inclusive-over-tag", ckoutTag, "c2", "ckout", "c2", true), // The keyword is the whole name only: the retry without the trailing // dot resolves "ckout" as a plain check-in request, so the tag. workingResolves("repostate.8.3.ckout-inclusive-whole-name", ckoutTag, "c2", "ckout.", "c1", false), // With no checkout version, ckout falls through to the other forms. workingResolves("repostate.8.3.ckout-no-version", ckoutTag, "", "ckout", "c1", false), workingResolves("repostate.8.3.ckout-no-version-untagged", twoChildren, "", "ckout", "", false), workingResolves("repostate.8.3.inclusive-current", twoChildren, "c2", "current", "c2", false), workingResolves("repostate.8.3.inclusive-checkin-only", noted, "c2", "note", "", false), // The untyped request reads ckout as an ordinary name, the tag. checkoutResolves("repostate.8.3.ckout-untyped-tag", "RS-8.3-4", ckoutTag, "c2", "ckout", "c1"), } } // workingResolves checks the checkout-inclusive request for name, with a // checkout on the artifact labelled at, or with no checkout version when at // is empty. It must name the check-in labelled want, or nothing when want is // empty, and say whether it is the working files. fossil(1) takes the request // as test-name-to-id --type ci+, which prints the checkout's hash for the // working files. func workingResolves(id string, arts []rsArt, at, name, want string, working bool) rsAPICase { if working && (want != at || at == "") { panic("conformance: the working files are on the checkout's version") } return rsAPICase{Case: Case{ID: id, Req: "RS-8.3-4"}, arts: arts, run: func(e *rsEnv) error { if e.ref != nil { if err := e.fossilWorking(at, name, want); err != nil { return err } } co, err := e.goCheckoutOrEmpty(at) if err != nil { return err } got, gotWorking, err := e.goResolveWorking(co, name) if err != nil { return err } if got != want || gotWorking != working { return expectf("with a checkout on %q, Checkout.ResolveWorking(%q) = %q "+ "(working %t), want %q (working %t)", at, name, got, gotWorking, want, working) } return nil }} } // fossilWorking checks fossil(1)'s answer to the inclusive request. fossil's // checkout opens on an empty repository, so it has no version until moved. func (e *rsEnv) fossilWorking(at, name, want string) error { if at != "" { if err := e.fossilCheckout(at); err != nil { return err } } out, err := e.fossilRun("test-name-to-id", "--type", "ci+", name) if err != nil { return err } _, result, ok := strings.Cut(strings.TrimSpace(out), " -> ") if !ok { return fmt.Errorf("test-name-to-id: unrecognized output %q", out) } got := "" if !strings.HasPrefix(result, "ERROR") { got = labelOf(result, e.script.labels) } if got != want { return expectf("fossil(1) with a checkout on %q resolves ci+ %q to %q; "+ "the case expects %q", at, name, got, want) } return nil } // goCheckoutOrEmpty is goCheckout on the artifact labelled at, or, when at // is empty, a checkout whose recorded version is blanked: go-libfossil opens // a checkout on a version, so none can be created without one. func (e *rsEnv) goCheckoutOrEmpty(at string) (*gofossil.Checkout, error) { if at != "" { return e.goCheckout(at) } co, err := e.goCheckout("c1") if err != nil { return nil, err } ck, err := sql.Open(db.RegisteredDriver().Name, filepath.Join(co.Dir(), ".fslckout")) if err != nil { return nil, err } defer ck.Close() _, err = ck.Exec("UPDATE vvar SET value = '' WHERE name = 'checkout'") return co, err } // goResolveWorking is the label and working flag co.ResolveWorking gives // name, or "" for an error that says the name is unknown or ambiguous. func (e *rsEnv) goResolveWorking(co *gofossil.Checkout, name string) (string, bool, error) { r, err := e.open() if err != nil { return "", false, err } v, err := co.ResolveWorking(name) if errors.Is(err, gofossil.ErrVersionNotFound) || errors.Is(err, gofossil.ErrAmbiguousVersion) { return "", false, nil } if err != nil { return "", false, err } return e.labelOfRid(r.Inner(), v.RID), v.Working, nil }