v0.5.0 ad674f5

hotpath_test.go RawHistoryFolder

1package server
2
3import (
4	"context"
5	"testing"
6	"unsafe"
7
8	"github.com/bnema/purego"
9	"golang.org/x/sys/unix"
10)
11
12// hotResource creates a real wl_client/resource on the display thread without
13// needing a separate client event loop.
14func hotResource(t testing.TB, d *Display, iface *Interface, h Handler) *Resource {
15	t.Helper()
16	lib, err := purego.Dlopen("libwayland-server.so.0", purego.RTLD_NOW)
17	if err != nil {
18		t.Fatal(err)
19	}
20	create, err := purego.Dlsym(lib, "wl_client_create")
21	if err != nil {
22		t.Fatal(err)
23	}
24	fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
25	if err != nil {
26		t.Fatal(err)
27	}
28	t.Cleanup(func() { _ = unix.Close(fds[1]) })
29	var r *Resource
30	if !d.Do(func() {
31		client, _, _ := purego.SyscallN(create, d.c, uintptr(fds[0]))
32		if client == 0 {
33			_ = unix.Close(fds[0])
34			return
35		}
36		r, err = (Client{client}).CreateResource(iface, 1, 2, h)
37	}) {
38		t.Fatal("display stopped")
39	}
40	if err != nil || r == nil {
41		t.Fatalf("create resource: %v", err)
42	}
43	return r
44}
45
46func hotDisplay(t testing.TB, iface *Interface, h Handler) (*Display, *Resource) {
47	t.Helper()
48	if err := NewInterfaces(iface); err != nil {
49		t.Fatal(err)
50	}
51	d, err := NewDisplay()
52	if err != nil {
53		t.Fatal(err)
54	}
55	ctx, cancel := context.WithCancel(context.Background())
56	done := make(chan error, 1)
57	go func() { done <- d.Run(ctx) }()
58	t.Cleanup(func() {
59		cancel()
60		if err := <-done; err != nil {
61			t.Error(err)
62		}
63	})
64	return d, hotResource(t, d, iface, h)
65}
66
67func TestServerAllocatedResourceID(t *testing.T) {
68	iface := &Interface{Name: "server_allocated_id_test", Version: 1}
69	d, existing := hotDisplay(t, iface, nil)
70	if !d.Do(func() {
71		r, err := (Client{existing.client}).CreateResource(iface, 1, 0, nil)
72		if err != nil {
73			t.Errorf("create server-side resource: %v", err)
74			return
75		}
76		if r.ID() == 0 || r.ID() != wlResourceGetID(r.c) {
77			t.Errorf("resource ID = %d, libwayland ID = %d", r.ID(), wlResourceGetID(r.c))
78		}
79	}) {
80		t.Fatal("display stopped")
81	}
82}
83
84func requestMessage(iface *Interface) unsafe.Pointer {
85	return *(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&tables.mem[0]), iface.c-tables.base+16))
86}
87
88var hotSink uint32
89
90func hotHandler(_ *Resource, _ uint32, args []Arg) { hotSink = args[0].Uint() }
91
92func TestHotPathAllocs(t *testing.T) {
93	iface := &Interface{Name: "hot_path_test", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}, Events: []Message{{Name: "event", Signature: "uuu"}}}
94	d, r := hotDisplay(t, iface, hotHandler)
95	var post, dispatchAllocs float64
96	if !d.Do(func() {
97		post = testing.AllocsPerRun(100, func() { r.PostEvent(0, Uint(1), Uint(2), Uint(3)) })
98		a := [3]Arg{Uint(1), Uint(2), Uint(3)}
99		dispatchAllocs = testing.AllocsPerRun(100, func() { dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0])) })
100	}) {
101		t.Fatal("display stopped")
102	}
103	if post != 0 || dispatchAllocs != 0 {
104		t.Fatalf("allocs: PostEvent=%g dispatch=%g, want zero", post, dispatchAllocs)
105	}
106}
107
108func BenchmarkDispatch(b *testing.B) {
109	iface := &Interface{Name: "hot_dispatch_bench", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}}
110	d, r := hotDisplay(b, iface, hotHandler)
111	a := [3]Arg{Uint(1), Uint(2), Uint(3)}
112	if !d.Do(func() {
113		b.ReportAllocs()
114		b.ResetTimer()
115		for i := 0; i < b.N; i++ {
116			dispatch(0, r.c, 0, requestMessage(iface), unsafe.Pointer(&a[0]))
117		}
118		b.StopTimer()
119	}) {
120		b.Fatal("display stopped")
121	}
122}
123func BenchmarkPostEvent(b *testing.B) {
124	iface := &Interface{Name: "hot_event_bench", Version: 1, Events: []Message{{Name: "event", Signature: "uuu"}}}
125	d, r := hotDisplay(b, iface, nil)
126	if !d.Do(func() {
127		b.ReportAllocs()
128		b.ResetTimer()
129		for i := 0; i < b.N; i++ {
130			r.PostEvent(0, Uint(1), Uint(2), Uint(3))
131		}
132		b.StopTimer()
133	}) {
134		b.Fatal("display stopped")
135	}
136}

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