detached 602baa7

replies_test.go RawHistoryFolder

1package xturbo_test
2
3import (
4	"bytes"
5	"encoding/binary"
6	"testing"
7
8	"bnema.dev/xturbo/internal/xtest"
9	"bnema.dev/xturbo/render"
10	"bnema.dev/xturbo/res"
11	"bnema.dev/xturbo/xfixes"
12	"bnema.dev/xturbo/xproto"
13)
14
15func loadReply(t testing.TB, name string) []byte {
16	t.Helper()
17	raw, err := xtest.LoadHex("testdata/golden/" + name + ".reply.hex")
18	if err != nil {
19		t.Fatal(err)
20	}
21	if raw[0] != 1 || len(raw) != 32+4*int(binary.LittleEndian.Uint32(raw[4:])) {
22		t.Fatalf("%s: not a framed reply", name)
23	}
24	return raw
25}
26
27// Expected values are independent of the generated code: they follow from the
28// capture script (window geometry, written property, ConfigureWindow state)
29// or are read from the raw golden at protocol-spec offsets.
30func TestReplyGoldens(t *testing.T) {
31	u32 := func(raw []byte, off int) uint32 { return binary.LittleEndian.Uint32(raw[off:]) }
32	t.Run("xproto/GetGeometry", func(t *testing.T) {
33		raw := loadReply(t, "xproto/GetGeometry")
34		r, ok := xproto.ParseGetGeometryReply(raw)
35		if !ok || r.Depth() != 24 || r.Root() != xproto.Window(u32(raw, 8)) || r.X() != 0 || r.Y() != 0 ||
36			r.Width() != 800 || r.Height() != 600 || r.BorderWidth() != 0 || !bytes.Equal(r.Raw(), raw) {
37			t.Fatalf("decoded %+v", r)
38		}
39	})
40	t.Run("xproto/QueryTree", func(t *testing.T) {
41		raw := loadReply(t, "xproto/QueryTree")
42		r, ok := xproto.ParseQueryTreeReply(raw)
43		if !ok || r.Root() != xproto.Window(u32(raw, 8)) || r.Parent() != xproto.WindowNone || r.ChildrenLen() != 2 {
44			t.Fatal("fixed fields")
45		}
46		c := r.Children()
47		// Stacking order bottom-to-top: the sibling was created before the child.
48		if c.Len() != 2 || c.At(0)&0x1fffff != 2 || c.At(1)&0x1fffff != 1 || c.At(0)&^0x1fffff != c.At(1)&^0x1fffff {
49			t.Fatalf("children %x %x", c.At(0), c.At(1))
50		}
51	})
52	t.Run("xproto/GetWindowAttributes", func(t *testing.T) {
53		raw := loadReply(t, "xproto/GetWindowAttributes")
54		r, ok := xproto.ParseGetWindowAttributesReply(raw)
55		if !ok || r.BackingStore() != xproto.BackingStoreNotUseful || r.Visual() != xproto.Visualid(u32(raw, 8)) ||
56			r.Class() != xproto.WindowClassInputOutput || r.BitGravity() != xproto.GravityBitForget ||
57			r.WinGravity() != xproto.GravityNorthWest || r.BackingPlanes() != 0xffffffff || r.BackingPixel() != 0 ||
58			!r.SaveUnder() || !r.MapIsInstalled() || r.MapState() != xproto.MapStateUnmapped || r.OverrideRedirect() ||
59			r.Colormap() != xproto.Colormap(u32(raw, 28)) || r.AllEventMasks() != 0 || r.YourEventMask() != 0 ||
60			r.DoNotPropagateMask() != 0 {
61			t.Fatal("decoded attributes")
62		}
63	})
64	t.Run("xproto/InternAtom", func(t *testing.T) {
65		raw := loadReply(t, "xproto/InternAtom")
66		r, ok := xproto.ParseInternAtomReply(raw)
67		if !ok || r.Atom() == xproto.AtomNone || r.Atom() != xproto.Atom(u32(raw, 8)) {
68			t.Fatal("atom")
69		}
70	})
71	t.Run("xproto/GetProperty", func(t *testing.T) {
72		raw := loadReply(t, "xproto/GetProperty")
73		r, ok := xproto.ParseGetPropertyReply(raw)
74		if !ok || r.Format() != 8 || r.Type() != xproto.AtomString || r.BytesAfter() != 0 || r.ValueLen() != 3 ||
75			string(r.Value()) != "abc" || cap(r.Value()) != 3 {
76			t.Fatalf("value %q", r.Value())
77		}
78	})
79	t.Run("xproto/GetPropertyEmpty", func(t *testing.T) {
80		raw := loadReply(t, "xproto/GetPropertyEmpty")
81		r, ok := xproto.ParseGetPropertyReply(raw)
82		if !ok || r.Format() != 0 || r.Type() != xproto.AtomNone || r.ValueLen() != 0 || len(r.Value()) != 0 {
83			t.Fatal("empty property")
84		}
85	})
86	t.Run("xproto/QueryExtension", func(t *testing.T) {
87		raw := loadReply(t, "xproto/QueryExtension")
88		r, ok := xproto.ParseQueryExtensionReply(raw)
89		if !ok || !r.Present() || r.MajorOpcode() < 128 || r.FirstEvent() != raw[10] || r.FirstError() != raw[11] {
90			t.Fatal("XFIXES query")
91		}
92	})
93	t.Run("xproto/ListExtensions", func(t *testing.T) {
94		raw := loadReply(t, "xproto/ListExtensions")
95		r, ok := xproto.ParseListExtensionsReply(raw)
96		if !ok {
97			t.Fatal("parse")
98		}
99		it := r.Names()
100		var names []string
101		total := 32
102		for it.Next() {
103			v := it.Value()
104			if int(v.NameLen()) != len(v.Name()) || v.Size() != 1+len(v.Name()) {
105				t.Fatal("STR view")
106			}
107			names = append(names, string(v.Name()))
108			total += v.Size()
109		}
110		if it.Err() || len(names) != int(r.NamesLen()) || total > len(raw) || len(raw)-total > 3 {
111			t.Fatalf("iterated %d of %d, %d/%d bytes", len(names), r.NamesLen(), total, len(raw))
112		}
113		found := map[string]bool{}
114		for _, n := range names {
115			found[n] = true
116		}
117		for _, want := range []string{"XFIXES", "RENDER", "BIG-REQUESTS", "Composite", "X-Resource"} {
118			if !found[want] {
119				t.Fatalf("missing %s in %q", want, names)
120			}
121		}
122	})
123	t.Run("xfixes/QueryVersion", func(t *testing.T) {
124		r, ok := xfixes.ParseQueryVersionReply(loadReply(t, "xfixes/QueryVersion"))
125		if !ok || r.MajorVersion() != 6 || r.MinorVersion() != 0 {
126			t.Fatal("version")
127		}
128	})
129	t.Run("res/QueryClientIds", func(t *testing.T) {
130		r, ok := res.ParseQueryClientIdsReply(loadReply(t, "res/QueryClientIds"))
131		if !ok || r.NumIds() != 2 {
132			t.Fatal("parse")
133		}
134		// Spec client 0 asks for every client's XID: the server client and ours.
135		it := r.Ids()
136		var clients []uint32
137		for it.Next() {
138			v := it.Value()
139			if v.Spec().Mask != res.ClientIdMaskClientXID || v.Length() != 0 || v.Value().Len() != 0 || v.Size() != 12 {
140				t.Fatal("ClientIdValue view")
141			}
142			clients = append(clients, v.Spec().Client)
143		}
144		if it.Err() || len(clients) != 2 || clients[0] != 0 || clients[1] == 0 {
145			t.Fatalf("clients %x", clients)
146		}
147	})
148	t.Run("render/QueryPictFormats", func(t *testing.T) {
149		raw := loadReply(t, "render/QueryPictFormats")
150		r, ok := render.ParseQueryPictFormatsReply(raw)
151		if !ok || r.NumScreens() != 1 || r.Formats().Len() != int(r.NumFormats()) || r.Subpixels().Len() != int(r.NumSubpixel()) {
152			t.Fatal("counts")
153		}
154		formats := map[render.Pictformat]bool{}
155		for i := range r.Formats().Len() {
156			f := r.Formats().At(i)
157			formats[f.Id] = true
158		}
159		screens := r.Screens()
160		visuals := 0
161		for screens.Next() {
162			depths := screens.Value().Depths()
163			for depths.Next() {
164				vs := depths.Value().Visuals()
165				for i := range vs.Len() {
166					if !formats[vs.At(i).Format] {
167						t.Fatal("visual references an unknown format")
168					}
169					visuals++
170				}
171			}
172			if depths.Err() {
173				t.Fatal("depth iterator")
174			}
175		}
176		if screens.Err() || visuals == 0 {
177			t.Fatal("screen iterator")
178		}
179		for i := range r.Subpixels().Len() {
180			if r.Subpixels().At(i) != render.SubPixelUnknown {
181				t.Fatal("Xvfb subpixel order")
182			}
183		}
184	})
185}
186
187func TestReplyParseRejects(t *testing.T) {
188	raw := loadReply(t, "xproto/QueryTree")
189	if _, ok := xproto.ParseQueryTreeReply(raw[:31]); ok {
190		t.Fatal("short fixed prefix accepted")
191	}
192	if _, ok := xproto.ParseQueryTreeReply(raw[:len(raw)-1]); ok {
193		t.Fatal("truncated list accepted")
194	}
195	raw = loadReply(t, "xproto/GetWindowAttributes")
196	if _, ok := xproto.ParseGetWindowAttributesReply(raw[:43]); ok {
197		t.Fatal("44-byte reply prefix not enforced")
198	}
199	raw = bytes.Clone(loadReply(t, "xproto/ListExtensions"))
200	raw[1] = 255 // names_len: far more STRs than the reply holds
201	if _, ok := xproto.ParseListExtensionsReply(raw); ok {
202		t.Fatal("STR count beyond the reply accepted")
203	}
204	prop := bytes.Clone(loadReply(t, "xproto/GetProperty"))
205	prop[1] = 32 // 3 items of 4 bytes overrun the 36-byte reply
206	if _, ok := xproto.ParseGetPropertyReply(prop); ok {
207		t.Fatal("format*value_len overrun accepted")
208	}
209}
210
211func TestReplyAllocs(t *testing.T) {
212	prop := loadReply(t, "xproto/GetProperty")
213	tree := loadReply(t, "xproto/QueryTree")
214	exts := loadReply(t, "xproto/ListExtensions")
215	formats := loadReply(t, "render/QueryPictFormats")
216	ids := loadReply(t, "res/QueryClientIds")
217	var sink uint64
218	n := testing.AllocsPerRun(1000, func() {
219		p, _ := xproto.ParseGetPropertyReply(prop)
220		v := p.Value()
221		sink += uint64(len(v)) + uint64(p.Type()) + uint64(p.Format())
222		q, _ := xproto.ParseQueryTreeReply(tree)
223		c := q.Children()
224		for i := range c.Len() {
225			sink += uint64(c.At(i))
226		}
227		sink += uint64(q.Root()) + uint64(q.Parent())
228		l, _ := xproto.ParseListExtensionsReply(exts)
229		it := l.Names()
230		for it.Next() {
231			sink += uint64(len(it.Value().Name()))
232		}
233		f, _ := render.ParseQueryPictFormatsReply(formats)
234		fl := f.Formats()
235		for i := range fl.Len() {
236			sink += uint64(fl.At(i).Id)
237		}
238		s := f.Screens()
239		for s.Next() {
240			d := s.Value().Depths()
241			for d.Next() {
242				vs := d.Value().Visuals()
243				for i := range vs.Len() {
244					sink += uint64(vs.At(i).Visual)
245				}
246			}
247		}
248		sp := f.Subpixels()
249		for i := range sp.Len() {
250			sink += uint64(sp.At(i))
251		}
252		r, _ := res.ParseQueryClientIdsReply(ids)
253		ri := r.Ids()
254		for ri.Next() {
255			sink += uint64(ri.Value().Value().Len())
256		}
257	})
258	if n != 0 {
259		t.Fatalf("reply parse/getters/iteration allocate %v times", n)
260	}
261	_ = sink
262}
263
264func BenchmarkGetPropertyReply(b *testing.B) {
265	raw := loadReply(b, "xproto/GetProperty")
266	var sink int
267	b.ReportAllocs()
268	for b.Loop() {
269		r, ok := xproto.ParseGetPropertyReply(raw)
270		if !ok {
271			b.Fatal("parse")
272		}
273		sink += len(r.Value()) + int(r.Type())
274	}
275	_ = sink
276}
277
278func BenchmarkQueryTreeReply(b *testing.B) {
279	raw := loadReply(b, "xproto/QueryTree")
280	var sink xproto.Window
281	b.ReportAllocs()
282	for b.Loop() {
283		r, _ := xproto.ParseQueryTreeReply(raw)
284		c := r.Children()
285		for i := range c.Len() {
286			sink ^= c.At(i)
287		}
288	}
289	_ = sink
290}
291
292func BenchmarkListExtensionsReply(b *testing.B) {
293	raw := loadReply(b, "xproto/ListExtensions")
294	var sink int
295	b.ReportAllocs()
296	for b.Loop() {
297		r, _ := xproto.ParseListExtensionsReply(raw)
298		it := r.Names()
299		for it.Next() {
300			sink += len(it.Value().Name())
301		}
302	}
303	_ = sink
304}

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 1

↑↓ moveenter openesc close