Artifact bcca6fbb8308dc8f7f1ca4605bc894a90f9f051f5b152edbdcbde69367e238c3:
- File
conformance/horizon_cluster_zit_test.go
— part of check-in
[5768a5342d]
at
2026-10-23 02:13:23
on branch feat/horizon-cones
— fix(sync): withhold a cluster a horizon request names by gimme
An origin answering a horizon-scoped request already announced no cluster in its clone, pull or sync inventory and answered req-clusters with nothing, but a gimme naming a cluster still got the cluster's file card. A horizon clone that learned a cluster's name anywhere could so still be sent one, against draft-fossil-horizon-00 §4.5 and §6.2.
The handler now drops a cluster from a gimme's answer when the request carries a horizon pragma. A new conformance case records the origin's replies to a horizon clone, pull and sync across freshly minted clusters, with and without a cursor, and to every gimme and req-clusters shape, and checks no cluster name crosses; an interop test checks zit's origin the same way. (user: [email protected], size: 4322) [annotate] [blame] [check-ins using] [more...]
package conformance import ( "context" "net/http/httptest" "os" "path/filepath" "testing" "github.com/danmestas/go-libfossil/internal/content" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/sync" "github.com/danmestas/go-libfossil/internal/xfer" ) // TestZitHorizonOriginClustersInterop runs a horizon clone, pull and sync // of go-libfossil against `zit serve` over a store holding clusters, minted // before zit's copy was cloned and after (draft-fossil-horizon-00 §4.1, // §4.5, §6.2), and records whether zit's replies name any cluster. However // zit answers, the horizon store ends holding no cluster. func TestZitHorizonOriginClustersInterop(t *testing.T) { bin := zitHorizonBinary(t) dir := t.TempDir() home := filepath.Join(dir, "home") if err := os.Mkdir(home, 0o755); err != nil { t.Fatal(err) } g := newHZSyncGraph(t) hzClustered(t, g.f.r, "old", content.ClusterThreshold) server := httptest.NewServer(&hzRecorder{r: g.f.r}) defer server.Close() zd := filepath.Join(dir, "zdense") zitRun(t, bin, home, dir, "clone", "--no-open", server.URL, zd) zitURL := zitServe(t, bin, home, zd) peer := &hzRecordingTransport{inner: &sync.HTTPTransport{URL: zitURL}} sent := &hzSentTransport{inner: peer} r, _, err := sync.Clone(context.Background(), filepath.Join(dir, "horizon.fossil"), sent, sync.CloneOpts{StoreCone: g.c, PullPhantoms: true, Origin: zitURL}) if err != nil { t.Fatalf("go-libfossil's horizon clone from zit: %v", err) } defer r.Close() parent := hzCommit(t, g.f, "c5", []libfossil.FslID{g.m}, nil) hzUnclustered(t, g.f.r, "new", content.ClusterThreshold) hzCommit(t, g.f, "c6", []libfossil.FslID{parent}, nil) zitRun(t, bin, home, zd, "pull", server.URL) for _, opts := range []sync.SyncOpts{{Pull: true}, {Pull: true, Push: true}, {Pull: true}} { opts.Origin = zitURL if _, err := sync.Sync(context.Background(), r, sent, opts); err != nil { t.Fatalf("go-libfossil's sync with zit: %v", err) } } dense, _, err := sync.Clone(context.Background(), filepath.Join(dir, "dense.fossil"), &sync.HTTPTransport{URL: zitURL}, sync.CloneOpts{}) if err != nil { t.Fatalf("go-libfossil's dense clone from zit: %v", err) } defer dense.Close() clusters, err := hzClusterNames(dense) if err != nil { t.Fatal(err) } if len(clusters) == 0 { t.Fatal("fixture: zit's store holds no cluster") } if err := hzCrossed(peer.replies, clusters); err != nil { t.Fatalf("zit's replies to go-libfossil's horizon clone, pull and sync: %v", err) } if err := hzNoGimme(sent.requests, clusters); err != nil { t.Fatalf("go-libfossil's horizon store asked zit: %v", err) } if hzReqClusters(sent.requests) { t.Fatal("go-libfossil's horizon store asked zit for the cluster catalog") } if err := hzHeld(r, clusters, false); err != nil { t.Fatalf("go-libfossil's horizon store from zit: %v", err) } // Named outright, a cluster is withheld from a horizon gimme. zit still // answers req-clusters under a horizon pragma with the catalog, which // go-libfossil's handler does not; a store served at its horizon never // sends one (horizon.4.5.served-no-cluster-chase), so it is logged. code, err := g.f.r.Config("project-code") if err != nil { t.Fatal(err) } h, _ := g.c.Horizon() horizon := &xfer.PragmaCard{Name: "horizon", Values: []string{h.Hash(), h.Rule().String()}} gimmes := []xfer.Card{horizon} for _, uuid := range scSorted(clusters) { gimmes = append(gimmes, &xfer.GimmeCard{UUID: uuid}) } ask := func(extra ...xfer.Card) error { cards := append([]xfer.Card{&xfer.PragmaCard{Name: "client-version", Values: []string{"22800"}}, &xfer.PullCard{ServerCode: "x", ProjectCode: code}}, extra...) resp, err := (&sync.HTTPTransport{URL: zitURL}).Exchange(context.Background(), &xfer.Message{Cards: cards}) if err != nil { t.Fatal(err) } return hzCrossed([]*xfer.Message{resp}, clusters) } if ask(gimmes[1:]...) == nil { t.Fatal("fixture: zit sent no cluster to a dense gimme naming it") } if err := ask(gimmes...); err != nil { t.Fatalf("zit's reply to a horizon gimme naming its clusters: %v", err) } if err := ask(horizon, &xfer.PragmaCard{Name: "req-clusters"}); err != nil { t.Logf("zit's reply to req-clusters under a horizon: %v", err) } }