1package server
2
3import (
4 "context"
5 "encoding/binary"
6 "errors"
7 "sync"
8 "testing"
9 "time"
10
11 "golang.org/x/sys/unix"
12)
13
14// clientDisplay runs a real display and returns it with a stop function that
15// waits for Run to return (and so for display destruction).
16func clientDisplay(t *testing.T) (*Display, func()) {
17 t.Helper()
18 d, err := NewDisplay()
19 if err != nil {
20 t.Fatal(err)
21 }
22 ctx, cancel := context.WithCancel(context.Background())
23 done := make(chan error, 1)
24 go func() { done <- d.Run(ctx) }()
25 var once bool
26 stop := func() {
27 if once {
28 return
29 }
30 once = true
31 cancel()
32 select {
33 case err := <-done:
34 if err != nil {
35 t.Error(err)
36 }
37 case <-time.After(3 * time.Second):
38 t.Fatal("Run did not return")
39 }
40 }
41 t.Cleanup(stop)
42 return d, stop
43}
44
45// sockPair is a connected socketpair. srv is meant to be adopted by the
46// display; peer is the test's end, closed at most once.
47type sockPair struct {
48 srv, peer int
49 once sync.Once
50}
51
52func socketPair(t *testing.T) *sockPair {
53 t.Helper()
54 raw, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
55 if err != nil {
56 t.Fatal(err)
57 }
58 p := &sockPair{srv: raw[0], peer: raw[1]}
59 t.Cleanup(p.closePeer)
60 return p
61}
62
63// closePeer closes the peer end; it is idempotent, so a test may call it and
64// the cleanup still will not close a reused descriptor number.
65func (p *sockPair) closePeer() { p.once.Do(func() { _ = unix.Close(p.peer) }) }
66
67// fileID identifies the open file behind fd, immune to fd number reuse.
68type fileID struct{ dev, ino uint64 }
69
70func identify(t *testing.T, fd int) fileID {
71 t.Helper()
72 var st unix.Stat_t
73 if err := unix.Fstat(fd, &st); err != nil {
74 t.Fatal(err)
75 }
76 return fileID{uint64(st.Dev), st.Ino}
77}
78
79// stillOpen reports whether fd still refers to the file id.
80func stillOpen(fd int, id fileID) bool {
81 var st unix.Stat_t
82 if unix.Fstat(fd, &st) != nil {
83 return false
84 }
85 return fileID{uint64(st.Dev), st.Ino} == id
86}
87
88func adopt(t *testing.T, d *Display, fd int) Client {
89 t.Helper()
90 var c Client
91 var err error
92 if !d.Do(func() { c, err = d.CreateClient(fd) }) {
93 t.Fatal("display stopped")
94 }
95 if err != nil {
96 t.Fatal(err)
97 }
98 return c
99}
100
101func TestClientFD(t *testing.T) {
102 d, _ := clientDisplay(t)
103 fds := socketPair(t)
104 c := adopt(t, d, fds.srv)
105 var (
106 got, zero int
107 err, zerr error
108 allocs float64
109 )
110 if !d.Do(func() {
111 got, err = c.FD()
112 zero, zerr = Client{}.FD()
113 allocs = testing.AllocsPerRun(100, func() { _, _ = c.FD() })
114 }) {
115 t.Fatal("display stopped")
116 }
117 if err != nil || got != fds.srv {
118 t.Fatalf("FD = %d, %v; want %d", got, err, fds.srv)
119 }
120 if zerr == nil || zero != -1 {
121 t.Fatalf("invalid client FD = %d, %v", zero, zerr)
122 }
123 if allocs != 0 {
124 t.Fatalf("FD allocs = %g, want 0", allocs)
125 }
126}
127
128func TestCreateClientFailureKeepsFD(t *testing.T) {
129 d, _ := clientDisplay(t)
130 var p [2]int
131 if err := unix.Pipe2(p[:], unix.O_CLOEXEC); err != nil {
132 t.Fatal(err)
133 }
134 defer unix.Close(p[0])
135 defer unix.Close(p[1])
136 pipeID := identify(t, p[0])
137 var err, errNeg error
138 var c, cn Client
139 if !d.Do(func() {
140 c, err = d.CreateClient(p[0]) // not a socket: SO_PEERCRED fails
141 cn, errNeg = d.CreateClient(-1)
142 }) {
143 t.Fatal("display stopped")
144 }
145 if !errors.Is(err, unix.ENOTSOCK) {
146 t.Fatalf("error %v does not wrap ENOTSOCK", err)
147 }
148 if err == nil || c != (Client{}) || errNeg == nil || cn != (Client{}) {
149 t.Fatalf("got %v %v, %v %v; want errors", c, err, cn, errNeg)
150 }
151 if !stillOpen(p[0], pipeID) {
152 t.Fatal("failed CreateClient closed the fd")
153 }
154}
155
156func TestCreateClientOwnsFD(t *testing.T) {
157 d, _ := clientDisplay(t)
158 fds := socketPair(t)
159 c := adopt(t, d, fds.srv)
160 gone := make(chan struct{})
161 d.Do(func() { c.OnDestroy(func() { close(gone) }) })
162 id := identify(t, fds.srv)
163 fds.closePeer()
164 select {
165 case <-gone:
166 case <-time.After(3 * time.Second):
167 t.Fatal("client not destroyed")
168 }
169 // The listener runs before libwayland closes the fd; Do returns only once
170 // the destruction that ran it has finished.
171 d.Do(func() {})
172 if stillOpen(fds.srv, id) {
173 t.Fatal("libwayland did not close the adopted fd")
174 }
175}
176
177// A raw client speaks the wire protocol over the other socketpair end.
178func wireMsg(id uint32, opcode uint16, args ...[]byte) []byte {
179 var body []byte
180 for _, a := range args {
181 body = append(body, a...)
182 }
183 b := make([]byte, 8, 8+len(body))
184 binary.LittleEndian.PutUint32(b, id)
185 binary.LittleEndian.PutUint32(b[4:], uint32(8+len(body))<<16|uint32(opcode))
186 return append(b, body...)
187}
188
189func u32(v uint32) []byte { return binary.LittleEndian.AppendUint32(nil, v) }
190
191func wireString(s string) []byte {
192 b := u32(uint32(len(s) + 1))
193 b = append(b, s...)
194 b = append(b, 0)
195 for len(b)%4 != 0 {
196 b = append(b, 0)
197 }
198 return b
199}
200
201func TestCreateClientBindsRegistry(t *testing.T) {
202 iface := &Interface{Name: "adopted_client_test", Version: 1}
203 if err := NewInterfaces(iface); err != nil {
204 t.Fatal(err)
205 }
206 d, err := NewDisplay()
207 if err != nil {
208 t.Fatal(err)
209 }
210 bound := make(chan Client, 1)
211 if err := d.CreateGlobal(iface, 1, func(c Client, version, id uint32) { bound <- c }); err != nil {
212 t.Fatal(err)
213 }
214 ctx, cancel := context.WithCancel(context.Background())
215 done := make(chan error, 1)
216 go func() { done <- d.Run(ctx) }()
217 defer func() { cancel(); <-done }()
218
219 fds := socketPair(t)
220 c := adopt(t, d, fds.srv)
221 if err := unix.SetsockoptTimeval(fds.peer, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 3}); err != nil {
222 t.Fatal(err)
223 }
224 // wl_display.get_registry(new_id 2), then wl_display.sync(new_id 3).
225 req := append(wireMsg(1, 1, u32(2)), wireMsg(1, 0, u32(3))...)
226 if _, err := unix.Write(fds.peer, req); err != nil {
227 t.Fatal(err)
228 }
229 // Read until the sync callback's done event (object 3), collecting globals.
230 var stream []byte
231 var name uint32
232 buf := make([]byte, 4096)
233 for sawDone := false; !sawDone; {
234 n, err := unix.Read(fds.peer, buf)
235 if err != nil || n == 0 {
236 t.Fatalf("read: %d, %v", n, err)
237 }
238 stream = append(stream, buf[:n]...)
239 for len(stream) >= 8 {
240 size := int(binary.LittleEndian.Uint32(stream[4:]) >> 16)
241 if size < 8 || len(stream) < size {
242 break
243 }
244 id, opcode := binary.LittleEndian.Uint32(stream), uint16(binary.LittleEndian.Uint32(stream[4:]))
245 body := stream[8:size]
246 if id == 2 && opcode == 0 { // wl_registry.global(name, interface, version)
247 strLen := int(binary.LittleEndian.Uint32(body[4:]))
248 if string(body[8:8+strLen-1]) == iface.Name {
249 name = binary.LittleEndian.Uint32(body)
250 }
251 }
252 if id == 3 {
253 sawDone = true
254 }
255 stream = stream[size:]
256 }
257 }
258 if name == 0 {
259 t.Fatal("global not advertised to the adopted client")
260 }
261 // wl_registry.bind(name, interface, version, new_id 4)
262 if _, err := unix.Write(fds.peer, wireMsg(2, 0, u32(name), wireString(iface.Name), u32(1), u32(4))); err != nil {
263 t.Fatal(err)
264 }
265 select {
266 case got := <-bound:
267 if got != c {
268 t.Fatalf("bind client %v, want adopted %v", got, c)
269 }
270 case <-time.After(3 * time.Second):
271 t.Fatal("bind never reached the global")
272 }
273}
274
275func TestOnDestroyOnDisconnect(t *testing.T) {
276 iface := &Interface{Name: "on_destroy_test", Version: 1}
277 if err := NewInterfaces(iface); err != nil {
278 t.Fatal(err)
279 }
280 d, _ := clientDisplay(t)
281 fds := socketPair(t)
282 c := adopt(t, d, fds.srv)
283 var (
284 order []int
285 resAlive, fdOK bool
286 other = map[Client]int{c: 1}
287 freeBefore, free int
288 fired = make(chan struct{})
289 res *Resource
290 createErr, fdError error
291 )
292 if !d.Do(func() {
293 res, createErr = c.CreateResource(iface, 1, 2, nil)
294 for i := 1; i <= 3; i++ {
295 c.OnDestroy(func() {
296 order = append(order, i)
297 if i == 3 {
298 var fd int
299 fd, fdError = c.FD()
300 fdOK = fd == fds.srv
301 resAlive = res.Alive()
302 delete(other, c)
303 close(fired)
304 }
305 })
306 }
307 freeBefore = len(listeners.free) // after the slots are taken
308 c.OnDestroy(nil)
309 Client{}.OnDestroy(func() { t.Error("zero client listener ran") })
310 }) {
311 t.Fatal("display stopped")
312 }
313 if createErr != nil {
314 t.Fatal(createErr)
315 }
316 select {
317 case <-fired:
318 t.Fatal("fired before disconnect")
319 case <-time.After(50 * time.Millisecond):
320 }
321 fds.closePeer()
322 select {
323 case <-fired:
324 case <-time.After(3 * time.Second):
325 t.Fatal("OnDestroy did not fire on disconnect")
326 }
327 d.Do(func() { free = len(listeners.free) })
328 if len(order) != 3 || order[0] != 1 || order[1] != 2 || order[2] != 3 {
329 t.Fatalf("order %v, want [1 2 3]", order)
330 }
331 if fdError != nil || !fdOK || !resAlive {
332 t.Fatalf("during OnDestroy: fd ok=%v err=%v resource alive=%v", fdOK, fdError, resAlive)
333 }
334 if len(other) != 0 || res.Alive() {
335 t.Fatalf("map %v, resource alive %v", other, res.Alive())
336 }
337 if free != freeBefore+3 || len(live.clientGone) != 0 {
338 t.Fatalf("listener slots not recycled: free %d -> %d, live %d", freeBefore, free, len(live.clientGone))
339 }
340}
341
342func TestOnDestroyOnDisplayClose(t *testing.T) {
343 d, err := NewDisplay()
344 if err != nil {
345 t.Fatal(err)
346 }
347 fds := socketPair(t)
348 c, err := d.CreateClient(fds.srv)
349 if err != nil {
350 t.Fatal(err)
351 }
352 n := 0
353 c.OnDestroy(func() { n++ })
354 c.OnDestroy(func() { n += 10 })
355 d.Close()
356 d.Close()
357 if n != 11 {
358 t.Fatalf("listeners ran %d, want 11 (each once)", n)
359 }
360}
361
362func TestOnDestroyOnRunStop(t *testing.T) {
363 d, stop := clientDisplay(t)
364 fds := socketPair(t)
365 c := adopt(t, d, fds.srv)
366 n := 0
367 d.Do(func() { c.OnDestroy(func() { n++ }) })
368 stop()
369 if n != 1 {
370 t.Fatalf("listener ran %d times, want 1", n)
371 }
372}
373
374// A client destroyed at disconnect must not fire again when the display stops.
375func TestOnDestroyOnce(t *testing.T) {
376 d, stop := clientDisplay(t)
377 fds := socketPair(t)
378 c := adopt(t, d, fds.srv)
379 fired := make(chan struct{}, 4)
380 d.Do(func() { c.OnDestroy(func() { fired <- struct{}{} }) })
381 fds.closePeer()
382 select {
383 case <-fired:
384 case <-time.After(3 * time.Second):
385 t.Fatal("OnDestroy did not fire")
386 }
387 stop()
388 if len(fired) != 0 {
389 t.Fatal("OnDestroy fired twice")
390 }
391}
392
393// A peer that already hung up does not stop adoption; the client is then
394// destroyed and its listener runs.
395func TestCreateClientPeerAlreadyClosed(t *testing.T) {
396 d, _ := clientDisplay(t)
397 fds := socketPair(t)
398 fds.closePeer()
399 id := identify(t, fds.srv) // before adoption: the client may die at once
400 gone := make(chan struct{})
401 var err error
402 // Adopt and register in one call, before the loop can destroy the client.
403 if !d.Do(func() {
404 var c Client
405 if c, err = d.CreateClient(fds.srv); err == nil {
406 c.OnDestroy(func() { close(gone) })
407 }
408 }) {
409 t.Fatal("display stopped")
410 }
411 if err != nil {
412 t.Fatal(err)
413 }
414 select {
415 case <-gone:
416 case <-time.After(3 * time.Second):
417 t.Fatal("client with a closed peer was not destroyed")
418 }
419 d.Do(func() {})
420 if stillOpen(fds.srv, id) {
421 t.Fatal("adopted fd not closed")
422 }
423}
424
425// A listener may register on another live client from inside its callback,
426// even though it is handed the slot that is firing.
427func TestOnDestroyRegistersOnOtherClient(t *testing.T) {
428 d, _ := clientDisplay(t)
429 a, b := socketPair(t), socketPair(t)
430 ca, cb := adopt(t, d, a.srv), adopt(t, d, b.srv)
431 aGone := make(chan struct{})
432 bGone := make(chan struct{}, 2)
433 var slotA, slotB uintptr
434 d.Do(func() {
435 for l := range live.clientGone {
436 t.Errorf("unexpected live listener %#x", l)
437 }
438 ca.OnDestroy(func() {
439 cb.OnDestroy(func() { bGone <- struct{}{} })
440 for l := range live.clientGone {
441 slotB = l
442 }
443 close(aGone)
444 })
445 for l := range live.clientGone {
446 slotA = l
447 }
448 })
449 a.closePeer()
450 select {
451 case <-aGone:
452 case <-time.After(3 * time.Second):
453 t.Fatal("first client's listener did not fire")
454 }
455 if slotA == 0 || slotA != slotB {
456 t.Fatalf("freed slot not reused: %#x then %#x", slotA, slotB)
457 }
458 select {
459 case <-bGone:
460 t.Fatal("second client's listener fired early")
461 case <-time.After(50 * time.Millisecond):
462 }
463 b.closePeer()
464 select {
465 case <-bGone:
466 case <-time.After(3 * time.Second):
467 t.Fatal("listener registered from a callback did not fire")
468 }
469 time.Sleep(50 * time.Millisecond)
470 if len(bGone) != 0 {
471 t.Fatal("listener fired twice")
472 }
473}
474
475// More listeners than one mapped chunk holds: all fire once, every slot comes
476// back, and a later registration reuses a recycled slot.
477func TestOnDestroyManyListeners(t *testing.T) {
478 d, _ := clientDisplay(t)
479 fds := socketPair(t)
480 fds2 := socketPair(t)
481 c := adopt(t, d, fds.srv)
482 const n = 3*listenerChunkSize/listenerSlot + 5
483 var fired, chunksBefore int
484 done := make(chan struct{})
485 var before, after int
486 d.Do(func() {
487 for i := 0; i < n; i++ {
488 c.OnDestroy(func() {
489 fired++
490 if fired == n {
491 close(done)
492 }
493 })
494 }
495 before = len(listeners.free)
496 chunksBefore = len(listeners.chunks)
497 })
498 if chunksBefore < 3 {
499 t.Fatalf("test did not cross a chunk boundary: %d chunks", chunksBefore)
500 }
501 fds.closePeer()
502 select {
503 case <-done:
504 case <-time.After(3 * time.Second):
505 t.Fatal("not every listener fired")
506 }
507 var reused bool
508 var firedNow int
509 d.Do(func() {
510 firedNow = fired
511 after = len(listeners.free)
512 c2, err := d.CreateClient(fds2.srv)
513 if err != nil {
514 t.Error(err)
515 return
516 }
517 c2.OnDestroy(func() {})
518 reused = len(listeners.chunks) == chunksBefore && len(listeners.free) == after-1
519 })
520 if after != before+n {
521 t.Fatalf("free slots %d -> %d, want +%d", before, after, n)
522 }
523 if firedNow != n || !reused {
524 t.Fatalf("fired %d, slot reused %v", firedNow, reused)
525 }
526}