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 C: copy-selection(CLIPBOARD) \\n\\", " Ctrl Shift ~Alt 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 C", "paste-binding": "Ctrl Shift ~Alt 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 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: `: \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:}: %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) } } }