main e82a709

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 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}

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