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
275 lines
9.1 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|