194 lines
9.7 KiB
Go
194 lines
9.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/builder"
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// One build per change (novox/hq ADR 0350), as the controller asks it and takes it in.
|
|
|
|
// The check of a change is asked to build what a merge of it would move, in the walk's order: a module of the
|
|
// repository checked from the merge, one standing on it from its own repository as the walk builds it, and one
|
|
// that reads the repository through a context not at all. A change that moves no module builds nothing.
|
|
func TestACheckIsAskedToBuildWhatTheMergeWouldMove(t *testing.T) {
|
|
entries := []inventory.Entry{
|
|
{Manifest: catalogue.Manifest{Module: "app"}, Source: inventory.Source{Repository: "novox/shop", Seat: "git", Path: "modules/app"}},
|
|
{Manifest: catalogue.Manifest{Module: "web"}, Source: inventory.Source{Repository: "novox/web", Seat: "git", Ref: "main"}},
|
|
{Manifest: catalogue.Manifest{Module: "docs"}, Source: inventory.Source{Repository: "novox/docs", Seat: "git"}},
|
|
}
|
|
p := link.PullUpdated{Owner: "novox", Repo: "shop", Number: 7, Base: "main", Commit: "abc"}
|
|
scope := checkScope{Reach: mergeReach{Packaging: entries[2:], Plan: inventory.Plan{Tiers: [][]string{{"app", "docs"}, {"web"}}}}}
|
|
clone := func(s inventory.Source) (string, error) { return "http://forge.invalid/" + s.Repository + ".git", nil }
|
|
|
|
got := checkBuildsOf(p, scope, entries, clone)
|
|
want := []link.CheckBuild{
|
|
{Module: "app", Path: "modules/app", Merged: true, Tier: 0},
|
|
{Module: "docs", Tier: 0, Skip: "its build reads novox/shop through a context, which a check cannot hand the merge"},
|
|
{Module: "web", Repository: "http://forge.invalid/novox/web.git", Ref: "main", Tier: 1},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("the check is asked to build\n%+v\nwant\n%+v", got, want)
|
|
}
|
|
if got := checkBuildsOf(p, checkScope{}, entries, clone); got != nil {
|
|
t.Fatalf("a change that moves no module asks for builds: %+v", got)
|
|
}
|
|
}
|
|
|
|
// A walk's ask carries its leave to take a check's build, naming the pull requests of the merges it answers; a dry
|
|
// run never does, and neither does an ask that is not a walk's.
|
|
|
|
// A walk ended lets go of its merges' check builds — only once it ended, after it took what it reuses.
|
|
func TestAWalkEndedLetsGoOfItsMergesCheckBuilds(t *testing.T) {
|
|
var forgot []builder.PullRef
|
|
was := forgetChecks
|
|
forgetChecks = func(_ context.Context, _ *inventory.Inventory, pull builder.PullRef) (string, error) {
|
|
forgot = append(forgot, pull)
|
|
return "", nil
|
|
}
|
|
t.Cleanup(func() { forgetChecks = was })
|
|
p := inventory.Plan{ID: "plan-1", Delivery: &inventory.PlanDelivery{Merges: []inventory.PlanMerge{
|
|
{Repository: "novox/shop", Number: 7}}}}
|
|
|
|
p.WalkSaid = inventory.WalkStarted
|
|
forgetChecksOfWalk(t.Context(), nil, p)
|
|
p.WalkSaid = ""
|
|
forgetChecksOfWalk(t.Context(), nil, p)
|
|
if len(forgot) != 0 {
|
|
t.Fatalf("a walk not ended let go of %v, which it may still take", forgot)
|
|
}
|
|
p.WalkSaid = inventory.WalkEnded
|
|
forgetChecksOfWalk(t.Context(), nil, p)
|
|
if want := []builder.PullRef{{Owner: "novox", Repo: "shop", Number: 7}}; !reflect.DeepEqual(forgot, want) {
|
|
t.Fatalf("the walk ended let go of %v", forgot)
|
|
}
|
|
}
|
|
|
|
// A build in the scratch namespace is never registered: only the walk's copy in the module's own namespace is.
|
|
func TestABuildInTheScratchNamespaceIsNeverRegistered(t *testing.T) {
|
|
if got := inScratch([]link.MadeArtifact{{Name: "server", Reference: "store.internal:5000/app/server@sha256:aa"}}); got != "" {
|
|
t.Fatalf("a module's own build reads as a scratch build: %s", got)
|
|
}
|
|
scratch := "store.internal:5000/scratch/app/server@sha256:aa"
|
|
if got := inScratch([]link.MadeArtifact{{Name: "server", Reference: scratch}}); got != scratch {
|
|
t.Fatalf("a scratch build was not found: %q", got)
|
|
}
|
|
if got := inScratch([]link.MadeArtifact{{Name: "look", Reference: "http://store.internal:5000/v2/scratch/app/look/blobs/sha256:bb"}}); got == "" {
|
|
t.Fatal("a scratch archive was not found")
|
|
}
|
|
}
|
|
|
|
// The walk says per module what it did: "reused from …" or "built: <why>" — and a build seat from before says
|
|
// neither, which is said.
|
|
func TestAWalksBuildSaysWhetherItReusedOrBuilt(t *testing.T) {
|
|
if got := (link.BuildResult{Reused: "reused from the check of novox/shop#7"}).MadeWords(); got != "reused from the check of novox/shop#7" {
|
|
t.Fatalf("said %q", got)
|
|
}
|
|
if got := (link.BuildResult{BuiltWhy: "no check built it"}).MadeWords(); got != "built: no check built it" {
|
|
t.Fatalf("said %q", got)
|
|
}
|
|
if got := (link.BuildResult{}).MadeWords(); !strings.Contains(got, "predates ADR 0350") {
|
|
t.Fatalf("said %q", got)
|
|
}
|
|
}
|
|
|
|
// A walk's module the check's build stood in for is registered by the digest the check built — the copy in the
|
|
// module's namespace — and the walk keeps "reused from …" for it; its first-node gate judges that registered
|
|
// build as it judges one the walk built. A scratch reference is refused. Needs the store (MESH_TEST_POSTGRES), and
|
|
// is named in testdata/store-tests.
|
|
|
|
// A walk's leave names the builds its own pull requests' verdicts made at the head each merge merged — never an
|
|
// earlier head's — read once per walk, and none when they cannot be read: the builder takes nothing a verdict does
|
|
// not name (novox/hq ADR 0350 §2, issue 703).
|
|
func TestAWalksLeaveNamesTheBuildsItsPullRequestsVerdictsMade(t *testing.T) {
|
|
was := checkedBuildsOf
|
|
forgetWalkVerdicts()
|
|
t.Cleanup(func() { checkedBuildsOf = was; forgetWalkVerdicts() })
|
|
var asked [][]link.PullRef
|
|
pull := link.PullRef{Owner: "novox", Repo: "shop", Number: 7}
|
|
atHead := link.CheckedBuild{Pull: pull, Check: "build-2", Head: "bbbbbbbbbbbb", Built: link.CheckBuilt{Module: "app", Key: "r1:2"}}
|
|
earlier := link.CheckedBuild{Pull: pull, Check: "build-1", Head: "aaaaaaaaaaaa", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
|
checkedBuildsOf = func(_ context.Context, pulls []link.PullRef) ([]link.CheckedBuild, error) {
|
|
asked = append(asked, pulls)
|
|
return []link.CheckedBuild{atHead, earlier}, nil
|
|
}
|
|
pulls := []link.PullRef{pull}
|
|
heads := map[link.PullRef]string{pull: "bbbbbbbbbbbb0123456789"}
|
|
for range 3 {
|
|
if got := reuseWithVerdicts(t.Context(), "plan-1", &link.ReuseAsk{Pulls: pulls}, heads); !reflect.DeepEqual(got.Builds,
|
|
[]link.CheckedBuild{atHead}) {
|
|
t.Fatalf("the leave names %+v, not only the merged head's build", got.Builds)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(asked, [][]link.PullRef{pulls}) {
|
|
t.Fatalf("the verdicts were read %d times for one walk: %v", len(asked), asked)
|
|
}
|
|
// A merge whose head was not said takes nothing.
|
|
if got := reuseWithVerdicts(t.Context(), "plan-1", &link.ReuseAsk{Pulls: pulls}, nil); got.Builds != nil {
|
|
t.Fatalf("a merge of no said head names %+v", got.Builds)
|
|
}
|
|
checkedBuildsOf = func(context.Context, []link.PullRef) ([]link.CheckedBuild, error) {
|
|
return nil, errors.New("no bus")
|
|
}
|
|
if got := reuseWithVerdicts(t.Context(), "plan-2", &link.ReuseAsk{Pulls: pulls}, heads); got.Builds != nil {
|
|
t.Fatalf("verdicts that could not be read named %+v", got.Builds)
|
|
}
|
|
}
|
|
|
|
// A check is handed only the builds earlier checks of its own pull request at its own head made: another pull
|
|
// request's build, or an earlier head's, is never kept (novox/hq issue 703). None when they cannot be read.
|
|
func TestACheckIsHandedOnlyItsOwnHeadsBuilds(t *testing.T) {
|
|
was := checkedBuildsOf
|
|
t.Cleanup(func() { checkedBuildsOf = was })
|
|
pull := link.PullRef{Owner: "novox", Repo: "shop", Number: 7}
|
|
mine := link.CheckedBuild{Pull: pull, Check: "build-1", Head: "bbbbbbbbbbbb", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
|
earlier := link.CheckedBuild{Pull: pull, Check: "build-0", Head: "aaaaaaaaaaaa", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
|
another := link.CheckedBuild{Pull: link.PullRef{Owner: "novox", Repo: "shop", Number: 8}, Check: "build-9",
|
|
Head: "bbbbbbbbbbbb", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
|
var asked [][]link.PullRef
|
|
checkedBuildsOf = func(_ context.Context, pulls []link.PullRef) ([]link.CheckedBuild, error) {
|
|
asked = append(asked, pulls)
|
|
return []link.CheckedBuild{mine, earlier, another}, nil
|
|
}
|
|
p := link.PullUpdated{Owner: "novox", Repo: "shop", Number: 7, Commit: "bbbbbbbbbbbb0123456789"}
|
|
builds := []link.CheckBuild{{Module: "app"}}
|
|
if got := namedForTheCheck(t.Context(), p, builds); !reflect.DeepEqual(got, []link.CheckedBuild{mine}) ||
|
|
!reflect.DeepEqual(asked, [][]link.PullRef{{pull}}) {
|
|
t.Fatalf("the check is handed %+v, read for %v", got, asked)
|
|
}
|
|
checkedBuildsOf = func(context.Context, []link.PullRef) ([]link.CheckedBuild, error) {
|
|
return nil, errors.New("no bus")
|
|
}
|
|
if got := namedForTheCheck(t.Context(), p, builds); got != nil {
|
|
t.Fatalf("verdicts that could not be read named %+v", got)
|
|
}
|
|
}
|
|
|
|
func forgetWalkVerdicts() {
|
|
walkVerdicts.Lock()
|
|
defer walkVerdicts.Unlock()
|
|
walkVerdicts.read = map[string]verdictsRead{}
|
|
}
|
|
|
|
// A walk's merged heads are the forge's word for each merge; a merge announced without its head, or a pull request
|
|
// merged at two heads in one walk, has none, and its walk takes no check's build (novox/hq issue 703).
|
|
func TestAWalksMergedHeadsAreTheForgesWord(t *testing.T) {
|
|
merge := func(number int, head string) inventory.PlanMerge {
|
|
return inventory.PlanMerge{Repository: "novox/shop", Number: number, Head: head}
|
|
}
|
|
p := &inventory.Plan{Delivery: &inventory.PlanDelivery{Merges: []inventory.PlanMerge{
|
|
merge(7, "aaaaaaaaaaaa1"), merge(8, ""), merge(9, "bbbbbbbbbbbb"), merge(9, "cccccccccccc")}}}
|
|
heads := mergedHeadsOf(p)
|
|
shop := func(n int) link.PullRef { return link.PullRef{Owner: "novox", Repo: "shop", Number: n} }
|
|
if heads[shop(7)] != "aaaaaaaaaaaa1" || heads[shop(8)] != "" || heads[shop(9)] != "" {
|
|
t.Fatalf("merged heads %v", heads)
|
|
}
|
|
}
|