1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/stretchr/testify/mock"
8 "golang.org/x/sys/unix"
9)
10
11// explicitWaiter is a waiter whose explicit waits register with a mocked
12// display-loop source; the callback and fd are captured.
13type explicitWaiter struct {
14 sw *syncWaiter
15 src *mockacquireSource
16 rm *mockacquireRemover
17 paced int
18 fd int
19 fire func()
20 point syncPoint
21}
22
23func newExplicitWaiter(t *testing.T, addErr error) *explicitWaiter {
24 t.Helper()
25 e := &explicitWaiter{src: newMockacquireSource(t), rm: newMockacquireRemover(t)}
26 dev := newMocksyncobjDevice(t)
27 dev.EXPECT().signalled(uint32(7), uint64(4)).Return(false, nil).Once()
28 dev.EXPECT().eventfd(uint32(7), uint64(4), mock.Anything).Return(nil).Once()
29 e.point = syncPoint{tl: &timeline{dev: dev, handle: 7}, point: 4}
30 sw, err := newSyncWaiter(func() { t.Error("explicit wait posted a tick") })
31 if err != nil {
32 t.Fatal(err)
33 }
34 t.Cleanup(func() { sw.discard(); sw.close() })
35 sw.src, sw.pace = e.src, func() { e.paced++ }
36 e.sw = sw
37 e.src.EXPECT().add(mock.Anything, mock.Anything).RunAndReturn(func(fd int, f func()) (acquireRemover, error) {
38 e.fd, e.fire = fd, f
39 if addErr != nil {
40 return nil, addErr
41 }
42 return e.rm, nil
43 }).Once()
44 return e
45}
46
47func fdClosed(fd int) bool {
48 _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0)
49 return err == unix.EBADF
50}
51
52// A fired eventfd removes the source, closes the fd and wakes the pacer
53// inline, never through a posted tick.
54func TestExplicitWaitFiresOnDisplayLoop(t *testing.T) {
55 e := newExplicitWaiter(t, nil)
56 w, err := e.sw.watch(e.point)
57 if err != nil || e.sw.fired(w) {
58 t.Fatalf("watch: %v fired=%v", err, e.sw.fired(w))
59 }
60 e.rm.EXPECT().Remove().Once()
61 one := [8]byte{1}
62 if _, err := unix.Write(e.fd, one[:]); err != nil {
63 t.Fatal(err)
64 }
65 fd := e.fd
66 e.fire()
67 if !e.sw.fired(w) || w.failed || e.paced != 1 || !fdClosed(fd) {
68 t.Fatalf("fired=%v failed=%v paced=%d closed=%v", e.sw.fired(w), w.failed, e.paced, fdClosed(fd))
69 }
70 e.sw.cancel(w) // applySync cancels after the fire: nothing more to release
71}
72
73// Cancelling before the fire removes the source, then closes the fd.
74func TestExplicitWaitCancelRemovesThenCloses(t *testing.T) {
75 e := newExplicitWaiter(t, nil)
76 w, err := e.sw.watch(e.point)
77 if err != nil {
78 t.Fatal(err)
79 }
80 e.rm.EXPECT().Remove().RunAndReturn(func() {
81 if fdClosed(e.fd) {
82 t.Error("fd closed before Remove")
83 }
84 }).Once()
85 e.sw.cancel(w)
86 if !e.sw.fired(w) || !fdClosed(e.fd) || e.paced != 0 {
87 t.Fatal("cancel left the wait registered")
88 }
89 e.sw.cancel(w)
90}
91
92func TestExplicitWaitAddFailureFails(t *testing.T) {
93 e := newExplicitWaiter(t, unix.ENOMEM)
94 w, err := e.sw.watch(e.point)
95 if err == nil || !e.sw.fired(w) || !w.failed || !fdClosed(e.fd) {
96 t.Fatalf("add failure: err=%v", err)
97 }
98}
99
100// A callback with nothing to read keeps the wait registered.
101func TestExplicitWaitSpuriousCallbackKeepsWaiting(t *testing.T) {
102 e := newExplicitWaiter(t, nil)
103 w, err := e.sw.watch(e.point)
104 if err != nil {
105 t.Fatal(err)
106 }
107 e.fire()
108 if e.sw.fired(w) || e.paced != 0 || fdClosed(e.fd) {
109 t.Fatal("spurious callback ended the wait")
110 }
111 e.rm.EXPECT().Remove().Once()
112 e.sw.cancel(w)
113}
114
115// Shutdown releases waits still registered, Remove before Close.
116func TestExplicitWaitReleasePending(t *testing.T) {
117 e := newExplicitWaiter(t, nil)
118 if _, err := e.sw.watch(e.point); err != nil {
119 t.Fatal(err)
120 }
121 e.rm.EXPECT().Remove().Once()
122 e.sw.releasePending()
123 if !fdClosed(e.fd) {
124 t.Fatal("pending wait leaked its eventfd")
125 }
126}
127
128// End to end: a queued commit stays queued until the callback runs, then
129// is applied by that same tick.
130func TestSyncobjCommitAppliedByAcquireCallback(t *testing.T) {
131 h := newSyncHarness(t)
132 h.releases = map[uint32]*syncReleaseProxy{}
133 src, rm := newMockacquireSource(t), newMockacquireRemover(t)
134 var fire func()
135 src.EXPECT().add(mock.Anything, mock.Anything).RunAndReturn(func(_ int, f func()) (acquireRemover, error) {
136 fire = f
137 return rm, nil
138 }).Once()
139 rm.EXPECT().Remove().Maybe()
140 h.s.display.Do(func() { h.s.syncWait.src = src })
141 a := h.dmabuf()
142 h.commit(a, 1, 2)
143 if _, ok := h.content(50 * time.Millisecond); ok {
144 t.Fatal("content before the acquire point fired")
145 }
146 h.fire(1) // the kernel signals: efd readable (display loop is mocked, so nothing reads it)
147 var applied, posted bool
148 h.s.display.Do(func() {
149 fire()
150 // Same tick: the commit left the queue without posting a pace tick.
151 applied = len(h.s.windows[h.win].xdg.surface.queue) == 0
152 posted = h.s.paceSignal.Load()
153 })
154 if !applied || posted {
155 t.Fatalf("acquire callback: applied=%v posted tick=%v, want applied inline", applied, posted)
156 }
157 got, ok := h.content(2 * time.Second)
158 if !ok || got.Acquire == nil {
159 t.Fatalf("content %+v", got)
160 }
161}