detached bfb1f6d

test: cover hidden secret keys, mask no-op, no_keymap, pointer and modifier allocations

commit
bfb1f6d032ad021f122906c7c9bdf01f0035e629
author
brice · · 2026-10-01 18:57 UTC
parent
869d03d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +146 -15
M alloc_test.go +3 -1
@@ -17,7 +17,7 @@ import (
1717 type countingHandler struct {
1818 neferclient.NopHandler
1919 fdReady, errs, frames int
20- keys int
20+ keys, pointers int
2121 lastText string // set only by the allocation-free comparison below
2222 }
2323
@@ -25,6 +25,8 @@ func (h *countingHandler) FDReady(uint64) { h.fdReady++ }
2525 func (h *countingHandler) Error(error) { h.errs++ }
2626 func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ }
2727
28+func (h *countingHandler) Pointer(*neferclient.PointerEvent) { h.pointers++ }
29+
2830 // Key records the text through a comparison that does not allocate: the
2931 // string is only built the first time a different text shows up.
3032 func (h *countingHandler) Key(ev *neferclient.KeyEvent) {
@@ -17,7 +17,7 @@ import (
17 type countingHandler struct {17 type countingHandler struct {
18 neferclient.NopHandler18 neferclient.NopHandler
19 fdReady, errs, frames int19 fdReady, errs, frames int
20- keys int20+ keys, pointers int
21 lastText string // set only by the allocation-free comparison below21 lastText string // set only by the allocation-free comparison below
22 }22 }
23 23
@@ -25,6 +25,8 @@ func (h *countingHandler) FDReady(uint64) { h.fdReady++ }
25 func (h *countingHandler) Error(error) { h.errs++ }25 func (h *countingHandler) Error(error) { h.errs++ }
26 func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ }26 func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ }
27 27
28+func (h *countingHandler) Pointer(*neferclient.PointerEvent) { h.pointers++ }
29+
28 // Key records the text through a comparison that does not allocate: the30 // Key records the text through a comparison that does not allocate: the
29 // string is only built the first time a different text shows up.31 // string is only built the first time a different text shows up.
30 func (h *countingHandler) Key(ev *neferclient.KeyEvent) {32 func (h *countingHandler) Key(ev *neferclient.KeyEvent) {
M keyboard_test.go +67 -8
@@ -126,14 +126,14 @@ func TestKeyboardComposeAndFocus(t *testing.T) {
126126
127127 pressKey(t, k, keyCircum)
128128 k.setFocus(false)
129- _, ok := pressKey2(k, keyE)
129+ _, ok := pressUnchecked(k, keyE)
130130 require.False(t, ok, "no key without focus")
131131 k.setFocus(true)
132132 ev, _ := pressKey(t, k, keyE)
133133 require.Equal(t, "e", string(ev.Text), "focus loss resets compose")
134134 }
135135
136-func pressKey2(k *keyboard, code uint32) (KeyEvent, bool) {
136+func pressUnchecked(k *keyboard, code uint32) (KeyEvent, bool) {
137137 var ev KeyEvent
138138 ok, _ := k.press(code, &ev)
139139 return ev, ok
@@ -193,16 +193,27 @@ func TestSecretKeysNeverReachText(t *testing.T) {
193193 ev, ok := pressKey(t, k, code)
194194 require.True(t, ok)
195195 require.Nil(t, ev.Text, "secret mode never fills KeyEvent.Text")
196- releaseKey(k, code)
196+ rel, ok := releaseKey(k, code)
197+ require.True(t, ok)
198+ require.Equal(t, ev.Secret, rel.Secret, "the release is hidden like the press")
199+ if ev.Secret {
200+ require.Zero(t, rel.Keycode)
201+ require.Zero(t, rel.Keysym)
202+ }
197203 return ev
198204 }
199- press(keyQ)
200- press(keyCircum) // dead key
201- press(keyE) // composes ê
205+ for _, code := range []uint32{keyQ, keyCircum, keyE} { // text, dead key, composed ê
206+ ev := press(code)
207+ require.True(t, ev.Secret, "text keys are hidden")
208+ require.Zero(t, ev.Keycode)
209+ require.Zero(t, ev.Keysym)
210+ }
202211 require.Equal(t, "qê", string(buf.Bytes()))
203212 require.Equal(t, 2, buf.Len())
204213
205- press(keyBackspace)
214+ bs := press(keyBackspace)
215+ require.False(t, bs.Secret)
216+ require.Equal(t, uint32(raw.XKB_KEY_BackSpace), bs.Keysym, "BackSpace keeps its keysym")
206217 require.Equal(t, 1, buf.Len(), "backspace removes one code point")
207218 press(keyBackspace)
208219 press(keyBackspace) // empty: nothing to remove
@@ -210,6 +221,7 @@ func TestSecretKeysNeverReachText(t *testing.T) {
210221
211222 press(keyQ)
212223 enter := press(keyEnter)
224+ require.False(t, enter.Secret)
213225 require.Equal(t, uint32(raw.XKB_KEY_Return), enter.Keysym)
214226 require.Equal(t, 1, buf.Len(), "Enter is delivered, not typed")
215227
@@ -218,6 +230,8 @@ func TestSecretKeysNeverReachText(t *testing.T) {
218230 require.NoError(t, err)
219231 require.NoError(t, k.setMask(1<<idx, 0, 0, 0))
220232 ev := press(keyU)
233+ require.False(t, ev.Secret)
234+ require.NotZero(t, ev.Keysym, "Control combinations keep their keysym")
221235 require.Equal(t, ModCtrl, ev.Modifiers)
222236 require.Equal(t, 1, buf.Len())
223237 require.NoError(t, k.setMask(0, 0, 0, 0))
@@ -228,11 +242,16 @@ func TestSecretKeysNeverReachText(t *testing.T) {
228242 }
229243 require.Equal(t, 8, buf.Len())
230244
231- // Scratch is wiped after use.
245+ // Scratch and the work area are wiped after use.
232246 require.Equal(t, make([]byte, scratchSize), k.scratch[:])
247+ require.Equal(t, secretWork{}, k.w)
248+ for _, h := range k.held {
249+ require.Zero(t, h, "no stale held key")
250+ }
233251
234252 k.setSecret(nil)
235253 ev, _ = pressKey(t, k, keyQ)
254+ require.False(t, ev.Secret)
236255 require.Equal(t, "q", string(ev.Text), "ordinary text again once the buffer is removed")
237256 }
238257
@@ -281,3 +300,43 @@ func TestSecretRepeat(t *testing.T) {
281300 require.True(t, ok)
282301 require.Equal(t, 1, buf.Len())
283302 }
303+
304+func TestSecretRepeatIsHiddenToo(t *testing.T) {
305+ k := testKeyboard(t, "C", "us")
306+ k.setSecret(NewSecretBuffer(8))
307+ k.setRepeat(100, 1)
308+ pressKey(t, k, keyQ)
309+ require.Eventually(t, k.expired, 2e9, 1e6)
310+ var ev KeyEvent
311+ ok, err := k.repeat(&ev)
312+ require.NoError(t, err)
313+ require.True(t, ok)
314+ require.True(t, ev.Secret)
315+ require.Zero(t, ev.Keycode)
316+ require.Zero(t, ev.Keysym)
317+}
318+
319+func TestKeyboardHeldTailAndResetAreCleared(t *testing.T) {
320+ k := testKeyboard(t, "C", "us")
321+ k.setSecret(NewSecretBuffer(8))
322+ pressKey(t, k, keyQ)
323+ pressKey(t, k, keyE)
324+ releaseKey(k, keyQ)
325+ require.Equal(t, heldKey{}, k.held[:cap(k.held)][1], "vacated slot cleared")
326+ k.setFocus(false)
327+ for _, h := range k.held[:cap(k.held)] {
328+ require.Zero(t, h)
329+ }
330+}
331+
332+func TestKeyboardIdenticalMaskIsNoOp(t *testing.T) {
333+ k := testKeyboard(t, "C", "us")
334+ idx, err := k.keymap.ModIndex("Shift")
335+ require.NoError(t, err)
336+ require.NoError(t, k.setMask(1<<idx, 0, 0, 0))
337+ require.Equal(t, ModShift, k.mods)
338+ require.NoError(t, k.setMask(1<<idx, 0, 0, 0))
339+ require.Equal(t, ModShift, k.mods)
340+ require.NoError(t, k.setMask(0, 0, 0, 0))
341+ require.Zero(t, k.mods)
342+}
@@ -126,14 +126,14 @@ func TestKeyboardComposeAndFocus(t *testing.T) {
126 126
127 pressKey(t, k, keyCircum)127 pressKey(t, k, keyCircum)
128 k.setFocus(false)128 k.setFocus(false)
129- _, ok := pressKey2(k, keyE)129+ _, ok := pressUnchecked(k, keyE)
130 require.False(t, ok, "no key without focus")130 require.False(t, ok, "no key without focus")
131 k.setFocus(true)131 k.setFocus(true)
132 ev, _ := pressKey(t, k, keyE)132 ev, _ := pressKey(t, k, keyE)
133 require.Equal(t, "e", string(ev.Text), "focus loss resets compose")133 require.Equal(t, "e", string(ev.Text), "focus loss resets compose")
134 }134 }
135 135
136-func pressKey2(k *keyboard, code uint32) (KeyEvent, bool) {136+func pressUnchecked(k *keyboard, code uint32) (KeyEvent, bool) {
137 var ev KeyEvent137 var ev KeyEvent
138 ok, _ := k.press(code, &ev)138 ok, _ := k.press(code, &ev)
139 return ev, ok139 return ev, ok
@@ -193,16 +193,27 @@ func TestSecretKeysNeverReachText(t *testing.T) {
193 ev, ok := pressKey(t, k, code)193 ev, ok := pressKey(t, k, code)
194 require.True(t, ok)194 require.True(t, ok)
195 require.Nil(t, ev.Text, "secret mode never fills KeyEvent.Text")195 require.Nil(t, ev.Text, "secret mode never fills KeyEvent.Text")
196- releaseKey(k, code)196+ rel, ok := releaseKey(k, code)
197+ require.True(t, ok)
198+ require.Equal(t, ev.Secret, rel.Secret, "the release is hidden like the press")
199+ if ev.Secret {
200+ require.Zero(t, rel.Keycode)
201+ require.Zero(t, rel.Keysym)
202+ }
197 return ev203 return ev
198 }204 }
199- press(keyQ)205+ for _, code := range []uint32{keyQ, keyCircum, keyE} { // text, dead key, composed ê
200- press(keyCircum) // dead key206+ ev := press(code)
201- press(keyE) // composes ê207+ require.True(t, ev.Secret, "text keys are hidden")
208+ require.Zero(t, ev.Keycode)
209+ require.Zero(t, ev.Keysym)
210+ }
202 require.Equal(t, "qê", string(buf.Bytes()))211 require.Equal(t, "qê", string(buf.Bytes()))
203 require.Equal(t, 2, buf.Len())212 require.Equal(t, 2, buf.Len())
204 213
205- press(keyBackspace)214+ bs := press(keyBackspace)
215+ require.False(t, bs.Secret)
216+ require.Equal(t, uint32(raw.XKB_KEY_BackSpace), bs.Keysym, "BackSpace keeps its keysym")
206 require.Equal(t, 1, buf.Len(), "backspace removes one code point")217 require.Equal(t, 1, buf.Len(), "backspace removes one code point")
207 press(keyBackspace)218 press(keyBackspace)
208 press(keyBackspace) // empty: nothing to remove219 press(keyBackspace) // empty: nothing to remove
@@ -210,6 +221,7 @@ func TestSecretKeysNeverReachText(t *testing.T) {
210 221
211 press(keyQ)222 press(keyQ)
212 enter := press(keyEnter)223 enter := press(keyEnter)
224+ require.False(t, enter.Secret)
213 require.Equal(t, uint32(raw.XKB_KEY_Return), enter.Keysym)225 require.Equal(t, uint32(raw.XKB_KEY_Return), enter.Keysym)
214 require.Equal(t, 1, buf.Len(), "Enter is delivered, not typed")226 require.Equal(t, 1, buf.Len(), "Enter is delivered, not typed")
215 227
@@ -218,6 +230,8 @@ func TestSecretKeysNeverReachText(t *testing.T) {
218 require.NoError(t, err)230 require.NoError(t, err)
219 require.NoError(t, k.setMask(1<<idx, 0, 0, 0))231 require.NoError(t, k.setMask(1<<idx, 0, 0, 0))
220 ev := press(keyU)232 ev := press(keyU)
233+ require.False(t, ev.Secret)
234+ require.NotZero(t, ev.Keysym, "Control combinations keep their keysym")
221 require.Equal(t, ModCtrl, ev.Modifiers)235 require.Equal(t, ModCtrl, ev.Modifiers)
222 require.Equal(t, 1, buf.Len())236 require.Equal(t, 1, buf.Len())
223 require.NoError(t, k.setMask(0, 0, 0, 0))237 require.NoError(t, k.setMask(0, 0, 0, 0))
@@ -228,11 +242,16 @@ func TestSecretKeysNeverReachText(t *testing.T) {
228 }242 }
229 require.Equal(t, 8, buf.Len())243 require.Equal(t, 8, buf.Len())
230 244
231- // Scratch is wiped after use.245+ // Scratch and the work area are wiped after use.
232 require.Equal(t, make([]byte, scratchSize), k.scratch[:])246 require.Equal(t, make([]byte, scratchSize), k.scratch[:])
247+ require.Equal(t, secretWork{}, k.w)
248+ for _, h := range k.held {
249+ require.Zero(t, h, "no stale held key")
250+ }
233 251
234 k.setSecret(nil)252 k.setSecret(nil)
235 ev, _ = pressKey(t, k, keyQ)253 ev, _ = pressKey(t, k, keyQ)
254+ require.False(t, ev.Secret)
236 require.Equal(t, "q", string(ev.Text), "ordinary text again once the buffer is removed")255 require.Equal(t, "q", string(ev.Text), "ordinary text again once the buffer is removed")
237 }256 }
238 257
@@ -281,3 +300,43 @@ func TestSecretRepeat(t *testing.T) {
281 require.True(t, ok)300 require.True(t, ok)
282 require.Equal(t, 1, buf.Len())301 require.Equal(t, 1, buf.Len())
283 }302 }
303+
304+func TestSecretRepeatIsHiddenToo(t *testing.T) {
305+ k := testKeyboard(t, "C", "us")
306+ k.setSecret(NewSecretBuffer(8))
307+ k.setRepeat(100, 1)
308+ pressKey(t, k, keyQ)
309+ require.Eventually(t, k.expired, 2e9, 1e6)
310+ var ev KeyEvent
311+ ok, err := k.repeat(&ev)
312+ require.NoError(t, err)
313+ require.True(t, ok)
314+ require.True(t, ev.Secret)
315+ require.Zero(t, ev.Keycode)
316+ require.Zero(t, ev.Keysym)
317+}
318+
319+func TestKeyboardHeldTailAndResetAreCleared(t *testing.T) {
320+ k := testKeyboard(t, "C", "us")
321+ k.setSecret(NewSecretBuffer(8))
322+ pressKey(t, k, keyQ)
323+ pressKey(t, k, keyE)
324+ releaseKey(k, keyQ)
325+ require.Equal(t, heldKey{}, k.held[:cap(k.held)][1], "vacated slot cleared")
326+ k.setFocus(false)
327+ for _, h := range k.held[:cap(k.held)] {
328+ require.Zero(t, h)
329+ }
330+}
331+
332+func TestKeyboardIdenticalMaskIsNoOp(t *testing.T) {
333+ k := testKeyboard(t, "C", "us")
334+ idx, err := k.keymap.ModIndex("Shift")
335+ require.NoError(t, err)
336+ require.NoError(t, k.setMask(1<<idx, 0, 0, 0))
337+ require.Equal(t, ModShift, k.mods)
338+ require.NoError(t, k.setMask(1<<idx, 0, 0, 0))
339+ require.Equal(t, ModShift, k.mods)
340+ require.NoError(t, k.setMask(0, 0, 0, 0))
341+ require.Zero(t, k.mods)
342+}
M seat_wire_test.go +76 -6
@@ -6,6 +6,7 @@ import (
66 "runtime"
77 "sync"
88 "testing"
9+ "time"
910
1011 "github.com/stretchr/testify/mock"
1112 "github.com/stretchr/testify/require"
@@ -282,12 +283,19 @@ func TestSeatKeymapFDClosedOnClose(t *testing.T) {
282283 require.Equal(t, without, run(true), "descriptor closed with the connection")
283284 }
284285
285-// TestSeatKeymapFDClosedOnBadFormat: an unsupported keymap is closed, not leaked.
286+// TestSeatKeymapFDClosedOnBadFormat: an unknown keymap format is reported and
287+// its descriptor closed; no_keymap (0) is closed silently.
286288 func TestSeatKeymapFDClosedOnBadFormat(t *testing.T) {
287289 c, _, peer, _ := seatSetup(t)
288290 _, kb := peer.ids()
289291 before := openFDs(t)
290- peer.keymap(kb, 0) // XKB_V1 is 1; 0 is no_keymap
292+
293+ peer.keymap(kb, 0) // no_keymap: no error, no leak
294+ require.NoError(t, c.Roundtrip())
295+ require.NoError(t, c.Dispatch(neferclientmocks.NewMockHandler(t))) // any Handler call fails
296+ require.Equal(t, before, openFDs(t))
297+
298+ peer.keymap(kb, 7) // unknown format
291299 require.NoError(t, c.Roundtrip())
292300 var errs int
293301 h := neferclientmocks.NewMockHandler(t)
@@ -320,7 +328,6 @@ func TestSeatCapabilityLossReleasesDevices(t *testing.T) {
320328 peer.mu.Lock()
321329 require.True(t, peer.pointerReleased)
322330 peer.mu.Unlock()
323- _ = srv
324331 }
325332
326333 // TestSeatRepeatThroughEpoll: the repeat timerfd is watched by the connection's
@@ -350,9 +357,7 @@ func TestSeatRepeatThroughEpoll(t *testing.T) {
350357 require.NoError(t, c.Roundtrip())
351358 require.NoError(t, c.Dispatch(h))
352359 n := repeats
353- for range 5 {
354- runtime.Gosched()
355- }
360+ time.Sleep(50 * time.Millisecond) // five repeat intervals at 100/s
356361 require.NoError(t, c.Dispatch(h))
357362 require.Equal(t, n, repeats, "release stops the repeat")
358363 }
@@ -366,6 +371,10 @@ func TestSeatRepeatThroughEpoll(t *testing.T) {
366371 // cost so a regression above it shows.
367372 const xkbAllocsPerPress = 20
368373
374+// xkbAllocsPerMask bounds a mask change: update_mask, serialize_mods and
375+// serialize_layout, boxed the same way.
376+const xkbAllocsPerMask = 12
377+
369378 // TestAllocKey measures the steady-state key path over a real socketpair, on
370379 // the real code path (no mocks): key frames in, reader decode, queue, Dispatch
371380 // with xkb state and translation, and the Handler call.
@@ -425,6 +434,23 @@ func TestAllocKey(t *testing.T) {
425434 require.LessOrEqual(t, text, float64(pairs*xkbAllocsPerPress))
426435 require.Equal(t, "a", h.lastText)
427436
437+ // Modifier masks: an identical mask is a no-op and allocates nothing; a
438+ // changing one updates the xkb state (purego-xkbcommon calls, bounded).
439+ mods := func(depressed uint32) []byte {
440+ b := appendU32(appendU32(nil, 1), depressed)
441+ return frame(kb, 4, appendU32(appendU32(appendU32(b, 0), 0), 0))
442+ }
443+ var same, changing []byte
444+ for range 100 {
445+ same = append(same, mods(0)...)
446+ }
447+ for i := range 100 {
448+ changing = append(changing, mods(uint32(i%2))...)
449+ }
450+ require.Zero(t, measure("modifiers, 100 identical masks", run(same, 100)), "identical masks cost nothing")
451+ changes := measure("modifiers, 100 alternating masks", run(changing, 100))
452+ require.LessOrEqual(t, changes, float64(100*xkbAllocsPerMask))
453+
428454 buf := neferclient.NewSecretBuffer(4096)
429455 c.Seat().SetSecret(buf)
430456 secret := measure("secret, 50 presses + releases", step)
@@ -434,3 +460,47 @@ func TestAllocKey(t *testing.T) {
434460 buf.Wipe()
435461 require.Zero(t, h.errs)
436462 }
463+
464+// TestAllocPointer measures the pointer path over a real socketpair: a batch
465+// of motion, button and axis frames in, reader decode, queue, Dispatch and the
466+// Handler call. Pointer events need no xkb, so the whole path allocates
467+// nothing.
468+func TestAllocPointer(t *testing.T) {
469+ skipUnderRace(t)
470+ c, srv, peer, s := seatSetup(t)
471+ pointer, _ := peer.ids()
472+ enter := appendU32(appendU32(nil, 5), s.SurfaceObjectID())
473+ srv.write(frame(pointer, 0, appendU32(appendU32(enter, fixed(1)), fixed(1))))
474+ require.NoError(t, c.Roundtrip())
475+ h := &countingHandler{}
476+ require.NoError(t, c.Dispatch(h))
477+ require.Equal(t, 1, h.pointers)
478+
479+ const rounds = 40
480+ var batch []byte
481+ for i := range rounds {
482+ batch = append(batch, frame(pointer, 2, append(appendU32(appendU32(nil, 0), fixed(float64(i))), appendU32(nil, fixed(2))...))...)
483+ batch = append(batch, frame(pointer, 3, appendU32(appendU32(appendU32(appendU32(nil, 6), 0), 0x110), uint32(i%2)))...)
484+ batch = append(batch, frame(pointer, 4, appendU32(appendU32(appendU32(nil, 0), 0), fixed(2)))...)
485+ batch = append(batch, frame(pointer, 9, appendU32(appendU32(nil, 0), 120))...)
486+ }
487+ step := func() {
488+ if _, err := srv.conn.Write(batch); err != nil {
489+ t.Fatal(err)
490+ }
491+ for c.QueueLen() < 4*rounds {
492+ runtime.Gosched()
493+ }
494+ if err := c.Dispatch(h); err != nil {
495+ t.Fatal(err)
496+ }
497+ }
498+ step()
499+ step()
500+ before := h.pointers
501+ allocs := testing.AllocsPerRun(20, step)
502+ t.Logf("allocs per Dispatch of %d pointer events: %v", 4*rounds, allocs)
503+ require.Zero(t, allocs)
504+ require.Greater(t, h.pointers, before)
505+ require.Zero(t, h.errs)
506+}
@@ -6,6 +6,7 @@ import (
6 "runtime"6 "runtime"
7 "sync"7 "sync"
8 "testing"8 "testing"
9+ "time"
9 10
10 "github.com/stretchr/testify/mock"11 "github.com/stretchr/testify/mock"
11 "github.com/stretchr/testify/require"12 "github.com/stretchr/testify/require"
@@ -282,12 +283,19 @@ func TestSeatKeymapFDClosedOnClose(t *testing.T) {
282 require.Equal(t, without, run(true), "descriptor closed with the connection")283 require.Equal(t, without, run(true), "descriptor closed with the connection")
283 }284 }
284 285
285-// TestSeatKeymapFDClosedOnBadFormat: an unsupported keymap is closed, not leaked.286+// TestSeatKeymapFDClosedOnBadFormat: an unknown keymap format is reported and
287+// its descriptor closed; no_keymap (0) is closed silently.
286 func TestSeatKeymapFDClosedOnBadFormat(t *testing.T) {288 func TestSeatKeymapFDClosedOnBadFormat(t *testing.T) {
287 c, _, peer, _ := seatSetup(t)289 c, _, peer, _ := seatSetup(t)
288 _, kb := peer.ids()290 _, kb := peer.ids()
289 before := openFDs(t)291 before := openFDs(t)
290- peer.keymap(kb, 0) // XKB_V1 is 1; 0 is no_keymap292+
293+ peer.keymap(kb, 0) // no_keymap: no error, no leak
294+ require.NoError(t, c.Roundtrip())
295+ require.NoError(t, c.Dispatch(neferclientmocks.NewMockHandler(t))) // any Handler call fails
296+ require.Equal(t, before, openFDs(t))
297+
298+ peer.keymap(kb, 7) // unknown format
291 require.NoError(t, c.Roundtrip())299 require.NoError(t, c.Roundtrip())
292 var errs int300 var errs int
293 h := neferclientmocks.NewMockHandler(t)301 h := neferclientmocks.NewMockHandler(t)
@@ -320,7 +328,6 @@ func TestSeatCapabilityLossReleasesDevices(t *testing.T) {
320 peer.mu.Lock()328 peer.mu.Lock()
321 require.True(t, peer.pointerReleased)329 require.True(t, peer.pointerReleased)
322 peer.mu.Unlock()330 peer.mu.Unlock()
323- _ = srv
324 }331 }
325 332
326 // TestSeatRepeatThroughEpoll: the repeat timerfd is watched by the connection's333 // TestSeatRepeatThroughEpoll: the repeat timerfd is watched by the connection's
@@ -350,9 +357,7 @@ func TestSeatRepeatThroughEpoll(t *testing.T) {
350 require.NoError(t, c.Roundtrip())357 require.NoError(t, c.Roundtrip())
351 require.NoError(t, c.Dispatch(h))358 require.NoError(t, c.Dispatch(h))
352 n := repeats359 n := repeats
353- for range 5 {360+ time.Sleep(50 * time.Millisecond) // five repeat intervals at 100/s
354- runtime.Gosched()
355- }
356 require.NoError(t, c.Dispatch(h))361 require.NoError(t, c.Dispatch(h))
357 require.Equal(t, n, repeats, "release stops the repeat")362 require.Equal(t, n, repeats, "release stops the repeat")
358 }363 }
@@ -366,6 +371,10 @@ func TestSeatRepeatThroughEpoll(t *testing.T) {
366 // cost so a regression above it shows.371 // cost so a regression above it shows.
367 const xkbAllocsPerPress = 20372 const xkbAllocsPerPress = 20
368 373
374+// xkbAllocsPerMask bounds a mask change: update_mask, serialize_mods and
375+// serialize_layout, boxed the same way.
376+const xkbAllocsPerMask = 12
377+
369 // TestAllocKey measures the steady-state key path over a real socketpair, on378 // TestAllocKey measures the steady-state key path over a real socketpair, on
370 // the real code path (no mocks): key frames in, reader decode, queue, Dispatch379 // the real code path (no mocks): key frames in, reader decode, queue, Dispatch
371 // with xkb state and translation, and the Handler call.380 // with xkb state and translation, and the Handler call.
@@ -425,6 +434,23 @@ func TestAllocKey(t *testing.T) {
425 require.LessOrEqual(t, text, float64(pairs*xkbAllocsPerPress))434 require.LessOrEqual(t, text, float64(pairs*xkbAllocsPerPress))
426 require.Equal(t, "a", h.lastText)435 require.Equal(t, "a", h.lastText)
427 436
437+ // Modifier masks: an identical mask is a no-op and allocates nothing; a
438+ // changing one updates the xkb state (purego-xkbcommon calls, bounded).
439+ mods := func(depressed uint32) []byte {
440+ b := appendU32(appendU32(nil, 1), depressed)
441+ return frame(kb, 4, appendU32(appendU32(appendU32(b, 0), 0), 0))
442+ }
443+ var same, changing []byte
444+ for range 100 {
445+ same = append(same, mods(0)...)
446+ }
447+ for i := range 100 {
448+ changing = append(changing, mods(uint32(i%2))...)
449+ }
450+ require.Zero(t, measure("modifiers, 100 identical masks", run(same, 100)), "identical masks cost nothing")
451+ changes := measure("modifiers, 100 alternating masks", run(changing, 100))
452+ require.LessOrEqual(t, changes, float64(100*xkbAllocsPerMask))
453+
428 buf := neferclient.NewSecretBuffer(4096)454 buf := neferclient.NewSecretBuffer(4096)
429 c.Seat().SetSecret(buf)455 c.Seat().SetSecret(buf)
430 secret := measure("secret, 50 presses + releases", step)456 secret := measure("secret, 50 presses + releases", step)
@@ -434,3 +460,47 @@ func TestAllocKey(t *testing.T) {
434 buf.Wipe()460 buf.Wipe()
435 require.Zero(t, h.errs)461 require.Zero(t, h.errs)
436 }462 }
463+
464+// TestAllocPointer measures the pointer path over a real socketpair: a batch
465+// of motion, button and axis frames in, reader decode, queue, Dispatch and the
466+// Handler call. Pointer events need no xkb, so the whole path allocates
467+// nothing.
468+func TestAllocPointer(t *testing.T) {
469+ skipUnderRace(t)
470+ c, srv, peer, s := seatSetup(t)
471+ pointer, _ := peer.ids()
472+ enter := appendU32(appendU32(nil, 5), s.SurfaceObjectID())
473+ srv.write(frame(pointer, 0, appendU32(appendU32(enter, fixed(1)), fixed(1))))
474+ require.NoError(t, c.Roundtrip())
475+ h := &countingHandler{}
476+ require.NoError(t, c.Dispatch(h))
477+ require.Equal(t, 1, h.pointers)
478+
479+ const rounds = 40
480+ var batch []byte
481+ for i := range rounds {
482+ batch = append(batch, frame(pointer, 2, append(appendU32(appendU32(nil, 0), fixed(float64(i))), appendU32(nil, fixed(2))...))...)
483+ batch = append(batch, frame(pointer, 3, appendU32(appendU32(appendU32(appendU32(nil, 6), 0), 0x110), uint32(i%2)))...)
484+ batch = append(batch, frame(pointer, 4, appendU32(appendU32(appendU32(nil, 0), 0), fixed(2)))...)
485+ batch = append(batch, frame(pointer, 9, appendU32(appendU32(nil, 0), 120))...)
486+ }
487+ step := func() {
488+ if _, err := srv.conn.Write(batch); err != nil {
489+ t.Fatal(err)
490+ }
491+ for c.QueueLen() < 4*rounds {
492+ runtime.Gosched()
493+ }
494+ if err := c.Dispatch(h); err != nil {
495+ t.Fatal(err)
496+ }
497+ }
498+ step()
499+ step()
500+ before := h.pointers
501+ allocs := testing.AllocsPerRun(20, step)
502+ t.Logf("allocs per Dispatch of %d pointer events: %v", 4*rounds, allocs)
503+ require.Zero(t, allocs)
504+ require.Greater(t, h.pointers, before)
505+ require.Zero(t, h.errs)
506+}

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 4

↑↓ moveenter openesc close