Files
mesh-catalog/modules/i3/cmd/i3-tools/manifest_test.go
T
jochen 19a79bef86 i3, screen-lock: the session's output to the journal, and the colour locker removed before i3lock is checked
lemurs leaves the session a stdout nobody reads, so a program writing to it dies of EPIPE.
i3lock-color provides i3lock, so with it still installed the host saw i3lock as present,
skipped the install and then removed the only locker; removing it first lets the same
apply install i3lock.
2026-10-04 15:09:39 +02:00

206 lines
7.1 KiB
Go

package main
// i3's shape (novox/hq ADR 0208): it holds node-display-session and requires the X display on its own
// machine; it contributes the session's exec to the last xinitrc slot and the desktop's identity to
// the environment; it owns the main configuration, which ends by including the drop-in directory, and
// the login manager's session entry, which runs the session's start.
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
type manifest struct {
Capabilities []string `json:"capabilities"`
Requires []string `json:"requires"`
Claims []struct {
Name string `json:"name"`
Scope string `json:"scope"`
Serves []string `json:"serves"`
} `json:"claims"`
Seats any `json:"seats"`
Tools []string
Environment struct {
Variables map[string]string `json:"variables"`
} `json:"environment"`
Shell []struct {
For, Slot, Code string
} `json:"shell"`
Resources []map[string]any `json:"resources"`
Build struct {
Artifacts []map[string]any `json:"artifacts"`
} `json:"build"`
}
func readManifest(t *testing.T) manifest {
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
if err != nil {
t.Fatal(err)
}
var m manifest
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
return m
}
func resource(t *testing.T, m manifest, id string) map[string]any {
for _, r := range m.Resources {
if r["id"] == id {
return r
}
}
t.Fatalf("no resource %s", id)
return nil
}
func TestItHoldsTheSessionSeatOnAMachineWithAnXDisplay(t *testing.T) {
m := readManifest(t)
if m.Seats != nil || len(m.Claims) != 1 || m.Claims[0].Name != "node-display-session" || strings.Join(m.Claims[0].Serves, ",") != "reload,workspaces,windows" {
t.Fatalf("%+v", m.Claims)
}
if strings.Join(m.Requires, ",") != "x11-display" {
t.Fatalf("requires %v", m.Requires)
}
served := map[string]bool{}
for _, tool := range tools(&i3{}) {
served[tool.Name] = true
}
for _, v := range m.Claims[0].Serves {
if !served["node-display-session."+v] {
t.Errorf("the seat's %s is not served", v)
}
}
if len(served) != len(m.Tools)+3 {
t.Fatalf("served %d, listed %d", len(served), len(m.Tools))
}
for _, n := range m.Tools {
if !served[n] || !strings.HasPrefix(n, "i3_") {
t.Errorf("%s", n)
}
}
}
func TestItContributesTheSessionsExecLastAndTheDesktopsIdentity(t *testing.T) {
m := readManifest(t)
if len(m.Shell) != 1 || m.Shell[0].For != "xinitrc" || m.Shell[0].Slot != "last" {
t.Fatalf("%+v", m.Shell)
}
var code []string
for _, l := range strings.Split(strings.TrimSpace(m.Shell[0].Code), "\n") {
if !strings.HasPrefix(l, "#") {
code = append(code, l)
}
}
if len(code) != 1 || !strings.HasPrefix(code[0], "exec i3") {
t.Fatalf("one line, the exec: %q", code)
}
if v := m.Environment.Variables; len(v) != 2 || v["XDG_CURRENT_DESKTOP"] != "i3" || v["XDG_SESSION_DESKTOP"] != "i3" {
t.Fatalf("%v", v)
}
}
func TestTheConfigurationIsTheModulesFileImprovedAndEndsWithTheDropIns(t *testing.T) {
m := readManifest(t)
r := resource(t, m, "config")
raw, _ := os.ReadFile(filepath.Join("..", "..", "config", "config"))
if r["content"] != string(raw) || r["path"] != "${machine:account-home}/.config/i3/config" || r["into"] != nil {
t.Fatal("the manifest carries config/config whole, as an owned file")
}
c := string(raw)
lines := strings.Split(strings.TrimSpace(c), "\n")
if lines[len(lines)-1] != "include ~/.config/i3/config.d/*.conf" {
t.Fatal("the drop-ins are read last, with every variable in scope")
}
var lines2 []string
for _, l := range lines {
if !strings.HasPrefix(strings.TrimSpace(l), "#") {
lines2 = append(lines2, l)
}
}
code := strings.Join(lines2, "\n")
for _, gone := range []string{"lxpolkit", "xdg-desktop-portal", "xrdb", "Hack Nerd Font", "refresh_i3status", "rice_set", "exec xterm",
"exec --no-startup-id picom", "exec --no-startup-id nm-applet", "exec --no-startup-id blueman-applet", "exec --no-startup-id nextcloud", "hal/",
// carried by their own modules' drop-ins: rofi, clipmenu, feh, i3status-rust, gnome-keyring
"rofi", "greenclip", "$mod+period", "powermenu", "theme-picker", ".fehbg", "bar {", "i3status-rs", "unlock-keyring"} {
if strings.Contains(code, gone) {
t.Errorf("the configuration still holds %q", gone)
}
}
if !strings.Contains(c, "\nfont pango:JetBrainsMono Nerd Font 11\n") || !strings.Contains(c, "exec --no-startup-id dex --autostart --environment i3") {
t.Fatal("the chosen face for titles; XDG autostart through dex")
}
if !strings.Contains(c, "bindsym $mod+Delete exec --no-startup-id loginctl lock-session") {
t.Fatal("the lock key goes through logind, which the lock screen's module relies on")
}
if i3, err := exec.LookPath("i3"); err == nil {
out, err := exec.Command(i3, "-C", "-c", filepath.Join("..", "..", "config", "config")).CombinedOutput()
if err != nil || len(ParseCheck(string(out))) > 0 {
t.Fatalf("i3 -C: %v %s", err, out)
}
}
}
func TestTheLoginManagersEntryRunsTheSessionsStart(t *testing.T) {
m := readManifest(t)
r := resource(t, m, "session")
if r["path"] != "/etc/lemurs/wms/i3" || r["mode"] != "0755" {
t.Fatalf("%v", r)
}
if !strings.Contains(r["content"].(string), `exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc"`) {
t.Fatal("the entry runs the session's start, never i3 bare")
}
pkgs := map[string]bool{}
for _, x := range m.Resources {
if x["type"] == "package" {
pkgs[x["package"].(string)] = true
}
}
if !pkgs["i3-wm"] || !pkgs["dex"] || len(pkgs) != 2 {
t.Fatalf("%v", pkgs)
}
a := m.Build.Artifacts[0]
if a["from"] != "cmd/i3-tools" || a["binary"] != "i3-tools" || a["language"] != "go" {
t.Fatalf("a binary not named i3, so nothing that looks for i3 by name finds the tools: %v", a)
}
}
// Every module of the catalogue that drops a file into i3's config.d is loaded with the main file, as
// i3 would load them on a machine with all of them assigned: no two bind one key, and every line parses.
func TestTheMainFileAndEveryModulesDropInLoadTogether(t *testing.T) {
i3, err := exec.LookPath("i3")
if err != nil {
t.Skip("no i3 here to check with")
}
dir := t.TempDir()
drop := filepath.Join(dir, "config.d")
os.MkdirAll(drop, 0o755)
manifests, _ := filepath.Glob(filepath.Join("..", "..", "..", "*", "module.json"))
var found []string
for _, p := range manifests {
raw, _ := os.ReadFile(p)
var m struct {
Resources []map[string]any `json:"resources"`
}
json.Unmarshal(raw, &m)
for _, r := range m.Resources {
path, _ := r["path"].(string)
if r["type"] == "file" && strings.Contains(path, "/.config/i3/config.d/") {
os.WriteFile(filepath.Join(drop, filepath.Base(path)), []byte(r["content"].(string)), 0o644)
found = append(found, filepath.Base(path))
}
}
}
main, _ := os.ReadFile(filepath.Join("..", "..", "config", "config"))
text := strings.Replace(string(main), "include ~/.config/i3/config.d/*.conf", "include "+drop+"/*.conf", 1)
os.WriteFile(filepath.Join(dir, "config"), []byte(text), 0o644)
out, err := exec.Command(i3, "-C", "-c", filepath.Join(dir, "config")).CombinedOutput()
if err != nil || len(ParseCheck(string(out))) > 0 {
t.Fatalf("with the drop-ins %v: %v\n%s", found, err, out)
}
}