Keep a check build whose key is unchanged instead of building it again
mesh/merge-gate head 1666747a2246 merged into trunk 04730b54a8f2 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 1666747a2246 merged into trunk 04730b54a8f2 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 1666747a2246 merged into trunk 04730b54a8f2 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 1666747a2246 merged into trunk 04730b54a8f2 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
A fresh check asked when only the trunk moved rebuilt every image, though the reuse key said nothing it is built from changed: a controller check took about 12 minutes instead of 5.5. The check now keeps a build kept under its key when a verdict of the build seat names that key and every digest, names it as the build's own check so a walk takes it, and the cleanup keeps it while any check names it (novox/hq issue 703).
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -625,3 +626,122 @@ func TestABuildTwoChecksShareGoesWithTheLastOfThem(t *testing.T) {
|
||||
t.Fatal("the build stayed after the last check naming it went")
|
||||
}
|
||||
}
|
||||
|
||||
// aCheckAgain is a later check (build-2) of aPull building the module from a tree, with the builds the build seat's
|
||||
// verdicts name for it (novox/hq issue 703).
|
||||
func aCheckAgain(t *testing.T, scratch *aScratch, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
|
||||
t.Helper()
|
||||
r, run, workspace := aModuleAt(t, tree)
|
||||
spec := CheckSpec{ID: "build-2", Owner: aPull.Owner, Repo: aPull.Repo, Number: aPull.Number,
|
||||
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
|
||||
Ref: "main"}}}
|
||||
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch, Named: leave},
|
||||
workspace, "", "", CheckVerdict{Head: "fedcbafedcba", Trunk: "654321654321"}, GitCredential{}, nil, logging(nil))
|
||||
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
|
||||
t.Fatalf("the second check did not answer the module for its merge: %+v", built)
|
||||
}
|
||||
return built[0], r
|
||||
}
|
||||
|
||||
// A check asked again when only the trunk moved, whose key is unchanged, builds nothing: it keeps the build an
|
||||
// earlier verdict names and names it by key and digest, so the walk can take it (novox/hq issue 703).
|
||||
func TestASecondCheckWhoseKeyIsUnchangedBuildsNothingAndNamesTheEarlierBuild(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
first := aCheckBuilt(t, scratch, "tree-one")
|
||||
|
||||
again, r := aCheckAgain(t, scratch, "tree-one", named(first))
|
||||
if dockerBuilt(r) || len(r.images) > 0 || len(r.archives) > 0 {
|
||||
t.Fatalf("the second check built again: ran %v, published %v %v", r.ran, r.images, r.archives)
|
||||
}
|
||||
if again.Key != first.Key || again.Kept != "build-1" || !reflect.DeepEqual(again.Made, first.Made) {
|
||||
t.Fatalf("the second check names %+v, not the first check's build %+v", again, first)
|
||||
}
|
||||
// The walk takes it by what the second verdict names.
|
||||
leave := named(again)
|
||||
leave[0].Check = again.Kept
|
||||
w, run, workspace := aModuleAt(t, "tree-one")
|
||||
got, err := BuildReusing(t.Context(), run, w, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: leave},
|
||||
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if err != nil || got.Reused == nil || dockerBuilt(w) {
|
||||
t.Fatalf("the walk did not take the kept build: %v, why %q", err, got.Why)
|
||||
}
|
||||
}
|
||||
|
||||
// A check whose key changed builds, as before.
|
||||
func TestASecondCheckWhoseKeyChangedBuilds(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
first := aCheckBuilt(t, scratch, "tree-one")
|
||||
|
||||
again, r := aCheckAgain(t, scratch, "tree-two", named(first))
|
||||
if !dockerBuilt(r) || again.Kept != "" || again.Key == first.Key {
|
||||
t.Fatalf("a check whose key changed did not build: %+v", again)
|
||||
}
|
||||
}
|
||||
|
||||
// A scratch record no verdict names is never kept: the store takes writes from anything on its network, so a
|
||||
// check builds rather than name a planted record (novox/hq issue 703, ADR 0350 §2).
|
||||
func TestASecondCheckKeepsNoRecordAVerdictDoesNotName(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
first := aCheckBuilt(t, scratch, "tree-one")
|
||||
for name, leave := range map[string][]NamedBuild{
|
||||
"no verdict": nil,
|
||||
"another digest": func() []NamedBuild {
|
||||
n := named(first)
|
||||
n[0].Digests["server"] = "sha256:" + strings.Repeat("e", 64)
|
||||
return n
|
||||
}(),
|
||||
"another key": func() []NamedBuild { n := named(first); n[0].Key = "r1:" + strings.Repeat("f", 64); return n }(),
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
again, r := aCheckAgain(t, scratch, "tree-one", leave)
|
||||
if !dockerBuilt(r) || again.Kept != "" {
|
||||
t.Fatalf("a record no verdict names was kept: %+v", again)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A build a later check kept stays when the check that built it goes: its artifacts and its record, which the later
|
||||
// check's verdict names (novox/hq issue 703, ADR 0350 §5).
|
||||
func TestABuildALaterCheckKeptStaysWhenTheCheckThatBuiltItGoes(t *testing.T) {
|
||||
m := &aTaggingRegistry{aRegistryOfRepositories: newRegistryOfRepositories(), tags: map[string]map[string]string{}}
|
||||
server := httptest.NewServer(m.serve())
|
||||
defer server.Close()
|
||||
address := strings.TrimPrefix(server.URL, "http://")
|
||||
registry := Registry{Address: address}
|
||||
ctx := t.Context()
|
||||
now := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
|
||||
|
||||
body := `{"mediaType":"` + mediaManifestOCI + `","layers":[]}`
|
||||
digest := digestOf([]byte(body))
|
||||
m.manifests["scratch/meshboard/server@"+digest] = []byte(body)
|
||||
key := "r1:" + strings.Repeat("1", 64)
|
||||
b := CheckBuild{Key: key, Module: "meshboard", Pull: aPull, Check: "build-1", At: now,
|
||||
Made: []catalogue.Built{{Name: "server", Kind: "image", Reference: address + "/scratch/meshboard/server@" + digest}}}
|
||||
if err := registry.Keep(ctx, aPull, "build-1", now, []CheckBuild{b}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Another pull request's check keeps it: its own document names the build, which stays the first check's.
|
||||
other := PullRef{Owner: "novox", Repo: "meshboard", Number: 8}
|
||||
if err := registry.Keep(ctx, other, "build-2", now, []CheckBuild{b}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
held := func() bool { _, ok := m.manifests["scratch/meshboard/server@"+digest]; return ok }
|
||||
found := func() bool { _, ok, _ := registry.Find(ctx, "meshboard", key); return ok }
|
||||
|
||||
if n, err := registry.Forget(ctx, aPull); err != nil || n != 1 {
|
||||
t.Fatalf("forgot %d (%v)", n, err)
|
||||
}
|
||||
if !held() || !found() {
|
||||
t.Fatalf("the build a later check names went with the check that built it: held %v, record %v", held(), found())
|
||||
}
|
||||
if n, err := registry.Forget(ctx, other); err != nil || n != 1 {
|
||||
t.Fatalf("forgot %d (%v)", n, err)
|
||||
}
|
||||
if held() || found() {
|
||||
t.Fatalf("the build stayed after the last check naming it went: held %v, record %v", held(), found())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user