244 lines
8.4 KiB
Go
244 lines
8.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// xdg's shape: it claims no seat (the conventions are not a role anything calls), needs no
|
|
// display, owns the account's folders file whole and the machine's lowest list of default
|
|
// applications, and holds the person's own list of defaults and every autostart entry as found.
|
|
|
|
type manifest struct {
|
|
Module string `json:"module"`
|
|
Capabilities []string `json:"capabilities"`
|
|
Requires []string `json:"requires"`
|
|
Claims []any `json:"claims"`
|
|
Tools []string `json:"tools"`
|
|
Shell []any `json:"shell"`
|
|
Contributions []any `json:"contributions"`
|
|
Resources []map[string]any `json:"resources"`
|
|
Build struct {
|
|
Artifacts []map[string]any `json:"artifacts"`
|
|
} `json:"build"`
|
|
}
|
|
|
|
func readManifest(t *testing.T) (manifest, string) {
|
|
t.Helper()
|
|
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)
|
|
}
|
|
return m, string(raw)
|
|
}
|
|
|
|
func TestItClaimsNoSeatNeedsNoDisplayAndContributesNothing(t *testing.T) {
|
|
m, _ := readManifest(t)
|
|
if m.Module != "xdg" || m.Claims != nil || m.Requires != nil || m.Shell != nil || m.Contributions != nil {
|
|
t.Fatalf("%+v", m)
|
|
}
|
|
if !reflect.DeepEqual(m.Capabilities, []string{"package-manager"}) {
|
|
t.Fatalf("%v", m.Capabilities)
|
|
}
|
|
}
|
|
|
|
func TestItOwnsTheFilesItsSourcesHold(t *testing.T) {
|
|
m, _ := readManifest(t)
|
|
want := map[string][3]string{
|
|
"user-dirs": {"${machine:account-home}/.config/user-dirs.dirs", "0600", "user-dirs.dirs"},
|
|
"user-dirs-locale": {"${machine:account-home}/.config/user-dirs.locale", "0644", "user-dirs.locale"},
|
|
"user-dirs-update": {"${machine:account-home}/.config/user-dirs.conf", "0644", "user-dirs.conf"},
|
|
"default-applications": {MeshList, "0644", "mimeapps.list"},
|
|
}
|
|
var pkgs []string
|
|
files := 0
|
|
for _, r := range m.Resources {
|
|
id, _ := r["id"].(string)
|
|
if r["type"] == "package" {
|
|
pkgs = append(pkgs, r["package"].(string))
|
|
continue
|
|
}
|
|
w, ok := want[id]
|
|
if !ok || r["type"] != "file" {
|
|
t.Errorf("unexpected resource %v", r)
|
|
continue
|
|
}
|
|
files++
|
|
_, inline := r["content"]
|
|
if _, err := os.Stat(filepath.Join("..", "..", "files", w[2])); err != nil || inline ||
|
|
r["template"] != "files/"+w[2] || r["path"] != w[0] || r["mode"] != w[1] || r["into"] != nil {
|
|
t.Errorf("%s is not files/%s, whole, as its template, at %s mode %s: %v", id, w[2], w[0], w[1], r["path"])
|
|
}
|
|
home := strings.HasPrefix(w[0], "${machine:account-home}/")
|
|
if home != (r["owner"] == "${machine:account}") {
|
|
t.Errorf("%s: a file in the home is the account's, a system file root's: owner %v", id, r["owner"])
|
|
}
|
|
}
|
|
if files != len(want) {
|
|
t.Errorf("%d files, want %d", files, len(want))
|
|
}
|
|
if !reflect.DeepEqual(pkgs, []string{"xdg-user-dirs", "xdg-utils"}) {
|
|
t.Errorf("packages %v", pkgs)
|
|
}
|
|
for _, r := range m.Resources {
|
|
if p, _ := r["path"].(string); strings.Contains(p, "mimeapps.list") && strings.Contains(p, "account-home") {
|
|
t.Errorf("the person's mimeapps.list is held as found, never written: %v", r)
|
|
}
|
|
if p, _ := r["path"].(string); strings.Contains(p, "autostart") || strings.Contains(p, "xdg-desktop-portal") {
|
|
t.Errorf("autostart entries are their applications' modules', the portal is adwaita's: %v", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheFoldersFileIsXdgUserDirsOwnFormatWithThreeSettings(t *testing.T) {
|
|
raw, _ := os.ReadFile(filepath.Join("..", "..", "files", "user-dirs.dirs"))
|
|
text := string(raw)
|
|
settings := regexp.MustCompile(`\$\{config:([a-z0-9-]+)\}`).FindAllStringSubmatch(text, -1)
|
|
var names []string
|
|
for _, s := range settings {
|
|
names = append(names, s[1])
|
|
}
|
|
sort.Strings(names)
|
|
if !reflect.DeepEqual(names, []string{"desktop-dir", "publicshare-dir", "videos-dir"}) {
|
|
t.Fatalf("the folders that differ between the workstations are the settings: %v", names)
|
|
}
|
|
lines := 0
|
|
for _, l := range strings.Split(strings.TrimSpace(text), "\n") {
|
|
if strings.HasPrefix(l, "#") {
|
|
continue
|
|
}
|
|
lines++
|
|
if !userDirLine.MatchString(l) {
|
|
t.Errorf("not xdg-user-dirs' format: %q", l)
|
|
}
|
|
}
|
|
// The eight folders GLib knows, and xdg-user-dirs' PROJECTS.
|
|
if lines != 9 {
|
|
t.Errorf("%d folders", lines)
|
|
}
|
|
if !strings.Contains(firstLines(text, 3), meshMark) {
|
|
t.Error("the header xdg_check recognises the file by")
|
|
}
|
|
conf, _ := os.ReadFile(filepath.Join("..", "..", "files", "user-dirs.conf"))
|
|
if !regexp.MustCompile(`(?m)^enabled=False$`).Match(conf) {
|
|
t.Error("xdg-user-dirs-update must be disabled, or it rewrites the mesh's file at login")
|
|
}
|
|
}
|
|
|
|
func TestTheMeshsDefaultsNameEachTypeOnceInOneGroupEach(t *testing.T) {
|
|
raw, _ := os.ReadFile(filepath.Join("..", "..", "files", "mimeapps.list"))
|
|
k := ParseKeyFile(string(raw))
|
|
if len(k.DupGroups)+len(k.DupKeys)+len(k.Unparsable) != 0 {
|
|
t.Fatalf("%+v", k)
|
|
}
|
|
if !reflect.DeepEqual(k.Order, []string{"Default Applications", "Added Associations"}) {
|
|
t.Fatalf("%v", k.Order)
|
|
}
|
|
for typ, v := range k.Groups["Default Applications"] {
|
|
if _, err := Normalise(typ); err != nil {
|
|
t.Errorf("%s: %v", typ, err)
|
|
}
|
|
for _, id := range List(v) {
|
|
if !desktopID.MatchString(id) || regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-`).MatchString(id) {
|
|
t.Errorf("%s=%s: an id one machine generated is not a default for every machine", typ, id)
|
|
}
|
|
if id == "code-oss.desktop" {
|
|
t.Errorf("%s: code-oss is installed on neither workstation", typ)
|
|
}
|
|
}
|
|
}
|
|
if k.Groups["Default Applications"]["x-scheme-handler/https"] != "firefox.desktop;" {
|
|
t.Error("the browser is the operator's")
|
|
}
|
|
}
|
|
|
|
func TestTheToolsAgreeWithTheManifest(t *testing.T) {
|
|
m, raw := readManifest(t)
|
|
served := map[string]bool{}
|
|
for _, tool := range tools(&Machine{}) {
|
|
served[tool.Name] = true
|
|
if !strings.HasPrefix(tool.Name, "xdg_") || strings.TrimSpace(tool.Description) == "" {
|
|
t.Errorf("%s", tool.Name)
|
|
}
|
|
}
|
|
if len(served) != len(m.Tools) {
|
|
t.Fatalf("%v %v", served, m.Tools)
|
|
}
|
|
for _, n := range m.Tools {
|
|
if !served[n] {
|
|
t.Errorf("module.json lists %s, which the bundle does not serve", n)
|
|
}
|
|
}
|
|
var bundle map[string]any
|
|
for _, a := range m.Build.Artifacts {
|
|
if a["kind"] == "bundle" {
|
|
bundle = a
|
|
}
|
|
}
|
|
if bundle == nil || bundle["language"] != "go" || bundle["system"] != "arch" || bundle["from"] != "cmd/xdg-tools" || bundle["binary"] != "xdg-tools" {
|
|
t.Errorf("the Go tools bundle: %v", bundle)
|
|
}
|
|
s := strings.ToLower(raw)
|
|
for _, never := range []string{"/home/", "jochen", "g14", "shanks", "novox.be", "zurag", ".hal/", "api_key"} {
|
|
if strings.Contains(s, never) {
|
|
t.Errorf("module.json names %q", never)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The folders file stays trusted (unmarked), so its three settings are set at the controller's terminal
|
|
// alone. A value is pasted into it as it is, and the format is shell assignments defined to be sourced
|
|
// by a shell: `$HOME/x"; <command>; X="` would add a command of the caller's (hq issue 346). Until
|
|
// settings are typed and quoted at render, no verb may set them.
|
|
func TestTheFoldersFileStaysTrustedUntilSettingsAreQuoted(t *testing.T) {
|
|
m, _ := readManifest(t)
|
|
for _, r := range m.Resources {
|
|
if r["id"] != "user-dirs" {
|
|
continue
|
|
}
|
|
if _, marked := r["trusted"]; marked {
|
|
t.Fatal("user-dirs.dirs carries a trusted marking: its settings are the terminal's alone (hq issue 346)")
|
|
}
|
|
return
|
|
}
|
|
t.Fatal("no user-dirs resource")
|
|
}
|
|
|
|
// No file resource carries its text inline: each names its template in files/, and the build packs files/
|
|
// into the one archive the controller reads the templates back from (novox/hq ADR 0349 §2).
|
|
func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
|
|
m, _ := readManifest(t)
|
|
for _, r := range m.Resources {
|
|
if _, inline := r["content"]; inline {
|
|
t.Errorf("resource %v keeps its text inline: keep it in files/ and name it as the template", r["id"])
|
|
}
|
|
if name, ok := r["template"].(string); ok {
|
|
if _, err := os.Stat(filepath.Join("..", "..", name)); err != nil {
|
|
t.Errorf("resource %v names %s: %v", r["id"], name, err)
|
|
}
|
|
}
|
|
}
|
|
packs := 0
|
|
for _, a := range m.Build.Artifacts {
|
|
if a["kind"] == "archive" && a["from"] == "files" {
|
|
packs++
|
|
if a["name"] != "files" {
|
|
t.Errorf("the templates archive is named %v, not files", a["name"])
|
|
}
|
|
}
|
|
}
|
|
if packs != 1 {
|
|
t.Errorf("the build packs files/ into %d archives, not one", packs)
|
|
}
|
|
}
|