Files
mesh-catalog/modules/xterm/cmd/xterm-tools/config_test.go
T
jschoubben f178aab641
mesh/delivery-group group fix/1656-xterm-config rejected: a member's own check failed
mesh/merge-gate waits for its group's composed check
mesh/repo-check head f178aab641dc merged into trunk 3259765d46bc beside mesh-controller#420@69a67876 — fail: its merge-check.sh failed: files asking for a …
mesh/delivery superseded: a newer head of the same pull request
Make every xterm resource a config value with today's value as default; tools read the rendered resources (issue 1656)
2026-10-21 12:51:40 +02:00

275 lines
9.1 KiB
Go

package main
import (
"context"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
"xterm/internal/desktop"
)
// todaysResources is the file every machine had before the module took config values (novox/hq issue 1656).
var todaysResources = strings.Join([]string{
"! xterm: the face, the palette and the clipboard keys. Scoped to the XTerm class, so they",
"! outrank any `*name` resource of yours and touch no other X program.",
"XTerm*termName: xterm-256color",
"XTerm*saveLines: 10000",
"XTerm*renderFont: true",
"XTerm*faceName: JetBrainsMono Nerd Font",
"XTerm*faceSize: 12",
"XTerm*boldMode: false",
"XTerm*alwaysBoldMode: false",
"XTerm*allowBoldFonts: true",
"XTerm*background: #000000",
"XTerm*foreground: #a8a8a8",
"XTerm*color0: #000000",
"XTerm*color8: #666666",
"XTerm*color1: #9e1828",
"XTerm*color9: #bc5766",
"XTerm*color2: #008800",
"XTerm*color10: #61a171",
"XTerm*color3: #d2bb4b",
"XTerm*color11: #e7db52",
"XTerm*color4: #414171",
"XTerm*color12: #5085af",
"XTerm*color5: #963c59",
"XTerm*color13: #a97a99",
"XTerm*color6: #418179",
"XTerm*color14: #6ba4a4",
"XTerm*color7: #bebebe",
"XTerm*color15: #ffffff",
"XTerm*VT100.Translations: #override \\n\\",
" Ctrl Shift ~Alt <Key> C: copy-selection(CLIPBOARD) \\n\\",
" Ctrl Shift ~Alt <Key> V: insert-selection(CLIPBOARD)",
"",
}, "\n")
var placeholder = regexp.MustCompile(`\$\{config:([a-z0-9][a-z0-9_.-]*)\}`)
// asText is a default as the controller writes a value into a file: a string as it is, a number without a
// needless fraction, a boolean as true or false.
func asText(t *testing.T, v any) string {
t.Helper()
switch v := v.(type) {
case string:
return v
case float64:
return strconv.FormatFloat(v, 'f', -1, 64)
case bool:
return strconv.FormatBool(v)
}
t.Fatalf("a default of no type: %v", v)
return ""
}
func template(t *testing.T) string {
t.Helper()
m := readManifest(t)
if len(m.Shell) != 1 {
t.Fatalf("%+v", m.Shell)
}
return m.Shell[0].Code
}
// 1656/AC1: every value the resources set is a config value with today's value as its default.
func TestEveryResourceValueIsAConfigValueWithTodaysDefault(t *testing.T) {
m := readManifest(t)
want := map[string]any{
"face": "JetBrainsMono Nerd Font", "size": 12.0, "save-lines": 10000.0, "term-name": "xterm-256color",
"render-font": true, "bold-mode": false, "always-bold-mode": false, "allow-bold-fonts": true,
"background": "#000000", "foreground": "#a8a8a8",
"color0": "#000000", "color1": "#9e1828", "color2": "#008800", "color3": "#d2bb4b",
"color4": "#414171", "color5": "#963c59", "color6": "#418179", "color7": "#bebebe",
"color8": "#666666", "color9": "#bc5766", "color10": "#61a171", "color11": "#e7db52",
"color12": "#5085af", "color13": "#a97a99", "color14": "#6ba4a4", "color15": "#ffffff",
"copy-binding": "Ctrl Shift ~Alt <Key> C", "paste-binding": "Ctrl Shift ~Alt <Key> V",
}
if len(m.Config) != len(want) {
t.Errorf("declares %d config values, want %d", len(m.Config), len(want))
}
for key, def := range want {
c, ok := m.Config[key]
if !ok {
t.Errorf("%s: not declared", key)
continue
}
if c.Default != def || c.Required || strings.TrimSpace(c.Description) == "" {
t.Errorf("%s: %+v, want default %v and a description", key, c, def)
}
wantType := map[bool]string{true: "string"}[true]
switch def.(type) {
case float64:
wantType = "number"
case bool:
wantType = "boolean"
}
if c.Type != wantType {
t.Errorf("%s: type %q, want %q", key, c.Type, wantType)
}
}
size := m.Config["size"]
if size.Predefined != "font-size" || size.Minimum == nil || *size.Minimum != 6 || size.Maximum == nil || *size.Maximum != 72 {
t.Errorf("size: %+v, want the predefined font-size from 6 to 72", size)
}
names := []string{"background", "foreground"}
for i := 0; i < 16; i++ {
names = append(names, "color"+strconv.Itoa(i))
}
for _, key := range names {
c := m.Config[key]
re, err := regexp.Compile("^(?:" + c.Pattern + ")$")
if err != nil || c.Pattern == "" {
t.Errorf("%s: pattern %q", key, c.Pattern)
continue
}
if !re.MatchString("#0a1B2c") || re.MatchString("red") || re.MatchString("#12345") || re.MatchString("#1234567") {
t.Errorf("%s: pattern %q is not #rrggbb", key, c.Pattern)
}
}
for _, key := range []string{"copy-binding", "paste-binding"} {
re := regexp.MustCompile("^(?:" + m.Config[key].Pattern + ")$")
if !re.MatchString("Ctrl Shift ~Alt <Key> C") || re.MatchString("a: b") || re.MatchString("a\nb") || re.MatchString(`a\`) {
t.Errorf("%s: pattern %q lets a value end the resource", key, m.Config[key].Pattern)
}
}
}
// 1656/AC2: no literal value is left in the resources, and every declared key is used.
func TestEveryResourceValueIsAPlaceholderOfADeclaredKeyAndEveryKeyIsUsed(t *testing.T) {
m := readManifest(t)
text := template(t)
used := map[string]bool{}
for _, l := range strings.Split(text, "\n") {
switch {
case l == "", strings.HasPrefix(l, "!"):
continue
case strings.HasPrefix(l, "XTerm*VT100.Translations: #override"):
continue
}
_, value, ok := strings.Cut(l, ": ")
if strings.HasPrefix(l, " ") {
// a translation line: `<binding>: <action> \n\`
value, _, _ = strings.Cut(strings.TrimSpace(l), ": ")
ok = true
}
if !ok {
t.Errorf("not a resource: %q", l)
continue
}
f := placeholder.FindStringSubmatch(value)
if f == nil || f[0] != value {
t.Errorf("a literal value, not a ${config:<key>}: %q", l)
continue
}
if _, declared := m.Config[f[1]]; !declared {
t.Errorf("%q names a key the module does not declare", l)
}
used[f[1]] = true
}
for key := range m.Config {
if !used[key] {
t.Errorf("%s is declared and no resource uses it", key)
}
}
}
// 1656/AC3: filled with the defaults, the resources are the file every machine had.
func TestFilledWithTheDefaultsTheResourcesAreTodaysFileByteForByte(t *testing.T) {
m := readManifest(t)
got := placeholder.ReplaceAllStringFunc(template(t), func(ref string) string {
c, ok := m.Config[placeholder.FindStringSubmatch(ref)[1]]
if !ok {
t.Fatalf("%s: no such key", ref)
}
return asText(t, c.Default)
})
if got != todaysResources {
t.Fatalf("filled with the defaults:\n%s\nwant:\n%s", got, todaysResources)
}
}
// renderedWith is the resources file xorg writes with this module's piece filled from the defaults, and
// the given values changed.
func renderedWith(t *testing.T, changed map[string]string) string {
t.Helper()
m := readManifest(t)
text := placeholder.ReplaceAllStringFunc(template(t), func(ref string) string {
key := placeholder.FindStringSubmatch(ref)[1]
if v, ok := changed[key]; ok {
return v
}
return asText(t, m.Config[key].Default)
})
return "! The mesh's X resources (module xorg)\nXft.dpi: 96\n# xterm\n" + text + "Xft.rgba: rgb\n"
}
func deskWithResources(t *testing.T, rendered string, f *fake) xterm {
t.Helper()
path := filepath.Join(t.TempDir(), "xresources")
if rendered != "" {
if err := os.WriteFile(path, []byte(rendered), 0o644); err != nil {
t.Fatal(err)
}
}
return xterm{d: f.desk(true), resources: path}
}
// 1656/AC4: the tools' defaults are what the module's resources were rendered with, not constants.
func TestFontReportsTheDefaultTheModuleRenderedNotAConstant(t *testing.T) {
f := &fake{query: "XTerm*faceName:\tInter\nXTerm*faceSize:\t13\n"}
x := deskWithResources(t, renderedWith(t, map[string]string{"size": "10", "face": "Fira Code"}), f)
got, err := x.font(context.Background(), desktop.Args{})
if err != nil {
t.Fatal(err)
}
if j := asJSON(got); !strings.Contains(j, `"default":{"face":"Fira Code","size":10}`) {
t.Fatalf("%s", j)
}
}
// 1656/AC4
func TestColoursReportTheDefaultTheModuleRenderedNotAConstant(t *testing.T) {
f := &fake{query: "XTerm*color1:\t#aa0000\nXTerm*foreground:\t#112233\nXTerm*color2:\trgb:00/88/01\n"}
x := deskWithResources(t, renderedWith(t, map[string]string{"color1": "#aa0000", "foreground": "#112233"}), f)
got, err := x.colours(context.Background(), desktop.Args{})
if err != nil {
t.Fatal(err)
}
j := asJSON(got)
for _, want := range []string{`{"name":"color1","value":"#aa0000","default":"#aa0000"}`,
`{"name":"foreground","value":"#112233","default":"#112233"}`,
`{"name":"color2","value":"#008801","default":"#008800","differs":true}`} {
if !strings.Contains(j, want) {
t.Errorf("lacks %s in %s", want, j)
}
}
}
// 1656/AC4: without the rendered file there is no default to report, said loudly.
func TestWithoutTheRenderedResourcesTheToolsSayWhy(t *testing.T) {
x := deskWithResources(t, "", &fake{})
if _, err := x.font(context.Background(), desktop.Args{}); err == nil || !strings.Contains(err.Error(), "xresources") {
t.Fatalf("font: %v", err)
}
if _, err := x.colours(context.Background(), desktop.Args{}); err == nil || !strings.Contains(err.Error(), "xresources") {
t.Fatalf("colours: %v", err)
}
}
// 1656/AC4: tools.go holds no copy of the defaults.
func TestTheToolsHoldNoCopyOfTheDefaults(t *testing.T) {
src, err := os.ReadFile("tools.go")
if err != nil {
t.Fatal(err)
}
for _, lit := range []string{"JetBrainsMono", "#a8a8a8", "#9e1828", "xterm-256color", "Face =", "Size =", "func Resources"} {
if strings.Contains(string(src), lit) {
t.Errorf("tools.go holds %q", lit)
}
}
}