Files
mesh-catalog/modules/xterm/cmd/xterm-tools/xterm_test.go
T
jochen e4abc6eb88 xterm: the terminal holds node-terminal-emulator; its resources through xorg's slot (hq ADR 0208)
It requires x11-display, names itself in TERMINAL, and contributes its X resources
to the xresources slot normal: today's palette and clipboard keys, JetBrainsMono
Nerd Font, 10000 lines of scrollback, all scoped to XTerm* instead of every Xt
program. It owns no file.

The tools are the seat's open, which starts a terminal through the account's
service manager so it outlives the runtime's restarts, and font (in force, what
fontconfig resolves it to, set for new terminals) and colours.
2026-10-04 13:21:47 +02:00

211 lines
7.2 KiB
Go

package main
import (
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"xterm/internal/desktop"
)
type manifest struct {
Capabilities []string `json:"capabilities"`
Requires []string `json:"requires"`
Claims []struct {
Name string `json:"name"`
Serves []string `json:"serves"`
} `json:"claims"`
Tools []string `json:"tools"`
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 TestItHoldsTheTerminalSeatOnAnXDisplayAndNamesItselfTheTerminal(t *testing.T) {
m := readManifest(t)
if len(m.Claims) != 1 || m.Claims[0].Name != "node-terminal-emulator" || strings.Join(m.Claims[0].Serves, ",") != "open" {
t.Fatalf("%+v", m.Claims)
}
if strings.Join(m.Requires, ",") != "x11-display" || m.Environment.Variables["TERMINAL"] != "xterm" || len(m.Environment.Variables) != 1 {
t.Fatalf("%v %v", m.Requires, m.Environment)
}
served := map[string]bool{}
for _, tool := range tools(xterm{}) {
served[tool.Name] = true
}
if !served["node-terminal-emulator.open"] || len(served) != len(m.Tools)+1 {
t.Fatalf("%v %v", served, m.Tools)
}
for _, n := range m.Tools {
if !served[n] {
t.Errorf("%s", n)
}
}
if len(m.Resources) != 1 || m.Resources[0]["package"] != "xterm" {
t.Fatalf("%v", m.Resources)
}
if a := m.Build.Artifacts[0]; a["binary"] != "xterm-tools" || a["from"] != "cmd/xterm-tools" {
t.Fatalf("a binary not named xterm, so nothing looking for xterm by name finds the tools: %v", a)
}
}
func TestItsResourcesAreOneContributionInTheNormalSlotScopedToXTerm(t *testing.T) {
m := readManifest(t)
if len(m.Shell) != 1 || m.Shell[0].For != "xresources" || m.Shell[0].Slot != "normal" {
t.Fatalf("%+v", m.Shell)
}
if m.Shell[0].Code != Resources() {
t.Fatal("the manifest's resources are not the ones the bundle describes: regenerate the manifest from Resources()")
}
for _, l := range strings.Split(strings.TrimSpace(Resources()), "\n") {
switch {
case strings.HasPrefix(l, "!"), strings.HasPrefix(l, " "):
case strings.HasPrefix(l, "#"):
t.Errorf("merged with -nocpp, a # line is skipped: %q", l)
case !strings.HasPrefix(l, "XTerm*"):
t.Errorf("a resource of another program, or of every one: %q", l)
}
}
if !strings.Contains(Resources(), "XTerm*faceName: JetBrainsMono Nerd Font") || !strings.Contains(Resources(), "copy-selection(CLIPBOARD)") {
t.Fatal("the chosen face and the clipboard keys")
}
}
type fake struct {
ran []string
stdin []string
query string
}
func (f *fake) desk(session bool) desktop.Desk {
return desktop.Desk{
Find: func() (*desktop.Session, error) {
if !session {
return nil, &desktop.NoSession{Reason: "none"}
}
return &desktop.Session{Display: ":1", XAuthority: "/h/.Xauthority", Type: "x11", RuntimeDir: "/run/user/1000", Bus: "unix:path=/run/user/1000/bus"}, nil
},
Run: func(_ context.Context, _ []string, stdin []byte, name string, args ...string) desktop.Result {
f.ran = append(f.ran, strings.Join(append([]string{name}, args...), " "))
f.stdin = append(f.stdin, string(stdin))
switch name {
case "xrdb":
return desktop.Result{Stdout: f.query}
case "fc-list":
if strings.Contains(args[0], "Missing") {
return desktop.Result{}
}
return desktop.Result{Stdout: args[0] + "\n"}
case "fc-match":
return desktop.Result{Stdout: "JetBrainsMono Nerd Font|Regular|/usr/share/fonts/TTF/J.ttf"}
}
return desktop.Result{}
},
}
}
func TestOpenHandsTheTerminalToTheAccountsServiceManagerWithTheSessionsDisplay(t *testing.T) {
f := &fake{}
dir := t.TempDir()
got, err := xterm{d: f.desk(true)}.open(context.Background(), desktop.Args{"directory": dir, "command": "htop", "hold": true, "title": "top"})
if err != nil {
t.Fatal(err)
}
if len(f.ran) != 1 || !strings.HasPrefix(f.ran[0], "systemd-run --user --collect --quiet --unit=mesh-xterm-") {
t.Fatalf("%q", f.ran)
}
for _, want := range []string{"--setenv=DISPLAY=:1", "--setenv=XAUTHORITY=/h/.Xauthority", "-- xterm -title top -hold -e /bin/sh -c", "sh " + dir + " htop"} {
if !strings.Contains(f.ran[0], want) {
t.Errorf("lacks %q: %s", want, f.ran[0])
}
}
if !strings.Contains(asJSON(got), `"unit":"mesh-xterm-`) {
t.Fatalf("%s", asJSON(got))
}
if _, err := (xterm{d: f.desk(true)}).open(context.Background(), desktop.Args{"directory": "/no/such/dir"}); err == nil {
t.Fatal("a directory that is not there")
}
if _, err := (xterm{d: (&fake{}).desk(false)}).open(context.Background(), desktop.Args{}); !desktop.IsNoSession(err) {
t.Fatalf("%v", err)
}
}
func TestTheTerminalsScriptStartsInTheDirectoryAndRunsTheCommandThroughTheShell(t *testing.T) {
dir := t.TempDir()
args := OpenArgs(dir, "pwd > out", "", false)
sh := args[len(args)-6:] // /bin/sh -c script sh dir command
cmd := exec.Command(sh[0], sh[1:]...)
cmd.Env = []string{"SHELL=/bin/sh", "PATH=/usr/bin:/bin", "HOME=" + dir}
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%v %s", err, out)
}
b, _ := os.ReadFile(filepath.Join(dir, "out"))
if strings.TrimSpace(string(b)) != dir {
t.Fatalf("%q", b)
}
}
func TestFontAndColoursAreReadAsAnXTermSeesThem(t *testing.T) {
q := "*faceName:\tHack Nerd Font\nXTerm*faceName:\tJetBrainsMono Nerd Font\nXTerm*faceSize:\t12\n" +
"XTerm*background:\t#000000\n*foreground:\trgb:a8/a8/a8\nXTerm*color1:\trgb:ff/00/00\n"
if Effective(q, "faceName") != "JetBrainsMono Nerd Font" || Effective(q, "foreground") != "rgb:a8/a8/a8" {
t.Fatal("XTerm's own value outranks the global one; the global one stands alone")
}
for in, want := range map[string]string{"rgb:9e/18/28": "#9e1828", "rgb:f/0/8": "#ff0088", "rgb:ffff/0000/8080": "#ff0080", "#ABCDEF": "#abcdef", "red": "red"} {
if got := Hex(in); got != want {
t.Errorf("%s: %s", in, got)
}
}
f := &fake{query: q}
got, err := xterm{d: f.desk(true)}.colours(context.Background(), desktop.Args{})
if err != nil {
t.Fatal(err)
}
j := asJSON(got)
if !strings.Contains(j, `{"name":"color1","value":"#ff0000","default":"#9e1828","differs":true}`) || !strings.Contains(j, `{"name":"foreground","value":"#a8a8a8","default":"#a8a8a8"}`) {
t.Fatalf("%s", j)
}
if _, err := (xterm{d: f.desk(true)}).font(context.Background(), desktop.Args{"face": "Missing Font"}); err == nil {
t.Fatal("a face that is not installed")
}
if _, err := (xterm{d: f.desk(true)}).font(context.Background(), desktop.Args{"size": 99.0}); err == nil {
t.Fatal("size out of range")
}
f.ran, f.stdin = nil, nil
got, err = xterm{d: f.desk(true)}.font(context.Background(), desktop.Args{"face": "Inter", "size": 13.0})
if err != nil {
t.Fatal(err)
}
if f.stdin[1] != "XTerm*faceName: Inter\nXTerm*faceSize: 13\n" || !strings.Contains(asJSON(got), `"lasts"`) {
t.Fatalf("%q %s", f.stdin, asJSON(got))
}
}
func asJSON(v any) string {
b, _ := json.Marshal(v)
return string(b)
}