Move no module whose fingerprint is unchanged, and let a key's build stand on the held build-agent build of the same digests at the switch (issue 1851, A9)
This commit is contained in:
@@ -132,18 +132,3 @@ func TestACheckIsHandedOnlyItsOwnHeadsBuilds(t *testing.T) {
|
||||
t.Fatalf("verdicts that could not be read named %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,3 +190,33 @@ func TestAKeysRecordKeepsTheKeepSetAndReadsItsManifest(t *testing.T) {
|
||||
t.Fatalf("the key's record does not read its manifest's context: %s", text)
|
||||
}
|
||||
}
|
||||
|
||||
// A9 (ADR 0453 §6, ADR 0499 consequences): at the switch, a key's build whose every artifact has the digest of the
|
||||
// build-agent build the mesh holds (`src1:`) is no move: it is registered with that build's artifacts, and a walk sends
|
||||
// nothing for it.
|
||||
func TestAKeyWithTheHeldBuildsDigestsIsNoMove(t *testing.T) {
|
||||
open := theCatalogue(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
withTrunkList(t, true)
|
||||
keys, trunk := aKeyFor("sudo", "6", fpOf('6'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789")
|
||||
k := keys.keys["sudo"]
|
||||
held := inventory.Build{ID: "build-sudo-old", Module: "sudo", Repository: "novox/mesh-catalog", Path: "modules/sudo",
|
||||
Commit: "0ld0ld0ld0ld0ld0", SourceFingerprint: "src1:" + strings.Repeat("9", 64),
|
||||
Made: []inventory.Artifact{{Name: "a", Kind: "archive", Reference: k.Build.Artifacts[0].Reference}}}
|
||||
held.Manifest, _ = json.Marshal(catalogue.Manifest{Module: "sudo", Version: "held"})
|
||||
if err := inv.RecordBuild(ctx, held); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, kept, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
same, err := inv.SameSourceCommits(ctx, "sudo", kept.ID)
|
||||
if err != nil || !same["0ld0ld0ld0ld0ld0"] {
|
||||
t.Fatalf("A9: the key's build does not stand on the held build of the same digests: %v %v", same, err)
|
||||
}
|
||||
if shelf, _ := inv.Catalogue(ctx); shelf["sudo"].Version != "held" {
|
||||
t.Fatalf("A9: sudo is registered as %q, not with the held build's artifacts", shelf["sudo"].Version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,29 +451,6 @@ var checkedBuildsOf = func(ctx context.Context, pulls []link.PullRef) ([]link.Ch
|
||||
return link.ReadCheckBuilds(ctx, js, buildSeatHeld(ctx), pulls, time.Now().Add(-builder.CheckBuildsKept))
|
||||
}
|
||||
|
||||
// mergedHeadsOf are the head commits the walk's merges merged, by pull request (novox/hq issue 703): a pull request
|
||||
// merged twice in one walk, or whose head the announcement did not say, has none, and takes no check's build.
|
||||
func mergedHeadsOf(p *inventory.Plan) map[link.PullRef]string {
|
||||
heads := map[link.PullRef]string{}
|
||||
if p.Delivery == nil {
|
||||
return heads
|
||||
}
|
||||
seen := map[link.PullRef]bool{}
|
||||
for _, m := range p.Delivery.Merges {
|
||||
pull, ok := pullOf(m)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if seen[pull] && heads[pull] != m.Head {
|
||||
heads[pull] = ""
|
||||
continue
|
||||
}
|
||||
seen[pull] = true
|
||||
heads[pull] = m.Head
|
||||
}
|
||||
return heads
|
||||
}
|
||||
|
||||
// pullOf is a merge's pull request: owner and repository, the last two parts of however the repository is spelled
|
||||
// (owner/repo, a path on the git seat, a URL), and its number. False when the merge names none.
|
||||
func pullOf(m inventory.PlanMerge) (link.PullRef, bool) {
|
||||
|
||||
@@ -8,45 +8,22 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
// **A module moves only when its program changes** (novox/hq issue 721). A merge check builds every module the
|
||||
// delivery plan moves and compares each with the build the mesh holds of it; a walk is cut without a module whose
|
||||
// check found its whole image the same as that build, while the mesh still holds it, and says so among the modules
|
||||
// it left unchanged. A merge of one sentence in a package a program imports but never reaches moves no build of it.
|
||||
// **A module moves only when its source changes** (novox/hq issue 721; from R4e by its fingerprint, ADR 0453 §6, ADR
|
||||
// 0499; issue 1851, A9). A walk is cut without a module whose fingerprint at the commit the walk carries is the
|
||||
// fingerprint of the build the mesh holds of it, and says so among the modules it left unchanged: a merge of a file no
|
||||
// module reads moves nothing. A build the mesh holds of the old form (build-agent's `src1:`) has no fingerprint to
|
||||
// compare: the module stays in the walk, and the rule of the standing build (equal digests, no move) judges it once its
|
||||
// key is taken.
|
||||
|
||||
// sameAsRunning are the modules of a walk its checks found the same as the build the mesh holds, each with what its
|
||||
// check said. **Only when every pull request the walk answers was checked at the head it merged, and every one of
|
||||
// those checks built the module and found it the same as the build the mesh still holds**: a merge the walk answers
|
||||
// without a pull request, a check of another head, a check that built it differently, or a held build that moved
|
||||
// since, leaves the module in the walk.
|
||||
func sameAsRunning(plan *inventory.Plan, builds []link.CheckedBuild, held map[string]string) map[string]string {
|
||||
if plan.Delivery == nil || len(plan.Delivery.Merges) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, m := range plan.Delivery.Merges {
|
||||
if _, ok := pullOf(m); !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
pulls := pullsOfWalk(plan)
|
||||
heads := mergedHeadsOf(plan)
|
||||
// sameByFingerprint are the modules whose fingerprint now (`src2:`, the indexer's trunk value at the walk's commit) is
|
||||
// the fingerprint of the build the mesh holds, each with why; one whose either is unknown, or of the old form, is not.
|
||||
func sameByFingerprint(now, held map[string]string) map[string]string {
|
||||
out := map[string]string{}
|
||||
for name := range plan.Modules {
|
||||
said := ""
|
||||
for _, pull := range pulls {
|
||||
b, ok := checkedBuildOf(builds, pull, heads[pull], name)
|
||||
if !ok || !stillHeld(b.Built, name, held) {
|
||||
said = ""
|
||||
break
|
||||
}
|
||||
if said == "" {
|
||||
said = b.Built.Same
|
||||
}
|
||||
}
|
||||
if said != "" {
|
||||
out[name] = said
|
||||
for m, fp := range now {
|
||||
if strings.HasPrefix(fp, "src2:") && held[m] == fp {
|
||||
out[m] = fmt.Sprintf("unchanged: its fingerprint %s is the build the mesh holds", short(fp))
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
@@ -55,65 +32,67 @@ func sameAsRunning(plan *inventory.Plan, builds []link.CheckedBuild, held map[st
|
||||
return out
|
||||
}
|
||||
|
||||
// checkedBuildOf is the newest build a check of the pull request at head made of the module; false when none did.
|
||||
func checkedBuildOf(builds []link.CheckedBuild, pull link.PullRef, head, module string) (link.CheckedBuild, bool) {
|
||||
if head == "" {
|
||||
return link.CheckedBuild{}, false
|
||||
// heldFingerprint is the source fingerprint of the build the mesh holds of a module: the newest build of the commit
|
||||
// it is registered at.
|
||||
func heldFingerprint(ctx context.Context, inv *inventory.Inventory, module string) (string, error) {
|
||||
src, err := inv.SourceOf(ctx, module)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, b := range builds {
|
||||
if b.Pull == pull && b.Built.Module == module && link.SameHead(b.Head, head) {
|
||||
return b, true
|
||||
fps, err := inv.BuildSourceFingerprints(ctx, module)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for commit, fp := range fps {
|
||||
if sameCommitID(commit, src.BuiltFrom) {
|
||||
return fp, nil
|
||||
}
|
||||
}
|
||||
return link.CheckedBuild{}, false
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// stillHeld is whether a check found every artifact of the module the same as a build the mesh still holds.
|
||||
func stillHeld(b link.CheckBuilt, module string, held map[string]string) bool {
|
||||
if b.Same == "" || b.NotBuilt != "" || len(b.Made) == 0 || len(b.SameAs) != len(b.Made) {
|
||||
return false
|
||||
}
|
||||
for _, m := range b.Made {
|
||||
was := pinnedDigest(b.SameAs[m.Name])
|
||||
if was == "" || was != pinnedDigest(held[module+"/"+m.Name]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// pinnedDigest is a reference's pinned digest, empty when it is not pinned.
|
||||
func pinnedDigest(ref string) string {
|
||||
if i := strings.LastIndex(ref, "@"); i >= 0 {
|
||||
return ref[i+1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// leaveSameAsRunning takes from a walk being cut every module its checks found the same as the build the mesh holds
|
||||
// (sameAsRunning), and every dependent in the walk only for one of those (withDependentsOf): what was taken, with why. **Anything not read leaves the walk as it was**, said.
|
||||
// leaveSameAsRunning takes from a walk being cut every module whose fingerprint is unchanged (sameByFingerprint), and
|
||||
// every dependent in the walk only for one of those (withDependentsOf): what was taken, with why. **Anything not read
|
||||
// leaves the walk as it was**, said.
|
||||
func leaveSameAsRunning(ctx context.Context, inv *inventory.Inventory, plan *inventory.Plan, moved []string,
|
||||
edges []inventory.Edge) map[string]string {
|
||||
if plan.Delivery == nil || len(plan.Modules) == 0 {
|
||||
if len(plan.Modules) == 0 {
|
||||
return nil
|
||||
}
|
||||
pulls := pullsOfWalk(plan)
|
||||
if len(pulls) == 0 {
|
||||
return nil
|
||||
}
|
||||
builds, err := checkedBuildsOf(ctx, pulls)
|
||||
entries, err := inv.Catalogued(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" the checks' builds could not be read, so every module moves: %v\n", err)
|
||||
fmt.Printf(" the catalogue could not be read, so every module moves: %v\n", err)
|
||||
// empty-on-error: said above; nothing left out of the walk is the safe answer, every module moves as before
|
||||
return nil
|
||||
}
|
||||
held, err := inv.RegisteredHeld(ctx)
|
||||
keys, closeKeys, err := openPlanKeys()
|
||||
if err != nil {
|
||||
fmt.Printf(" what the mesh holds could not be read, so every module moves: %v\n", err)
|
||||
fmt.Printf(" the indexer's fingerprints could not be read, so every module moves: %v\n", err)
|
||||
// empty-on-error: said above; nothing left out of the walk is the safe answer, every module moves as before
|
||||
return nil
|
||||
}
|
||||
same := withDependentsOf(sameAsRunning(plan, builds, held), plan, moved, edges)
|
||||
defer closeKeys()
|
||||
byName := map[string]inventory.Entry{}
|
||||
for _, e := range entries {
|
||||
byName[e.Manifest.Module] = e
|
||||
}
|
||||
now, held := map[string]string{}, map[string]string{}
|
||||
for m := range plan.Modules {
|
||||
e, known := byName[m]
|
||||
if !known || isBuildersOwn(m) {
|
||||
continue
|
||||
}
|
||||
v, err := keys.ModuleAt(ctx, m, plan.CommitOn(e.Source.Repository, followedBranch(e.Source.Ref)))
|
||||
if err != nil || v.FP2 == "" {
|
||||
continue // not written yet, or none: the module moves, and its key is waited for in the walk
|
||||
}
|
||||
fp, err := heldFingerprint(ctx, inv, m)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
now[m], held[m] = v.FP2, fp
|
||||
}
|
||||
same := withDependentsOf(sameByFingerprint(now, held), plan, moved, edges)
|
||||
if len(same) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,45 +3,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
func TestAWalkWhoseEveryBuildCameOutTheSameIsDone(t *testing.T) {
|
||||
// R4e A9 (ADR 0453 §6, ADR 0499; issue 1851), against the store and a real bus: a merge of a file no module reads moves
|
||||
// nothing. Its module's fingerprint at the merge, as the indexer wrote it, is the fingerprint of the build the mesh holds,
|
||||
// so the walk is cut done with nothing to walk, and its record names the module unchanged and why — the walk
|
||||
// mesh-delivery reads as delivered, never stranded; no build is asked.
|
||||
func TestAWalkWhoseEveryFingerprintIsUnchangedIsDone(t *testing.T) {
|
||||
open := windowed(t)
|
||||
endsItsLine(t)
|
||||
asksRecorded(t)
|
||||
noBuildAsked(t)
|
||||
b := aBuilderOnTheBus(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-one", Repository: "novox/one", Ref: "main",
|
||||
Module: "one", Commit: "c0", On: "builder", Asked: t0.Add(-time.Hour),
|
||||
Made: []inventory.Artifact{{Name: "one", Kind: "image", Reference: "store/one@sha256:aaaa"}},
|
||||
Sources: []inventory.BuildSource{{Paths: []string{"main.go", "go.mod"}}}}); err != nil {
|
||||
fp := fpOf('5')
|
||||
if err := inv.RecordBuild(ctx, inventory.Build{ID: "key-held", Repository: "novox/one", Ref: "main",
|
||||
Module: "one", Commit: "c0", On: "ace", SourceFingerprint: fp,
|
||||
Made: []inventory.Artifact{{Name: "one", Kind: "image", Reference: "store/one@sha256:aaaa"}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// What runs: the module registered with that build's image (RegisteredHeld reads the module table).
|
||||
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "one", Version: "1", Resources: []map[string]any{
|
||||
{"id": "app", "type": "container", "name": "one", "image": "store/one@sha256:aaaa"}}},
|
||||
inventory.Source{Repository: "novox/one", Ref: "main", BuiltFrom: "c0", Asked: time.Now()}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pull := link.PullRef{Owner: "novox", Repo: "one", Number: 7}
|
||||
was := checkedBuildsOf
|
||||
checkedBuildsOf = func(context.Context, []link.PullRef) ([]link.CheckedBuild, error) {
|
||||
return []link.CheckedBuild{{Pull: pull, Head: "headaaaaaaaa", Built: link.CheckBuilt{Module: "one", Key: "k",
|
||||
Made: []link.MadeArtifact{{Name: "one", Kind: "image", Reference: "scratch/one@sha256:bbbb"}},
|
||||
Same: "unchanged: its program is the same as running build one aaaa",
|
||||
SameAs: map[string]string{"one": "10.0.0.1:5000/one@sha256:aaaa"}}}}, nil
|
||||
}
|
||||
t.Cleanup(func() { checkedBuildsOf = was })
|
||||
code := repoMerge("one", "one1aaaaaaaa", t0)
|
||||
code.Number, code.HeadSHA = 7, "headaaaaaaaa1111"
|
||||
b.indexed(t, "one", "1", "novox/one", "", code.Commit, fp)
|
||||
hear(t, open, code, t0.Add(time.Second))
|
||||
cutAt(t, open, t0.Add(2*time.Minute))
|
||||
ws, _ := walks(t, open)
|
||||
@@ -52,7 +45,7 @@ func TestAWalkWhoseEveryBuildCameOutTheSameIsDone(t *testing.T) {
|
||||
if w.State != inventory.PlanDone || len(w.Tiers) != 0 || len(w.Modules) != 0 {
|
||||
t.Fatalf("the walk reads %s, tiers %v, modules %v; want done with nothing to walk", w.State, w.Tiers, w.Modules)
|
||||
}
|
||||
if w.Delivery == nil || !strings.Contains(w.Delivery.Unchanged["one"], "its program is the same as running build") {
|
||||
t.Fatalf("the walk does not say one matched what runs: %+v", w.Delivery)
|
||||
if w.Delivery == nil || !strings.Contains(w.Delivery.Unchanged["one"], "is the build the mesh holds") {
|
||||
t.Fatalf("the walk does not say one's fingerprint is unchanged: %+v", w.Delivery)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,66 +5,26 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
// TestAWalkLeavesOutAModuleWhoseProgramDidNotChange is novox/hq issue 721: mesh-controller #335 changed one sentence
|
||||
// that mesh-builder never reaches; its check found build-agent the same as the build the mesh holds, so the walk moves
|
||||
// mesh-controller alone and says build-agent is unchanged.
|
||||
func TestAWalkLeavesOutAModuleWhoseProgramDidNotChange(t *testing.T) {
|
||||
pull := link.PullRef{Owner: "novox", Repo: "mesh-controller", Number: 335}
|
||||
head := "34050dc6aaaabbbbccccdddd"
|
||||
aPlan := func() *inventory.Plan {
|
||||
return &inventory.Plan{Tiers: [][]string{{"mesh-controller", "build-agent"}},
|
||||
Modules: map[string]*inventory.PlanModule{"mesh-controller": {}, "build-agent": {}},
|
||||
Delivery: &inventory.PlanDelivery{Merges: []inventory.PlanMerge{{Repository: "novox/mesh-controller",
|
||||
Commit: "c2ec0a60", Number: 335, Head: head}}}}
|
||||
}
|
||||
held := map[string]string{"build-agent/agent": "store/build-agent@sha256:aaaa",
|
||||
"mesh-controller/controller": "store/mesh-controller@sha256:cccc"}
|
||||
builds := []link.CheckedBuild{
|
||||
{Pull: pull, Head: head[:12], Built: link.CheckBuilt{Module: "build-agent", Key: "k1",
|
||||
Made: []link.MadeArtifact{{Name: "agent", Kind: "image", Reference: "scratch/build-agent@sha256:bbbb"}},
|
||||
Same: "unchanged: its program is the same as running build agent aaaa",
|
||||
SameAs: map[string]string{"agent": "10.0.0.1:5000/build-agent@sha256:aaaa"}}},
|
||||
{Pull: pull, Head: head[:12], Built: link.CheckBuilt{Module: "mesh-controller", Key: "k2",
|
||||
Made: []link.MadeArtifact{{Name: "controller", Kind: "image", Reference: "scratch/mesh-controller@sha256:dddd"}}}},
|
||||
}
|
||||
|
||||
p := aPlan()
|
||||
same := sameAsRunning(p, builds, held)
|
||||
if len(same) != 1 || same["build-agent"] == "" {
|
||||
// TestAWalkLeavesOutAModuleWhoseFingerprintDidNotChange is novox/hq issue 721 on fingerprints (R4e A9, ADR 0453 §6):
|
||||
// mesh-controller #335 changed one sentence that mesh-builder never reaches; the indexer's fingerprint of build-agent at
|
||||
// the merge is the fingerprint of the build the mesh holds, so the walk moves mesh-controller alone and says build-agent
|
||||
// is unchanged. A held build of the old form, or a fingerprint not written, leaves the module in the walk.
|
||||
func TestAWalkLeavesOutAModuleWhoseFingerprintDidNotChange(t *testing.T) {
|
||||
a, b := "src2:"+strings.Repeat("a", 64), "src2:"+strings.Repeat("b", 64)
|
||||
now := map[string]string{"build-agent": a, "mesh-controller": b, "old": a, "unwritten": ""}
|
||||
held := map[string]string{"build-agent": a, "mesh-controller": a, "old": "src1:" + strings.Repeat("a", 64)}
|
||||
same := sameByFingerprint(now, held)
|
||||
if len(same) != 1 || !strings.Contains(same["build-agent"], "is the build the mesh holds") {
|
||||
t.Fatalf("the walk leaves out %v, want build-agent alone", same)
|
||||
}
|
||||
p := &inventory.Plan{Tiers: [][]string{{"mesh-controller", "build-agent"}},
|
||||
Modules: map[string]*inventory.PlanModule{"mesh-controller": {}, "build-agent": {}}}
|
||||
withhold(p, []string{"build-agent"}, nil)
|
||||
if len(p.Tiers) != 1 || len(p.Tiers[0]) != 1 || p.Tiers[0][0] != "mesh-controller" {
|
||||
t.Fatalf("the walk moves %v, want mesh-controller alone", p.Tiers)
|
||||
}
|
||||
|
||||
// **The held build moved since the check**: what it was compared with no longer runs.
|
||||
moved := map[string]string{"build-agent/agent": "store/build-agent@sha256:eeee"}
|
||||
if same := sameAsRunning(aPlan(), builds, moved); len(same) != 0 {
|
||||
t.Fatalf("a module compared with a build the mesh no longer holds is left out: %v", same)
|
||||
}
|
||||
// **A check of another head** says nothing of this merge.
|
||||
other := aPlan()
|
||||
other.Delivery.Merges[0].Head = "ffffffffffffffff"
|
||||
if same := sameAsRunning(other, builds, held); len(same) != 0 {
|
||||
t.Fatalf("a check of another head left out %v", same)
|
||||
}
|
||||
// **Another pull request the walk answers** that did not find it the same leaves it in.
|
||||
two := aPlan()
|
||||
two.Delivery.Merges = append(two.Delivery.Merges, inventory.PlanMerge{Repository: "novox/mesh-catalog",
|
||||
Commit: "1234", Number: 9, Head: "abcdefabcdefabcdef"})
|
||||
if same := sameAsRunning(two, builds, held); len(same) != 0 {
|
||||
t.Fatalf("a module another merge moves is left out: %v", same)
|
||||
}
|
||||
// **A merge with no pull request** leaves the walk as it is.
|
||||
direct := aPlan()
|
||||
direct.Delivery.Merges[0].Number = 0
|
||||
if same := sameAsRunning(direct, builds, held); len(same) != 0 {
|
||||
t.Fatalf("a walk answering a merge without a pull request left out %v", same)
|
||||
}
|
||||
}
|
||||
|
||||
// TestADependentOfABaseThatCameOutTheSameIsLeftOut is ADR 0267 rule 3 (novox/hq issue 607): a dependent is in a walk
|
||||
|
||||
@@ -2,6 +2,8 @@ package inventory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/chain"
|
||||
)
|
||||
@@ -43,6 +45,11 @@ func (i *Inventory) sourceRows(ctx context.Context, module string) (map[string][
|
||||
|
||||
// standing is, of a module's builds newest first, the index of the build that stands for the build at
|
||||
// `from`: the oldest of the unbroken run of builds behind it with its source fingerprint.
|
||||
//
|
||||
// **Across the switch to the new form** (novox/hq ADR 0453 §6, ADR 0499; R4e A9): a build of the new form (`src2:`, taken
|
||||
// from the builder's key) continues the run of the build before it of the old form (`src1:` or none, build-agent's)
|
||||
// when every artifact of the two has the same digest: the fingerprints cannot be compared, the programs can, and equal
|
||||
// digests are no move. Past it the run goes on by the old fingerprint.
|
||||
func standing(builds []sourceRow, from int) int {
|
||||
at := from
|
||||
fp := builds[from].fingerprint
|
||||
@@ -50,14 +57,53 @@ func standing(builds []sourceRow, from int) int {
|
||||
return at
|
||||
}
|
||||
for j := from + 1; j < len(builds); j++ {
|
||||
if builds[j].fingerprint != fp || builds[j].failedGate {
|
||||
if builds[j].failedGate {
|
||||
break
|
||||
}
|
||||
if builds[j].fingerprint != fp {
|
||||
if !newForm(fp) || newForm(builds[j].fingerprint) || !sameDigests(builds[at].made, builds[j].made) {
|
||||
break
|
||||
}
|
||||
fp = builds[j].fingerprint
|
||||
}
|
||||
at = j
|
||||
}
|
||||
return at
|
||||
}
|
||||
|
||||
// newForm says a source fingerprint is of the new form (ADR 0453 §6).
|
||||
func newForm(fp string) bool { return strings.HasPrefix(fp, "src2:") }
|
||||
|
||||
// sameDigests says two builds' artifacts, as recorded (JSON of []Artifact), are the same programs: the same names, each
|
||||
// with the same digest, wherever each was pushed. A build naming an artifact with no digest is never the same.
|
||||
func sameDigests(a, b []byte) bool {
|
||||
digests := func(raw []byte) map[string]string {
|
||||
var made []Artifact
|
||||
if json.Unmarshal(raw, &made) != nil || len(made) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := map[string]string{}
|
||||
for _, m := range made {
|
||||
d := digestIn(m.Reference)
|
||||
if d == "" {
|
||||
return nil
|
||||
}
|
||||
out[m.Name] = d
|
||||
}
|
||||
return out
|
||||
}
|
||||
da, db := digests(a), digests(b)
|
||||
if da == nil || db == nil || len(da) != len(db) {
|
||||
return false
|
||||
}
|
||||
for name, d := range da {
|
||||
if db[name] != d {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StandingBuild is the build that stands for a module's build (novox/hq issue 280): the oldest build of
|
||||
// the unbroken run of builds with its source fingerprint, at or before it — itself when its fingerprint
|
||||
// is empty or it starts the run. Answers its id and the manifest it was recorded with; empty when the
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func madeOf(t *testing.T, refs map[string]string) []byte {
|
||||
t.Helper()
|
||||
var made []Artifact
|
||||
for name, ref := range refs {
|
||||
made = append(made, Artifact{Name: name, Kind: "bundle", Reference: ref})
|
||||
}
|
||||
raw, err := json.Marshal(made)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// R4e A9 (ADR 0453 §6, ADR 0499): at the switch, a build taken from the builder's key (`src2:`) whose every artifact has
|
||||
// the digest of the build-agent build before it (`src1:`) is no move: it stands on that build, wherever each was pushed.
|
||||
// One digest that differs is a move, as is a build of the old form after one of the new.
|
||||
func TestAKeysBuildWithTheOldBuildsDigestsIsNoMove(t *testing.T) {
|
||||
const d1, d2 = "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:2222222222222222222222222222222222222222222222222222222222222222"
|
||||
old := sourceRow{id: "build-1", fingerprint: "src1:aa", made: madeOf(t, map[string]string{
|
||||
"tools": "artifact-store://x/tools/blobs/" + d1, "img": "artifact-store://x/img@" + d2})}
|
||||
key := sourceRow{id: "key-bb-1", fingerprint: "src2:bb", made: madeOf(t, map[string]string{
|
||||
"tools": "artifact-store://x/tools/blobs/" + d1, "img": "registry.internal:5000/x/img@" + d2})}
|
||||
if at := standing([]sourceRow{key, old}, 0); at != 1 {
|
||||
t.Fatalf("the key's build with the same digests stands on %d, want the old build", at)
|
||||
}
|
||||
moved := key
|
||||
moved.made = madeOf(t, map[string]string{"tools": "artifact-store://x/tools/blobs/" + d2, "img": "x/img@" + d2})
|
||||
if at := standing([]sourceRow{moved, old}, 0); at != 0 {
|
||||
t.Fatalf("a key's build with another digest stands on %d, want itself (a move)", at)
|
||||
}
|
||||
// Two key builds of one fingerprint are one run, as two builds of one fingerprint always were.
|
||||
again := key
|
||||
again.id = "key-bb-2"
|
||||
if at := standing([]sourceRow{again, key, old}, 0); at != 2 {
|
||||
t.Fatalf("a run of one new fingerprint on an old build of the same digests stands on %d", at)
|
||||
}
|
||||
// An old build after a new one never continues the new one's run.
|
||||
if at := standing([]sourceRow{old, key}, 0); at != 0 {
|
||||
t.Fatalf("an old build continues a new one's run: %d", at)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user