main f2d166b

nomem_test.go RawHistoryFolder

1package drm
2
3import (
4	"sync/atomic"
5	"testing"
6	"testing/synctest"
7	"time"
8
9	"github.com/bnema/neferwl/internal/ports"
10	"github.com/stretchr/testify/mock"
11	"golang.org/x/sys/unix"
12)
13
14func TestNoMemRetriesWithoutModeset(t *testing.T) {
15	synctest.Test(t, func(t *testing.T) {
16		h := startStuck(t, nil, unix.ENOMEM, unix.ENOMEM, nil)
17		h.scenes <- ports.Scene{Seq: 1}
18		synctest.Wait()
19		for _, delay := range []time.Duration{nomemRetryMin, 2 * nomemRetryMin} {
20			select {
21			case p := <-h.presented:
22				t.Fatalf("refused frame reported: %+v", p)
23			default:
24			}
25			time.Sleep(delay)
26			synctest.Wait()
27		}
28		c := h.snapshot()
29		if len(c) != 5 {
30			t.Fatalf("commits %d, want 5", len(c))
31		}
32		for _, r := range c[2:] {
33			if r.flags&atomicAllowModes != 0 || r.user&3 != userFrame {
34				t.Fatalf("not a frame retry: %+v", r)
35			}
36		}
37		select {
38		case p := <-h.presented:
39			t.Fatalf("reported before successful flip: %+v", p)
40		default:
41		}
42		h.flipped <- eventOf(c[4])
43		synctest.Wait()
44		select {
45		case <-h.presented:
46		default:
47			t.Fatal("successful flip not reported")
48		}
49		select {
50		case p := <-h.presented:
51			t.Fatalf("extra report: %+v", p)
52		default:
53		}
54		h.stop()
55	})
56}
57
58func TestLastingNoMemKeepsOutput(t *testing.T) {
59	synctest.Test(t, func(t *testing.T) {
60		errs := make([]error, 21)
61		for i := range errs {
62			errs[i] = unix.ENOMEM
63		}
64		h := startStuck(t, nil, errs...)
65		h.scenes <- ports.Scene{Seq: 1}
66		synctest.Wait()
67		for i := range 19 {
68			time.Sleep(min(nomemRetryMax, nomemRetryMin*time.Duration(1<<min(i, 7))))
69			synctest.Wait()
70		}
71		select {
72		case err := <-h.done:
73			t.Fatalf("Run stopped: %v", err)
74		default:
75		}
76		c := h.stop()
77		if len(c) != 22 {
78			t.Fatalf("commits %d want 22", len(c))
79		}
80		for _, r := range c[2:] {
81			if r.flags&atomicAllowModes != 0 {
82				t.Fatal("ENOMEM triggered modeset")
83			}
84		}
85	})
86}
87
88func TestNoMemModesetRetries(t *testing.T) {
89	synctest.Test(t, func(t *testing.T) {
90		h := startStuck(t, nil, nil, unix.ENOMEM, nil)
91		h.scenes <- ports.Scene{Seq: 1, Off: true}
92		synctest.Wait()
93		if h.count() != 3 {
94			t.Fatal("power off did not commit")
95		}
96		h.scenes <- ports.Scene{Seq: 2}
97		synctest.Wait()
98		time.Sleep(nomemRetryMin)
99		synctest.Wait()
100		select {
101		case err := <-h.done:
102			t.Fatalf("Run stopped: %v", err)
103		default:
104		}
105		c := h.stop()
106		if len(c) != 6 {
107			t.Fatalf("commits %d want 6", len(c))
108		}
109		for _, r := range c[3:5] {
110			if r.flags != atomicAllowModes {
111				t.Fatalf("not a retried modeset: %#x", r.flags)
112			}
113		}
114	})
115}
116
117func TestScanoutNoMemComposes(t *testing.T) {
118	synctest.Test(t, func(t *testing.T) {
119		o, _, _ := testOutput(t, unix.ENOMEM)
120		s, surfaces := fullscreenScene()
121		c := surfaces[1]
122		o.clientFBs[9] = &clientFB{fbID: 80}
123		o.primary.formats = []ports.DMABufFormat{{Format: fourccXRGB}}
124		direct, err := o.commitScanoutRect(80, c, pendingFrame{}, fullPlaneRect(200, 100), colorBypass)
125		if direct || err != nil || o.reason != "nomem" || o.clientFBs[9].failed != "" {
126			t.Fatalf("direct=%v err=%v reason=%q failed=%q", direct, err, o.reason, o.clientFBs[9].failed)
127		}
128		if d := o.composeFrame(s, surfaces); d.overlay.fb != 0 || d.composed.Seq != s.Seq {
129			t.Fatal("immediate fallback did not compose")
130		}
131		for _, delay := range []time.Duration{0, nomemScanoutPause - time.Nanosecond} {
132			time.Sleep(delay)
133			d := o.decideFrame(s, surfaces, false)
134			if d.fb != 0 || d.overlay.fb != 0 || d.composed.Seq != s.Seq || o.reason != "nomem" {
135				t.Fatalf("not composed: %+v reason=%q", d, o.reason)
136			}
137		}
138		time.Sleep(time.Nanosecond)
139		if d := o.decideFrame(s, surfaces, false); d.fb != 80 {
140			t.Fatalf("scanout not resumed: %+v reason=%q", d, o.reason)
141		}
142	})
143}
144
145func TestProtectedNoMemKeepsProtectionRetry(t *testing.T) {
146	for _, stage := range []string{"modeset", "commit"} {
147		t.Run(stage, func(t *testing.T) {
148			o, k, _ := testOutput(t, unix.ENOMEM)
149			var state atomic.Uint64
150			state.Store(3)
151			securityGate(t, o, &state)
152			o.observeSecurity()
153			events := make(chan ports.SecurityBackendEvent, 4)
154			o.SecurityEvents = events
155			var err error
156			if stage == "modeset" {
157				k.EXPECT().createBlob(mock.Anything).Return(99, nil)
158				k.EXPECT().destroyBlob(mock.Anything).Return(nil)
159				err = o.modesetImage(70, true)
160			} else {
161				o.securityPrepared = true // an admitted lock-screen frame
162				err = o.commitFrame(70, nil, false, false, pendingFrame{security: o.securityState})
163			}
164			enabled := true
165			if !o.commitFailed(err, &enabled) || !enabled || o.securityPrepared || !o.protectBackoffActive() || !o.frame.nomemSince.IsZero() {
166				t.Fatalf("protection retry bypassed: err=%v prepared=%v backoff=%v nomem=%v", err, o.securityPrepared, o.protectBackoff, o.frame.nomemSince)
167			}
168			select {
169			case ev := <-events:
170				t.Fatalf("proof on refusal: %T", ev)
171			default:
172			}
173		})
174	}
175}
176
177func TestNoMemSuccessClearsRun(t *testing.T) {
178	o, _, _ := testOutput(t, unix.ENOMEM, nil)
179	enabled := true
180	if !o.commitFailed(o.commitFrame(70, nil, false, false, pendingFrame{}), &enabled) {
181		t.Fatal("ENOMEM unhandled")
182	}
183	o.frame.timeout(o.frame.stuckAt, true)
184	if err := o.commitFrame(70, nil, false, false, pendingFrame{}); err != nil {
185		t.Fatal(err)
186	}
187	if !o.frame.nomemSince.IsZero() || o.frame.nomemAttempts != 0 {
188		t.Fatal("successful commit did not clear run")
189	}
190}
191
192func TestNoMemRetryRechecksSecurity(t *testing.T) {
193	o, _, commits := testOutput(t, unix.ENOMEM)
194	enabled := true
195	if !o.commitFailed(o.commitFrame(70, nil, false, false, pendingFrame{}), &enabled) {
196		t.Fatal("ENOMEM unhandled")
197	}
198	o.frame.timeout(o.frame.stuckAt, true)
199	var state atomic.Uint64
200	state.Store(3)
201	securityGate(t, o, &state)
202	if err := o.commitFrame(70, nil, false, false, pendingFrame{}); err != errSecurityScene {
203		t.Fatalf("stale retry: %v", err)
204	}
205	if len(*commits) != 1 {
206		t.Fatal("stale retry reached KMS")
207	}
208}

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