mesh/merge-gate pass: builds asus-zephyrus-g14, i3status-rust, power → ace, g14, novox, shanks; no bus step; every machine composes with the change as it d…
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/delivery delivered
The Go toolchain the repository check runs in carries no bash, so the scripts' syntax check failed there on a missing interpreter, not a broken script.
128 lines
3.8 KiB
Go
128 lines
3.8 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|