v0.2.0 ab733e5

client_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close