Research 026/04 counted two plain dmenu calls failing with dmenu installed nowhere. Measured, they are one line on each workstation: dunst's `dmenu = /usr/bin/dmenu -p dunst:`. Installing the package fixes both by existing; the line stays the dunst module's. No claim: node-launcher is not in the controller's seat table yet, and the module says so. Two Go tools: menu, shaped like that seat's verb (chosen line, index, typed, cancelled, timed out within 25 s), and session.
95 lines
3.2 KiB
Go
95 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// aMachine gives findSession a /proc and an X socket directory of the test's own.
|
|
func aMachine(t *testing.T, env map[string]string) (proc, sockets string) {
|
|
t.Helper()
|
|
root := t.TempDir()
|
|
proc, sockets = filepath.Join(root, "proc"), filepath.Join(root, "x11")
|
|
for _, d := range []string{proc, sockets} {
|
|
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
wasProc, wasX, wasEnv := procRoot, x11Sockets, getenv
|
|
procRoot, x11Sockets = proc, sockets
|
|
getenv = func(k string) string { return env[k] }
|
|
t.Cleanup(func() { procRoot, x11Sockets, getenv = wasProc, wasX, wasEnv })
|
|
return proc, sockets
|
|
}
|
|
|
|
func aProcess(t *testing.T, proc string, pid int, comm string, env ...string) {
|
|
t.Helper()
|
|
dir := filepath.Join(proc, strconv.Itoa(pid))
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = os.WriteFile(filepath.Join(dir, "comm"), []byte(comm+"\n"), 0o644)
|
|
_ = os.WriteFile(filepath.Join(dir, "environ"), []byte(strings.Join(env, "\x00")+"\x00"), 0o644)
|
|
}
|
|
|
|
func aSocket(t *testing.T, dir, name string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(filepath.Join(dir, name), nil, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestSessionTheWindowManagersDisplayAndCookieAreTheSessions(t *testing.T) {
|
|
proc, sockets := aMachine(t, map[string]string{"MESH_OPERATOR_HOME": "/home/op"})
|
|
aSocket(t, sockets, "X1")
|
|
aProcess(t, proc, 3, "bash", "DISPLAY=:9", "XAUTHORITY=/stale")
|
|
aProcess(t, proc, 4, "kitty", "DISPLAY=:1", "XAUTHORITY=/home/op/.Xauthority")
|
|
aProcess(t, proc, 5, "i3", "DISPLAY=:1.0", "XAUTHORITY=/home/op/.Xauthority")
|
|
aProcess(t, proc, 6, "sshd", "PATH=/bin")
|
|
s, err := findSession()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s.Display != ":1.0" || s.XAuthority != "/home/op/.Xauthority" || !strings.Contains(s.FoundBy, "i3") {
|
|
t.Fatalf("%+v: a display without a socket (:9) is skipped, and the window manager's is preferred", s)
|
|
}
|
|
if got := strings.Join(s.Env(), " "); got != "DISPLAY=:1.0 XAUTHORITY=/home/op/.Xauthority" {
|
|
t.Fatalf("env %s", got)
|
|
}
|
|
}
|
|
|
|
func TestSessionTheOnlySocketWithTheHomesCookieIsTheFallback(t *testing.T) {
|
|
home := t.TempDir()
|
|
_ = os.WriteFile(filepath.Join(home, ".Xauthority"), []byte("c"), 0o600)
|
|
_, sockets := aMachine(t, map[string]string{"MESH_OPERATOR_HOME": home})
|
|
aSocket(t, sockets, "X0")
|
|
s, err := findSession()
|
|
if err != nil || s.Display != ":0" || s.XAuthority != filepath.Join(home, ".Xauthority") || s.FoundBy != "socket" {
|
|
t.Fatalf("%+v %v", s, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionNoSessionIsSaidNotGuessed(t *testing.T) {
|
|
_, sockets := aMachine(t, map[string]string{})
|
|
if _, err := findSession(); err == nil || !strings.Contains(err.Error(), "no graphical session") {
|
|
t.Fatalf("none: %v", err)
|
|
}
|
|
aSocket(t, sockets, "X0")
|
|
aSocket(t, sockets, "X1")
|
|
if _, err := findSession(); err == nil || !strings.Contains(err.Error(), "2 X sockets") {
|
|
t.Fatalf("two: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSessionTheProcessesOwnDisplayComesFirst(t *testing.T) {
|
|
_, sockets := aMachine(t, map[string]string{"DISPLAY": ":2", "XAUTHORITY": "/a"})
|
|
aSocket(t, sockets, "X2")
|
|
s, err := findSession()
|
|
if err != nil || s.Display != ":2" || s.FoundBy != "environment" {
|
|
t.Fatalf("%+v %v", s, err)
|
|
}
|
|
}
|