test(daemon): guard copy entry allocations
- commit
- cea36cdf62bce775442293a94f6df86e80af3296
- author
- brice · · 2026-08-04 01:30 UTC
- parent
- 23e220c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) { | |||
| 1247 | 1247 | } | |
| 1248 | 1248 | } | |
| 1249 | 1249 | ||
| 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 | + | } | |
| 1253 | 1285 | ||
| 1254 | 1286 | func TestPerformanceFixturePaintLiveUsesPrecomputedAlternatingWrites(t *testing.T) { | |
| 1255 | 1287 | fixture := newPerformanceFixture(t, performanceConfig{}) | |
| @@ -1600,6 +1632,12 @@ func (f *performanceFixture) searchMatches() int { | |||
| 1600 | 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 | 1641 | func (f *performanceFixture) resize() { | |
| 1604 | 1642 | f.resizeTo(f.resizeSizes[f.resizes%len(f.resizeSizes)]) | |
| 1605 | 1643 | } | |
| @@ -1247,9 +1247,41 @@ func TestLivePaintAllocationBudget(t *testing.T) { | |||||
| 1247 | } | 1247 | } | ||
| 1248 | } | 1248 | } | ||
| 1249 | 1249 | ||||
| 1250 | - | // TestCopyEnterAllocationBudget protects the parent baseline plus 10%. Copy | 1250 | + | // TestCopyEnterAllocationBudget protects the attachment-aware baseline plus |
| 1251 | - | // rendering borrows sealed VT history rows; allocating a copy per viewport row | 1251 | + | // 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 | } | ||