1package launcher
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "reflect"
8 "slices"
9 "testing"
10 "time"
11
12 "github.com/bnema/neferwl/internal/adapters/logging"
13 "github.com/bnema/neferwl/internal/ports"
14)
15
16func TestChildEnv(t *testing.T) {
17 got := ChildEnv([]string{"TERM=x", "DISPLAY=:0", "WAYLAND_SOCKET=4", "WAYLAND_DISPLAY=old", "HOME=/home/me", "PATH=/bin", "LC_ALL=C", "XDG_RUNTIME_DIR=old", "XCURSOR_THEME=Adwaita", "XCURSOR_PATH=/icons", "SECRET=bad", "NOTIFY_SOCKET=/tmp/notify", "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/me/bus"}, "wayland-7", "/run/me", 24)
18 want := []string{"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/me/bus", "HOME=/home/me", "LC_ALL=C", "PATH=/bin", "WAYLAND_DISPLAY=wayland-7", "XCURSOR_PATH=/icons", "XCURSOR_SIZE=24", "XCURSOR_THEME=Adwaita", "XDG_CURRENT_DESKTOP=neferwl", "XDG_RUNTIME_DIR=/run/me", "XDG_SESSION_TYPE=wayland"}
19 if !reflect.DeepEqual(got, want) {
20 t.Fatalf("got %v want %v", got, want)
21 }
22 // The compositor's cursor size wins over the inherited one, valid or not.
23 for _, size := range []string{"", "32", "abc", "1000"} {
24 env := ChildEnv([]string{"XCURSOR_SIZE=" + size}, "w", "/r", 48)
25 if !slices.Contains(env, "XCURSOR_SIZE=48") {
26 t.Errorf("XCURSOR_SIZE=%q: %v", size, env)
27 }
28 }
29}
30
31func TestRun(t *testing.T) {
32 dir := t.TempDir()
33 file := filepath.Join(dir, "result")
34 ctx, cancel := context.WithCancel(context.Background())
35 done := make(chan error, 1)
36 reqs := make(chan ports.SpawnRequest, 3)
37 go func() {
38 done <- New([]string{"PATH=/bin", "FILE=" + file}, logging.For(ctx, "launcher")).Run(ctx, reqs)
39 }()
40 reqs <- ports.SpawnRequest{}
41 reqs <- ports.SpawnRequest{Argv: []string{"nonexistent-neferwl-binary"}}
42 // Env adds to the child environment (slot tokens).
43 reqs <- ports.SpawnRequest{Argv: []string{"sh", "-c", "echo ok$NEFERWL_SLOT > $FILE"}, Env: []string{"NEFERWL_SLOT=7"}}
44 deadline := time.After(3 * time.Second)
45 for {
46 data, err := os.ReadFile(file)
47 if err == nil {
48 if string(data) != "ok7\n" {
49 t.Fatalf("output: %q", data)
50 }
51 break
52 }
53 select {
54 case <-deadline:
55 t.Fatal("child did not write")
56 case <-time.After(10 * time.Millisecond):
57 }
58 }
59 cancel()
60 select {
61 case err := <-done:
62 if err != nil {
63 t.Fatal(err)
64 }
65 case <-time.After(time.Second):
66 t.Fatal("launcher did not stop")
67 }
68}
69
70func TestChildPath(t *testing.T) {
71 dir := t.TempDir()
72 name := "neferwl-child-only-script"
73 result := filepath.Join(dir, "result")
74 if err := os.WriteFile(filepath.Join(dir, name), []byte("#!/bin/sh\nprintf ok > \"$1\"\n"), 0700); err != nil {
75 t.Fatal(err)
76 }
77 ctx, cancel := context.WithCancel(context.Background())
78 defer cancel()
79 reqs := make(chan ports.SpawnRequest, 1)
80 done := make(chan error, 1)
81 go func() { done <- New([]string{"PATH=" + dir}, logging.For(ctx, "launcher")).Run(ctx, reqs) }()
82 reqs <- ports.SpawnRequest{Argv: []string{name, result}}
83 deadline := time.After(3 * time.Second)
84 for {
85 if data, err := os.ReadFile(result); err == nil {
86 if string(data) != "ok" {
87 t.Fatalf("result: %q", data)
88 }
89 break
90 }
91 select {
92 case <-deadline:
93 t.Fatal("child not started")
94 case <-time.After(10 * time.Millisecond):
95 }
96 }
97 cancel()
98 select {
99 case <-done:
100 case <-time.After(time.Second):
101 t.Fatal("launcher hung")
102 }
103}