1package wlturbo
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "net"
8 "testing"
9)
10
11// Unit tests that don't require a compositor
12
13func TestFixed(t *testing.T) {
14 // Test Fixed type conversion
15 tests := []struct {
16 input float64
17 expected float64
18 }{
19 {1.0, 1.0},
20 {0.5, 0.5},
21 {123.456, 123.456},
22 {-1.5, -1.5},
23 {0.0, 0.0},
24 {256.0, 256.0},
25 }
26
27 for _, test := range tests {
28 fixed := NewFixed(test.input)
29 result := fixed.Float64()
30
31 // Allow small precision differences
32 diff := result - test.expected
33 if diff < 0 {
34 diff = -diff
35 }
36 if diff > 0.01 {
37 t.Errorf("Fixed conversion: input=%f, expected=%f, got=%f",
38 test.input, test.expected, result)
39 }
40 }
41}
42
43func TestEventPool(t *testing.T) {
44 // Test that the global event pool works without requiring a compositor
45 // Get an event
46 event := eventPool.Get().(*Event)
47 if event == nil {
48 t.Fatal("Expected event from pool, got nil")
49 }
50
51 // Set some data
52 event.ProxyID = 123
53 event.Opcode = 456
54
55 // Return to pool
56 eventPool.Put(event)
57
58 // Get another event - should be reused
59 event2 := eventPool.Get().(*Event)
60 if event2 == nil {
61 t.Fatal("Expected reused event from pool, got nil")
62 }
63
64 // The pool might return a clean event or the same one, both are valid
65 t.Logf("Event2: ProxyID=%d, Opcode=%d", event2.ProxyID, event2.Opcode)
66
67 // Clean up
68 eventPool.Put(event2)
69}
70
71func TestEventDispatcher(t *testing.T) {
72 dispatcher := NewEventDispatcher()
73
74 called := false
75 handler := func(event *Event) {
76 called = true
77 if event.ProxyID != 123 || event.Opcode != 1 {
78 t.Errorf("Expected ProxyID=123, Opcode=1, got ProxyID=%d, Opcode=%d", event.ProxyID, event.Opcode)
79 }
80 }
81
82 // Register handler
83 dispatcher.RegisterHandler(123, 1, handler)
84
85 // Dispatch event
86 dispatcher.Dispatch(123, 1, []byte{})
87
88 if !called {
89 t.Error("Handler should have been called")
90 }
91}
92
93func TestEventDispatcherMultipleHandlers(t *testing.T) {
94 dispatcher := NewEventDispatcher()
95
96 called1 := false
97 called2 := false
98
99 handler1 := func(event *Event) {
100 called1 = true
101 }
102
103 handler2 := func(event *Event) {
104 called2 = true
105 }
106
107 // Register handlers for different opcodes
108 dispatcher.RegisterHandler(123, 1, handler1)
109 dispatcher.RegisterHandler(123, 2, handler2)
110
111 // Dispatch different events
112 dispatcher.Dispatch(123, 1, []byte{})
113 dispatcher.Dispatch(123, 2, []byte{})
114
115 if !called1 {
116 t.Error("Handler1 should have been called")
117 }
118 if !called2 {
119 t.Error("Handler2 should have been called")
120 }
121}
122
123func TestMessageMarshalingBasic(t *testing.T) {
124 // Create a display to test marshaling (without connecting)
125 d := &Display{
126 nextID: 2,
127 }
128
129 buf := &bytes.Buffer{}
130
131 // Test marshaling different argument types
132 tests := []struct {
133 name string
134 arg interface{}
135 want []byte
136 }{
137 {
138 name: "uint32",
139 arg: uint32(0x12345678),
140 want: []byte{0x78, 0x56, 0x34, 0x12}, // little endian
141 },
142 {
143 name: "int32",
144 arg: int32(-1),
145 want: []byte{0xFF, 0xFF, 0xFF, 0xFF},
146 },
147 {
148 name: "Fixed",
149 arg: NewFixed(1.0),
150 want: []byte{0x00, 0x01, 0x00, 0x00}, // 256 in little endian
151 },
152 {
153 name: "string",
154 arg: "test",
155 want: []byte{0x05, 0x00, 0x00, 0x00, 't', 'e', 's', 't', 0x00, 0x00, 0x00, 0x00}, // length + string + null + padding
156 },
157 {
158 name: "nil object",
159 arg: nil,
160 want: []byte{0x00, 0x00, 0x00, 0x00},
161 },
162 }
163
164 for _, test := range tests {
165 t.Run(test.name, func(t *testing.T) {
166 buf.Reset()
167 err := d.marshalArg(buf, test.arg)
168 if err != nil {
169 t.Fatalf("marshalArg failed: %v", err)
170 }
171
172 got := buf.Bytes()
173 if !bytes.Equal(got, test.want) {
174 t.Errorf("marshalArg(%v) = %v, want %v", test.arg, got, test.want)
175 }
176 })
177 }
178}
179
180func TestMessageHeaderParsing(t *testing.T) {
181 // Test parsing message headers
182 tests := []struct {
183 name string
184 header []byte
185 wantID uint32
186 wantSize uint32
187 wantOp uint16
188 }{
189 {
190 name: "basic header",
191 header: []byte{0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0C, 0x00}, // ID=5, opcode=2, size=12 (size is upper 16 bits)
192 wantID: 5,
193 wantSize: 12,
194 wantOp: 2,
195 },
196 {
197 name: "large values",
198 header: []byte{0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x10}, // ID=65535, opcode=255, size=4096
199 wantID: 65535,
200 wantSize: 4096,
201 wantOp: 255,
202 },
203 }
204
205 for _, test := range tests {
206 t.Run(test.name, func(t *testing.T) {
207 if len(test.header) != 8 {
208 t.Fatalf("header must be 8 bytes, got %d", len(test.header))
209 }
210
211 objectID := binary.LittleEndian.Uint32(test.header[0:4])
212 sizeOpcode := binary.LittleEndian.Uint32(test.header[4:8])
213 size := sizeOpcode >> 16
214 opcode := sizeOpcode & 0xffff
215
216 if objectID != test.wantID {
217 t.Errorf("object ID = %d, want %d", objectID, test.wantID)
218 }
219 if size != test.wantSize {
220 t.Errorf("size = %d, want %d", size, test.wantSize)
221 }
222 if uint16(opcode) != test.wantOp {
223 t.Errorf("opcode = %d, want %d", opcode, test.wantOp)
224 }
225 })
226 }
227}
228
229// Close must be safe to call repeatedly and must not report an error from the
230// second call.
231func TestDisplayCloseIsIdempotent(t *testing.T) {
232 d := newDisplay(&chunkConn{})
233
234 if err := d.Close(); err != nil {
235 t.Fatalf("first Close: %v", err)
236 }
237 if err := d.Close(); err != nil {
238 t.Fatalf("second Close: %v", err)
239 }
240 if !d.Closed() {
241 t.Error("Closed() = false after Close")
242 }
243}
244
245// A closed display rejects further dispatch instead of reading from a closed
246// descriptor.
247func TestDisplayDispatchAfterCloseReturnsErrClosed(t *testing.T) {
248 d := newDisplay(&chunkConn{chunks: [][]byte{message(7, 3, nil)}})
249
250 if err := d.Close(); err != nil {
251 t.Fatalf("Close: %v", err)
252 }
253 if err := d.Dispatch(); !errors.Is(err, net.ErrClosed) {
254 t.Fatalf("Dispatch after Close = %v, want %v", err, net.ErrClosed)
255 }
256}
257
258// Binding registers the proxy for dispatch, and unregistering removes it.
259func TestRegistryBindRegistration(t *testing.T) {
260 d := newDisplay(&chunkConn{})
261 records := []EventRecord{}
262 proxy := &recordingProxy{BaseProxy: BaseProxy{id: 7, context: d.Context()}, records: &records}
263
264 if err := d.Registry().Bind(1, "wl_compositor", 4, proxy); err != nil {
265 t.Fatalf("Bind: %v", err)
266 }
267 if _, ok := d.objects.Load(uint32(7)); !ok {
268 t.Fatal("bound proxy is not registered for dispatch")
269 }
270
271 d.Context().Unregister(proxy)
272 if _, ok := d.objects.Load(uint32(7)); ok {
273 t.Fatal("unregistered proxy is still registered for dispatch")
274 }
275}
276
277func TestAllocateID(t *testing.T) {
278 d := &Display{
279 nextID: 2, // Start at 2 (1 is reserved for display)
280 }
281
282 // Test sequential ID allocation
283 id1 := d.allocateID()
284 id2 := d.allocateID()
285 id3 := d.allocateID()
286
287 if id1 != 2 {
288 t.Errorf("First ID = %d, want 2", id1)
289 }
290 if id2 != 3 {
291 t.Errorf("Second ID = %d, want 3", id2)
292 }
293 if id3 != 4 {
294 t.Errorf("Third ID = %d, want 4", id3)
295 }
296}
297
298func TestRegistryGlobalStorage(t *testing.T) {
299 registry := &Registry{
300 id: 2,
301 globals: make(map[uint32]Global),
302 }
303
304 // Test storing and retrieving globals
305 global1 := Global{
306 Name: 1,
307 Interface: "wl_compositor",
308 Version: 4,
309 }
310
311 global2 := Global{
312 Name: 2,
313 Interface: "wl_seat",
314 Version: 7,
315 }
316
317 registry.globals[global1.Name] = global1
318 registry.globals[global2.Name] = global2
319
320 // Test GetGlobals
321 globals := registry.GetGlobals()
322 if len(globals) != 2 {
323 t.Errorf("GetGlobals() returned %d globals, want 2", len(globals))
324 }
325
326 // Test FindGlobal
327 found, exists := registry.FindGlobal("wl_compositor")
328 if !exists {
329 t.Error("wl_compositor should be found")
330 }
331 if found.Name != global1.Name || found.Version != global1.Version {
332 t.Errorf("Found global = %+v, want %+v", found, global1)
333 }
334
335 // Test non-existent global
336 _, exists = registry.FindGlobal("non_existent")
337 if exists {
338 t.Error("non_existent should not be found")
339 }
340}
341
342func BenchmarkEventDispatch(b *testing.B) {
343 dispatcher := NewEventDispatcher()
344
345 handler := func(event *Event) {
346 // Minimal handler for benchmarking
347 }
348
349 // Register handler
350 dispatcher.RegisterHandler(123, 1, handler)
351
352 data := []byte{0x01, 0x02, 0x03, 0x04}
353
354 b.ResetTimer()
355 for i := 0; i < b.N; i++ {
356 dispatcher.Dispatch(123, 1, data)
357 }
358}
359
360func BenchmarkFixedConversion(b *testing.B) {
361 values := []float64{1.0, 0.5, 123.456, -1.5, 256.789}
362
363 b.ResetTimer()
364 for i := 0; i < b.N; i++ {
365 for _, v := range values {
366 fixed := NewFixed(v)
367 _ = fixed.Float64()
368 }
369 }
370}