package beads
import (
"context"
"fmt"
"net/url"
"testing"
"sourcecraft.dev/bigbes/sr-ht-dolt/browse"
)
// --- what these measure -------------------------------------------------------
//
// The board and the detail pane are what a hosted Dolt database costs to look
// at. There is no SQL engine behind either: a bare NBS store has no working
// set, so every table is read whole and projected in process (see the package
// doc), and that projection is the per-request work — the row reads themselves
// are the store's, and are the same reads whatever the page does with them.
//
// So the benchmarks below drive Build through a session that hands the rows
// over already read. That is deliberately the seam the projections were given:
// what is timed is the bucketing, the dependency walk, the label join, the
// filter options and the sorts, and nothing about how fast a disk is that day.
//
// The corpus is benchIssues issues with a dependency edge for most of them,
// which is the size a real bd tracker on this instance reaches; Max is 2000, so
// the fixture sits inside the cap and no clip path is being measured instead of
// the projection.
// benchIssues is how many issues the synthetic tracker holds. Chosen to be a
// realistic large tracker while staying under Max (2000), so what is measured
// is the projection and not the truncation branch.
const benchIssues = 1200
// benchStatuses cycles the three status categories over the corpus so all four
// lanes are populated and statusCategory is exercised on both its custom-status
// hit and its heuristic fallback.
var benchStatuses = []string{"open", "in_progress", "closed", "blocked"}
// benchSession builds a fakeSession over a synthetic tracker: issues, the
// dependency edges between them, labels, custom statuses, comments and events.
// Every table the board and the detail pane read is present, so nothing is
// measured through the optional-table degradation path.
func benchSession() *fakeSession {
issues := &browse.RowPage{
Columns: []string{
"id", "title", "status", "priority", "issue_type", "assignee",
"created_at", "started_at", "updated_at", "closed_at", "close_reason",
"description", "design", "acceptance_criteria", "notes",
"created_by", "owner", "estimated_minutes", "external_ref", "spec_id",
"is_blocked",
},
}
for i := 0; i < benchIssues; i++ {
id := fmt.Sprintf("bench-%04d", i)
status := benchStatuses[i%len(benchStatuses)]
closedAt, closeReason := "", ""
if status == "closed" {
closedAt = fmt.Sprintf("2026-03-%02d", i%28+1)
closeReason = "resolved in review"
}
issues.Rows = append(issues.Rows, []string{
id,
fmt.Sprintf("Issue %d: the projection this benchmark measures", i),
status,
fmt.Sprint(i % 5),
[]string{"feature", "bug", "chore", "epic"}[i%4],
fmt.Sprintf("dev%d", i%7),
fmt.Sprintf("2026-01-%02d", i%28+1),
fmt.Sprintf("2026-02-%02d", i%28+1),
fmt.Sprintf("2026-02-%02d", i%28+1),
closedAt,
closeReason,
"A description long enough that copying it is not free, written the " +
"way an issue body is written and not as a placeholder token.",
"The design note, likewise.",
"Given a projection, when it runs, then it produces the same lanes.",
"Notes.",
fmt.Sprintf("dev%d", i%3),
fmt.Sprintf("dev%d", i%3),
"90",
"",
"",
fmt.Sprint(i % 9 / 8), // roughly one issue in nine carries the flag
})
}
issues.Total = len(issues.Rows)
// A chain of edges: every issue past the first depends on an earlier one, so
// the transitive walk in the detail pane has a real tree to descend and the
// blocked/blocking counts are non-trivial for most cards.
deps := &browse.RowPage{Columns: []string{"id", "issue_id", "depends_on_issue_id", "type"}}
for i := 1; i < benchIssues; i++ {
deps.Rows = append(deps.Rows, []string{
fmt.Sprintf("d-%04d", i),
fmt.Sprintf("bench-%04d", i),
fmt.Sprintf("bench-%04d", i/2), // a binary tree, so depth is log2(n)
[]string{"blocks", "related", "parent-child"}[i%3],
})
}
deps.Total = len(deps.Rows)
labels := &browse.RowPage{Columns: []string{"issue_id", "label"}}
for i := 0; i < benchIssues; i++ {
labels.Rows = append(labels.Rows,
[]string{fmt.Sprintf("bench-%04d", i), fmt.Sprintf("area-%d", i%11)},
[]string{fmt.Sprintf("bench-%04d", i), fmt.Sprintf("release-%d", i%3)},
)
}
labels.Total = len(labels.Rows)
statuses := &browse.RowPage{
Columns: []string{"name", "category"},
Rows: [][]string{
{"open", "open"},
{"in_progress", "in_progress"},
{"closed", "closed"},
// "blocked" is deliberately absent, so a quarter of the corpus falls
// through to the name heuristics — the branch a tracker with a status
// bd does not know about actually takes.
},
}
statuses.Total = len(statuses.Rows)
comments := &browse.RowPage{Columns: []string{"id", "issue_id", "author", "text", "created_at"}}
events := &browse.RowPage{Columns: []string{
"id", "issue_id", "actor", "event_type", "old_value", "new_value", "comment", "created_at",
}}
for i := 0; i < benchIssues; i++ {
id := fmt.Sprintf("bench-%04d", i)
comments.Rows = append(comments.Rows, []string{
fmt.Sprintf("c-%04d", i), id, fmt.Sprintf("dev%d", i%7),
"A comment on this issue, of the length a comment has.",
fmt.Sprintf("2026-02-%02d", i%28+1),
})
events.Rows = append(events.Rows, []string{
fmt.Sprintf("e-%04d", i), id, fmt.Sprintf("dev%d", i%7),
"status_changed", "open", "in_progress", "",
fmt.Sprintf("2026-02-%02d", i%28+1),
})
}
comments.Total = len(comments.Rows)
events.Total = len(events.Rows)
return &fakeSession{rowsByTable: map[string]*browse.RowPage{
"issues": issues,
"dependencies": deps,
"labels": labels,
"custom_statuses": statuses,
"comments": comments,
"events": events,
}}
}
// BenchmarkBoardBuild is the whole board: four lanes bucketed out of the issue
// rows, the dependency edges aggregated into blocked/blocking counts, the label
// join, the filter dropdown options and the per-lane sort.
func BenchmarkBoardBuild(b *testing.B) {
sess := benchSession()
ctx := context.Background()
b.ReportAllocs()
for b.Loop() {
data, err := Build(ctx, sess, "main", url.Values{})
if err != nil {
b.Fatalf("build: %v", err)
}
// Asserted rather than assumed: a projection that silently produced an
// empty board would otherwise be the fastest one here.
if data.Total != benchIssues {
b.Fatalf("board carried %d cards, want %d", data.Total, benchIssues)
}
}
}
// BenchmarkBoardBuildFiltered is the same board under the sticky filters, which
// is the request a reader who narrowed the board sends: every row is still read
// and still matched, and only the survivors are bucketed.
func BenchmarkBoardBuildFiltered(b *testing.B) {
sess := benchSession()
ctx := context.Background()
query := url.Values{
"q": {"projection"},
"type": {"bug"},
"label": {"area-3"},
"assignee": {"dev2"},
}
b.ReportAllocs()
for b.Loop() {
data, err := Build(ctx, sess, "main", query)
if err != nil {
b.Fatalf("build: %v", err)
}
if data.Total == 0 {
b.Fatal("the filtered board matched nothing; the filter is measuring an empty loop")
}
}
}
// BenchmarkDetailBuild is one issue's pane: the same four tables plus comments
// and events, the transitive dependency walk in both directions, the raw-row
// projection and the merged, time-ordered history.
func BenchmarkDetailBuild(b *testing.B) {
sess := benchSession()
ctx := context.Background()
// An issue deep in the chain, so the transitive walk has something to walk.
query := url.Values{"issue": {fmt.Sprintf("bench-%04d", benchIssues-1)}}
b.ReportAllocs()
for b.Loop() {
data, err := Build(ctx, sess, "main", query)
if err != nil {
b.Fatalf("build: %v", err)
}
if data.Issue == nil {
b.Fatal("the detail pane resolved no issue")
}
}
}