Artifact 471672e5958cae95789b84cc7c248e3441b60e2ed797dd12388050b893c2848c:
- File
internal/sync/sparse_rebuild_test.go
— part of check-in
[b5a64230b3]
at
2026-10-21 14:43:59
on branch feat/submanifest-reader
— feat(sparse): adopt sparse-cone at horizon-rfc a5741263ab
The draft now settles zit #257 to #260, so the four rows that waited on it close against code:
- Compaction and local filtering keep every manifest and sub-manifest node, interiors included (5.4, 6.3). RetainProvenance previously let an outside interior whose children were all held leaves go; the draft now requires it held, since it is the range proof that reaches the leaf. - A server answering a cone request acknowledges the reply with pragma cone-served in its leading pragma run (6.3). This server applies a request's whole cone or refuses it, and a refusal resets the reply, so the pragma is never sent for a refused or dense reply. SyncResult and CloneResult report ConeServed beside Elided, now public too. - An empty cone is refused (3.2); a new row and case pin it, locally and over the wire. - The per-round bound covers the tree walk only (6.2). A new row tests that a requester does not act on a dense inventory. It stays partial: an out-of-cone blob announced in the same reply as its check-in cannot be classified yet and is fetched, then filtered; zit #262 asks the draft to account for that cost. (user: [email protected], size: 6055) [annotate] [blame] [check-ins using] [more...]
package sync import ( "context" "path/filepath" "strings" "testing" "time" "github.com/danmestas/go-libfossil/internal/cone" "github.com/danmestas/go-libfossil/internal/content" "github.com/danmestas/go-libfossil/internal/deck" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/verify" "github.com/danmestas/go-libfossil/internal/xfer" ) func TestSparseReceiveProvenanceAfterRebuild(t *testing.T) { for _, ignorant := range []bool{false, true} { name := "aware" if ignorant { name = "ignorant" } t.Run(name, func(t *testing.T) { source := setupSyncTestRepo(t) put := func(d *deck.Deck) string { t.Helper() data, err := d.Marshal() if err != nil { t.Fatal(err) } return storeAndLink(t, source, data) } inside := storeAndLink(t, source, []byte("rebuild inside")) outside := storeAndLink(t, source, []byte("rebuild outside")) inLeaf := put(&deck.Deck{F: []deck.FileCard{{Name: "src/a", UUID: inside}}}) outLeaf := put(&deck.Deck{F: []deck.FileCard{{Name: "zzz/a", UUID: outside}}}) outNode := put(&deck.Deck{S: []deck.SubmanifestCard{{Name: "zzz/a", UUID: outLeaf}}}) missing := strings.Repeat("a", 40) missingChild := put(&deck.Deck{S: []deck.SubmanifestCard{{Name: "zzzz/a", UUID: missing}}}) missingParent := put(&deck.Deck{S: []deck.SubmanifestCard{{Name: "zzzz/a", UUID: missingChild}}}) root := put(&deck.Deck{C: "partial receive", D: time.Now().UTC(), U: deck.User("u"), S: []deck.SubmanifestCard{ {Name: "src/a", UUID: inLeaf}, {Name: "zzz/a", UUID: outNode}, {Name: "zzzz/a", UUID: missingParent}, }}) c, err := cone.New([]string{"src"}, nil) if err != nil { t.Fatal(err) } gimmes := map[string]bool{} transport := &MockTransport{Handler: func(req *xfer.Message) *xfer.Message { var cards []xfer.Card for _, card := range req.Cards { if g, ok := card.(*xfer.GimmeCard); ok { gimmes[g.UUID] = true } if p, ok := card.(*xfer.PragmaCard); ignorant && ok && (p.Name == "cone" || p.Name == "cone-not") { continue } cards = append(cards, card) } resp, err := HandleSync(context.Background(), source, &xfer.Message{Cards: cards}) if err != nil { t.Fatal(err) } return resp }} r, _, err := Clone(context.Background(), filepath.Join(t.TempDir(), "partial.fossil"), transport, CloneOpts{StoreCone: c, PullPhantoms: true}) if err != nil { t.Fatal(err) } defer r.Close() // Pull a new spine before clearing caches, exercising both receive paths. next := storeAndLink(t, source, []byte("rebuild next inside")) nextLeaf := put(&deck.Deck{F: []deck.FileCard{{Name: "src/a", UUID: next}}}) nextOutside := storeAndLink(t, source, []byte("rebuild next outside")) nextOutLeaf := put(&deck.Deck{F: []deck.FileCard{{Name: "zzz/b", UUID: nextOutside}}}) nextOutNode := put(&deck.Deck{S: []deck.SubmanifestCard{{Name: "zzz/a", UUID: outLeaf}, {Name: "zzz/b", UUID: nextOutLeaf}}}) put(&deck.Deck{C: "partial pull", D: time.Now().UTC(), U: deck.User("u"), P: []string{root}, S: []deck.SubmanifestCard{ {Name: "src/a", UUID: nextLeaf}, {Name: "zzz/a", UUID: nextOutNode}, {Name: "zzzz/a", UUID: missingParent}, }}) if res, err := Sync(context.Background(), r, transport, SyncOpts{Pull: true}); err != nil || len(res.Errors) != 0 { t.Fatalf("initial pull: %v, %v", res, err) } // A cone-ignorant server sends every node; sparse-cone ยง6.3 keeps // each one received, so only blobs are reclaimable there. reclaimable := []string{outside, nextOutside} if ignorant { for _, uuid := range []string{outLeaf, nextOutLeaf, outNode, nextOutNode, missingChild, missingParent} { if _, ok := content.AvailableByUUID(r.DB(), uuid); !ok { t.Fatalf("lost raw provenance node %s", uuid) } } } if !ignorant { reclaimable = append(reclaimable, outNode, nextOutNode) } for _, uuid := range reclaimable { if _, ok := content.AvailableByUUID(r.DB(), uuid); ok { t.Fatalf("retained reclaimable outside artifact %s", uuid) } } // A stale disposable link must not be needed to reconstruct elision. if _, err := r.DB().Exec("INSERT INTO mlink(mid,fid,fnid) VALUES(999999,0,0)"); err != nil { t.Fatal(err) } rebuilt, err := verify.Rebuild(r) if err != nil { t.Fatal(err) } if !rebuilt.OK() || rebuilt.Phantoms != 0 || rebuilt.Elided == 0 { t.Fatalf("rebuild report: %+v", rebuilt) } var stale int if err := r.DB().QueryRow("SELECT count(*) FROM mlink WHERE mid=999999").Scan(&stale); err != nil || stale != 0 { t.Fatalf("rebuild retained stale mlink: %d, %v", stale, err) } for round := 0; round < 2; round++ { gimmes = map[string]bool{} res, err := Sync(context.Background(), r, transport, SyncOpts{Pull: true}) if err != nil || len(res.Errors) != 0 { t.Fatalf("post-rebuild pull %d: %v, %v", round, res, err) } for _, uuid := range []string{outside, outLeaf, outNode, nextOutside, nextOutLeaf, nextOutNode, missing, missingChild, missingParent} { if gimmes[uuid] { t.Fatalf("requested outside artifact after rebuild: %s", uuid) } } a, err := content.LoadAbsencesContext(context.Background(), r.DB()) if err != nil { t.Fatal(err) } if a.Count(content.Transit) != 0 { t.Fatalf("post-rebuild transit phantoms=%d", a.Count(content.Transit)) } elided := []string{outside, nextOutside, missing} if !ignorant { elided = []string{outNode, nextOutNode, missingParent} } for _, uuid := range elided { var rid libfossil.FslID if err := r.DB().QueryRow("SELECT rid FROM blob WHERE uuid=?", uuid).Scan(&rid); err != nil { t.Fatal(err) } if a.Class(rid) != content.Elided { t.Fatalf("outside node %s is not elided", uuid) } } if _, ok := content.AvailableByUUID(r.DB(), next); !ok { t.Fatal("lost newly pulled covered payload") } } }) } }