test(surface): restore strict FeedbackDone expectation and guard popup events
Revert TestHeadlessReconfigureWhilePending to its original expectation (the loose form stays in the headlessLayer helper) and add TestAllocPopupEvents for popup configure and popup_done.
- commit
- f655a676b561a031b569bb76fa2d1a6e4ddc0a4d
- author
- brice · · 2026-10-09 03:55 UTC
- parent
- 2671432
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +51 -1
M alloc_test.go +50 -0
| @@ -18,6 +18,7 @@ type countingHandler struct { | |||
| 18 | 18 | neferclient.NopHandler | |
| 19 | 19 | fdReady, errs, frames int | |
| 20 | 20 | keys, pointers int | |
| 21 | + | configures, popupDone int | |
| 21 | 22 | lastText string // set only by the allocation-free comparison below | |
| 22 | 23 | } | |
| 23 | 24 | ||
| @@ -25,6 +26,9 @@ func (h *countingHandler) FDReady(uint64) { h.fdReady++ } | |||
| 25 | 26 | func (h *countingHandler) Error(error) { h.errs++ } | |
| 26 | 27 | func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ } | |
| 27 | 28 | ||
| 29 | + | func (h *countingHandler) Configure(neferclient.SurfaceID, int32, int32) { h.configures++ } | |
| 30 | + | func (h *countingHandler) PopupDone(neferclient.SurfaceID) { h.popupDone++ } | |
| 31 | + | ||
| 28 | 32 | func (h *countingHandler) Pointer(*neferclient.PointerEvent) { h.pointers++ } | |
| 29 | 33 | ||
| 30 | 34 | // Key records the text through a comparison that does not allocate: the | |
| @@ -160,3 +164,49 @@ func TestAllocPresent(t *testing.T) { | |||
| 160 | 164 | require.Positive(t, h.frames) | |
| 161 | 165 | require.Zero(t, h.errs) | |
| 162 | 166 | } | |
| 167 | + | ||
| 168 | + | // TestAllocPopupEvents measures the popup events in steady state: the | |
| 169 | + | // xdg_popup configure (with its xdg_surface configure and the ack request it | |
| 170 | + | // triggers) and popup_done. | |
| 171 | + | func TestAllocPopupEvents(t *testing.T) { | |
| 172 | + | skipUnderRace(t) | |
| 173 | + | e := newPopupEnv(t, popupGlobals) | |
| 174 | + | p, err := e.c.NewPopup(neferclient.PopupConfig{Parent: e.layer, Place: place, Grab: true, Serial: 3}) | |
| 175 | + | require.NoError(t, err) | |
| 176 | + | require.NoError(t, e.c.Roundtrip()) | |
| 177 | + | xdg := e.xdgSurfaceOf(t, p.SurfaceObjectID()) | |
| 178 | + | _, gp := e.getPopup(t, xdg) | |
| 179 | + | popup := gp.u32(0) | |
| 180 | + | e.srv.mu.Lock() | |
| 181 | + | e.srv.hook = func(uint32, uint16, []byte) {} // parse and discard, as the real peer's requests would be | |
| 182 | + | e.srv.mu.Unlock() | |
| 183 | + | ||
| 184 | + | const rounds = 40 | |
| 185 | + | var batch []byte | |
| 186 | + | for i := range rounds { | |
| 187 | + | batch = append(batch, frame(popup, 0, appendU32(appendU32(appendU32(appendU32(nil, 0), 0), 60), 40))...) | |
| 188 | + | batch = append(batch, frame(xdg, 0, appendU32(nil, uint32(i+1)))...) | |
| 189 | + | batch = append(batch, frame(popup, 1, nil)...) // popup_done | |
| 190 | + | } | |
| 191 | + | h := &countingHandler{} | |
| 192 | + | step := func() { | |
| 193 | + | if _, err := e.srv.conn.Write(batch); err != nil { | |
| 194 | + | t.Fatal(err) | |
| 195 | + | } | |
| 196 | + | for e.c.QueueLen() < 3*rounds { | |
| 197 | + | runtime.Gosched() | |
| 198 | + | } | |
| 199 | + | if err := e.c.Dispatch(h); err != nil { | |
| 200 | + | t.Fatal(err) | |
| 201 | + | } | |
| 202 | + | } | |
| 203 | + | step() | |
| 204 | + | step() | |
| 205 | + | before := h.popupDone | |
| 206 | + | allocs := testing.AllocsPerRun(20, step) | |
| 207 | + | t.Logf("allocs per Dispatch of %d popup events: %v", 3*rounds, allocs) | |
| 208 | + | require.Zero(t, allocs) | |
| 209 | + | require.Greater(t, h.popupDone, before) | |
| 210 | + | require.Positive(t, h.configures) | |
| 211 | + | require.Zero(t, h.errs) | |
| 212 | + | } | |
| @@ -18,6 +18,7 @@ type countingHandler struct { | |||||
| 18 | neferclient.NopHandler | 18 | neferclient.NopHandler | ||
| 19 | fdReady, errs, frames int | 19 | fdReady, errs, frames int | ||
| 20 | keys, pointers int | 20 | keys, pointers int | ||
| 21 | + | configures, popupDone int | |||
| 21 | lastText string // set only by the allocation-free comparison below | 22 | lastText string // set only by the allocation-free comparison below | ||
| 22 | } | 23 | } | ||
| 23 | 24 | ||||
| @@ -25,6 +26,9 @@ func (h *countingHandler) FDReady(uint64) { h.fdReady++ } | |||||
| 25 | func (h *countingHandler) Error(error) { h.errs++ } | 26 | func (h *countingHandler) Error(error) { h.errs++ } | ||
| 26 | func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ } | 27 | func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ } | ||
| 27 | 28 | ||||
| 29 | + | func (h *countingHandler) Configure(neferclient.SurfaceID, int32, int32) { h.configures++ } | |||
| 30 | + | func (h *countingHandler) PopupDone(neferclient.SurfaceID) { h.popupDone++ } | |||
| 31 | + | ||||
| 28 | func (h *countingHandler) Pointer(*neferclient.PointerEvent) { h.pointers++ } | 32 | func (h *countingHandler) Pointer(*neferclient.PointerEvent) { h.pointers++ } | ||
| 29 | 33 | ||||
| 30 | // Key records the text through a comparison that does not allocate: the | 34 | // Key records the text through a comparison that does not allocate: the | ||
| @@ -160,3 +164,49 @@ func TestAllocPresent(t *testing.T) { | |||||
| 160 | require.Positive(t, h.frames) | 164 | require.Positive(t, h.frames) | ||
| 161 | require.Zero(t, h.errs) | 165 | require.Zero(t, h.errs) | ||
| 162 | } | 166 | } | ||
| 167 | + | ||||
| 168 | + | // TestAllocPopupEvents measures the popup events in steady state: the | |||
| 169 | + | // xdg_popup configure (with its xdg_surface configure and the ack request it | |||
| 170 | + | // triggers) and popup_done. | |||
| 171 | + | func TestAllocPopupEvents(t *testing.T) { | |||
| 172 | + | skipUnderRace(t) | |||
| 173 | + | e := newPopupEnv(t, popupGlobals) | |||
| 174 | + | p, err := e.c.NewPopup(neferclient.PopupConfig{Parent: e.layer, Place: place, Grab: true, Serial: 3}) | |||
| 175 | + | require.NoError(t, err) | |||
| 176 | + | require.NoError(t, e.c.Roundtrip()) | |||
| 177 | + | xdg := e.xdgSurfaceOf(t, p.SurfaceObjectID()) | |||
| 178 | + | _, gp := e.getPopup(t, xdg) | |||
| 179 | + | popup := gp.u32(0) | |||
| 180 | + | e.srv.mu.Lock() | |||
| 181 | + | e.srv.hook = func(uint32, uint16, []byte) {} // parse and discard, as the real peer's requests would be | |||
| 182 | + | e.srv.mu.Unlock() | |||
| 183 | + | ||||
| 184 | + | const rounds = 40 | |||
| 185 | + | var batch []byte | |||
| 186 | + | for i := range rounds { | |||
| 187 | + | batch = append(batch, frame(popup, 0, appendU32(appendU32(appendU32(appendU32(nil, 0), 0), 60), 40))...) | |||
| 188 | + | batch = append(batch, frame(xdg, 0, appendU32(nil, uint32(i+1)))...) | |||
| 189 | + | batch = append(batch, frame(popup, 1, nil)...) // popup_done | |||
| 190 | + | } | |||
| 191 | + | h := &countingHandler{} | |||
| 192 | + | step := func() { | |||
| 193 | + | if _, err := e.srv.conn.Write(batch); err != nil { | |||
| 194 | + | t.Fatal(err) | |||
| 195 | + | } | |||
| 196 | + | for e.c.QueueLen() < 3*rounds { | |||
| 197 | + | runtime.Gosched() | |||
| 198 | + | } | |||
| 199 | + | if err := e.c.Dispatch(h); err != nil { | |||
| 200 | + | t.Fatal(err) | |||
| 201 | + | } | |||
| 202 | + | } | |||
| 203 | + | step() | |||
| 204 | + | step() | |||
| 205 | + | before := h.popupDone | |||
| 206 | + | allocs := testing.AllocsPerRun(20, step) | |||
| 207 | + | t.Logf("allocs per Dispatch of %d popup events: %v", 3*rounds, allocs) | |||
| 208 | + | require.Zero(t, allocs) | |||
| 209 | + | require.Greater(t, h.popupDone, before) | |||
| 210 | + | require.Positive(t, h.configures) | |||
| 211 | + | require.Zero(t, h.errs) | |||
| 212 | + | } | |||
M headless_surface_test.go +1 -1
| @@ -228,7 +228,7 @@ func TestHeadlessReconfigureWhilePending(t *testing.T) { | |||
| 228 | 228 | var frames int | |
| 229 | 229 | hd := neferclientmocks.NewMockHandler(t) | |
| 230 | 230 | hd.EXPECT().Configure(s.ID(), mock.Anything, mock.Anything).Run(func(neferclient.SurfaceID, int32, int32) { configured = true }).Return().Maybe() | |
| 231 | - | hd.EXPECT().FeedbackDone(mock.Anything).Run(func(id neferclient.SurfaceID) { feedback = feedback || id == s.ID() }).Return().Maybe() | |
| 231 | + | hd.EXPECT().FeedbackDone(s.ID()).Run(func(neferclient.SurfaceID) { feedback = true }).Return().Maybe() | |
| 232 | 232 | hd.EXPECT().Scale(s.ID(), mock.Anything).Return().Maybe() | |
| 233 | 233 | hd.EXPECT().Frame(s.ID()).Run(func(neferclient.SurfaceID) { frames++ }).Return().Maybe() | |
| 234 | 234 | dispatchUntil(t, c, hd, func() bool { return configured && feedback }) | |
| @@ -228,7 +228,7 @@ func TestHeadlessReconfigureWhilePending(t *testing.T) { | |||||
| 228 | var frames int | 228 | var frames int | ||
| 229 | hd := neferclientmocks.NewMockHandler(t) | 229 | hd := neferclientmocks.NewMockHandler(t) | ||
| 230 | hd.EXPECT().Configure(s.ID(), mock.Anything, mock.Anything).Run(func(neferclient.SurfaceID, int32, int32) { configured = true }).Return().Maybe() | 230 | hd.EXPECT().Configure(s.ID(), mock.Anything, mock.Anything).Run(func(neferclient.SurfaceID, int32, int32) { configured = true }).Return().Maybe() | ||
| 231 | - | hd.EXPECT().FeedbackDone(mock.Anything).Run(func(id neferclient.SurfaceID) { feedback = feedback || id == s.ID() }).Return().Maybe() | 231 | + | hd.EXPECT().FeedbackDone(s.ID()).Run(func(neferclient.SurfaceID) { feedback = true }).Return().Maybe() |
| 232 | hd.EXPECT().Scale(s.ID(), mock.Anything).Return().Maybe() | 232 | hd.EXPECT().Scale(s.ID(), mock.Anything).Return().Maybe() | ||
| 233 | hd.EXPECT().Frame(s.ID()).Run(func(neferclient.SurfaceID) { frames++ }).Return().Maybe() | 233 | hd.EXPECT().Frame(s.ID()).Run(func(neferclient.SurfaceID) { frames++ }).Return().Maybe() | ||
| 234 | dispatchUntil(t, c, hd, func() bool { return configured && feedback }) | 234 | dispatchUntil(t, c, hd, func() bool { return configured && feedback }) | ||