1package server
2
3import (
4 "bytes"
5 "encoding/binary"
6 "runtime"
7 "testing"
8 "time"
9
10 "golang.org/x/sys/unix"
11)
12
13// Tests in this file drive the runtime with hand-built interface tables and a
14// raw wire-protocol peer: no client library and no generated bindings.
15
16func i32(v int32) []byte { return u32(uint32(v)) }
17
18func wireArray(b []byte) []byte {
19 out := u32(uint32(len(b)))
20 out = append(out, b...)
21 for len(out)%4 != 0 {
22 out = append(out, 0)
23 }
24 return out
25}
26
27// wireEvent is one message read from the server.
28type wireEvent struct {
29 id uint32
30 opcode uint16
31 body []byte
32}
33
34func readFull(t *testing.T, fd, n int) ([]byte, bool) {
35 t.Helper()
36 buf := make([]byte, n)
37 for got := 0; got < n; {
38 m, err := unix.Read(fd, buf[got:])
39 if err == unix.EINTR {
40 continue
41 }
42 if err != nil {
43 t.Fatalf("read: %v", err)
44 }
45 if m == 0 {
46 if got != 0 {
47 t.Fatalf("EOF after %d of %d bytes", got, n)
48 }
49 return nil, false
50 }
51 got += m
52 }
53 return buf, true
54}
55
56// readEvent reads one message; ok is false on a clean EOF.
57func readEvent(t *testing.T, fd int) (ev wireEvent, ok bool) {
58 t.Helper()
59 hdr, ok := readFull(t, fd, 8)
60 if !ok {
61 return ev, false
62 }
63 size := int(binary.LittleEndian.Uint32(hdr[4:]) >> 16)
64 ev = wireEvent{id: binary.LittleEndian.Uint32(hdr), opcode: uint16(binary.LittleEndian.Uint32(hdr[4:]))}
65 if size > 8 {
66 ev.body, _ = readFull(t, fd, size-8)
67 }
68 return ev, true
69}
70
71// rtEnv is a running display with one adopted client and the raw peer end.
72type rtEnv struct {
73 d *Display
74 client Client
75 peer int
76}
77
78func newRTEnv(t *testing.T) *rtEnv {
79 t.Helper()
80 d, _ := clientDisplay(t)
81 fds := socketPair(t)
82 c := adopt(t, d, fds.srv)
83 if err := unix.SetsockoptTimeval(fds.peer, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 3}); err != nil {
84 t.Fatal(err)
85 }
86 return &rtEnv{d: d, client: c, peer: fds.peer}
87}
88
89func (e *rtEnv) do(t *testing.T, fn func()) {
90 t.Helper()
91 if !e.d.Do(fn) {
92 t.Fatal("display stopped")
93 }
94}
95
96func (e *rtEnv) send(t *testing.T, msg []byte) {
97 t.Helper()
98 if _, err := unix.Write(e.peer, msg); err != nil {
99 t.Fatal(err)
100 }
101}
102
103func (e *rtEnv) resource(t *testing.T, iface *Interface, id uint32, h Handler) *Resource {
104 t.Helper()
105 var r *Resource
106 var err error
107 e.do(t, func() { r, err = e.client.CreateResource(iface, 3, id, h) })
108 if err != nil {
109 t.Fatal(err)
110 }
111 return r
112}
113
114func rtInterface(t *testing.T, name string) *Interface {
115 t.Helper()
116 iface := &Interface{
117 Name: name,
118 Version: 3,
119 Requests: []Message{
120 {Name: "all", Signature: "iufsaon"},
121 {Name: "nulls", Signature: "?s?o"},
122 },
123 Events: []Message{
124 {Name: "all", Signature: "iufsaon"},
125 {Name: "nulls", Signature: "?s?oa"},
126 },
127 }
128 if err := NewInterfaces(iface); err != nil {
129 t.Fatal(err)
130 }
131 return iface
132}
133
134type rtRequest struct {
135 i int32
136 u uint32
137 f Fixed
138 s string
139 a []byte
140 obj *Resource
141 newID uint32
142 nullS string
143 nullO *Resource
144 objID uint32
145 opcode uint32
146}
147
148func TestRuntimeArgumentRoundTrip(t *testing.T) {
149 iface := rtInterface(t, "runtime_round_trip_test")
150 e := newRTEnv(t)
151 // The handler runs on the display goroutine: it reports what it decoded
152 // over a channel, which also orders it before the test's reads.
153 reqs := make(chan rtRequest, 2)
154 var newRes *Resource // written by the handler, read via e.do after the event
155 handler := func(r *Resource, opcode uint32, args []Arg) {
156 got := rtRequest{opcode: opcode}
157 defer func() { reqs <- got }()
158 switch opcode {
159 case 0:
160 got.i, got.u, got.f = args[0].Int(), args[1].Uint(), args[2].Fixed()
161 got.s, got.a, got.obj, got.newID = args[3].String(), args[4].Array(), args[5].Resource(), args[6].NewID()
162 got.objID = got.obj.ID()
163 // Echo everything back as an event, with a server-created resource
164 // for the new_id argument.
165 var err error
166 if newRes, err = r.Client().CreateResource(iface, 1, 0, nil); err != nil {
167 t.Error(err)
168 return
169 }
170 var p runtime.Pinner
171 defer p.Unpin()
172 r.PostEvent(0, Int(got.i), Uint(got.u), FixedArg(FixedFromFloat(got.f.Float())),
173 String(&p, got.s, false), Array(&p, got.a), Object(got.obj), NewID(newRes))
174 case 1:
175 got.nullS, got.nullO = args[0].String(), args[1].Resource()
176 var p runtime.Pinner
177 defer p.Unpin()
178 r.PostEvent(1, String(&p, "", true), Object(nil), Array(&p, nil))
179 }
180 }
181 e.resource(t, iface, 2, handler)
182
183 payload := []byte{1, 2, 3, 4, 5}
184 e.send(t, wireMsg(2, 0, i32(-7), u32(0xdeadbeef), i32(-384), wireString("héllo"), wireArray(payload), u32(2), u32(3)))
185 ev, ok := readEvent(t, e.peer)
186 if !ok {
187 t.Fatal("EOF before event")
188 }
189 got := <-reqs
190 if got.i != -7 || got.u != 0xdeadbeef || got.f.Float() != -1.5 || got.s != "héllo" ||
191 !bytes.Equal(got.a, payload) || got.obj == nil || got.objID != 2 || got.newID != 3 {
192 t.Fatalf("request decoded as %+v", got)
193 }
194 var newID uint32
195 e.do(t, func() { newID = newRes.ID() })
196 if newID < 0xff000000 {
197 t.Fatalf("server-allocated id %#x", newID)
198 }
199 want := wireMsg(2, 0, i32(-7), u32(0xdeadbeef), i32(-384), wireString("héllo"), wireArray(payload), u32(2), u32(newID))
200 have := wireMsg(ev.id, ev.opcode, ev.body)
201 if !bytes.Equal(have, want) {
202 t.Fatalf("event bytes\n got %x\nwant %x", have, want)
203 }
204
205 // Null string and object in, null string and empty array out.
206 e.send(t, wireMsg(2, 1, u32(0), u32(0)))
207 ev, ok = readEvent(t, e.peer)
208 if !ok {
209 t.Fatal("EOF before nulls event")
210 }
211 got = <-reqs
212 if got.opcode != 1 || got.nullS != "" || got.nullO != nil {
213 t.Fatalf("null request decoded as %+v", got)
214 }
215 want = wireMsg(2, 1, u32(0), u32(0), wireArray(nil))
216 if have := wireMsg(ev.id, ev.opcode, ev.body); !bytes.Equal(have, want) {
217 t.Fatalf("nulls event\n got %x\nwant %x", have, want)
218 }
219}
220
221func TestResourcePostError(t *testing.T) {
222 iface := rtInterface(t, "runtime_post_error_test")
223 e := newRTEnv(t)
224 r := e.resource(t, iface, 2, nil)
225 gone := make(chan struct{})
226 e.do(t, func() {
227 e.client.OnDestroy(func() { close(gone) })
228 r.PostError(7, "100% bad")
229 })
230 ev, ok := readEvent(t, e.peer)
231 if !ok {
232 t.Fatal("EOF before wl_display.error")
233 }
234 want := wireMsg(1, 0, u32(2), u32(7), wireString("100% bad"))
235 if have := wireMsg(ev.id, ev.opcode, ev.body); !bytes.Equal(have, want) {
236 t.Fatalf("error event\n got %x\nwant %x", have, want)
237 }
238 // libwayland disconnects the client after a protocol error, once the
239 // connection is next serviced: send a wl_display.sync to trigger that.
240 e.send(t, wireMsg(1, 0, u32(3)))
241 select {
242 case <-gone:
243 case <-time.After(3 * time.Second):
244 t.Fatal("client not disconnected after the protocol error")
245 }
246 if _, ok := readEvent(t, e.peer); ok {
247 t.Fatal("unexpected data after the error")
248 }
249 e.do(t, func() {
250 if r.Alive() {
251 t.Error("resource alive after the client was disconnected")
252 }
253 r.PostError(1, "ignored") // no-op once gone
254 })
255}
256
257func TestGlobalRemoveEmitsGlobalRemove(t *testing.T) {
258 iface := rtInterface(t, "runtime_global_remove_test")
259 e := newRTEnv(t)
260 var g *Global
261 var err error
262 e.do(t, func() {
263 g, err = e.d.AddGlobal(iface, 3, func(Client, uint32, uint32) {})
264 })
265 if err != nil {
266 t.Fatal(err)
267 }
268 e.send(t, wireMsg(1, 1, u32(2))) // wl_display.get_registry(new_id 2)
269 var name uint32
270 for name == 0 {
271 ev, ok := readEvent(t, e.peer)
272 if !ok {
273 t.Fatal("EOF before the global was advertised")
274 }
275 if ev.id != 2 || ev.opcode != 0 {
276 continue
277 }
278 strLen := int(binary.LittleEndian.Uint32(ev.body[4:]))
279 if string(ev.body[8:8+strLen-1]) == iface.Name {
280 name = binary.LittleEndian.Uint32(ev.body)
281 }
282 }
283 e.do(t, func() {
284 g.Remove()
285 g.Remove() // idempotent
286 (*Global)(nil).Remove()
287 })
288 ev, ok := readEvent(t, e.peer)
289 if !ok {
290 t.Fatal("EOF before global_remove")
291 }
292 if ev.id != 2 || ev.opcode != 1 || !bytes.Equal(ev.body, u32(name)) {
293 t.Fatalf("got %+v, want wl_registry.global_remove(%d)", ev, name)
294 }
295}
296
297func TestResourceDestroyLifecycle(t *testing.T) {
298 iface := rtInterface(t, "runtime_destroy_test")
299 e := newRTEnv(t)
300 r := e.resource(t, iface, 2, nil)
301 destroyed := 0
302 e.do(t, func() {
303 r.OnDestroy = func() { destroyed++ }
304 before := LiveResources()
305 if v := r.Version(); v != 3 {
306 t.Errorf("Version = %d, want 3", v)
307 }
308 if r.Iface() != iface || r.ID() != 2 || r.Client() != e.client || !r.Alive() {
309 t.Errorf("accessors before destroy: %v %d %v %v", r.Iface(), r.ID(), r.Client(), r.Alive())
310 }
311 r.Destroy()
312 r.Destroy() // no-op once gone
313 if n := LiveResources(); n != before-1 {
314 t.Errorf("LiveResources %d -> %d, want one fewer", before, n)
315 }
316 if destroyed != 1 || r.Alive() {
317 t.Errorf("destroy callback ran %d times, alive %v", destroyed, r.Alive())
318 }
319 if r.Version() != 0 || r.ID() != 0 || r.Client() != (Client{}) {
320 t.Errorf("accessors after destroy: %d %d %v", r.Version(), r.ID(), r.Client())
321 }
322 r.PostEvent(0, Int(0), Uint(0), FixedArg(0), Arg(0), Arg(0), Arg(0), Arg(0)) // no-op
323 })
324 // Destroying a client-allocated id tells the client it can reuse it. A
325 // wl_display.sync round trip (callback id 3) ensures the events are flushed.
326 e.send(t, wireMsg(1, 0, u32(3)))
327 var sawDelete bool
328 for done := false; !done; {
329 ev, ok := readEvent(t, e.peer)
330 if !ok {
331 t.Fatal("EOF before the sync callback")
332 }
333 switch {
334 case ev.id == 1 && ev.opcode == 1 && bytes.Equal(ev.body, u32(2)): // wl_display.delete_id(2)
335 sawDelete = true
336 case ev.id == 3: // wl_callback.done
337 done = true
338 }
339 }
340 if !sawDelete {
341 t.Fatal("no wl_display.delete_id(2) after Destroy")
342 }
343}