v0.1.0 7deb448

benchmark_test.go RawHistoryFolder

1package vt
2
3import (
4	"bytes"
5	"testing"
6
7	"github.com/bnema/vev/pkg/renderer"
8)
9
10var (
11	benchmarkHistoryViewSink     HistoryView
12	benchmarkHistorySnapshotSink HistorySnapshotView
13	benchmarkHistoryBlobSink     []byte
14	benchmarkHistoryBlobsSink    [][]byte
15)
16
17// TestHistoryViewPartialTailAllocationBounded guards the capture property used
18// by persistence: retained sealed chunks are shared, while only the small
19// mutable tail is copied into a view.
20func TestHistoryViewPartialTailAllocationBounded(t *testing.T) {
21	const (
22		width     = 120
23		chunkRows = 64
24		tailRows  = 7
25	)
26	sealed := benchmarkHistory(t, chunkRows*4, width, chunkRows)
27	sealed.SealAndView()
28	partial := benchmarkHistory(t, chunkRows*4+tailRows, width, chunkRows)
29	partialBefore := partial.View()
30
31	// Copying a partial tail costs one allocation per row plus a fixed set of
32	// headers: the chunk slice, its growth for the tail chunk, the chunk itself,
33	// the row slice, and the bounds slice parallel to it.
34	const tailHeaders = 4
35
36	sealedAllocs := testing.AllocsPerRun(20, func() { benchmarkHistoryViewSink = sealed.View() })
37	partialAllocs := testing.AllocsPerRun(20, func() { benchmarkHistoryViewSink = partial.View() })
38	if partialAllocs > sealedAllocs+tailRows+tailHeaders {
39		t.Fatalf("partial-tail View allocations = %v, sealed=%v, tail rows=%d; sealed history was copied", partialAllocs, sealedAllocs, tailRows)
40	}
41	if partial.View().Chunk(0) != partialBefore.Chunk(0) {
42		t.Fatal("unchanged sealed chunks must retain identity across partial-tail views")
43	}
44}
45
46func BenchmarkHistoryView10KRowsSealedReuse(b *testing.B) {
47	history := benchmarkHistory(b, 10_000, 120, 256)
48	sealed := history.SealAndView()
49	if sealed.Len() != 10_000 || sealed.ChunkCount() != 40 || sealed.Chunk(0) == nil {
50		b.Fatalf("invalid sealed history fixture: rows=%d chunks=%d", sealed.Len(), sealed.ChunkCount())
51	}
52
53	b.ReportAllocs()
54	b.ResetTimer()
55	for b.Loop() {
56		benchmarkHistoryViewSink = history.View()
57	}
58	b.StopTimer()
59	if got := benchmarkHistoryViewSink; got.Len() != sealed.Len() || got.Chunk(0) != sealed.Chunk(0) {
60		b.Fatal("unchanged sealed history chunks were not reused")
61	}
62	b.ReportMetric(float64(sealed.ChunkCount()), "sealedchunks/view")
63}
64
65func BenchmarkHistoryView10KRowsPartialTail(b *testing.B) {
66	history := benchmarkHistory(b, 10_000, 120, 256)
67	before := history.View()
68	if before.Len() != 10_000 || before.ChunkCount() != 40 || before.Chunk(0) == nil {
69		b.Fatalf("invalid partial-tail history fixture: rows=%d chunks=%d", before.Len(), before.ChunkCount())
70	}
71
72	b.ReportAllocs()
73	b.ResetTimer()
74	for b.Loop() {
75		benchmarkHistoryViewSink = history.View()
76	}
77	b.StopTimer()
78	if got := benchmarkHistoryViewSink; got.Len() != before.Len() || got.Chunk(0) != before.Chunk(0) {
79		b.Fatal("partial-tail capture did not reuse its sealed chunks")
80	}
81	b.ReportMetric(float64(10_000%256), "tailrows/view")
82}
83
84func BenchmarkHistorySnapshotView10KRowsPartialTail(b *testing.B) {
85	history := benchmarkHistory(b, 10_000, 120, 256)
86	if got, want := len(history.tail), 16; got != want {
87		b.Fatalf("partial tail rows = %d, want %d", got, want)
88	}
89	sealed := history.View().Chunk(0)
90
91	b.ReportAllocs()
92	b.ResetTimer()
93	for b.Loop() {
94		benchmarkHistorySnapshotSink = history.SnapshotView()
95	}
96	b.StopTimer()
97	if got := benchmarkHistorySnapshotSink; got.Chunk(0) != sealed || got.Tail().Len() != 16 {
98		b.Fatal("snapshot did not preserve chunk identity and tail rows")
99	}
100	b.ReportMetric(float64(benchmarkHistorySnapshotSink.ChunkCount()), "sealedchunks/view")
101}
102
103func BenchmarkMarshalHistoryChunk256x120(b *testing.B) {
104	history := benchmarkHistory(b, 256, 120, 256)
105	chunk := history.SealAndView().Chunk(0)
106	if chunk == nil {
107		b.Fatal("invalid history chunk fixture")
108	}
109
110	b.ReportAllocs()
111	b.ResetTimer()
112	for b.Loop() {
113		var err error
114		benchmarkHistoryBlobSink, err = MarshalHistoryChunk(chunk)
115		if err != nil {
116			b.Fatal(err)
117		}
118	}
119	b.StopTimer()
120	if _, err := PreflightHistoryBlob(benchmarkHistoryBlobSink); err != nil {
121		b.Fatalf("encoded history chunk is invalid: %v", err)
122	}
123	b.SetBytes(int64(len(benchmarkHistoryBlobSink)))
124}
125
126func BenchmarkUnmarshalHistoryChunk256x120(b *testing.B) {
127	history := benchmarkHistory(b, 256, 120, 256)
128	blob, err := MarshalHistoryChunk(history.SealAndView().Chunk(0))
129	if err != nil {
130		b.Fatal(err)
131	}
132	if stats, err := PreflightHistoryBlob(blob); err != nil || stats.Rows != 256 || stats.Cells != 256*120 {
133		b.Fatalf("invalid encoded history chunk fixture: stats=%+v err=%v", stats, err)
134	}
135
136	b.ReportAllocs()
137	b.SetBytes(int64(len(blob)))
138	b.ResetTimer()
139	for b.Loop() {
140		var err error
141		benchmarkHistoryViewSink, err = UnmarshalHistory(blob)
142		if err != nil {
143			b.Fatal(err)
144		}
145	}
146	b.StopTimer()
147	if benchmarkHistoryViewSink.Len() != 256 || benchmarkHistoryViewSink.ChunkCount() != 1 {
148		b.Fatal("decoded history chunk lost rows")
149	}
150}
151
152func BenchmarkMarshalSealedHistory10KRows(b *testing.B) {
153	history := benchmarkHistory(b, 10_000, 120, 256)
154	view := history.SealAndView()
155	if view.Len() != 10_000 || view.ChunkCount() != 40 {
156		b.Fatalf("invalid sealed history fixture: rows=%d chunks=%d", view.Len(), view.ChunkCount())
157	}
158
159	b.ReportAllocs()
160	b.ResetTimer()
161	for b.Loop() {
162		var err error
163		benchmarkHistoryBlobsSink, benchmarkHistoryBlobSink, err = MarshalSealedHistory(view)
164		if err != nil {
165			b.Fatal(err)
166		}
167	}
168	b.StopTimer()
169	if len(benchmarkHistoryBlobsSink) != view.ChunkCount() || len(benchmarkHistoryBlobSink) == 0 {
170		b.Fatal("sealed-history encoder produced an incomplete capture")
171	}
172	b.ReportMetric(float64(len(benchmarkHistoryBlobsSink)), "sealedchunks/op")
173}
174
175func benchmarkHistory(t testing.TB, rows, width, chunkRows int) *History {
176	t.Helper()
177	history := NewHistory(HistoryConfig{MaxRows: rows, ChunkRows: chunkRows})
178	for row := range rows {
179		cells := make([]renderer.Cell, width)
180		for col := range cells {
181			cells[col] = renderer.Cell{Rune: rune('a' + (row+col)%26)}
182		}
183		requireHistoryAppend(t, history, cells)
184	}
185	return history
186}
187
188// BenchmarkScreenResizeReflowViewport verifies resize only visits the live
189// viewport. The 10K-history variant must track the control rather than history
190// length; history is deliberately populated without changing the viewport.
191func BenchmarkScreenResizeReflowViewport(b *testing.B) {
192	b.Run("control", func(b *testing.B) { benchmarkScreenResizeReflow(b, 0) })
193	b.Run("10k-history", func(b *testing.B) { benchmarkScreenResizeReflow(b, 10_000) })
194}
195
196func benchmarkScreenResizeReflow(b *testing.B, historyRows int) {
197	b.Helper()
198	s := NewScreenWithHistory(120, 40, HistoryConfig{MaxRows: historyRows + 500})
199	if historyRows > 0 {
200		for range historyRows {
201			requireHistoryAppend(b, s.history, make([]renderer.Cell, 120))
202		}
203	}
204	s.Write(bytes.Repeat([]byte("x"), 120*39))
205	s.ClearDamage()
206	sizes := [2][2]int{{80, 40}, {120, 40}}
207
208	b.ReportAllocs()
209	b.ResetTimer()
210	for i := 0; b.Loop(); i++ {
211		s.Resize(sizes[i%len(sizes)][0], sizes[i%len(sizes)][1])
212		s.ClearDamage()
213	}
214	b.StopTimer()
215	if got := s.History(); historyRows > 0 && got.Len() < historyRows {
216		b.Fatalf("resize discarded retained history rows: got %d want at least %d", got.Len(), historyRows)
217	}
218}
219
220func BenchmarkScreenShellRedrawBurst(b *testing.B) {
221	chunk := []byte("\r\x1b[K❯ abc\x1b[90m autosuggestion\x1b[39m\r\x1b[5C")
222	b.ReportAllocs()
223	for b.Loop() {
224		s := NewScreen(120, 40)
225		for range 200 {
226			s.Write(chunk)
227			s.ClearDamage()
228		}
229	}
230}
231
232func BenchmarkScreenFullscreenScrollRegion(b *testing.B) {
233	chunk := []byte("\x1b[2;39r\x1b[39;1Hline\n")
234	b.ReportAllocs()
235	for b.Loop() {
236		s := NewScreen(120, 40)
237		for range 500 {
238			s.Write(chunk)
239			s.ClearDamage()
240		}
241	}
242}

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 12

Tags 11

↑↓ moveenter openesc close