A second read that failed, or that turned true in between, refused the ask and failed the whole plan.
224 lines
9.4 KiB
Go
224 lines
9.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
|
|
"github.com/novox/mesh-controller/internal/builderkeys"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// theBuilderSays sets what the running builder says for one test.
|
|
func theBuilderSays(t *testing.T, says bool, err error) {
|
|
t.Helper()
|
|
was := builderSaysReferences
|
|
builderSaysReferences = func(context.Context) (bool, error) { return says, err }
|
|
t.Cleanup(func() { builderSaysReferences = was })
|
|
}
|
|
|
|
// askingBuildAgent records the modules build-agent is asked for, by path.
|
|
func askingBuildAgent(t *testing.T) *[]string {
|
|
t.Helper()
|
|
var asked []string
|
|
was := askABuild
|
|
askABuild = func(_ context.Context, _ buildSource, path, _ string) (string, error) {
|
|
asked = append(asked, path)
|
|
return "build-9", nil
|
|
}
|
|
t.Cleanup(func() { askABuild = was })
|
|
return &asked
|
|
}
|
|
|
|
// 1886/AC2: while no running builder says it writes references, a walk asks build-agent for a module of its tier, as
|
|
// before R4e, and takes nothing from the builder's key.
|
|
func TestAWalkAsksBuildAgentWhileTheBuilderDoesNotSayItWritesReferences(t *testing.T) {
|
|
theBuilderSays(t, false, nil)
|
|
asked := askingBuildAgent(t)
|
|
p, byName := aWalkOfOne("sudo")
|
|
askModule(t.Context(), p, "sudo", byName)
|
|
s := p.Modules["sudo"]
|
|
if s.Key || s.Build != "build-9" || s.State != "asked" || len(*asked) != 1 {
|
|
t.Fatalf("the module is not asked of build-agent: %+v, asked %v", s, *asked)
|
|
}
|
|
}
|
|
|
|
// 1886/AC3: once a running builder says it writes references, a walk takes the module from the builder's key (ADR
|
|
// 0498 §8) and asks build-agent for nothing.
|
|
func TestAWalkTakesTheKeyOnceTheBuilderSaysItWritesReferences(t *testing.T) {
|
|
theBuilderSays(t, true, nil)
|
|
asked := askingBuildAgent(t)
|
|
p, byName := aWalkOfOne("sudo")
|
|
askModule(t.Context(), p, "sudo", byName)
|
|
if s := p.Modules["sudo"]; !s.Key || s.Build != "" || len(*asked) != 0 {
|
|
t.Fatalf("the module is not taken from its key: %+v, asked %v", s, *asked)
|
|
}
|
|
}
|
|
|
|
// 1886/AC2: a say that cannot be read leaves the module unasked and says why, so the next look asks it: neither path is
|
|
// guessed.
|
|
func TestASayNotReadLeavesTheModuleUnasked(t *testing.T) {
|
|
theBuilderSays(t, false, errors.New("the bus did not answer"))
|
|
asked := askingBuildAgent(t)
|
|
p, byName := aWalkOfOne("sudo")
|
|
askModule(t.Context(), p, "sudo", byName)
|
|
s := p.Modules["sudo"]
|
|
if s.State != "" || s.Key || len(*asked) != 0 || !strings.Contains(s.Why, "the bus did not answer") {
|
|
t.Fatalf("a say not read: %+v, asked %v", s, *asked)
|
|
}
|
|
if got := unaskedOf(*p); len(got) != 1 {
|
|
t.Fatalf("the module is not left for the next look to ask: %v", got)
|
|
}
|
|
}
|
|
|
|
// 1886/AC2: the one function every ask goes through lets build-agent build any module while the builder does not say
|
|
// it writes references, and refuses it again once it does.
|
|
func TestBuildAgentIsAskedForAnyModuleWhileTheBuilderDoesNotSay(t *testing.T) {
|
|
was := askedModuleOf
|
|
t.Cleanup(func() { askedModuleOf = was })
|
|
askedModuleOf = func(_ context.Context, _ buildSource, path string) (string, error) {
|
|
return strings.TrimPrefix(path, "modules/"), nil
|
|
}
|
|
theBuilderSays(t, false, nil)
|
|
if err := onlyTheBuildersOwn(t.Context(), buildSource{Repository: "novox/mesh-catalog", Seat: "git"}, "modules/sudo"); err != nil {
|
|
t.Fatalf("an ask while the builder does not say is refused: %v", err)
|
|
}
|
|
theBuilderSays(t, true, nil)
|
|
if err := onlyTheBuildersOwn(t.Context(), buildSource{Repository: "novox/mesh-catalog", Seat: "git"}, "modules/sudo"); !errors.Is(err, errNotTheBuildersOwn) {
|
|
t.Fatalf("an ask once the builder says is not refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1886/AC4: an open walk that waits on a key the running builder cannot reference (done without a reference, as R4e's
|
|
// own walk did in plan-1792707174627079192) is judged again at the next look: the module is handed back unasked, and
|
|
// that look's ask goes to build-agent. With the builder saying it writes references, the same walk keeps waiting.
|
|
func TestAnOpenWalkWaitingOnAnUnreferencedKeyIsAskedOfBuildAgent(t *testing.T) {
|
|
t.Cleanup(func() {
|
|
keyTakes.Lock()
|
|
keyTakes.running, keyTakes.waits, keyTakes.taken = map[string]bool{}, map[string]string{}, map[string]bool{}
|
|
keyTakes.Unlock()
|
|
})
|
|
fp := "src2:" + strings.Repeat("e", 64)
|
|
unreferenced := builderkeys.Key{Module: "sudo", Fingerprint: fp, Found: true, Revision: 1,
|
|
Build: sdkdelivery.BuilderBuild{State: sdkdelivery.BuildDone, Manifest: strings.Repeat("b", 64),
|
|
Artifacts: []sdkdelivery.BuiltArtifact{{Name: "tools", Kind: "bundle", Digest: "sha256:1"}}}}
|
|
walk := func() (*inventory.Plan, map[string]inventory.Entry, planKeysOf) {
|
|
theBuilderSays(t, true, nil)
|
|
p, byName := aWalkOfOne("sudo")
|
|
askModule(t.Context(), p, "sudo", byName)
|
|
keys := planKeysOf{values: map[string]sdkdelivery.Module{"sudo@c0ffee0123456789": {FP2: fp}},
|
|
keys: map[string]builderkeys.Key{"sudo": unreferenced}}
|
|
return p, byName, keys
|
|
}
|
|
take := func(string, sdkdelivery.Module, string) { t.Fatal("an unreferenced key was taken in") }
|
|
records := recordsOf{builds: map[string]inventory.Build{}}
|
|
|
|
// Said: it waits for the reference, as ADR 0498 §8 has it.
|
|
p, _, keys := walk()
|
|
if _, err := settleKeys(t.Context(), records, keys, p, p.Tiers[0], take); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := p.Modules["sudo"]; s.State != "asked" || !s.Key || !strings.Contains(s.Why, "waits for the reference") {
|
|
t.Fatalf("with the builder saying, the walk does not wait for the reference: %+v", s)
|
|
}
|
|
|
|
// Not said: handed back, and the next look asks build-agent.
|
|
p, byName, keys := walk()
|
|
keys.notSaid = true
|
|
theBuilderSays(t, false, nil)
|
|
asked := askingBuildAgent(t)
|
|
changed, err := settleKeys(t.Context(), records, keys, p, p.Tiers[0], take)
|
|
if err != nil || !changed {
|
|
t.Fatalf("the walk was not judged again: %v %v", changed, err)
|
|
}
|
|
s := p.Modules["sudo"]
|
|
if s.Key || s.State != "" || p.State != inventory.PlanBuilding || !strings.Contains(s.Why, "does not say") {
|
|
t.Fatalf("the module is not handed back to be asked of build-agent: %+v %s", s, p.State)
|
|
}
|
|
for _, m := range unaskedOf(*p) {
|
|
askModule(t.Context(), p, m, byName)
|
|
}
|
|
if s := p.Modules["sudo"]; s.Key || s.Build != "build-9" || len(*asked) != 1 {
|
|
t.Fatalf("the next look did not ask build-agent: %+v, asked %v", s, *asked)
|
|
}
|
|
}
|
|
|
|
// 1886/AC4: a key the running builder failed while it does not say it writes references fails no walk: the module is
|
|
// asked of build-agent, whose build is the one delivered.
|
|
func TestAFailedKeyOfABuilderThatDoesNotSayFailsNoWalk(t *testing.T) {
|
|
theBuilderSays(t, true, nil)
|
|
p, byName := aWalkOfOne("sudo")
|
|
askModule(t.Context(), p, "sudo", byName)
|
|
_ = byName
|
|
fp := "src2:" + strings.Repeat("f", 64)
|
|
keys := planKeysOf{notSaid: true, values: map[string]sdkdelivery.Module{"sudo@c0ffee0123456789": {FP2: fp}},
|
|
keys: map[string]builderkeys.Key{"sudo": {Module: "sudo", Fingerprint: fp, Found: true,
|
|
Build: sdkdelivery.BuilderBuild{State: sdkdelivery.BuildFailed, Error: "not stored: tools: no reference"}}}}
|
|
if _, err := settleKeys(t.Context(), recordsOf{}, keys, p, p.Tiers[0], func(string, sdkdelivery.Module, string) {}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := p.Modules["sudo"]; p.State != inventory.PlanBuilding || s.State != "" || s.Key {
|
|
t.Fatalf("a failed key of a builder that does not say failed the walk: %s %q %+v", p.State, p.Note, s)
|
|
}
|
|
}
|
|
|
|
// 1886/AC6 (ADR 0522 §6): the parts that write references, the builder and the build modules, are build-agent's own until
|
|
// R4g: asked of build-agent and never taken from keys they write themselves, whatever the builder says.
|
|
func TestTheReferenceWritersAreAskedOfBuildAgentWhateverTheBuilderSays(t *testing.T) {
|
|
theBuilderSays(t, true, nil)
|
|
asked := askingBuildAgent(t)
|
|
for _, m := range []string{"mesh-build-go", "mesh-build-node", "mesh-build-image", "mesh-delivery-builder"} {
|
|
p, byName := aWalkOfOne(m)
|
|
askModule(t.Context(), p, m, byName)
|
|
if s := p.Modules[m]; s.Key || s.Build != "build-9" {
|
|
t.Errorf("%s is not asked of build-agent: %+v", m, s)
|
|
}
|
|
}
|
|
if len(*asked) != 4 {
|
|
t.Fatalf("build-agent was asked %v", *asked)
|
|
}
|
|
}
|
|
|
|
// 1886 review round 1 (ADR 0522 §2): the guard of the one ask function judges by the say askModule read, never a second
|
|
// read: a second read that fails, or that says otherwise, fails no plan.
|
|
func TestTheAskGuardUsesTheSayTheWalkRead(t *testing.T) {
|
|
was := askedModuleOf
|
|
t.Cleanup(func() { askedModuleOf = was })
|
|
askedModuleOf = func(_ context.Context, _ buildSource, path string) (string, error) {
|
|
return strings.TrimPrefix(path, "modules/"), nil
|
|
}
|
|
reads := 0
|
|
wasSays := builderSaysReferences
|
|
t.Cleanup(func() { builderSaysReferences = wasSays })
|
|
for _, second := range []struct {
|
|
name string
|
|
says bool
|
|
err error
|
|
}{{"a second read that fails", false, errors.New("the bus did not answer")}, {"a say that turned true", true, nil}} {
|
|
reads = 0
|
|
builderSaysReferences = func(context.Context) (bool, error) {
|
|
reads++
|
|
if reads == 1 {
|
|
return false, nil
|
|
}
|
|
return second.says, second.err
|
|
}
|
|
wasAsk := askABuild
|
|
askABuild = func(ctx context.Context, source buildSource, path, _ string) (string, error) {
|
|
if err := onlyTheBuildersOwn(ctx, source, path); err != nil {
|
|
return "", err
|
|
}
|
|
return "build-9", nil
|
|
}
|
|
p, byName := aWalkOfOne("sudo")
|
|
askModule(t.Context(), p, "sudo", byName)
|
|
askABuild = wasAsk
|
|
if s := p.Modules["sudo"]; p.State == inventory.PlanFailed || s.Build != "build-9" || s.Key {
|
|
t.Errorf("%s: the walk %s, the module %+v", second.name, p.State, s)
|
|
}
|
|
}
|
|
}
|