detached aedc87d

test(xwayland): assert AttachWM is skipped when Xwayland exits before readiness

commit
aedc87dc6fe00c58eb7cac7d7dd5859ae463810d
author
brice · · 2026-10-22 10:58 UTC
parent
42dd2dd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +20 -7
M internal/adapters/xwayland/builtin.go +1 -1
@@ -75,7 +75,7 @@ func (builtinStarter) start(ctx context.Context, d *Display, stderr *logWriter)
7575 files[wm+1].Close()
7676 wr.Close()
7777 d.opts.Log.Info().Str("display", d.Name()).Int("pid", cmd.Process.Pid).Msg("Xwayland started")
78- // Detach the raw descriptors from os.File ownership before handing them
78+ // Detach the Wayland descriptor from os.File ownership before handing it
7979 // to the display (including its finalizers).
8080 if d.opts.Attach != nil {
8181 // Dup transfers an independent descriptor; closing the File wrapper below
@@ -75,7 +75,7 @@ func (builtinStarter) start(ctx context.Context, d *Display, stderr *logWriter)
75 files[wm+1].Close()75 files[wm+1].Close()
76 wr.Close()76 wr.Close()
77 d.opts.Log.Info().Str("display", d.Name()).Int("pid", cmd.Process.Pid).Msg("Xwayland started")77 d.opts.Log.Info().Str("display", d.Name()).Int("pid", cmd.Process.Pid).Msg("Xwayland started")
78- // Detach the raw descriptors from os.File ownership before handing them78+ // Detach the Wayland descriptor from os.File ownership before handing it
79 // to the display (including its finalizers).79 // to the display (including its finalizers).
80 if d.opts.Attach != nil {80 if d.opts.Attach != nil {
81 // Dup transfers an independent descriptor; closing the File wrapper below81 // Dup transfers an independent descriptor; closing the File wrapper below
M internal/adapters/xwayland/xwayland_test.go +19 -6
@@ -371,24 +371,29 @@ func TestWaitReady(t *testing.T) {
371371 }
372372
373373 // readyScript stands in for Xwayland: after a short delay it creates a marker
374-// file, reports readiness on the -displayfd descriptor, then runs tail.
375-func readyScript(t *testing.T, dir, tail string) (bin, marker string) {
374+// file and reports readiness on the -displayfd descriptor when ready is true,
375+// then runs tail.
376+func readyScript(t *testing.T, dir string, ready bool, tail string) (bin, marker string) {
376377 t.Helper()
377378 bin, marker = filepath.Join(dir, "xwayland"), filepath.Join(dir, "ready")
378379 script := "#!/bin/sh\nwhile [ $# -gt 0 ]; do [ \"$1\" = -displayfd ] && fd=$2; shift; done\n" +
379- "sleep 0.3\n: > " + marker + "\neval \"echo 1 >&$fd\"\n" + tail + "\n"
380+ "sleep 0.3\n"
381+ if ready {
382+ script += ": > " + marker + "\neval \"echo 1 >&$fd\"\n"
383+ }
384+ script += tail + "\n"
380385 if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {
381386 t.Fatal(err)
382387 }
383388 return bin, marker
384389 }
385390
386-func runBuiltin(t *testing.T, ctx context.Context, tail string, attachWM func(wmFD int)) (events []string) {
391+func runBuiltin(t *testing.T, ctx context.Context, ready bool, tail string, attachWM func(wmFD int)) (events []string) {
387392 t.Helper()
388393 opts := testOptions(t, "")
389394 opts.Backend = ports.XwaylandBuiltin
390395 var marker string
391- opts.Binary, marker = readyScript(t, opts.TmpDir, tail)
396+ opts.Binary, marker = readyScript(t, opts.TmpDir, ready, tail)
392397 exists := func() bool { _, err := os.Stat(marker); return err == nil }
393398 opts.Attach = func(wlFD int) {
394399 if exists() {
@@ -424,7 +429,7 @@ func TestBuiltinAttachWMAfterReadiness(t *testing.T) {
424429 t.Run(name, func(t *testing.T) {
425430 ctx, cancel := context.WithCancel(t.Context())
426431 defer cancel()
427- events := runBuiltin(t, ctx, tc.tail, func(wmFD int) {
432+ events := runBuiltin(t, ctx, true, tc.tail, func(wmFD int) {
428433 if _, err := unix.FcntlInt(uintptr(wmFD), unix.F_GETFD, 0); err != nil {
429434 t.Errorf("wmFD not open: %v", err)
430435 }
@@ -439,3 +444,11 @@ func TestBuiltinAttachWMAfterReadiness(t *testing.T) {
439444 })
440445 }
441446 }
447+
448+// A process that exits before reporting readiness never reaches AttachWM.
449+func TestBuiltinNoAttachWMWithoutReadiness(t *testing.T) {
450+ events := runBuiltin(t, t.Context(), false, "exit 1", func(int) {})
451+ if got := strings.Join(events, ","); got != "attach,returned" {
452+ t.Fatalf("events %s", got)
453+ }
454+}
@@ -371,24 +371,29 @@ func TestWaitReady(t *testing.T) {
371 }371 }
372 372
373 // readyScript stands in for Xwayland: after a short delay it creates a marker373 // readyScript stands in for Xwayland: after a short delay it creates a marker
374-// file, reports readiness on the -displayfd descriptor, then runs tail.374+// file and reports readiness on the -displayfd descriptor when ready is true,
375-func readyScript(t *testing.T, dir, tail string) (bin, marker string) {375+// then runs tail.
376+func readyScript(t *testing.T, dir string, ready bool, tail string) (bin, marker string) {
376 t.Helper()377 t.Helper()
377 bin, marker = filepath.Join(dir, "xwayland"), filepath.Join(dir, "ready")378 bin, marker = filepath.Join(dir, "xwayland"), filepath.Join(dir, "ready")
378 script := "#!/bin/sh\nwhile [ $# -gt 0 ]; do [ \"$1\" = -displayfd ] && fd=$2; shift; done\n" +379 script := "#!/bin/sh\nwhile [ $# -gt 0 ]; do [ \"$1\" = -displayfd ] && fd=$2; shift; done\n" +
379- "sleep 0.3\n: > " + marker + "\neval \"echo 1 >&$fd\"\n" + tail + "\n"380+ "sleep 0.3\n"
381+ if ready {
382+ script += ": > " + marker + "\neval \"echo 1 >&$fd\"\n"
383+ }
384+ script += tail + "\n"
380 if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {385 if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {
381 t.Fatal(err)386 t.Fatal(err)
382 }387 }
383 return bin, marker388 return bin, marker
384 }389 }
385 390
386-func runBuiltin(t *testing.T, ctx context.Context, tail string, attachWM func(wmFD int)) (events []string) {391+func runBuiltin(t *testing.T, ctx context.Context, ready bool, tail string, attachWM func(wmFD int)) (events []string) {
387 t.Helper()392 t.Helper()
388 opts := testOptions(t, "")393 opts := testOptions(t, "")
389 opts.Backend = ports.XwaylandBuiltin394 opts.Backend = ports.XwaylandBuiltin
390 var marker string395 var marker string
391- opts.Binary, marker = readyScript(t, opts.TmpDir, tail)396+ opts.Binary, marker = readyScript(t, opts.TmpDir, ready, tail)
392 exists := func() bool { _, err := os.Stat(marker); return err == nil }397 exists := func() bool { _, err := os.Stat(marker); return err == nil }
393 opts.Attach = func(wlFD int) {398 opts.Attach = func(wlFD int) {
394 if exists() {399 if exists() {
@@ -424,7 +429,7 @@ func TestBuiltinAttachWMAfterReadiness(t *testing.T) {
424 t.Run(name, func(t *testing.T) {429 t.Run(name, func(t *testing.T) {
425 ctx, cancel := context.WithCancel(t.Context())430 ctx, cancel := context.WithCancel(t.Context())
426 defer cancel()431 defer cancel()
427- events := runBuiltin(t, ctx, tc.tail, func(wmFD int) {432+ events := runBuiltin(t, ctx, true, tc.tail, func(wmFD int) {
428 if _, err := unix.FcntlInt(uintptr(wmFD), unix.F_GETFD, 0); err != nil {433 if _, err := unix.FcntlInt(uintptr(wmFD), unix.F_GETFD, 0); err != nil {
429 t.Errorf("wmFD not open: %v", err)434 t.Errorf("wmFD not open: %v", err)
430 }435 }
@@ -439,3 +444,11 @@ func TestBuiltinAttachWMAfterReadiness(t *testing.T) {
439 })444 })
440 }445 }
441 }446 }
447+
448+// A process that exits before reporting readiness never reaches AttachWM.
449+func TestBuiltinNoAttachWMWithoutReadiness(t *testing.T) {
450+ events := runBuiltin(t, t.Context(), false, "exit 1", func(int) {})
451+ if got := strings.Join(events, ","); got != "attach,returned" {
452+ t.Fatalf("events %s", got)
453+ }
454+}

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