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}