detached cea36cd

test(daemon): guard copy entry allocations

commit
cea36cdf62bce775442293a94f6df86e80af3296
author
brice · · 2026-08-04 01:30 UTC
parent
23e220c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +56 -3
A internal/usecase/daemon/copy_enter_allocation_budget_race_test.go +8 -0
@@ -0,0 +1,8 @@
1+//go:build race
2+
3+package daemon
4+
5+// Race instrumentation allocations are not part of the non-race benchmark's
6+// allocation baseline. TestCopyEnterAllocationBudget still exercises copy
7+// entry and validates its result under -race.
8+const copyEnterAllocationBudgetEnabled = false
@@ -0,0 +1,8 @@
1+//go:build race
2+
3+package daemon
4+
5+// Race instrumentation allocations are not part of the non-race benchmark's
6+// allocation baseline. TestCopyEnterAllocationBudget still exercises copy
7+// entry and validates its result under -race.
8+const copyEnterAllocationBudgetEnabled = false
A internal/usecase/daemon/copy_enter_allocation_budget_test.go +7 -0
@@ -0,0 +1,7 @@
1+//go:build !race
2+
3+package daemon
4+
5+// copyEnterAllocationBudgetEnabled keeps the allocation gate aligned with the
6+// non-race benchmark, whose allocation baseline defines the budget.
7+const copyEnterAllocationBudgetEnabled = true
@@ -0,0 +1,7 @@
1+//go:build !race
2+
3+package daemon
4+
5+// copyEnterAllocationBudgetEnabled keeps the allocation gate aligned with the
6+// non-race benchmark, whose allocation baseline defines the budget.
7+const copyEnterAllocationBudgetEnabled = true
M internal/usecase/daemon/performance_benchmark_test.go +41 -3
@@ -1247,9 +1247,41 @@ func TestLivePaintAllocationBudget(t *testing.T) {
12471247 }
12481248 }
12491249
1250-// TestCopyEnterAllocationBudget protects the parent baseline plus 10%. Copy
1251-// rendering borrows sealed VT history rows; allocating a copy per viewport row
1252-// is a production regression even though the capture itself remains immutable.
1250+// TestCopyEnterAllocationBudget protects the attachment-aware baseline plus
1251+// 10%. Copy rendering borrows sealed VT history rows; allocating a copy per
1252+// viewport row is a production regression even though capture is immutable.
1253+func TestCopyEnterAllocationBudget(t *testing.T) {
1254+ for _, tt := range []struct {
1255+ name string
1256+ tabs, panes, max int
1257+ }{
1258+ {name: "1tab-1pane", tabs: 1, panes: 1, max: 55},
1259+ {name: "1tab-4panes", tabs: 1, panes: 4, max: 55},
1260+ {name: "4tabs-1pane", tabs: 4, panes: 1, max: 60},
1261+ {name: "4tabs-4panes", tabs: 4, panes: 4, max: 60},
1262+ {name: "8tabs-1pane", tabs: 8, panes: 1, max: 68},
1263+ } {
1264+ t.Run(tt.name, func(t *testing.T) {
1265+ fixture := newPerformanceFixture(t, performanceConfig{size: domain.Size{Cols: 120, Rows: 40}, tabs: tt.tabs, panes: tt.panes, historyRows: 10_000})
1266+ run := func() {
1267+ fixture.d.enterCopyMode(fixture.sess, fixture.ac)
1268+ fixture.ac.ackOutputState(fixture.ac.output.next)
1269+ }
1270+
1271+ // Always exercise and validate copy entry. The race detector adds
1272+ // instrumentation allocations which are not reported by the parent
1273+ // benchmark, so its allocation count cannot represent this budget.
1274+ run()
1275+ require.True(t, fixture.copyModeActive(), "copy entry must install a history-backed mode")
1276+ if !copyEnterAllocationBudgetEnabled {
1277+ return
1278+ }
1279+
1280+ allocs := testing.AllocsPerRun(20, run)
1281+ require.LessOrEqual(t, allocs, float64(tt.max), "copy enter must stay within 10%% of the attachment-aware allocation baseline")
1282+ })
1283+ }
1284+}
12531285
12541286 func TestPerformanceFixturePaintLiveUsesPrecomputedAlternatingWrites(t *testing.T) {
12551287 fixture := newPerformanceFixture(t, performanceConfig{})
@@ -1600,6 +1632,12 @@ func (f *performanceFixture) searchMatches() int {
16001632 return len(f.ac.overlays.copyMode.Searches)
16011633 }
16021634
1635+func (f *performanceFixture) copyModeActive() bool {
1636+ f.ac.overlays.copyMu.Lock()
1637+ defer f.ac.overlays.copyMu.Unlock()
1638+ return f.ac.overlays.copyMode != nil && f.ac.overlays.copyDocument != nil && f.ac.overlays.copyDocument.Len() >= 10_000
1639+}
1640+
16031641 func (f *performanceFixture) resize() {
16041642 f.resizeTo(f.resizeSizes[f.resizes%len(f.resizeSizes)])
16051643 }
@@ -1247,9 +1247,41 @@ func TestLivePaintAllocationBudget(t *testing.T) {
1247 }1247 }
1248 }1248 }
1249 1249
1250-// TestCopyEnterAllocationBudget protects the parent baseline plus 10%. Copy1250+// TestCopyEnterAllocationBudget protects the attachment-aware baseline plus
1251-// rendering borrows sealed VT history rows; allocating a copy per viewport row1251+// 10%. Copy rendering borrows sealed VT history rows; allocating a copy per
1252-// is a production regression even though the capture itself remains immutable.1252+// viewport row is a production regression even though capture is immutable.
1253+func TestCopyEnterAllocationBudget(t *testing.T) {
1254+ for _, tt := range []struct {
1255+ name string
1256+ tabs, panes, max int
1257+ }{
1258+ {name: "1tab-1pane", tabs: 1, panes: 1, max: 55},
1259+ {name: "1tab-4panes", tabs: 1, panes: 4, max: 55},
1260+ {name: "4tabs-1pane", tabs: 4, panes: 1, max: 60},
1261+ {name: "4tabs-4panes", tabs: 4, panes: 4, max: 60},
1262+ {name: "8tabs-1pane", tabs: 8, panes: 1, max: 68},
1263+ } {
1264+ t.Run(tt.name, func(t *testing.T) {
1265+ fixture := newPerformanceFixture(t, performanceConfig{size: domain.Size{Cols: 120, Rows: 40}, tabs: tt.tabs, panes: tt.panes, historyRows: 10_000})
1266+ run := func() {
1267+ fixture.d.enterCopyMode(fixture.sess, fixture.ac)
1268+ fixture.ac.ackOutputState(fixture.ac.output.next)
1269+ }
1270+
1271+ // Always exercise and validate copy entry. The race detector adds
1272+ // instrumentation allocations which are not reported by the parent
1273+ // benchmark, so its allocation count cannot represent this budget.
1274+ run()
1275+ require.True(t, fixture.copyModeActive(), "copy entry must install a history-backed mode")
1276+ if !copyEnterAllocationBudgetEnabled {
1277+ return
1278+ }
1279+
1280+ allocs := testing.AllocsPerRun(20, run)
1281+ require.LessOrEqual(t, allocs, float64(tt.max), "copy enter must stay within 10%% of the attachment-aware allocation baseline")
1282+ })
1283+ }
1284+}
1253 1285
1254 func TestPerformanceFixturePaintLiveUsesPrecomputedAlternatingWrites(t *testing.T) {1286 func TestPerformanceFixturePaintLiveUsesPrecomputedAlternatingWrites(t *testing.T) {
1255 fixture := newPerformanceFixture(t, performanceConfig{})1287 fixture := newPerformanceFixture(t, performanceConfig{})
@@ -1600,6 +1632,12 @@ func (f *performanceFixture) searchMatches() int {
1600 return len(f.ac.overlays.copyMode.Searches)1632 return len(f.ac.overlays.copyMode.Searches)
1601 }1633 }
1602 1634
1635+func (f *performanceFixture) copyModeActive() bool {
1636+ f.ac.overlays.copyMu.Lock()
1637+ defer f.ac.overlays.copyMu.Unlock()
1638+ return f.ac.overlays.copyMode != nil && f.ac.overlays.copyDocument != nil && f.ac.overlays.copyDocument.Len() >= 10_000
1639+}
1640+
1603 func (f *performanceFixture) resize() {1641 func (f *performanceFixture) resize() {
1604 f.resizeTo(f.resizeSizes[f.resizes%len(f.resizeSizes)])1642 f.resizeTo(f.resizeSizes[f.resizes%len(f.resizeSizes)])
1605 }1643 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close