Files
mesh-delivery/modules/mesh-delivery-view/internal/view/reach_test.go
T

158 lines
7.2 KiB
Go

package view
import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// 1280 (ADR 0556 clauses 2 and 3): the view derives, per repository, how far each module reached and the reached head, writes
// it when it differs, and says each new reached head once as its event delivered.
func TestTheViewDerivesHowFarEachRepositoryReached(t *testing.T) {
const web, sdk = "novox/web", "novox/sdk"
commitA, commitB, commitC := strings.Repeat("a", 40), strings.Repeat("b", 40), strings.Repeat("c", 40)
commitS, commitX := strings.Repeat("5", 40), strings.Repeat("e", 40)
const fpMail = "src2:3333333333333333333333333333333333333333333333333333333333333333"
mod := func(module, fp, commit string, src uint64, history bool) Entry {
v, err := delivery.EncodeModule(delivery.Module{FP2: fp, Manifest: strings.Repeat("a", 64), Repository: web, Commit: commit, Src: src, Path: module})
if err != nil {
t.Fatal(err)
}
return Entry{Bucket: delivery.ModulesBucket, Key: module, Value: v, At: t0, History: history}
}
src := func(rev uint64, webHead string) Entry {
v, err := delivery.EncodeSource(delivery.Source{Group: "g", Heads: map[string]string{web: webHead, sdk: commitS}})
if err != nil {
t.Fatal(err)
}
return Entry{Bucket: delivery.SourceBucket, Key: delivery.SourceMainKey, Value: v, At: t0, Revision: rev, History: true}
}
taken := func(repository, merge string) Entry {
key, _ := delivery.RepositoryKey(repository)
v, err := delivery.EncodeTaken(delivery.Taken{Repository: repository, Merge: merge, At: t0})
if err != nil {
t.Fatal(err)
}
return Entry{Bucket: delivery.TakenBucket, Key: key, Value: v, At: t0}
}
haveIn := func(node, module, fp string, phase delivery.Phase, health delivery.Health) Entry {
e := haveFP(t, node, module, fp, health)
v, _ := delivery.EncodeHave(delivery.Have{FP: fp, Phase: phase, Health: health})
e.Value = v
return e
}
// web changed at A (fpOld) and at C (fpNew); mail first appeared at A. The source named A, B, then C as web's head.
world := func(t *testing.T) []Entry {
cur, _ := delivery.DecodeSource(src(12, commitC).Value)
current := Entry{Bucket: delivery.SourceBucket, Key: delivery.SourceMainKey, Revision: 12, At: t0}
current.Value, _ = delivery.EncodeSource(cur)
return []Entry{src(10, commitA), src(11, commitB), src(12, commitC), current,
mod("web", fpOld, commitA, 10, true), mod("web", fpNew, commitC, 12, true), mod("mail", fpMail, commitA, 0, true),
mod("web", fpNew, commitC, 12, false), mod("mail", fpMail, commitA, 0, false),
taken(web, commitC), taken(sdk, commitS),
wantE(t, "ace", "web", delivery.Want{FP: fpNew}), haveFP(t, "ace", "web", fpNew, delivery.HealthOK),
wantE(t, "g14", "web", delivery.Want{FP: fpNew}), haveFP(t, "g14", "web", fpNew, delivery.HealthOK),
wantE(t, "ace", "mail", delivery.Want{FP: fpMail}), haveFP(t, "ace", "mail", fpMail, delivery.HealthOK)}
}
for _, tc := range []struct {
name string
then []Entry
reached string // web's reached head; "" none
holds string // words one of web's holds carries; "" none
waiting string // words web's module web waits with; "" none
notYet []string // the merges the reached head's event says are not yet on the machines
}{
{name: "every node runs the newest build", reached: commitC},
{name: "a node lagging stops the head before the change it lacks", reached: commitB, holds: "web on g14",
then: []Entry{haveFP(t, "g14", "web", fpOld, delivery.HealthOK)}, waiting: "not yet the"},
{name: "a want held back at an older build holds the head as a lag does", reached: commitB, holds: "web on g14 runs the build of",
then: []Entry{wantE(t, "g14", "web", delivery.Want{FP: fpOld, Newest: fpNew, Rule: 2}), haveFP(t, "g14", "web", fpOld, delivery.HealthOK)}},
{name: "a failing health check is said and holds nothing", reached: commitC, waiting: "health check failed",
then: []Entry{haveFP(t, "g14", "web", fpNew, delivery.HealthFailed)}},
{name: "a node with no running have holds no head back and its module's merges are not yet", reached: commitC, waiting: "not running",
notYet: []string{commitA, commitC},
then: []Entry{haveIn("g14", "web", fpNew, delivery.PhaseApplying, delivery.HealthUnknown)}},
{name: "an unrelated module not running leaves the head reached (the reviewer's probe)", reached: commitC,
notYet: []string{commitA},
then: []Entry{haveIn("ace", "mail", fpMail, delivery.PhaseFailed, delivery.HealthFailed)}},
{name: "a module no node wants holds nothing", reached: commitC,
then: []Entry{wantE(t, "ace", "mail", delivery.Want{Removed: true})}},
{name: "the indexer not yet having taken the head in", reached: commitB, holds: "has not yet taken in",
then: []Entry{taken(web, commitB)}},
{name: "a change whose source revision is not known yet holds no head the source named", reached: commitC,
then: []Entry{mod("web", "src2:"+strings.Repeat("9", 64), commitX, 0, true), mod("web", "src2:"+strings.Repeat("9", 64), commitX, 0, false)}},
} {
t.Run(tc.name, func(t *testing.T) {
m := New()
var written []ReachWrite
var said []DeliveredEvent
m.SetReachWriter(func(w ReachWrite) { written = append(written, w) })
m.SetDeliveredEvent(func(e DeliveredEvent) {
if e.Repository != "" {
said = append(said, e)
}
})
fill(m, world(t)...)
if len(written) != 0 {
t.Fatalf("written before every watch handed over: %v", written)
}
for _, b := range reachBuckets {
m.Initial(b)
}
fill(m, tc.then...)
key, _ := delivery.RepositoryKey(web)
r := m.Reach()[key]
if r.Reached != tc.reached {
t.Errorf("reached = %.8s, want %.8s (holds %v)", r.Reached, tc.reached, r.Holds)
}
if got := strings.Join(r.Holds, "; "); (tc.holds == "") != (got == "") || !strings.Contains(got, tc.holds) {
t.Errorf("holds = %q, want words with %q", got, tc.holds)
}
if got := r.Modules["web"].Waiting; (tc.waiting == "") != (got == "") || !strings.Contains(got, tc.waiting) {
t.Errorf("web waits with %q, want words with %q", got, tc.waiting)
}
// A library reaches the head the indexer took in, with no module.
sk, _ := delivery.RepositoryKey(sdk)
if lib := m.Reach()[sk]; lib.Reached != commitS || len(lib.Modules) != 0 {
t.Errorf("the library's reach = %+v", lib)
}
// Each key is written once with its last value; a reached head is said once, for its repository alone.
last := map[string]delivery.Reach{}
for _, w := range written {
v, err := delivery.DecodeReach(w.Value)
if err != nil {
t.Fatalf("written %s: %v", w.Key, err)
}
last[w.Key] = v
}
if !delivery.SameReach(last[key], r) {
t.Errorf("the last value written is %+v, not the reach derived %+v", last[key], r)
}
if tc.reached != "" {
var last DeliveredEvent
for _, e := range said {
if e.Repository == web {
last = e
}
}
if strings.Join(last.NotYet, ",") != strings.Join(tc.notYet, ",") {
t.Errorf("not yet = %v, want %v", last.NotYet, tc.notYet)
}
}
{
n := len(said)
fill(m, tc.then...)
if len(said) != n && tc.reached == commitC {
t.Errorf("said again: %+v", said[n:])
}
for _, e := range said {
if len(e.Heads) != 1 || e.Group != "" || e.Revision == 0 {
t.Errorf("a reached head said as %+v", e)
}
}
}
})
}
}