Assert the key takes where a walk asks no build, and hold keys where a walk must stand still (issue 1931)
This commit is contained in:
@@ -241,3 +241,23 @@ func (k keysHolding) Build(ctx context.Context, module, fp string) (builderkeys.
|
||||
}
|
||||
return k.planKeyReads.Build(ctx, module, fp)
|
||||
}
|
||||
|
||||
// holdKeys has the stand-in builder's keys of these modules (at any commit) unbuilt until the returned flag is cleared,
|
||||
// so a walk that took them stays on its tier as it did while its build ran. It restores what it replaced at the end of
|
||||
// the test, and the flag is set until the test lowers it.
|
||||
func holdKeys(t *testing.T, modules ...string) *atomic.Bool {
|
||||
t.Helper()
|
||||
held := &atomic.Bool{}
|
||||
held.Store(true)
|
||||
was := openPlanKeys
|
||||
openPlanKeys = func() (planKeyReads, func(), error) {
|
||||
all, done, err := was()
|
||||
var keys planKeyReads = all
|
||||
for _, m := range modules {
|
||||
keys = keysHolding{keys, m, held, ""}
|
||||
}
|
||||
return keys, done, err
|
||||
}
|
||||
t.Cleanup(func() { openPlanKeys = was })
|
||||
return held
|
||||
}
|
||||
|
||||
@@ -94,8 +94,10 @@ func TestAFeatureWithANewSeatVerbIsOneBatchAndOnePlanInOrderWithNoAfterLine(t *t
|
||||
if plan.Delivery != nil && (plan.Delivery.Awaits != "" || plan.Waiting()) {
|
||||
t.Fatalf("a plan carrying the controller waits for %q's word", plan.Delivery.Awaits)
|
||||
}
|
||||
if plan.Modules["mesh-controller"].Build == "" || len(*asked) != 1 {
|
||||
t.Fatalf("the first tier is asked once: build %q, %d ask(s) %v", plan.Modules["mesh-controller"].Build, len(*asked), *asked)
|
||||
// The cut takes the first tier from the controller's key at its commit and asks no build; the take-in itself is a
|
||||
// later look's, which this test does not run, so nothing is recorded as asked.
|
||||
if c := plan.Modules["mesh-controller"]; !c.Key || c.State != "asked" || c.KeyAt != "k1" || len(*asked) != 0 {
|
||||
t.Fatalf("the first tier is not taken from its key once: %+v, %d ask(s) %v", c, len(*asked), *asked)
|
||||
}
|
||||
// Every merge is answered by that one plan.
|
||||
for _, repository := range []string{"novox/mesh-controller", "novox/mesh-catalog"} {
|
||||
|
||||
@@ -266,9 +266,18 @@ func TestAPlansBuildRequestIsThePlans(t *testing.T) {
|
||||
if err := (following{open: open}).SourceMoved(ctx, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A walk asks no build, so it leaves no build request (askModule takes the module from its key): the plan's app is
|
||||
// taken from the key at the commit the plan carries.
|
||||
requests, err := open.inventory.RequestsNamed(ctx, "app")
|
||||
if err != nil || len(requests) != 1 || requests[0].For != "plan" {
|
||||
t.Fatalf("the plan's request: %+v %v", requests, err)
|
||||
if err != nil || len(requests) != 0 {
|
||||
t.Fatalf("a request was recorded for the plan: %+v %v", requests, err)
|
||||
}
|
||||
recent, err := open.inventory.RecentPlans(ctx, 5)
|
||||
if err != nil || len(recent) != 1 {
|
||||
t.Fatalf("the plans: %+v %v", recent, err)
|
||||
}
|
||||
if app := recent[0].Modules["app"]; app == nil || !app.Key || app.KeyAt != "c1aaaaaaaa" {
|
||||
t.Fatalf("the plan's app is not taken from its key at the commit it carries: %+v", app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ func TestAPlanStandingStillIsNotSavedAgain(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
asked := asksRecorded(t)
|
||||
// app's key is not built, so the plan stands on its tier as it did while the build ran; with the stand-in builder's
|
||||
// key done at once, one step would carry it to done.
|
||||
holdKeys(t, "app")
|
||||
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "app", Version: "1"},
|
||||
inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/app", Ref: "main",
|
||||
BuiltFrom: "c0", Head: "c0"}); err != nil {
|
||||
@@ -27,11 +30,12 @@ func TestAPlanStandingStillIsNotSavedAgain(t *testing.T) {
|
||||
}
|
||||
advanceHeld(ctx, open)
|
||||
recent, err := inv.RecentPlans(ctx, 1)
|
||||
if err != nil || len(recent) != 1 || len(*asked) != 1 {
|
||||
if err != nil || len(recent) != 1 || len(*asked) != 0 {
|
||||
t.Fatalf("no plan building: %v %v, asked %v", recent, err, *asked)
|
||||
}
|
||||
before := recent[0]
|
||||
if before.State != inventory.PlanBuilding || before.Modules["app"] == nil || before.Modules["app"].State != "asked" {
|
||||
if before.State != inventory.PlanBuilding || before.Modules["app"] == nil || before.Modules["app"].State != "asked" ||
|
||||
!before.Modules["app"].Key || before.Modules["app"].KeyAt != "c1aaaaaaaa" {
|
||||
t.Fatalf("the plan is not waiting on its build: %+v", before)
|
||||
}
|
||||
|
||||
@@ -46,7 +50,7 @@ func TestAPlanStandingStillIsNotSavedAgain(t *testing.T) {
|
||||
t.Fatalf("a plan that did not move was saved again: revision %d → %d, saved %s → %s",
|
||||
before.Revision, after.Revision, before.Updated, after.Updated)
|
||||
}
|
||||
if len(*asked) != 1 {
|
||||
t.Fatalf("advancing asked again: %v", *asked)
|
||||
if len(*asked) != 0 {
|
||||
t.Fatalf("advancing took the key in: %v", *asked)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +316,8 @@ func TestReplay296APlanStandingStillInItsTierIsNotSavedAgain(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
asked := asksRecorded(t)
|
||||
// app's key is not built, so the plan stands on its tier as it did while the build ran (see plan_clock_integration_test.go).
|
||||
holdKeys(t, "app")
|
||||
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "app", Version: "1"},
|
||||
inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/app", Ref: "main",
|
||||
BuiltFrom: "c0", Head: "c0"}); err != nil {
|
||||
@@ -328,11 +330,12 @@ func TestReplay296APlanStandingStillInItsTierIsNotSavedAgain(t *testing.T) {
|
||||
}
|
||||
advanceHeld(ctx, open)
|
||||
recent, err := inv.RecentPlans(ctx, 1)
|
||||
if err != nil || len(recent) != 1 || len(*asked) != 1 {
|
||||
if err != nil || len(recent) != 1 || len(*asked) != 0 {
|
||||
t.Fatalf("no plan waiting on its build: %v %v, asked %v", recent, err, *asked)
|
||||
}
|
||||
before := recent[0]
|
||||
if before.State != inventory.PlanBuilding || before.Modules["app"] == nil || before.Modules["app"].State != "asked" {
|
||||
if before.State != inventory.PlanBuilding || before.Modules["app"] == nil || before.Modules["app"].State != "asked" ||
|
||||
!before.Modules["app"].Key || before.Modules["app"].KeyAt != "3ad22356aa" {
|
||||
t.Fatalf("the plan is not waiting on its build: %+v", before)
|
||||
}
|
||||
for range 3 {
|
||||
|
||||
@@ -167,6 +167,9 @@ func TestARolloutAdvancesOnEventsAndAReplacedControllerResumesIt(t *testing.T) {
|
||||
return p
|
||||
}
|
||||
|
||||
// Tier 1's module b has its key unbuilt while the first controller runs, so the plan stands at tier 1 as it did while
|
||||
// its build ran; with the key done at once, the same look would take it and send tier 1 as well.
|
||||
heldB := holdKeys(t, "b")
|
||||
// The first controller. Its plan has tier 0 built and tier 1 not asked: nothing but the save asks anything.
|
||||
first, stopFirst := context.WithCancel(ctx)
|
||||
defer stopFirst()
|
||||
@@ -181,10 +184,11 @@ func TestARolloutAdvancesOnEventsAndAReplacedControllerResumesIt(t *testing.T) {
|
||||
if err := inv.SavePlan(ctx, &p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eventually(t, "tier 0 sent to both machines and tier 1 asked, on the save alone", func() bool {
|
||||
eventually(t, "tier 0 sent to both machines and tier 1 taken from its key, on the save alone", func() bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return len(sends) == 1 && len(*asked) == 1 && plan().Tier == 1
|
||||
b := plan().Modules["b"]
|
||||
return len(sends) == 1 && len(*asked) == 0 && plan().Tier == 1 && b != nil && b.Key && b.KeyAt == "c2"
|
||||
})
|
||||
eventually(t, "a key on the bus for each machine tier 0 was sent to", func() bool { return len(heldKeys()) == 2 })
|
||||
|
||||
@@ -194,6 +198,7 @@ func TestARolloutAdvancesOnEventsAndAReplacedControllerResumesIt(t *testing.T) {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
reports("anchor")
|
||||
|
||||
heldB.Store(false) // b's key is built by the time the new controller looks
|
||||
second, stopSecond := context.WithCancel(ctx)
|
||||
defer stopSecond()
|
||||
waitSecond := startRolloutEvents(second, open, js)
|
||||
|
||||
@@ -65,6 +65,9 @@ func TestAPlanPassesATierWhoseMachineNeverReportsAfterTheBoundAndSendsTheNextToT
|
||||
var mu sync.Mutex
|
||||
var sends [][]string
|
||||
asksRecorded(t) // each module is taken from a done key as the walk reaches it
|
||||
// Except tier 1's module b, whose key is not built until the test says so: a walk that takes it in the same look goes on
|
||||
// through tier 1's send and overwrites the note of the move before the test reads it (see away_integration_test.go).
|
||||
heldB := holdKeys(t, "b")
|
||||
digests := 0
|
||||
wasSend := sendRollout
|
||||
sendRollout = func(ctx context.Context, open *stores, names []string) ([]string, error) {
|
||||
@@ -190,7 +193,12 @@ func TestAPlanPassesATierWhoseMachineNeverReportsAfterTheBoundAndSendsTheNextToT
|
||||
if !strings.Contains(moved, "laptop") || strings.Contains(moved, "anchor") {
|
||||
t.Errorf("1160/AC5: the plan says %q; want laptop named silent and not anchor", moved)
|
||||
}
|
||||
// Tier 1's module is taken from its key, which the walk's look does by itself.
|
||||
if b := plan().Modules["b"]; b == nil || !b.Key {
|
||||
t.Errorf("tier 1's module is not taken from its key: %+v", b)
|
||||
}
|
||||
// Tier 1's module is taken from its key once that is built, which the walk's look does by itself.
|
||||
heldB.Store(false)
|
||||
askToLook()
|
||||
eventually(t, "tier 1 sent to anchor alone", func() bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/novox/mesh-controller/internal/broker"
|
||||
"github.com/novox/mesh-controller/internal/builderkeys"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
"github.com/novox/mesh-controller/internal/testbus"
|
||||
@@ -205,11 +208,17 @@ func TestAWalkStoppedByHandAndRetriedEndsFailedWhenItFailsAgain(t *testing.T) {
|
||||
if p.State != inventory.PlanBuilding || (p.Delivery != nil && p.Delivery.Stopped != "") {
|
||||
t.Fatalf("%s: the walk is %s and keeps its stop: %+v", again[0], p.State, p.Delivery)
|
||||
}
|
||||
// The build asked fails again: its record says so, and the look settles the walk from it.
|
||||
if err := open.inventory.RecordBuild(ctx, inventory.Build{ID: p.Modules["a"].Build, Module: "a", Commit: "c0ffee",
|
||||
Repository: "novox/a", At: time.Now().UTC(), Asked: before, Failed: "it broke again"}); err != nil {
|
||||
t.Fatal(err)
|
||||
// The retry takes a from its key (it asks no build), at the commit the walk carries.
|
||||
if a := p.Modules["a"]; a == nil || !a.Key || a.KeyAt != "c0ffee" {
|
||||
t.Fatalf("%s: the retried walk does not take a from its key: %+v", again[0], a)
|
||||
}
|
||||
// The key fails again: the builder's record says so, and the look settles the walk from it.
|
||||
wholeKeys := openPlanKeys
|
||||
openPlanKeys = func() (planKeyReads, func(), error) {
|
||||
all, done, err := wholeKeys()
|
||||
return keysFailing{all, "a", "it broke again"}, done, err
|
||||
}
|
||||
t.Cleanup(func() { openPlanKeys = wholeKeys })
|
||||
advancePlans(ctx, open)
|
||||
if p, _ = open.inventory.PlanByID(ctx, walk.ID); p.State != inventory.PlanFailed || p.Outcome() != inventory.WalkFailed {
|
||||
t.Fatalf("%s: failing again, the walk is %s and ends %s", again[0], p.State, p.Outcome())
|
||||
@@ -259,3 +268,18 @@ func TestAWalksStartIsSaidAgainWithAMachineWhoseDeclarationChanged(t *testing.T)
|
||||
t.Fatalf("said again: %v", again)
|
||||
}
|
||||
}
|
||||
|
||||
// keysFailing is the stand-in builder's keys but for one module, whose key is failed in these words.
|
||||
type keysFailing struct {
|
||||
planKeyReads
|
||||
module string
|
||||
words string
|
||||
}
|
||||
|
||||
func (k keysFailing) Build(ctx context.Context, module, fp string) (builderkeys.Key, error) {
|
||||
if module == k.module {
|
||||
return builderkeys.Key{Module: module, Fingerprint: fp, Found: true, Revision: 1,
|
||||
Build: sdkdelivery.BuilderBuild{State: sdkdelivery.BuildFailed, Error: k.words}}, nil
|
||||
}
|
||||
return k.planKeyReads.Build(ctx, module, fp)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user