Files
mesh-controller/internal/builder/check_test.go
T
jochen 487aa040de
mesh/merge-gate pass: every machine composes with the change as it did without (0 of 4 compose)
mesh/delivery delivered
mesh/delivery-group group feat/mesh-delivery delivered: every member is delivered
Let a walk wait for its delivery's word, and serve the delivery's owner (hq ADR 0239)
While the mesh-delivery seat has a holder on record, a merge that moves no
core module opens its walk and asks nothing until mesh-delivery or a person
says go; nothing of it is registered before its turn, so no other send
carries it. The controller keeps the planner, the gate, sending and the
walk, and gains the verbs the owner asks with: delivery-plan, -order,
-check (a group composed as one future state), deliver, delivery-stop,
delivery-walks; every walk kept is said as plan-moved.
2026-10-07 00:01:42 +02:00

417 lines
18 KiB
Go

package builder
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/artifacts"
"github.com/novox/mesh-controller/internal/facts"
)
// A merge check on the build seat (novox/hq to-be 45 §9), against a real container runtime and a real
// registry: the repository's own merge-check.sh runs with the facts the controller keeps, beside a
// throwaway store and bus of **the versions the mesh runs**, and everything raised is removed.
//
// MESH_TEST_DOCKER=1 MESH_TEST_REGISTRY=127.0.0.1:15000 go test ./internal/builder -run Check
func TestTheStoreAndBusAChecksStandsOnAreTheOnesTheMeshRuns(t *testing.T) {
if got := StoreImage("17.11"); got != "postgres:17-alpine" {
t.Errorf("a store at 17.11 is checked against %s", got)
}
if got := StoreImage("16.4 (Debian 16.4-1.pgdg120+1)"); got != "postgres:16-alpine" {
t.Errorf("a store at 16.4 is checked against %s", got)
}
if got := BusImage("2.11.17"); got != "nats:2.11.17-alpine" {
t.Errorf("a bus at 2.11.17 is checked against %s", got)
}
}
// aRepository is a git repository holding these files, committed, and its head.
func aCheckedRepository(t *testing.T, files map[string]string) (string, string) {
t.Helper()
dir := t.TempDir()
git := func(args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git("init", "--quiet", "-b", "main")
for name, body := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o755); err != nil {
t.Fatal(err)
}
}
git("add", "-A")
git("commit", "--quiet", "-m", "x")
return dir, git("rev-parse", "HEAD")
}
func checkEnvironment(t *testing.T) string {
t.Helper()
if os.Getenv("MESH_TEST_DOCKER") != "1" || os.Getenv("MESH_TEST_REGISTRY") == "" {
t.Skip("MESH_TEST_DOCKER=1 and MESH_TEST_REGISTRY: a check raises containers and reads the registry")
}
registry := os.Getenv("MESH_TEST_REGISTRY")
body, err := json.Marshal(facts.Facts{Format: facts.Format, Taken: time.Now().UTC(),
Versions: facts.Versions{Bus: "2.11.17", Store: "17.11"},
Machines: []facts.Machine{{Name: "abcdef", Length: 6}}})
if err != nil {
t.Fatal(err)
}
if _, err := (artifacts.Store{Address: registry}).PutTagged(t.Context(), facts.Repository, facts.Tag,
facts.MediaType, body); err != nil {
t.Fatal(err)
}
return registry
}
// goToolchain is the Go image a check's script runs in here: the base the controller's own image is built on.
const goToolchain = "golang@sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c"
func labelled(id string) []string {
out, _ := exec.Command("docker", "ps", "-aq", "--filter", "label="+BuildLabel+"="+id).Output()
return strings.Fields(string(out))
}
func TestACheckRunsTheRepositorysOwnScriptBesideTheMeshsVersionsAndLeavesNothing(t *testing.T) {
registry := checkEnvironment(t)
// The script proves what it was given: the facts, a store that answers, a bus that answers — and no
// container runtime socket.
script := `set -e
test -s "$MESH_FACTS"
grep -q '"bus": "2.11.17"' "$MESH_FACTS" || grep -q '"bus":"2.11.17"' "$MESH_FACTS"
case "$MESH_TEST_POSTGRES" in postgres://*127.0.0.1:*) ;; *) echo "no store: $MESH_TEST_POSTGRES"; exit 1;; esac
case "$MESH_TEST_NATS" in nats://127.0.0.1:*) ;; *) echo "no bus: $MESH_TEST_NATS"; exit 1;; esac
test "$MESH_CHECK_REPOSITORY" = "novox/hq"
test "$MESH_CHECK_CHANGED" = "a.go,b.go"
test ! -S /var/run/docker.sock || { echo "the check holds the container runtime's socket"; exit 1; }
echo checked
`
repo, head := aCheckedRepository(t, map[string]string{CheckScript: script})
id := fmt.Sprintf("check-test-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Number: 7, Paths: []string{"a.go", "b.go"}, Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
// Nothing of the graph touched: the gate a pass that says so, the repository's own check run.
if v.Gate == nil || v.Gate.Verdict != "pass" || v.Gate.Summary != noModule {
t.Errorf("the gate answered %+v", v.Gate)
}
if v.Repo == nil || v.Repo.Verdict != "pass" || !strings.Contains(v.Report, "checked") {
t.Fatalf("the repository's check answered %+v\n%s", v.Repo, v.Report)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
}
func TestAFailingCheckFailsAndOneThatCannotRunIsNeverAPass(t *testing.T) {
registry := checkEnvironment(t)
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo 'resource \"x.service\": refused'; exit 3\n"})
v, err := Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-fail-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Repo == nil || v.Repo.Verdict != "fail" || !strings.Contains(v.Repo.Summary, "refused") {
t.Fatalf("a failing script answered %+v", v.Repo)
}
// A script in a toolchain the mesh does not hold: an error, never a pass.
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "# mesh-check-toolchain: cobol\nexit 0\n"})
v, err = Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-cobol-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil || v.Repo == nil || v.Repo.Verdict != "error" {
t.Fatalf("a script in a toolchain nobody holds answered %+v, %v", v.Repo, err)
}
// Past its bound: an error, not a pass.
was := CheckTimeout
CheckTimeout = 25 * time.Second
t.Cleanup(func() { CheckTimeout = was })
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "sleep 120\n"})
v, err = Check(context.Background(), Command, CheckSpec{ID: fmt.Sprintf("check-slow-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err == nil && (v.Repo == nil || v.Repo.Verdict != "error") {
t.Fatalf("a check past its bound answered %+v", v.Repo)
}
// No facts: it cannot run, and says so.
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "exit 0\n"})
_, err = Check(t.Context(), Command, CheckSpec{ID: "check-nofacts", Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Toolchain: goToolchain}, t.TempDir(), "127.0.0.1:1", GitCredential{}, nil)
if err == nil || !strings.Contains(err.Error(), "facts snapshot") {
t.Fatalf("a check with no facts said %v", err)
}
}
// A repository that touches nothing of the graph and has no script of its own runs nothing, and says
// both: the gate a pass that names why, the repository a warning — never silent.
func TestACheckWithNothingToRunSaysSo(t *testing.T) {
repo, head := aCheckedRepository(t, map[string]string{"README.md": "x"})
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-nothing", Repository: repo, Ref: head, Owner: "novox",
Repo: "hq"}, t.TempDir(), "", GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate == nil || v.Gate.Verdict != "pass" || v.Repo == nil || v.Repo.Verdict != "warning" ||
!strings.Contains(v.Repo.Summary, CheckScript) {
t.Fatalf("a check with nothing to run said %+v / %+v", v.Gate, v.Repo)
}
// A gated change never takes that path: with no store to read the facts from, it cannot run.
_, err = Check(t.Context(), Command, CheckSpec{ID: "check-gated", Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-catalog", Modules: []string{"gitea"}}, t.TempDir(), "", GitCredential{}, nil)
if err == nil {
t.Fatal("a change touching a module ran nothing and was not refused")
}
}
// A script declares its toolchain among its first lines; go when it declares none.
func TestAScriptDeclaresItsToolchain(t *testing.T) {
for script, want := range map[string]string{
"#!/bin/sh\nset -eu\n": "go",
"#!/bin/sh\n# mesh-check-toolchain: typescript\nnpm test\n": "typescript",
"#!/bin/sh\n#mesh-check-toolchain:go\n": "go",
"#!/bin/sh\necho '# mesh-check-toolchain: python'\n": "go",
} {
if got := ScriptToolchain([]byte(script)); got != want {
t.Errorf("%q declares %q, read as %q", script, want, got)
}
}
held := map[string]string{"mesh-tools/build": "reg/mesh-tools-build@sha256:a", "mesh-tools-go/build": "reg/go@sha256:b"}
chains := ToolchainsOf(held)
if chains["typescript"] != "reg/mesh-tools-build@sha256:a" || chains["go"] != "reg/go@sha256:b" || ToolchainOf(held) != chains["go"] {
t.Fatalf("the toolchains held read as %v", chains)
}
if _, held := chains["python"]; held {
t.Error("a toolchain the mesh does not hold read as held")
}
}
// A node-engine change's validator is put in place of the one the judge vendors: its Go files, never its tests.
func TestTheChangesValidatorReplacesTheVendoredOne(t *testing.T) {
from, into := t.TempDir(), t.TempDir()
for name, body := range map[string]string{"v.go": "package validate // new", "v_test.go": "package validate"} {
if err := os.WriteFile(filepath.Join(from, name), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
if err := os.WriteFile(filepath.Join(into, "old.go"), []byte("package validate // old"), 0o644); err != nil {
t.Fatal(err)
}
if err := replaceGoFiles(from, into); err != nil {
t.Fatal(err)
}
got, _ := filepath.Glob(filepath.Join(into, "*.go"))
if len(got) != 1 || filepath.Base(got[0]) != "v.go" {
t.Fatalf("the vendored validator holds %v", got)
}
}
// aJudge is a controller that judges as told: `merge-gate` answers a warning, `module check` refuses a
// manifest that says it is broken. What the gate layer is tested against without building the real one.
var aJudge = map[string]string{
"go.mod": "module example.org/judge\n\ngo 1.22\n",
"cmd/mesh-controller/main.go": `package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
func main() {
switch {
case len(os.Args) == 2 && os.Args[1] == "merge-gate":
fmt.Println("usage: merge-gate --facts <file> --store <postgres>")
os.Exit(2)
case len(os.Args) > 2 && os.Args[1] == "module":
for _, m := range os.Args[3:] {
body, _ := os.ReadFile(m)
if strings.Contains(string(body), "broken") {
fmt.Println(m + ": refused, it says it is broken")
os.Exit(1)
}
fmt.Println(m + ": ok")
}
case os.Args[1] == "merge-gate":
for i, a := range os.Args {
if a == "--group" && i+1 < len(os.Args) {
body, _ := os.ReadFile(os.Args[i+1])
var heads []struct {
Repository string ` + "`json:\"repository\"`" + `
Tree string ` + "`json:\"tree\"`" + `
}
_ = json.Unmarshal(body, &heads)
var said []string
for _, h := range heads {
if _, err := os.Stat(h.Tree + "/module.json"); err == nil {
said = append(said, h.Repository)
}
}
fmt.Printf("{\"verdict\":\"pass\",\"summary\":\"composed with %s\"}\n", strings.Join(said, ","))
return
}
}
fmt.Println(` + "`" + `{"verdict":"warning","summary":"a merge rebuilds 14 module(s)"}` + "`" + `)
}
}
`,
}
func TestTheGateRunsWhenTheGraphIsTouchedBesideTheRepositorysOwnCheck(t *testing.T) {
registry := checkEnvironment(t)
judgeRepo, judgeHead := aCheckedRepository(t, aJudge)
beside := map[string]Beside{"mesh-controller": {Repository: judgeRepo, Ref: judgeHead}}
check := func(files map[string]string, judge string) CheckVerdict {
t.Helper()
repo, head := aCheckedRepository(t, files)
id := fmt.Sprintf("check-gate-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-catalog", Number: 9, Paths: []string{"modules/gitea/index.ts"}, Beside: beside,
Modules: []string{"gitea"}, Manifests: []string{"modules/gitea/module.json"}, Judge: judge,
Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
return v
}
v := check(map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`, CheckScript: "echo own; exit 0\n"}, "")
if v.Gate == nil || v.Gate.Verdict != "warning" || v.Gate.Summary != "a merge rebuilds 14 module(s)" ||
strings.Join(v.Gate.Modules, ",") != "gitea" || v.Verdict != "warning" {
t.Fatalf("the gate answered %+v\n%s", v.Gate, v.Report)
}
if v.Repo == nil || v.Repo.Verdict != "pass" {
t.Fatalf("its own check answered %+v", v.Repo)
}
v = check(map[string]string{"modules/gitea/module.json": `{"module":"gitea","broken":true}`}, "")
if v.Gate.Verdict != "fail" || !strings.Contains(v.Gate.Summary, "module check") || v.Repo.Verdict != "warning" {
t.Fatalf("a manifest the judge refuses answered %+v / %+v\n%s", v.Gate, v.Repo, v.Report)
}
// A fault the base branch already had is said and fails nothing; one the change brings fails.
repo, _ := aCheckedRepository(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea","broken":true}`})
git := func(args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = repo
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git("checkout", "--quiet", "-b", "change")
if err := os.WriteFile(filepath.Join(repo, "modules/gitea/index.ts"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
git("add", "-A")
git("commit", "--quiet", "-m", "y")
id := fmt.Sprintf("check-base-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: git("rev-parse", "HEAD"), Owner: "novox",
Repo: "mesh-catalog", Base: "main", Paths: []string{"modules/gitea/index.ts"}, Beside: beside,
Modules: []string{"gitea"}, Manifests: []string{"modules/gitea/module.json"}, Toolchain: goToolchain},
t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate.Verdict != "warning" || !strings.Contains(v.Report, "already") {
t.Fatalf("a fault the base already had failed the change: %+v\n%s", v.Gate, v.Report)
}
// A delivery group (novox/hq ADR 0239): the other heads cloned beside it at their heads and handed to the
// gate to compose with this one; the repository's own check is each pull request's, not the group's.
app, appHead := aCheckedRepository(t, map[string]string{"module.json": `{"module":"app"}`})
repo2, head2 := aCheckedRepository(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
CheckScript: "exit 1\n"})
gid := fmt.Sprintf("check-group-%d", time.Now().UnixNano())
v, err = Check(t.Context(), Command, CheckSpec{ID: gid, Repository: repo2, Ref: head2, Owner: "novox",
Repo: "mesh-catalog", Paths: []string{"modules/gitea/module.json"}, Beside: beside, Modules: []string{"gitea"},
Manifests: []string{"modules/gitea/module.json"}, Toolchain: goToolchain,
Group: []GroupHead{{Owner: "novox", Repo: "app", Repository: app, Ref: appHead, Paths: []string{"module.json"}}}},
t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate.Verdict != "pass" || v.Gate.Summary != "composed with novox/app" {
t.Fatalf("the group's other head was not composed: %+v\n%s", v.Gate, v.Report)
}
if v.Repo == nil || v.Repo.Verdict != "pass" || !strings.Contains(v.Repo.Summary, "group") {
t.Fatalf("a group's check ran a member's own script: %+v", v.Repo)
}
// A change to the controller judges itself: one that does not build fails its own gate.
v = check(map[string]string{"go.mod": "module x\n\ngo 1.22\n", "cmd/mesh-controller/main.go": "package main\nfunc main() { nope }\n",
"modules/gitea/module.json": "{}"}, "self")
if v.Gate.Verdict != "fail" || !strings.Contains(v.Gate.Summary, "does not build") {
t.Fatalf("a controller that does not build judged itself %+v", v.Gate)
}
}
// **Only a commit on the trunk is published** (novox/hq ADR 0238): the build seat reads, from the clone it
// just made, the branch the forge names its default and whether the commit built is on it.
func TestABuildSaysWhetherItsCommitIsOnTheTrunk(t *testing.T) {
repo, onMain := aCheckedRepository(t, map[string]string{"module.json": `{"module":"x","version":"1"}`})
git := func(dir string, args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git(repo, "checkout", "--quiet", "-b", "feature")
if err := os.WriteFile(filepath.Join(repo, "x"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
git(repo, "add", "-A")
git(repo, "commit", "--quiet", "-m", "off the trunk")
offMain := git(repo, "rev-parse", "HEAD")
git(repo, "checkout", "--quiet", "main")
clone := filepath.Join(t.TempDir(), "clone")
git(filepath.Dir(clone), "clone", "--quiet", repo, clone)
if trunk, on := trunkOf(t.Context(), Command, clone, onMain); trunk != "main" || !on {
t.Errorf("a commit on main reads as on %q: %v", trunk, on)
}
if trunk, on := trunkOf(t.Context(), Command, clone, offMain); trunk != "main" || on {
t.Errorf("a feature branch's commit reads as on %q: %v", trunk, on)
}
if got := strings.Join(branchesHolding(t.Context(), Command, clone, offMain), ","); got != "feature" {
t.Errorf("the feature branch's commit is said to be on %q", got)
}
if got := strings.Join(branchesHolding(t.Context(), Command, clone, onMain), ","); got != "feature,main" {
t.Errorf("main's commit is said to be on %q", got)
}
// A tree that says no trunk is not known — never read as on it.
if trunk, on := trunkOf(t.Context(), Command, repo, onMain); trunk != "" || on {
t.Errorf("a repository with no origin reads as trunk %q, on %v", trunk, on)
}
}