Owns one dunstrc (the laptop's, in the interface face, its menu on the seat's dmenu command) and the dunstrc.d directory for other modules' rules; D-Bus starts it, so nothing else does. Go tools over the session bus.
175 lines
6.0 KiB
Go
175 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// fakeMachine points the session finder at a temporary /proc, /run/user and X socket directory, with
|
|
// none of the test process's own session variables, and gives back the root.
|
|
func fakeMachine(t *testing.T) string {
|
|
t.Helper()
|
|
root := t.TempDir()
|
|
procRoot, runUserDir, x11Sockets = filepath.Join(root, "proc"), filepath.Join(root, "run-user"), filepath.Join(root, "x11")
|
|
for _, d := range []string{procRoot, runUserDir, x11Sockets} {
|
|
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, k := range sessionKeys {
|
|
t.Setenv(k, "")
|
|
}
|
|
t.Setenv("MESH_OPERATOR_HOME", filepath.Join(root, "home"))
|
|
t.Cleanup(func() { procRoot, runUserDir, x11Sockets = "/proc", "/run/user", "/tmp/.X11-unix" })
|
|
return root
|
|
}
|
|
|
|
func fakeProcess(t *testing.T, pid int, comm string, env ...string) {
|
|
t.Helper()
|
|
dir := filepath.Join(procRoot, strconv.Itoa(pid))
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "comm"), []byte(comm+"\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "environ"), []byte(strings.Join(env, "\x00")+"\x00"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestTheSessionIsReadFromTheWindowManagerBeforeAnyOtherProcess(t *testing.T) {
|
|
fakeMachine(t)
|
|
fakeProcess(t, 900, "xterm", "DISPLAY=:9", "XAUTHORITY=/elsewhere")
|
|
fakeProcess(t, 100, "i3", "DISPLAY=:1", "XAUTHORITY=/home/op/.Xauthority",
|
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus", "XDG_SESSION_ID=3", "SECRET_TOKEN=never-copied")
|
|
fakeProcess(t, 50, "bash", "PATH=/usr/bin")
|
|
s, err := findSession()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s.Display != ":1" || s.XAuthority != "/home/op/.Xauthority" || s.SessionID != "3" || !strings.Contains(s.From, "i3 (pid 100)") {
|
|
t.Fatalf("the window manager's environment: %+v", s)
|
|
}
|
|
for _, kv := range s.Env() {
|
|
if strings.HasPrefix(kv, "SECRET_TOKEN=") {
|
|
t.Fatal("a variable of the session process that is not a session variable was handed on")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAnyProcessCarryingADisplayServesWhenTheWindowManagerIsNotFound(t *testing.T) {
|
|
fakeMachine(t)
|
|
fakeProcess(t, 10, "firefox", "DISPLAY=:0")
|
|
fakeProcess(t, 20, "firefox", "DISPLAY=:2")
|
|
s, err := findSession()
|
|
if err != nil || s.Display != ":2" {
|
|
t.Fatalf("the newest of two equals: %+v, %v", s, err)
|
|
}
|
|
}
|
|
|
|
func TestNoSessionIsAClearAnswerNotAGuess(t *testing.T) {
|
|
fakeMachine(t)
|
|
fakeProcess(t, 10, "sshd", "PATH=/usr/bin")
|
|
_, err := findSession()
|
|
if !errors.Is(err, ErrNoSession) || !strings.Contains(err.Error(), "logged in to the desktop") {
|
|
t.Fatalf("no session: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOneXSocketAndTheAccountsAuthorityFileAreASession(t *testing.T) {
|
|
root := fakeMachine(t)
|
|
if err := os.WriteFile(filepath.Join(x11Sockets, "X0"), nil, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(root, "home"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "home", ".Xauthority"), nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s, err := findSession()
|
|
if err != nil || s.Display != ":0" || !strings.HasSuffix(s.XAuthority, "/home/.Xauthority") {
|
|
t.Fatalf("socket and authority: %+v, %v", s, err)
|
|
}
|
|
}
|
|
|
|
func TestTheBusIsTheAccountsRuntimeDirectoryWhenNoProcessNamesIt(t *testing.T) {
|
|
fakeMachine(t)
|
|
runtime := filepath.Join(runUserDir, strconv.Itoa(os.Getuid()))
|
|
if _, err := findBus(); !errors.Is(err, ErrNoBus) {
|
|
t.Fatalf("no runtime directory is no bus: %v", err)
|
|
}
|
|
if err := os.MkdirAll(runtime, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(runtime, "bus"), nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s, err := findBus()
|
|
if err != nil || s.Bus != "unix:path="+filepath.Join(runtime, "bus") || s.RuntimeDir != runtime {
|
|
t.Fatalf("bus: %+v, %v", s, err)
|
|
}
|
|
env := strings.Join(s.Env(), "\n")
|
|
if !strings.Contains(env, "XDG_RUNTIME_DIR="+runtime) || !strings.Contains(env, "DBUS_SESSION_BUS_ADDRESS=unix:path=") {
|
|
t.Fatalf("the bus is handed on: %s", env)
|
|
}
|
|
}
|
|
|
|
func TestACommandIsBoundedAndANonZeroExitIsAResult(t *testing.T) {
|
|
fakeMachine(t)
|
|
s := Session{}
|
|
r, err := s.run(5*time.Second, "in", "sh", "-c", "cat; echo err >&2; exit 3")
|
|
if err != nil || r.Stdout != "in" || r.Code != 3 || strings.TrimSpace(r.Stderr) != "err" {
|
|
t.Fatalf("result: %+v, %v", r, err)
|
|
}
|
|
start := time.Now()
|
|
if _, err := s.run(200*time.Millisecond, "", "sh", "-c", "sleep 30 & sleep 30"); err == nil || time.Since(start) > 5*time.Second {
|
|
t.Fatalf("a command past its time is ended with what it started: %v after %s", err, time.Since(start))
|
|
}
|
|
if _, err := s.run(time.Second, "", "no-such-program-here"); err == nil || !strings.Contains(err.Error(), "not installed") {
|
|
t.Fatalf("a missing program: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDetachAsksTheAccountsServiceManagerWithTheSessionsDisplay(t *testing.T) {
|
|
fakeMachine(t)
|
|
bin := fakeBinaries(t, map[string]string{
|
|
"systemctl": `echo "systemctl $*" >> "$LOG"`,
|
|
"systemd-run": `echo "systemd-run $*" >> "$LOG"`,
|
|
})
|
|
log := filepath.Join(bin, "log")
|
|
t.Setenv("LOG", log)
|
|
s := Session{Display: ":1", XAuthority: "/x", RuntimeDir: "/run/user/1"}
|
|
if err := s.detach("picom-session", "picom", "--config", "/c"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ := os.ReadFile(log)
|
|
want := "systemctl --user stop picom-session.service\n" +
|
|
"systemd-run --user --collect --quiet --unit=picom-session --setenv=DISPLAY=:1 --setenv=XAUTHORITY=/x -- picom --config /c\n"
|
|
if string(got) != want {
|
|
t.Fatalf("detach ran:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
if err := (Session{}).detach("x", "y"); !errors.Is(err, ErrNoBus) {
|
|
t.Fatalf("no runtime directory: %v", err)
|
|
}
|
|
}
|
|
|
|
// fakeBinaries puts shell scripts named for programs first on PATH, and answers their directory.
|
|
func fakeBinaries(t *testing.T, scripts map[string]string) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
for name, body := range scripts {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte("#!/bin/sh\n"+body+"\n"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
return dir
|
|
}
|