acquire_failure_test.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"math"
6	"os"
7	"testing"
8	"time"
9
10	"github.com/bnema/go-wayland-bindings/server/wayland"
11	"github.com/bnema/neferwl/internal/ports"
12	"github.com/stretchr/testify/mock"
13	"golang.org/x/sys/unix"
14)
15
16func TestExplicitAcquireWatchFailureDiscardsCommit(t *testing.T) {
17	for _, failure := range []string{"signalled", "eventfd"} {
18		t.Run(failure, func(t *testing.T) {
19			h := newSyncHarness(t)
20			h.releases = map[uint32]*syncReleaseProxy{}
21			// Order setup after import and reset on the display owner so no
22			// concurrent device call can read the generated expectations.
23			if err := h.c.Roundtrip(); err != nil {
24				t.Fatal(err)
25			}
26			h.s.display.Do(func() { h.dev.ExpectedCalls = nil })
27			h.dev.EXPECT().fdToHandle(mock.Anything).Return(7, nil).Maybe()
28			h.dev.EXPECT().destroy(mock.Anything).Return(nil).Maybe()
29			if failure == "signalled" {
30				h.dev.EXPECT().signalled(uint32(7), uint64(101)).Return(false, unix.EIO).Once()
31			} else {
32				h.dev.EXPECT().signalled(uint32(7), uint64(101)).Return(false, nil).Once()
33				h.dev.EXPECT().eventfd(uint32(7), uint64(101), mock.Anything).Return(unix.EIO).Once()
34			}
35			drainContents(h.contents)
36			buffer := h.dmabuf()
37			h.commit(buffer, 101, 102)
38			if got, ok := h.content(50 * time.Millisecond); ok {
39				t.Fatalf("untracked acquire published %+v", got)
40			}
41			h.s.display.Do(func() {
42				surf := h.s.windows[h.win].xdg.surface
43				if len(surf.queue) != 0 || surf.hold.acquire != nil {
44					t.Error("failed acquire retained queued or rendered fence")
45				}
46				for _, tl := range h.s.timelines {
47					if tl.uses != 0 {
48						t.Errorf("timeline uses=%d", tl.uses)
49					}
50				}
51			})
52			if len(h.signalled()) != 0 {
53				t.Fatal("unsafe release point signalled")
54			}
55			select {
56			case <-h.releases[buffer].released:
57				t.Fatal("explicit buffer release event")
58			default:
59			}
60		})
61	}
62}
63
64func TestImplicitAcquireWatchFailureDiscardsCommit(t *testing.T) {
65	for _, failure := range []string{"poll", "hangup"} {
66		t.Run(failure, func(t *testing.T) {
67			h := newSyncHarness(t)
68			h.releases = map[uint32]*syncReleaseProxy{}
69			drainContents(h.contents)
70			buffer := h.dmabuf()
71			var plane *os.File
72			var writer *os.File
73			if failure == "poll" {
74				plane = os.NewFile(uintptr(math.MaxInt32), "invalid")
75			} else {
76				var err error
77				plane, writer, err = os.Pipe()
78				if err != nil {
79					t.Fatal(err)
80				}
81				defer plane.Close()
82			}
83			// Real pollable descriptors exercise observation errors without replacing
84			// watch/poll functions or constructing a handwritten device double.
85			if err := h.c.Roundtrip(); err != nil {
86				t.Fatal(err)
87			}
88			h.s.display.Do(func() {
89				var buf *dmabufBuffer
90				var resource *wayland.Buffer
91				for r, b := range h.s.buffers {
92					if r.ID() == buffer {
93						buf = b.(*dmabufBuffer)
94						resource = wayland.WrapBuffer(r)
95					}
96				}
97				if buf == nil {
98					t.Error("buffer missing")
99					return
100				}
101				original := buf.buf.Planes[0].File
102				defer func() { buf.buf.Planes[0].File = original }()
103				buf.buf.Planes[0].File = plane
104				surf := h.s.windows[h.win].xdg.surface
105				surf.Attach(surf.wl, resource, 0, 0)
106				surf.Commit(surf.wl)
107			})
108			if writer != nil {
109				writer.Close()
110			}
111			if got, ok := h.content(100 * time.Millisecond); ok {
112				t.Fatalf("failed implicit acquire published %+v", got)
113			}
114			h.s.display.Do(func() {
115				if len(h.s.windows[h.win].xdg.surface.queue) != 0 {
116					t.Error("failed implicit acquire left queue blocked")
117				}
118			})
119		})
120	}
121}
122
123func TestAcquirePollTerminalFailureRetiresActiveAndAdded(t *testing.T) {
124	poller := newMockacquirePoller(t)
125	wake := make(chan struct{}, 1)
126	sw, err := newSyncWaiter(func() { wake <- struct{}{} })
127	if err != nil {
128		t.Fatal(err)
129	}
130	sw.poller = poller
131	defer sw.close()
132	fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC)
133	if err != nil {
134		t.Fatal(err)
135	}
136	active := sw.add(fd)
137	var added *syncWait
138	poller.EXPECT().poll(mock.Anything, 100).RunAndReturn(func(fds []unix.PollFd, _ int) (int, error) { fds[0].Revents = unix.POLLIN; return 1, nil }).Once()
139	poller.EXPECT().poll(mock.Anything, 100).RunAndReturn(func([]unix.PollFd, int) (int, error) {
140		fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC)
141		if err != nil {
142			t.Error(err)
143			return -1, err
144		}
145		added = sw.add(fd)
146		return -1, unix.EIO
147	}).Once()
148	sw.run(context.Background())
149	select {
150	case <-wake:
151	default:
152		t.Fatal("terminal failure did not wake owner")
153	}
154	for _, w := range []*syncWait{active, added} {
155		if w == nil || !sw.fired(w) || !w.failed {
156			t.Fatal("wait stranded after terminal poll failure")
157		}
158	}
159	if !sw.stopped() {
160		t.Fatal("waiter not terminal")
161	}
162	dev := newMocksyncobjDevice(t) // no device access permitted after terminal failure
163	if w, err := sw.watch(syncPoint{tl: &timeline{dev: dev, handle: 7}, point: 1}); err == nil || !w.failed {
164		t.Fatal("new explicit watch accepted")
165	}
166	var waits [4]*syncWait
167	if err := sw.watchImplicit(&ports.DMABuf{}, &waits); err == nil {
168		t.Fatal("new implicit watch accepted")
169	}
170}
171
172func TestFailedRootRetiresEveryChildPredecessorGraph(t *testing.T) {
173	h := newSyncHarness(t)
174	sw := h.s.syncWait
175	root := &surface{server: h.s}
176	child := &surface{server: h.s}
177	a, b := &surface{server: h.s}, &surface{server: h.s}
178	tl := &timeline{dev: h.dev, handle: 7, alive: true, uses: 4}
179	var waits [2]*syncWait
180	var graphs [2]*update
181	h.s.display.Do(func() {
182		for i, surf := range []*surface{a, b} {
183			fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK)
184			if err != nil {
185				t.Error(err)
186				return
187			}
188			waits[i] = sw.add(fd)
189			surf.next.sync = &commitSync{acquire: syncPoint{tl: tl, point: uint64(21 + i*2)}, release: syncPoint{tl: tl, point: uint64(22 + i*2)}, wait: waits[i]}
190			surf.queueUpdate()
191			graphs[i] = surf.queue[len(surf.queue)-1]
192			graphs[i].refs++ // pin retired state for assertions
193		}
194		child.queueUpdate()
195		child.queueUpdate()
196		one, two := child.queue[0], child.queue[1]
197		one.deps = []*update{graphs[0]}
198		graphs[0].refs++
199		two.deps = []*update{graphs[1]}
200		graphs[1].refs++
201		root.queueUpdate()
202		u := root.queue[0]
203		u.deps = []*update{two}
204		two.refs++
205		u.failed = true
206		child.queueUpdate()
207		later := child.queue[2]
208		u.applyGraph()
209		if len(child.queue) != 1 || child.queue[0] != later || len(a.queue) != 0 || len(b.queue) != 0 || tl.uses != 0 {
210			t.Errorf("retirement stranded prefix graphs: uses=%d child=%d a=%d b=%d", tl.uses, len(child.queue), len(a.queue), len(b.queue))
211		}
212		for i, g := range graphs {
213			if !g.retired || g.refs != 1 || !waits[i].cancelled.Load() {
214				t.Error("grandchild not safely retired")
215			}
216			g.releaseRef()
217		}
218		child.dropQueue()
219	})
220	if len(h.signalled()) != 0 {
221		t.Fatal("release signalled before acquire")
222	}
223}
224
225func TestAcquireExportFailurePreservesCurrentContent(t *testing.T) {
226	h := newSyncHarness(t)
227	h.releases = map[uint32]*syncReleaseProxy{}
228	if err := h.c.Roundtrip(); err != nil {
229		t.Fatal(err)
230	}
231	h.s.display.Do(func() { h.dev.ExpectedCalls = nil })
232	h.dev.EXPECT().destroy(mock.Anything).Return(nil).Maybe()
233	h.dev.EXPECT().signalled(uint32(7), uint64(111)).Return(true, nil).Once()
234	h.dev.EXPECT().exportSyncFile(uint32(7), uint64(111)).Return(nil, unix.EIO).Once()
235	h.dev.EXPECT().signal(uint32(7), uint64(112)).Return(nil).Once() // observed acquire: safe discarded release
236	drainContents(h.contents)
237	buffer := h.dmabuf()
238	var before ports.SurfaceContent
239	h.s.display.Do(func() { before = h.s.windows[h.win].xdg.surface.content })
240	h.commit(buffer, 111, 112)
241	if got, ok := h.content(50 * time.Millisecond); ok {
242		t.Fatalf("export failure published %+v", got)
243	}
244	h.s.display.Do(func() {
245		surf := h.s.windows[h.win].xdg.surface
246		if surf.content.Seq != before.Seq || surf.content.DMABuf != before.DMABuf || surf.hold.acquire != nil || len(surf.queue) != 0 {
247			t.Error("export failure changed current content")
248		}
249		for _, tl := range h.s.timelines {
250			if tl.uses != 0 {
251				t.Error("export failure pinned timeline")
252			}
253		}
254	})
255}

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