main f2d166b

protection_review_test.go RawHistoryFolder

1package drm
2
3import (
4	"context"
5	"errors"
6	"maps"
7	"os"
8	"testing"
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 TestProtectedModesetPartialPlaneMetadataFailsClosed(t *testing.T) {
17	for _, role := range []string{"primary", "cursor", "overlay", "stray"} {
18		for _, missing := range []string{"FB_ID", "CRTC_ID"} {
19			t.Run(role+"/"+missing, func(t *testing.T) {
20				o, k, commits := testOutput(t)
21				props := maps.Clone(planeProps)
22				props[missing] = 0
23				p := &plane{id: tOverlay, props: props, crtc: tCrtc}
24				switch role {
25				case "primary":
26					o.primary.props = props
27				case "cursor":
28					o.cursor.plane.props = props
29				case "overlay":
30					o.overlay = p
31					o.stray = []*plane{p} // pickOverlay keeps the plane in stray.
32				case "stray":
33					o.stray = []*plane{p} // old master's overlay may still be here.
34					k.EXPECT().objProps(uint32(tOverlay), uint32(objPlane)).Return(map[string][2]uint64{"CRTC_ID": {pCrtcID, tCrtc}}, nil).Once()
35				}
36				r := portsmocks.NewMockRenderer(t) // no GPU calls permitted
37				if err := o.protectedModeset(context.Background(), r); err == nil {
38					t.Fatal("partial metadata reported protection success")
39				}
40				if !o.protected || len(*commits) != 0 || o.off || o.back != 0 {
41					t.Fatal("unverified detachment committed, flipped or claimed off")
42				}
43			})
44		}
45	}
46}
47
48func TestProtectedModesetSkipsOwnedStrayDuplicates(t *testing.T) {
49	o, k, commits := testOutput(t)
50	o.overlay = &plane{id: tOverlay, props: planeProps}
51	o.stray = []*plane{o.overlay, o.primary, o.cursor.plane}
52	// Even if a fresh query would fail, cached owned-plane IDs suffice.
53	// No owned plane may be queried, during disable or activation.
54	k.EXPECT().objProps(mock.Anything, uint32(objPlane)).Return(nil, unix.EACCES).Maybe()
55	k.EXPECT().createBlob(mock.Anything).Return(99, nil).Once()
56	r := portsmocks.NewMockRenderer(t)
57	r.EXPECT().UseTarget(0).Return().Once()
58	r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Once()
59	if err := o.protectedModeset(context.Background(), r); err != nil {
60		t.Fatal(err)
61	}
62	k.AssertNotCalled(t, "objProps", mock.Anything, mock.Anything)
63	if len(*commits) != 2 {
64		t.Fatalf("commits %d", len(*commits))
65	}
66	checkProtectedDisable(t, (*commits)[0], tPrimary, tCursor, tOverlay)
67}
68
69func TestProtectedModesetDisableSendsInactiveFormats(t *testing.T) {
70	for _, stage := range []string{"disable-failure", "clear-failure", "cancel", "off", "activate"} {
71		t.Run(stage, func(t *testing.T) {
72			var failures []error
73			if stage == "disable-failure" {
74				failures = []error{unix.EACCES}
75			}
76			o, k, _ := testOutput(t, failures...)
77			o.device, o.hdr.on = 42, true
78			feedback := make(chan ports.OutputFormats, 2)
79			o.formats = feedback
80			r := portsmocks.NewMockRenderer(t)
81			ctx, cancel := context.WithCancel(context.Background())
82			defer cancel()
83			if stage != "disable-failure" {
84				r.EXPECT().UseTarget(0).Return().Once()
85				r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
86					// Disable feedback must precede GPU writes, not just return.
87					select {
88					case f := <-feedback:
89						if f.Output != o.conn.name || f.Device != 42 || len(f.Formats) != 0 || f.HDR != nil || !o.off {
90							t.Fatalf("not inactive feedback: %+v off=%v", f, o.off)
91						}
92					default:
93						t.Fatal("successful disable sent no inactive feedback")
94					}
95					if stage == "clear-failure" {
96						return nil, errors.New("clear failed")
97					}
98					if stage == "cancel" {
99						cancel()
100					}
101					return nil, nil
102				}).Once()
103			}
104			if stage == "off" {
105				o.wantOff = true
106			}
107			if stage == "activate" {
108				k.EXPECT().createBlob(mock.Anything).Return(99, nil).Once()
109			}
110			err := o.protectedModeset(ctx, r)
111			wantErr := stage == "disable-failure" || stage == "clear-failure" || stage == "cancel"
112			if (err != nil) != wantErr {
113				t.Fatalf("result %v", err)
114			}
115			if stage == "activate" {
116				select {
117				case f := <-feedback:
118					if f.HDR == nil || o.off {
119						t.Fatalf("activation did not send active feedback: %+v", f)
120					}
121				default:
122					t.Fatal("missing active feedback after activation")
123				}
124			}
125			if len(feedback) != 0 {
126				t.Fatal("unexpected feedback without a successful state transition")
127			}
128		})
129	}
130}

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