Split the unit level from the integration level in cmd, builder, link, catalogue, inventory; cheap fixtures for the archive-entry bound, the registry waits and the catalogue copy (issue 895)
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -104,38 +103,8 @@ func dockerBuilt(r *recorded) bool {
|
||||
var aPull = PullRef{Owner: "novox", Repo: "meshboard", Number: 7}
|
||||
|
||||
// aCheckBuilt is the check of aPull building the module from a tree, into the scratch namespace.
|
||||
func aCheckBuilt(t *testing.T, scratch *aScratch, tree string) CheckBuilt {
|
||||
t.Helper()
|
||||
r, run, workspace := aModuleAt(t, tree)
|
||||
return aCheckBuiltBy(t, scratch, r, run, workspace)
|
||||
}
|
||||
|
||||
// aCheckBuiltBy is aCheckBuilt of the repository given.
|
||||
func aCheckBuiltBy(t *testing.T, scratch *aScratch, r *recorded, run Runner, workspace string) CheckBuilt {
|
||||
t.Helper()
|
||||
spec := CheckSpec{ID: "build-1", 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}, workspace, "", "",
|
||||
CheckVerdict{Head: "abcdefabcdef", Trunk: "123456123456"}, GitCredential{}, nil, logging(nil))
|
||||
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
|
||||
t.Fatalf("the check did not build the module for its merge: %+v", built)
|
||||
}
|
||||
if !dockerBuilt(r) {
|
||||
t.Fatal("the check built no image")
|
||||
}
|
||||
for repository := range r.images {
|
||||
if !strings.HasPrefix(repository, ScratchPrefix) {
|
||||
t.Fatalf("the check published %s outside the scratch namespace", repository)
|
||||
}
|
||||
}
|
||||
for repository := range r.archives {
|
||||
if !strings.HasPrefix(repository, ScratchPrefix) {
|
||||
t.Fatalf("the check published %s outside the scratch namespace", repository)
|
||||
}
|
||||
}
|
||||
return built[0]
|
||||
}
|
||||
|
||||
// named is the walk's leave naming what a check's verdict says it built, as the controller reads it.
|
||||
func named(c CheckBuilt) []NamedBuild {
|
||||
@@ -159,211 +128,21 @@ func withVersion(t *testing.T, v string) {
|
||||
|
||||
// A merge whose reuse key equals its check's builds nothing again: the walk copies the check's build by digest
|
||||
// and registers that digest.
|
||||
func TestAWalkWhoseKeyIsItsChecksBuildsNothingAndRegistersTheChecksDigest(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
checked := aCheckBuilt(t, scratch, "tree-one")
|
||||
|
||||
r, run, workspace := aModuleAt(t, "tree-one")
|
||||
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
|
||||
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dockerBuilt(r) || len(r.images) > 0 || len(r.archives) > 0 {
|
||||
t.Fatalf("the walk built again: ran %v, published %v %v", r.ran, r.images, r.archives)
|
||||
}
|
||||
if got.Reused == nil || got.Why != "" {
|
||||
t.Fatalf("the walk did not say it reused the check's build: reused %+v, why %q", got.Reused, got.Why)
|
||||
}
|
||||
if got.ReuseKey != checked.Key {
|
||||
t.Fatalf("the walk's key %s is not the check's %s", got.ReuseKey, checked.Key)
|
||||
}
|
||||
if len(scratch.took) != 2 {
|
||||
t.Fatalf("the walk copied %v, not the check's two artifacts", scratch.took)
|
||||
}
|
||||
for _, made := range checked.Made {
|
||||
registered := strings.Replace(made.Reference, ScratchPrefix, "", 1)
|
||||
found := false
|
||||
for _, b := range got.Built {
|
||||
found = found || (b.Reference == registered && b.Digest == made.Digest)
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("the walk registers %+v, not the check's %s copied", got.Built, made.Reference)
|
||||
}
|
||||
}
|
||||
if image, _ := got.Manifest.Resources[0]["image"].(string); image != "registry.invalid/meshboard/server@sha256:"+strings.Repeat("a", 64) {
|
||||
t.Fatalf("the manifest pins %q, not the check's digest in the module's namespace", image)
|
||||
}
|
||||
words := got.Reused.Words()
|
||||
if !strings.HasPrefix(words, "reused from the check of novox/meshboard#7 at head abcdefabcdef (merged into trunk 123456123456)") ||
|
||||
!strings.Contains(words, "sha256:") {
|
||||
t.Fatalf("the walk says %q", words)
|
||||
}
|
||||
}
|
||||
|
||||
// A record no verdict names is refused and built instead, and the walk says so: the store takes writes from anything
|
||||
// on its network, so a record planted under another merge's key is never registered.
|
||||
func TestARecordNoVerdictNamesIsRefusedAndBuilt(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
checked := aCheckBuilt(t, scratch, "tree-one")
|
||||
for name, leave := range map[string][]NamedBuild{
|
||||
"no verdict": nil,
|
||||
"another digest": func() []NamedBuild {
|
||||
n := named(checked)
|
||||
n[0].Digests["server"] = "sha256:" + strings.Repeat("e", 64)
|
||||
return n
|
||||
}(),
|
||||
"another check": func() []NamedBuild { n := named(checked); n[0].Check = "build-9"; return n }(),
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
scratch.took = nil
|
||||
r, run, workspace := aModuleAt(t, "tree-one")
|
||||
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: leave},
|
||||
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Reused != nil || len(scratch.took) > 0 || !dockerBuilt(r) {
|
||||
t.Fatalf("a record no verdict names was taken: %+v, copied %v", got.Reused, scratch.took)
|
||||
}
|
||||
if !strings.Contains(got.Why, "no check verdict of the walk's pull requests at the head the forge merged names it") ||
|
||||
!strings.Contains(got.Why, "at head abcdefabcdef") {
|
||||
t.Fatalf("the walk says %q", got.Why)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A differing key builds, and says which part differs.
|
||||
func TestAWalkWhoseKeyDiffersBuildsAndSaysWhy(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
checked := aCheckBuilt(t, scratch, "tree-one")
|
||||
|
||||
r, run, workspace := aModuleAt(t, "tree-two")
|
||||
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
|
||||
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dockerBuilt(r) || got.Reused != nil {
|
||||
t.Fatalf("a walk whose source differs did not build: reused %+v", got.Reused)
|
||||
}
|
||||
if !strings.Contains(got.Why, "the key differs") || !strings.Contains(got.Why, partTree) {
|
||||
t.Fatalf("the walk says %q, not which part of the key differs", got.Why)
|
||||
}
|
||||
|
||||
// And a builder of another version builds too, saying so.
|
||||
withVersion(t, "builder-2")
|
||||
r, run, workspace = aModuleAt(t, "tree-one")
|
||||
got, err = BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
|
||||
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dockerBuilt(r) || !strings.Contains(got.Why, partBuilder) {
|
||||
t.Fatalf("a newer builder took the older one's build, or did not say why not: %q", got.Why)
|
||||
}
|
||||
|
||||
// No check of the walk's pull requests built it: said.
|
||||
r, run, workspace = aModuleAt(t, "tree-one")
|
||||
got, _ = BuildReusing(t.Context(), run, r, Reuse{Scratch: newScratch(), Pulls: []PullRef{aPull}},
|
||||
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if !dockerBuilt(r) || !strings.Contains(got.Why, "no check of novox/meshboard#7 built it") {
|
||||
t.Fatalf("the walk says %q", got.Why)
|
||||
}
|
||||
}
|
||||
|
||||
// A copy that fails is said and falls back to a build; it never fails the walk.
|
||||
func TestAFailedCopyOfAChecksBuildFallsBackToBuilding(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
checked := aCheckBuilt(t, scratch, "tree-one")
|
||||
scratch.failTake = true
|
||||
|
||||
r, run, workspace := aModuleAt(t, "tree-one")
|
||||
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
|
||||
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("a failed copy failed the walk: %v", err)
|
||||
}
|
||||
if !dockerBuilt(r) || got.Reused != nil || len(r.images) == 0 {
|
||||
t.Fatal("a failed copy did not fall back to a build")
|
||||
}
|
||||
if !strings.Contains(got.Why, "copying meshboard's") || !strings.Contains(got.Why, "refused the mount") {
|
||||
t.Fatalf("the walk says %q", got.Why)
|
||||
}
|
||||
for repository := range r.images {
|
||||
if strings.HasPrefix(repository, ScratchPrefix) {
|
||||
t.Fatalf("the walk's own build went into the scratch namespace: %s", repository)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only a walk takes a check's build: a plain build — a check's, a dry run's, `build --ref`'s — builds, and a commit
|
||||
// off the trunk never takes one.
|
||||
func TestOnlyAWalkOfATrunkCommitTakesAChecksBuild(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
scratch := newScratch()
|
||||
checked := aCheckBuilt(t, scratch, "tree-one")
|
||||
|
||||
r, run, workspace := aModuleAt(t, "tree-one")
|
||||
if _, err := Build(t.Context(), run, r, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil,
|
||||
Npmrc{}, GitCredential{}, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dockerBuilt(r) || len(scratch.took) > 0 {
|
||||
t.Fatal("a build not asked to reuse took a check's build")
|
||||
}
|
||||
|
||||
r, _, workspace = aModuleAt(t, "tree-one") // no trunk said: off it
|
||||
got, err := BuildReusing(t.Context(), r.run, r, Reuse{Scratch: scratch, Named: named(checked)}, "https://forge.invalid/meshboard.git",
|
||||
"mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dockerBuilt(r) || got.Reused != nil || !strings.Contains(got.Why, "not on its trunk") {
|
||||
t.Fatalf("a commit off the trunk took a check's build: %q", got.Why)
|
||||
}
|
||||
}
|
||||
|
||||
// A check that did not pass builds nothing for its merge, and one asked to build nothing builds nothing.
|
||||
func TestACheckThatFailedBuildsNothingForItsMerge(t *testing.T) {
|
||||
if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "fail"}}) == "" {
|
||||
t.Fatal("a check whose repository layer failed builds for its merge")
|
||||
}
|
||||
if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "warning"}}) != "" {
|
||||
t.Fatal("a check that passed with a warning builds nothing for its merge")
|
||||
}
|
||||
r, run, workspace := aModuleAt(t, "tree-one")
|
||||
if built := buildForTheMerge(t.Context(), run, CheckSpec{ID: "build-1"}, CheckBuilding{Publish: r, Scratch: newScratch()},
|
||||
workspace, "", "", CheckVerdict{}, GitCredential{}, nil, logging(nil)); built != nil || len(r.ran) > 0 {
|
||||
t.Fatalf("a check moving no module built %v, ran %v", built, r.ran)
|
||||
}
|
||||
}
|
||||
|
||||
// A module whose source does not pin its build has no key and is not built by the check; what stands after it
|
||||
// is not built either.
|
||||
func TestACheckBuildsNoModuleWithoutAKeyNorWhatStandsAfterIt(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
r, run, workspace := aModuleAt(t, "tree-one")
|
||||
r.contents["mod/"+ManifestName] = `{"module":"meshboard","version":"1","build":{"artifacts":[
|
||||
{"name":"mirror","kind":"upstream","from":"docker.io/library/alpine:3"}]}}`
|
||||
spec := CheckSpec{ID: "build-1", Owner: "novox", Repo: "meshboard", Number: 7, Builds: []CheckBuildSpec{
|
||||
{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod", Tier: 0},
|
||||
{Module: "after", Repository: "https://forge.invalid/after.git", Tier: 1}}}
|
||||
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: newScratch()}, workspace, "", "",
|
||||
CheckVerdict{}, GitCredential{}, nil, logging(nil))
|
||||
if len(built) != 2 || !strings.Contains(built[0].NotBuilt, "named by tag") ||
|
||||
!strings.Contains(built[1].NotBuilt, "stands after meshboard") {
|
||||
t.Fatalf("built %+v", built)
|
||||
}
|
||||
if len(r.images) > 0 {
|
||||
t.Fatalf("a module without a key was published: %v", r.images)
|
||||
}
|
||||
}
|
||||
|
||||
// What the key names: every input a fresh build could differ by.
|
||||
func TestTheReuseKeyNamesEveryInputOfTheBuild(t *testing.T) {
|
||||
@@ -642,86 +421,16 @@ func TestABuildTwoChecksShareGoesWithTheLastOfThem(t *testing.T) {
|
||||
|
||||
// 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()
|
||||
return aCheckOf(t, scratch, aPull, "abcdefabcdef", tree, leave)
|
||||
}
|
||||
|
||||
// aCheckOf is a later check (build-2) of a pull request at a head, the trunk moved since build-1.
|
||||
func aCheckOf(t *testing.T, scratch *aScratch, pull PullRef, head, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
|
||||
t.Helper()
|
||||
r, run, workspace := aModuleAt(t, tree)
|
||||
spec := CheckSpec{ID: "build-2", Owner: pull.Owner, Repo: pull.Repo, Number: pull.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: head, 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).
|
||||
@@ -767,25 +476,3 @@ func TestABuildALaterCheckKeptStaysWhenTheCheckThatBuiltItGoes(t *testing.T) {
|
||||
|
||||
// Only an earlier check of the same pull request at the same head is kept from: another head of it, or another pull
|
||||
// request whose verdict names the same key and digests, builds (novox/hq issue 703).
|
||||
func TestACheckKeepsNoBuildOfAnotherHeadOrPullRequest(t *testing.T) {
|
||||
withVersion(t, "builder-1")
|
||||
other := PullRef{Owner: "novox", Repo: "meshboard", Number: 8}
|
||||
for name, c := range map[string]struct {
|
||||
pull PullRef
|
||||
head string
|
||||
}{
|
||||
"another head": {aPull, "999999999999"},
|
||||
"another pull request": {other, "abcdefabcdef"},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
scratch := newScratch()
|
||||
first := aCheckBuilt(t, scratch, "tree-one")
|
||||
leave := named(first)
|
||||
leave[0].Pull = c.pull // as if its own verdict named it
|
||||
again, r := aCheckOf(t, scratch, c.pull, c.head, "tree-one", leave)
|
||||
if !dockerBuilt(r) || again.Kept != "" {
|
||||
t.Fatalf("a build of %s was kept: %+v", name, again)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user