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 TestMessageMarshalingBasic(t *testing.T) {
44 // Create a display to test marshaling (without connecting)
45 d := &Display{
46 nextID: 2,
47 }
48
49 buf := &bytes.Buffer{}
50
51 // Test marshaling different argument types
52 tests := []struct {
53 name string
54 arg interface{}
55 want []byte
56 }{
57 {
58 name: "uint32",
59 arg: uint32(0x12345678),
60 want: []byte{0x78, 0x56, 0x34, 0x12}, // little endian
61 },
62 {
63 name: "int32",
64 arg: int32(-1),
65 want: []byte{0xFF, 0xFF, 0xFF, 0xFF},
66 },
67 {
68 name: "Fixed",
69 arg: NewFixed(1.0),
70 want: []byte{0x00, 0x01, 0x00, 0x00}, // 256 in little endian
71 },
72 {
73 name: "string",
74 arg: "test",
75 want: []byte{0x05, 0x00, 0x00, 0x00, 't', 'e', 's', 't', 0x00, 0x00, 0x00, 0x00}, // length + string + null + padding
76 },
77 {
78 name: "nil object",
79 arg: nil,
80 want: []byte{0x00, 0x00, 0x00, 0x00},
81 },
82 }
83
84 for _, test := range tests {
85 t.Run(test.name, func(t *testing.T) {
86 buf.Reset()
87 err := d.marshalArg(buf, test.arg)
88 if err != nil {
89 t.Fatalf("marshalArg failed: %v", err)
90 }
91
92 got := buf.Bytes()
93 if !bytes.Equal(got, test.want) {
94 t.Errorf("marshalArg(%v) = %v, want %v", test.arg, got, test.want)
95 }
96 })
97 }
98}
99
100func TestMessageHeaderParsing(t *testing.T) {
101 // Test parsing message headers
102 tests := []struct {
103 name string
104 header []byte
105 wantID uint32
106 wantSize uint32
107 wantOp uint16
108 }{
109 {
110 name: "basic header",
111 header: []byte{0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0C, 0x00}, // ID=5, opcode=2, size=12 (size is upper 16 bits)
112 wantID: 5,
113 wantSize: 12,
114 wantOp: 2,
115 },
116 {
117 name: "large values",
118 header: []byte{0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x10}, // ID=65535, opcode=255, size=4096
119 wantID: 65535,
120 wantSize: 4096,
121 wantOp: 255,
122 },
123 }
124
125 for _, test := range tests {
126 t.Run(test.name, func(t *testing.T) {
127 if len(test.header) != 8 {
128 t.Fatalf("header must be 8 bytes, got %d", len(test.header))
129 }
130
131 objectID := binary.LittleEndian.Uint32(test.header[0:4])
132 sizeOpcode := binary.LittleEndian.Uint32(test.header[4:8])
133 size := sizeOpcode >> 16
134 opcode := sizeOpcode & 0xffff
135
136 if objectID != test.wantID {
137 t.Errorf("object ID = %d, want %d", objectID, test.wantID)
138 }
139 if size != test.wantSize {
140 t.Errorf("size = %d, want %d", size, test.wantSize)
141 }
142 if uint16(opcode) != test.wantOp {
143 t.Errorf("opcode = %d, want %d", opcode, test.wantOp)
144 }
145 })
146 }
147}
148
149// Close must be safe to call repeatedly and must not report an error from the
150// second call.
151func TestDisplayCloseIsIdempotent(t *testing.T) {
152 d := newDisplay(&chunkConn{})
153
154 if err := d.Close(); err != nil {
155 t.Fatalf("first Close: %v", err)
156 }
157 if err := d.Close(); err != nil {
158 t.Fatalf("second Close: %v", err)
159 }
160 if !d.Closed() {
161 t.Error("Closed() = false after Close")
162 }
163}
164
165// A closed display rejects further dispatch instead of reading from a closed
166// descriptor.
167func TestDisplayDispatchAfterCloseReturnsErrClosed(t *testing.T) {
168 d := newDisplay(&chunkConn{chunks: [][]byte{message(7, 3, nil)}})
169
170 if err := d.Close(); err != nil {
171 t.Fatalf("Close: %v", err)
172 }
173 if err := d.Dispatch(); !errors.Is(err, net.ErrClosed) {
174 t.Fatalf("Dispatch after Close = %v, want %v", err, net.ErrClosed)
175 }
176}
177
178// Binding registers the proxy for dispatch, and unregistering removes it.
179func TestRegistryBindRegistration(t *testing.T) {
180 d := newDisplay(&chunkConn{})
181 records := []EventRecord{}
182 proxy := &recordingProxy{BaseProxy: BaseProxy{id: 7, context: d.Context()}, records: &records}
183
184 if err := d.Registry().Bind(1, "wl_compositor", 4, proxy); err != nil {
185 t.Fatalf("Bind: %v", err)
186 }
187 if _, ok := d.objects.Load(uint32(7)); !ok {
188 t.Fatal("bound proxy is not registered for dispatch")
189 }
190
191 d.Context().Unregister(proxy)
192 if _, ok := d.objects.Load(uint32(7)); ok {
193 t.Fatal("unregistered proxy is still registered for dispatch")
194 }
195}
196
197func TestAllocateID(t *testing.T) {
198 d := &Display{
199 nextID: 2, // Start at 2 (1 is reserved for display)
200 }
201
202 // Test sequential ID allocation
203 id1 := d.allocateID()
204 id2 := d.allocateID()
205 id3 := d.allocateID()
206
207 if id1 != 2 {
208 t.Errorf("First ID = %d, want 2", id1)
209 }
210 if id2 != 3 {
211 t.Errorf("Second ID = %d, want 3", id2)
212 }
213 if id3 != 4 {
214 t.Errorf("Third ID = %d, want 4", id3)
215 }
216}
217
218func TestRegistryGlobalStorage(t *testing.T) {
219 registry := &Registry{
220 id: 2,
221 globals: make(map[uint32]Global),
222 }
223
224 // Test storing and retrieving globals
225 global1 := Global{
226 Name: 1,
227 Interface: "wl_compositor",
228 Version: 4,
229 }
230
231 global2 := Global{
232 Name: 2,
233 Interface: "wl_seat",
234 Version: 7,
235 }
236
237 registry.globals[global1.Name] = global1
238 registry.globals[global2.Name] = global2
239
240 // Test GetGlobals
241 globals := registry.GetGlobals()
242 if len(globals) != 2 {
243 t.Errorf("GetGlobals() returned %d globals, want 2", len(globals))
244 }
245
246 // Test FindGlobal
247 found, exists := registry.FindGlobal("wl_compositor")
248 if !exists {
249 t.Error("wl_compositor should be found")
250 }
251 if found.Name != global1.Name || found.Version != global1.Version {
252 t.Errorf("Found global = %+v, want %+v", found, global1)
253 }
254
255 // Test non-existent global
256 _, exists = registry.FindGlobal("non_existent")
257 if exists {
258 t.Error("non_existent should not be found")
259 }
260}
261
262func BenchmarkFixedConversion(b *testing.B) {
263 values := []float64{1.0, 0.5, 123.456, -1.5, 256.789}
264
265 b.ResetTimer()
266 for i := 0; i < b.N; i++ {
267 for _, v := range values {
268 fixed := NewFixed(v)
269 _ = fixed.Float64()
270 }
271 }
272}