158 lines
7.2 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|