The workstations' XDG conventions had no owner: xdg-user-dirs-update rewrote the folders file at every login, both machines' default-application lists named an editor neither has, and the laptop kept two dead links of the retired predecessor. The module adopts the operator's folders (three as settings, as the machines differ), disables the update so it cannot undo the mesh's file, and writes the machine's own mimeapps list, the last one read, so the person's choices in their own list always win. Autostart is listed, not owned: each entry is its application's module's.
430 lines
18 KiB
Go
430 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const home = "/home/op"
|
|
|
|
// fake is a machine with these files under a temporary root; a value starting with "->" is a
|
|
// symbolic link to the rest, and one starting with "#!" is an executable.
|
|
func fake(t *testing.T, files map[string]string) *Machine {
|
|
t.Helper()
|
|
root := t.TempDir()
|
|
for p, c := range files {
|
|
full := filepath.Join(root, p)
|
|
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
switch {
|
|
case strings.HasPrefix(c, "->"):
|
|
if err := os.Symlink(strings.TrimPrefix(c, "->"), full); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case c == "<dir>":
|
|
if err := os.MkdirAll(full, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
default:
|
|
mode := os.FileMode(0o644)
|
|
if strings.HasPrefix(c, "#!") {
|
|
mode = 0o755
|
|
}
|
|
if err := os.WriteFile(full, []byte(c), mode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
return &Machine{Root: root, Home: home, UID: os.Getuid(), Desktop: "i3", Timeout: time.Second,
|
|
Run: func(context.Context, []string, string, ...string) (string, error) { return "", nil }}
|
|
}
|
|
|
|
func app(name, exec, mime string) string {
|
|
s := "[Desktop Entry]\nType=Application\nName=" + name + "\nExec=" + exec + "\n"
|
|
if mime != "" {
|
|
s += "MimeType=" + mime + "\n"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// The desktop's folders as found on 2026-10-05, in the module's file with that machine's settings.
|
|
func desktopUserDirs(t *testing.T) string {
|
|
raw, err := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.dirs"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := strings.NewReplacer("${setting:desktop-dir}", "$HOME/", "${setting:publicshare-dir}", "$HOME/", "${setting:videos-dir}", "$HOME/")
|
|
return r.Replace(string(raw))
|
|
}
|
|
|
|
func TestTheFoldersAreReadAsXdgUserDirResolvesThem(t *testing.T) {
|
|
conf, _ := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.conf"))
|
|
m := fake(t, map[string]string{
|
|
home + "/.config/user-dirs.dirs": desktopUserDirs(t),
|
|
home + "/.config/user-dirs.conf": string(conf),
|
|
home + "/.config/user-dirs.locale": "en_US\n",
|
|
"/etc/xdg/user-dirs.conf": "enabled=True\n",
|
|
"/etc/xdg/user-dirs.defaults": "# c\nDESKTOP=Desktop\nDOWNLOAD=Downloads\nPROJECTS=Projects\nNEWTHING=New\n",
|
|
home + "/Desktop": "a file where the folder would be\n",
|
|
home + "/Downloads": "<dir>",
|
|
home + "/projects": "<dir>",
|
|
})
|
|
a := m.UserDirs()
|
|
if !a.Meshs || a.Locale != "en_US" || a.Update.Enabled || a.Update.DecidedBy != "~/.config/user-dirs.conf" {
|
|
t.Fatalf("%+v", a)
|
|
}
|
|
got := map[string]UserDir{}
|
|
for _, d := range a.Dirs {
|
|
got[d.Name] = d
|
|
}
|
|
if len(a.Dirs) != 9 {
|
|
t.Fatalf("%d folders: %+v", len(a.Dirs), a.Dirs)
|
|
}
|
|
if d := got["DESKTOP"]; !d.Home || d.Path != "~" || d.Default != "~/Desktop" {
|
|
t.Errorf("DESKTOP: %+v", d)
|
|
}
|
|
if d := got["DOWNLOAD"]; d.Home || !d.Exists || d.Path != "~/Downloads" {
|
|
t.Errorf("DOWNLOAD: %+v", d)
|
|
}
|
|
if d := got["PROJECTS"]; d.Path != "~/projects" || !d.Exists || d.Default != "~/Projects" {
|
|
t.Errorf("PROJECTS: %+v", d)
|
|
}
|
|
if d := got["MUSIC"]; d.Exists || d.Kind != "absent" {
|
|
t.Errorf("MUSIC: %+v", d)
|
|
}
|
|
if !reflect.DeepEqual(a.Missing, []string{"NEWTHING"}) {
|
|
t.Errorf("defaults without a line: %v", a.Missing)
|
|
}
|
|
}
|
|
|
|
func TestAnUpdateLeftEnabledIsSaid(t *testing.T) {
|
|
m := fake(t, map[string]string{"/etc/xdg/user-dirs.conf": "# c\nenabled=True\nfilename_encoding=UTF-8\n"})
|
|
a := m.UserDirs()
|
|
if !a.Update.Enabled || a.Update.DecidedBy != "/etc/xdg/user-dirs.conf" || !strings.Contains(a.Update.Note, "rewrites the file") {
|
|
t.Fatalf("%+v", a.Update)
|
|
}
|
|
}
|
|
|
|
func TestATypeIsAMimeTypeOrAScheme(t *testing.T) {
|
|
for in, want := range map[string]string{
|
|
"https": "x-scheme-handler/https", "mailto:": "x-scheme-handler/mailto", "http://": "x-scheme-handler/http",
|
|
"text/html": "text/html", "x-scheme-handler/tg": "x-scheme-handler/tg", "application/xhtml+xml": "application/xhtml+xml",
|
|
} {
|
|
if got, err := Normalise(in); err != nil || got != want {
|
|
t.Errorf("%s: %s %v", in, got, err)
|
|
}
|
|
}
|
|
for _, bad := range []string{"", "text/html; rm -rf", "../x", "Text Html"} {
|
|
if _, err := Normalise(bad); err == nil {
|
|
t.Errorf("%q accepted", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheProgramIsFoundBehindLaunchers(t *testing.T) {
|
|
// Exec lines found on the workstations.
|
|
for exec, want := range map[string]string{
|
|
`env APPIMAGELAUNCHER_DISABLE=1 DESKTOPINTEGRATION=0 /usr/bin/Plexamp.AppImage --disable-seccomp-filter-sandbox`: "/usr/bin/Plexamp.AppImage",
|
|
`sh -c "/opt/forticlient/gui/FortiClient >/dev/null 2>&1"`: "/opt/forticlient/gui/FortiClient",
|
|
`zsh -c 'dockly'`: "dockly",
|
|
`"/opt/claude-code/bin/claude" --handle-uri %u`: "/opt/claude-code/bin/claude",
|
|
`env LIBGL_ALWAYS_SOFTWARE=1 Telegram -- %U`: "Telegram",
|
|
`/usr/bin/slack --gtk-version=3 -s %U`: "/usr/bin/slack",
|
|
`"/path with space/Fleet" %u`: "/path with space/Fleet",
|
|
`steam steam://rungameid/427520`: "steam",
|
|
`xterm -fa monospace -fs 11 -e glow -p %f`: "xterm",
|
|
``: "",
|
|
} {
|
|
if got := Program(exec); got != want {
|
|
t.Errorf("%s: %q, want %q", exec, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestADuplicateGroupOrKeyIsReportedNotMerged(t *testing.T) {
|
|
k := ParseKeyFile("# >>> mesh >>>\n[Default Applications]\nx=a.desktop\n# <<<\n\n[Default Applications]\nx=b.desktop\ny=c\n[Added Associations]\nz=d;\nstray\n")
|
|
if !reflect.DeepEqual(k.DupGroups, []string{"Default Applications"}) || !reflect.DeepEqual(k.DupKeys, []string{"Default Applications: x"}) {
|
|
t.Fatalf("%+v", k)
|
|
}
|
|
if k.Groups["Default Applications"]["x"] != "a.desktop" || k.Groups["Default Applications"]["y"] != "c" || len(k.Unparsable) != 1 {
|
|
t.Fatalf("%+v", k)
|
|
}
|
|
}
|
|
|
|
// A machine with the mesh's list, the person's list, and some applications.
|
|
func mimeMachine(t *testing.T, extra map[string]string) *Machine {
|
|
mesh, err := os.ReadFile(filepath.Join("..", "..", "config", "mimeapps.list"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
files := map[string]string{
|
|
MeshList: string(mesh),
|
|
home + "/.config/mimeapps.list": "[Default Applications]\ntext/plain=code-oss.desktop;\ninode/directory=xplr.desktop\nx-scheme-handler/https=gone.desktop;chromium.desktop;\n",
|
|
"/usr/share/applications/firefox.desktop": app("Firefox", "/usr/lib/firefox/firefox %u", "text/html;x-scheme-handler/https;"),
|
|
"/usr/share/applications/chromium.desktop": app("Chromium", "/usr/bin/chromium %U", "x-scheme-handler/https;"),
|
|
"/usr/share/applications/thunar.desktop": app("Thunar", "thunar %F", "inode/directory;"),
|
|
home + "/.local/share/applications/xplr.desktop": app("xplr", "xplr", "inode/directory;"),
|
|
home + "/.local/share/applications/fleet.desktop": app("Fleet", `"/home/op/fleet/bin/Fleet" %u`, "text/plain;"),
|
|
"/usr/lib/firefox/firefox": "#!/bin/sh\n",
|
|
"/usr/bin/chromium": "#!/bin/sh\n",
|
|
"/usr/bin/thunar": "#!/bin/sh\n",
|
|
}
|
|
for k, v := range extra {
|
|
files[k] = v
|
|
}
|
|
return fake(t, files)
|
|
}
|
|
|
|
func TestTheDefaultIsDecidedInTheSpecificationsOrder(t *testing.T) {
|
|
m := mimeMachine(t, nil)
|
|
// The person's list names an application that is gone, then one that is installed.
|
|
if d := m.Decide("x-scheme-handler/https"); d.ID != "chromium.desktop" || d.File != "~/.config/mimeapps.list" ||
|
|
!reflect.DeepEqual(d.Skipped, []string{"gone.desktop (~/.config/mimeapps.list): not installed"}) {
|
|
t.Fatalf("%+v", d)
|
|
}
|
|
// The person's choice wins over the mesh's.
|
|
if d := m.Decide("inode/directory"); d.ID != "xplr.desktop" {
|
|
t.Fatalf("%+v", d)
|
|
}
|
|
// What the person has not chosen, the mesh's list answers.
|
|
if d := m.Decide("text/html"); d.ID != "firefox.desktop" || d.File != MeshList {
|
|
t.Fatalf("%+v", d)
|
|
}
|
|
// Nothing installed is named: no list decides.
|
|
if d := m.Decide("text/plain"); d.ID != "" || len(d.Skipped) != 1 {
|
|
t.Fatalf("%+v", d)
|
|
}
|
|
// The desktop's own list comes first of all.
|
|
m2 := mimeMachine(t, map[string]string{home + "/.config/i3-mimeapps.list": "[Default Applications]\ninode/directory=thunar.desktop\n"})
|
|
if d := m2.Decide("inode/directory"); d.ID != "thunar.desktop" || d.File != "~/.config/i3-mimeapps.list" {
|
|
t.Fatalf("%+v", d)
|
|
}
|
|
}
|
|
|
|
// recorded answers xdg-mime and gio as they answered on the laptop on 2026-10-05.
|
|
func recorded(calls *[]string) Runner {
|
|
return func(_ context.Context, env []string, name string, args ...string) (string, error) {
|
|
*calls = append(*calls, name+" "+strings.Join(args, " "))
|
|
key := name + " " + strings.Join(args, " ")
|
|
switch key {
|
|
case "xdg-mime query default audio/mpeg":
|
|
return "audacity.desktop\n", nil
|
|
case "gio mime audio/mpeg":
|
|
return "Default application for “audio/mpeg”: mpv.desktop\nRegistered applications:\n\tmpv.desktop\n\taudacity.desktop\nRecommended applications:\n\tmpv.desktop\n\taudacity.desktop\n", nil
|
|
case "xdg-mime query default x-scheme-handler/https":
|
|
return "chromium.desktop\n", nil
|
|
case "gio mime x-scheme-handler/https":
|
|
return "Default application for “x-scheme-handler/https”: chromium.desktop\nRegistered applications:\n\tchromium.desktop\n", nil
|
|
case "xdg-mime query default x-scheme-handler/nothing-here":
|
|
return "", nil
|
|
case "gio mime x-scheme-handler/nothing-here":
|
|
return "No default applications for “x-scheme-handler/nothing-here”\n", nil
|
|
}
|
|
return "", nil
|
|
}
|
|
}
|
|
|
|
func TestTwoReadersThatDisagreeAreSaid(t *testing.T) {
|
|
var calls []string
|
|
m := mimeMachine(t, map[string]string{
|
|
"/usr/share/applications/audacity.desktop": app("Audacity", "audacity %F", "audio/mpeg;"),
|
|
"/usr/share/applications/mpv.desktop": app("mpv", "mpv -- %U", "audio/mpeg;video/mp4;"),
|
|
"/usr/bin/audacity": "#!/bin/sh\n",
|
|
})
|
|
m.Run = recorded(&calls)
|
|
h := m.Handle("audio/mpeg")
|
|
if h.XdgOpen != "audacity.desktop" || h.Gio != "mpv.desktop" || !h.Disagree || h.App == nil || !h.App.Found {
|
|
t.Fatalf("%+v", h)
|
|
}
|
|
if !strings.Contains(h.Decision.File, "no list names") {
|
|
t.Fatalf("%+v", h.Decision)
|
|
}
|
|
none := m.Handle("x-scheme-handler/nothing-here")
|
|
if none.XdgOpen != "" || none.Gio != "" || none.Problem != "nothing opens it" {
|
|
t.Fatalf("%+v", none)
|
|
}
|
|
}
|
|
|
|
func TestTheEnvironmentGivenIsTheAccountsXdgOnly(t *testing.T) {
|
|
var env []string
|
|
m := mimeMachine(t, nil)
|
|
m.Run = func(_ context.Context, e []string, _ string, _ ...string) (string, error) { env = e; return "", nil }
|
|
m.Handle("text/html")
|
|
joined := strings.Join(env, "\n")
|
|
for _, want := range []string{"HOME=" + home, "XDG_CONFIG_HOME=" + home + "/.config", "XDG_CURRENT_DESKTOP=i3", "LANG=C.UTF-8"} {
|
|
if !strings.Contains(joined, want) {
|
|
t.Errorf("lacks %s: %v", want, env)
|
|
}
|
|
}
|
|
if len(env) != 7 {
|
|
t.Errorf("only the seven XDG words are passed on: %v", env)
|
|
}
|
|
}
|
|
|
|
func TestSettingADefaultWritesThePersonsListThroughXdgMime(t *testing.T) {
|
|
var calls []string
|
|
m := mimeMachine(t, nil)
|
|
m.Run = recorded(&calls)
|
|
a, err := m.SetDefault("x-scheme-handler/https", "chromium.desktop")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !contains(calls, "xdg-mime default chromium.desktop x-scheme-handler/https") {
|
|
t.Fatalf("%v", calls)
|
|
}
|
|
if a["written_to"] != "~/.config/mimeapps.list" || a["after"] != "chromium.desktop" || a["problem"] != nil {
|
|
t.Fatalf("%v", a)
|
|
}
|
|
for _, refused := range []string{"gone.desktop", "../../etc/passwd", "firefox", "x.desktop; rm"} {
|
|
calls = nil
|
|
if _, err := m.SetDefault("text/html", refused); err == nil || len(calls) != 0 {
|
|
t.Errorf("%s: %v %v", refused, err, calls)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeadDefaultsBrokenHandlersAndListsAreFound(t *testing.T) {
|
|
m := mimeMachine(t, map[string]string{
|
|
home + "/.local/share/applications/mimeapps.list": "->/home/op/.retired/dotfiles/mimeapps.list",
|
|
home + "/.local/share/applications/defaults.list": "->/home/op/.retired/dotfiles/mimeapps.list",
|
|
home + "/.local/share/applications/fleet-old.desktop": app("Fleet", "/home/op/gone/Fleet %u", "x-scheme-handler/fleet;"),
|
|
})
|
|
dead := m.deadDefaults()
|
|
var ids []string
|
|
for _, d := range dead {
|
|
// The fake installs none of the mesh's list's applications but firefox and thunar.
|
|
if d.File == "~/.config/mimeapps.list" {
|
|
ids = append(ids, d.Type+"="+d.ID)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(ids, []string{"text/plain=code-oss.desktop", "x-scheme-handler/https=gone.desktop"}) {
|
|
t.Fatalf("%v", ids)
|
|
}
|
|
var broken []string
|
|
for _, e := range m.brokenHandlers() {
|
|
broken = append(broken, e.ID)
|
|
}
|
|
// xplr's program is not installed in the fake, nor fleet's.
|
|
if !reflect.DeepEqual(broken, []string{"fleet-old.desktop", "fleet.desktop", "xplr.desktop"}) {
|
|
t.Fatalf("%v", broken)
|
|
}
|
|
c := m.Check("i3")
|
|
checks := map[string]Check{}
|
|
for _, x := range c["checks"].([]Check) {
|
|
checks[x.Name] = x
|
|
}
|
|
if s := checks["the mimeapps lists are sane"]; s.OK || !strings.Contains(s.Detail, "a link to nothing") || !strings.Contains(s.Detail, "defaults.list") {
|
|
t.Fatalf("%+v", s)
|
|
}
|
|
if s := checks["the mesh's default applications are in place"]; !s.OK {
|
|
t.Fatalf("%+v", s)
|
|
}
|
|
}
|
|
|
|
func TestAMeshBlockInThePersonsListIsWhatTheCheckRefuses(t *testing.T) {
|
|
// Why the module does not write a block into ~/.config/mimeapps.list: GLib reads the last of two
|
|
// groups, xdg-mime the first, and either rewrites the file around the block.
|
|
m := mimeMachine(t, map[string]string{
|
|
home + "/.config/mimeapps.list": "# >>> mesh >>>\n[Default Applications]\ntext/html=firefox.desktop\n# <<< mesh <<<\n[Default Applications]\ntext/html=chromium.desktop\n",
|
|
})
|
|
c := m.Check("i3")
|
|
for _, x := range c["checks"].([]Check) {
|
|
if x.Name == "the mimeapps lists are sane" && (x.OK || !strings.Contains(x.Detail, "written twice")) {
|
|
t.Fatalf("%+v", x)
|
|
}
|
|
}
|
|
}
|
|
|
|
func autostartMachine(t *testing.T) *Machine {
|
|
return fake(t, map[string]string{
|
|
home + "/.config/autostart/slack.desktop": app("Slack", "/usr/bin/slack -s %U", "") + "Hidden=false\n",
|
|
home + "/.config/autostart/blueman.desktop": app("Blueman", "blueman-applet", "") + "Hidden=true\n",
|
|
home + "/.config/autostart/nextcloud.desktop": app("Nextcloud", `"/usr/bin/nextcloud" --background`, "") + "X-GNOME-Autostart-enabled=false\n",
|
|
"/etc/xdg/autostart/blueman.desktop": app("Blueman", "blueman-applet", ""),
|
|
"/etc/xdg/autostart/picom.desktop": app("picom", "picom", "") + "TryExec=picom\n",
|
|
"/etc/xdg/autostart/nm-applet.desktop": app("nm", "nm-applet", "") + "NotShowIn=KDE;GNOME;\n",
|
|
"/etc/xdg/autostart/gnome-keyring.desktop": app("keyring", "/usr/bin/gnome-keyring-daemon --start", "") + "OnlyShowIn=GNOME;Unity;\n",
|
|
"/etc/xdg/autostart/geoclue.desktop": app("geoclue", "/usr/lib/geoclue-2.0/demos/agent", "") + "NotShowIn=i3;\n",
|
|
"/etc/xdg/autostart/print.desktop": app("print", "system-config-printer-applet", "") + "TryExec=system-config-printer-applet\n",
|
|
"/etc/xdg/autostart/polychromatic.desktop": app("poly", "polychromatic-helper --autostart", ""),
|
|
"/etc/xdg/autostart/slack-too.desktop": app("Slack", "/usr/bin/slack", ""),
|
|
home + "/.config/i3/config": "exec --no-startup-id dex --autostart --environment i3\nexec --no-startup-id picom\n",
|
|
home + "/.config/i3/config.d/90-x.conf": "exec_always --no-startup-id polychromatic-tray-applet\n",
|
|
"/usr/bin/slack": "#!/bin/sh\n",
|
|
"/usr/bin/picom": "#!/bin/sh\n",
|
|
"/usr/bin/nm-applet": "#!/bin/sh\n",
|
|
"/usr/bin/nextcloud": "#!/bin/sh\n",
|
|
"/usr/bin/polychromatic-helper": "#!/bin/sh\n",
|
|
"/proc/101/cmdline": "/usr/lib/slack/slack --type=renderer --enable-crash-reporter",
|
|
"/proc/101/comm": "slack\n",
|
|
"/proc/102/cmdline": "picom\x00",
|
|
"/proc/103/cmdline": "python3\x00/usr/bin/polychromatic-helper\x00--autostart\x00",
|
|
"/proc/104/cmdline": "nextcloud-sync-helper\x00",
|
|
"/proc/self/cmdline": "not a process\x00",
|
|
})
|
|
}
|
|
|
|
func TestAutostartIsDecidedAsDexDecides(t *testing.T) {
|
|
m := autostartMachine(t)
|
|
got := map[string]Autostart{}
|
|
for _, a := range m.Autostarts("i3") {
|
|
got[a.Scope+"/"+a.ID] = a
|
|
}
|
|
want := map[string]bool{
|
|
"user/slack.desktop": true, "user/blueman.desktop": false, "user/nextcloud.desktop": true,
|
|
"system/blueman.desktop": false, "system/picom.desktop": true, "system/nm-applet.desktop": true,
|
|
"system/gnome-keyring.desktop": false, "system/geoclue.desktop": false, "system/print.desktop": false,
|
|
"system/polychromatic.desktop": true, "system/slack-too.desktop": true,
|
|
}
|
|
if len(got) != len(want) {
|
|
t.Fatalf("%d entries: %v", len(got), got)
|
|
}
|
|
for k, starts := range want {
|
|
if got[k].DexStarts != starts {
|
|
t.Errorf("%s: dex starts %v (%s)", k, got[k].DexStarts, got[k].Why)
|
|
}
|
|
}
|
|
if a := got["system/blueman.desktop"]; a.OverriddenBy != "~/.config/autostart/blueman.desktop" || got["user/blueman.desktop"].Overrides != "/etc/xdg/autostart/blueman.desktop" {
|
|
t.Errorf("the user's entry hides the system's of the same name: %+v", a)
|
|
}
|
|
if a := got["user/nextcloud.desktop"]; !strings.Contains(a.Why, "does not stop dex") {
|
|
t.Errorf("%s", a.Why)
|
|
}
|
|
if a := got["system/print.desktop"]; !strings.Contains(a.Why, "TryExec") {
|
|
t.Errorf("%s", a.Why)
|
|
}
|
|
if a := got["system/picom.desktop"]; a.AlsoStarted != "~/.config/i3/config:2" || a.Running != 1 {
|
|
t.Errorf("picom: %+v", a)
|
|
}
|
|
if got["user/slack.desktop"].Running != 1 || got["system/polychromatic.desktop"].Running != 1 || got["user/nextcloud.desktop"].Running != 0 {
|
|
t.Errorf("running: slack %d, polychromatic %d, nextcloud %d", got["user/slack.desktop"].Running,
|
|
got["system/polychromatic.desktop"].Running, got["user/nextcloud.desktop"].Running)
|
|
}
|
|
}
|
|
|
|
func TestTheCheckFindsTwoStartersAndAMissingProgram(t *testing.T) {
|
|
m := autostartMachine(t)
|
|
checks := map[string]Check{}
|
|
for _, c := range m.Check("i3")["checks"].([]Check) {
|
|
checks[c.Name] = c
|
|
}
|
|
twice := checks["no program is started twice at login"]
|
|
if twice.OK || !strings.Contains(twice.Detail, "picom: by /etc/xdg/autostart/picom.desktop and by ~/.config/i3/config:2") ||
|
|
!strings.Contains(twice.Detail, "slack: by ~/.config/autostart/slack.desktop and /etc/xdg/autostart/slack-too.desktop") {
|
|
t.Fatalf("%+v", twice)
|
|
}
|
|
// blueman's system entry is hidden by the user's, so dex starts nothing that is missing.
|
|
if c := checks["every program dex starts is installed"]; !c.OK {
|
|
t.Fatalf("%+v", c)
|
|
}
|
|
if c := checks["user-dirs.dirs is the mesh's"]; c.OK {
|
|
t.Fatalf("no file, yet %+v", c)
|
|
}
|
|
}
|