Artifact 382c8457b1c08dbd479cafc929030ed06aae397851c55fd1f4e9c0506cc325dd:
- File
conformance/sync_client_cases_test.go
— part of check-in
[636e873e6a]
at
2026-10-13 07:32:56
on branch main
— fix(sync): stop the client reply loop at a card with no artifact name (#404)
* fix(sync): stop the client reply loop at a gimme or igot with no artifact name
fossil's client reads gimme and igot only with blob_is_hname; any other line is an unknown command that ends the reply-card loop, as sync-protocol-01 Section 5.2 requires of a malformed defined card. A server case pins the existing bad-command answer to such a gimme.
Tracked in #397.
* fix(sync): stop the client reply loop at a file or cfile with no artifact name
Tracked in #397.
* test(conformance): split the case tables this round grew past 70 lines
Tracked in #397. Each half keeps its cases in order; only the table functions move. (user: [email protected], size: 3225) [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(), clientCardNameCases(), clientAuthCases(), clientFileCases(), clientCFileCases(), clientUVFileCases(), clientDeltaCases(), clientPrivateCases(), clientCloneCases(), clientCloneStallCases(), clientCloneErrorCases(), clientUVCases(), clientUVStatusCases(), clientMessageCases(), clientGateCases(), clientAdmissionCases(), clientPullOnlyCases(), clientShortUVIGotCases(), clientRefusalCases(), clientCloneBadCardCases(), clientLinkCases(), } { all = append(all, group...) } return all }