pace_inline_test.go RawHistoryFolder

1package wayland
2
3import (
4	"testing"
5	"time"
6
7	"github.com/bnema/go-wayland-bindings/server/wayland"
8	"github.com/bnema/neferwl/internal/ports"
9	"golang.org/x/sys/unix"
10)
11
12// paceFixture is a server whose surface waits on an output whose next
13// frame is an hour away: a commit queues its callback and nothing fires.
14func paceFixture(t *testing.T, timer bool) (*Server, *surface) {
15	t.Helper()
16	out := &output{place: ports.OutputPlacement{Info: ports.OutputInfo{Name: "DP-2", RefreshMilli: 60000}}}
17	s := &Server{
18		outputs:      []*output{out},
19		awaiting:     map[string][]*wayland.Callback{},
20		frameDue:     map[string]time.Time{"DP-2": time.Now().Add(time.Hour)},
21		fifoSurfaces: map[*surface]struct{}{},
22		lastFlip:     map[string]time.Time{},
23		flips:        map[string]ports.FlipInfo{},
24		reports:      map[string]ports.OutputPresented{},
25	}
26	if timer {
27		fd, err := unix.TimerfdCreate(unix.CLOCK_MONOTONIC, unix.TFD_NONBLOCK|unix.TFD_CLOEXEC)
28		if err != nil {
29			t.Fatal(err)
30		}
31		t.Cleanup(func() { unix.Close(fd) })
32		s.paceFD, s.paceReady = fd, true
33	}
34	w := &window{hasLast: true, last: ports.ConfigureWindow{Output: "DP-2", Visible: true}}
35	surf := &surface{server: s, xdg: &xdgSurface{window: w}}
36	surf.next.callbacks = []*wayland.Callback{new(wayland.Callback)}
37	return s, surf
38}
39
40// A commit on the display goroutine ticks the pacer inline: it arms the
41// pace timer itself and posts no tick, which would only run later on the
42// display goroutine it is already on.
43func TestCommitTicksPacerInline(t *testing.T) {
44	s, surf := paceFixture(t, true)
45	surf.queueAndTick()
46	if len(s.awaiting["DP-2"]) != 1 {
47		t.Fatalf("callback not queued: %+v", s.awaiting)
48	}
49	if s.paceSignal.Load() {
50		t.Fatal("commit posted a tick")
51	}
52	waitArmed(t, s, "inline tick did not arm the pacer timer for the next deadline")
53	if s.paceDepth != 0 || s.paceDirty {
54		t.Fatalf("pace state leaked: depth %d dirty %v", s.paceDepth, s.paceDirty)
55	}
56}
57
58// Without a registered pace timer the wake falls back to a posted tick.
59func TestCommitWakesPacerWithoutTimer(t *testing.T) {
60	s, surf := paceFixture(t, false)
61	surf.queueAndTick()
62	if !s.paceSignal.Load() {
63		t.Fatal("tick not signalled")
64	}
65}
66
67// Wakes inside a tick coalesce into one more pass, not one posted tick each.
68func TestWakePacerDefersInsideTick(t *testing.T) {
69	s, _ := paceFixture(t, true)
70	s.paceDepth = 1
71	s.wakePacer()
72	s.wakePacer()
73	if s.paceSignal.Load() || !s.paceDirty {
74		t.Fatalf("wake inside a tick: signalled %v dirty %v", s.paceSignal.Load(), s.paceDirty)
75	}
76	s.paceDepth = 0
77	s.wakePacer()
78	if !s.paceSignal.Load() {
79		t.Fatal("wake outside a tick lost")
80	}
81}
82
83// A commit that waits (a fifo barrier) is timed by the inline tick.
84func TestCommitWaitingOnBarrierArmsTimer(t *testing.T) {
85	s, surf := paceFixture(t, true)
86	surf.next.callbacks = nil
87	surf.barrier, surf.barrierAt = true, time.Now()
88	surf.next.wait = true
89	surf.queueAndTick()
90	if len(surf.queue) != 1 || s.paceSignal.Load() {
91		t.Fatalf("queued %d, tick signalled %v", len(surf.queue), s.paceSignal.Load())
92	}
93	waitArmed(t, s, "timer not armed for the barrier deadline")
94}
95
96// waitArmed asserts the timerfd was armed: it is pending, or it already
97// fired and holds its expiry.
98func waitArmed(t *testing.T, s *Server, msg string) {
99	t.Helper()
100	var cur unix.ItimerSpec
101	if err := unix.TimerfdGettime(s.paceFD, &cur); err != nil {
102		t.Fatal(err)
103	}
104	if cur.Value != (unix.Timespec{}) {
105		return
106	}
107	var buf [8]byte
108	if n, _ := unix.Read(s.paceFD, buf[:]); n != 8 {
109		t.Fatal(msg)
110	}
111}
112
113// A synchronized subsurface commit waits for its parent's commit: it does
114// not tick the pacer, so a burst of child commits costs no ticks.
115func TestSyncedCommitDoesNotTick(t *testing.T) {
116	s, surf := paceFixture(t, true)
117	surf.sub.parent = &surface{server: s}
118	surf.sub.synced = true
119	surf.queueAndTick()
120	if len(surf.queue) != 1 || !surf.queue[0].synced {
121		t.Fatalf("synced commit not queued: %d", len(surf.queue))
122	}
123	var cur unix.ItimerSpec
124	if err := unix.TimerfdGettime(s.paceFD, &cur); err != nil {
125		t.Fatal(err)
126	}
127	if s.paceSignal.Load() || cur.Value != (unix.Timespec{}) {
128		t.Fatal("synced commit ticked the pacer")
129	}
130}
131
132// An arm for the deadline already armed (within paceTolerance) keeps the
133// timer; another deadline re-arms it; an expiry consumed by onPaceTimer, or a
134// stop, leaves it disarmed, and a stop of a disarmed timer does nothing.
135func TestArmPaceSkipsUnchangedDeadline(t *testing.T) {
136	s, _ := paceFixture(t, true)
137	remaining := func() time.Duration {
138		var cur unix.ItimerSpec
139		if err := unix.TimerfdGettime(s.paceFD, &cur); err != nil {
140			t.Fatal(err)
141		}
142		return time.Duration(cur.Value.Nano())
143	}
144	s.stopPace()
145	if s.paceArmed {
146		t.Fatal("stop of a never armed timer armed it")
147	}
148	s.armPace(time.Hour)
149	first := s.paceDeadline
150	if !s.paceArmed || remaining() <= 59*time.Minute {
151		t.Fatalf("not armed: %v", remaining())
152	}
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)
156	if !s.paceDeadline.Equal(first) {
157		t.Fatalf("an earlier deadline within tolerance re-armed the timer: %v -> %v", first, s.paceDeadline)
158	}
159	// A later deadline, however close, re-arms: the timer must never fire
160	// before the requested deadline.
161	s.armPace(time.Until(first) + time.Microsecond)
162	if !s.paceDeadline.After(first) {
163		t.Fatalf("a later deadline kept the earlier timer: %v -> %v", first, s.paceDeadline)
164	}
165	first = s.paceDeadline
166	s.armPace(time.Hour - time.Minute)
167	if s.paceDeadline.Equal(first) || remaining() > 59*time.Minute {
168		t.Fatalf("a new deadline kept the old timer: remaining %v", remaining())
169	}
170	s.stopPace()
171	if s.paceArmed || remaining() != 0 {
172		t.Fatalf("stop left the timer armed: %v", remaining())
173	}
174	// One-shot expiry: the fired timer is disarmed, so a stop is a no-op and
175	// the next arm sets the timer again even for the same deadline.
176	s.armPace(time.Nanosecond)
177	fds := []unix.PollFd{{Fd: int32(s.paceFD), Events: unix.POLLIN}}
178	if n, err := unix.Poll(fds, 5000); n != 1 {
179		t.Fatalf("the timer did not fire: %d, %v", n, err)
180	}
181	s.onPaceTimer()
182	if s.paceArmed {
183		t.Fatal("expiry did not disarm")
184	}
185	s.stopPace() // no syscall to observe: it must not re-arm anything
186	if remaining() != 0 {
187		t.Fatalf("stop after the expiry: %v", remaining())
188	}
189	s.armPace(time.Hour)
190	if !s.paceArmed || remaining() <= 59*time.Minute {
191		t.Fatalf("not re-armed after the expiry: %v", remaining())
192	}
193}

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 23

Tags 10

↑↓ moveenter openesc close