Artifact 7a8c829fc87f000459b9c9eef6065e48c9a57eb8548cbceba2c3c8ae482f6e6e:
- File
conformance/sync_client_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: 3130) [annotate] [blame] [check-ins using]
package conformance import ( "os" "testing" ) // syncClientCase is a scripted exchange and what the draft says a client // does in it. type syncClientCase struct { Case op clientOp setup []string // SQL on the client's copy of F // replies are the scripted replies' card lines; see syncFixture.body. replies [][]string // raw, when set, builds the reply entities instead, for cases about // framing. raw func(fx *syncFixture) [][]byte ctype string // the replies' media type; uncompressed unless set want clientCheck // fc also runs fossil(1)'s client against the script, whose requests // must meet want. fc bool // fossil is set only when fossil(1)'s client does not do what the // case expects, and says why the case follows the draft. fossil string // deviates, for a case marked Deviation, is what go-libfossil does // instead; a run that is neither is fatal. deviates clientCheck } func (c syncClientCase) peer(fx *syncFixture) *scriptedPeer { p := &scriptedPeer{fx: fx, ctype: c.ctype} if p.ctype == "" { p.ctype = typeUncompressed } if c.raw != nil { p.replies = c.raw(fx) return p } for _, reply := range c.replies { p.replies = append(p.replies, fx.body(reply...)) } return p } // run runs fossil(1)'s client, for an fc case, then go-libfossil's. fatal // is a fossil(1) disagreement or a harness failure; problem is // go-libfossil's departure from the case. func (c syncClientCase) run(fx *syncFixture, ref *fossilReference, dir string) (fatal, problem error) { if ref != nil && c.fc { run, err := ref.runFossilClient(fx, c.op, c.setup, c.peer(fx), dir) if err != nil { return err, nil } if err := fossilAgrees(c.want(run), c.fossil); err != nil { return expectf("%v (fossil output %.300q)", err, run.output), nil } } run, err := runGoClient(fx, c.op, c.setup, c.peer(fx), dir) if err != nil { return nil, err } problem = c.want(run) if problem != nil && c.deviates != nil { if err := c.deviates(run); err != nil { return expectf("not the deviation the case names: %v", err), nil } } return nil, problem } func TestSyncClient(t *testing.T) { ref := newFossilReference(t) top := t.TempDir() fx := newSyncFixture(t, top) for _, c := range syncClientCases() { 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 syncClientCases() []syncClientCase { var all []syncClientCase for _, group := range [][]syncClientCase{ clientRoundCases(), clientPhantomCases(), clientGimmeCases(), clientTerminationCases(), clientFramingCases(), clientPayloadCases(), clientCardCases(), clientAuthCases(), clientFileCases(), clientCFileCases(), clientUVFileCases(), clientDeltaCases(), clientPrivateCases(), clientCloneCases(), clientCloneStallCases(), clientCloneErrorCases(), clientUVCases(), clientUVStatusCases(), clientMessageCases(), clientGateCases(), clientAdmissionCases(), clientPullOnlyCases(), clientShortUVIGotCases(), } { all = append(all, group...) } return all }