test(xwayland): assert AttachWM is skipped when Xwayland exits before readiness
- commit
- 4eada3bb27cc06f13b2fc5664ffe23001fd87d14
- author
- brice · · 2026-10-22 10:58 UTC
- committer
- brice · · 2026-10-22 17:54 UTC
- parent
- 04fea8d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) | |||
| 75 | 75 | files[wm+1].Close() | |
| 76 | 76 | wr.Close() | |
| 77 | 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 them | |
| 78 | + | // Detach the Wayland descriptor from os.File ownership before handing it | |
| 79 | 79 | // to the display (including its finalizers). | |
| 80 | 80 | if d.opts.Attach != nil { | |
| 81 | 81 | // 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 them | 78 | + | // 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 below | 81 | // 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) { | |||
| 371 | 371 | } | |
| 372 | 372 | ||
| 373 | 373 | // 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) { | |
| 376 | 377 | t.Helper() | |
| 377 | 378 | bin, marker = filepath.Join(dir, "xwayland"), filepath.Join(dir, "ready") | |
| 378 | 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 | 385 | if err := os.WriteFile(bin, []byte(script), 0o755); err != nil { | |
| 381 | 386 | t.Fatal(err) | |
| 382 | 387 | } | |
| 383 | 388 | 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 | 392 | t.Helper() | |
| 388 | 393 | opts := testOptions(t, "") | |
| 389 | 394 | opts.Backend = ports.XwaylandBuiltin | |
| 390 | 395 | var marker string | |
| 391 | - | opts.Binary, marker = readyScript(t, opts.TmpDir, tail) | |
| 396 | + | opts.Binary, marker = readyScript(t, opts.TmpDir, ready, tail) | |
| 392 | 397 | exists := func() bool { _, err := os.Stat(marker); return err == nil } | |
| 393 | 398 | opts.Attach = func(wlFD int) { | |
| 394 | 399 | if exists() { | |
| @@ -424,7 +429,7 @@ func TestBuiltinAttachWMAfterReadiness(t *testing.T) { | |||
| 424 | 429 | t.Run(name, func(t *testing.T) { | |
| 425 | 430 | ctx, cancel := context.WithCancel(t.Context()) | |
| 426 | 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 | 433 | if _, err := unix.FcntlInt(uintptr(wmFD), unix.F_GETFD, 0); err != nil { | |
| 429 | 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 | + | } | |
| @@ -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 marker | 373 | // 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, marker | 388 | 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.XwaylandBuiltin | 394 | opts.Backend = ports.XwaylandBuiltin | ||
| 390 | var marker string | 395 | 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 | + | } | |||