weights_test.go RawHistoryFolder

1package snapshotcodec
2
3import (
4	"bytes"
5	"encoding/binary"
6	"encoding/hex"
7	"math"
8	"testing"
9
10	"github.com/bnema/vev/internal/domain/layout"
11)
12
13func TestManifestWeightRoundTripAndOldCompatibility(t *testing.T) {
14	weighted := testManifest(&layout.Tree{Root: &layout.Node{
15		Kind: layout.Split,
16		Dir:  layout.Horizontal,
17		Children: []*layout.Node{
18			{Kind: layout.Leaf, Leaf: "one", Weight: 2},
19			{Kind: layout.Leaf, Leaf: "two", Weight: 1},
20		},
21	}}, nil)
22
23	encoded, err := MarshalManifest(weighted)
24	if err != nil {
25		t.Fatal(err)
26	}
27	weightedBody, err := unmarshalManifestEnvelope(encoded, manifestMagic)
28	if err != nil {
29		t.Fatal(err)
30	}
31	extensionSize := len(manifestWeightTag) + 4 + 3*manifestWeightSize
32	if len(weightedBody) < extensionSize {
33		t.Fatalf("weighted body length = %d, want at least %d", len(weightedBody), extensionSize)
34	}
35	unweightedBody := weightedBody[:len(weightedBody)-extensionSize]
36	extension := weightedBody[len(unweightedBody):]
37	if string(extension[:len(manifestWeightTag)]) != manifestWeightTag || binary.BigEndian.Uint32(extension[len(manifestWeightTag):]) != 3 {
38		t.Fatalf("weight extension header = %x", extension[:len(manifestWeightTag)+4])
39	}
40	for i, want := range []float64{0, 2, 1} {
41		offset := len(manifestWeightTag) + 4 + i*manifestWeightSize
42		if got := math.Float64frombits(binary.BigEndian.Uint64(extension[offset:])); got != want {
43			t.Fatalf("preorder weight %d = %v, want %v", i, got, want)
44		}
45	}
46
47	got, err := UnmarshalManifest(encoded)
48	if err != nil {
49		t.Fatal(err)
50	}
51	if got.Tabs[0].Tree.Root.Weight != 0 || got.Tabs[0].Tree.Root.Children[0].Weight != 2 || got.Tabs[0].Tree.Root.Children[1].Weight != 1 {
52		t.Fatalf("decoded weights = root %v, children %v/%v", got.Tabs[0].Tree.Root.Weight, got.Tabs[0].Tree.Root.Children[0].Weight, got.Tabs[0].Tree.Root.Children[1].Weight)
53	}
54
55	unweighted := testManifest(&layout.Tree{Root: &layout.Node{
56		Kind: layout.Split,
57		Dir:  layout.Horizontal,
58		Children: []*layout.Node{
59			layout.NewLeaf("one"),
60			layout.NewLeaf("two"),
61		},
62	}}, nil)
63	oldEncoded, err := MarshalManifest(unweighted)
64	if err != nil {
65		t.Fatal(err)
66	}
67	oldBody, err := unmarshalManifestEnvelope(oldEncoded, manifestMagic)
68	if err != nil {
69		t.Fatal(err)
70	}
71	if !bytes.Equal(oldBody, unweightedBody) {
72		t.Fatalf("weighted manifest changed the legacy body before its extension")
73	}
74	oldDecoded, err := UnmarshalManifest(oldEncoded)
75	if err != nil {
76		t.Fatal(err)
77	}
78	for _, node := range manifestNodes(oldDecoded) {
79		if node.Weight != 0 {
80			t.Fatalf("old manifest decoded weight = %v, want zero", node.Weight)
81		}
82	}
83}
84
85func TestSnapshotEnvelopeCompatibility(t *testing.T) {
86	const (
87		headGolden     = "56455648000100000000002898ebcac200000000000000070100000000000000000000000000000000000000000000000000000000000000"
88		objectGolden   = "5645564f000200000000001ef35457a7030000000c7801000c00f3ff63616e6f6e6963616c2d767403001f2b04c0"
89		manifestGolden = "5645564d0005000000000133d118cc030000000000000001010000000000000000000000000000000000056e616d6564000000000000000000000001000374616200500018000000000000000000036f6e65010000020000036f6e6500000374776f000200036f6e6500036f6e6500042f6f6e65000101010000000000000000000000000000000000000000000000000000000000000000000016020200000000000000000000000000000000000000000000000000000000000000000000160303000000000000000000000000000000000000000000000000000000000000000000001600000374776f000374776f00042f74776f0000020400000000000000000000000000000000000000000000000000000000000000000000160305000000000000000000000000000000000000000000000000000000000000000000001600"
90	)
91	if ManifestVersion != 5 {
92		t.Fatalf("ManifestVersion = %d, want 5", ManifestVersion)
93	}
94
95	head, err := MarshalHead(Head{Generation: 7, ManifestDigest: SnapshotDigest{1}})
96	if err != nil {
97		t.Fatal(err)
98	}
99	object, err := MarshalObject(RecoveryTranscript, []byte("canonical-vt"))
100	if err != nil {
101		t.Fatal(err)
102	}
103	manifest, err := MarshalManifest(unweightedTestManifest())
104	if err != nil {
105		t.Fatal(err)
106	}
107	for _, tc := range []struct {
108		name   string
109		got    []byte
110		golden string
111	}{
112		{name: "HEAD", got: head, golden: headGolden},
113		{name: "object", got: object.Data, golden: objectGolden},
114		{name: "unweighted manifest", got: manifest, golden: manifestGolden},
115	} {
116		t.Run(tc.name, func(t *testing.T) {
117			want, err := hex.DecodeString(tc.golden)
118			if err != nil {
119				t.Fatal(err)
120			}
121			if !bytes.Equal(tc.got, want) {
122				t.Fatalf("bytes changed:\n got %x\nwant %x", tc.got, want)
123			}
124		})
125	}
126}
127
128func TestManifestWeightExtensionMalformedCorpus(t *testing.T) {
129	oldEncoded, err := MarshalManifest(unweightedTestManifest())
130	if err != nil {
131		t.Fatal(err)
132	}
133	oldBody, err := unmarshalManifestEnvelope(oldEncoded, manifestMagic)
134	if err != nil {
135		t.Fatal(err)
136	}
137
138	weighted := unweightedTestManifest()
139	weighted.Tabs[0].Tree.Root.Children[0].Weight = 2
140	weightedEncoded, err := MarshalManifest(weighted)
141	if err != nil {
142		t.Fatal(err)
143	}
144	weightedBody, err := unmarshalManifestEnvelope(weightedEncoded, manifestMagic)
145	if err != nil {
146		t.Fatal(err)
147	}
148	extensionOffset := len(oldBody)
149	if len(weightedBody) <= extensionOffset || string(weightedBody[extensionOffset:extensionOffset+len(manifestWeightTag)]) != manifestWeightTag {
150		t.Fatalf("weighted body has no %q extension", manifestWeightTag)
151	}
152
153	mutate := func(fn func([]byte)) []byte {
154		body := append([]byte(nil), weightedBody...)
155		fn(body)
156		return body
157	}
158	firstWeight := extensionOffset + len(manifestWeightTag) + 4
159	corpus := []struct {
160		name  string
161		body  []byte
162		valid bool
163	}{
164		{name: "legacy without extension", body: oldBody, valid: true},
165		{name: "valid extension", body: weightedBody, valid: true},
166		{name: "malformed tag", body: mutate(func(body []byte) { body[extensionOffset] ^= 0xff })},
167		{name: "node count mismatch", body: mutate(func(body []byte) { binary.BigEndian.PutUint32(body[extensionOffset+len(manifestWeightTag):], 2) })},
168		{name: "truncated weight", body: weightedBody[:len(weightedBody)-1]},
169		{name: "trailing garbage", body: append(append([]byte(nil), weightedBody...), 0)},
170		{name: "negative weight", body: mutate(func(body []byte) { binary.BigEndian.PutUint64(body[firstWeight:], math.Float64bits(-1)) })},
171		{name: "NaN weight", body: mutate(func(body []byte) { binary.BigEndian.PutUint64(body[firstWeight:], math.Float64bits(math.NaN())) })},
172		{name: "infinite weight", body: mutate(func(body []byte) { binary.BigEndian.PutUint64(body[firstWeight:], math.Float64bits(math.Inf(1))) })},
173	}
174
175	for _, tc := range corpus {
176		t.Run(tc.name, func(t *testing.T) {
177			preflightErr := preflightManifest(tc.body)
178			_, decodeErr := decodeManifest(tc.body)
179			if (preflightErr == nil) != tc.valid {
180				t.Fatalf("preflight error = %v, valid=%v", preflightErr, tc.valid)
181			}
182			if (decodeErr == nil) != tc.valid {
183				t.Fatalf("decode error = %v, valid=%v", decodeErr, tc.valid)
184			}
185			if (preflightErr == nil) != (decodeErr == nil) {
186				t.Fatalf("preflight/decode disagreement: %v / %v", preflightErr, decodeErr)
187			}
188		})
189	}
190}
191
192func TestMarshalManifestRejectsInvalidWeights(t *testing.T) {
193	for _, weight := range []float64{-1, math.NaN(), math.Inf(1), math.Inf(-1)} {
194		manifest := unweightedTestManifest()
195		manifest.Tabs[0].Tree.Root.Weight = weight
196		if _, err := MarshalManifest(manifest); err == nil {
197			t.Fatalf("MarshalManifest() accepted weight %v", weight)
198		}
199	}
200}
201
202func unweightedTestManifest() Manifest {
203	return testManifest(&layout.Tree{Root: &layout.Node{
204		Kind: layout.Split,
205		Dir:  layout.Horizontal,
206		Children: []*layout.Node{
207			layout.NewLeaf("one"),
208			layout.NewLeaf("two"),
209		},
210	}}, nil)
211}

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