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) }