v0.3.0 99dc266

vevs_v4_golden_test.go RawHistoryFolder

1package snapshot
2
3import (
4	"bytes"
5	"encoding/hex"
6	"testing"
7
8	vt "github.com/bnema/vev-vt"
9	"github.com/bnema/vev/internal/usecase/layout"
10)
11
12func TestVEVSV4GoldenRestoreCompatibility(t *testing.T) {
13	const golden = "5645565300040000000001291707ae820007726573746f7265000000000000007b0000000100037461620050001800000000000000020001700000017000010001700006737461626c6500042f746d700000000100000078565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff000000000000020000000011565448310300000000000000000000002a0000004f565448310300000001000000000000002b00000001000000000000002a000000010000754cff000ffffffffffffffff9000000000000010101020304050605fffffffffffffff7070809000000010100"
14	data, err := hex.DecodeString(golden)
15	if err != nil {
16		t.Fatal(err)
17	}
18
19	got, err := Unmarshal(data)
20	if err != nil {
21		t.Fatalf("Unmarshal golden VEVS: %v", err)
22	}
23	if got.Name != "restore" || got.CreatedAt != 123 || len(got.Tabs) != 1 {
24		t.Fatalf("session header = %#v", got)
25	}
26	tab := got.Tabs[0]
27	if tab.StableID != "tab" || tab.Cols != 80 || tab.Rows != 24 || tab.NextPaneID != 2 || tab.Focus != layout.PaneID("p") || len(tab.Panes) != 1 {
28		t.Fatalf("tab header = %#v", tab)
29	}
30	pane := tab.Panes[0]
31	if pane.ID != layout.PaneID("p") || pane.StableID != "stable" || pane.Cwd != "/tmp" || pane.Process != nil || len(pane.SealedChunks) != 1 {
32		t.Fatalf("pane header = %#v", pane)
33	}
34
35	sealed, err := vt.UnmarshalHistory(pane.SealedChunks[0])
36	if err != nil {
37		t.Fatalf("sealed history: %v", err)
38	}
39	if sealed.RowID(0) != 41 || sealed.Row(0)[0].Rune != 'A' {
40		t.Fatalf("sealed history = row %d %#v", sealed.RowID(0), sealed.Row(0))
41	}
42	tail, err := vt.UnmarshalHistory(pane.Tail)
43	if err != nil {
44		t.Fatalf("history tail: %v", err)
45	}
46	if tail.Len() != 0 || tail.NextRowID() != 42 {
47		t.Fatalf("history tail = len %d next %d", tail.Len(), tail.NextRowID())
48	}
49	transcript, err := vt.UnmarshalHistory(pane.Transcript)
50	if err != nil {
51		t.Fatalf("recovery transcript: %v", err)
52	}
53	if transcript.RowID(0) != 42 || transcript.Row(0)[0].Rune != '界' {
54		t.Fatalf("recovery transcript = row %d %#v", transcript.RowID(0), transcript.Row(0))
55	}
56
57	screen, err := vt.NewScreenWithRecoveryTranscript(8, 2, vt.HistoryConfig{MaxRows: 8, ChunkRows: 2}, pane.SealedChunks, pane.Tail, pane.Transcript)
58	if err != nil {
59		t.Fatalf("restore VT history: %v", err)
60	}
61	view := screen.History().View()
62	if view.Len() != 2 || view.RowID(0) != 41 || view.RowID(1) != 42 || view.NextRowID() != 43 {
63		t.Fatalf("restored history = len %d IDs %d/%d next %d", view.Len(), view.RowID(0), view.RowID(1), view.NextRowID())
64	}
65}
66
67func TestVEVSV4GoldenRejectsMalformedInput(t *testing.T) {
68	const golden = "5645565300040000000001291707ae820007726573746f7265000000000000007b0000000100037461620050001800000000000000020001700000017000010001700006737461626c6500042f746d700000000100000078565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff000000000000020000000011565448310300000000000000000000002a0000004f565448310300000001000000000000002b00000001000000000000002a000000010000754cff000ffffffffffffffff9000000000000010101020304050605fffffffffffffff7070809000000010100"
69	valid, err := hex.DecodeString(golden)
70	if err != nil {
71		t.Fatal(err)
72	}
73	mutate := func(fn func([]byte)) []byte {
74		out := append([]byte(nil), valid...)
75		fn(out)
76		return out
77	}
78	cases := []struct {
79		name string
80		data []byte
81	}{
82		{name: "truncated", data: valid[:len(valid)-1]},
83		{name: "trailing bytes", data: append(append([]byte(nil), valid...), 0)},
84		{name: "bad magic", data: mutate(func(data []byte) { data[0] ^= 0xff })},
85		{name: "bad version", data: mutate(func(data []byte) { data[4] = 3 })},
86		{name: "bad flags", data: mutate(func(data []byte) { data[7] = 1 })},
87		{name: "bad crc", data: mutate(func(data []byte) { data[15] ^= 0xff })},
88	}
89
90	body := append([]byte(nil), valid[16:]...)
91	if offset := bytes.Index(body, []byte("VTH1")); offset < 0 {
92		t.Fatal("golden has no VT blob")
93	} else {
94		body[offset+4] = 2
95		cases = append(cases, struct {
96			name string
97			data []byte
98		}{name: "bad nested VT version", data: v4Envelope(4, 0, body)})
99	}
100
101	for _, test := range cases {
102		t.Run(test.name, func(t *testing.T) {
103			reject(t, test.data, nil)
104		})
105	}
106	for length := 0; length < len(valid); length++ {
107		reject(t, valid[:length], nil)
108	}
109}

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