v0.8.0 446d1e3

alloc_hotpath_test.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"testing"
6	"time"
7
8	"github.com/bnema/neferwl/internal/ports"
9	"github.com/stretchr/testify/mock"
10)
11
12// The steady-state hot paths that run for every frame or commit allocate
13// nothing (or one pooled object per damage-history generation).
14
15func TestReleaseHeldAllocations(t *testing.T) {
16	out := &output{place: ports.OutputPlacement{Info: ports.OutputInfo{Name: "DP-2", RefreshMilli: 60000}}}
17	s := &Server{outputs: []*output{out}, reports: map[string]ports.OutputPresented{}}
18	reports := []ports.OutputPresented{{Output: "DP-2", Seen: map[ports.WindowID]uint64{1: 1}}}
19	now := time.Unix(100, 0)
20	s.releaseHeld(now, reports)
21	if allocs := testing.AllocsPerRun(100, func() { s.releaseHeld(now, reports) }); allocs != 0 {
22		t.Errorf("releaseHeld: %.1f allocs, want 0", allocs)
23	}
24}
25
26func TestCommitDamageAllocations(t *testing.T) {
27	srv := &Server{fifoSurfaces: map[*surface]struct{}{}, frameReady: make(chan struct{}, 1)}
28	surf := &surface{server: srv}
29	commit := func() {
30		surf.Damage(nil, 0, 0, 4, 4)
31		surf.DamageBuffer(nil, 1, 1, 2, 2)
32		surf.queueUpdate()
33		surf.queue[0].applyGraph()
34	}
35	commit()
36	if allocs := testing.AllocsPerRun(100, commit); allocs != 0 {
37		t.Errorf("damage requests + queueUpdate + apply: %.1f allocs, want 0", allocs)
38	}
39	// The queued update keeps its rects while the next commit collects its own.
40	surf.Damage(nil, 0, 0, 1, 1)
41	surf.queueUpdate()
42	first := surf.queue[0]
43	surf.Damage(nil, 5, 5, 2, 2)
44	if len(first.damage) != 1 || first.damage[0].W != 1 || len(surf.next.damage) != 1 || surf.next.damage[0].W != 2 {
45		t.Fatalf("damage shared between commits: queued %v pending %v", first.damage, surf.next.damage)
46	}
47	surf.queueUpdate()
48	if second := surf.queue[1]; len(second.damage) != 1 || second.damage[0].W != 2 || first.damage[0].W != 1 {
49		t.Fatalf("damage mixed up: %v %v", first.damage, second.damage)
50	}
51}
52
53// An explicit-sync commit moves its points into the update: the commit
54// itself allocates no commitSync, and a pending one is not overwritten.
55func TestCommitSyncStorageIsPerUpdate(t *testing.T) {
56	dev := newMocksyncobjDevice(t)
57	dev.EXPECT().signalled(mock.Anything, mock.Anything).Return(true, nil)
58	srv := &Server{fifoSurfaces: map[*surface]struct{}{}, frameReady: make(chan struct{}, 1)}
59	sw, err := newSyncWaiter(func() {})
60	if err != nil {
61		t.Fatal(err)
62	}
63	defer sw.pipeR.Close()
64	defer sw.close()
65	srv.syncWait = sw
66	tl := &timeline{dev: dev, handle: 7, alive: true}
67	surf := &surface{server: srv, sync: &syncState{}}
68	take := func(point uint64) {
69		surf.sync.acquire, surf.sync.release = syncPoint{tl: tl, point: point}, syncPoint{tl: tl, point: point + 1}
70		surf.takeSyncPoints()
71		surf.queueUpdate()
72	}
73	take(10)
74	take(20)
75	a, b := surf.queue[0], surf.queue[1]
76	if a.sync == nil || b.sync == nil || a.sync == b.sync || a.sync.acquire.point != 10 || b.sync.acquire.point != 20 || surf.next.sync != nil {
77		t.Fatalf("sync points mixed up: %+v %+v pending %+v", a.sync, b.sync, surf.next.sync)
78	}
79	// The commit's points live in the surface until queueUpdate: no heap
80	// commitSync per commit (the mocked device's own allocations would drown
81	// an AllocsPerRun count here).
82	surf.sync.acquire, surf.sync.release = syncPoint{tl: tl, point: 30}, syncPoint{tl: tl, point: 31}
83	surf.takeSyncPoints()
84	if surf.next.sync != &surf.syncScratch {
85		t.Fatal("commit points not held in the surface scratch")
86	}
87}
88
89func TestEmitContentAllocations(t *testing.T) {
90	s := &Server{
91		channels:     Channels{Contents: make(chan ports.SurfaceContent)},
92		contents:     map[ports.WindowID]ports.SurfaceContent{},
93		contentSeq:   map[ports.WindowID]uint64{},
94		damage:       map[ports.WindowID][]ports.SeqDamage{},
95		contentReady: make(chan struct{}, 1),
96	}
97	c := ports.SurfaceContent{ID: 1, Solid: &ports.SolidColor{}}
98	var held []ports.SurfaceContent // readers retaining published histories
99	var snapshots [][]ports.SeqDamage
100	for range 3 * damageHistoryCap {
101		s.emitContent(c, damage{full: true})
102		got := s.contents[1]
103		held = append(held, got)
104		snapshots = append(snapshots, append([]ports.SeqDamage(nil), got.DamageHistory...))
105	}
106	for i, h := range held {
107		// A published history never changes, nor can its owner append to it.
108		if len(h.DamageHistory) != len(snapshots[i]) || cap(h.DamageHistory) != len(h.DamageHistory) {
109			t.Fatalf("history %d: len %d cap %d", i, len(h.DamageHistory), cap(h.DamageHistory))
110		}
111		for j, d := range h.DamageHistory {
112			if d.Seq != snapshots[i][j].Seq || d.Full != snapshots[i][j].Full {
113				t.Fatalf("history %d changed at %d: %+v want %+v", i, j, d, snapshots[i][j])
114			}
115		}
116		if n := len(h.DamageHistory); h.DamageHistory[n-1].Seq != h.Seq || n > damageHistory {
117			t.Fatalf("history %d: %+v", i, h.DamageHistory)
118		}
119	}
120	allocs := testing.AllocsPerRun(100, func() { s.emitContent(c, damage{full: true}) })
121	if allocs != 0 {
122		t.Errorf("emitContent: %.2f allocs, want 0 (amortized)", allocs)
123	}
124	t.Logf("emitContent: %.2f allocs", allocs)
125}
126
127// A forwarder blocked on a stale frame is woken by the next publication
128// and sends the newer frame instead.
129func TestForwardContentsSendsNewerFrame(t *testing.T) {
130	contents := make(chan ports.SurfaceContent)
131	s := &Server{
132		channels:     Channels{Contents: contents},
133		contents:     map[ports.WindowID]ports.SurfaceContent{},
134		contentSeq:   map[ports.WindowID]uint64{},
135		damage:       map[ports.WindowID][]ports.SeqDamage{},
136		contentReady: make(chan struct{}, 1),
137	}
138	ctx, cancel := context.WithCancel(context.Background())
139	done := make(chan struct{})
140	go func() { defer close(done); s.forwardContents(ctx) }()
141	defer func() { cancel(); <-done }()
142	s.emitContent(ports.SurfaceContent{ID: 1, Seq: 1, Solid: &ports.SolidColor{}}, damage{full: true})
143	time.Sleep(10 * time.Millisecond) // let the forwarder block on seq 1
144	s.emitContent(ports.SurfaceContent{ID: 1, Seq: 2, Solid: &ports.SolidColor{}}, damage{full: true})
145	select {
146	case c := <-contents:
147		if c.Seq != 2 {
148			t.Fatalf("stale frame %d forwarded, want 2", c.Seq)
149		}
150	case <-time.After(2 * time.Second):
151		t.Fatal("no frame forwarded")
152	}
153}

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