1package wlturbo
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "io"
8 "net"
9 "os"
10 "testing"
11 "time"
12)
13
14// A read deadline must not leave the Display permanently broken.
15func TestDispatchDeadlineIsNotSticky(t *testing.T) {
16 client, server := net.Pipe()
17 defer client.Close()
18 defer server.Close()
19 d := newDisplay(client)
20 records := []EventRecord{}
21 p := &recordingProxy{BaseProxy: BaseProxy{id: 7, context: d.context}, records: &records}
22 d.objects.Store(uint32(7), p)
23 d.RegisterEventSignature(7, 4, "uint,")
24
25 if err := client.SetReadDeadline(time.Now().Add(20 * time.Millisecond)); err != nil {
26 t.Fatal(err)
27 }
28 if err := d.Dispatch(); !errors.Is(err, os.ErrDeadlineExceeded) {
29 t.Fatalf("Dispatch = %v, want deadline exceeded", err)
30 }
31 if err := client.SetReadDeadline(time.Time{}); err != nil {
32 t.Fatal(err)
33 }
34 go func() { _, _ = server.Write(message(7, 4, []byte{1, 0, 0, 0})) }()
35 if err := d.Dispatch(); err != nil {
36 t.Fatalf("Dispatch after clearing deadline = %v", err)
37 }
38 if len(records) != 1 {
39 t.Fatalf("records = %d, want 1", len(records))
40 }
41}
42
43type countingGlobalHandler struct{ n *int }
44
45func (h countingGlobalHandler) HandleRegistryGlobal(RegistryGlobalEvent) { *h.n++ }
46
47// Every registered global handler runs, not only the last one.
48func TestRegistryGlobalHandlersAccumulate(t *testing.T) {
49 d := newDisplay(&chunkConn{})
50 var a, b, specific int
51 d.registry.AddGlobalHandler(countingGlobalHandler{&a})
52 d.registry.AddGlobalHandler(countingGlobalHandler{&b})
53 d.registry.AddHandler("abc", func(*Registry, uint32, uint32) { specific++ })
54 d.registry.handleGlobal(1, "abc", 1)
55 if a != 1 || b != 1 || specific != 1 {
56 t.Fatalf("calls a=%d b=%d specific=%d, want 1 each", a, b, specific)
57 }
58}
59
60type fdSigProxy struct {
61 BaseProxy
62 calls int
63}
64
65func (p *fdSigProxy) EventSignature(op uint16) (string, bool) {
66 switch op {
67 case 0:
68 return "uint,", true
69 case 1:
70 return "fd,", true
71 }
72 return "", false
73}
74func (p *fdSigProxy) Dispatch(*Event) { p.calls++ }
75
76// Events already in flight for an object the client destroyed are dropped,
77// with their descriptors closed, until the compositor sends delete_id.
78func TestDestroyedObjectEventsAreDroppedUntilDeleteID(t *testing.T) {
79 client, peer := unixSocketPair(t)
80 d := newDisplay(client)
81 p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}}
82 d.context.Register(p)
83 if err := d.context.SendDestructor(p, 0); err != nil {
84 t.Fatal(err)
85 }
86 if err := d.context.SendRequest(p, 1); err == nil {
87 t.Fatal("request on destroyed proxy succeeded")
88 }
89
90 r, w, err := os.Pipe()
91 if err != nil {
92 t.Fatal(err)
93 }
94 defer r.Close()
95 sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil)
96 sendMessageWithFDs(t, peer, message(p.ID(), 1, nil), []int{int(w.Fd())})
97 w.Close()
98 for i := 0; i < 2; i++ {
99 if err := d.Dispatch(); err != nil {
100 t.Fatalf("in-flight event %d: %v", i, err)
101 }
102 }
103 if p.calls != 0 {
104 t.Fatalf("destroyed proxy received %d events", p.calls)
105 }
106 if err := r.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil {
107 t.Fatal(err)
108 }
109 if _, err := r.Read(make([]byte, 1)); !errors.Is(err, io.EOF) {
110 t.Fatalf("descriptor for destroyed object not closed: %v", err)
111 }
112
113 body := make([]byte, 4)
114 binary.LittleEndian.PutUint32(body, p.ID())
115 sendMessageWithFDs(t, peer, message(displayObjectID, 1, body), nil)
116 if err := d.Dispatch(); err != nil {
117 t.Fatalf("delete_id for zombie: %v", err)
118 }
119 sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil)
120 if err := d.Dispatch(); !errors.Is(err, ErrUnknownObject) {
121 t.Fatalf("event after delete_id = %v, want ErrUnknownObject", err)
122 }
123 if got := d.AllocateID(); got != p.ID() {
124 t.Fatalf("ID %d not reused after delete_id (got %d)", p.ID(), got)
125 }
126}
127
128// An abandoned object (a wl_callback the compositor destroys itself) sends no
129// request: a late event is dropped and the compositor's delete_id retires the
130// ID, both without a protocol error.
131func TestAbandonedObjectAcceptsLateEventAndServerDeleteID(t *testing.T) {
132 client, peer := unixSocketPair(t)
133 d := newDisplay(client)
134 p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}}
135 d.context.Register(p)
136 if err := d.context.Abandon(p); err != nil {
137 t.Fatal(err)
138 }
139 if err := d.context.Abandon(p); err == nil {
140 t.Fatal("second Abandon succeeded")
141 }
142 if err := d.context.SendRequest(p, 0); err == nil {
143 t.Fatal("request on abandoned proxy succeeded")
144 }
145 if err := peer.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
146 t.Fatal(err)
147 }
148 if n, _ := peer.Read(make([]byte, 64)); n != 0 {
149 t.Fatalf("Abandon sent %d bytes", n)
150 }
151
152 sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil)
153 if err := d.Dispatch(); err != nil {
154 t.Fatalf("late event for abandoned object: %v", err)
155 }
156 if p.calls != 0 {
157 t.Fatalf("abandoned proxy received %d events", p.calls)
158 }
159 body := make([]byte, 4)
160 binary.LittleEndian.PutUint32(body, p.ID())
161 sendMessageWithFDs(t, peer, message(displayObjectID, 1, body), nil)
162 if err := d.Dispatch(); err != nil {
163 t.Fatalf("delete_id for abandoned object: %v", err)
164 }
165 if got := d.AllocateID(); got != p.ID() {
166 t.Fatalf("ID %d not reused after delete_id (got %d)", p.ID(), got)
167 }
168}
169
170// A plain Go int is ambiguous (value or descriptor) and must be rejected
171// instead of being written as zero.
172func TestMarshalRejectsPlainInt(t *testing.T) {
173 d := newDisplay(&chunkConn{})
174 if err := d.marshalArg(&bytes.Buffer{}, 42); err == nil {
175 t.Fatal("marshalArg(int) succeeded, want error")
176 }
177 if err := d.SendRequest(9, 0, 42); err == nil {
178 t.Fatal("SendRequest with int argument succeeded, want error")
179 }
180}
181
182func TestCheckVersion(t *testing.T) {
183 if err := CheckVersion(0, 5, "x.y"); err != nil {
184 t.Fatalf("unknown version rejected: %v", err)
185 }
186 if err := CheckVersion(5, 5, "x.y"); err != nil {
187 t.Fatalf("exact version rejected: %v", err)
188 }
189 if err := CheckVersion(4, 5, "x.y"); !errors.Is(err, ErrVersionTooLow) {
190 t.Fatalf("older version = %v, want ErrVersionTooLow", err)
191 }
192}
193
194// Bind records the negotiated version so generated requests can check it.
195func TestBindRecordsVersion(t *testing.T) {
196 d := newDisplay(&chunkConn{})
197 p := &BaseProxy{}
198 if err := d.Registry().Bind(1, "wl_seat", 3, p); err != nil {
199 t.Fatal(err)
200 }
201 if p.Version() != 3 {
202 t.Fatalf("version = %d, want 3", p.Version())
203 }
204}
205
206// A late Unregister of a destroyed proxy (for example from a handler that was
207// already running) must not remove its zombie.
208func TestUnregisterKeepsZombie(t *testing.T) {
209 d := newDisplay(&chunkConn{})
210 p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}}
211 d.context.Register(p)
212 if err := d.context.SendDestructor(p, 0); err != nil {
213 t.Fatal(err)
214 }
215 d.context.Unregister(p)
216 if obj, _ := d.objects.Load(p.ID()); obj == nil {
217 t.Fatal("Unregister removed the zombie")
218 } else if _, ok := obj.(*zombie); !ok {
219 t.Fatalf("object = %T, want *zombie", obj)
220 }
221}
222
223// Context.Request owns the request lifecycle generated code relies on.
224func TestContextRequestLifecycle(t *testing.T) {
225 d := newDisplay(&chunkConn{})
226 parent := &BaseProxy{id: d.AllocateID(), context: d.context, version: 2}
227 d.context.Register(parent)
228
229 child := &BaseProxy{context: d.context}
230 if err := d.context.Request(Request{Proxy: parent, Opcode: 0, Name: "x.make", Child: child}, child); err != nil {
231 t.Fatal(err)
232 }
233 if child.ID() == 0 || child.Version() != 2 {
234 t.Fatalf("child id=%d version=%d, want allocated id and version 2", child.ID(), child.Version())
235 }
236 if _, ok := d.objects.Load(child.ID()); !ok {
237 t.Fatal("child not registered")
238 }
239
240 late := &BaseProxy{context: d.context}
241 err := d.context.Request(Request{Proxy: parent, Opcode: 1, Name: "x.late", Since: 3, Child: late}, late)
242 if !errors.Is(err, ErrVersionTooLow) {
243 t.Fatalf("since=3 on v2 = %v, want ErrVersionTooLow", err)
244 }
245 if late.ID() != 0 {
246 t.Fatal("child allocated for a refused request")
247 }
248
249 bad := &BaseProxy{context: d.context}
250 if err := d.context.Request(Request{Proxy: parent, Opcode: 2, Name: "x.bad", Child: bad}, bad, 42); err == nil {
251 t.Fatal("marshal error not reported")
252 }
253 if _, ok := d.objects.Load(bad.ID()); ok {
254 t.Fatal("child left registered after a failed send")
255 }
256}