121 lines
6.4 KiB
Go
121 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/provisions"
|
|
|
|
"mesh-delivery-view/internal/view"
|
|
)
|
|
|
|
// encoded is v as its contract's encoder writes it, failing the test when it refuses.
|
|
func encoded(t *testing.T, v []byte, err error) string {
|
|
t.Helper()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return string(v)
|
|
}
|
|
|
|
// tiersToolSaysEachModulesWait: view_tiers orders a source revision's modules by the module graph and names each one's
|
|
// wait (ADR 0603 §6): a consumer after its provider, held by the assigner's provision rule and its provision not provided;
|
|
// a module whose build waits for a package; a provider with no wait; a node offline named away; older modules left out.
|
|
func tiersToolSaysEachModulesWait(t *testing.T) {
|
|
fp := func(c string) string { return "src2:" + strings.Repeat(c, 64) }
|
|
manifest := strings.Repeat("d", 64)
|
|
mod := func(f string, src uint64) string {
|
|
b, err := delivery.EncodeModule(delivery.Module{FP2: f, Manifest: manifest, Src: src, FPSrc: src, Repository: "novox/mesh-catalog", Commit: "c"})
|
|
return encoded(t, b, err)
|
|
}
|
|
node := func(g delivery.GraphNode) string {
|
|
b, err := delivery.EncodeGraphNode(g)
|
|
return encoded(t, b, err)
|
|
}
|
|
build := func(b delivery.BuilderBuild) string {
|
|
v, err := delivery.EncodeBuilderBuild(b)
|
|
return encoded(t, v, err)
|
|
}
|
|
want := func(w delivery.Want) string {
|
|
v, err := delivery.EncodeWant(w)
|
|
return encoded(t, v, err)
|
|
}
|
|
have := func(h delivery.Have) string {
|
|
v, err := delivery.EncodeHave(h)
|
|
return encoded(t, v, err)
|
|
}
|
|
key := func(k string, err error) string {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return k
|
|
}
|
|
since := time.Date(2026, 11, 3, 16, 40, 0, 0, time.UTC)
|
|
r := fake{"db": mod(fp("a"), 9), "web": mod(fp("b"), 9), "lib": mod(fp("c"), 9), "old": mod(fp("e"), 5)}.reader()
|
|
r.GraphState = fake{
|
|
"db": node(delivery.GraphNode{FP: fp("a"), Repository: "novox/mesh-catalog", Provides: []string{"database"}}),
|
|
"web": node(delivery.GraphNode{FP: fp("b"), Repository: "novox/mesh-catalog", Requires: []string{"database"}, Awaits: []string{"database"}}),
|
|
"lib": node(delivery.GraphNode{FP: fp("c"), Repository: "novox/mesh-catalog"}),
|
|
}.reader().State
|
|
r.Builds = fake{
|
|
key(delivery.BuilderBuildKey("db", fp("a"))): build(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: manifest}),
|
|
key(delivery.BuilderBuildKey("web", fp("b"))): build(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: manifest}),
|
|
key(delivery.BuilderBuildKey("lib", fp("c"))): build(delivery.BuilderBuild{State: delivery.BuildQueued, Waits: "@novox/x@1.2.0"}),
|
|
}.reader().State
|
|
r.WantsState = fake{
|
|
key(delivery.WantKey("ace", "db")): want(delivery.Want{FP: fp("a"), Self: true}),
|
|
key(delivery.WantKey("ace", "web")): want(delivery.Want{Self: true, FP: fp("f"), Newest: fp("b"), Rule: delivery.RuleProvisions, Words: "the provision ace.web.database of web on ace is asked, not provided"}),
|
|
key(delivery.WantKey("g14", "web")): want(delivery.Want{Self: true, FP: fp("f"), Newest: fp("b"), Rule: delivery.RuleCanary, Words: "the canary"}),
|
|
}.reader().State
|
|
r.WantsState.History = func(string) ([]view.Revision, error) { return []view.Revision{{At: since}}, nil }
|
|
r.HavesState = fake{key(delivery.HaveKey("ace", "db")): have(delivery.Have{FP: fp("a"), Phase: delivery.PhaseRunning, Health: delivery.HealthOK})}.reader().State
|
|
p, err := provisions.Encode(provisions.Provision{Provision: "database", Consumer: provisions.End{Module: "web", Node: "ace"},
|
|
Provider: provisions.End{Module: "db", Node: "ace"}, State: provisions.Asked, Why: "the build db runs does not offer it", At: since})
|
|
r.ProvisionsState = fake{key(provisions.Key("ace", "web", "database", "")): encoded(t, p, err)}.reader().State
|
|
off, err := delivery.EncodePresence(delivery.Presence{Online: false})
|
|
r.PresenceState = fake{key(delivery.PresenceNodeKey("g14")): encoded(t, off, err)}.reader().State
|
|
|
|
out, err := run(t, r, view.TiersTool, map[string]any{"revision": float64(9)})
|
|
got, _ := out.(view.Tiers)
|
|
if err != nil || got.Revision != 9 || got.Modules != 3 || len(got.Tiers) != 2 || !reflect.DeepEqual(got.Away, []string{"g14"}) || got.Graph != "" {
|
|
t.Fatalf("view_tiers 9 = %+v, %v", out, err)
|
|
}
|
|
first := fmt.Sprint(got.Tiers[0])
|
|
if len(got.Tiers[0]) != 2 || got.Tiers[0][0].Module != "db" || len(got.Tiers[0][0].Waits) != 0 || got.Tiers[0][1].Module != "lib" ||
|
|
len(got.Tiers[0][1].Waits) != 1 || !strings.Contains(got.Tiers[0][1].Waits[0].Words, "waiting for the package @novox/x@1.2.0") {
|
|
t.Errorf("the first tier = %s, want db with no wait and lib's build waiting for its package", first)
|
|
}
|
|
web := got.Tiers[1][0]
|
|
if web.Module != "web" || !reflect.DeepEqual(web.After, []string{"db"}) || len(web.Waits) != 2 ||
|
|
web.Waits[0].Edge != "want" || web.Waits[0].Node != "ace" || web.Waits[0].Rule != delivery.RuleProvisions || !web.Waits[0].Since.Equal(since) ||
|
|
web.Waits[1].Edge != "provision database" || !strings.Contains(web.Waits[1].Words, "asked: the build db runs does not offer it") {
|
|
t.Errorf("web = %+v, want after db, held on ace by the provision rule and its provision asked (g14 is away)", web)
|
|
}
|
|
// No revision named: the gate's newest, its source key's revision.
|
|
r.SourceState = view.StateRead{GetEntry: func(string) (json.RawMessage, uint64, error) { return json.RawMessage(`{}`), 9, nil }}
|
|
if out, err := run(t, r, view.TiersTool, map[string]any{}); err != nil || out.(view.Tiers).Revision != 9 || out.(view.Tiers).Modules != 3 {
|
|
t.Errorf("view_tiers of the gate's newest = %+v, %v", out, err)
|
|
}
|
|
// The graph not readable: every module in the first tier, said; past 500 modules the answer says it is cut short.
|
|
r.GraphState = view.StateRead{Keys: func() ([]string, error) { return nil, errors.New("no bucket") }, Get: r.GraphState.Get}
|
|
if out, err := run(t, r, view.TiersTool, map[string]any{"revision": float64(9)}); err != nil || len(out.(view.Tiers).Tiers) != 1 ||
|
|
!strings.Contains(out.(view.Tiers).Graph, "cannot be read") {
|
|
t.Errorf("view_tiers with no graph = %+v, %v", out, err)
|
|
}
|
|
many := fake{}
|
|
for i := 0; i < view.TiersMost+2; i++ {
|
|
many[fmt.Sprintf("m%03d", i)] = mod(fp("a"), 9)
|
|
}
|
|
r.State = many.reader().State
|
|
if out, err := run(t, r, view.TiersTool, map[string]any{"revision": "9"}); err != nil || out.(view.Tiers).Modules != view.TiersMost+2 ||
|
|
!strings.Contains(out.(view.Tiers).Cut, "the first 500") {
|
|
t.Errorf("view_tiers past its bound = %+v, %v", out.(view.Tiers).Cut, err)
|
|
}
|
|
}
|