Files
mesh-controller/internal/builder/replay716_test.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
2026-10-15 21:27:33 +00:00

159 lines
6.2 KiB
Go

package builder
import (
"context"
"errors"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
// novox/hq issue 716: a check built the images its merge moves one after another, and only after its merge-check.sh
// had passed — 3 to 6 minutes on a check of about 5. The modules of a tier stand on none of each other and are built
// at the same time; with a judge cache, beside the tests; a check that does not pass lets go of them.
// atOnce is a fake engine whose compiles wait until every one of a tier's has started: builds one after another never
// get there, and the first one's compile says so.
type atOnce struct {
mu sync.Mutex
r *recorded
want int
started int
all chan struct{}
shared string
}
func (a *atOnce) run(ctx context.Context, dir, name string, args ...string) (string, error) {
if name == "docker" && len(args) > 0 && args[0] == "run" && strings.Contains(strings.Join(args, " "), "go build") {
a.mu.Lock()
a.started++
if a.started == a.want {
close(a.all)
}
a.mu.Unlock()
select {
case <-a.all:
case <-time.After(5 * time.Second):
return "", errors.New("built alone: no other module of its tier was being built")
}
}
a.mu.Lock()
defer a.mu.Unlock()
return onTrunk{recorded: a.r}.run(ctx, dir, name, args...)
}
func (a *atOnce) PublishImage(ctx context.Context, localTag, repository string) (string, error) {
a.mu.Lock()
defer a.mu.Unlock()
return a.r.PublishImage(ctx, localTag, repository)
}
func (a *atOnce) PublishArchive(ctx context.Context, repository string, body []byte, digest string) (string, error) {
a.mu.Lock()
defer a.mu.Unlock()
return a.r.PublishArchive(ctx, repository, body, digest)
}
// aLockedScratch is the scratch namespace in memory, said to by builds at once.
type aLockedScratch struct {
mu sync.Mutex
*aScratch
discarded []CheckBuild
}
func (s *aLockedScratch) Keep(ctx context.Context, p PullRef, c string, at time.Time, b []CheckBuild) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.aScratch.Keep(ctx, p, c, at, b)
}
func (s *aLockedScratch) Find(ctx context.Context, module, key string) (CheckBuild, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.aScratch.Find(ctx, module, key)
}
func (s *aLockedScratch) Discard(_ context.Context, b []CheckBuild) error {
s.mu.Lock()
defer s.mu.Unlock()
s.discarded = append(s.discarded, b...)
return nil
}
// threeAtOnce are three Go modules in one tier, each from its own repository.
func threeAtOnce(t *testing.T) (*atOnce, CheckSpec, map[string]string) {
t.Helper()
r := &recorded{tree: "tree-of-the-merge", contents: aGoModule("mesh-controller"),
secondary: map[string]map[string]string{}}
spec := CheckSpec{ID: "build-716", Owner: "novox", Repo: "mesh-controller", Number: 716,
Builds: []CheckBuildSpec{{Module: "mesh-controller", Merged: true, Tier: 0}}}
for _, module := range []string{"build-agent", "route-proxy"} {
repository := "https://forge.invalid/" + module + ".git"
r.secondary[repository] = aGoModule(module)
spec.Builds = append(spec.Builds, CheckBuildSpec{Module: module, Repository: repository, Ref: "main", Tier: 0})
}
held := map[string]string{"mesh-tools-go/build": "registry.invalid/mesh-tools-go/build@sha256:" + strings.Repeat("b", 64)}
return &atOnce{r: r, want: 3, all: make(chan struct{})}, spec, held
}
// The modules of one tier are built at the same time, each in a working copy of its own.
func TestReplay716TheModulesOfATierAreBuiltAtTheSameTime(t *testing.T) {
withVersion(t, "builder-1")
engine, spec, held := threeAtOnce(t)
scratch := &aLockedScratch{aScratch: newScratch()}
built := buildForTheMerge(t.Context(), engine.run, spec, CheckBuilding{Publish: engine, Scratch: scratch, Held: held},
t.TempDir(), filepath.Join(t.TempDir(), "merged"), "2d22279b1d07",
CheckVerdict{Head: "98e856874913", Trunk: "c975dbf7b053"}, GitCredential{}, nil, logging(nil))
if builtCount(built) != 3 {
t.Fatalf("the check built %d of 3 module(s) of one tier: %+v", builtCount(built), built)
}
if len(scratch.builds) != 3 {
t.Fatalf("the check kept %d build(s), not 3", len(scratch.builds))
}
}
// With a judge cache, the builds run while the tests run, where no container of the check reaches; a check that passed
// keeps them, and one that failed lets go of every one and names none.
func TestReplay716TheBuildsRunBesideTheTestsAndAFailedCheckLetsGoOfThem(t *testing.T) {
withVersion(t, "builder-1")
workspace := t.TempDir()
if _, err := buildsBesideTheTests(filepath.Join(workspace, "judge-cache"), workspace, "build-716"); err == nil {
t.Fatal("a judge cache inside the workspace was built in, where a pull request's script reaches")
}
for _, passed := range []bool{true, false} {
cache := t.TempDir()
place, err := buildsBesideTheTests(cache, workspace, "build-716")
if err != nil || !withinDir(cache, place) || withinDir(workspace, place) {
t.Fatalf("the builds' place %q (%v) is not the judge cache's, outside the workspace", place, err)
}
engine, spec, held := threeAtOnce(t)
scratch := &aLockedScratch{aScratch: newScratch()}
spec.BuildWith = &CheckBuilding{Publish: engine, Scratch: scratch, Held: held}
judged := CheckVerdict{Head: "98e856874913", Trunk: "c975dbf7b053", Merge: "2d22279b1d07"}
beside := startBesideTheTests(t.Context(), engine.run, spec, place, filepath.Join(t.TempDir(), "merged"),
judged, GitCredential{}, nil, logging(nil))
// The tests: the builds are under way before the check has a verdict.
select {
case <-engine.all:
case <-time.After(5 * time.Second):
t.Fatal("nothing was built while the tests ran")
}
verdict := CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "pass"}}
if !passed {
verdict.Repo.Verdict = "fail"
}
built := beside.settle(t.Context(), verdict)
beside.abandon()
switch {
case passed && (builtCount(built) != 3 || len(scratch.builds) != 3 || len(scratch.discarded) != 0):
t.Fatalf("a check that passed: built %+v, kept %d, let go of %d", built, len(scratch.builds),
len(scratch.discarded))
case !passed && (built != nil || len(scratch.builds) != 0 || len(scratch.discarded) != 3):
t.Fatalf("a check that failed: named %+v, kept %d, let go of %d", built, len(scratch.builds),
len(scratch.discarded))
}
}
}