v0.6.0 b250c5e

history_bytes_test.go RawHistoryFolder

1package vt
2
3import (
4	"errors"
5	"testing"
6
7	renderer "github.com/bnema/vev-vt/core"
8)
9
10func TestHistoryBoundsRetainedLogicalBytes(t *testing.T) {
11	const budget = 2*(2*renderer.StoredCellLogicalBytes+renderer.RowDescriptorLogicalBytes) + renderer.StyleRecordLogicalBytes
12	history := NewHistory(HistoryConfig{MaxRows: 8, MaxBytes: budget, ChunkRows: 4})
13	for _, text := range []string{"aa", "bb", "cc"} {
14		requireHistoryAppend(t, history, historyRow(text))
15	}
16
17	if got, want := historyViewTexts(history.View()), []string{"bb", "cc"}; !equalStrings(got, want) {
18		t.Fatalf("retained rows = %#v, want %#v", got, want)
19	}
20	if got, want := history.LogicalBytes(), budget; got != want {
21		t.Fatalf("logical bytes = %d, want %d", got, want)
22	}
23	if got, want := history.View().LogicalBytes(), budget; got != want {
24		t.Fatalf("view logical bytes = %d, want %d", got, want)
25	}
26	if got, want := history.ByteCap(), budget; got != want {
27		t.Fatalf("byte capacity = %d, want %d", got, want)
28	}
29}
30
31func TestHistoryLogicalBytesDeduplicateCanonicalStylesPerSlab(t *testing.T) {
32	const budget = 2*(renderer.StoredCellLogicalBytes+renderer.RowDescriptorLogicalBytes) + 2*renderer.StyleRecordLogicalBytes
33	history := NewHistory(HistoryConfig{MaxRows: 8, MaxBytes: budget, ChunkRows: 2})
34	style := renderer.Style{Bold: true}
35	for _, r := range []rune{'a', 'b'} {
36		if err := history.Append([]renderer.Cell{{Rune: r, Style: style}}, LineBound{End: 1}); err != nil {
37			t.Fatalf("append %q: %v", r, err)
38		}
39	}
40
41	if got, want := history.LogicalBytes(), budget; got != want {
42		t.Fatalf("logical bytes = %d, want %d", got, want)
43	}
44	if got := history.SnapshotView().LogicalBytes(); got != history.LogicalBytes() {
45		t.Fatalf("snapshot logical bytes = %d, want %d", got, history.LogicalBytes())
46	}
47	if got := history.SealAndView().LogicalBytes(); got != history.LogicalBytes() {
48		t.Fatalf("sealed view logical bytes = %d, want %d", got, history.LogicalBytes())
49	}
50}
51
52func TestHistoryLogicalBytesRemainStableAfterStyledRowEvictionAndCodecRoundTrip(t *testing.T) {
53	history := NewHistory(HistoryConfig{MaxRows: 3, MaxBytes: 1 << 20, ChunkRows: 3})
54	styles := []renderer.Style{{Bold: true}, renderer.DefaultStyle(), renderer.DefaultStyle(), renderer.DefaultStyle()}
55	for i, style := range styles {
56		if err := history.Append([]renderer.Cell{{Rune: rune('a' + i), Style: style}}, LineBound{End: 1}); err != nil {
57			t.Fatalf("append row %d: %v", i, err)
58		}
59	}
60
61	before := history.View()
62	blob, err := MarshalHistory(before)
63	if err != nil {
64		t.Fatalf("marshal history: %v", err)
65	}
66	after, err := UnmarshalHistory(blob)
67	if err != nil {
68		t.Fatalf("unmarshal history: %v", err)
69	}
70	if got, want := after.LogicalBytes(), before.LogicalBytes(); got != want {
71		t.Fatalf("round-trip logical bytes = %d, want %d", got, want)
72	}
73}
74
75func TestHistoryRejectsSingleRowLargerThanLogicalByteBudget(t *testing.T) {
76	history := NewHistory(HistoryConfig{MaxRows: 8, MaxBytes: 79, ChunkRows: 4})
77	row := []renderer.Cell{{Rune: 'x', Style: renderer.Style{Bold: true}}}
78
79	err := history.Append(row, LineBound{End: 1})
80	if !errors.Is(err, ErrHistoryRowTooLarge) {
81		t.Fatalf("append oversized row error = %v, want ErrHistoryRowTooLarge", err)
82	}
83	if history.Len() != 0 || history.LogicalBytes() != 0 {
84		t.Fatalf("rejected append mutated history: rows=%d bytes=%d", history.Len(), history.LogicalBytes())
85	}
86}
87
88func BenchmarkHistoryByteLimitedAppend(b *testing.B) {
89	const rows, width = 256, 120
90	maxBytes := uint64(rows)*(uint64(width)*renderer.StoredCellLogicalBytes+renderer.RowDescriptorLogicalBytes) + renderer.StyleRecordLogicalBytes
91	history := NewHistory(HistoryConfig{MaxRows: rows * 2, MaxBytes: maxBytes, ChunkRows: rows})
92	row := make([]renderer.Cell, width)
93	for range rows {
94		requireHistoryAppend(b, history, row)
95	}
96
97	b.ReportAllocs()
98	b.SetBytes(int64(width) * int64(renderer.StoredCellLogicalBytes))
99	b.ResetTimer()
100	for b.Loop() {
101		requireHistoryAppend(b, history, row)
102	}
103	b.StopTimer()
104	if history.LogicalBytes() > maxBytes {
105		b.Fatalf("logical bytes = %d, exceeds capacity %d", history.LogicalBytes(), maxBytes)
106	}
107}
108
109func TestHistoryRestoreEvictsToLogicalByteBudget(t *testing.T) {
110	source := NewHistory(HistoryConfig{MaxRows: 8, MaxBytes: 1 << 20, ChunkRows: 4})
111	for _, text := range []string{"aa", "bb", "cc"} {
112		requireHistoryAppend(t, source, historyRow(text))
113	}
114	sealed, tail, err := MarshalSealedHistory(source.SealAndView())
115	if err != nil {
116		t.Fatalf("marshal history: %v", err)
117	}
118
119	const budget = 2*(2*renderer.StoredCellLogicalBytes+renderer.RowDescriptorLogicalBytes) + renderer.StyleRecordLogicalBytes
120	restored, err := HistoryFromBlobs(HistoryConfig{MaxRows: 8, MaxBytes: budget, ChunkRows: 4}, sealed, tail)
121	if err != nil {
122		t.Fatalf("restore history: %v", err)
123	}
124	if got, want := historyViewTexts(restored.View()), []string{"bb", "cc"}; !equalStrings(got, want) {
125		t.Fatalf("restored rows = %#v, want %#v", got, want)
126	}
127	if got := restored.LogicalBytes(); got > restored.ByteCap() {
128		t.Fatalf("restored logical bytes = %d, exceeds capacity %d", got, restored.ByteCap())
129	}
130}

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