Keep judge builds warm in the build agent's judge cache, and leave nothing on the host
mesh/delivery-group group fix/704-release-branch-judge rejected: its order contradicts itself: novox/mesh-catalog@47ef2644aca1 and novox/mesh-controller@2bee29…
mesh/merge-gate head 2bee290eea56 merged into trunk 76304d9db817 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 2bee290eea56 merged into trunk 76304d9db817 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
mesh/delivery-group group fix/704-release-branch-judge rejected: its order contradicts itself: novox/mesh-catalog@47ef2644aca1 and novox/mesh-controller@2bee29…
mesh/merge-gate head 2bee290eea56 merged into trunk 76304d9db817 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 2bee290eea56 merged into trunk 76304d9db817 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
Review of #329: a judge's home made with a temporary directory inside the build agent is resolved on the host by the container runtime, leaving a cold cache behind on every check, and a kept judge was lost on every redeploy. Judge builds now use the judge cache the build agent declares (MESH_JUDGE_CACHE), mounted into judge builds only: one warm Go cache, the kept judges, and a fresh home per check removed after it (hq ADR 0358 §6, issue 704, issue 482). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -130,6 +131,7 @@ case "$cmd" in
|
||||
ps|rm|exec) exit 0 ;;
|
||||
port) echo "` + bus.Addr().String() + `"; exit 0 ;;
|
||||
run)
|
||||
if [ -n "$RUNS" ]; then echo "$*" >> "$RUNS"; fi
|
||||
workdir=""; detached=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
@@ -256,12 +258,16 @@ func TestACataloguePullRequestIntoAReleaseBranchIsJudgedByThatBranchsController(
|
||||
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.
|
||||
// **The judge is built with the judge cache's Go cache** (ADR 0358 §6), which no check's container mounts, and no
|
||||
// Go environment file.
|
||||
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)
|
||||
if !strings.Contains(string(said), "GOCACHE="+filepath.Join(cache, "go-cache")+" GOENV=off go build") {
|
||||
t.Fatalf("the release judge was not built with the judge cache's Go cache:\n%s", said)
|
||||
}
|
||||
// **Clause 5**: the head was merged onto the release branch's head, which the verdict names as its trunk commit,
|
||||
// so the release branch's move puts it out of date as a trunk's move does (ADR 0323 §4, the forge's holder).
|
||||
if releaseHead := gitInRepo(t, catalogue, "rev-parse", releaseBranch); v.Trunk != releaseHead[:12] {
|
||||
t.Fatalf("the verdict names trunk %q, not the release branch's head %s", v.Trunk, releaseHead[:12])
|
||||
}
|
||||
|
||||
// **Built once per release-branch head**: a second check while that head stands takes the judge kept.
|
||||
@@ -545,3 +551,104 @@ func TestTheBuildSeatNeverReadsMeshLabAtAReleaseBranch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **Judges are built warm and where no pull request reaches** (ADR 0358 §6): every judge build uses the judge cache's
|
||||
// one Go cache, kept between checks; its home is made fresh under the judge cache and removed after; nothing is left in
|
||||
// a temporary directory of the host; and no container a check runs for the pull request mounts the judge cache.
|
||||
func TestJudgesAreBuiltWarmFromAJudgeCacheNoCheckContainerMounts(t *testing.T) {
|
||||
built := aRuntimeThatBuildsTheJudge(t)
|
||||
runs := filepath.Join(t.TempDir(), "runs")
|
||||
t.Setenv("RUNS", runs)
|
||||
tmp := t.TempDir()
|
||||
t.Setenv("TMPDIR", tmp)
|
||||
registry := aFactsRegistry(t)
|
||||
cache := t.TempDir()
|
||||
controller, controllerMain, _ := aRepositoryWithARelease(t, aJudgeOf("the controller the mesh runs"), nil)
|
||||
beside := map[string]Beside{"mesh-controller": {Repository: controller, Ref: controllerMain}}
|
||||
for i := range 2 {
|
||||
catalogue, head := aPullRequestInto(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
|
||||
CheckScript: "true\n"}, "main", map[string]string{"modules/gitea/index.ts": fmt.Sprint(i)})
|
||||
v, err := Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-704-warm-%d", i), Repository: catalogue,
|
||||
Ref: head, Owner: "novox", Repo: "mesh-catalog", Number: 1, Base: "main", JudgeCache: cache,
|
||||
Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"}, Toolchain: "golang", Beside: beside},
|
||||
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("check %d was not judged: %+v\n%s", i, v.Gate, v.Report)
|
||||
}
|
||||
if i == 0 {
|
||||
if entries, _ := os.ReadDir(filepath.Join(cache, "go-cache")); len(entries) == 0 {
|
||||
t.Fatal("the judge's build left nothing in the judge cache's Go cache to build the next judge warm")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Both judges built with the one Go cache of the judge cache.
|
||||
said, _ := os.ReadFile(built)
|
||||
if n := strings.Count(string(said), "GOCACHE="+filepath.Join(cache, "go-cache")+" GOENV=off go build"); n != 2 {
|
||||
t.Fatalf("%d of 2 judge builds used the judge cache's Go cache:\n%s", n, said)
|
||||
}
|
||||
// Nothing left behind: no home under the judge cache, nothing in the host's temporary directory.
|
||||
if left, _ := os.ReadDir(filepath.Join(cache, "homes")); len(left) != 0 {
|
||||
t.Fatalf("judge builds left %d home(s) under the judge cache", len(left))
|
||||
}
|
||||
if left, _ := os.ReadDir(tmp); len(left) != 0 {
|
||||
t.Fatalf("a check left %d entr(ies) in the temporary directory, e.g. %s", len(left), left[0].Name())
|
||||
}
|
||||
// Only a judge build mounts the judge cache: no container the check runs for the pull request does.
|
||||
body, _ := os.ReadFile(runs)
|
||||
judgeBuilds := 0
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(body)), "\n") {
|
||||
judging := strings.Contains(line, " go build ")
|
||||
if judging {
|
||||
judgeBuilds++
|
||||
if !strings.Contains(line, "--volume "+filepath.Join(cache, "go-cache")+":") {
|
||||
t.Fatalf("a judge build did not mount the judge cache's Go cache: %s", line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.Contains(line, cache) {
|
||||
t.Fatalf("a container the check runs for the pull request names the judge cache: %s", line)
|
||||
}
|
||||
}
|
||||
if judgeBuilds != 2 {
|
||||
t.Fatalf("%d judge builds were run, not 2", judgeBuilds)
|
||||
}
|
||||
}
|
||||
|
||||
// Without a judge cache, a judge is built cold under the check's own root — never in a temporary directory of the
|
||||
// host — and a judge cache inside the workspace is refused.
|
||||
func TestWithoutAJudgeCacheAJudgeIsBuiltColdUnderTheChecksRoot(t *testing.T) {
|
||||
built := aRuntimeThatBuildsTheJudge(t)
|
||||
tmp := t.TempDir()
|
||||
t.Setenv("TMPDIR", tmp)
|
||||
registry := aFactsRegistry(t)
|
||||
controller, controllerMain, _ := aRepositoryWithARelease(t, aJudgeOf("the controller the mesh runs"), nil)
|
||||
beside := map[string]Beside{"mesh-controller": {Repository: controller, Ref: controllerMain}}
|
||||
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"})
|
||||
workspace := t.TempDir()
|
||||
spec := CheckSpec{ID: "check-704-cold", Repository: catalogue, Ref: head, Owner: "novox", Repo: "mesh-catalog",
|
||||
Number: 1, Base: "main", Paths: []string{"modules/gitea/index.ts"}, Modules: []string{"gitea"},
|
||||
Toolchain: "golang", Beside: beside}
|
||||
v, err := Check(t.Context(), Command, spec, workspace, registry, GitCredential{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Gate == nil || v.Gate.Verdict != "pass" {
|
||||
t.Fatalf("the check was not judged: %+v\n%s", v.Gate, v.Report)
|
||||
}
|
||||
said, _ := os.ReadFile(built)
|
||||
if !strings.Contains(string(said), "GOCACHE="+filepath.Join(workspace, "check", "judge-go-cache")+" GOENV=off go build") {
|
||||
t.Fatalf("without a judge cache the judge was not built under the check's root:\n%s", said)
|
||||
}
|
||||
if left, _ := os.ReadDir(tmp); len(left) != 0 {
|
||||
t.Fatalf("a check left %d entr(ies) in the temporary directory", len(left))
|
||||
}
|
||||
spec.JudgeCache = filepath.Join(workspace, "judge-cache")
|
||||
if _, err := Check(t.Context(), Command, spec, workspace, registry, GitCredential{}, nil); err == nil ||
|
||||
!strings.Contains(err.Error(), "inside the workspace") {
|
||||
t.Fatalf("a judge cache inside the workspace was taken: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user