main df9bf83

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

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