Artifact 58d7ed88d1cc3d0165d05eea075332a61cbbd054af86422c206e7ddcc1810ce3:
- File
internal/index/summary_test.go
— part of check-in
[c5d8786875]
at
2026-08-25 22:13:22
on branch fleet-census
— Fleet home: stop paying for the numbers a row does not show
The fleet listing called Counts, the per-section census the repository nav needs: eight aggregates per store, of which a fleet row displays two. A fold invalidates the memo, so during indexing every row went cold at once.
Cold, on tcl (668MB, 42k check-ins): 230ms of census per repository against 80ms for the two numbers. Contributors now reads the maintained contributor table rather than a DISTINCT scan over every event — verified identical across all 25 demo indexes. (user: dmestas, size: 4554) [annotate] [blame] [check-ins using] [more...]
package index import ( "testing" "craftdesign.group/quarry/internal/artifact" ) func checkinBy(hash, user, date string) Parsed { return Parsed{Hash: hash, Art: &artifact.Artifact{ Type: artifact.Manifest, Date: date, User: user, Comment: "c " + hash, }} } // Summary is a narrower answer to a question Counts also answers, so // the two must never disagree — a fleet row and the repository page it // links to show the same numbers side by side. func TestSummaryAgreesWithTheCensus(t *testing.T) { ix := openIndex(t) if err := ix.FoldNow([]Parsed{ checkinBy("c1", "dan", "2026-01-01T00:00:00"), checkinBy("c2", "eve", "2026-01-02T00:00:00"), checkinBy("c3", "dan", "2026-01-03T00:00:00"), wikiEdit("w1", "Home", "2026-01-04T00:00:00"), }); err != nil { t.Fatal(err) } sm, err := ix.Summary() if err != nil { t.Fatal(err) } c, err := ix.Counts(false) if err != nil { t.Fatal(err) } if sm.Checkins != c.Checkins || sm.Contributors != c.Contributors { t.Fatalf("summary %+v disagrees with census checkins=%d contributors=%d", sm, c.Checkins, c.Contributors) } if sm.Checkins != 3 { t.Errorf("checkins=%d, want 3", sm.Checkins) } // dan, eve, and the wiki edit's author — distinct authors of // anything, which is what the fleet row means by "contributors". if sm.Contributors != 2 { t.Errorf("contributors=%d, want 2 (dan, eve)", sm.Contributors) } } // Contributors now reads the maintained contributor table instead of a // DISTINCT scan over every event. Same answer or the change is a bug, // and the fold triggers are what has to keep it true. func TestContributorTableMatchesDistinctAuthors(t *testing.T) { ix := openIndex(t) if err := ix.FoldNow([]Parsed{ checkinBy("c1", "dan", "2026-01-01T00:00:00"), checkinBy("c2", "eve", "2026-01-02T00:00:00"), checkinBy("c3", "dan", "2026-01-03T00:00:00"), wikiEdit("w1", "Home", "2026-01-04T00:00:00"), }); err != nil { t.Fatal(err) } var distinct int if err := ix.db.QueryRow(`SELECT count(DISTINCT user) FROM event`).Scan(&distinct); err != nil { t.Fatal(err) } c, err := ix.Counts(false) if err != nil { t.Fatal(err) } if c.Contributors != distinct { t.Fatalf("census contributors=%d, DISTINCT user over events=%d", c.Contributors, distinct) } } // The memo exists because the fleet home renders one row per // repository; the fold that invalidates it is the same fold that makes // it wrong. A fleet row that lags a push is the small lie the census // memo was already careful about. func TestSummaryRefreshesAfterAFold(t *testing.T) { ix := openIndex(t) if sm, err := ix.Summary(); err != nil || sm.Checkins != 0 { t.Fatalf("empty index: %+v err=%v", sm, err) } if err := ix.FoldNow([]Parsed{checkinBy("c1", "dan", "2026-01-01T00:00:00")}); err != nil { t.Fatal(err) } sm, err := ix.Summary() if err != nil { t.Fatal(err) } if sm.Checkins != 1 || sm.Contributors != 1 { t.Fatalf("after fold %+v, want 1/1 — the memo went stale", sm) } if err := ix.FoldNow([]Parsed{checkinBy("c2", "eve", "2026-01-02T00:00:00")}); err != nil { t.Fatal(err) } if sm, _ = ix.Summary(); sm.Checkins != 2 || sm.Contributors != 2 { t.Fatalf("after second fold %+v, want 2/2", sm) } // Reset is the reindex path; neither memo may survive it. if err := ix.Reset(); err != nil { t.Fatal(err) } if sm, _ = ix.Summary(); sm.Checkins != 0 { t.Fatalf("summary survived a reset: %+v", sm) } } // A warm census must satisfy Summary without touching the database // again — the repository page and the fleet row ask for overlapping // work and whichever arrives first should pay for both. func TestSummaryIsServedFromAWarmCensus(t *testing.T) { ix := openIndex(t) if err := ix.FoldNow([]Parsed{ checkinBy("c1", "dan", "2026-01-01T00:00:00"), checkinBy("c2", "eve", "2026-01-02T00:00:00"), }); err != nil { t.Fatal(err) } if _, err := ix.Counts(false); err != nil { t.Fatal(err) } // With the census warm and the narrow memo still empty, an answer // can only have come from the census. ix.countMu.Lock() warm, narrow := ix.counts != nil, ix.summary != nil ix.countMu.Unlock() if !warm || narrow { t.Fatalf("precondition: census warm=%v narrow memo=%v", warm, narrow) } sm, err := ix.Summary() if err != nil { t.Fatal(err) } if sm.Checkins != 2 || sm.Contributors != 2 { t.Fatalf("%+v, want 2/2", sm) } ix.countMu.Lock() filled := ix.summary != nil ix.countMu.Unlock() if filled { t.Error("Summary queried and memoised separately although the census was already warm") } }