main f2d166b

security_resume_test.go RawHistoryFolder

1package drm
2
3import (
4	"context"
5	"os"
6	"sync/atomic"
7	"testing"
8	"time"
9
10	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
11	"github.com/bnema/neferwl/internal/ports"
12	"github.com/stretchr/testify/mock"
13	"golang.org/x/sys/unix"
14)
15
16func TestSecurityUnlockedResumeUsesOwnerRetry(t *testing.T) {
17	for _, stage := range []string{"engage-during-request", "master-loss"} {
18		t.Run(stage, func(t *testing.T) {
19			var state atomic.Uint64
20			var rule func(*atomicReq, uint32) error
21			if stage == "master-loss" {
22				// The lock engages as the kernel refuses the first enable for
23				// lack of master: the retry must be a protected modeset.
24				refused := false
25				rule = func(*atomicReq, uint32) error {
26					if refused {
27						return nil
28					}
29					refused = true
30					state.Store(3)
31					return unix.EACCES
32				}
33			}
34			o, k, commits, mu := testOutputRules(t, rule)
35			o.cursor, o.tearing = nil, false
36			securityGate(t, o, &state)
37			events := make(chan ports.SecurityBackendEvent, 4)
38			o.SecurityEvents = events
39			active := make(chan bool, 2)
40			active <- false // retain existing images; first modeset is resume
41			wake := make(chan ports.SecurityState, 1)
42			o.SecurityChanges = wake
43			r := portsmocks.NewMockRenderer(t)
44			r.EXPECT().UseTarget(0).Return().Once()
45			r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
46				if s.Seq != 0 || s.Background != "#000000" || len(s.Windows) != 0 {
47					t.Fatal("resume rendered desktop")
48				}
49				return nil, nil
50			}).Once()
51			r.EXPECT().Close().Return().Once()
52			var blobs atomic.Int32
53			k.EXPECT().createBlob(mock.Anything).RunAndReturn(func([]byte) (uint32, error) {
54				if blobs.Add(1) == 1 && stage == "engage-during-request" {
55					state.Store(3)
56				}
57				return 99, nil
58			})
59			k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
60			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
61			defer cancel()
62			done := make(chan error, 1)
63			go func() {
64				done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, nil, nil, nil, nil, nil, nil, nil)
65			}()
66			active <- true
67			if stage == "master-loss" {
68				waitFor(t, func() bool { mu.Lock(); defer mu.Unlock(); return len(*commits) >= 1 })
69				wake <- ports.SecurityState{}
70				// The seat reported active before master came back: the owner
71				// retries the protected modeset itself, with no second seat
72				// event. The proof below must follow that successful retry.
73			}
74			for {
75				select {
76				case ev := <-events:
77					if p, ok := ev.(ports.SecurityOutputProof); ok {
78						if p.Proof.Generation != 1 || p.Proof.Instance != 77 {
79							t.Fatalf("proof %+v", p)
80						}
81						if stage == "master-loss" {
82							mu.Lock()
83							n := len(*commits)
84							mu.Unlock()
85							if n < 2 {
86								t.Fatal("master loss forged proof: no successful retry")
87							}
88						}
89						cancel()
90						if err := <-done; err != nil {
91							t.Fatal(err)
92						}
93						return
94					}
95				case err := <-done:
96					t.Fatalf("resume ended before fresh proof: %v", err)
97				case <-ctx.Done():
98					t.Fatal("missing fresh resume proof")
99				}
100			}
101		})
102	}
103}

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