mesh/merge-gate out of date: judged against trunk aee97bf8ad42, the trunk is now 7443938fef69; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk aee97bf8ad42, the trunk is now 7443938fef69; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
clipmenud is declared as the operator's user unit, enabled and running, with a launcher that finds the display. Copy reads the clipboard back and fails with the reason. History and paste answer counts, times and lengths, never content.
143 lines
5.6 KiB
Go
143 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// clipmenu's shape (novox/hq ADR 0208, research 026/04): it claims node-clipboard serving history
|
|
// and copy, requires the X display on its own machine, replaces greenclip, starts its daemon once
|
|
// from the session's start, and binds its menu as an i3 drop-in through the launcher's dmenu command.
|
|
|
|
func TestItClaimsTheClipboardSeatServingHistoryAndCopy(t *testing.T) {
|
|
m := readManifest(t)
|
|
if m.Module != "clipmenu" || m.Seats != nil {
|
|
t.Fatalf("module %q declares seats %v", m.Module, m.Seats)
|
|
}
|
|
if !reflect.DeepEqual(m.Claims, []claim{{Name: "node-clipboard", Scope: "node", Serves: []string{"history", "copy"}}}) {
|
|
t.Fatalf("claims: %+v", m.Claims)
|
|
}
|
|
if !reflect.DeepEqual(m.Requires, []string{"x11-display"}) {
|
|
t.Fatalf("requires: %v", m.Requires)
|
|
}
|
|
present, absent := m.packages()
|
|
if !reflect.DeepEqual(present, []string{"clipmenu"}) || !reflect.DeepEqual(absent, []string{"rofi-greenclip"}) {
|
|
t.Fatalf("packages: %v, absent %v", present, absent)
|
|
}
|
|
}
|
|
|
|
func TestTheDaemonIsTheOperatorsUserUnitEnabledAndRunning(t *testing.T) {
|
|
m := readManifest(t)
|
|
if len(m.Shell) != 0 {
|
|
t.Fatalf("a second start from the session: %+v", m.Shell)
|
|
}
|
|
svc := m.resource(t, "daemon")
|
|
want := map[string]any{"id": "daemon", "type": "service", "unit": "clipmenud.service", "scope": "user",
|
|
"user": "${machine:account}", "state": "running", "boot": "enabled", "reload-on": nil}
|
|
for k, v := range want {
|
|
if k != "reload-on" && svc[k] != v {
|
|
t.Fatalf("daemon %s = %v, want %v: %v", k, svc[k], v, svc)
|
|
}
|
|
}
|
|
dropin := m.resource(t, "daemon-dropin")
|
|
content, _ := dropin["content"].(string)
|
|
if dropin["path"] != "${machine:account-home}/.config/systemd/user/clipmenud.service.d/mesh.conf" ||
|
|
dropin["owner"] != "${machine:account}" ||
|
|
!strings.Contains(content, "ExecStart=/usr/local/lib/mesh-clipmenu/clipmenud-session") ||
|
|
strings.Contains(content, "greenclip") {
|
|
t.Fatalf("drop-in: %v", dropin)
|
|
}
|
|
launcher, _ := m.resource(t, "launcher")["content"].(string)
|
|
if !strings.Contains(launcher, "DISPLAY") || !strings.Contains(launcher, "/tmp/.X11-unix") ||
|
|
!strings.Contains(launcher, `PATH="/usr/local/lib/mesh-clipmenu:$PATH"`) || !strings.Contains(launcher, "exec clipmenud") {
|
|
t.Fatalf("launcher: %q", launcher)
|
|
}
|
|
// Waits for the display instead of exiting into the start limit; DISPLAY from the manager wins.
|
|
if !strings.Contains(launcher, "until [ -n \"$DISPLAY\" ]") || !strings.Contains(launcher, "sleep 1") ||
|
|
strings.Contains(launcher, "exit 1") || strings.Contains(launcher, "exit 0") {
|
|
t.Fatalf("the launcher must wait, not exit: %q", launcher)
|
|
}
|
|
if !strings.Contains(content, "StartLimitIntervalSec=0") || !strings.Contains(content, "RestartSec=2s") {
|
|
t.Fatalf("drop-in lacks the safety net: %q", content)
|
|
}
|
|
for k, v := range m.Environment.Variables {
|
|
if !strings.Contains(content, "Environment="+k+"="+v) {
|
|
t.Fatalf("the unit lacks %s=%s", k, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestItsSettingsAreEnvironmentAndItsMenuIsTheLaunchersDmenu(t *testing.T) {
|
|
m := readManifest(t)
|
|
if !reflect.DeepEqual(m.Environment.Variables, map[string]string{"CM_SELECTIONS": "clipboard", "CM_MAX_CLIPS": "500", "CM_HISTLENGTH": "15"}) {
|
|
t.Fatalf("%v", m.Environment.Variables)
|
|
}
|
|
if _, set := m.Environment.Variables["CM_LAUNCHER"]; set {
|
|
t.Fatal("the launcher is clipmenu's default, dmenu: the seat's command")
|
|
}
|
|
m.i3LinesAreSource(t, "files/i3/50-clipmenu.conf")
|
|
if c := m.i3Lines(); !strings.Contains(c, "bindsym $mod+period exec --no-startup-id clipmenu") {
|
|
t.Fatalf("%s", c)
|
|
}
|
|
}
|
|
|
|
func TestTheToolsAgreeWithTheManifest(t *testing.T) {
|
|
m := readManifest(t)
|
|
checkTheToolsAgree(t, m)
|
|
checkNoSecretsOrInstallationNames(t)
|
|
}
|
|
|
|
// Run for real: DISPLAY from the manager is used at once; several sockets and no DISPLAY wait.
|
|
func TestTheLauncherUsesTheManagersDisplayAndWaitsOtherwise(t *testing.T) {
|
|
m := readManifest(t)
|
|
script, _ := m.resource(t, "launcher")["content"].(string)
|
|
dir := t.TempDir()
|
|
file := dir + "/launcher"
|
|
// clipmenud and the socket directory are faked: the script's own paths are rewritten.
|
|
body := strings.ReplaceAll(script, "/tmp/.X11-unix", dir+"/x11")
|
|
body = strings.Replace(body, "exec clipmenud", `echo "DISPLAY=$DISPLAY"`, 1)
|
|
if err := os.WriteFile(file, []byte(body), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.MkdirAll(dir+"/x11", 0o755)
|
|
fakeBin := dir + "/bin"
|
|
os.MkdirAll(fakeBin, 0o755)
|
|
// the manager's environment: empty until the file appears
|
|
os.WriteFile(fakeBin+"/systemctl", []byte("#!/bin/sh\n[ -f \""+dir+"/env\" ] && cat \""+dir+"/env\"\nexit 0\n"), 0o755)
|
|
run := func(env ...string) (string, bool) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2500*time.Millisecond)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(ctx, "sh", file)
|
|
cmd.Env = append([]string{"PATH=" + fakeBin + ":" + os.Getenv("PATH"), "HOME=" + dir}, env...)
|
|
out, _ := cmd.Output()
|
|
return strings.TrimSpace(string(out)), ctx.Err() == nil
|
|
}
|
|
if out, ok := run("DISPLAY=:7"); !ok || out != "DISPLAY=:7" {
|
|
t.Fatalf("with DISPLAY: %q %v", out, ok)
|
|
}
|
|
if _, ok := run(); ok {
|
|
t.Fatal("it exited with no X server")
|
|
}
|
|
for _, n := range []string{"X0", "X1"} {
|
|
l, _ := net.Listen("unix", dir+"/x11/"+n)
|
|
defer l.Close()
|
|
}
|
|
if _, ok := run(); ok {
|
|
t.Fatal("it chose one of two servers")
|
|
}
|
|
// the session hands the manager its display while the launcher waits
|
|
go func() {
|
|
time.Sleep(1200 * time.Millisecond)
|
|
os.WriteFile(dir+"/env", []byte("DISPLAY=:1\nOTHER=x\n"), 0o644)
|
|
}()
|
|
if out, ok := run(); !ok || out != "DISPLAY=:1" {
|
|
t.Fatalf("the display that arrived while waiting: %q %v", out, ok)
|
|
}
|
|
}
|