1package daemon
2
3import (
4 "testing"
5
6 renderer "github.com/bnema/vev-vt"
7 "github.com/bnema/vev/internal/adapters/sessionwire"
8 "github.com/bnema/vev/internal/domain"
9 "github.com/bnema/vev/internal/protocol"
10 scopy "github.com/bnema/vev/internal/usecase/copy"
11 "github.com/stretchr/testify/require"
12)
13
14func TestCopyCompositionDoesNotAllocateIntermediateFrame(t *testing.T) {
15 rows := make([][]renderer.Cell, 53)
16 for y := range rows {
17 rows[y] = testRow("scrollback")
18 }
19 mode := scopy.NewMode(scopy.NewDocument(scopy.NewSnapshotFromRows(rows, 182, 53), ""))
20 frame := renderer.NewFrame(182, 55)
21 target := domain.Rect{Y: 1, Width: 182, Height: 53}
22 styles := resolveStyles(nil)
23 // A reusable semantic row and status text fit in 32 KiB. A second compact
24 // viewport alone needs over 150 KiB and must not be built just to copy it.
25 assertRenderByteBudget(t, func() {
26 composeCopyClientFrame(mode, target, frame, styles)
27 }, 32<<10)
28}
29
30// unmarshalBenchmarkOutput decodes one captured output envelope under a
31// benchmark harness, mirroring unmarshalTestOutput.
32func unmarshalBenchmarkOutput(tb testing.TB, payload []byte) protocol.Output {
33 tb.Helper()
34 message, err := sessionwire.DecodeServerEnvelope(payload)
35 require.NoError(tb, err)
36 output, ok := message.(protocol.Output)
37 require.True(tb, ok, "expected Output, got %T", message)
38 return output
39}
40
41// Each operation renders two wheel movements, up three rows then down three.
42// Keep history setup and copy entry outside the measured steady-state scroll.
43func BenchmarkDaemonHistoryCopyScroll(b *testing.B) {
44 for _, size := range []struct {
45 name string
46 size domain.Size
47 }{
48 {"120x40", domain.Size{Cols: 120, Rows: 40}},
49 {"182x53", domain.Size{Cols: 182, Rows: 53}},
50 {"240x70", domain.Size{Cols: 240, Rows: 70}},
51 } {
52 b.Run(size.name, func(b *testing.B) {
53 f := newPerformanceFixture(b, performanceConfig{size: size.size, tabs: 1, panes: 1, historyRows: 10000})
54 require.True(b, f.hasHistoryTopology(1, 1, 10000))
55 f.d.enterCopyMode(f.sess, f.ac)
56 f.d.copyWheel(f.sess, f.ac, -5000)
57 f.ac.ackOutputState(f.ac.output.currentEpoch(), f.ac.output.next)
58 b.ReportAllocs()
59 f.output.reset()
60 for b.Loop() {
61 f.d.copyWheel(f.sess, f.ac, -3)
62 f.ac.ackOutputState(f.ac.output.currentEpoch(), f.ac.output.next)
63 f.d.copyWheel(f.sess, f.ac, 3)
64 f.ac.ackOutputState(f.ac.output.currentEpoch(), f.ac.output.next)
65 }
66 metrics := f.output.metrics()
67 b.ReportMetric(float64(metrics.payloadBytes)/float64(2*b.N), "wire-B/wheel")
68 output := unmarshalBenchmarkOutput(b, f.output.lastPayload())
69 b.ReportMetric(float64(len(output.Data)), "last-ANSI-B/wheel")
70 })
71 }
72}