mesh/merge-gate out of date: judged against trunk c975dbf7b053, the trunk is now 76304d9db817; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk c975dbf7b053, the trunk is now 76304d9db817; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
Review of #329: a judge built with HOME in the workspace runs git under a configuration a pull request's script can plant, so a hook could rewrite the judge's sources before they compile and the result be kept under an honest key (issue 482, issue 704). Every judge now builds with its own HOME, configuration and Go cache outside the workspace, no git configuration and no VCS stamping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
548 lines
27 KiB
Go
548 lines
27 KiB
Go
package builder
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// **A pull request into a release branch is judged by the controller of that branch** (novox/hq ADR 0358, issue
|
|
// 704): the build seat's check builds the judge from mesh-controller read at the release branch — with mesh-host's
|
|
// validator of that branch when mesh-host has one — reads every repository beside it at that branch where it has one
|
|
// and at its trunk otherwise, and names the branch, the judge's commit and each repository read with its commit.
|
|
|
|
const releaseBranch = "feat/57-config-and-provisioning"
|
|
|
|
// gitInRepo runs git in a repository made for a test.
|
|
func gitInRepo(t *testing.T, dir string, args ...string) string {
|
|
t.Helper()
|
|
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))
|
|
}
|
|
|
|
// commitFiles writes files into a repository and commits them on the branch checked out.
|
|
func commitFiles(t *testing.T, dir string, files map[string]string) string {
|
|
t.Helper()
|
|
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)
|
|
}
|
|
}
|
|
gitInRepo(t, dir, "add", "-A")
|
|
gitInRepo(t, dir, "commit", "--quiet", "-m", "x")
|
|
return gitInRepo(t, dir, "rev-parse", "HEAD")
|
|
}
|
|
|
|
// aRepositoryWithARelease is a repository whose main holds mainFiles and, when releaseFiles is not nil, a release
|
|
// branch over them holding releaseFiles. It answers the repository, main's head and the release branch's head.
|
|
func aRepositoryWithARelease(t *testing.T, mainFiles, releaseFiles map[string]string) (string, string, string) {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
gitInRepo(t, dir, "init", "--quiet", "-b", "main")
|
|
main := commitFiles(t, dir, mainFiles)
|
|
if releaseFiles == nil {
|
|
return dir, main, ""
|
|
}
|
|
gitInRepo(t, dir, "checkout", "--quiet", "-b", releaseBranch)
|
|
head := commitFiles(t, dir, releaseFiles)
|
|
gitInRepo(t, dir, "checkout", "--quiet", "main")
|
|
return dir, main, head
|
|
}
|
|
|
|
// aJudgeOf is a controller that judges as told: its gate passes, saying who it is and the word of the node-engine
|
|
// validator it vendors — the one it was built with.
|
|
func aJudgeOf(who string) map[string]string {
|
|
return map[string]string{
|
|
"go.mod": "module example.org/judge\n\ngo 1.22\n\nrequire github.com/novox/mesh-host v0.0.0\n",
|
|
"vendor/modules.txt": "# github.com/novox/mesh-host v0.0.0\n## explicit; go 1.22\ngithub.com/novox/mesh-host/validate\n",
|
|
"vendor/github.com/novox/mesh-host/validate/v.go": "package validate\n\nconst Word = \"the vendored validator\"\n",
|
|
"vendor/github.com/novox/mesh-host/internal/declaration/d.go": "package declaration\n",
|
|
"cmd/mesh-controller/main.go": `package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/novox/mesh-host/validate"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) == 2 && os.Args[1] == "merge-gate" {
|
|
fmt.Println("usage: merge-gate --facts <file> --store <postgres>")
|
|
os.Exit(2)
|
|
}
|
|
fmt.Printf("{\"verdict\":\"pass\",\"summary\":\"%s with %s\"}\n", "` + who + `", validate.Word)
|
|
}
|
|
`,
|
|
}
|
|
}
|
|
|
|
// aHostSaying is the node-engine's validator, saying a word.
|
|
func aHostSaying(word string) map[string]string {
|
|
return map[string]string{
|
|
"validate/v.go": "package validate\n\nconst Word = \"" + word + "\"\n",
|
|
"internal/declaration/d.go": "package declaration\n",
|
|
"validate/v_test.go": "package validate\n",
|
|
"internal/declaration/x_test.go": "package declaration\n",
|
|
}
|
|
}
|
|
|
|
// aRuntimeThatBuildsTheJudge puts a container runtime on PATH that runs every container's command on this machine,
|
|
// in the directory and with the environment the check gives it — the judge's build too — and a store and a bus that
|
|
// answer. It answers the file each `go` the check runs is written to.
|
|
func aRuntimeThatBuildsTheJudge(t *testing.T) string {
|
|
t.Helper()
|
|
if _, err := exec.LookPath("go"); err != nil {
|
|
t.Skip("no Go toolchain on this machine to build a judge with")
|
|
}
|
|
bus, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { bus.Close() })
|
|
go func() {
|
|
for {
|
|
c, err := bus.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}()
|
|
dir := t.TempDir()
|
|
built := filepath.Join(dir, "built")
|
|
runtime := `#!/bin/sh
|
|
cmd="$1"; shift
|
|
case "$cmd" in
|
|
ps|rm|exec) exit 0 ;;
|
|
port) echo "` + bus.Addr().String() + `"; exit 0 ;;
|
|
run)
|
|
workdir=""; detached=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-d) detached=1; shift ;;
|
|
--rm) shift ;;
|
|
--workdir) workdir="$2"; shift 2 ;;
|
|
--env) export "$2"; shift 2 ;;
|
|
--network|--volume|--user|--label|--name|-p|-e) shift 2 ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
if [ -n "$detached" ]; then echo a-container; exit 0; fi
|
|
shift
|
|
if [ "$1" = go ]; then echo "GOCACHE=$GOCACHE GOENV=$GOENV $*" >> "` + built + `"; fi
|
|
if [ "$1" = go ] && [ "$2" = build ] && [ -n "$JUDGE_HOMES" ]; then echo "$HOME" >> "$JUDGE_HOMES"; fi
|
|
cd "$workdir" && exec "$@" ;;
|
|
esac
|
|
exit 1
|
|
`
|
|
if err := os.WriteFile(filepath.Join(dir, "docker"), []byte(runtime), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
t.Setenv("GOTOOLCHAIN", "local")
|
|
return built
|
|
}
|
|
|
|
// builds is how many times the check ran go to build a judge.
|
|
func builds(t *testing.T, file string) int {
|
|
t.Helper()
|
|
body, err := os.ReadFile(file)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return strings.Count(string(body), "go build")
|
|
}
|
|
|
|
// theReleasedMesh is the core beside a release-branch check: a controller whose main and release branch judge
|
|
// differently, a node-engine with a validator of each, and a lab with no release branch.
|
|
func theReleasedMesh(t *testing.T) (map[string]Beside, string, string, string) {
|
|
t.Helper()
|
|
controller, _, controllerRelease := aRepositoryWithARelease(t, aJudgeOf("main's controller"),
|
|
aJudgeOf("the release branch's controller"))
|
|
host, _, hostRelease := aRepositoryWithARelease(t, aHostSaying("main's validator"),
|
|
aHostSaying("the release branch's validator"))
|
|
lab, labMain, _ := aRepositoryWithARelease(t, map[string]string{"README": "the lab"}, nil)
|
|
return map[string]Beside{
|
|
"mesh-controller": {Repository: controller, Ref: releaseBranch},
|
|
"mesh-controller-main": {Repository: controller, Ref: "main"},
|
|
"mesh-host": {Repository: host, Ref: releaseBranch, Else: "main"},
|
|
"mesh-lab": {Repository: lab, Ref: releaseBranch, Else: "main"},
|
|
}, controllerRelease, hostRelease, labMain
|
|
}
|
|
|
|
// aPullRequestInto is a repository whose pull request's head is cut from base, with a change.
|
|
func aPullRequestInto(t *testing.T, files map[string]string, base string, change map[string]string) (string, string) {
|
|
t.Helper()
|
|
repo, _, _ := aRepositoryWithARelease(t, files, map[string]string{"released": "r"})
|
|
gitInRepo(t, repo, "checkout", "--quiet", "-b", "change", base)
|
|
head := commitFiles(t, repo, change)
|
|
gitInRepo(t, repo, "checkout", "--quiet", "main")
|
|
return repo, head
|
|
}
|
|
|
|
func TestACataloguePullRequestIntoAReleaseBranchIsJudgedByThatBranchsController(t *testing.T) {
|
|
built := aRuntimeThatBuildsTheJudge(t)
|
|
registry := aFactsRegistry(t)
|
|
beside, controllerRelease, hostRelease, labMain := theReleasedMesh(t)
|
|
catalogue, head := aPullRequestInto(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
|
|
CheckScript: "true\n"}, releaseBranch, map[string]string{"modules/gitea/index.ts": "x"})
|
|
cache := t.TempDir()
|
|
check := func(id string) CheckVerdict {
|
|
t.Helper()
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: catalogue, Ref: head, Owner: "novox",
|
|
Repo: "mesh-catalog", Number: 286, Base: releaseBranch, Release: releaseBranch, JudgeCache: cache,
|
|
Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Beside: beside, Toolchain: "golang"},
|
|
t.TempDir(), registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v
|
|
}
|
|
v := check("check-704-catalogue")
|
|
// The release branch's controller judged, with mesh-host's validator of the release branch.
|
|
if v.Gate == nil || v.Gate.Verdict != "pass" ||
|
|
!strings.HasSuffix(v.Gate.Summary, "the release branch's controller with the release branch's validator") {
|
|
t.Fatalf("the gate was not judged by the release branch's controller with its node-engine's validator: %+v\n%s",
|
|
v.Gate, v.Report)
|
|
}
|
|
// It names the release branch, the judge's commit, and each repository read beside it at its branch and commit.
|
|
mark := "[release " + releaseBranch + ", judged by its controller " + controllerRelease[:12] + "] "
|
|
if !strings.HasPrefix(v.Gate.Summary, mark) || !strings.HasPrefix(v.Repo.Summary, mark) {
|
|
t.Fatalf("the statuses do not name the release branch and its judge first:\n%s\n%s", v.Gate.Summary, v.Repo.Summary)
|
|
}
|
|
if v.Release != releaseBranch || v.JudgedBy != controllerRelease[:12] {
|
|
t.Fatalf("the verdict names release %q judged by %q, not %s by %s", v.Release, v.JudgedBy, releaseBranch,
|
|
controllerRelease[:12])
|
|
}
|
|
if v.BesideAt["mesh-host"] != releaseBranch || v.Beside["mesh-host"] != hostRelease[:12] {
|
|
t.Fatalf("mesh-host was read at %q (%s), not its release branch at %s", v.BesideAt["mesh-host"],
|
|
v.Beside["mesh-host"], hostRelease[:12])
|
|
}
|
|
// A repository with no release branch is read at its trunk, and says so.
|
|
if v.BesideAt["mesh-lab"] != "main" || v.Beside["mesh-lab"] != labMain[:12] {
|
|
t.Fatalf("the lab, with no release branch, was read at %q (%s), not main at %s", v.BesideAt["mesh-lab"],
|
|
v.Beside["mesh-lab"], labMain[:12])
|
|
}
|
|
if !strings.Contains(v.Report, "read beside it: ") || !strings.Contains(v.Report, "mesh-host "+releaseBranch+" at "+hostRelease[:12]) {
|
|
t.Fatalf("the report does not say what was read beside it:\n%s", v.Report)
|
|
}
|
|
// It says which validator judged: mesh-host's of the release branch, with its commit.
|
|
if want := "mesh-host's validator of " + releaseBranch + " at " + hostRelease[:12]; v.Validator != want ||
|
|
!strings.Contains(v.Report, want) {
|
|
t.Fatalf("the verdict says the validator was %q, not %q:\n%s", v.Validator, want, v.Report)
|
|
}
|
|
// Each repository read beside it is named with the branch and the commit read.
|
|
for dir := range beside {
|
|
if v.BesideAt[dir] == "" || v.Beside[dir] == "" ||
|
|
!strings.Contains(v.Report, dir+" "+v.BesideAt[dir]+" at "+v.Beside[dir]) {
|
|
t.Fatalf("%s is not named with its branch and commit: at %q, commit %q\n%s", dir, v.BesideAt[dir],
|
|
v.Beside[dir], v.Report)
|
|
}
|
|
}
|
|
if n := builds(t, built); n != 1 {
|
|
t.Fatalf("the judge was built %d times for one check", n)
|
|
}
|
|
// **The judge is built with a cache no pull request's script can write** (issue 482): a fresh one under the
|
|
// check's own root, never the workspace's shared go-cache, and no Go environment file from the workspace's HOME.
|
|
said, _ := os.ReadFile(built)
|
|
if !strings.Contains(string(said), "/mesh-check-judge-") || !strings.Contains(string(said), "GOENV=off go build") ||
|
|
strings.Contains(string(said), "/check/../go-cache") {
|
|
t.Fatalf("the release judge was built with the workspace's cache or Go environment:\n%s", said)
|
|
}
|
|
|
|
// **Built once per release-branch head**: a second check while that head stands takes the judge kept.
|
|
v = check("check-704-catalogue-again")
|
|
if !strings.HasSuffix(v.Gate.Summary, "the release branch's controller with the release branch's validator") {
|
|
t.Fatalf("the kept judge did not judge: %+v\n%s", v.Gate, v.Report)
|
|
}
|
|
if n := builds(t, built); n != 1 {
|
|
t.Fatalf("the judge of the same release-branch head was built again: %d builds", n)
|
|
}
|
|
}
|
|
|
|
func TestAControllerPullRequestIntoAReleaseBranchReadsTheNodeEngineAtThatBranch(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
registry := aFactsRegistry(t)
|
|
beside, _, hostRelease, _ := theReleasedMesh(t)
|
|
delete(beside, "mesh-controller")
|
|
controller, head := aPullRequestInto(t, aJudgeOf("the change's controller"), releaseBranch,
|
|
map[string]string{"cmd/x.txt": "x"})
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-controller", Repository: controller, Ref: head,
|
|
Owner: "novox", Repo: "mesh-controller", Number: 315, Base: releaseBranch, Release: releaseBranch,
|
|
JudgeCache: t.TempDir(), Paths: []string{"cmd/x.txt"}, Modules: []string{"mesh-controller"}, Judge: "self",
|
|
Beside: beside, Toolchain: "golang"}, t.TempDir(), registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.Gate == nil || !strings.HasSuffix(v.Gate.Summary, "the change's controller with the vendored validator") {
|
|
t.Fatalf("a controller change into a release branch did not judge itself: %+v\n%s", v.Gate, v.Report)
|
|
}
|
|
if v.BesideAt["mesh-host"] != releaseBranch || v.Beside["mesh-host"] != hostRelease[:12] {
|
|
t.Fatalf("mesh-host was read beside it at %q (%s), not at the release branch's %s", v.BesideAt["mesh-host"],
|
|
v.Beside["mesh-host"], hostRelease[:12])
|
|
}
|
|
if v.Release != releaseBranch || v.JudgedBy == "" || v.JudgedBy != v.Merge[:12] {
|
|
t.Fatalf("the verdict names release %q judged by %q, not its own merge %s", v.Release, v.JudgedBy, v.Merge)
|
|
}
|
|
}
|
|
|
|
func TestAPullRequestIntoTheTrunkIsJudgedAsBefore(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
registry := aFactsRegistry(t)
|
|
controller, controllerMain, _ := aRepositoryWithARelease(t, aJudgeOf("main's controller"),
|
|
aJudgeOf("the release branch's controller"))
|
|
host, hostMain, _ := aRepositoryWithARelease(t, aHostSaying("main's validator"),
|
|
aHostSaying("the release branch's validator"))
|
|
catalogue, head := aPullRequestInto(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
|
|
CheckScript: "true\n"}, "main", map[string]string{"modules/gitea/index.ts": "x"})
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-trunk", Repository: catalogue, Ref: head,
|
|
Owner: "novox", Repo: "mesh-catalog", Number: 1, Base: "main", JudgeCache: t.TempDir(),
|
|
Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Toolchain: "golang",
|
|
Beside: map[string]Beside{"mesh-controller": {Repository: controller, Ref: controllerMain},
|
|
"mesh-host": {Repository: host, Ref: hostMain}}}, t.TempDir(), registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.Gate == nil || v.Gate.Summary != "main's controller with the vendored validator" {
|
|
t.Fatalf("a trunk pull request was not judged by the controller beside it as before: %+v\n%s", v.Gate, v.Report)
|
|
}
|
|
if v.Release != "" || v.JudgedBy != "" || v.BesideAt != nil || strings.Contains(v.Report, "release branch") {
|
|
t.Fatalf("a trunk pull request's verdict speaks of a release branch: %q %q %v", v.Release, v.JudgedBy, v.BesideAt)
|
|
}
|
|
if v.Beside["mesh-host"] != hostMain[:12] {
|
|
t.Fatalf("mesh-host was read at %s, not the commit asked %s", v.Beside["mesh-host"], hostMain[:12])
|
|
}
|
|
}
|
|
|
|
// **Without mesh-host's release branch, the release branch's controller keeps the validator it vendors** (ADR 0358
|
|
// §2), and the verdict says so.
|
|
func TestWithoutTheNodeEnginesReleaseBranchTheJudgeKeepsItsVendoredValidator(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
registry := aFactsRegistry(t)
|
|
beside, controllerRelease, _, _ := theReleasedMesh(t)
|
|
host, hostMain, _ := aRepositoryWithARelease(t, aHostSaying("main's validator"), nil)
|
|
beside["mesh-host"] = Beside{Repository: host, Ref: releaseBranch, Else: "main"}
|
|
catalogue, head := aPullRequestInto(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
|
|
CheckScript: "true\n"}, releaseBranch, map[string]string{"modules/gitea/index.ts": "x"})
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-vendored", Repository: catalogue, Ref: head,
|
|
Owner: "novox", Repo: "mesh-catalog", Number: 286, Base: releaseBranch, Release: releaseBranch,
|
|
JudgeCache: t.TempDir(), Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Beside: beside,
|
|
Toolchain: "golang"}, t.TempDir(), registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.Gate == nil || !strings.HasSuffix(v.Gate.Summary, "the release branch's controller with the vendored validator") {
|
|
t.Fatalf("without mesh-host's release branch the judge did not keep its vendored validator: %+v\n%s", v.Gate, v.Report)
|
|
}
|
|
if v.JudgedBy != controllerRelease[:12] || v.BesideAt["mesh-host"] != "main" || v.Beside["mesh-host"] != hostMain[:12] {
|
|
t.Fatalf("judged by %q, mesh-host read at %q (%s)", v.JudgedBy, v.BesideAt["mesh-host"], v.Beside["mesh-host"])
|
|
}
|
|
if !strings.Contains(v.Validator, "controller vendors") || !strings.Contains(v.Report, v.Validator) {
|
|
t.Fatalf("the verdict does not say the vendored validator judged: %q\n%s", v.Validator, v.Report)
|
|
}
|
|
}
|
|
|
|
// **Clause 5** (ADR 0358 §5): a release branch's own pull request into its trunk is checked as any pull request into a
|
|
// trunk — by the controller beside it as the mesh runs it, the repositories at what a trunk check reads — and its
|
|
// verdict names no release branch.
|
|
func TestAReleaseBranchsPullRequestIntoItsTrunkIsJudgedByTheControllerTheMeshRuns(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
registry := aFactsRegistry(t)
|
|
controller, controllerMain, _ := aRepositoryWithARelease(t, aJudgeOf("the controller the mesh runs"),
|
|
aJudgeOf("the release branch's controller"))
|
|
host, hostMain, _ := aRepositoryWithARelease(t, aHostSaying("main's validator"),
|
|
aHostSaying("the release branch's validator"))
|
|
catalogue, _, releaseHead := aRepositoryWithARelease(t, map[string]string{
|
|
"modules/gitea/module.json": `{"module":"gitea"}`, CheckScript: "true\n"},
|
|
map[string]string{"modules/gitea/index.ts": "released"})
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-release-into-trunk", Repository: catalogue,
|
|
Ref: releaseHead, Owner: "novox", Repo: "mesh-catalog", Number: 300, Base: "main", JudgeCache: t.TempDir(),
|
|
Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Toolchain: "golang",
|
|
Beside: map[string]Beside{"mesh-controller": {Repository: controller, Ref: controllerMain},
|
|
"mesh-host": {Repository: host, Ref: hostMain}}}, t.TempDir(), registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.Gate == nil || v.Gate.Summary != "the controller the mesh runs with the vendored validator" {
|
|
t.Fatalf("the release branch's pull request into its trunk was not judged by the controller the mesh runs: %+v\n%s",
|
|
v.Gate, v.Report)
|
|
}
|
|
if v.Release != "" || v.JudgedBy != "" || v.BesideAt != nil || v.Validator != "" ||
|
|
strings.Contains(v.Report, "release branch") || strings.Contains(v.Gate.Summary, "[release") {
|
|
t.Fatalf("its verdict names a release branch: %q %q %v %q", v.Release, v.JudgedBy, v.BesideAt, v.Validator)
|
|
}
|
|
if v.Beside["mesh-host"] != hostMain[:12] || v.Beside["mesh-controller"] != controllerMain[:12] {
|
|
t.Fatalf("beside it were read %v, not the trunk's %s and %s", v.Beside, controllerMain[:12], hostMain[:12])
|
|
}
|
|
}
|
|
|
|
// **mesh-lab is read at its trunk, always** (ADR 0358): its replays run with the container runtime's socket, so a
|
|
// branch of the release's name in mesh-lab — which anyone may create — is never what a check reads.
|
|
func TestAReleaseBranchCheckReadsMeshLabAtItsTrunkEvenWhenItHasThatBranch(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
registry := aFactsRegistry(t)
|
|
beside, _, _, _ := theReleasedMesh(t)
|
|
lab, labMain, labRelease := aRepositoryWithARelease(t, map[string]string{"README": "the lab"},
|
|
map[string]string{"README": "a branch anyone could push"})
|
|
beside["mesh-lab"] = Beside{Repository: lab, Ref: releaseBranch, Else: "main"}
|
|
catalogue, head := aPullRequestInto(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
|
|
CheckScript: "true\n"}, releaseBranch, map[string]string{"modules/gitea/index.ts": "x"})
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-lab", Repository: catalogue, Ref: head,
|
|
Owner: "novox", Repo: "mesh-catalog", Number: 286, Base: releaseBranch, Release: releaseBranch,
|
|
JudgeCache: t.TempDir(), Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Beside: beside,
|
|
Toolchain: "golang"}, t.TempDir(), registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.BesideAt["mesh-lab"] != "main" || v.Beside["mesh-lab"] != labMain[:12] || v.Beside["mesh-lab"] == labRelease[:12] {
|
|
t.Fatalf("mesh-lab was read at %q (%s), not its trunk main at %s", v.BesideAt["mesh-lab"], v.Beside["mesh-lab"],
|
|
labMain[:12])
|
|
}
|
|
}
|
|
|
|
// aPlantedWorkspace is a workspace a pull request's own script has written into, as it can: a git configuration in its
|
|
// HOME whose fsmonitor hook says each directory git runs in, a git configuration under XDG_CONFIG_HOME doing the same,
|
|
// and a Go environment file that would hand the judge's build a tool of its own. It answers the workspace and the
|
|
// file the hook writes to.
|
|
func aPlantedWorkspace(t *testing.T) (string, string) {
|
|
t.Helper()
|
|
workspace, logs := t.TempDir(), t.TempDir()
|
|
ran := filepath.Join(logs, "hook-ran")
|
|
hook := filepath.Join(workspace, "hook.sh")
|
|
files := map[string]string{
|
|
hook: "#!/bin/sh\necho \"$PWD\" >> " + ran + "\n",
|
|
filepath.Join(workspace, ".gitconfig"): "[core]\n\tfsmonitor = " + hook + "\n",
|
|
filepath.Join(workspace, ".config", "git", "config"): "[core]\n\tfsmonitor = " + hook + "\n",
|
|
filepath.Join(workspace, ".config", "go", "env"): "GOFLAGS=-toolexec=" + hook + "\n",
|
|
}
|
|
for name, body := range files {
|
|
if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(name, []byte(body), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return workspace, ran
|
|
}
|
|
|
|
// judgeRanTheHook says whether the planted hook ran in a judge's tree: the controller beside the check, or the
|
|
// change's own when it judges itself.
|
|
func judgeRanTheHook(t *testing.T, ran string) string {
|
|
t.Helper()
|
|
body, _ := os.ReadFile(ran)
|
|
for _, line := range strings.Split(string(body), "\n") {
|
|
if strings.HasSuffix(line, "/check/mesh-controller") {
|
|
return string(body)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// **Every judge is built where no pull request reaches** (issue 482, ADR 0358 review): a git configuration with a
|
|
// hook, and a Go environment file, planted in the workspace by an earlier pull request's script, do not run during
|
|
// the release judge's build — while the pull request's own script, in the workspace, still meets them.
|
|
func TestAHookPlantedInTheWorkspaceDoesNotRunDuringTheReleaseJudgesBuild(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
homes := filepath.Join(t.TempDir(), "homes")
|
|
t.Setenv("JUDGE_HOMES", homes)
|
|
registry := aFactsRegistry(t)
|
|
workspace, ran := aPlantedWorkspace(t)
|
|
beside, _, _, _ := theReleasedMesh(t)
|
|
catalogue, head := aPullRequestInto(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
|
|
CheckScript: "git status >/dev/null 2>&1; true\n"}, releaseBranch, map[string]string{"modules/gitea/index.ts": "x"})
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-planted", Repository: catalogue, Ref: head,
|
|
Owner: "novox", Repo: "mesh-catalog", Number: 286, Base: releaseBranch, Release: releaseBranch,
|
|
JudgeCache: t.TempDir(), Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Beside: beside,
|
|
Toolchain: "golang"}, workspace, registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.Gate == nil || v.Gate.Verdict != "pass" {
|
|
t.Fatalf("the check did not judge: %+v\n%s", v.Gate, v.Report)
|
|
}
|
|
if body, _ := os.ReadFile(ran); !strings.Contains(string(body), "/check/mesh-catalog") {
|
|
t.Fatalf("the planted hook never ran, so this test proves nothing: %q", body)
|
|
}
|
|
if said := judgeRanTheHook(t, ran); said != "" {
|
|
t.Fatalf("the hook planted in the workspace ran during the judge's build:\n%s", said)
|
|
}
|
|
if body, _ := os.ReadFile(homes); len(body) == 0 || strings.Contains(string(body), workspace) {
|
|
t.Fatalf("the judge was built with a HOME in the workspace: %q", body)
|
|
}
|
|
}
|
|
|
|
// The same for the judge built with a node-engine change's own validator, into a release branch (ADR 0358 §2).
|
|
func TestAHookPlantedInTheWorkspaceDoesNotRunDuringTheValidatorJudgesBuild(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
homes := filepath.Join(t.TempDir(), "homes")
|
|
t.Setenv("JUDGE_HOMES", homes)
|
|
registry := aFactsRegistry(t)
|
|
workspace, ran := aPlantedWorkspace(t)
|
|
beside, _, _, _ := theReleasedMesh(t)
|
|
delete(beside, "mesh-host")
|
|
files := aHostSaying("the release branch's validator")
|
|
files["module.json"] = `{"module":"mesh-host"}`
|
|
files[CheckScript] = "git status >/dev/null 2>&1; true\n"
|
|
host, head := aPullRequestInto(t, files, releaseBranch, aHostSaying("this change's validator"))
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-planted-validator", Repository: host, Ref: head,
|
|
Owner: "novox", Repo: "mesh-host", Number: 111, Base: releaseBranch, Release: releaseBranch, Judge: "validator",
|
|
JudgeCache: t.TempDir(), Paths: []string{"validate/v.go"}, Modules: []string{"mesh-host"}, Beside: beside,
|
|
Toolchain: "golang"}, workspace, registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.Gate == nil || !strings.HasSuffix(v.Gate.Summary, "the release branch's controller with this change's validator") {
|
|
t.Fatalf("a node-engine change into a release branch was not judged by that branch's controller with its own "+
|
|
"validator: %+v\n%s", v.Gate, v.Report)
|
|
}
|
|
if body, _ := os.ReadFile(ran); !strings.Contains(string(body), "/check/mesh-host") {
|
|
t.Fatalf("the planted hook never ran, so this test proves nothing: %q", body)
|
|
}
|
|
if said := judgeRanTheHook(t, ran); said != "" {
|
|
t.Fatalf("the hook planted in the workspace ran during a judge's build:\n%s", said)
|
|
}
|
|
if body, _ := os.ReadFile(homes); strings.Count(string(body), "\n") != 2 || strings.Contains(string(body), workspace) {
|
|
t.Fatalf("the judges were not both built with a HOME outside the workspace: %q", body)
|
|
}
|
|
}
|
|
|
|
// **mesh-lab is never read at a release branch**, asked with a fallback or without (ADR 0358).
|
|
func TestTheBuildSeatNeverReadsMeshLabAtAReleaseBranch(t *testing.T) {
|
|
aRuntimeThatBuildsTheJudge(t)
|
|
registry := aFactsRegistry(t)
|
|
lab, labMain, _ := aRepositoryWithARelease(t, map[string]string{"README": "the lab"},
|
|
map[string]string{"README": "a branch anyone could push"})
|
|
for _, asked := range []Beside{{Repository: lab, Ref: releaseBranch}, {Repository: lab, Ref: releaseBranch, Else: "main"}} {
|
|
beside, _, _, _ := theReleasedMesh(t)
|
|
beside["mesh-lab"] = asked
|
|
catalogue, head := aPullRequestInto(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
|
|
CheckScript: "true\n"}, releaseBranch, map[string]string{"modules/gitea/index.ts": "x"})
|
|
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-704-lab-asked", Repository: catalogue, Ref: head,
|
|
Owner: "novox", Repo: "mesh-catalog", Number: 286, Base: releaseBranch, Release: releaseBranch,
|
|
JudgeCache: t.TempDir(), Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Beside: beside,
|
|
Toolchain: "golang"}, t.TempDir(), registry, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.BesideAt["mesh-lab"] != "main" || v.Beside["mesh-lab"] != labMain[:12] {
|
|
t.Fatalf("mesh-lab asked as %+v was read at %q (%s), not main at %s", asked, v.BesideAt["mesh-lab"],
|
|
v.Beside["mesh-lab"], labMain[:12])
|
|
}
|
|
}
|
|
}
|