package main import ( "encoding/json" "os" "os/exec" "path/filepath" "sort" "strings" "testing" ) type manifest struct { Module string `json:"module"` Tools []string `json:"tools"` Emits []string `json:"emits"` Resources []map[string]any `json:"resources"` // Contributions to other modules' seats (novox/hq ADR 0212): the vendor keys go to node-hotkeys. Contributions []struct { Seat string `json:"seat"` Kind string `json:"kind"` Content string `json:"content"` } `json:"contributions"` } // triggers is the module's contribution to node-hotkeys: its vendor keys. func (m manifest) triggers() string { for _, c := range m.Contributions { if c.Seat == "node-hotkeys" && c.Kind == "trigger" { return c.Content } } return "" } func readManifest(t *testing.T) manifest { t.Helper() raw, err := os.ReadFile("../../module.json") if err != nil { t.Fatal(err) } var m manifest if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } return m } func TestTheManifestNamesExactlyTheToolsTheBundleServes(t *testing.T) { m := readManifest(t) var served []string for _, tool := range Tools(&Machine{Root: t.TempDir(), Run: newFake(t).run}, nil) { served = append(served, tool.Name) } sort.Strings(served) listed := append([]string(nil), m.Tools...) sort.Strings(listed) if strings.Join(served, ",") != strings.Join(listed, ",") { t.Fatalf("served %v, listed %v", served, listed) } if len(m.Emits) != 1 || m.Emits[0] != "profile.switched" { t.Fatalf("emits %v", m.Emits) } } // The module names the model, never a node, a person or a user id (novox/hq ADR 0112), and every // trigger it names a service restart or reload on is one of its own resources. func TestTheManifestNamesNoMachineAndItsTriggersExist(t *testing.T) { m := readManifest(t) ids := map[string]bool{} for _, r := range m.Resources { ids[r["id"].(string)] = true } raw, _ := os.ReadFile("../../module.json") for _, banned := range []string{"jochen", "/home/", "/run/user/1000", "\"g14\"", "shanks"} { if strings.Contains(string(raw), banned) { t.Errorf("the manifest says %q", banned) } } for _, r := range m.Resources { for _, key := range []string{"restart-on", "reload-on"} { list, _ := r[key].([]any) for _, id := range list { if !ids[id.(string)] { t.Errorf("%s %s names %v, which is not a resource", r["id"], key, id) } } } } } // Every trigger runs a script the module ships, and every script parses. func TestTheVendorKeysRunTheModulesOwnScriptsAndTheyParse(t *testing.T) { m := readManifest(t) triggers := m.triggers() if triggers == "" { t.Fatal("no trigger contribution to node-hotkeys") } for _, line := range strings.Split(triggers, "\n") { f := strings.Split(line, "\t") if strings.HasPrefix(line, "#") || len(f) < 3 { continue } script := strings.Fields(f[2])[0] local := filepath.Join("../../files/bin", filepath.Base(script)) if !strings.HasPrefix(script, "/usr/local/lib/asus-zephyrus-g14/bin/") { t.Errorf("%s runs %s, which the module does not ship", f[0], script) } else if st, err := os.Stat(local); err != nil || st.Mode()&0o111 == 0 { t.Errorf("%s: %s is missing or not executable", f[0], local) } } scripts, _ := filepath.Glob("../../files/bin/*") if len(scripts) == 0 { t.Fatal("no scripts") } // The scripts are bash's. The build seat's Go toolchain carries no bash, so there they are said as // not parsed rather than failed: a missing interpreter is not a broken script. if _, err := exec.LookPath("bash"); err != nil { t.Logf("NOT PARSED: no bash on this machine, so the scripts' syntax was not checked") return } for _, s := range scripts { if out, err := exec.Command("bash", "-n", s).CombinedOutput(); err != nil { t.Errorf("%s: %v %s", s, err, out) } } }