Files
mesh-catalog/modules/fonts/cmd/fonts-tools/fonts_test.go
T
jochen 86cf6d438d fonts: the five decided faces as packages, and what the generic families mean (hq to-be 42 phase 2.1)
JetBrains Mono Nerd Font for monospace, Inter for the interface, the Nerd
Fonts symbols and Noto Color Emoji as fallbacks, Noto for serif. One owned
fontconfig file in the account's conf.d maps monospace, sans-serif,
system-ui, serif and emoji, bound `same`: measured with fontconfig 2.18, a
weakly bound preference loses to Noto Sans Mono. Families today's configs
name but the laptop lacks (Iosevka, the old JetBrains name) stop falling
back to sans-serif.

Six Go tools: families, match, glyph, sources (which hand-copied files can
go and why), config, cache-rebuild. The README lists the hand-copied files
to remove and the modules that must name the new family.
2026-10-04 13:02:23 +02:00

303 lines
11 KiB
Go

package main
import (
"encoding/xml"
"os"
"path/filepath"
"strings"
"testing"
)
func TestTheManifestInstallsTheFiveDecidedFacesAndOwnsOneAccountFile(t *testing.T) {
m := readManifest(t)
holdsTheBundle(t, m, "fonts")
want := "inter-font,noto-fonts,noto-fonts-emoji,ttf-jetbrains-mono-nerd,ttf-nerd-fonts-symbols"
if got := strings.Join(m.packages(), ","); got != want {
t.Errorf("packages %s, want %s", got, want)
}
files := 0
for _, r := range m.Resources {
if r["type"] == "file" {
files++
}
if r["type"] == "service" || r["type"] == "directory" {
t.Errorf("a font needs no %s: %v", r["type"], r["id"])
}
}
f := m.resource("defaults")
if files != 1 || f == nil {
t.Fatalf("one owned file, got %d", files)
}
if f["path"] != "${machine:account-home}/"+ConfigFile || f["owner"] != "${machine:account}" || f["mode"] != "0644" {
t.Errorf("the file is the account's, at %s: %v", ConfigFile, f)
}
if _, into := f["into"]; into {
t.Error("the file is the module's whole, not written into")
}
}
// fontconfig's document, as far as the module's file uses it.
type fcDoc struct {
Aliases []struct {
Binding string `xml:"binding,attr"`
Family string `xml:"family"`
Prefer []string `xml:"prefer>family"`
Accept []string `xml:"accept>family"`
} `xml:"alias"`
Matches []struct {
Target string `xml:"target,attr"`
Edits []struct {
Name string `xml:"name,attr"`
Mode string `xml:"mode,attr"`
String string `xml:"string"`
} `xml:"edit"`
} `xml:"match"`
}
func TestTheFontconfigFileMapsEachGenericToTheDecidedFaceStronglyAndFallsBackLast(t *testing.T) {
m := readManifest(t)
content, _ := m.resource("defaults")["content"].(string)
if !strings.HasPrefix(content, "<?xml") || !strings.Contains(content, `<!DOCTYPE fontconfig`) {
t.Fatal("not a fontconfig document")
}
if strings.Contains(content, "$") {
t.Error("the file holds no placeholder: it is the same on every machine")
}
var doc fcDoc
if err := xml.Unmarshal([]byte(content), &doc); err != nil {
t.Fatalf("not well-formed: %v", err)
}
firstPrefer := map[string]string{}
order := []string{}
for _, a := range doc.Aliases {
order = append(order, a.Family)
if len(a.Prefer) > 0 {
firstPrefer[a.Family] = a.Prefer[0]
if a.Binding != "same" {
t.Errorf("%s is bound %q: a weakly added face loses to the distribution's choice", a.Family, a.Binding)
}
}
}
for generic, face := range Decided {
if firstPrefer[generic] != face {
t.Errorf("%s prefers %q, decided %q", generic, firstPrefer[generic], face)
}
}
// The retired families lead to monospace, and are placed before the monospace rule.
mono := strings.Index(strings.Join(order, "|"), "|monospace|")
for _, r := range []string{"Hack Nerd Font", "MesloLGS NF", "Iosevka Nerd Font"} {
at := strings.Index(strings.Join(order, "|"), r)
if at < 0 || at > mono {
t.Errorf("%s is not mapped to monospace before the monospace rule", r)
}
}
if len(doc.Matches) != 1 || doc.Matches[0].Target != "pattern" {
t.Fatalf("one pattern match for the fallbacks: %+v", doc.Matches)
}
got := []string{}
for _, e := range doc.Matches[0].Edits {
if e.Name != "family" || e.Mode != "append_last" {
t.Errorf("a fallback is appended last, never prepended: %+v", e)
}
got = append(got, e.String)
}
if strings.Join(got, ",") != "Symbols Nerd Font,Noto Color Emoji" {
t.Errorf("fallbacks %v", got)
}
}
func TestFamiliesGroupsFilesAndTellsAHandCopiedFaceFromAPackagedOne(t *testing.T) {
t.Setenv("MESH_OPERATOR_HOME", "/home/op")
using(t, func(line string, c Cmd) Result {
return ok("Inter\tRegular\t\t/usr/share/fonts/inter/Inter.ttc\n" +
"Inter\tBold\t\t/usr/share/fonts/inter/Inter-Bold.ttc\n" +
"Hack Nerd Font\tRegular\t100\t/home/op/.local/share/fonts/Hack.ttf\n" +
"Noto Sans Mono\tRegular\t100\t/usr/share/fonts/noto/NotoSansMono.ttf\n")
})
got, err := Families("", 10)
if err != nil || got.Count != 3 {
t.Fatalf("%+v %v", got, err)
}
if f := got.Families[0]; f.Family != "Hack Nerd Font" || f.Source != "account" || !f.Monospace {
t.Errorf("%+v", f)
}
if f := got.Families[1]; f.Family != "Inter" || f.Files != 2 || f.Monospace || f.Source != "package" || len(f.Styles) != 2 {
t.Errorf("%+v", f)
}
got, _ = Families("noto", 10)
if got.Count != 1 {
t.Errorf("filtered: %+v", got)
}
got, _ = Families("", 1)
if !got.Truncated || len(got.Families) != 1 || got.Count != 3 {
t.Errorf("bounded: %+v", got)
}
}
func TestMatchSaysWhetherEachGenericIsTheDecidedFace(t *testing.T) {
f := using(t, func(line string, c Cmd) Result {
switch c.Args[len(c.Args)-1] {
case "monospace":
return ok("JetBrainsMono Nerd Font\tRegular\t/usr/share/fonts/TTF/JetBrainsMonoNerdFont-Regular.ttf")
case "sans-serif", "system-ui":
return ok("Noto Sans\tRegular\t/usr/share/fonts/noto/NotoSans-Regular.ttf")
case "serif":
return ok("Noto Serif\tRegular\t/usr/share/fonts/noto/NotoSerif-Regular.ttf")
}
return ok("Noto Color Emoji\tRegular\t/usr/share/fonts/noto/NotoColorEmoji.ttf")
})
got, err := Match(nil)
if err != nil || len(got.Resolved) != 5 || got.AllAsDecided == nil || *got.AllAsDecided {
t.Fatalf("%+v %v", got, err)
}
if r := got.Resolved[0]; !*r.AsDecided || r.Source != "package" {
t.Errorf("monospace: %+v", r)
}
if r := got.Resolved[1]; *r.AsDecided || r.Decided != "Inter" {
t.Errorf("sans-serif is not Inter here: %+v", r)
}
if len(f.asked) != 5 {
t.Errorf("one fc-match per generic: %v", f.lines())
}
got, _ = Match([]string{"Inter:bold"})
if got.AllAsDecided != nil || got.Resolved[0].AsDecided != nil {
t.Errorf("a pattern of the caller's own has no decided face: %+v", got)
}
if _, err := Match([]string{"--help"}); err == nil {
t.Error("an option as a pattern")
}
}
func TestMatchAnEmptyAnswerIsAnError(t *testing.T) {
using(t, func(string, Cmd) Result { return ok("") })
if _, err := Match(nil); err == nil {
t.Fatal("an empty fc-match answer was read as a face")
}
}
func TestGlyphReadsACharacterOrACodePointAndAsksForIt(t *testing.T) {
for in, want := range map[string]rune{"\uf120": 0xf120, "U+F120": 0xf120, "0x1f600": 0x1f600, "a": 'a', "é": 'é'} {
got, err := codePoint(in)
if err != nil || got != want {
t.Errorf("%q: %x %v", in, got, err)
}
}
for _, bad := range []string{"ab", "U+ZZ", "U+0", "120"} {
if _, err := codePoint(bad); err == nil {
t.Errorf("%q was read as a code point", bad)
}
}
f := using(t, func(line string, c Cmd) Result {
if c.Name == "fc-list" {
return ok("Symbols Nerd Font\tRegular\t100\t/usr/share/fonts/TTF/SymbolsNerdFont-Regular.ttf\n" +
"JetBrainsMono Nerd Font\tBold\t100\t/usr/share/fonts/TTF/a.ttf\nJetBrainsMono Nerd Font\tRegular\t100\t/usr/share/fonts/TTF/b.ttf\n")
}
return ok("JetBrainsMono Nerd Font\tRegular\t/usr/share/fonts/TTF/b.ttf")
})
got, err := Glyph("U+F120")
if err != nil || got.Count != 2 || got.CodePoint != "U+F120" || got.DrawnBy["monospace"] != "JetBrainsMono Nerd Font" {
t.Fatalf("%+v %v", got, err)
}
if !strings.Contains(f.lines()[0], ":charset=f120") || !strings.Contains(f.lines()[1], "monospace:charset=f120") {
t.Errorf("asked %v", f.lines())
}
}
func TestSourcesMarksDuplicatesEncodedNamesPackagedAndRetiredFacesForRemoval(t *testing.T) {
home := t.TempDir()
t.Setenv("MESH_OPERATOR_HOME", home)
dir := filepath.Join(home, ".local/share/fonts")
_ = os.MkdirAll(dir, 0o755)
write := func(name, body string) string {
p := filepath.Join(dir, name)
_ = os.WriteFile(p, []byte(body), 0o644)
return p
}
meslo := write("MesloLGS NF Regular.ttf", "meslo")
encoded := write("MesloLGS%20NF%20Regular.ttf", "meslo")
grape := write("GrapeNuts-Regular.ttf", "grape")
noto := write("NotoSans-Copy.ttf", "noto")
_ = os.WriteFile(filepath.Join(dir, ".uuid"), []byte("x"), 0o644)
f := using(t, func(line string, c Cmd) Result {
switch c.Name {
case "fc-scan":
return ok(meslo + "\tMesloLGS NF\tRegular\n" + encoded + "\tMesloLGS NF\tRegular\n" + grape + "\tGrape Nuts\tRegular\n" + noto + "\tNoto Sans\tRegular\n")
case "fc-list":
return ok("Noto Sans\t/usr/share/fonts/noto/NotoSans-Regular.ttf\nNoto Sans\t/usr/share/fonts/noto/NotoSans-Bold.ttf\nInter\t/usr/share/fonts/inter/Inter.ttc\nMesloLGS NF\t" + meslo + "\n")
case "pacman":
return Result{Status: 1, Stdout: "/usr/share/fonts/noto/NotoSans-Regular.ttf is owned by noto-fonts 1:2026.08.01-1\n", Stderr: "error: No package owns /usr/share/fonts/inter/Inter.ttc\n"}
}
return Result{Status: 9}
})
got, err := Sources()
if err != nil {
t.Fatal(err)
}
if len(got.Hand) != 4 {
t.Fatalf("four font files, the .uuid is not one: %+v", got.Hand)
}
by := map[string]HandFile{}
for _, h := range got.Hand {
by[filepath.Base(h.File)] = h
}
if h := by["GrapeNuts-Regular.ttf"]; h.Remove || h.Family != "Grape Nuts" {
t.Errorf("a face nothing replaces is kept: %+v", h)
}
if h := by["MesloLGS%20NF%20Regular.ttf"]; !h.Remove || len(h.Why) < 3 {
t.Errorf("an encoded duplicate of a retired face: %+v", h)
}
if h := by["NotoSans-Copy.ttf"]; !h.Remove || !strings.Contains(strings.Join(h.Why, ";"), "noto-fonts") {
t.Errorf("a face a package installs: %+v", h)
}
if got.RemoveCount != 3 || got.HandBytes != int64(len("meslo")*2+len("grape")+len("noto")) {
t.Errorf("%d removable, %d bytes", got.RemoveCount, got.HandBytes)
}
pk := map[string]string{}
for _, p := range got.Packaged {
pk[p.Family] = p.Package
}
if pk["Noto Sans"] != "noto-fonts" || pk["Inter"] != "(no package)" || pk["MesloLGS NF"] != "" {
t.Errorf("packaged %v", pk)
}
for _, l := range f.lines() {
if strings.Contains(l, "rm ") || strings.HasPrefix(l, "sudo") {
t.Errorf("sources only reads: %s", l)
}
}
}
func TestConfigSaysWhetherTheFileIsInPlaceAndLoaded(t *testing.T) {
home := t.TempDir()
t.Setenv("MESH_OPERATOR_HOME", home)
path := filepath.Join(home, ConfigFile)
using(t, func(string, Cmd) Result { return ok("+ " + path + ": The mesh\n- /etc/fonts/conf.d/x.conf\n") })
got, err := Config()
if err != nil || got.Present || !strings.Contains(got.Note, "not in place") {
t.Fatalf("absent: %+v %v", got, err)
}
_ = os.MkdirAll(filepath.Dir(path), 0o755)
_ = os.WriteFile(path, []byte("<fontconfig/>"), 0o644)
_ = os.WriteFile(filepath.Join(filepath.Dir(path), "99-mine.conf"), []byte("x"), 0o644)
got, err = Config()
if err != nil || !got.Present || !got.Loaded || len(got.Others) != 1 {
t.Fatalf("present: %+v %v", got, err)
}
}
func TestCacheRebuildIsTheAccountsUnlessTheSystemIsAskedWhichNeedsRoot(t *testing.T) {
f := using(t, func(string, Cmd) Result { return ok("") })
if got, err := CacheRebuild(false); err != nil || got.Scope != "account" {
t.Fatal(got, err)
}
if got, err := CacheRebuild(true); err != nil || got.Scope != "system" {
t.Fatal(got, err)
}
if l := f.lines(); l[0] != "fc-cache -f" || l[1] != "sudo -n fc-cache -s -f" {
t.Errorf("%v", l)
}
using(t, func(string, Cmd) Result { return Result{Status: 1, Stderr: "sudo: a password is required"} })
if _, err := CacheRebuild(true); err == nil || !strings.Contains(err.Error(), "sudo -n refused") {
t.Errorf("a refused escalation: %v", err)
}
}