Artifact eac54edf9e4ea3d04f9f158a909a643b5d792312758fcbac2ca65136b59d5a06:
- File
conformance/sync_server_cases_test.go
— part of check-in
[80c72b27c7]
at
2026-10-11 13:57:34
on branch main
— test(conformance): sync-protocol conformance suite (#262) (#311)
* test(conformance): sync-protocol conformance suite (#262)
Checks go-libfossil against draft-fossil-sync-protocol-00, requirement by requirement: 149 requirements, 199 cases. Server cases send raw requests to Repo.XferHandler and, through `fossil http --in`, to fossil(1) on a copy of the same repository; client cases drive the public Sync and Clone against a scripted peer, with fossil(1)'s client run against the same script for most of them; HTTP cases cover redirects, status codes and media types; interop cases run whole exchanges in both directions.
Known failures are marked with the placeholder #0 until their issues are filed, so TestCoverage fails until then.
* test(conformance): sync suite review fixes (#262)
Cases for the requirements that had none where fossil(1) can show the behavior: server-version, the SHA3 gate, shun admission, cookie and cleartext logins, pushed configuration, ci-unlock, cluster brackets, the clone's unversioned catalog and the pull-only message. Two rows added: link ARG handling and the short uvigot.
A fossil(1) disagreement, a harness failure or a deviation other than the one named now fails a case whatever its mark. The server's log output is discarded in the tests.
* test(conformance): mark sync gaps with their issues; drop gaps v0.11.1 closed (#262)
v0.11.1's read gate and redirect policy make seven sync cases conform, so their gap marks are removed. The rest are marked with the issues that track each fix: #296 and #299-#310.
* test(conformance): accept either fossil refusal of a negative clone SEQNO
fossil 2.28's macOS build answers clone 3 -1 with clone_seqno 0 and its Linux build, which CI pins, with the invalid-sequence error alone. The draft leaves the reply open; the case now holds go-libfossil only to sending no content. (user: [email protected], size: 3828) [annotate] [blame] [check-ins using]
package conformance import ( "os" "strings" "testing" ) // syncServerCase is a request and what the draft says the server does with // it. The expectation is checked against fossil(1)'s reply on every run. type syncServerCase struct { Case // setup is SQL run on the copy of F before the request. setup []string method string path string ctype string // body is the request's card lines; see syncFixture.body. body []string // cookie, when set, is "USER PASSWORD": the request carries a login // cookie for that user over its body (Section 6.5) and no body login. cookie string want replyCheck // deviates, for a case marked Deviation, is what go-libfossil does // instead; a reply that is neither is fatal, as the mark excuses only // the deviation it names. deviates replyCheck // notSent says why the request is not sent to fossil(1) at all. notSent string } func (c syncServerCase) request(fx *syncFixture) syncRequest { req := syncRequest{method: c.method, path: c.path, ctype: c.ctype, text: fx.body(c.body...)} if c.cookie != "" { user, password, _ := strings.Cut(c.cookie, " ") login := loginLine(fx.vars["PC"], user, password, "", string(req.text)) req.cookie = "x-f-l-c=" + strings.ReplaceAll(login, " ", "+") } if req.method == "" { req.method = "POST" } if req.path == "" { req.path = "/" } if req.ctype == "" { req.ctype = typeCompressed } return req } // run sends the request to fossil(1), when present, then to go-libfossil, // each on its own copy of F. fatal is a fossil(1) disagreement or a // harness failure; problem is go-libfossil's departure from the case. func (c syncServerCase) run(fx *syncFixture, ref *fossilReference, dir string) (fatal, problem error) { req := c.request(fx) if ref != nil && c.notSent == "" { path, err := prepare(fx, dir, "fossil.fossil", c.setup) if err != nil { return err, nil } reply, err := ref.serve(fx, path, req) if err != nil { return err, nil } if err := fossilAgrees(c.want(reply), ""); err != nil { return err, nil } } path, err := prepare(fx, dir, "go.fossil", c.setup) if err != nil { return err, nil } reply, err := serveGo(fx, path, req) if err != nil { return nil, err } problem = c.want(reply) if problem != nil && c.deviates != nil { if err := c.deviates(reply); err != nil { return expectf("not the deviation the case names: %v", err), nil } } return nil, problem } // fossilAgrees turns fossil(1)'s verdict on a case into a fatal problem: // none when fossil does what the case expects, or when it does not and the // case says why. func fossilAgrees(problem error, differs string) error { switch { case differs == "" && problem != nil: return expectf("fossil(1) disagrees with the case: %v", problem) case differs != "" && problem == nil: return expectf("fossil(1) now does what the case expects; remove fossil: %q", differs) } return nil } func TestSyncServer(t *testing.T) { ref := newFossilReference(t) quietLogs(t) top := t.TempDir() fx := newSyncFixture(t, top) for _, c := range syncServerCases() { c.Case = markSync(c.Case) checkSync(t, c.Case, func() (error, error) { dir, err := os.MkdirTemp(top, "case") if err != nil { return err, nil } return c.run(fx, ref, dir) }) } } func syncServerCases() []syncServerCase { var all []syncServerCase for _, group := range [][]syncServerCase{ serverRoundCases(), serverTransportCases(), serverCardCases(), serverErrorCases(), serverAuthCases(), serverLoginCases(), serverContentCases(), serverUVCases(), serverUVGimmeCases(), serverCloneCases(), serverDirectionCases(), serverPragmaCases(), serverConfigCases(), serverVersionCases(), serverGateCases(), serverAdmissionCases(), serverPragmaMoreCases(), } { all = append(all, group...) } return all }