alloc_test.go RawHistoryFolder

1package drm
2
3import (
4	"errors"
5	"os"
6	"testing"
7
8	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
9	"github.com/bnema/neferwl/internal/ports"
10	"github.com/stretchr/testify/mock"
11	"golang.org/x/sys/unix"
12)
13
14// gbmBuf is a dmabuf over a closed pipe, as an image buffer hands out.
15func gbmBuf(planes int) ports.DMABuf {
16	b := ports.DMABuf{Modifier: tiledMod}
17	for range planes {
18		f, w, _ := os.Pipe()
19		w.Close()
20		b.Planes = append(b.Planes, ports.DMABufPlane{File: f})
21	}
22	return b
23}
24
25// twoGBMImages makes an allocator that hands out two images of the given
26// plane count; each image closes exactly once, counted in closed.
27func twoGBMImages(t *testing.T, planes int) (a imageAllocator, closed *int) {
28	m := newMockimageAllocator(t)
29	closed = new(int)
30	m.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).RunAndReturn(func(int, int, uint32, []uint64) (imageBuffer, error) {
31		img := newMockimageBuffer(t)
32		img.EXPECT().dmabuf().RunAndReturn(func() (ports.DMABuf, error) { return gbmBuf(planes), nil })
33		img.EXPECT().close().Run(func() { *closed++ }).Once()
34		return img, nil
35	}).Twice()
36	m.EXPECT().close().Return().Maybe()
37	return m, closed
38}
39
40func gbmRenderer(t *testing.T) *portsmocks.MockRenderer {
41	r := portsmocks.NewMockRenderer(t)
42	r.EXPECT().SetHDR(float64(0)).Return().Maybe()
43	r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
44	r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Maybe()
45	return r
46}
47
48// GBM images come first: the renderer imports them, the framebuffers are
49// made from them, and no image is exported.
50func TestShowImagesAllocatesWithGBMFirst(t *testing.T) {
51	o, k, _ := testOutput(t)
52	o.cursor = nil
53	a, closed := twoGBMImages(t, 1)
54	o.openAlloc = func() (imageAllocator, error) { return a, nil }
55	r := gbmRenderer(t)
56	r.EXPECT().TargetModifiers([]uint64(nil), false).Return([]uint64{tiledMod}).Once()
57	r.EXPECT().ImportTargets(mock.MatchedBy(func(b []ports.DMABuf) bool { return len(b) == 2 && b[0].Modifier == tiledMod })).Return(nil).Once()
58	k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
59	k.EXPECT().createBlob(mock.Anything).Return(99, nil)
60	k.EXPECT().destroyBlob(mock.Anything).Return(nil)
61	k.EXPECT().rmFB(mock.Anything).Return(nil)
62	if err := o.showImages(r, imagesDriver, nil); err != nil {
63		t.Fatal(err)
64	}
65	if o.kind != imagesDriver || o.imageMod != tiledMod || len(o.images) != 2 || *closed != 0 {
66		t.Fatalf("kind %d modifier %#x images %d closed %d", o.kind, o.imageMod, len(o.images), *closed)
67	}
68	r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
69	o.dropImages(r)
70	if *closed != 2 || o.images != nil || o.fbs != [2]uint32{} {
71		t.Fatalf("closed %d images, %d left, fbs %v", *closed, len(o.images), o.fbs)
72	}
73}
74
75// A GBM failure at any step falls back to the renderer's own images of the same kind.
76func TestShowImagesFallsBackToVulkanWhenGBMFails(t *testing.T) {
77	for name, tc := range map[string]struct {
78		setup func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer)
79	}{
80		"gbm unavailable": {func(_ *testing.T, o *Output, _ *mockkms, _ *portsmocks.MockRenderer) {
81			o.openAlloc = func() (imageAllocator, error) { return nil, errors.New("no libgbm") }
82		}},
83		"no common modifier": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
84			a := newMockimageAllocator(t)
85			a.EXPECT().close().Return().Maybe()
86			o.openAlloc = func() (imageAllocator, error) { return a, nil }
87			r.EXPECT().TargetModifiers(mock.Anything, false).Return(nil).Once()
88		}},
89		"allocation fails": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
90			a := newMockimageAllocator(t)
91			a.EXPECT().close().Return().Maybe()
92			a.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).Return(nil, errors.New("gbm: no buffer")).Once()
93			o.openAlloc = func() (imageAllocator, error) { return a, nil }
94			r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
95		}},
96		"second allocation fails": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
97			a := newMockimageAllocator(t)
98			a.EXPECT().close().Return().Maybe()
99			img := newMockimageBuffer(t)
100			img.EXPECT().dmabuf().Return(gbmBuf(1), nil).Once()
101			img.EXPECT().close().Return().Once() // freed with the failed attempt
102			r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
103			a.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).Return(img, nil).Once()
104			a.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).Return(nil, errors.New("out of memory")).Once()
105			o.openAlloc = func() (imageAllocator, error) { return a, nil }
106			r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
107		}},
108		"import refused": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
109			a, _ := twoGBMImages(t, 1)
110			o.openAlloc = func() (imageAllocator, error) { return a, nil }
111			r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
112			r.EXPECT().ImportTargets(mock.Anything).Return(errors.New("explicit layout refused")).Once()
113			// The images made before the refusal go with a renderer drop.
114			r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
115		}},
116		"framebuffer refused": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
117			a, _ := twoGBMImages(t, 1)
118			o.openAlloc = func() (imageAllocator, error) { return a, nil }
119			r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
120			r.EXPECT().ImportTargets(mock.Anything).Return(nil).Once()
121			r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
122			k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(0, unix.EINVAL).Once()
123		}},
124	} {
125		t.Run(name, func(t *testing.T) {
126			o, k, _ := testOutput(t)
127			o.cursor = nil
128			r := gbmRenderer(t)
129			tc.setup(t, o, k, r)
130			r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
131			k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Maybe()
132			k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
133			k.EXPECT().createBlob(mock.Anything).Return(99, nil)
134			k.EXPECT().destroyBlob(mock.Anything).Return(nil)
135			if err := o.showImages(r, imagesDriver, nil); err != nil {
136				t.Fatal(err)
137			}
138			// The renderer's own images: no GBM image is alive.
139			if o.kind != imagesDriver || len(o.images) != 0 || o.fbs[0] == 0 || o.fbs[1] == 0 {
140				t.Fatalf("kind %d images %d fbs %v", o.kind, len(o.images), o.fbs)
141			}
142		})
143	}
144}
145
146// A failed GBM open is remembered: the next kind and the next setup do not
147// open the library again.
148func TestGBMOpenFailureIsRemembered(t *testing.T) {
149	o, k, _ := testOutput(t, unix.EINVAL)
150	o.cursor = nil
151	opens := 0
152	o.openAlloc = func() (imageAllocator, error) { opens++; return nil, errors.New("no libgbm") }
153	r := gbmRenderer(t)
154	r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(2), gbmBuf(2)}, nil).Once()
155	r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
156	k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
157	k.EXPECT().rmFB(mock.Anything).Return(nil)
158	k.EXPECT().createBlob(mock.Anything).Return(99, nil)
159	k.EXPECT().destroyBlob(mock.Anything).Return(nil)
160	if err := o.showImages(r, imagesDriver, nil); err != nil {
161		t.Fatal(err)
162	}
163	if opens != 1 || o.kind != imagesSinglePlane {
164		t.Fatalf("%d opens, kind %d", opens, o.kind)
165	}
166}
167
168// A modeset test that refuses GBM images tries the renderer's images of the
169// same kind before the next kind.
170func TestShowImagesRefusedGBMImagesTryVulkanOfTheSameKind(t *testing.T) {
171	o, k, commits := testOutput(t, unix.EINVAL)
172	o.cursor = nil
173	a, closed := twoGBMImages(t, 1)
174	o.openAlloc = func() (imageAllocator, error) { return a, nil }
175	r := gbmRenderer(t)
176	r.EXPECT().TargetModifiers([]uint64(nil), false).Return([]uint64{tiledMod}).Once()
177	r.EXPECT().ImportTargets(mock.MatchedBy(func(b []ports.DMABuf) bool { return len(b) == 2 })).Return(nil).Once()
178	// The refused GBM images leave no renderer target behind.
179	r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
180	r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
181	k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
182	k.EXPECT().rmFB(mock.Anything).Return(nil)
183	k.EXPECT().createBlob(mock.Anything).Return(99, nil)
184	k.EXPECT().destroyBlob(mock.Anything).Return(nil)
185	if err := o.showImages(r, imagesDriver, nil); err != nil {
186		t.Fatal(err)
187	}
188	if o.kind != imagesDriver || *closed != 2 || len(o.images) != 0 || len(*commits) != 3 {
189		t.Fatalf("kind %d closed %d images %d commits %d", o.kind, *closed, len(o.images), len(*commits))
190	}
191}
192
193// Closing the output frees the GBM images and closes the allocator once.
194func TestOutputCloseFreesGBMImagesAndAllocator(t *testing.T) {
195	o, k, _ := testOutput(t)
196	o.cursor = nil
197	img := newMockimageBuffer(t)
198	img.EXPECT().close().Return().Twice()
199	alloc := newMockimageAllocator(t)
200	alloc.EXPECT().close().Return().Once()
201	o.images = []imageBuffer{img, img}
202	o.alloc = alloc
203	o.fbs = [2]uint32{70, 71}
204	k.EXPECT().rmFB(uint32(70)).Return(nil).Once()
205	k.EXPECT().rmFB(uint32(71)).Return(nil).Once()
206	o.Close()
207	if o.images != nil || o.alloc != nil || o.fbs != [2]uint32{} {
208		t.Fatalf("images %v alloc %v fbs %v", o.images, o.alloc, o.fbs)
209	}
210}
211
212// A setup that finds images left over frees them first, with the renderer's
213// targets over GBM ones: the renderer never keeps a target over a freed
214// image.
215func TestSetupStartsFromCleanState(t *testing.T) {
216	o, k, _ := testOutput(t)
217	o.cursor = nil
218	img := newMockimageBuffer(t)
219	img.EXPECT().close().Return().Once()
220	o.images = []imageBuffer{img}
221	o.fbs = [2]uint32{70, 71}
222	k.EXPECT().rmFB(uint32(70)).Return(nil).Once()
223	k.EXPECT().rmFB(uint32(71)).Return(nil).Once()
224	k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(80, nil)
225	r := gbmRenderer(t)
226	r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
227	r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
228	if err := o.exportImages(r, nil, false); err != nil {
229		t.Fatal(err)
230	}
231	if o.images != nil || o.fbs != [2]uint32{80, 80} {
232		t.Fatalf("images %v fbs %v", o.images, o.fbs)
233	}
234}

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