Artifact 23cdabea35d40b487456ad1045499f09d7553bf2e7e4ad57c0eebe98c2dc6502:
- File
conformance/sync_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: 3084) [annotate] [blame] [check-ins using]
package conformance import ( "fmt" "io" "log/slog" "testing" ) // The sync suite checks draft-fossil-sync-protocol-00. It drives the real // code end to end, with nothing mocked but the far end of the wire: // // - a server case is raw request bytes sent to Repo.XferHandler, and the // same request sent to fossil(1) through `fossil http`, each on its own // copy of the fixture repository; // - a client case is a scripted peer answering the public Sync and Clone // through a FramedTransport; for the cases marked fc, fossil(1)'s // client talks to the same script over HTTP; // - an HTTP case is a scripted httptest server, for redirects, status // codes and media types; // - an interop case is a whole session between go-libfossil and fossil(1). // // Every expectation is checked against fossil(1) on every run where it can // be, so a case cannot drift from fossil unnoticed, and a disagreement is // fatal whatever the case's mark; a case that knowingly differs from // fossil, or cannot be sent to it, says why in its fossil or notSent // field. Each case's input // differs from an exchange another case shows to be handled normally in // one respect, and each refusal names its reason, so a case fails when // something other than its rule decides the outcome. // checkSync runs one sync case as a subtest. run returns two problems: // fatal, which fails the case whatever its mark (fossil(1) disagreeing // with the case, a deviation other than the one named, or the harness // itself failing), and conforms, go-libfossil's own departure from the // case, which the mark judges as check does. func checkSync(t *testing.T, c Case, run func() (fatal, conforms error)) { t.Helper() t.Run(c.ID, func(t *testing.T) { t.Helper() fatal, conforms := run() if fatal != nil { t.Fatalf("%s [%s]: %v", c.ID, c.Req, fatal) } result, message := judge(c, conforms) switch result { case outcomePass: case outcomeFail: t.Fatal(message) case outcomeSkip: t.Skip(message) default: panic(fmt.Sprintf("conformance: unknown outcome %d", result)) } }) } // quietLogs discards the slog output of go-libfossil's server for the rest // of a test: it logs every request it refuses, and many cases are refused // requests. func quietLogs(t *testing.T) { t.Helper() previous := slog.Default() slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil))) t.Cleanup(func() { slog.SetDefault(previous) }) } // markSync sets a case's Gap or Deviation from the suite's maps. func markSync(c Case) Case { c.Gap = syncGaps[c.ID] c.Deviation = syncDeviations[c.ID] return c } // syncSuiteCases is what the coverage test audits for this suite. func syncSuiteCases() []Case { var all []Case for _, c := range syncServerCases() { all = append(all, markSync(c.Case)) } for _, c := range syncClientCases() { all = append(all, markSync(c.Case)) } for _, c := range syncHTTPCases() { all = append(all, markSync(c.Case)) } for _, c := range syncInteropCases() { all = append(all, markSync(c.Case)) } return all }