detached 40240cd

test(wayland): remove wall-clock margins from pace timer tests

The unchanged-deadline test now arms from one clock reading
(armPaceFrom) instead of relying on 25 us between two reads. The
end-to-end 20 Hz barrier timing test is removed: under -race its upper
bound depended on scheduling, and the deterministic armed-deadline test
covers the contract. The merge warning test checks level and fields
instead of the message text.
commit
40240cd18aee456b8dceeffcb7a1671cbe3c2326
author
brice · · 2026-10-14 03:07 UTC
committer
brice · · 2026-10-14 04:34 UTC
parent
c7a0951
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +11 -33
M internal/adapters/wayland/frames.go +5 -2
@@ -53,8 +53,11 @@ const paceTolerance = 50 * time.Microsecond
5353 // requested deadline, since the tick's checks are strict and an early tick
5454 // would push due work back by their minimum re-arm. The tolerance is measured
5555 // against the armed deadline, so it never accumulates. Display goroutine only.
56-func (s *Server) armPace(d time.Duration) {
57- deadline := time.Now().Add(max(d, 0))
56+func (s *Server) armPace(d time.Duration) { s.armPaceFrom(time.Now(), d) }
57+
58+// armPaceFrom is armPace with the clock read at now.
59+func (s *Server) armPaceFrom(now time.Time, d time.Duration) {
60+ deadline := now.Add(max(d, 0))
5861 if s.paceArmed {
5962 if diff := deadline.Sub(s.paceDeadline); diff >= -paceTolerance && diff <= 0 {
6063 return
@@ -53,8 +53,11 @@ const paceTolerance = 50 * time.Microsecond
53 // requested deadline, since the tick's checks are strict and an early tick53 // requested deadline, since the tick's checks are strict and an early tick
54 // would push due work back by their minimum re-arm. The tolerance is measured54 // would push due work back by their minimum re-arm. The tolerance is measured
55 // against the armed deadline, so it never accumulates. Display goroutine only.55 // against the armed deadline, so it never accumulates. Display goroutine only.
56-func (s *Server) armPace(d time.Duration) {56+func (s *Server) armPace(d time.Duration) { s.armPaceFrom(time.Now(), d) }
57- deadline := time.Now().Add(max(d, 0))57+
58+// armPaceFrom is armPace with the clock read at now.
59+func (s *Server) armPaceFrom(now time.Time, d time.Duration) {
60+ deadline := now.Add(max(d, 0))
58 if s.paceArmed {61 if s.paceArmed {
59 if diff := deadline.Sub(s.paceDeadline); diff >= -paceTolerance && diff <= 0 {62 if diff := deadline.Sub(s.paceDeadline); diff >= -paceTolerance && diff <= 0 {
60 return63 return
M internal/adapters/wayland/pace_inline_test.go +5 -5
@@ -145,20 +145,20 @@ func TestArmPaceSkipsUnchangedDeadline(t *testing.T) {
145145 if s.paceArmed {
146146 t.Fatal("stop of a never armed timer armed it")
147147 }
148- s.armPace(time.Hour)
148+ now := time.Now()
149+ s.armPaceFrom(now, time.Hour)
149150 first := s.paceDeadline
150151 if !s.paceArmed || remaining() <= 59*time.Minute {
151152 t.Fatalf("not armed: %v", remaining())
152153 }
153- // A deadline a little before the armed one keeps the timer. A full
154- // tolerance of margin absorbs the clock advancing between the calls.
155- s.armPace(time.Until(first) - paceTolerance/2)
154+ // A deadline a little before the armed one keeps the timer.
155+ s.armPaceFrom(now, time.Hour-paceTolerance/2)
156156 if !s.paceDeadline.Equal(first) {
157157 t.Fatalf("an earlier deadline within tolerance re-armed the timer: %v -> %v", first, s.paceDeadline)
158158 }
159159 // A later deadline, however close, re-arms: the timer must never fire
160160 // before the requested deadline.
161- s.armPace(time.Until(first) + time.Microsecond)
161+ s.armPaceFrom(now, time.Hour+time.Nanosecond)
162162 if !s.paceDeadline.After(first) {
163163 t.Fatalf("a later deadline kept the earlier timer: %v -> %v", first, s.paceDeadline)
164164 }
@@ -145,20 +145,20 @@ func TestArmPaceSkipsUnchangedDeadline(t *testing.T) {
145 if s.paceArmed {145 if s.paceArmed {
146 t.Fatal("stop of a never armed timer armed it")146 t.Fatal("stop of a never armed timer armed it")
147 }147 }
148- s.armPace(time.Hour)148+ now := time.Now()
149+ s.armPaceFrom(now, time.Hour)
149 first := s.paceDeadline150 first := s.paceDeadline
150 if !s.paceArmed || remaining() <= 59*time.Minute {151 if !s.paceArmed || remaining() <= 59*time.Minute {
151 t.Fatalf("not armed: %v", remaining())152 t.Fatalf("not armed: %v", remaining())
152 }153 }
153- // A deadline a little before the armed one keeps the timer. A full154+ // A deadline a little before the armed one keeps the timer.
154- // tolerance of margin absorbs the clock advancing between the calls.155+ s.armPaceFrom(now, time.Hour-paceTolerance/2)
155- s.armPace(time.Until(first) - paceTolerance/2)
156 if !s.paceDeadline.Equal(first) {156 if !s.paceDeadline.Equal(first) {
157 t.Fatalf("an earlier deadline within tolerance re-armed the timer: %v -> %v", first, s.paceDeadline)157 t.Fatalf("an earlier deadline within tolerance re-armed the timer: %v -> %v", first, s.paceDeadline)
158 }158 }
159 // A later deadline, however close, re-arms: the timer must never fire159 // A later deadline, however close, re-arms: the timer must never fire
160 // before the requested deadline.160 // before the requested deadline.
161- s.armPace(time.Until(first) + time.Microsecond)161+ s.armPaceFrom(now, time.Hour+time.Nanosecond)
162 if !s.paceDeadline.After(first) {162 if !s.paceDeadline.After(first) {
163 t.Fatalf("a later deadline kept the earlier timer: %v -> %v", first, s.paceDeadline)163 t.Fatalf("a later deadline kept the earlier timer: %v -> %v", first, s.paceDeadline)
164 }164 }
M internal/adapters/wayland/presented_test.go +1 -26
@@ -301,7 +301,7 @@ func TestPresentationMergeWarnsInLog(t *testing.T) {
301301 <-finished
302302 s.display.Do(func() {})
303303 got := out.String()
304- if n := strings.Count(got, "outputs report faster than the display loop drains; flips merged"); n != 1 {
304+ if n := strings.Count(got, `"level":"warn"`); n != 1 {
305305 t.Fatalf("%d warnings: %s", n, got)
306306 }
307307 if !strings.Contains(got, `"component":"wayland"`) || !strings.Contains(got, `"merged":`) {
@@ -379,31 +379,6 @@ func TestPaceTimerfdArmedForBarrierDeadline(t *testing.T) {
379379 }
380380 }
381381
382-// End to end on a 20 Hz output: the queued commit is applied at barrierAt+50 ms
383-// (the timerfd), not at the next 100 ms held-buffer tick; the bound is generous
384-// (half a period) against scheduler noise.
385-func TestPaceTimerfdClearsBarrierOnDeadline(t *testing.T) {
386- out := ports.Layout{{Info: ports.OutputInfo{Name: "HEADLESS-1", Width: 1920, Height: 1080, RefreshMilli: 20000}, Width: 1920, Height: 1080, Scale: 1}}
387- s, events, commands, contents, dir := contentServerWith(t, Options{Outputs: out}, testLog())
388- c := protocolClient(t, s, dir)
389- w, surf, _ := surfaceMapper(t, c, events)()
390- placeWindow(t, s, commands, w, "HEADLESS-1")
391- nextContent(t, contents, w.ID) // the mapping commit
392- fifoQueue(t, c, surf)
393- var barrierAt time.Time
394- var period time.Duration
395- s.display.Do(func() {
396- root := s.windows[w.ID].xdg.surface
397- barrierAt, period = root.barrierAt, s.outputPeriod(s.fifoOutput(root))
398- })
399- nextContent(t, contents, w.ID) // the commit that set the barrier
400- late := nextContent(t, contents, w.ID).Sub(barrierAt.Add(period))
401- // Never early; late by far less than the period (not by a polling tick).
402- if late < -time.Millisecond || late > period/2 {
403- t.Fatalf("queued commit applied %v after barrierAt+%v", late, period)
404- }
405-}
406-
407382 func testLog() zerowrap.Logger {
408383 return logging.For(context.Background(), "wayland")
409384 }
@@ -301,7 +301,7 @@ func TestPresentationMergeWarnsInLog(t *testing.T) {
301 <-finished301 <-finished
302 s.display.Do(func() {})302 s.display.Do(func() {})
303 got := out.String()303 got := out.String()
304- if n := strings.Count(got, "outputs report faster than the display loop drains; flips merged"); n != 1 {304+ if n := strings.Count(got, `"level":"warn"`); n != 1 {
305 t.Fatalf("%d warnings: %s", n, got)305 t.Fatalf("%d warnings: %s", n, got)
306 }306 }
307 if !strings.Contains(got, `"component":"wayland"`) || !strings.Contains(got, `"merged":`) {307 if !strings.Contains(got, `"component":"wayland"`) || !strings.Contains(got, `"merged":`) {
@@ -379,31 +379,6 @@ func TestPaceTimerfdArmedForBarrierDeadline(t *testing.T) {
379 }379 }
380 }380 }
381 381
382-// End to end on a 20 Hz output: the queued commit is applied at barrierAt+50 ms
383-// (the timerfd), not at the next 100 ms held-buffer tick; the bound is generous
384-// (half a period) against scheduler noise.
385-func TestPaceTimerfdClearsBarrierOnDeadline(t *testing.T) {
386- out := ports.Layout{{Info: ports.OutputInfo{Name: "HEADLESS-1", Width: 1920, Height: 1080, RefreshMilli: 20000}, Width: 1920, Height: 1080, Scale: 1}}
387- s, events, commands, contents, dir := contentServerWith(t, Options{Outputs: out}, testLog())
388- c := protocolClient(t, s, dir)
389- w, surf, _ := surfaceMapper(t, c, events)()
390- placeWindow(t, s, commands, w, "HEADLESS-1")
391- nextContent(t, contents, w.ID) // the mapping commit
392- fifoQueue(t, c, surf)
393- var barrierAt time.Time
394- var period time.Duration
395- s.display.Do(func() {
396- root := s.windows[w.ID].xdg.surface
397- barrierAt, period = root.barrierAt, s.outputPeriod(s.fifoOutput(root))
398- })
399- nextContent(t, contents, w.ID) // the commit that set the barrier
400- late := nextContent(t, contents, w.ID).Sub(barrierAt.Add(period))
401- // Never early; late by far less than the period (not by a polling tick).
402- if late < -time.Millisecond || late > period/2 {
403- t.Fatalf("queued commit applied %v after barrierAt+%v", late, period)
404- }
405-}
406-
407 func testLog() zerowrap.Logger {382 func testLog() zerowrap.Logger {
408 return logging.For(context.Background(), "wayland")383 return logging.For(context.Background(), "wayland")
409 }384 }

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 25

Tags 10

↑↓ moveenter openesc close