v0.8.0 446d1e3

syncobj_test.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"os"
6	"strings"
7	"sync"
8	"testing"
9	"time"
10	"unsafe"
11
12	"github.com/bnema/go-wayland-bindings/server/committiming"
13	"github.com/bnema/go-wayland-bindings/server/fifo"
14	"github.com/bnema/go-wayland-bindings/server/linuxdmabuf"
15	"github.com/bnema/go-wayland-bindings/server/linuxdrmsyncobj"
16	"github.com/bnema/go-wayland-bindings/server/wayland"
17	"github.com/bnema/neferwl/internal/adapters/logging"
18	"github.com/bnema/neferwl/internal/ports"
19	"github.com/bnema/wlturbo"
20	"github.com/stretchr/testify/mock"
21	"golang.org/x/sys/unix"
22)
23
24func TestSyncobjStructSizes(t *testing.T) {
25	for _, tc := range []struct {
26		name string
27		got  uintptr
28		req  uintptr
29	}{
30		{"destroy", unsafe.Sizeof(syncobjDestroy{}), ioctlSyncobjDestroy},
31		{"handle_to_fd", unsafe.Sizeof(syncobjHandle{}), ioctlSyncobjHandleToFD},
32		{"fd_to_handle", unsafe.Sizeof(syncobjHandle{}), ioctlSyncobjFDToHandle},
33		{"timeline_signal", unsafe.Sizeof(syncobjTimelineArray{}), ioctlSyncobjTimelineSignal},
34		{"timeline_wait", unsafe.Sizeof(syncobjTimelineWait{}), ioctlSyncobjTimelineWait},
35		{"eventfd", unsafe.Sizeof(syncobjEventfd{}), ioctlSyncobjEventfd},
36		{"get_cap", unsafe.Sizeof(drmCap{}), ioctlGetCap},
37	} {
38		if want := (tc.req >> 16) & 0x3fff; tc.got != want {
39			t.Errorf("%s: size %d, ioctl encodes %d", tc.name, tc.got, want)
40		}
41	}
42}
43
44// syncHarness is a server with a mocked syncobj device and a client with
45// a mapped window, a dmabuf buffer and the syncobj objects.
46type syncHarness struct {
47	t         *testing.T
48	s         *Server
49	c         *wlturbo.Display
50	dev       *mocksyncobjDevice
51	contents  chan ports.SurfaceContent
52	presented chan ports.OutputPresented
53	surf      uint32
54	syncSurf  uint32
55	timeline  uint32
56	win       ports.WindowID
57	mu        sync.Mutex
58	efds      map[uint64]int // eventfd by acquire point
59	signals   []uint64
60	fences    []*os.File
61	releases  map[uint32]*syncReleaseProxy
62	mgr       uint32
63}
64
65func newSyncHarness(t *testing.T) *syncHarness {
66	h := &syncHarness{t: t, efds: map[uint64]int{}}
67	h.dev = newMocksyncobjDevice(t)
68	h.dev.EXPECT().fdToHandle(mock.Anything).Return(7, nil).Maybe()
69	h.dev.EXPECT().destroy(mock.Anything).Return(nil).Maybe()
70	h.dev.EXPECT().signalled(uint32(7), mock.Anything).Return(false, nil).Maybe()
71	h.dev.EXPECT().eventfd(uint32(7), mock.Anything, mock.Anything).RunAndReturn(func(_ uint32, point uint64, efd int) error {
72		// Keep a duplicate: the waiter closes its own when it fires.
73		dup, err := unix.Dup(efd)
74		if err != nil {
75			return err
76		}
77		h.mu.Lock()
78		h.efds[point] = dup
79		h.mu.Unlock()
80		return nil
81	}).Maybe()
82	h.dev.EXPECT().exportSyncFile(uint32(7), mock.Anything).RunAndReturn(func(uint32, uint64) (*os.File, error) {
83		r, w, err := os.Pipe()
84		if err != nil {
85			return nil, err
86		}
87		w.Close()
88		h.mu.Lock()
89		h.fences = append(h.fences, r)
90		h.mu.Unlock()
91		return r, nil
92	}).Maybe()
93	h.dev.EXPECT().signal(uint32(7), mock.Anything).RunAndReturn(func(_ uint32, point uint64) error {
94		h.mu.Lock()
95		h.signals = append(h.signals, point)
96		h.mu.Unlock()
97		return nil
98	}).Maybe()
99	dir := t.TempDir()
100	events := make(chan ports.ClientEvent, 16)
101	commands := make(chan ports.ClientCommand, 16)
102	h.contents = make(chan ports.SurfaceContent, 64)
103	h.presented = make(chan ports.OutputPresented, 16)
104	sup := ports.DMABufSupport{Formats: []ports.DMABufFormat{linearARGB}}
105	s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs, DMABuf: sup, syncDev: h.dev}, Channels{Events: events, Commands: commands, Contents: h.contents, Presented: h.presented}, logging.For(context.Background(), "wayland"))
106	if err != nil {
107		t.Fatal(err)
108	}
109	ctx, cancel := context.WithCancel(context.Background())
110	done := make(chan error, 1)
111	go func() { done <- s.Run(ctx) }()
112	t.Cleanup(func() {
113		cancel()
114		<-done
115		h.mu.Lock()
116		for _, fd := range h.efds {
117			unix.Close(fd)
118		}
119		h.mu.Unlock()
120	})
121	h.s = s
122	h.c = protocolClient(t, s, dir)
123	w, surf, xdg := surfaceMapper(t, h.c, events)()
124	registerProtocol(t, h.c, xdg)
125	h.win, h.surf = w.ID, surf
126	commands <- ports.ConfigureWindow{ID: w.ID, Width: 64, Height: 16, Output: "HEADLESS-1", Visible: true}
127	mgr := bindProtocol(t, h.c, "wp_linux_drm_syncobj_manager_v1")
128	h.syncSurf = h.c.AllocateID()
129	requestProtocol(t, h.c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, h.syncSurf, surf)
130	registerProtocol(t, h.c, h.syncSurf)
131	fd, err := unix.MemfdCreate("timeline", 0)
132	if err != nil {
133		t.Fatal(err)
134	}
135	defer unix.Close(fd)
136	h.timeline = h.c.AllocateID()
137	registerProtocol(t, h.c, h.timeline)
138	if err := wireRequest(h.c, mgr, uint16(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestImportTimeline), []int{fd}, h.timeline); err != nil {
139		t.Fatal(err)
140	}
141	for deadline := time.Now().Add(2 * time.Second); ; {
142		var out string
143		s.display.Do(func() { out = s.frameOutput(s.windows[w.ID].xdg.surface) })
144		if out != "" {
145			break
146		}
147		if time.Now().After(deadline) {
148			t.Fatal("configure not applied")
149		}
150		time.Sleep(2 * time.Millisecond)
151	}
152	return h
153}
154
155// dmabuf makes a client dmabuf wl_buffer (a memfd stands in for it).
156func (h *syncHarness) dmabuf() uint32 {
157	t, c := h.t, h.c
158	dm := bindVersion(t, c, "zwp_linux_dmabuf_v1", dmabufVersion)
159	fd, err := unix.MemfdCreate("buf", 0)
160	if err != nil {
161		t.Fatal(err)
162	}
163	defer unix.Close(fd)
164	if err := unix.Ftruncate(fd, 256*16); err != nil {
165		t.Fatal(err)
166	}
167	p := c.AllocateID()
168	requestProtocol(t, c, dm, linuxdmabuf.ZwpLinuxDmabufV1RequestCreateParams, p)
169	registerProtocol(t, c, p)
170	if err := wireRequest(c, p, uint16(linuxdmabuf.ZwpLinuxBufferParamsV1RequestAdd), []int{fd}, uint32(0), uint32(0), uint32(256), uint32(0), uint32(0)); err != nil {
171		t.Fatal(err)
172	}
173	b := c.AllocateID()
174	rel := &syncReleaseProxy{released: make(chan struct{}, 4)}
175	rel.SetID(b)
176	registerWireProxy(c, rel)
177	requestProtocol(t, c, p, linuxdmabuf.ZwpLinuxBufferParamsV1RequestCreateImmed, b, int32(64), int32(16), linearARGB.Format, uint32(0))
178	h.releases[b] = rel
179	return b
180}
181
182type syncReleaseProxy struct {
183	wlturbo.BaseProxy
184	released chan struct{}
185}
186
187func (p *syncReleaseProxy) Dispatch(e *wlturbo.Event) {
188	if uint32(e.Opcode) == wayland.BufferEventRelease {
189		p.released <- struct{}{}
190	}
191}
192
193func (h *syncHarness) setPoints(acquire, release uint64) {
194	requestProtocol(h.t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(acquire>>32), uint32(acquire))
195	requestProtocol(h.t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(release>>32), uint32(release))
196}
197
198func (h *syncHarness) commit(buf uint32, acquire, release uint64) {
199	t, c := h.t, h.c
200	requestProtocol(t, c, h.surf, wayland.SurfaceRequestAttach, buf, int32(0), int32(0))
201	h.setPoints(acquire, release)
202	requestProtocol(t, c, h.surf, wayland.SurfaceRequestCommit)
203	if err := c.Roundtrip(); err != nil {
204		t.Fatal(err)
205	}
206}
207
208// content waits for a DMABuf content of the window.
209func (h *syncHarness) content(wait time.Duration) (ports.SurfaceContent, bool) {
210	deadline := time.After(wait)
211	for {
212		select {
213		case c := <-h.contents:
214			if c.ID == h.win && c.DMABuf != nil {
215				return c, true
216			}
217		case <-deadline:
218			return ports.SurfaceContent{}, false
219		}
220	}
221}
222
223// fire makes the kernel's eventfd for an acquire point readable.
224func (h *syncHarness) fire(point uint64) {
225	h.t.Helper()
226	deadline := time.Now().Add(2 * time.Second)
227	for {
228		h.mu.Lock()
229		fd, ok := h.efds[point]
230		h.mu.Unlock()
231		if ok {
232			var one [8]byte
233			one[0] = 1
234			if _, err := unix.Write(fd, one[:]); err != nil {
235				h.t.Fatal(err)
236			}
237			return
238		}
239		if time.Now().After(deadline) {
240			h.t.Fatalf("no eventfd for point %d", point)
241		}
242		time.Sleep(2 * time.Millisecond)
243	}
244}
245
246func (h *syncHarness) signalled() []uint64 {
247	h.mu.Lock()
248	defer h.mu.Unlock()
249	return append([]uint64(nil), h.signals...)
250}
251
252// A commit waits for its acquire point; the content carries the fence;
253// replacing it signals the release point once outputs moved on, and
254// wl_buffer.release is never sent. The acquire fence closes only then.
255func TestSyncobjAcquireAndRelease(t *testing.T) {
256	h := newSyncHarness(t)
257	h.releases = map[uint32]*syncReleaseProxy{}
258	a, b := h.dmabuf(), h.dmabuf()
259	h.commit(a, 1, 2)
260	if _, ok := h.content(50 * time.Millisecond); ok {
261		t.Fatal("content before the acquire point had a fence")
262	}
263	h.fire(1)
264	first, ok := h.content(2 * time.Second)
265	if !ok || first.Acquire == nil {
266		t.Fatalf("content %+v", first)
267	}
268	h.commit(b, 3, 4)
269	h.fire(3)
270	second, ok := h.content(2 * time.Second)
271	if !ok {
272		t.Fatal("second content")
273	}
274	if _, err := first.Acquire.Stat(); err != nil {
275		t.Fatal("acquire fence closed before release")
276	}
277	// The output read the second content: the first buffer is released.
278	h.presented <- ports.OutputPresented{Output: "HEADLESS-1", Seen: map[ports.WindowID]uint64{h.win: second.Seq}}
279	deadline := time.Now().Add(2 * time.Second)
280	for len(h.signalled()) == 0 && time.Now().Before(deadline) {
281		if err := h.c.Roundtrip(); err != nil {
282			t.Fatal(err)
283		}
284		time.Sleep(2 * time.Millisecond)
285	}
286	if got := h.signalled(); len(got) != 1 || got[0] != 2 {
287		t.Fatalf("signalled %v", got)
288	}
289	if _, err := first.Acquire.Stat(); err == nil {
290		t.Fatal("acquire fence left open after release")
291	}
292	if err := h.c.Roundtrip(); err != nil {
293		t.Fatal(err)
294	}
295	select {
296	case <-h.releases[a].released:
297		t.Fatal("wl_buffer.release sent under explicit sync")
298	default:
299	}
300}
301
302// FIFO, commit timing and acquire are independent gates on the same ordered
303// update: clearing the first two cannot publish before its acquire fires.
304func TestQueuedCommitReadinessGatesInOrder(t *testing.T) {
305	h := newSyncHarness(t)
306	h.releases = map[uint32]*syncReleaseProxy{}
307	first, second := h.dmabuf(), h.dmabuf()
308	fm := bindProtocol(t, h.c, "wp_fifo_manager_v1")
309	f := h.c.AllocateID()
310	registerProtocol(t, h.c, f)
311	requestProtocol(t, h.c, fm, fifo.WpFifoManagerV1RequestGetFifo, f, h.surf)
312	tm := bindProtocol(t, h.c, "wp_commit_timing_manager_v1")
313	timer := h.c.AllocateID()
314	registerProtocol(t, h.c, timer)
315	requestProtocol(t, h.c, tm, committiming.WpCommitTimingManagerV1RequestGetTimer, timer, h.surf)
316
317	requestProtocol(t, h.c, f, fifo.WpFifoV1RequestSetBarrier)
318	h.commit(first, 1, 2)
319	h.fire(1)
320	one, ok := h.content(2 * time.Second)
321	if !ok {
322		t.Fatal("first update did not publish")
323	}
324	var now unix.Timespec
325	if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &now); err != nil {
326		t.Fatal(err)
327	}
328	target := time.Duration(now.Nano()) + 180*time.Millisecond
329	sec := uint64(target / time.Second)
330	requestProtocol(t, h.c, timer, committiming.WpCommitTimerV1RequestSetTimestamp, uint32(sec>>32), uint32(sec), uint32(target%time.Second))
331	requestProtocol(t, h.c, f, fifo.WpFifoV1RequestWaitBarrier)
332	h.commit(second, 3, 4)
333	// Even with an acquire fence available, timestamp and barrier hold it.
334	var waiting bool
335	h.s.display.Do(func() {
336		surf := h.s.windows[h.win].xdg.surface
337		waiting = len(surf.queue) == 1 && surf.barrier
338	})
339	if !waiting {
340		t.Fatal("update did not wait behind the FIFO barrier")
341	}
342	if _, ok := h.content(200 * time.Millisecond); ok {
343		t.Fatal("update published before its acquire fence")
344	}
345	var gatedByAcquire bool
346	h.s.display.Do(func() {
347		surf := h.s.windows[h.win].xdg.surface
348		gatedByAcquire = len(surf.queue) == 1 && !surf.barrier && !surf.tooEarly(surf.queue[0].at, time.Now()) && !surf.syncReady(surf.queue[0].sync)
349	})
350	if !gatedByAcquire {
351		t.Fatal("FIFO/timing did not clear while acquire still waited")
352	}
353	h.fire(3)
354	two, ok := h.content(2 * time.Second)
355	if !ok || two.Seq <= one.Seq || two.DMABuf.ID == one.DMABuf.ID {
356		t.Fatalf("ordered publication: first=%+v second=%+v", one, two)
357	}
358}
359
360// A destroyed queued buffer must not hold later commits behind its acquire.
361// Its release point cannot be advanced before the acquire is ready.
362func TestSyncobjDestroyedQueuedAcquire(t *testing.T) {
363	h := newSyncHarness(t)
364	h.releases = map[uint32]*syncReleaseProxy{}
365	first, dead, next := h.dmabuf(), h.dmabuf(), h.dmabuf()
366	h.commit(first, 1, 2)
367	h.fire(1)
368	if _, ok := h.content(2 * time.Second); !ok {
369		t.Fatal("initial content missing")
370	}
371	fm := bindProtocol(t, h.c, "wp_fifo_manager_v1")
372	f := h.c.AllocateID()
373	registerProtocol(t, h.c, f)
374	requestProtocol(t, h.c, fm, fifo.WpFifoManagerV1RequestGetFifo, f, h.surf)
375	requestProtocol(t, h.c, f, fifo.WpFifoV1RequestSetBarrier)
376	h.commit(dead, 3, 4)
377	requestProtocol(t, h.c, dead, wayland.BufferRequestDestroy)
378	if err := h.c.Roundtrip(); err != nil {
379		t.Fatal(err)
380	}
381	// The dead attachment is skipped; it does not replace the current one.
382	// A later attachment is queued while the old fence remains unsignalled.
383	h.commit(next, 5, 6)
384	h.fire(5)
385	got, ok := h.content(2 * time.Second)
386	if !ok || got.DMABuf == nil {
387		t.Fatalf("later content blocked: %+v", got)
388	}
389	for _, p := range h.signalled() {
390		if p == 4 {
391			t.Fatal("skipped buffer release signalled before acquire")
392		}
393	}
394}
395
396func TestSyncobjProtocolErrors(t *testing.T) {
397	for _, tc := range []struct {
398		name string
399		run  func(h *syncHarness)
400		obj  func(h *syncHarness) uint32
401		code uint32
402	}{
403		{"conflicting points", func(h *syncHarness) {
404			b := h.dmabuf()
405			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
406			requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(5))
407			requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(5))
408			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
409		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorConflictingPoints)},
410		{"no release point", func(h *syncHarness) {
411			b := h.dmabuf()
412			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
413			requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(5))
414			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
415		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoReleasePoint)},
416		{"buffer without points", func(h *syncHarness) {
417			b := h.dmabuf()
418			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
419			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
420		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoAcquirePoint)},
421		{"no acquire point", func(h *syncHarness) {
422			b := h.dmabuf()
423			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
424			requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(5))
425			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
426		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoAcquirePoint)},
427		{"points without attach", func(h *syncHarness) {
428			b := h.dmabuf()
429			h.commit(b, 1, 2)
430			h.fire(1)
431			h.setPoints(3, 4)
432			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
433		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoBuffer)},
434		{"points with null attach", func(h *syncHarness) {
435			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
436			h.setPoints(1, 2)
437			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
438		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoBuffer)},
439		{"wl_shm buffer", func(h *syncHarness) {
440			b := shmBuffer(t, h.c)
441			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
442			h.setPoints(1, 2)
443			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
444		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorUnsupportedBuffer)},
445		{"surface destroyed", func(h *syncHarness) {
446			requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
447			h.setPoints(1, 2)
448		}, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoSurface)},
449		{"surface exists", func(h *syncHarness) {
450			mgr := bindProtocol(t, h.c, "wp_linux_drm_syncobj_manager_v1")
451			h.mgr = mgr
452			requestProtocol(t, h.c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, h.c.AllocateID(), h.surf)
453		}, func(h *syncHarness) uint32 { return h.mgr }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1ErrorSurfaceExists)},
454	} {
455		t.Run(tc.name, func(t *testing.T) {
456			h := newSyncHarness(t)
457			h.releases = map[uint32]*syncReleaseProxy{}
458			h.t = t
459			tc.run(h)
460			expectProtocolError(t, h.c, tc.obj(h), tc.code)
461		})
462	}
463}
464
465// An invalid timeline fd is a protocol error.
466func TestSyncobjInvalidTimeline(t *testing.T) {
467	dev := newMocksyncobjDevice(t)
468	dev.EXPECT().fdToHandle(mock.Anything).Return(0, unix.EINVAL).Once()
469	dir := t.TempDir()
470	s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs, syncDev: dev}, Channels{}, logging.For(context.Background(), "wayland"))
471	if err != nil {
472		t.Fatal(err)
473	}
474	ctx, cancel := context.WithCancel(context.Background())
475	done := make(chan error, 1)
476	go func() { done <- s.Run(ctx) }()
477	t.Cleanup(func() { cancel(); <-done })
478	c := protocolClient(t, s, dir)
479	mgr := bindProtocol(t, c, "wp_linux_drm_syncobj_manager_v1")
480	fd, _ := unix.MemfdCreate("bad", 0)
481	defer unix.Close(fd)
482	if err := wireRequest(c, mgr, uint16(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestImportTimeline), []int{fd}, c.AllocateID()); err != nil {
483		t.Fatal(err)
484	}
485	expectProtocolError(t, c, mgr, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1ErrorInvalidTimeline))
486}
487
488// A synchronized subsurface dropped before its acquire is ready must
489// retire its release point without sending wl_buffer.release.
490func TestSyncobjDroppedSubsurfaceCommit(t *testing.T) {
491	h := newSyncHarness(t)
492	h.releases = map[uint32]*syncReleaseProxy{}
493	c := h.c
494	comp := bindProtocol(t, c, "wl_compositor")
495	subc := bindProtocol(t, c, "wl_subcompositor")
496	mgr := bindProtocol(t, c, "wp_linux_drm_syncobj_manager_v1")
497	child := c.AllocateID()
498	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
499	registerProtocol(t, c, child)
500	sub := c.AllocateID()
501	requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, h.surf)
502	registerProtocol(t, c, sub)
503	syncChild := c.AllocateID()
504	requestProtocol(t, c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, syncChild, child)
505	registerProtocol(t, c, syncChild)
506	b := h.dmabuf()
507	requestProtocol(t, c, child, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
508	requestProtocol(t, c, syncChild, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(21))
509	requestProtocol(t, c, syncChild, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(22))
510	requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
511	requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy)
512	if err := c.Roundtrip(); err != nil {
513		t.Fatal(err)
514	}
515	h.fire(21) // dropping before acquire means the later signal must not release
516	if err := c.Roundtrip(); err != nil {
517		t.Fatal(err)
518	}
519	if got := h.signalled(); len(got) != 0 {
520		t.Fatalf("premature release point: %v", got)
521	}
522	select {
523	case <-h.releases[b].released:
524		t.Fatal("explicit sync sent wl_buffer.release")
525	default:
526	}
527}
528
529// A commit dropped before its acquire point has a fence signals nothing
530// (the client has not submitted that point) and sends no wl_buffer.release;
531// the waiter closes its eventfd and no acquire file is left open.
532func TestSyncobjDroppedCommit(t *testing.T) {
533	h := newSyncHarness(t)
534	h.releases = map[uint32]*syncReleaseProxy{}
535	a := h.dmabuf()
536	before := openFDs(t)
537	h.commit(a, 1, 2)
538	requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
539	if err := h.c.Roundtrip(); err != nil {
540		t.Fatal(err)
541	}
542	// The point never gets a fence: the waiter must still stop polling it.
543	time.Sleep(250 * time.Millisecond)
544	if err := h.c.Roundtrip(); err != nil {
545		t.Fatal(err)
546	}
547	if got := h.signalled(); len(got) != 0 {
548		t.Fatalf("signalled %v for a commit whose acquire point never arrived", got)
549	}
550	select {
551	case <-h.releases[a].released:
552		t.Fatal("wl_buffer.release sent under explicit sync")
553	default:
554	}
555	// The harness keeps its own duplicate of the eventfd.
556	if after := openFDs(t); after > before+1 {
557		t.Fatalf("eventfds and sync files %d -> %d", before, after)
558	}
559}
560
561// A commit dropped after its acquire point fired signals its release point.
562func TestSyncobjDroppedReadyCommitReleases(t *testing.T) {
563	h := newSyncHarness(t)
564	h.releases = map[uint32]*syncReleaseProxy{}
565	a := h.dmabuf()
566	h.commit(a, 1, 2)
567	h.fire(1)
568	if _, ok := h.content(2 * time.Second); !ok {
569		t.Fatal("content")
570	}
571	requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
572	deadline := time.Now().Add(2 * time.Second)
573	for len(h.signalled()) == 0 && time.Now().Before(deadline) {
574		if err := h.c.Roundtrip(); err != nil {
575			t.Fatal(err)
576		}
577		time.Sleep(2 * time.Millisecond)
578	}
579	if got := h.signalled(); len(got) != 1 || got[0] != 2 {
580		t.Fatalf("signalled %v", got)
581	}
582}
583
584// openFDs counts the eventfds and sync files (pipes stand in for them)
585// the process holds.
586func openFDs(t *testing.T) int {
587	t.Helper()
588	ents, err := os.ReadDir("/proc/self/fd")
589	if err != nil {
590		t.Fatal(err)
591	}
592	n := 0
593	for _, e := range ents {
594		l, _ := os.Readlink("/proc/self/fd/" + e.Name())
595		if strings.Contains(l, "eventfd") || strings.HasPrefix(l, "pipe:") {
596			n++
597		}
598	}
599	return n
600}

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