1package wayland
2
3import (
4 "context"
5 "os"
6 "strings"
7 "sync"
8 "testing"
9 "time"
10 "unsafe"
11
12 "github.com/bnema/go-wayland-bindings/server/committiming"
13 "github.com/bnema/go-wayland-bindings/server/fifo"
14 "github.com/bnema/go-wayland-bindings/server/linuxdmabuf"
15 "github.com/bnema/go-wayland-bindings/server/linuxdrmsyncobj"
16 "github.com/bnema/go-wayland-bindings/server/wayland"
17 "github.com/bnema/neferwl/internal/adapters/logging"
18 "github.com/bnema/neferwl/internal/ports"
19 "github.com/bnema/wlturbo"
20 "github.com/stretchr/testify/mock"
21 "golang.org/x/sys/unix"
22)
23
24func TestSyncobjStructSizes(t *testing.T) {
25 for _, tc := range []struct {
26 name string
27 got uintptr
28 req uintptr
29 }{
30 {"destroy", unsafe.Sizeof(syncobjDestroy{}), ioctlSyncobjDestroy},
31 {"handle_to_fd", unsafe.Sizeof(syncobjHandle{}), ioctlSyncobjHandleToFD},
32 {"fd_to_handle", unsafe.Sizeof(syncobjHandle{}), ioctlSyncobjFDToHandle},
33 {"timeline_signal", unsafe.Sizeof(syncobjTimelineArray{}), ioctlSyncobjTimelineSignal},
34 {"timeline_wait", unsafe.Sizeof(syncobjTimelineWait{}), ioctlSyncobjTimelineWait},
35 {"eventfd", unsafe.Sizeof(syncobjEventfd{}), ioctlSyncobjEventfd},
36 {"get_cap", unsafe.Sizeof(drmCap{}), ioctlGetCap},
37 } {
38 if want := (tc.req >> 16) & 0x3fff; tc.got != want {
39 t.Errorf("%s: size %d, ioctl encodes %d", tc.name, tc.got, want)
40 }
41 }
42}
43
44// syncHarness is a server with a mocked syncobj device and a client with
45// a mapped window, a dmabuf buffer and the syncobj objects.
46type syncHarness struct {
47 t *testing.T
48 s *Server
49 c *wlturbo.Display
50 dev *mocksyncobjDevice
51 contents <-chan ports.SurfaceContent
52 presented ports.PresentationSink
53 surf uint32
54 syncSurf uint32
55 timeline uint32
56 win ports.WindowID
57 mu sync.Mutex
58 efds map[uint64]int // eventfd by acquire point
59 signals []uint64
60 fences []*os.File
61 releases map[uint32]*syncReleaseProxy
62 mgr uint32
63}
64
65func newSyncHarness(t *testing.T) *syncHarness {
66 h := &syncHarness{t: t, efds: map[uint64]int{}}
67 h.dev = newMocksyncobjDevice(t)
68 h.dev.EXPECT().fdToHandle(mock.Anything).Return(7, nil).Maybe()
69 h.dev.EXPECT().destroy(mock.Anything).Return(nil).Maybe()
70 h.dev.EXPECT().signalled(uint32(7), mock.Anything).Return(false, nil).Maybe()
71 h.dev.EXPECT().eventfd(uint32(7), mock.Anything, mock.Anything).RunAndReturn(func(_ uint32, point uint64, efd int) error {
72 // Keep a duplicate: the waiter closes its own when it fires.
73 dup, err := unix.Dup(efd)
74 if err != nil {
75 return err
76 }
77 h.mu.Lock()
78 h.efds[point] = dup
79 h.mu.Unlock()
80 return nil
81 }).Maybe()
82 h.dev.EXPECT().exportSyncFile(uint32(7), mock.Anything).RunAndReturn(func(uint32, uint64) (*os.File, error) {
83 r, w, err := os.Pipe()
84 if err != nil {
85 return nil, err
86 }
87 w.Close()
88 h.mu.Lock()
89 h.fences = append(h.fences, r)
90 h.mu.Unlock()
91 return r, nil
92 }).Maybe()
93 h.dev.EXPECT().signal(uint32(7), mock.Anything).RunAndReturn(func(_ uint32, point uint64) error {
94 h.mu.Lock()
95 h.signals = append(h.signals, point)
96 h.mu.Unlock()
97 return nil
98 }).Maybe()
99 dir := t.TempDir()
100 events := make(chan ports.ClientEvent, 16)
101 commands := make(chan ports.ClientCommand, 16)
102 sup := ports.DMABufSupport{Formats: []ports.DMABufFormat{linearARGB}}
103 s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs, DMABuf: sup, syncDev: h.dev}, Channels{Events: events, Commands: commands}, logging.For(context.Background(), "wayland"))
104 if err != nil {
105 t.Fatal(err)
106 }
107 h.contents = relayContents(t, s)
108 ctx, cancel := context.WithCancel(context.Background())
109 done := make(chan error, 1)
110 go func() { done <- s.Run(ctx) }()
111 t.Cleanup(func() {
112 cancel()
113 <-done
114 h.mu.Lock()
115 for _, fd := range h.efds {
116 unix.Close(fd)
117 }
118 h.mu.Unlock()
119 })
120 h.s, h.presented = s, s.Presentation()
121 h.c = protocolClient(t, s, dir)
122 w, surf, xdg := surfaceMapper(t, h.c, events)()
123 registerProtocol(t, h.c, xdg)
124 h.win, h.surf = w.ID, surf
125 commands <- ports.ConfigureWindow{ID: w.ID, Width: 64, Height: 16, Output: "HEADLESS-1", Visible: true}
126 mgr := bindProtocol(t, h.c, "wp_linux_drm_syncobj_manager_v1")
127 h.syncSurf = h.c.AllocateID()
128 requestProtocol(t, h.c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, h.syncSurf, surf)
129 registerProtocol(t, h.c, h.syncSurf)
130 fd, err := unix.MemfdCreate("timeline", 0)
131 if err != nil {
132 t.Fatal(err)
133 }
134 defer unix.Close(fd)
135 h.timeline = h.c.AllocateID()
136 registerProtocol(t, h.c, h.timeline)
137 if err := wireRequest(h.c, mgr, uint16(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestImportTimeline), []int{fd}, h.timeline); err != nil {
138 t.Fatal(err)
139 }
140 for deadline := time.Now().Add(2 * time.Second); ; {
141 var out string
142 s.display.Do(func() { out = s.frameOutput(s.windows[w.ID].xdg.surface) })
143 if out != "" {
144 break
145 }
146 if time.Now().After(deadline) {
147 t.Fatal("configure not applied")
148 }
149 time.Sleep(2 * time.Millisecond)
150 }
151 return h
152}
153
154// dmabuf makes a client dmabuf wl_buffer (a memfd stands in for it).
155func (h *syncHarness) dmabuf() uint32 {
156 t, c := h.t, h.c
157 dm := bindVersion(t, c, "zwp_linux_dmabuf_v1", dmabufVersion)
158 fd, err := unix.MemfdCreate("buf", 0)
159 if err != nil {
160 t.Fatal(err)
161 }
162 defer unix.Close(fd)
163 if err := unix.Ftruncate(fd, 256*16); err != nil {
164 t.Fatal(err)
165 }
166 p := c.AllocateID()
167 requestProtocol(t, c, dm, linuxdmabuf.ZwpLinuxDmabufV1RequestCreateParams, p)
168 registerProtocol(t, c, p)
169 if err := wireRequest(c, p, uint16(linuxdmabuf.ZwpLinuxBufferParamsV1RequestAdd), []int{fd}, uint32(0), uint32(0), uint32(256), uint32(0), uint32(0)); err != nil {
170 t.Fatal(err)
171 }
172 b := c.AllocateID()
173 rel := &syncReleaseProxy{released: make(chan struct{}, 4)}
174 rel.SetID(b)
175 registerWireProxy(c, rel)
176 requestProtocol(t, c, p, linuxdmabuf.ZwpLinuxBufferParamsV1RequestCreateImmed, b, int32(64), int32(16), linearARGB.Format, uint32(0))
177 h.releases[b] = rel
178 return b
179}
180
181type syncReleaseProxy struct {
182 wlturbo.BaseProxy
183 released chan struct{}
184}
185
186func (p *syncReleaseProxy) Dispatch(e *wlturbo.Event) {
187 if uint32(e.Opcode) == wayland.BufferEventRelease {
188 p.released <- struct{}{}
189 }
190}
191
192func (h *syncHarness) setPoints(acquire, release uint64) {
193 requestProtocol(h.t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(acquire>>32), uint32(acquire))
194 requestProtocol(h.t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(release>>32), uint32(release))
195}
196
197func (h *syncHarness) commit(buf uint32, acquire, release uint64) {
198 t, c := h.t, h.c
199 requestProtocol(t, c, h.surf, wayland.SurfaceRequestAttach, buf, int32(0), int32(0))
200 h.setPoints(acquire, release)
201 requestProtocol(t, c, h.surf, wayland.SurfaceRequestCommit)
202 if err := c.Roundtrip(); err != nil {
203 t.Fatal(err)
204 }
205}
206
207// content waits for a DMABuf content of the window.
208func (h *syncHarness) content(wait time.Duration) (ports.SurfaceContent, bool) {
209 deadline := time.After(wait)
210 for {
211 select {
212 case c := <-h.contents:
213 if c.ID == h.win && c.DMABuf != nil {
214 return c, true
215 }
216 case <-deadline:
217 return ports.SurfaceContent{}, false
218 }
219 }
220}
221
222// fire makes the kernel's eventfd for an acquire point readable.
223func (h *syncHarness) fire(point uint64) {
224 h.t.Helper()
225 deadline := time.Now().Add(2 * time.Second)
226 for {
227 h.mu.Lock()
228 fd, ok := h.efds[point]
229 h.mu.Unlock()
230 if ok {
231 var one [8]byte
232 one[0] = 1
233 if _, err := unix.Write(fd, one[:]); err != nil {
234 h.t.Fatal(err)
235 }
236 return
237 }
238 if time.Now().After(deadline) {
239 h.t.Fatalf("no eventfd for point %d", point)
240 }
241 time.Sleep(2 * time.Millisecond)
242 }
243}
244
245func (h *syncHarness) signalled() []uint64 {
246 h.mu.Lock()
247 defer h.mu.Unlock()
248 return append([]uint64(nil), h.signals...)
249}
250
251// A commit waits for its acquire point; the content carries the fence;
252// replacing it signals the release point once outputs moved on, and
253// wl_buffer.release is never sent. The acquire fence closes only then.
254func TestSyncobjAcquireAndRelease(t *testing.T) {
255 h := newSyncHarness(t)
256 h.releases = map[uint32]*syncReleaseProxy{}
257 a, b := h.dmabuf(), h.dmabuf()
258 h.commit(a, 1, 2)
259 if _, ok := h.content(50 * time.Millisecond); ok {
260 t.Fatal("content before the acquire point had a fence")
261 }
262 h.fire(1)
263 first, ok := h.content(2 * time.Second)
264 if !ok || first.Acquire == nil {
265 t.Fatalf("content %+v", first)
266 }
267 h.commit(b, 3, 4)
268 h.fire(3)
269 second, ok := h.content(2 * time.Second)
270 if !ok {
271 t.Fatal("second content")
272 }
273 if _, err := first.Acquire.Stat(); err != nil {
274 t.Fatal("acquire fence closed before release")
275 }
276 // The output read the second content: the first buffer is released.
277 h.presented.Push(ports.OutputPresented{Output: "HEADLESS-1", Seen: map[ports.WindowID]uint64{h.win: second.Seq}})
278 deadline := time.Now().Add(2 * time.Second)
279 for len(h.signalled()) == 0 && time.Now().Before(deadline) {
280 if err := h.c.Roundtrip(); err != nil {
281 t.Fatal(err)
282 }
283 time.Sleep(2 * time.Millisecond)
284 }
285 if got := h.signalled(); len(got) != 1 || got[0] != 2 {
286 t.Fatalf("signalled %v", got)
287 }
288 if _, err := first.Acquire.Stat(); err == nil {
289 t.Fatal("acquire fence left open after release")
290 }
291 if err := h.c.Roundtrip(); err != nil {
292 t.Fatal(err)
293 }
294 select {
295 case <-h.releases[a].released:
296 t.Fatal("wl_buffer.release sent under explicit sync")
297 default:
298 }
299}
300
301// FIFO, commit timing and acquire are independent gates on the same ordered
302// update: clearing the first two cannot publish before its acquire fires.
303func TestQueuedCommitReadinessGatesInOrder(t *testing.T) {
304 h := newSyncHarness(t)
305 h.releases = map[uint32]*syncReleaseProxy{}
306 first, second := h.dmabuf(), h.dmabuf()
307 fm := bindProtocol(t, h.c, "wp_fifo_manager_v1")
308 f := h.c.AllocateID()
309 registerProtocol(t, h.c, f)
310 requestProtocol(t, h.c, fm, fifo.WpFifoManagerV1RequestGetFifo, f, h.surf)
311 tm := bindProtocol(t, h.c, "wp_commit_timing_manager_v1")
312 timer := h.c.AllocateID()
313 registerProtocol(t, h.c, timer)
314 requestProtocol(t, h.c, tm, committiming.WpCommitTimingManagerV1RequestGetTimer, timer, h.surf)
315
316 requestProtocol(t, h.c, f, fifo.WpFifoV1RequestSetBarrier)
317 h.commit(first, 1, 2)
318 h.fire(1)
319 one, ok := h.content(2 * time.Second)
320 if !ok {
321 t.Fatal("first update did not publish")
322 }
323 var now unix.Timespec
324 if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &now); err != nil {
325 t.Fatal(err)
326 }
327 target := time.Duration(now.Nano()) + 180*time.Millisecond
328 sec := uint64(target / time.Second)
329 requestProtocol(t, h.c, timer, committiming.WpCommitTimerV1RequestSetTimestamp, uint32(sec>>32), uint32(sec), uint32(target%time.Second))
330 requestProtocol(t, h.c, f, fifo.WpFifoV1RequestWaitBarrier)
331 h.commit(second, 3, 4)
332 // Even with an acquire fence available, timestamp and barrier hold it.
333 var waiting bool
334 h.s.display.Do(func() {
335 surf := h.s.windows[h.win].xdg.surface
336 waiting = len(surf.queue) == 1 && surf.barrier
337 })
338 if !waiting {
339 t.Fatal("update did not wait behind the FIFO barrier")
340 }
341 if _, ok := h.content(200 * time.Millisecond); ok {
342 t.Fatal("update published before its acquire fence")
343 }
344 var gatedByAcquire bool
345 h.s.display.Do(func() {
346 surf := h.s.windows[h.win].xdg.surface
347 gatedByAcquire = len(surf.queue) == 1 && !surf.barrier && !surf.tooEarly(surf.queue[0].at, time.Now()) && !surf.syncReady(surf.queue[0].sync)
348 })
349 if !gatedByAcquire {
350 t.Fatal("FIFO/timing did not clear while acquire still waited")
351 }
352 h.fire(3)
353 two, ok := h.content(2 * time.Second)
354 if !ok || two.Seq <= one.Seq || two.DMABuf.ID == one.DMABuf.ID {
355 t.Fatalf("ordered publication: first=%+v second=%+v", one, two)
356 }
357}
358
359// A destroyed queued buffer must not hold later commits behind its acquire.
360// Its release point cannot be advanced before the acquire is ready.
361func TestSyncobjDestroyedQueuedAcquire(t *testing.T) {
362 h := newSyncHarness(t)
363 h.releases = map[uint32]*syncReleaseProxy{}
364 first, dead, next := h.dmabuf(), h.dmabuf(), h.dmabuf()
365 h.commit(first, 1, 2)
366 h.fire(1)
367 if _, ok := h.content(2 * time.Second); !ok {
368 t.Fatal("initial content missing")
369 }
370 fm := bindProtocol(t, h.c, "wp_fifo_manager_v1")
371 f := h.c.AllocateID()
372 registerProtocol(t, h.c, f)
373 requestProtocol(t, h.c, fm, fifo.WpFifoManagerV1RequestGetFifo, f, h.surf)
374 requestProtocol(t, h.c, f, fifo.WpFifoV1RequestSetBarrier)
375 h.commit(dead, 3, 4)
376 requestProtocol(t, h.c, dead, wayland.BufferRequestDestroy)
377 if err := h.c.Roundtrip(); err != nil {
378 t.Fatal(err)
379 }
380 // The dead attachment is skipped; it does not replace the current one.
381 // A later attachment is queued while the old fence remains unsignalled.
382 h.commit(next, 5, 6)
383 h.fire(5)
384 got, ok := h.content(2 * time.Second)
385 if !ok || got.DMABuf == nil {
386 t.Fatalf("later content blocked: %+v", got)
387 }
388 for _, p := range h.signalled() {
389 if p == 4 {
390 t.Fatal("skipped buffer release signalled before acquire")
391 }
392 }
393}
394
395func TestSyncobjProtocolErrors(t *testing.T) {
396 for _, tc := range []struct {
397 name string
398 run func(h *syncHarness)
399 obj func(h *syncHarness) uint32
400 code uint32
401 }{
402 {"conflicting points", func(h *syncHarness) {
403 b := h.dmabuf()
404 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
405 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(5))
406 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(5))
407 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
408 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorConflictingPoints)},
409 {"no release point", func(h *syncHarness) {
410 b := h.dmabuf()
411 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
412 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(5))
413 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
414 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoReleasePoint)},
415 {"buffer without points", func(h *syncHarness) {
416 b := h.dmabuf()
417 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
418 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
419 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoAcquirePoint)},
420 {"no acquire point", func(h *syncHarness) {
421 b := h.dmabuf()
422 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
423 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(5))
424 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
425 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoAcquirePoint)},
426 {"points without attach", func(h *syncHarness) {
427 b := h.dmabuf()
428 h.commit(b, 1, 2)
429 h.fire(1)
430 h.setPoints(3, 4)
431 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
432 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoBuffer)},
433 {"points with null attach", func(h *syncHarness) {
434 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
435 h.setPoints(1, 2)
436 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
437 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoBuffer)},
438 {"wl_shm buffer", func(h *syncHarness) {
439 b := shmBuffer(t, h.c)
440 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
441 h.setPoints(1, 2)
442 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
443 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorUnsupportedBuffer)},
444 {"surface destroyed", func(h *syncHarness) {
445 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
446 // The first request already raises the error and the server
447 // then closes the connection: a second write could hit EPIPE.
448 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(1))
449 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoSurface)},
450 {"surface exists", func(h *syncHarness) {
451 mgr := bindProtocol(t, h.c, "wp_linux_drm_syncobj_manager_v1")
452 h.mgr = mgr
453 requestProtocol(t, h.c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, h.c.AllocateID(), h.surf)
454 }, func(h *syncHarness) uint32 { return h.mgr }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1ErrorSurfaceExists)},
455 } {
456 t.Run(tc.name, func(t *testing.T) {
457 h := newSyncHarness(t)
458 h.releases = map[uint32]*syncReleaseProxy{}
459 h.t = t
460 tc.run(h)
461 expectProtocolError(t, h.c, tc.obj(h), tc.code)
462 })
463 }
464}
465
466// An invalid timeline fd is a protocol error.
467func TestSyncobjInvalidTimeline(t *testing.T) {
468 dev := newMocksyncobjDevice(t)
469 dev.EXPECT().fdToHandle(mock.Anything).Return(0, unix.EINVAL).Once()
470 dir := t.TempDir()
471 s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs, syncDev: dev}, Channels{}, logging.For(context.Background(), "wayland"))
472 if err != nil {
473 t.Fatal(err)
474 }
475 ctx, cancel := context.WithCancel(context.Background())
476 done := make(chan error, 1)
477 go func() { done <- s.Run(ctx) }()
478 t.Cleanup(func() { cancel(); <-done })
479 c := protocolClient(t, s, dir)
480 mgr := bindProtocol(t, c, "wp_linux_drm_syncobj_manager_v1")
481 fd, _ := unix.MemfdCreate("bad", 0)
482 defer unix.Close(fd)
483 if err := wireRequest(c, mgr, uint16(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestImportTimeline), []int{fd}, c.AllocateID()); err != nil {
484 t.Fatal(err)
485 }
486 expectProtocolError(t, c, mgr, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1ErrorInvalidTimeline))
487}
488
489// A synchronized subsurface dropped before its acquire is ready must
490// retire its release point without sending wl_buffer.release.
491func TestSyncobjDroppedSubsurfaceCommit(t *testing.T) {
492 h := newSyncHarness(t)
493 h.releases = map[uint32]*syncReleaseProxy{}
494 c := h.c
495 comp := bindProtocol(t, c, "wl_compositor")
496 subc := bindProtocol(t, c, "wl_subcompositor")
497 mgr := bindProtocol(t, c, "wp_linux_drm_syncobj_manager_v1")
498 child := c.AllocateID()
499 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
500 registerProtocol(t, c, child)
501 sub := c.AllocateID()
502 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, h.surf)
503 registerProtocol(t, c, sub)
504 syncChild := c.AllocateID()
505 requestProtocol(t, c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, syncChild, child)
506 registerProtocol(t, c, syncChild)
507 b := h.dmabuf()
508 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
509 requestProtocol(t, c, syncChild, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(21))
510 requestProtocol(t, c, syncChild, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(22))
511 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
512 requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy)
513 if err := c.Roundtrip(); err != nil {
514 t.Fatal(err)
515 }
516 h.fire(21) // dropping before acquire means the later signal must not release
517 if err := c.Roundtrip(); err != nil {
518 t.Fatal(err)
519 }
520 if got := h.signalled(); len(got) != 0 {
521 t.Fatalf("premature release point: %v", got)
522 }
523 select {
524 case <-h.releases[b].released:
525 t.Fatal("explicit sync sent wl_buffer.release")
526 default:
527 }
528}
529
530// A commit dropped before its acquire point has a fence signals nothing
531// (the client has not submitted that point) and sends no wl_buffer.release;
532// the waiter closes its eventfd and no acquire file is left open.
533func TestSyncobjDroppedCommit(t *testing.T) {
534 h := newSyncHarness(t)
535 h.releases = map[uint32]*syncReleaseProxy{}
536 a := h.dmabuf()
537 before := openFDs(t)
538 h.commit(a, 1, 2)
539 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
540 if err := h.c.Roundtrip(); err != nil {
541 t.Fatal(err)
542 }
543 // The point never gets a fence: the waiter must still stop polling it.
544 time.Sleep(250 * time.Millisecond)
545 if err := h.c.Roundtrip(); err != nil {
546 t.Fatal(err)
547 }
548 if got := h.signalled(); len(got) != 0 {
549 t.Fatalf("signalled %v for a commit whose acquire point never arrived", got)
550 }
551 select {
552 case <-h.releases[a].released:
553 t.Fatal("wl_buffer.release sent under explicit sync")
554 default:
555 }
556 // The harness keeps its own duplicate of the eventfd.
557 if after := openFDs(t); after > before+1 {
558 t.Fatalf("eventfds and sync files %d -> %d", before, after)
559 }
560}
561
562// A commit dropped after its acquire point fired signals its release point.
563func TestSyncobjDroppedReadyCommitReleases(t *testing.T) {
564 h := newSyncHarness(t)
565 h.releases = map[uint32]*syncReleaseProxy{}
566 a := h.dmabuf()
567 h.commit(a, 1, 2)
568 h.fire(1)
569 if _, ok := h.content(2 * time.Second); !ok {
570 t.Fatal("content")
571 }
572 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
573 deadline := time.Now().Add(2 * time.Second)
574 for len(h.signalled()) == 0 && time.Now().Before(deadline) {
575 if err := h.c.Roundtrip(); err != nil {
576 t.Fatal(err)
577 }
578 time.Sleep(2 * time.Millisecond)
579 }
580 if got := h.signalled(); len(got) != 1 || got[0] != 2 {
581 t.Fatalf("signalled %v", got)
582 }
583}
584
585// openFDs counts the eventfds and sync files (pipes stand in for them)
586// the process holds.
587func openFDs(t *testing.T) int {
588 t.Helper()
589 ents, err := os.ReadDir("/proc/self/fd")
590 if err != nil {
591 t.Fatal(err)
592 }
593 n := 0
594 for _, e := range ents {
595 l, _ := os.Readlink("/proc/self/fd/" + e.Name())
596 if strings.Contains(l, "eventfd") || strings.HasPrefix(l, "pipe:") {
597 n++
598 }
599 }
600 return n
601}