1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/extsessionlock"
8 "github.com/bnema/go-wayland-bindings/server/linuxdrmsyncobj"
9 "github.com/bnema/go-wayland-bindings/server/viewporter"
10 "github.com/bnema/go-wayland-bindings/server/wayland"
11 "github.com/bnema/go-wayland-bindings/server/wlrlayershell"
12 "github.com/bnema/go-wayland-bindings/server/xdgshell"
13 "github.com/bnema/wlturbo"
14 "golang.org/x/sys/unix"
15)
16
17// This is a real wire proxy, not a double for a project interface.
18type lockConfigureProxy struct {
19 wlturbo.BaseProxy
20 configures chan lockConfigure
21}
22
23func (p *lockConfigureProxy) Dispatch(e *wlturbo.Event) {
24 if e.Opcode == uint16(extsessionlock.ExtSessionLockSurfaceV1EventConfigure) {
25 p.configures <- lockConfigure{serial: e.Uint32(), width: int(e.Uint32()), height: int(e.Uint32())}
26 }
27}
28
29func lockTestSurface(t *testing.T, s *Server, c *wlturbo.Display) (uint32, uint32, uint32, *surface, *lockSurface, <-chan lockConfigure) {
30 t.Helper()
31 comp := bindVersion(t, c, "wl_compositor", 6)
32 surfID := c.AllocateID()
33 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surfID)
34 registerProtocol(t, c, surfID)
35 if err := c.Roundtrip(); err != nil {
36 t.Fatal(err)
37 }
38 ownerID, lockID := c.AllocateID(), c.AllocateID()
39 registerProtocol(t, c, ownerID)
40 setWireSchema(c, ownerID, "ext_session_lock_v1", 1)
41 setWireSchema(c, lockID, "ext_session_lock_surface_v1", 1)
42 configures := make(chan lockConfigure, 8)
43 p := &lockConfigureProxy{configures: configures}
44 p.SetID(lockID)
45 registerWireProxy(c, p)
46 var surf *surface
47 var l *lockSurface
48 s.display.Do(func() {
49 for _, v := range s.surfaces {
50 if v.wl.ID() == surfID {
51 surf = v
52 }
53 }
54 if surf == nil {
55 t.Error("surface missing")
56 return
57 }
58 // The role factory takes an already created owner resource. No manager
59 // or test global is needed and no admission behavior is simulated.
60 r, err := surf.wl.Client().CreateResource(extsessionlock.ExtSessionLockV1Interface, 1, ownerID, nil)
61 if err != nil {
62 t.Error(err)
63 return
64 }
65 l, err = s.newLockSurface(extsessionlock.WrapExtSessionLockV1(r), lockID, surf, s.outputs[0], nil)
66 if err != nil {
67 t.Error(err)
68 }
69 })
70 if l == nil {
71 t.Fatal("lock role missing")
72 }
73 if err := c.Roundtrip(); err != nil {
74 t.Fatal(err)
75 }
76 return surfID, ownerID, lockID, surf, l, configures
77}
78
79func lockViewport(t *testing.T, c *wlturbo.Display, surfID uint32, w, h int) uint32 {
80 t.Helper()
81 manager := bindProtocol(t, c, "wp_viewporter")
82 vp := c.AllocateID()
83 registerProtocol(t, c, vp)
84 requestProtocol(t, c, manager, viewporter.WpViewporterRequestGetViewport, vp, surfID)
85 requestProtocol(t, c, vp, viewporter.WpViewportRequestSetDestination, int32(w), int32(h))
86 return vp
87}
88
89func TestLockSurfaceInitialConfigureAndCommitErrors(t *testing.T) {
90 for _, scenario := range []string{"before ack", "queued before ack", "null", "dimensions", "unknown ack", "reused ack", "old ack"} {
91 t.Run(scenario, func(t *testing.T) {
92 s, _, _, dir := lifecycleServer(t)
93 c := protocolClient(t, s, dir)
94 surfID, _, lockID, surf, l, configures := lockTestSurface(t, s, c)
95 cfg := <-configures
96 if cfg.width != testOutputs[0].Width || cfg.height != testOutputs[0].Height || cfg.serial == 0 {
97 t.Fatalf("configure = %+v", cfg)
98 }
99 code := extsessionlock.ExtSessionLockSurfaceV1ErrorInvalidSerial
100 switch scenario {
101 case "before ack", "queued before ack":
102 if scenario == "queued before ack" {
103 s.display.Do(func() { surf.barrier = true; surf.next.wait = true })
104 }
105 // Hold the display goroutine while both requests are written: the
106 // server reads neither before the release, so the fatal commit
107 // cannot close the connection under the ACK write.
108 entered, release := make(chan struct{}), make(chan struct{})
109 go s.display.Do(func() { close(entered); <-release })
110 <-entered
111 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
112 // This later ACK must not legalize an earlier FIFO commit.
113 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
114 close(release)
115 code = extsessionlock.ExtSessionLockSurfaceV1ErrorCommitBeforeFirstAck
116 case "null":
117 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
118 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
119 code = extsessionlock.ExtSessionLockSurfaceV1ErrorNullBuffer
120 case "dimensions":
121 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
122 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
123 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
124 code = extsessionlock.ExtSessionLockSurfaceV1ErrorDimensionsMismatch
125 case "unknown ack":
126 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, uint32(0))
127 case "reused ack":
128 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
129 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
130 case "old ack":
131 s.display.Do(l.sendConfigure)
132 if err := c.Roundtrip(); err != nil {
133 t.Fatal(err)
134 }
135 newer := <-configures
136 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, newer.serial)
137 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
138 }
139 expectProtocolError(t, c, lockID, uint32(code))
140 })
141 }
142}
143
144func TestLockSurfaceCapturedAckGeometryAndDestroy(t *testing.T) {
145 s, _, _, contents, dir := contentServer(t)
146 c := protocolClient(t, s, dir)
147 surfID, _, lockID, surf, l, configures := lockTestSurface(t, s, c)
148 cfg := <-configures
149 vpID := lockViewport(t, c, surfID, cfg.width, cfg.height)
150 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
151 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
152 if err := c.Roundtrip(); err != nil {
153 t.Fatal(err)
154 }
155 // Capture a real buffer and geometry behind a future commit-time gate.
156 s.display.Do(func() {
157 surf.next.at = time.Now().Add(time.Hour)
158 surf.Commit(surf.wl)
159 if len(surf.queue) != 1 || surf.queue[0].lockAck != cfg {
160 t.Errorf("captured ACK missing")
161 }
162 l.output.place.Width++
163 l.sendConfigure()
164 })
165 if err := c.Roundtrip(); err != nil {
166 t.Fatal(err)
167 }
168 newer := <-configures
169 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, newer.serial)
170 requestProtocol(t, c, vpID, viewporter.WpViewportRequestSetDestination, int32(newer.width), int32(newer.height))
171 if err := c.Roundtrip(); err != nil {
172 t.Fatal(err)
173 }
174 s.display.Do(func() {
175 u := surf.queue[0]
176 u.at = time.Time{}
177 u.applyGraph()
178 if !l.mapped || surf.committedViewport.destW != int32(cfg.width) || l.ack != newer {
179 t.Errorf("captured commit read live geometry/ACK")
180 }
181 // Queue another update and destroy: retirement must clear all holds.
182 surf.next.at = time.Now().Add(time.Hour)
183 surf.Commit(surf.wl)
184 })
185 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestDestroy)
186 if err := c.Roundtrip(); err != nil {
187 t.Fatal(err)
188 }
189 s.display.Do(func() {
190 if surf.kind != roleSessionLock || surf.lock != nil || len(surf.queue) != 0 || surf.current != nil || len(s.lockSurfaces) != 0 || !l.closed {
191 t.Error("destroy lost permanent role or retained queued content")
192 }
193 })
194 select {
195 case content := <-contents:
196 if !content.Empty() {
197 select {
198 case content = <-contents:
199 if !content.Empty() {
200 t.Error("destroy did not emit empty content")
201 }
202 case <-time.After(time.Second):
203 t.Fatal("no empty content")
204 }
205 }
206 case <-time.After(time.Second):
207 t.Fatal("no content emitted")
208 }
209 wm := bindProtocol(t, c, "xdg_wm_base")
210 requestProtocol(t, c, wm, xdgshell.WmBaseRequestGetXdgSurface, c.AllocateID(), surfID)
211 expectProtocolError(t, c, wm, uint32(xdgshell.WmBaseErrorRole))
212}
213
214func TestLockSurfaceRejectsHistoricalBuffer(t *testing.T) {
215 s, _, _, dir := lifecycleServer(t)
216 c := protocolClient(t, s, dir)
217 comp := bindProtocol(t, c, "wl_compositor")
218 surfID := c.AllocateID()
219 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surfID)
220 registerProtocol(t, c, surfID)
221 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
222 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
223 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
224 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
225 if err := c.Roundtrip(); err != nil {
226 t.Fatal(err)
227 }
228 ownerID, id := c.AllocateID(), c.AllocateID()
229 registerProtocol(t, c, ownerID)
230 s.display.Do(func() {
231 for _, surf := range s.surfaces {
232 if surf.wl.ID() != surfID {
233 continue
234 }
235 r, err := surf.wl.Client().CreateResource(extsessionlock.ExtSessionLockV1Interface, 1, ownerID, nil)
236 if err != nil {
237 t.Error(err)
238 return
239 }
240 if l, err := s.newLockSurface(extsessionlock.WrapExtSessionLockV1(r), id, surf, s.outputs[0], nil); l != nil || err != nil {
241 t.Error("accepted historical buffer")
242 }
243 }
244 })
245 expectProtocolError(t, c, ownerID, uint32(extsessionlock.ExtSessionLockV1ErrorAlreadyConstructed))
246}
247
248func lockSizedBuffer(t *testing.T, c *wlturbo.Display, w, h int) uint32 {
249 t.Helper()
250 shm := bindProtocol(t, c, "wl_shm")
251 registerProtocol(t, c, shm)
252 fd, err := unix.MemfdCreate("lock-geometry", 0)
253 if err != nil {
254 t.Fatal(err)
255 }
256 defer unix.Close(fd)
257 size := int32(w * h * 4)
258 if err := unix.Ftruncate(fd, int64(size)); err != nil {
259 t.Fatal(err)
260 }
261 pool, buffer := c.AllocateID(), c.AllocateID()
262 registerProtocol(t, c, pool)
263 registerProtocol(t, c, buffer)
264 if err := wireRequest(c, shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, size); err != nil {
265 t.Fatal(err)
266 }
267 requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, buffer, int32(0), int32(w), int32(h), int32(w*4), uint32(0))
268 return buffer
269}
270
271func TestLockSurfaceScaleTransform(t *testing.T) {
272 s, _, _, dir := lifecycleServer(t)
273 c := protocolClient(t, s, dir)
274 surfID, _, lockID, surf, l, configures := lockTestSurface(t, s, c)
275 <-configures
276 s.display.Do(func() { l.output.place.Width, l.output.place.Height = 3, 2; l.sendConfigure() })
277 if err := c.Roundtrip(); err != nil {
278 t.Fatal(err)
279 }
280 cfg := <-configures
281 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
282 buffer := lockSizedBuffer(t, c, 4, 6)
283 requestProtocol(t, c, surfID, wayland.SurfaceRequestSetBufferScale, int32(2))
284 requestProtocol(t, c, surfID, wayland.SurfaceRequestSetBufferTransform, int32(1))
285 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
286 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
287 if err := c.Roundtrip(); err != nil {
288 t.Fatal(err)
289 }
290 s.display.Do(func() {
291 if !l.mapped || surf.content.LogicalW != 3 || surf.content.LogicalH != 2 {
292 t.Errorf("scaled transformed content = %+v", surf.content)
293 }
294 })
295 requestProtocol(t, c, surfID, wayland.SurfaceRequestDestroy)
296 if err := c.Roundtrip(); err != nil {
297 t.Fatal(err)
298 }
299 s.display.Do(func() {
300 if !l.closed || len(s.lockSurfaces) != 0 {
301 t.Error("wl_surface destruction did not unmap")
302 }
303 })
304}
305
306func TestLockSurfaceDestroyedReplacementAndViewportRetainsGeometry(t *testing.T) {
307 s, _, _, dir := lifecycleServer(t)
308 c := protocolClient(t, s, dir)
309 surfID, _, lockID, surf, l, configures := lockTestSurface(t, s, c)
310 cfg := <-configures
311 vp := lockViewport(t, c, surfID, cfg.width, cfg.height)
312 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
313 first := shmBuffer(t, c)
314 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, first, int32(0), int32(0))
315 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
316 if err := c.Roundtrip(); err != nil {
317 t.Fatal(err)
318 }
319 s.display.Do(func() { l.output.place.Width++; l.sendConfigure() })
320 if err := c.Roundtrip(); err != nil {
321 t.Fatal(err)
322 }
323 newer := <-configures
324 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, newer.serial)
325 requestProtocol(t, c, vp, viewporter.WpViewportRequestSetDestination, int32(newer.width), int32(newer.height))
326 replacement := shmBuffer(t, c)
327 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, replacement, int32(0), int32(0))
328 if err := c.Roundtrip(); err != nil {
329 t.Fatal(err)
330 }
331 s.display.Do(func() { surf.next.at = time.Now().Add(time.Hour); surf.Commit(surf.wl) })
332 requestProtocol(t, c, replacement, wayland.BufferRequestDestroy)
333 requestProtocol(t, c, vp, viewporter.WpViewportRequestDestroy)
334 if err := c.Roundtrip(); err != nil {
335 t.Fatal(err)
336 }
337 s.display.Do(func() {
338 surf.queue[0].at = time.Time{}
339 surf.queue[0].applyGraph()
340 if !l.mapped || surf.current.ID() != first || surf.content.LogicalW != cfg.width || surf.committedViewport.destW != int32(cfg.width) {
341 t.Error("destroyed replacement changed retained geometry")
342 }
343 })
344}
345
346func TestLockSurfaceDestroyedCurrentBufferAllowsNoAttachCommit(t *testing.T) {
347 s, _, _, dir := lifecycleServer(t)
348 c := protocolClient(t, s, dir)
349 surfID, _, lockID, surf, l, configures := lockTestSurface(t, s, c)
350 cfg := <-configures
351 lockViewport(t, c, surfID, cfg.width, cfg.height)
352 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
353 buffer := shmBuffer(t, c)
354 requestProtocol(t, c, surfID, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
355 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
356 if err := c.Roundtrip(); err != nil {
357 t.Fatal(err)
358 }
359 requestProtocol(t, c, buffer, wayland.BufferRequestDestroy)
360 callback := c.AllocateID()
361 registerProtocol(t, c, callback)
362 requestProtocol(t, c, surfID, wayland.SurfaceRequestFrame, callback)
363 requestProtocol(t, c, surfID, wayland.SurfaceRequestCommit)
364 if err := c.Roundtrip(); err != nil {
365 t.Fatal(err)
366 }
367 s.display.Do(func() {
368 if !l.mapped || !surf.has || surf.content.LogicalW != cfg.width {
369 t.Error("retained destroyed buffer unmapped")
370 }
371 })
372}
373
374func TestLockSurfaceDestroyBlocksLayerReuse(t *testing.T) {
375 s, _, _, dir := lifecycleServer(t)
376 c := protocolClient(t, s, dir)
377 surfID, _, lockID, _, _, _ := lockTestSurface(t, s, c)
378 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestDestroy)
379 if err := c.Roundtrip(); err != nil {
380 t.Fatal(err)
381 }
382 shell := bindVersion(t, c, "zwlr_layer_shell_v1", 4)
383 requestProtocol(t, c, shell, wlrlayershell.ZwlrLayerShellV1RequestGetLayerSurface, c.AllocateID(), surfID, uint32(0), uint32(2), "role-reuse")
384 expectProtocolError(t, c, shell, uint32(wlrlayershell.ZwlrLayerShellV1ErrorAlreadyConstructed))
385}
386
387func TestLockSurfaceDestroyRetiresFencedChildDependencies(t *testing.T) {
388 for _, destroy := range []string{"role", "surface", "role ready"} {
389 t.Run(destroy, func(t *testing.T) {
390 h := newSyncHarness(t)
391 h.releases = map[uint32]*syncReleaseProxy{}
392 c, s := h.c, h.s
393 rootID, _, lockID, root, _, configs := lockTestSurface(t, s, c)
394 cfg := <-configs
395 lockViewport(t, c, rootID, cfg.width, cfg.height)
396 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
397 rootBuffer := shmBuffer(t, c)
398 requestProtocol(t, c, rootID, wayland.SurfaceRequestAttach, rootBuffer, int32(0), int32(0))
399 requestProtocol(t, c, rootID, wayland.SurfaceRequestCommit)
400 if err := c.Roundtrip(); err != nil {
401 t.Fatal(err)
402 }
403 comp := bindProtocol(t, c, "wl_compositor")
404 subcomp := bindProtocol(t, c, "wl_subcompositor")
405 mgr := bindProtocol(t, c, "wp_linux_drm_syncobj_manager_v1")
406 childID := c.AllocateID()
407 registerProtocol(t, c, childID)
408 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, childID)
409 sub := c.AllocateID()
410 registerProtocol(t, c, sub)
411 requestProtocol(t, c, subcomp, wayland.SubcompositorRequestGetSubsurface, sub, childID, rootID)
412 syncID := c.AllocateID()
413 registerProtocol(t, c, syncID)
414 requestProtocol(t, c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, syncID, childID)
415 h.surf, h.syncSurf = childID, syncID
416 buffer := h.dmabuf()
417 h.commit(buffer, 31, 32)
418 var child *surface
419 var dep, parent *update
420 var wait *syncWait
421 var tl *timeline
422 s.display.Do(func() {
423 for _, v := range s.surfaces {
424 if v.wl.ID() == childID {
425 child = v
426 }
427 }
428 root.next.at = time.Now().Add(time.Hour)
429 root.Commit(root.wl)
430 dep, parent = child.queue[0], root.queue[0]
431 dep.refs++ // Pin for inspection just like another graph consumer.
432 parent.refs++
433 wait, tl = dep.sync.wait, dep.sync.acquire.tl
434 if !dep.synced || !dep.bound || len(parent.deps) != 1 || tl.uses != 2 {
435 t.Error("child was not captured with its fence")
436 }
437 })
438 if destroy == "role ready" {
439 h.fire(31)
440 deadline := time.NewTimer(time.Second)
441 defer deadline.Stop()
442 ready := false
443 for !ready {
444 s.display.Do(func() { ready = child.syncReady(dep.sync) })
445 if ready {
446 break
447 }
448 select {
449 case <-deadline.C:
450 t.Fatal("acquire waiter did not become ready")
451 case <-time.After(time.Millisecond):
452 }
453 }
454 }
455 // A later child commit is not owned by the captured root graph.
456 requestProtocol(t, c, childID, wayland.SurfaceRequestCommit)
457 if err := c.Roundtrip(); err != nil {
458 t.Fatal(err)
459 }
460 if destroy != "surface" {
461 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestDestroy)
462 } else {
463 requestProtocol(t, c, rootID, wayland.SurfaceRequestDestroy)
464 }
465 if err := c.Roundtrip(); err != nil {
466 t.Fatal(err)
467 }
468 s.display.Do(func() {
469 remaining, refs := 1, 2 // later child commit retains its prev link
470 if destroy == "surface" {
471 remaining, refs = 0, 1 // parent destruction detaches the child
472 }
473 if len(root.queue) != 0 || len(child.queue) != remaining || !dep.retired || !parent.retired || dep.refs != refs || parent.refs != 1 || !wait.cancelled.Load() || tl.uses != 0 {
474 t.Errorf("dependency teardown: child=%d dep refs=%d parent refs=%d uses=%d cancelled=%v", len(child.queue), dep.refs, parent.refs, tl.uses, wait.cancelled.Load())
475 }
476 dep.applyGraph()
477 parent.applyGraph()
478 if child.current != nil || root.current != nil {
479 t.Error("retired graph replayed")
480 }
481 child.dropQueue()
482 if dep.refs != 1 {
483 t.Errorf("retired dependency refs after later commit retirement = %d", dep.refs)
484 }
485 dep.releaseRef()
486 parent.releaseRef()
487 })
488 count := 0
489 for _, point := range h.signalled() {
490 if point == 32 {
491 count++
492 }
493 }
494 want := 0
495 if destroy == "role ready" {
496 want = 1
497 }
498 if count != want {
499 t.Errorf("release signals = %d, want %d", count, want)
500 }
501 select {
502 case <-h.releases[buffer].released:
503 t.Error("explicit-sync buffer received wl_buffer.release")
504 default:
505 }
506 })
507 }
508}
509
510func TestLockSurfaceChildPrefixKeepsSharedBufferOwnership(t *testing.T) {
511 for _, preserved := range []string{"suffix", "pending", "implicit suffix"} {
512 t.Run(preserved, func(t *testing.T) {
513 var s *Server
514 var c *wlturbo.Display
515 var h *syncHarness
516 if preserved == "implicit suffix" {
517 h = newSyncHarness(t)
518 h.releases = map[uint32]*syncReleaseProxy{}
519 s, c = h.s, h.c
520 } else {
521 var dir string
522 s, _, _, dir = lifecycleServer(t)
523 c = protocolClient(t, s, dir)
524 }
525 rootID, _, lockID, root, _, configs := lockTestSurface(t, s, c)
526 cfg := <-configs
527 lockViewport(t, c, rootID, cfg.width, cfg.height)
528 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestAckConfigure, cfg.serial)
529 requestProtocol(t, c, rootID, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
530 requestProtocol(t, c, rootID, wayland.SurfaceRequestCommit)
531 comp := bindProtocol(t, c, "wl_compositor")
532 subcomp := bindProtocol(t, c, "wl_subcompositor")
533 childID := c.AllocateID()
534 registerProtocol(t, c, childID)
535 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, childID)
536 sub := c.AllocateID()
537 registerProtocol(t, c, sub)
538 requestProtocol(t, c, subcomp, wayland.SubcompositorRequestGetSubsurface, sub, childID, rootID)
539 var buffer uint32
540 if h != nil {
541 buffer = h.dmabuf()
542 } else {
543 buffer = shmBuffer(t, c)
544 }
545 releases := &syncReleaseProxy{released: make(chan struct{}, 4)}
546 releases.SetID(buffer)
547 registerWireProxy(c, releases)
548 requestProtocol(t, c, childID, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
549 requestProtocol(t, c, childID, wayland.SurfaceRequestCommit)
550 if err := c.Roundtrip(); err != nil {
551 t.Fatal(err)
552 }
553 s.display.Do(func() { root.next.at = time.Now().Add(time.Hour); root.Commit(root.wl) })
554 requestProtocol(t, c, childID, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
555 if preserved != "pending" {
556 requestProtocol(t, c, childID, wayland.SurfaceRequestCommit)
557 }
558 requestProtocol(t, c, lockID, extsessionlock.ExtSessionLockSurfaceV1RequestDestroy)
559 if err := c.Roundtrip(); err != nil {
560 t.Fatal(err)
561 }
562 select {
563 case <-releases.released:
564 t.Fatal("captured prefix released shared buffer early")
565 default:
566 }
567 // Convert the pending reference to an update, then retire the last
568 // reference through the same normal queue path.
569 if preserved == "pending" {
570 requestProtocol(t, c, childID, wayland.SurfaceRequestCommit)
571 if err := c.Roundtrip(); err != nil {
572 t.Fatal(err)
573 }
574 }
575 s.display.Do(func() {
576 child := s.surfaceOf(findTestSurface(s, childID))
577 if len(child.queue) != 1 {
578 t.Errorf("preserved queue length = %d", len(child.queue))
579 }
580 child.dropQueue()
581 })
582 if err := c.Roundtrip(); err != nil {
583 t.Fatal(err)
584 }
585 select {
586 case <-releases.released:
587 default:
588 t.Fatal("last queued reference did not release buffer")
589 }
590 select {
591 case <-releases.released:
592 t.Fatal("buffer released twice")
593 default:
594 }
595 })
596 }
597}
598
599func findTestSurface(s *Server, id uint32) *wayland.Surface {
600 for _, surf := range s.surfaces {
601 if surf.wl.ID() == id {
602 return surf.wl
603 }
604 }
605 return nil
606}