v0.2.0 7790fe5

alloc_test.go RawHistoryFolder

1package xkbcommon
2
3import (
4	"bytes"
5	"errors"
6	"io"
7	"os"
8	"runtime"
9	"strings"
10	"testing"
11
12	"github.com/bnema/purego-xkbcommon/raw"
13)
14
15// fdKeymap compiles a pinned Wayland-format keymap from testdata.
16func fdKeymap(t *testing.T, c *Context, layout string) *Keymap {
17	t.Helper()
18	text, e := os.ReadFile("testdata/keymaps/" + layout + ".xkb")
19	if e != nil {
20		t.Fatal(e)
21	}
22	f := keymapFile(t, append(text, 0))
23	k, e := c.NewKeymapFD(int(f.Fd()), len(text)+1)
24	if e != nil {
25		t.Fatal(e)
26	}
27	t.Cleanup(func() { k.Close() })
28	return k
29}
30
31func assertNoAllocs(t *testing.T, name string, fn func()) {
32	t.Helper()
33	fn() // warm up lazy runtime state
34	if n := testing.AllocsPerRun(200, fn); n != 0 {
35		t.Errorf("%s: %v allocs/op, want 0", name, n)
36	} else {
37		t.Logf("%s: 0 allocs/op", name)
38	}
39}
40
41func TestHotPathAllocs(t *testing.T) {
42	c := live(t)
43	k := fdKeymap(t, c, "fr")
44	s := state(t, k)
45	code := WaylandKeycode(16)
46	var buf [MaxUTF8Buffer]byte
47	var fail error
48	check := func(e error) {
49		if e != nil {
50			fail = e
51		}
52	}
53
54	table, e := c.NewComposeTable("en_US.UTF-8")
55	if e != nil {
56		t.Fatal(e)
57	}
58	defer table.Close()
59	compose, e := table.NewState()
60	if e != nil {
61		t.Fatal(e)
62	}
63	defer compose.Close()
64
65	assertNoAllocs(t, "State.KeySym", func() { _, e := s.KeySym(code); check(e) })
66	assertNoAllocs(t, "State.UTF8Into", func() { _, e := s.UTF8Into(code, buf[:]); check(e) })
67	assertNoAllocs(t, "State.UpdateMask", func() { _, e := s.UpdateMask(1, 0, 2, 0, 0, 0); check(e) })
68	assertNoAllocs(t, "State.Layout", func() { _, e := s.Layout(); check(e) })
69	assertNoAllocs(t, "State.Mods", func() { _, e := s.Mods(); check(e) })
70	assertNoAllocs(t, "State.ModIndexActive", func() { _, e := s.ModIndexActive(0); check(e) })
71	assertNoAllocs(t, "State.ModNameActive", func() { _, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); check(e) })
72	assertNoAllocs(t, "Keymap.KeyRepeats", func() { _, e := k.KeyRepeats(code); check(e) })
73	assertNoAllocs(t, "KeysymNameInto", func() { _, e := KeysymNameInto(raw.XKB_KEY_a, buf[:]); check(e) })
74	assertNoAllocs(t, "compose key sequence", func() {
75		_, e := compose.Feed(raw.XKB_KEY_dead_circumflex)
76		check(e)
77		_, e = compose.Feed(raw.XKB_KEY_e)
78		check(e)
79		_, e = compose.Status()
80		check(e)
81		_, e = compose.KeySym()
82		check(e)
83		_, e = compose.UTF8Into(buf[:])
84		check(e)
85		check(compose.Reset())
86	})
87	if fail != nil {
88		t.Fatal(fail)
89	}
90}
91
92// TestHotPathValues guards the fixed-arity dispatch against argument/return
93// conversion mistakes.
94func TestHotPathValues(t *testing.T) {
95	c := live(t)
96	k := fdKeymap(t, c, "us")
97	s := state(t, k)
98	if r, e := k.KeyRepeats(WaylandKeycode(30)); e != nil || !r {
99		t.Fatalf("KeyRepeats(a) = %v, %v", r, e)
100	}
101	ctrl, e := k.ModIndex(raw.XKB_MOD_NAME_CTRL)
102	if e != nil {
103		t.Fatal(e)
104	}
105	if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || a {
106		t.Fatalf("ctrl active before update: %v %v", a, e)
107	}
108	if _, e := s.UpdateMask(1<<ctrl, 0, 0, 0, 0, 0); e != nil {
109		t.Fatal(e)
110	}
111	if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || !a {
112		t.Fatalf("ctrl name active: %v %v", a, e)
113	}
114	if a, e := s.ModIndexActive(ctrl); e != nil || !a {
115		t.Fatalf("ctrl index active: %v %v", a, e)
116	}
117	if a, e := s.ModNameActive("NoSuchModifier"); e != nil || a {
118		t.Fatalf("unknown modifier active: %v %v", a, e)
119	}
120	var buf [16]byte
121	n, e := KeysymNameInto(raw.XKB_KEY_EuroSign, buf[:])
122	if e != nil || string(buf[:n]) != "EuroSign" {
123		t.Fatalf("KeysymNameInto = %q %v", buf[:n], e)
124	}
125}
126
127func TestModNameActiveLongName(t *testing.T) {
128	c := live(t)
129	k := fdKeymap(t, c, "us")
130	s := state(t, k)
131	for _, n := range []int{62, 63, 64, 65, 200} {
132		name := strings.Repeat("x", n)
133		if a, e := s.ModNameActive(name); e != nil || a {
134			t.Fatalf("len %d: %v %v", n, a, e)
135		}
136	}
137	// A short name after a long one must not see stale scratch bytes.
138	ctrl, e := k.ModIndex(raw.XKB_MOD_NAME_CTRL)
139	if e != nil {
140		t.Fatal(e)
141	}
142	if _, e := s.UpdateMask(1<<ctrl, 0, 0, 0, 0, 0); e != nil {
143		t.Fatal(e)
144	}
145	if a, e := s.ModNameActive(strings.Repeat("y", 63)); e != nil || a {
146		t.Fatal(a, e)
147	}
148	if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || !a {
149		t.Fatal(a, e)
150	}
151	if _, e := s.ModNameActive("a\x00b"); e == nil {
152		t.Fatal("embedded NUL accepted")
153	}
154}
155
156func TestKeysymNameIntoShortBuffer(t *testing.T) {
157	live(t)
158	buf := []byte{0xff, 0xff, 0xff}
159	n, e := KeysymNameInto(raw.XKB_KEY_EuroSign, buf)
160	if !errors.Is(e, io.ErrShortBuffer) || n != len("EuroSign") || !bytes.Equal(buf, make([]byte, 3)) {
161		t.Fatalf("n=%d err=%v buf=%v", n, e, buf)
162	}
163	if _, e = KeysymNameInto(raw.XKB_KEY_a, nil); !errors.Is(e, ErrUTF8Buffer) {
164		t.Fatal(e)
165	}
166}
167
168func TestModIndexActiveInvalid(t *testing.T) {
169	c := live(t)
170	s := state(t, fdKeymap(t, c, "us"))
171	for _, idx := range []uint32{64, 1 << 20, ^uint32(0)} {
172		if a, e := s.ModIndexActive(idx); e != nil || a {
173			t.Fatalf("idx %d: %v %v", idx, a, e)
174		}
175	}
176	s.Close()
177	if _, e := s.ModIndexActive(0); !errors.Is(e, ErrClosed) {
178		t.Fatal(e)
179	}
180	if _, e := s.ModNameActive("Shift"); !errors.Is(e, ErrClosed) {
181		t.Fatal(e)
182	}
183}
184
185func TestNewKeymapRulesNames(t *testing.T) {
186	c := live(t)
187	// Every RMLVO component is exercised so each pointer in RuleNames must stay valid.
188	k, e := c.NewKeymapRules("evdev", "pc105", "us,fr", "dvorak,", "grp:alt_shift_toggle")
189	if e != nil {
190		t.Fatal(e)
191	}
192	defer k.Close()
193	if n, e := k.LayoutCount(); e != nil || n != 2 {
194		t.Fatalf("layouts %d %v", n, e)
195	}
196	s := state(t, k)
197	runtime.GC()
198	// Dvorak puts apostrophe on the evdev KEY_Q position.
199	if sym, e := s.KeySym(WaylandKeycode(16)); e != nil || sym != raw.XKB_KEY_apostrophe {
200		t.Fatalf("dvorak sym %#x %v", sym, e)
201	}
202	// Defaults (all empty) still work, and an unknown layout is rejected.
203	d, e := c.NewKeymapRules("", "", "", "", "")
204	if e != nil {
205		t.Fatal(e)
206	}
207	d.Close()
208	if _, e = c.NewKeymapRules("evdev", "pc105", "nosuchlayout", "", ""); e == nil {
209		t.Fatal("unknown layout accepted")
210	}
211}
212
213func TestZeroFuncsPanics(t *testing.T) {
214	defer func() {
215		if recover() == nil {
216			t.Fatal("zero raw.Funcs must panic, not call address 0")
217		}
218	}()
219	var f raw.Funcs
220	f.Xkb_keymap_num_layouts(0)
221}

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 2

↑↓ moveenter openesc close