v0.12.0 15e9011

xwm_test.go RawHistoryFolder

1package server
2
3import (
4	"encoding/binary"
5	"runtime"
6	"testing"
7
8	"github.com/bnema/purego"
9	"golang.org/x/sys/unix"
10)
11
12func xwmDisplay(t testing.TB) *Display {
13	t.Helper()
14	runtime.LockOSThread()
15	t.Cleanup(runtime.UnlockOSThread)
16	d, err := NewDisplay()
17	if err != nil {
18		t.Fatal(err)
19	}
20	t.Cleanup(d.Close)
21	return d
22}
23
24func xwmClient(t testing.TB, d *Display) (Client, int) {
25	t.Helper()
26	fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0)
27	if err != nil {
28		t.Fatal(err)
29	}
30	t.Cleanup(func() { unix.Close(fds[1]) })
31	c, err := d.CreateClient(fds[0])
32	if err != nil {
33		unix.Close(fds[0])
34		t.Fatal(err)
35	}
36	return c, fds[1]
37}
38
39// registry requests exercise libwayland's real advertisement/filter path.
40func registry(t testing.TB, d *Display, fd int) int {
41	t.Helper()
42	request := [12]byte{}
43	binary.NativeEndian.PutUint32(request[:4], 1)
44	binary.NativeEndian.PutUint32(request[4:8], 12<<16|1)
45	binary.NativeEndian.PutUint32(request[8:], 2)
46	if _, err := unix.Write(fd, request[:]); err != nil {
47		t.Fatal(err)
48	}
49	if xwmDispatch(d) < 0 {
50		t.Fatalf("dispatch failed: errno=%v", lastErrno())
51	}
52	wlDisplayFlushClients(d.c)
53	var buf [4096]byte
54	n, err := unix.Read(fd, buf[:])
55	if err == unix.EAGAIN {
56		return 0
57	}
58	if err != nil {
59		t.Fatal(err)
60	}
61	return n
62}
63
64func TestGlobalFilter(t *testing.T) {
65	d := xwmDisplay(t)
66	iface := &Interface{Name: "xwm_filter_test", Version: 1}
67	if err := NewInterfaces(iface); err != nil {
68		t.Fatal(err)
69	}
70	g, err := d.AddGlobal(iface, 1, nil)
71	if err != nil {
72		t.Fatal(err)
73	}
74	allowed, a := xwmClient(t, d)
75	_, b := xwmClient(t, d)
76	calls := 0
77	d.SetGlobalFilter(func(c Client, got *Global) bool {
78		calls++
79		if got != g {
80			t.Error("wrong global handle")
81		}
82		return c == allowed
83	})
84	if registry(t, d, a) == 0 {
85		t.Fatal("allowed client saw no global")
86	}
87	if registry(t, d, b) != 0 {
88		t.Fatal("denied client saw global")
89	}
90	if calls != 2 {
91		t.Fatalf("filter calls = %d", calls)
92	}
93	allocs := testing.AllocsPerRun(1000, func() { purego.Syscall6(cbGlobalFilter, allowed.c, g.c, d.c, 0, 0, 0) })
94	if allocs != 0 {
95		t.Fatalf("filter allocs = %g", allocs)
96	}
97	// Creating a global with registries already present invokes the filter
98	// before AddGlobal returns, but still passes the eventual handle.
99	var during *Global
100	d.SetGlobalFilter(func(_ Client, got *Global) bool { during = got; return false })
101	second, err := d.AddGlobal(iface, 1, nil)
102	if err != nil {
103		t.Fatal(err)
104	}
105	if during != second {
106		t.Fatal("creation-time filter handle differs")
107	}
108	d.SetGlobalFilter(nil)
109	_, c := xwmClient(t, d)
110	if registry(t, d, c) == 0 {
111		t.Fatal("nil did not remove filter")
112	}
113	g.Remove()
114	g.Remove()
115}
116
117func TestWritableInterest(t *testing.T) {
118	d := xwmDisplay(t)
119	fds := xwmSocketpair(t)
120	fd := fds[0]
121	reads, writes := 0, 0
122	s, err := d.AddFDRW(fd, func() { reads++ }, func() { writes++ })
123	if err != nil {
124		t.Fatal(err)
125	}
126	dispatch := func() {
127		if xwmDispatch(d) < 0 {
128			t.Fatalf("dispatch failed: errno=%v", lastErrno())
129		}
130	}
131	dispatch()
132	if writes != 0 {
133		t.Fatal("writable starts enabled")
134	}
135	if err := s.SetWritable(true); err != nil {
136		t.Fatal(err)
137	}
138	dispatch()
139	if writes != 1 || reads != 0 {
140		t.Fatalf("reads=%d writes=%d", reads, writes)
141	}
142	allocs := testing.AllocsPerRun(1000, dispatch)
143	if allocs != 0 {
144		t.Fatalf("fd dispatch allocs = %g", allocs)
145	}
146	before := writes
147	if err := s.SetWritable(false); err != nil {
148		t.Fatal(err)
149	}
150	dispatch()
151	if writes != before {
152		t.Fatal("writable not disabled")
153	}
154	// Readable callbacks also stay allocation-free through the real C loop.
155	serverFD := fds[1]
156	var payload = [1]byte{1}
157	if _, err := unix.Write(serverFD, payload[:]); err != nil {
158		t.Fatal(err)
159	}
160	allocs = testing.AllocsPerRun(1000, dispatch)
161	if allocs != 0 {
162		t.Fatalf("readable dispatch allocs = %g", allocs)
163	}
164	if reads == 0 {
165		t.Fatal("readable interest lost")
166	}
167	if _, err := unix.Read(fd, payload[:]); err != nil {
168		t.Fatal(err)
169	}
170	reads = 0
171	// Combined masks, hangup routing and removal during readable delivery.
172	fdReadable(0, 1|2, s.token)
173	if reads != 1 || writes != before+1 {
174		t.Fatal("combined mask not delivered")
175	}
176	s.onReadable = func() { reads++; s.Remove() }
177	fdReadable(0, 4|8|2, s.token)
178	if reads != 2 || writes != before+1 {
179		t.Fatal("removal did not suppress writable")
180	}
181	if err := s.SetWritable(true); err != nil {
182		t.Fatal(err)
183	}
184	plain, err := d.AddFD(fd, func() {})
185	if err != nil {
186		t.Fatal(err)
187	}
188	if plain.SetWritable(true) == nil {
189		t.Fatal("enabled without writable callback")
190	}
191}
192
193func BenchmarkGlobalFilter(b *testing.B) {
194	d := xwmDisplay(b)
195	iface := &Interface{Name: "xwm_filter_bench", Version: 1}
196	if err := NewInterfaces(iface); err != nil {
197		b.Fatal(err)
198	}
199	g, err := d.AddGlobal(iface, 1, nil)
200	if err != nil {
201		b.Fatal(err)
202	}
203	c, _ := xwmClient(b, d)
204	d.SetGlobalFilter(func(Client, *Global) bool { return true })
205	b.ReportAllocs()
206	b.ResetTimer()
207	for b.Loop() {
208		purego.Syscall6(cbGlobalFilter, c.c, g.c, d.c, 0, 0, 0)
209	}
210}
211
212func BenchmarkFDCallback(b *testing.B) {
213	d := xwmDisplay(b)
214	fds := xwmSocketpair(b)
215	fd := fds[0]
216	s, err := d.AddFDRW(fd, func() {}, func() {})
217	if err != nil {
218		b.Fatal(err)
219	}
220	if err := s.SetWritable(true); err != nil {
221		b.Fatal(err)
222	}
223	b.ReportAllocs()
224	b.ResetTimer()
225	for b.Loop() {
226		xwmDispatch(d)
227	}
228}
229
230func TestLateRestrictedGlobal(t *testing.T) {
231	d := xwmDisplay(t)
232	allowed, a := xwmClient(t, d)
233	_, denied := xwmClient(t, d)
234	if registry(t, d, a) != 0 || registry(t, d, denied) != 0 {
235		t.Fatal("unexpected globals")
236	}
237	iface := &Interface{Name: "xwm_late_restricted", Version: 2}
238	if err := NewInterfaces(iface); err != nil {
239		t.Fatal(err)
240	}
241	calls, binds := 0, 0
242	policy := func(c Client, g *Global) bool {
243		calls++
244		if g.Version() != 2 {
245			t.Error("creation-time version unavailable")
246		}
247		return g.Interface() != iface.Name || c == allowed
248	}
249	d.SetGlobalFilter(policy)
250	g, err := d.AddGlobal(iface, 2, func(Client, uint32, uint32) { binds++ })
251	if err != nil {
252		t.Fatal(err)
253	}
254	if calls != 2 {
255		t.Fatalf("creation-time calls = %d", calls)
256	}
257	wlDisplayFlushClients(d.c)
258	var buf [4096]byte
259	n, err := unix.Read(a, buf[:])
260	if err != nil || n < 16 {
261		t.Fatalf("allowed announcement: n=%d err=%v", n, err)
262	}
263	name := binary.NativeEndian.Uint32(buf[8:12])
264	if n, err := unix.Read(denied, buf[:]); err != unix.EAGAIN || n > 0 {
265		t.Fatalf("denied announcement: n=%d err=%v", n, err)
266	}
267	if allocs := testing.AllocsPerRun(1000, func() { purego.Syscall6(cbGlobalFilter, allowed.c, g.c, d.c, 0, 0, 0) }); allocs != 0 {
268		t.Fatalf("metadata policy allocs = %g", allocs)
269	}
270	// Guessing a hidden global's numeric name must not bypass the filter.
271	strLen := len(iface.Name) + 1
272	padded := (strLen + 3) &^ 3
273	req := make([]byte, 24+padded)
274	binary.NativeEndian.PutUint32(req[0:4], 2)                    // wl_registry
275	binary.NativeEndian.PutUint32(req[4:8], uint32(len(req))<<16) // bind
276	binary.NativeEndian.PutUint32(req[8:12], name)
277	binary.NativeEndian.PutUint32(req[12:16], uint32(strLen))
278	copy(req[16:], iface.Name)
279	binary.NativeEndian.PutUint32(req[16+padded:], 2)
280	binary.NativeEndian.PutUint32(req[20+padded:], 3)
281	if _, err := unix.Write(denied, req); err != nil {
282		t.Fatal(err)
283	}
284	if xwmDispatch(d) < 0 {
285		t.Fatal("dispatch")
286	}
287	wlDisplayFlushClients(d.c)
288	if binds != 0 {
289		t.Fatal("denied bind reached handler")
290	}
291	n, err = unix.Read(denied, buf[:])
292	if err != nil || n < 12 || binary.NativeEndian.Uint32(buf[:4]) != 1 || binary.NativeEndian.Uint32(buf[4:8])&0xffff != 0 {
293		t.Fatalf("expected wl_display.error: n=%d err=%v", n, err)
294	}
295}
296
297func TestWritableBackpressureAndRemoval(t *testing.T) {
298	d := xwmDisplay(t)
299	fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0)
300	if err != nil {
301		t.Fatal(err)
302	}
303	defer unix.Close(fds[0])
304	defer unix.Close(fds[1])
305	if err := unix.SetsockoptInt(fds[0], unix.SOL_SOCKET, unix.SO_SNDBUF, 4096); err != nil {
306		t.Fatal(err)
307	}
308	var payload [4096]byte
309	total := 0
310	for attempts := 0; attempts < 1024; attempts++ {
311		n, err := unix.Write(fds[0], payload[:])
312		if err == unix.EAGAIN {
313			break
314		}
315		if err != nil {
316			t.Fatal(err)
317		}
318		total += n
319	}
320	if _, err := unix.Write(fds[0], payload[:]); err != unix.EAGAIN {
321		t.Fatalf("not backpressured: %v", err)
322	}
323	writes := 0
324	var s *Source
325	s, err = d.AddFDRW(fds[0], func() { t.Error("unexpected readable") }, func() {
326		writes++
327		if n, err := unix.Write(fds[0], payload[:1]); err != nil || n != 1 {
328			t.Errorf("resume: n=%d err=%v", n, err)
329		}
330		if err := s.SetWritable(false); err != nil {
331			t.Error(err)
332		}
333		s.Remove() // Removal from a real C-dispatched callback.
334	})
335	if err != nil {
336		t.Fatal(err)
337	}
338	if err := s.SetWritable(true); err != nil {
339		t.Fatal(err)
340	}
341	xwmDispatch(d)
342	if writes != 0 {
343		t.Fatal("writable fired before peer drained")
344	}
345	for remaining := total; remaining > 0; {
346		n, err := unix.Read(fds[1], payload[:])
347		if err != nil || n == 0 {
348			t.Fatalf("drain: n=%d err=%v", n, err)
349		}
350		remaining -= n
351	}
352	xwmDispatch(d)
353	if writes != 1 || s.c != 0 {
354		t.Fatalf("writes=%d source=%x", writes, s.c)
355	}
356	xwmDispatch(d)
357	if writes != 1 {
358		t.Fatal("removed source dispatched again")
359	}
360}
361
362// The Go runtime can interrupt epoll_wait with its preemption signal.
363func xwmDispatch(d *Display) int32 {
364	for attempt := 0; attempt < 16; attempt++ {
365		result := wlEventLoopDispatch(d.loop, 0)
366		if result >= 0 || lastErrno() != unix.EINTR {
367			return result
368		}
369	}
370	return -1
371}
372
373// FD sources use raw sockets, not Wayland clients: arbitrary payloads must
374// never enter libwayland's protocol transport or its flush/error handling.
375func xwmSocketpair(t testing.TB) [2]int {
376	t.Helper()
377	fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0)
378	if err != nil {
379		t.Fatal(err)
380	}
381	t.Cleanup(func() { unix.Close(fds[0]); unix.Close(fds[1]) })
382	return fds
383}

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 14

↑↓ moveenter openesc close