233 lines
8.1 KiB
Go
233 lines
8.1 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"`
|
|
Items []struct {
|
|
Collection string `json:"collection"`
|
|
Attributes map[string]string `json:"attributes"`
|
|
Template string `json:"template"`
|
|
} `json:"items"`
|
|
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)
|
|
}
|
|
// The module's code and variables are the templates of its items (design 57 L3, issues 795 and 807).
|
|
for _, it := range m.Items {
|
|
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parts := strings.Split(it.Collection, ":")
|
|
switch {
|
|
case len(parts) == 3 && parts[0] == "shell":
|
|
m.Shell = append(m.Shell, struct{ For, Slot, Code string }{parts[1], parts[2], string(text)})
|
|
case it.Collection == "environment" && it.Attributes["kind"] == "variable":
|
|
if m.Environment.Variables == nil {
|
|
m.Environment.Variables = map[string]string{}
|
|
}
|
|
m.Environment.Variables[it.Attributes["name"]] = strings.TrimSuffix(string(text), "\n")
|
|
}
|
|
}
|
|
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)
|
|
}
|