v0.2.0 4859eb8

history_vth3_golden_external_test.go RawHistoryFolder

1package vt_test
2
3import (
4	"bytes"
5	"encoding/binary"
6	"encoding/hex"
7	"testing"
8
9	vt "github.com/bnema/vev-vt"
10	"github.com/bnema/vev-vt/core"
11)
12
13func TestExternalVTH3GoldenVectors(t *testing.T) {
14	vectors := []struct {
15		name  string
16		hex   string
17		stats vt.DecodeStats
18	}{
19		{
20			name:  "empty",
21			hex:   "565448310300000000000000000000002a",
22			stats: vt.DecodeStats{},
23		},
24		{
25			name:  "plain and continuation cells",
26			hex:   "565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff0000000000000200",
27			stats: vt.DecodeStats{Chunks: 1, Rows: 1, Cells: 2, Styles: 2, Bytes: 82},
28		},
29		{
30			name:  "all style fields",
31			hex:   "565448310300000001000000000000002b00000001000000000000002a000000010000754cff000ffffffffffffffff9000000000000010101020304050605fffffffffffffff70708090000000101",
32			stats: vt.DecodeStats{Chunks: 1, Rows: 1, Cells: 1, Styles: 1, Bytes: 41},
33		},
34	}
35
36	for _, vector := range vectors {
37		t.Run(vector.name, func(t *testing.T) {
38			data, err := hex.DecodeString(vector.hex)
39			if err != nil {
40				t.Fatal(err)
41			}
42			stats, err := vt.PreflightHistoryBlob(data)
43			if err != nil {
44				t.Fatalf("PreflightHistoryBlob: %v", err)
45			}
46			if stats != vector.stats {
47				t.Fatalf("decode stats = %#v, want %#v", stats, vector.stats)
48			}
49
50			view, err := vt.UnmarshalHistory(data)
51			if err != nil {
52				t.Fatalf("UnmarshalHistory: %v", err)
53			}
54			reencoded, err := vt.MarshalHistory(view)
55			if err != nil {
56				t.Fatalf("MarshalHistory: %v", err)
57			}
58			if !bytes.Equal(reencoded, data) {
59				t.Fatalf("canonical bytes changed:\n got %x\nwant %x", reencoded, data)
60			}
61
62			switch vector.name {
63			case "empty":
64				if view.Len() != 0 || view.ChunkCount() != 0 || view.NextRowID() != 42 {
65					t.Fatalf("empty view = len %d chunks %d next %d", view.Len(), view.ChunkCount(), view.NextRowID())
66				}
67			case "plain and continuation cells":
68				if view.Len() != 1 || view.RowID(0) != 41 || view.Bound(0) != (vt.LineBound{End: 2}) || view.NextRowID() != 42 {
69					t.Fatalf("plain view = len %d row %d bound %#v next %d", view.Len(), view.RowID(0), view.Bound(0), view.NextRowID())
70				}
71				row := view.Row(0)
72				if len(row) != 2 || row[0].Rune != 'A' || row[1].Rune != 0 || !row[1].Continuation {
73					t.Fatalf("plain row = %#v", row)
74				}
75			case "all style fields":
76				wantStyle := core.Style{
77					Bold:                 true,
78					Italic:               true,
79					Inverse:              true,
80					Attrs:                core.StyleAttrs(0x000f),
81					Foreground:           -7,
82					Background:           257,
83					HasForegroundRGB:     true,
84					ForegroundRGB:        core.RGB{R: 1, G: 2, B: 3},
85					HasBackgroundRGB:     true,
86					BackgroundRGB:        core.RGB{R: 4, G: 5, B: 6},
87					UnderlineStyle:       core.UnderlineDashed,
88					UnderlineColor:       -9,
89					HasUnderlineColorRGB: true,
90					UnderlineColorRGB:    core.RGB{R: 7, G: 8, B: 9},
91				}
92				if view.Len() != 1 || view.RowID(0) != 42 || view.Bound(0) != (vt.LineBound{End: 1, Soft: true}) || view.NextRowID() != 43 {
93					t.Fatalf("styled view = len %d row %d bound %#v next %d", view.Len(), view.RowID(0), view.Bound(0), view.NextRowID())
94				}
95				row := view.Row(0)
96				if len(row) != 1 || row[0].Rune != '界' || !row[0].Style.Equal(wantStyle) {
97					t.Fatalf("styled row = %#v, want %#v", row, wantStyle)
98				}
99			}
100		})
101	}
102}
103
104func TestExternalVTH3RejectsMalformedVectors(t *testing.T) {
105	valid, err := hex.DecodeString("565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff0000000000000200")
106	if err != nil {
107		t.Fatal(err)
108	}
109
110	mutate := func(fn func([]byte)) []byte {
111		out := append([]byte(nil), valid...)
112		fn(out)
113		return out
114	}
115	const (
116		rowIDOffset = 21
117		cellOffset  = 33
118		boundOffset = 115
119	)
120	cases := []struct {
121		name string
122		data []byte
123	}{
124		{name: "truncated", data: valid[:len(valid)-1]},
125		{name: "trailing bytes", data: append(append([]byte(nil), valid...), 0)},
126		{name: "bad magic", data: mutate(func(data []byte) { data[0] ^= 0xff })},
127		{name: "bad version", data: mutate(func(data []byte) { data[4] = 2 })},
128		{name: "zero next row ID", data: mutate(func(data []byte) { clear(data[9:17]) })},
129		{name: "zero row ID", data: mutate(func(data []byte) { clear(data[rowIDOffset : rowIDOffset+8]) })},
130		{name: "invalid rune", data: mutate(func(data []byte) { binary.BigEndian.PutUint32(data[cellOffset:], ^uint32(0)) })},
131		{name: "invalid underline style", data: mutate(func(data []byte) { data[cellOffset+29] = byte(core.UnderlineDashed + 1) })},
132		{name: "bound exceeds row", data: mutate(func(data []byte) { binary.BigEndian.PutUint32(data[boundOffset:], 3) })},
133	}
134
135	for _, test := range cases {
136		t.Run(test.name, func(t *testing.T) {
137			if _, err := vt.PreflightHistoryBlob(test.data); err == nil {
138				t.Fatal("PreflightHistoryBlob accepted malformed data")
139			}
140			if _, err := vt.UnmarshalHistory(test.data); err == nil {
141				t.Fatal("UnmarshalHistory accepted malformed data")
142			}
143		})
144	}
145	for length := 0; length < len(valid); length++ {
146		if _, err := vt.UnmarshalHistory(valid[:length]); err == nil {
147			t.Fatalf("accepted truncated prefix of length %d", length)
148		}
149	}
150}

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