v0.2.0 07ab4d2

wire_test.go RawHistoryFolder

1package zerobus
2
3import (
4	"errors"
5	"strings"
6	"testing"
7)
8
9func TestSignatures(t *testing.T) {
10	valid := []string{"", "s", "su", "a{sv}", "a(iiay)", "(s(ss))", "aa{s(ii)}", "a{oa{sa{sv}}}", "v", "g", "h"}
11	invalid := []string{"a", "(", "()", "{sv}", "a{vs}", "a{s}", "a{sss}", "(s", "z", "a{(s)s}", "a{as}"}
12	for _, s := range valid {
13		if !validSignature(s) {
14			t.Errorf("%q should be valid", s)
15		}
16	}
17	for _, s := range invalid {
18		if validSignature(s) {
19			t.Errorf("%q should be invalid", s)
20		}
21	}
22	// The spec allows 32 nested arrays and 32 nested structs.
23	nest := func(open, close string, n int) string {
24		return strings.Repeat(open, n) + "y" + strings.Repeat(close, n)
25	}
26	for _, c := range []struct {
27		sig string
28		ok  bool
29	}{
30		{nest("a", "", 32), true},
31		{nest("a", "", 33), false},
32		{nest("(", ")", 32), true},
33		{nest("(", ")", 33), false},
34		{nest("a(", ")", 32), true},
35		{strings.Repeat("a{s", 33) + "y" + strings.Repeat("}", 33), false},
36	} {
37		if validSignature(c.sig) != c.ok {
38			t.Errorf("validSignature(%d bytes) = %v, want %v", len(c.sig), !c.ok, c.ok)
39		}
40	}
41}
42
43func TestNames(t *testing.T) {
44	for _, s := range []string{"org.example", "org.kde.StatusNotifierWatcher", "a_b.C1"} {
45		if !validInterface(s) {
46			t.Errorf("interface %q should be valid", s)
47		}
48	}
49	for _, s := range []string{"", "org", "org.", ".org.a", "org.1a", "org.a-b"} {
50		if validInterface(s) {
51			t.Errorf("interface %q should be invalid", s)
52		}
53	}
54	for _, s := range []string{":1.42", "org.freedesktop.StatusNotifierItem-799476-1", "org.a"} {
55		if !validBusName(s) {
56			t.Errorf("bus name %q should be valid", s)
57		}
58	}
59	for _, s := range []string{"", ":", "org", "org..a", "org.1a", "org.a/b"} {
60		if validBusName(s) {
61			t.Errorf("bus name %q should be invalid", s)
62		}
63	}
64	for _, s := range []string{"", "1a", "a.b", "a-b"} {
65		if validMember(s) {
66			t.Errorf("member %q should be invalid", s)
67		}
68	}
69	for name, h := range map[string][4]string{ // dest, path, iface, member
70		"bad destination": {"bad name", "/a", "a.b", "M"},
71		"call no path":    {"a.b", "", "a.b", "M"},
72		"call no member":  {"a.b", "/a", "a.b", ""},
73	} {
74		var e Encoder
75		e.begin(TypeMethodCall, 1, 0, h[0], h[1], h[2], h[3], "", "")
76		if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
77			t.Errorf("%s accepted: %v", name, err)
78		}
79	}
80	var e Encoder
81	e.begin(TypeSignal, 1, 0, "", "/a", "", "C", "", "")
82	if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
83		t.Errorf("signal without interface accepted: %v", err)
84	}
85	e.begin(TypeError, 1, 1, "", "", "", "", "", "")
86	if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
87		t.Errorf("error without name accepted: %v", err)
88	}
89}
90
91func TestPaths(t *testing.T) {
92	for _, p := range []string{"/", "/a", "/org/kde/StatusNotifierItem", "/a_1/B2"} {
93		if !validPath(p) {
94			t.Errorf("%q should be valid", p)
95		}
96	}
97	for _, p := range []string{"", "a", "//", "/a/", "/a//b", "/a-b", "/a.b"} {
98		if validPath(p) {
99			t.Errorf("%q should be invalid", p)
100		}
101	}
102}
103
104// build encodes a method call with every type, the way Conn does.
105func build(e *Encoder) []byte {
106	const sig = "ybnqiuxtdsogva{sv}ay(si)"
107	e.begin(TypeMethodCall, 7, 0, "org.example", "/org/example", "org.example.I", "M", "", sig)
108	e.Byte(0xAB)
109	e.Bool(true)
110	e.Int16(-2)
111	e.Uint16(3)
112	e.Int32(-4)
113	e.Uint32(5)
114	e.Int64(-6)
115	e.Uint64(7)
116	e.Float64(8.5)
117	e.Str("héllo")
118	e.ObjectPath("/a/b")
119	e.Signature("a{sv}")
120	e.Variant("u")
121	e.Uint32(42)
122	d := e.BeginArray('{')
123	for _, k := range []string{"one", "two"} {
124		e.Struct()
125		e.Str(k)
126		e.Variant("s")
127		e.Str(k + "!")
128	}
129	e.EndArray(d)
130	e.ByteArray([]byte{1, 2, 3})
131	e.Struct()
132	e.Str("x")
133	e.Int32(9)
134	b, err := e.finish()
135	if err != nil {
136		panic(err)
137	}
138	return b
139}
140
141func TestRoundTrip(t *testing.T) {
142	var e Encoder
143	b := build(&e)
144	n, err := frameLen(b)
145	if err != nil || n != len(b) {
146		t.Fatalf("frameLen = %d, %v; want %d", n, err, len(b))
147	}
148	var m Message
149	if err := parse(b, &m); err != nil {
150		t.Fatal(err)
151	}
152	if m.Type != TypeMethodCall || m.Serial != 7 || m.Path != "/org/example" || m.Interface != "org.example.I" ||
153		m.Member != "M" || m.Destination != "org.example" || m.Signature != "ybnqiuxtdsogva{sv}ay(si)" {
154		t.Fatalf("header: %+v", m)
155	}
156	r := m.Body()
157	if r.Byte() != 0xAB || !r.Bool() || r.Int16() != -2 || r.Uint16() != 3 || r.Int32() != -4 || r.Uint32() != 5 ||
158		r.Int64() != -6 || r.Uint64() != 7 || r.Float64() != 8.5 || r.Str() != "héllo" || r.ObjectPath() != "/a/b" ||
159		r.Signature() != "a{sv}" {
160		t.Fatalf("basic values: %v", r.Err())
161	}
162	if sig := r.Variant(); sig != "u" || r.Uint32() != 42 {
163		t.Fatal("variant")
164	}
165	var keys []string
166	end := r.Array('{')
167	for r.More(end) {
168		r.Struct()
169		k := r.Str()
170		if r.Variant() != "s" || r.Str() != k+"!" {
171			t.Fatal("dict value")
172		}
173		keys = append(keys, k)
174	}
175	if len(keys) != 2 || keys[0] != "one" || keys[1] != "two" {
176		t.Fatalf("keys %v", keys)
177	}
178	if ay := r.ByteArray(); len(ay) != 3 || ay[2] != 3 {
179		t.Fatalf("ay %v", ay)
180	}
181	r.Struct()
182	if r.Str() != "x" || r.Int32() != 9 || !r.Done() {
183		t.Fatalf("struct: %v", r.Err())
184	}
185
186	// Skip the same body by its signature.
187	r = m.Body()
188	r.Skip(m.Signature)
189	if !r.Done() {
190		t.Fatalf("skip: %v off=%d len=%d", r.Err(), r.off, len(r.buf))
191	}
192}
193
194func TestEmptyArrayAlignment(t *testing.T) {
195	// An empty array of 8-aligned elements still pads to the element
196	// alignment, and its length excludes that padding.
197	var e Encoder
198	e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", "a(ii)u")
199	a := e.BeginArray('(')
200	e.EndArray(a)
201	e.Uint32(1)
202	b, err := e.finish()
203	if err != nil {
204		t.Fatal(err)
205	}
206	var m Message
207	if err := parse(b, &m); err != nil {
208		t.Fatal(err)
209	}
210	r := m.Body()
211	end := r.Array('(')
212	if r.More(end) || r.Uint32() != 1 || !r.Done() {
213		t.Fatalf("got %v", r.Err())
214	}
215}
216
217func TestBigEndian(t *testing.T) {
218	// A signal from a big-endian peer: path /a, interface a.b, member C,
219	// body "u" = 0x01020304. The field array is 55 bytes, then 1 byte of
220	// padding before the body.
221	be := []byte{'B', 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 55,
222		1, 1, 'o', 0, 0, 0, 0, 2, '/', 'a', 0, 0, 0, 0, 0, 0,
223		2, 1, 's', 0, 0, 0, 0, 3, 'a', '.', 'b', 0, 0, 0, 0, 0,
224		3, 1, 's', 0, 0, 0, 0, 1, 'C', 0, 0, 0, 0, 0, 0, 0,
225		8, 1, 'g', 0, 1, 'u', 0, 0,
226		1, 2, 3, 4}
227	n, err := frameLen(be)
228	if err != nil || n != len(be) {
229		t.Fatalf("frameLen %d %v want %d", n, err, len(be))
230	}
231	var m Message
232	if err := parse(be, &m); err != nil {
233		t.Fatal(err)
234	}
235	if m.Path != "/a" || m.Interface != "a.b" || m.Member != "C" || m.Body().Uint32() != 0x01020304 {
236		t.Fatalf("%+v", m)
237	}
238}
239
240func TestEncoderRejects(t *testing.T) {
241	cases := map[string]func(e *Encoder){
242		"nul in string":   func(e *Encoder) { e.Str("a\x00b") },
243		"bad utf-8":       func(e *Encoder) { e.Str("\xff") },
244		"bad path":        func(e *Encoder) { e.ObjectPath("a/b") },
245		"missing value":   func(e *Encoder) {},
246		"wrong type":      func(e *Encoder) { e.Byte(1) },
247		"extra value":     func(e *Encoder) { e.Str("a"); e.Str("b") },
248		"bad variant sig": func(e *Encoder) { e.Variant("ss") },
249		"wrong element": func(e *Encoder) {
250			a := e.BeginArray('s')
251			e.Uint32(77)
252			e.EndArray(a)
253		},
254		"bad nested variant": func(e *Encoder) {
255			a := e.BeginArray('v')
256			e.Byte(1)
257			e.Str("z")
258			e.EndArray(a)
259		},
260	}
261	sigs := map[string]string{"wrong element": "as", "bad nested variant": "av"}
262	for name, f := range cases {
263		sig := sigs[name]
264		if sig == "" {
265			sig = "s"
266		}
267		var e Encoder
268		e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", sig)
269		f(&e)
270		if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
271			t.Errorf("%s: err = %v, want ErrInvalid", name, err)
272		}
273	}
274}
275
276func TestParseRejects(t *testing.T) {
277	var e Encoder
278	good := append([]byte(nil), build(&e)...)
279	mutate := map[string]func(b []byte) []byte{
280		"bad endian":   func(b []byte) []byte { b[0] = 'x'; return b },
281		"bad version":  func(b []byte) []byte { b[3] = 2; return b },
282		"zero serial":  func(b []byte) []byte { b[8], b[9], b[10], b[11] = 0, 0, 0, 0; return b },
283		"short body":   func(b []byte) []byte { return b[:len(b)-1] },
284		"huge body":    func(b []byte) []byte { b[7] = 0xff; return b },
285		"no member":    func(b []byte) []byte { return dropField(b, fieldMember) },
286		"bad padding":  func(b []byte) []byte { return dirtyPadding(b) },
287		"no signature": func(b []byte) []byte { return dropField(b, fieldSignature) },
288	}
289	for name, f := range mutate {
290		b := f(append([]byte(nil), good...))
291		var m Message
292		n, err := frameLen(b)
293		if err == nil && n == len(b) {
294			err = parse(b, &m)
295		} else if err == nil {
296			err = ErrMalformed
297		}
298		if err == nil {
299			t.Errorf("%s: accepted", name)
300		}
301	}
302}
303
304// dropField renames the header field code to an unknown one, so parse
305// ignores it.
306func dropField(b []byte, code byte) []byte {
307	for i := 16; i < len(b)-1; i += 8 {
308		if b[i] == code && b[i+1] == 1 {
309			b[i] = 200
310			return b
311		}
312	}
313	return b
314}
315
316// dirtyPadding sets the last padding byte before the body to a non-zero
317// value.
318func dirtyPadding(b []byte) []byte {
319	fields := int(b[12]) | int(b[13])<<8
320	if end := headerLen + fields; end%8 != 0 {
321		b[pad(end, 8)-1] = 1
322	}
323	return b
324}
325
326func TestReaderStickyErrors(t *testing.T) {
327	r := Reader{buf: []byte{1, 0, 0}}
328	if r.Uint32() != 0 || r.Err() == nil {
329		t.Fatal("short read should fail")
330	}
331	if r.Byte() != 0 || r.Str() != "" {
332		t.Fatal("reads after an error should return zero values")
333	}
334	// A string without its NUL.
335	r = Reader{buf: []byte{1, 0, 0, 0, 'a', 'b'}}
336	if r.Str() != "" || r.Err() == nil {
337		t.Fatal("string without NUL should fail")
338	}
339	// An array longer than the buffer.
340	r = Reader{buf: []byte{0xff, 0, 0, 0}}
341	end := r.Array('y')
342	if r.More(end) || r.Err() == nil {
343		t.Fatal("oversized array should fail")
344	}
345}
346
347func TestEncodeParseNoAllocs(t *testing.T) {
348	var e Encoder
349	var m Message
350	build(&e) // grow the buffer once
351	allocs := testing.AllocsPerRun(100, func() {
352		b := build(&e)
353		if err := parse(b, &m); err != nil {
354			t.Fatal(err)
355		}
356		r := m.Body()
357		r.Skip(m.Signature)
358		if !r.Done() {
359			t.Fatal(r.Err())
360		}
361	})
362	if allocs != 0 {
363		t.Fatalf("%v allocations per message, want 0", allocs)
364	}
365}
366
367func FuzzParse(f *testing.F) {
368	var e Encoder
369	f.Add(append([]byte(nil), build(&e)...))
370	f.Fuzz(func(t *testing.T, b []byte) {
371		if len(b) < headerLen {
372			return
373		}
374		n, err := frameLen(b)
375		if err != nil || n > len(b) {
376			return
377		}
378		var m Message
379		if parse(b[:n], &m) != nil {
380			return
381		}
382		r := m.Body()
383		r.Skip(m.Signature)
384	})
385}
386
387// FuzzCheck checks that every body the encoder accepts parses back with
388// its signature.
389func FuzzCheck(f *testing.F) {
390	f.Add("a{sv}", []byte{8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 'a', 0, 1, 'u', 0, 0, 0, 0, 7, 0, 0, 0})
391	f.Add("as", []byte{4, 0, 0, 0, 77, 0, 0, 0})
392	f.Fuzz(func(t *testing.T, sig string, body []byte) {
393		if !validSignature(sig) {
394			return
395		}
396		var e Encoder
397		e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", sig)
398		e.buf = append(e.buf, body...)
399		b, err := e.finish()
400		if err != nil {
401			return
402		}
403		var m Message
404		if err := parse(b, &m); err != nil {
405			t.Fatalf("accepted body does not parse: %v", err)
406		}
407		r := m.Body()
408		r.check(m.Signature)
409		if !r.Done() {
410			t.Fatalf("accepted body does not match its signature: %v", r.Err())
411		}
412	})
413}
414
415func FuzzSkip(f *testing.F) {
416	f.Add("a{sv}", []byte{8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 'a', 0, 1, 'u', 0, 0, 0, 0, 7, 0, 0, 0})
417	f.Add("(v)", []byte{1, 'v', 0})
418	f.Fuzz(func(t *testing.T, sig string, b []byte) {
419		if !validSignature(sig) {
420			return
421		}
422		r := Reader{buf: b}
423		r.Skip(sig)
424	})
425}
426
427func BenchmarkEncodeParse(b *testing.B) {
428	var e Encoder
429	var m Message
430	b.ReportAllocs()
431	for b.Loop() {
432		buf := build(&e)
433		if err := parse(buf, &m); err != nil {
434			b.Fatal(err)
435		}
436	}
437}

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 1

Tags 3

↑↓ moveenter openesc close