package main import ( "crypto/ed25519" "encoding/json" "fmt" "reflect" "strings" "sync/atomic" "testing" "time" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-delivery-view/internal/view" ) type fake map[string]string func (f fake) reader() view.Reader { return view.Reader{State: view.StateRead{ Keys: func() ([]string, error) { var k []string for key := range f { k = append(k, key) } return k, nil }, Get: func(key string) (json.RawMessage, error) { if v, ok := f[key]; ok { return json.RawMessage(v), nil } return nil, nil }, }} } func run(t *testing.T, r view.Reader, name string, args map[string]any) (any, error) { t.Helper() for _, tl := range Tools(r, view.UsageReader{}) { if tl.Name == name { return tl.Run(args) } } t.Fatalf("no tool %s", name) return nil, nil } const ( withFP2 = `{"v":3,"fp":"a1","fp2":"b2","manifest":"m1","inputs2":{"tree":"t1","bases":["x"]},"repository":"novox/r","commit":"c1","path":".","toolchains":{"go":{"version":"go1.27.1","recipe":"2"}}}` withNone = `{"v":3,"none":"no successful trunk build recorded its inputs","none2":"builder's fingerprint not yet known","repository":"novox/r","commit":"c2","path":"modules/b"}` ) // 1562/AC1: indexer_module answers the key's value as stored: FP, FP2, None2, Inputs2, Path and Toolchains are all there. func TestModuleToolAnswersTheStoredValue(t *testing.T) { t.Run("indexer_packages answers the published modules", packagesToolAnswersThePublishedModules) r := fake{"a": withFP2, "b": withNone}.reader() for key, want := range map[string]string{"a": withFP2, "b": withNone} { out, err := run(t, r, view.ModuleReadTool, map[string]any{"module": key}) if err != nil { t.Fatal(err) } if got := string(out.(json.RawMessage)); got != want { t.Errorf("%s = %s, want %s", key, got, want) } } out, _ := run(t, r, view.ModuleReadTool, map[string]any{"module": "a"}) var m map[string]any if err := json.Unmarshal(out.(json.RawMessage), &m); err != nil || m["fp"] != "a1" || m["fp2"] != "b2" || m["path"] != "." || m["inputs2"] == nil || m["toolchains"] == nil { t.Errorf("stored fields are not all there: %v %v", m, err) } // A module with no key, and a name that is no key, are said so. if _, err := run(t, r, view.ModuleReadTool, map[string]any{"module": "gone"}); err == nil || !strings.Contains(err.Error(), "no module gone") { t.Errorf("a missing module: %v", err) } if _, err := run(t, r, view.ModuleReadTool, map[string]any{"module": ""}); err == nil { t.Errorf("an empty name was answered") } // 1280 (ADR 0556): view_reach answers the view's reach of one repository as stored, or of every one, and says a // repository it holds none of. const reach = `{"v":1,"repository":"novox/web","reached":"c1","reached_src":12}` r.ReachState = fake{"novox/web": reach}.reader().State if out, err := run(t, r, view.ReachReadTool, map[string]any{"repository": "novox/Web"}); err != nil || string(out.(json.RawMessage)) != reach { t.Errorf("view_reach novox/Web = %v, %v", out, err) } if out, err := run(t, r, view.ReachReadTool, map[string]any{}); err != nil || !strings.Contains(fmt.Sprint(out), "novox/web") { t.Errorf("view_reach of every repository = %v, %v", out, err) } if _, err := run(t, r, view.ReachReadTool, map[string]any{"repository": "novox/gone"}); err == nil || !strings.Contains(err.Error(), "no reach of novox/gone") { t.Errorf("a repository with no reach: %v", err) } // The module argument keeps only that module of each repository, and only the holds that name it. const reach2 = `{"v":1,"repository":"novox/web","reached":"c1","reached_src":12,"modules":{"web":{"nodes":1},"api":{"nodes":2}},"holds":["web on g14 runs 1","api on g14 runs 2","the indexer has not yet taken in x"]}` r.ReachState = fake{"novox/web": reach2, "novox/other": `{"v":1,"repository":"novox/other","modules":{"o":{"nodes":1}}}`}.reader().State if out, err := run(t, r, view.ReachReadTool, map[string]any{"repository": "novox/web", "module": "web"}); err != nil || !strings.Contains(string(out.(json.RawMessage)), `"web"`) || strings.Contains(string(out.(json.RawMessage)), `"api"`) || strings.Contains(string(out.(json.RawMessage)), "api on") || !strings.Contains(string(out.(json.RawMessage)), "web on g14") { t.Errorf("view_reach novox/web module web = %s, %v", out, err) } if out, err := run(t, r, view.ReachReadTool, map[string]any{"module": "api"}); err != nil || !strings.Contains(fmt.Sprint(out), "api") || strings.Contains(fmt.Sprint(out), "novox/other") { t.Errorf("view_reach module api over every repository = %v, %v", out, err) } if _, err := run(t, r, view.ReachReadTool, map[string]any{"repository": "novox/web", "module": "nope"}); err == nil || !strings.Contains(err.Error(), "no module nope") { t.Errorf("a module the repository does not hold: %v", err) } // Issue 1126 note 76: indexer_module_history answers modules.'s kept values newest first, each with its // revision, time, commit, source revision and fp2; indexer_heard the FORGE messages the indexer handled, newest first. at := time.Date(2026, 10, 28, 13, 7, 46, 0, time.UTC) r.State.History = func(key string) ([]view.Revision, error) { if key != "a" { return nil, nil } return []view.Revision{{Revision: 538, At: at.Add(-time.Minute), Op: "put", Value: json.RawMessage(withNone)}, {Revision: 539, At: at, Op: "put", Value: json.RawMessage(withFP2)}, {Revision: 540, At: at.Add(time.Minute), Op: "del"}}, nil } out, err := run(t, r, view.ModuleHistoryTool, map[string]any{"module": "a"}) h, _ := out.(view.ModuleHistory) if err != nil || len(h.Values) != 3 || h.Values[0].Revision != 540 || h.Values[0].Op != "del" || h.Values[1].Revision != 539 || !h.Values[1].At.Equal(at) || h.Values[1].Commit != "c1" || h.Values[1].FP2 != "b2" || h.Values[2].Commit != "c2" || h.Values[2].None2 == "" { t.Errorf("indexer_module_history a = %+v, %v", out, err) } if _, err := run(t, r, view.ModuleHistoryTool, map[string]any{"module": "gone"}); err == nil || !strings.Contains(err.Error(), "no history of gone") { t.Errorf("a module with no history: %v", err) } const heard1, heard2 = `{"v":1,"subject":"s","sequence":41,"repository":"novox/mesh-catalog","commit":"9cdac09a"}`, `{"v":1,"subject":"s","sequence":42,"repository":"novox/mesh-catalog","commit":"69056948"}` r.HeardState = view.StateRead{History: func(key string) ([]view.Revision, error) { return []view.Revision{{Revision: 1, At: at, Op: "put", Value: json.RawMessage(heard1)}, {Revision: 2, At: at, Op: "put", Value: json.RawMessage(heard2)}}, nil }} out, err = run(t, r, view.HeardTool, map[string]any{}) if rows, _ := out.(view.HeardAnswer); err != nil || len(rows.Heard) != 2 || rows.Heard[0].Revision != 2 || string(rows.Heard[0].Heard) != heard2 || !rows.Heard[1].At.Equal(at) { t.Errorf("indexer_heard = %+v, %v", out, err) } if out, err = run(t, r, view.HeardTool, map[string]any{"limit": float64(1)}); err != nil || len(out.(view.HeardAnswer).Heard) != 1 { t.Errorf("indexer_heard limit 1 = %+v, %v", out, err) } } // 1562/AC1: indexer_modules answers every key as stored, and with none2 only those that say why they have no FP2. func TestModulesToolAnswersEveryKeyAsStored(t *testing.T) { r := fake{"a": withFP2, "b": withNone}.reader() out, err := run(t, r, view.ModulesReadTool, map[string]any{}) if err != nil { t.Fatal(err) } all := out.(map[string]any)["modules"].(map[string]json.RawMessage) if len(all) != 2 || string(all["a"]) != withFP2 || string(all["b"]) != withNone { t.Errorf("modules = %v", all) } out, err = run(t, r, view.ModulesReadTool, map[string]any{"none2": true}) if err != nil { t.Fatal(err) } only := out.(map[string]any)["modules"].(map[string]json.RawMessage) if len(only) != 1 || string(only["b"]) != withNone { t.Errorf("none2 modules = %v", only) } } const ( fp2A = "src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" fp2B = "src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" keyA = "netcheck.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" keyB = "zsh.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" doneBuild = `{"v":1,"state":"done","by":"novox.mesh-delivery-builder","step":"built on shanks, ace","manifest":"m","artifacts":[{"name":"program","kind":"bundle","digest":"sha256:d1"}]}` failBuild = `{"v":1,"state":"failed","by":"novox.mesh-delivery-builder","error":"refused kind: package"}` ) func both(builds, modules fake) view.Reader { r := modules.reader() r.Builds = builds.reader().State return r } // 1723/AC1: builder_builds lists every key as stored (module, fingerprint, state, digests, error), and narrows by module and state. func TestBuilderBuildsListsKeysAsStored(t *testing.T) { r := both(fake{keyA: doneBuild, keyB: failBuild, "junk": "{}"}, fake{}) out, err := run(t, r, view.BuilderBuildsReadTool, map[string]any{}) if err != nil { t.Fatal(err) } rows := out.(view.BuildsAnswer).Builds if u := out.(view.BuildsAnswer).Unreadable; len(u) != 1 || u[0] != "junk" { t.Errorf("unreadable = %v, want [junk] (issue 1818)", u) } if len(rows) != 2 || rows[0].Key != keyA || rows[0].Module != "netcheck" || rows[0].Fingerprint != fp2A || string(rows[0].Build) != doneBuild || string(rows[1].Build) != failBuild { t.Errorf("rows = %+v", rows) } for args, want := range map[string]string{"state": keyB, "module": keyA} { out, _ := run(t, r, view.BuilderBuildsReadTool, map[string]any{args: map[string]string{keyB: "failed", keyA: "netcheck"}[want]}) got := out.(view.BuildsAnswer).Builds if len(got) != 1 || got[0].Key != want { t.Errorf("%s filter = %+v, want %s", args, got, want) } } // 1955/L7 (ADR 0571 clause 1): builder_history answers each end from the builder's log with its duration from its start, // the start of a later build of the same fingerprint not counted, and narrows by module through the log's key. t0 := time.Date(2026, 10, 29, 22, 0, 0, 0, time.UTC) entry := func(event string, at time.Duration) []byte { e := delivery.BuildLogEntry{Event: event, Module: "netcheck", FP: fp2A, Repository: "novox/mesh-catalog", Directory: "modules/netcheck", Commit: strings.Repeat("7", 40), Node: "ace", At: t0.Add(at)} if event == delivery.BuildEndedEvent { e.Outcome = delivery.BuildDone } raw, _ := delivery.EncodeBuildLogEntry(e) return raw } var asked string r.BuildLog = func(at time.Time, keys string, limit int) (*stdio.LogPage, error) { asked = keys return &stdio.LogPage{Entries: []stdio.LogEntry{{Sequence: 1, Value: entry(delivery.BuildStartedEvent, 0)}, {Sequence: 2, Value: entry(delivery.BuildEndedEvent, 90*time.Second)}, {Sequence: 3, Value: entry(delivery.BuildStartedEvent, time.Hour)}}}, nil } out, err = run(t, r, view.BuildHistoryTool, map[string]any{"module": "netcheck", "since": t0.Format(time.RFC3339)}) if h, _ := out.(view.HistoryAnswer); err != nil || len(h.Builds) != 1 || h.Builds[0].Seconds != 90 || h.Builds[0].Outcome != delivery.BuildDone || asked != "netcheck" || h.More { t.Errorf("history = %+v, %v (keys %q), want the one end at 90 s", out, err, asked) } } // 1723/AC2: builder_build answers one key by module and fp2, and by module alone through the indexer's fp2 (joined at its commit). func TestBuilderBuildJoinsTheIndexersFingerprint(t *testing.T) { idx := `{"v":3,"fp2":"` + fp2A + `","commit":"c1","path":"."}` r := both(fake{keyA: doneBuild}, fake{"netcheck": idx, "zsh": `{"v":3,"none2":"not known","commit":"c9"}`}) for _, args := range []map[string]any{{"module": "netcheck", "fp2": fp2A}, {"module": "netcheck"}, {"module": "netcheck", "commit": "c1"}} { out, err := run(t, r, view.BuilderBuildReadTool, args) if err != nil { t.Fatalf("%v: %v", args, err) } if row := out.(view.BuilderRow); row.Key != keyA || string(row.Build) != doneBuild { t.Errorf("%v = %+v", args, row) } } for args, want := range map[string]string{"netcheck/c2": "not c2", "zsh": "not known", "gone": "no module gone"} { a := map[string]any{"module": strings.Split(args, "/")[0]} if strings.Contains(args, "/") { a["commit"] = "c2" } if _, err := run(t, r, view.BuilderBuildReadTool, a); err == nil || !strings.Contains(err.Error(), want) { t.Errorf("%v: err = %v, want %q", a, err, want) } } if _, err := run(t, r, view.BuilderBuildReadTool, map[string]any{"module": "netcheck", "fp2": fp2B}); err == nil || !strings.Contains(err.Error(), "holds no build") { t.Errorf("an unbuilt fingerprint: %v", err) } } // bigBuilds is a bucket of n keys over 100 modules that counts the values read. type bigBuilds struct { fake fake reads *atomic.Int64 } func newBig(n int) bigBuilds { f := fake{} for i := 0; i < n; i++ { f[fmt.Sprintf("mod%02d.%064x", i%100, i)] = doneBuild } return bigBuilds{fake: f, reads: new(atomic.Int64)} } func (b bigBuilds) reader() view.Reader { r := b.fake.reader() get := r.State.Get r.Builds = view.StateRead{Keys: r.State.Keys, Get: func(k string) (json.RawMessage, error) { b.reads.Add(1); return get(k) }, GetEntry: func(k string) (json.RawMessage, uint64, error) { b.reads.Add(1) raw, err := get(k) var rev uint64 fmt.Sscanf(k[strings.Index(k, ".")+1:], "%x", &rev) // the fake's revision is the key's number return raw, rev + 1, err }} return r } // 1827/AC1: with 2,000 keys, a module's builds are found by key prefix, so only that module's values are read (20, not 2,000), // newest revision first, within the limit, and the answer says it was cut. func TestBuilderBuildsFiltersByPrefixBeforeAnyGet(t *testing.T) { b := newBig(2000) out, err := run(t, b.reader(), view.BuilderBuildsReadTool, map[string]any{"module": "mod07", "limit": float64(5)}) if err != nil { t.Fatal(err) } a := out.(view.BuildsAnswer) if b.reads.Load() != 20 { t.Errorf("read %d values for a module of 20 keys, want 20", b.reads.Load()) } if len(a.Builds) != 5 || !a.Cut || !strings.Contains(a.Note, "5") { t.Errorf("answer = %d rows, cut %v, note %q", len(a.Builds), a.Cut, a.Note) } for i := 1; i < len(a.Builds); i++ { if a.Builds[i-1].Revision < a.Builds[i].Revision { t.Errorf("not newest first: %d before %d", a.Builds[i-1].Revision, a.Builds[i].Revision) } } if a.Builds[0].Revision != 1908 { t.Errorf("newest revision = %d, want 1908", a.Builds[0].Revision) } } // 1827/AC2: with no module the answer reads at most the limit's worth in key order (default 50), never the 2,000. func TestBuilderBuildsWithoutAModuleIsBounded(t *testing.T) { b := newBig(2000) out, err := run(t, b.reader(), view.BuilderBuildsReadTool, map[string]any{}) if err != nil { t.Fatal(err) } a := out.(view.BuildsAnswer) if len(a.Builds) != view.DefaultBuildsLimit || !a.Cut || b.reads.Load() > view.DefaultBuildsLimit { t.Errorf("%d rows, cut %v, %d reads", len(a.Builds), a.Cut, b.reads.Load()) } b = newBig(2000) out, _ = run(t, b.reader(), view.BuilderBuildsReadTool, map[string]any{"state": "failed"}) if a := out.(view.BuildsAnswer); b.reads.Load() != view.MaxBuildReads || !a.Cut || len(a.Builds) != 0 { t.Errorf("a state matching nothing: %d reads (want the bound %d), cut %v", b.reads.Load(), view.MaxBuildReads, a.Cut) } } // 1818: fp2 together with commit is refused, naming both. func TestBuilderBuildRefusesFP2WithCommit(t *testing.T) { r := both(fake{keyA: doneBuild}, fake{"netcheck": `{"v":3,"fp2":"` + fp2A + `","commit":"c1","path":"."}`}) _, err := run(t, r, view.BuilderBuildReadTool, map[string]any{"module": "netcheck", "fp2": fp2A, "commit": "c1"}) if err == nil || !strings.Contains(err.Error(), "fp2") || !strings.Contains(err.Error(), "commit") || !strings.Contains(err.Error(), "not both") { t.Errorf("err = %v", err) } } // 1681: indexer_candidates and indexer_candidate answer the stored value; a pull request's fp2 per touched module shows. func TestCandidateToolsAnswerTheStoredValue(t *testing.T) { cand := `{"v":1,"group":"feat/x","base":3,"heads":"h","pulls":[{"modules":["zsh"],"fps":{"zsh":{"fp2":"` + fp2B + `","manifest":"m"}}}]}` other := `{"v":1,"group":"fix","base":3,"heads":"h","pulls":[{"modules":["netcheck"]}]}` r := fake{"feat/x": cand, "fix": other, "bad": "{"}.reader() r.CandidatesState = r.State out, err := run(t, r, view.CandidateReadTool, map[string]any{"group": "feat/x"}) if err != nil || string(out.(json.RawMessage)) != cand || !strings.Contains(string(out.(json.RawMessage)), fp2B) { t.Fatalf("candidate = %v, %v", out, err) } if _, err := run(t, r, view.CandidateReadTool, map[string]any{"group": "gone"}); err == nil || !strings.Contains(err.Error(), "no candidate gone") { t.Errorf("err = %v", err) } out, err = run(t, r, view.CandidatesReadTool, map[string]any{}) m := out.(map[string]any) if err != nil || len(m["candidates"].(map[string]json.RawMessage)) != 2 || len(m["unreadable"].([]string)) != 1 { t.Errorf("all = %v, %v", m, err) } out, _ = run(t, r, view.CandidatesReadTool, map[string]any{"module": "zsh"}) if c := out.(map[string]any)["candidates"].(map[string]json.RawMessage); len(c) != 1 || c["feat/x"] == nil { t.Errorf("by module = %v", c) } } // 1906/D1 AC1: assigner_wants, assigner_want, assigner_differences and node_haves answer as stored, narrowed by node and module. func keptReader() view.Reader { st := func(f fake) view.StateRead { return f.reader().State } return view.Reader{ WantsState: st(fake{ "want.ace.zsh": `{"fp":"` + fp2A + `"}`, "want.novox.zsh": `{"fp":"` + fp2B + `"}`, "want.ace.netcheck": `{"fp":"` + fp2A + `","rule":3}`, "held." + keyB: `{"node":"ace","words":"failed"}`, }), DifferencesState: st(fake{"ace.zsh": `{"unexplained":true}`, "novox.zsh": `{"rule":3}`}), HavesState: st(fake{"ace.zsh": `{"fp":"x"}`, "ace.node": `{"facts":1}`, "novox.zsh": `{"fp":"y"}`}), } } // slowBucket is a bucket of the live shape (2026-10-25: node_haves and assigner_wants timed out at 30 s): keys over 4 nodes // and perNode modules each, written by key(node, module), whose Get takes delay and counts the values read, the lists of // the bucket and the most Gets in flight at once. type slowBucket struct { fake fake delay time.Duration reads, lists atomic.Int64 inFlight, maxFlight atomic.Int64 } func newSlow(perNode int, delay time.Duration, key func(node string, i int) string) *slowBucket { b := &slowBucket{fake: fake{}, delay: delay} for _, n := range []string{"ace", "g14", "novox", "shanks"} { for i := 0; i < perNode; i++ { b.fake[key(n, i)] = `{"fp":"x"}` } } return b } func (b *slowBucket) state() view.StateRead { f := b.fake.reader().State return view.StateRead{ Keys: func() ([]string, error) { b.lists.Add(1); return f.Keys() }, Get: func(k string) (json.RawMessage, error) { b.reads.Add(1) now := b.inFlight.Add(1) for m := b.maxFlight.Load(); now > m && !b.maxFlight.CompareAndSwap(m, now); m = b.maxFlight.Load() { } time.Sleep(b.delay) b.inFlight.Add(-1) return f.Get(k) }} } func TestAssignerWantsToolAnswersAsStoredAndNarrows(t *testing.T) { r := keptReader() out, err := run(t, r, view.WantsReadTool, map[string]any{}) if err != nil { t.Fatal(err) } a := out.(view.WantsAnswer) if len(a.Wants) != 3 || len(a.Held) != 1 || string(a.Wants["want.ace.zsh"]) != `{"fp":"`+fp2A+`"}` { t.Errorf("all = %+v", a) } out, _ = run(t, r, view.WantsReadTool, map[string]any{"node": "ace"}) if got := out.(view.WantsAnswer).Wants; len(got) != 2 { t.Errorf("node ace = %v", got) } out, _ = run(t, r, view.WantsReadTool, map[string]any{"module": "zsh"}) if got := out.(view.WantsAnswer).Wants; len(got) != 2 { t.Errorf("module zsh = %v", got) } // On the live shape (4 nodes, 150 wants each, 1 ms a Get): a node and a module read that one key and list nothing; // a node reads only its own keys; the whole answer reads its values in parallel. slow := newSlow(150, time.Millisecond, func(n string, i int) string { return fmt.Sprintf("want.%s.m%03d", n, i) }) r = view.Reader{WantsState: slow.state()} for _, row := range []struct { args map[string]any wants int reads, lists int64 }{ {map[string]any{"node": "ace", "module": "m007"}, 1, 1, 0}, {map[string]any{"node": "ace", "module": "gone"}, 0, 1, 0}, {map[string]any{"node": "g14"}, 150, 150, 1}, {map[string]any{"module": "m007"}, 4, 4, 1}, {map[string]any{}, 600, 600, 1}, } { slow.reads.Store(0) slow.lists.Store(0) start := time.Now() out, err := run(t, r, view.WantsReadTool, row.args) took := time.Since(start) if err != nil { t.Fatalf("%v: %v", row.args, err) } a := out.(view.WantsAnswer) if len(a.Wants) != row.wants || slow.reads.Load() != row.reads || slow.lists.Load() != row.lists || a.Cut { t.Errorf("%v: %d wants, %d reads, %d lists, cut %v; want %d, %d, %d", row.args, len(a.Wants), slow.reads.Load(), slow.lists.Load(), a.Cut, row.wants, row.reads, row.lists) } t.Logf("assigner_wants %v: %d values in %v (one after the other: %v)", row.args, slow.reads.Load(), took, time.Duration(slow.reads.Load())*slow.delay) } if slow.maxFlight.Load() < 2 { t.Errorf("the values were read one at a time (at most %d in flight)", slow.maxFlight.Load()) } } func TestAssignerWantToolAnswersOneAndSaysWhenThereIsNone(t *testing.T) { r := keptReader() out, err := run(t, r, view.WantReadTool, map[string]any{"node": "ace", "module": "zsh"}) if err != nil || string(out.(json.RawMessage)) != `{"fp":"`+fp2A+`"}` { t.Errorf("want = %v %v", out, err) } if _, err := run(t, r, view.WantReadTool, map[string]any{"node": "ace", "module": "gone"}); err == nil || !strings.Contains(err.Error(), "no want") { t.Errorf("a missing want: %v", err) } } func TestAssignerDifferencesAndNodeHavesToolsAnswerAsStored(t *testing.T) { r := keptReader() out, err := run(t, r, view.DifferencesReadTool, map[string]any{"node": "novox"}) if err != nil { t.Fatal(err) } if got := out.(view.KeptAnswer).Keys; len(got) != 1 || string(got["novox.zsh"]) != `{"rule":3}` { t.Errorf("differences = %v", got) } out, _ = run(t, r, view.HavesReadTool, map[string]any{"module": "zsh"}) if got := out.(view.KeptAnswer).Keys; len(got) != 2 { t.Errorf("haves of zsh = %v", got) } out, _ = run(t, r, view.HavesReadTool, map[string]any{"node": "ace"}) if got := out.(view.KeptAnswer).Keys; len(got) != 2 || got["ace.node"] == nil { t.Errorf("haves of ace (the node's own key too) = %v", got) } // L3 s6b (ADR 0550 §2): node_trusts answers for every node in one call whether its record names the vault's key the // vault publishes, and every is true only once all do. vaultPub, _, _ := ed25519.GenerateKey(nil) vaultKey, _ := json.Marshal(delivery.NewSignerKey(vaultPub, time.Now())) vaultFP := delivery.SignerFingerprint(vaultPub) record := func(node string, trusts ...string) string { raw, _ := delivery.EncodeHaveNode(delivery.HaveNode{V: delivery.Version, Record: &delivery.NodeRecord{Node: node, Trusts: trusts}}) return string(raw) } for _, row := range []struct { name string haves fake every bool notYet []string }{ {"one node trusts the controller's alone", fake{"ace.node": record("ace", "controller key sha256:aa", "vault key "+vaultFP), "g14.node": record("g14", "controller key sha256:aa"), "ace.zsh": `{"fp":"x"}`}, false, []string{"g14"}}, {"every node trusts the vault's key", fake{"ace.node": record("ace", "vault key "+vaultFP), "g14.node": record("g14", "controller key sha256:aa", "vault key "+vaultFP)}, true, []string{}}, } { tr := view.Reader{HavesState: row.haves.reader().State, SignersState: fake{"key": string(vaultKey)}.reader().State} out, err := run(t, tr, view.TrustsReadTool, map[string]any{}) if err != nil { t.Fatalf("%s: %v", row.name, err) } a := out.(view.TrustsAnswer) if a.Every != row.every || !reflect.DeepEqual(a.NotYet, row.notYet) || a.VaultKey != vaultFP || len(a.Nodes) != 2 { t.Errorf("%s: %+v", row.name, a) } } if out, err := run(t, view.Reader{HavesState: fake{"ace.node": record("ace", "vault key "+vaultFP)}.reader().State, SignersState: fake{}.reader().State}, view.TrustsReadTool, map[string]any{}); err != nil || out.(view.TrustsAnswer).Every { t.Errorf("with no key published: %+v, %v", out, err) } // More haves than one call reads (4 nodes, 300 each, 1 ms a Get): a node and a module read that one key and list // nothing; the whole answer reads MaxStateReads values in parallel and says it was cut. slow := newSlow(300, time.Millisecond, func(n string, i int) string { return fmt.Sprintf("%s.m%03d", n, i) }) r = view.Reader{HavesState: slow.state(), DifferencesState: slow.state()} for _, row := range []struct { tool string args map[string]any keys int reads, lists int64 cut bool }{ {view.HavesReadTool, map[string]any{"node": "shanks", "module": "m042"}, 1, 1, 0, false}, {view.DifferencesReadTool, map[string]any{"node": "shanks", "module": "m042"}, 1, 1, 0, false}, {view.HavesReadTool, map[string]any{"node": "ace"}, 300, 300, 1, false}, {view.HavesReadTool, map[string]any{"module": "m042"}, 4, 4, 1, false}, {view.HavesReadTool, map[string]any{}, view.MaxStateReads, view.MaxStateReads, 1, true}, } { slow.reads.Store(0) slow.lists.Store(0) start := time.Now() out, err := run(t, r, row.tool, row.args) took := time.Since(start) if err != nil { t.Fatalf("%s %v: %v", row.tool, row.args, err) } a := out.(view.KeptAnswer) if len(a.Keys) != row.keys || slow.reads.Load() != row.reads || slow.lists.Load() != row.lists || a.Cut != row.cut || (a.Cut && !strings.Contains(a.Note, "narrow")) { t.Errorf("%s %v: %d keys, %d reads, %d lists, cut %v %q; want %d, %d, %d, %v", row.tool, row.args, len(a.Keys), slow.reads.Load(), slow.lists.Load(), a.Cut, a.Note, row.keys, row.reads, row.lists, row.cut) } t.Logf("%s %v: %d values in %v (one after the other: %v)", row.tool, row.args, slow.reads.Load(), took, time.Duration(slow.reads.Load())*slow.delay) } if slow.maxFlight.Load() < 2 { t.Errorf("the values were read one at a time (at most %d in flight)", slow.maxFlight.Load()) } } // ADR 0593 §2 (D2): indexer_packages answers the indexer's published packages, one Go module by its path or all. func packagesToolAnswersThePublishedModules(t *testing.T) { key, err := delivery.PackagesKey("git.novox.be/novox/mesh-sdk/go") if err != nil { t.Fatal(err) } const value = `{"v":1,"module":"git.novox.be/novox/mesh-sdk/go","commit":"c1"}` r := view.Reader{PackagesState: fake{key: value}.reader().State} out, err := run(t, r, view.PackagesReadTool, map[string]any{"module": "git.novox.be/novox/mesh-sdk/go"}) if err != nil || string(out.(json.RawMessage)) != value { t.Fatalf("one module: %v %v", out, err) } if all, err := run(t, r, view.PackagesReadTool, nil); err != nil || all.(map[string]any)["holds"] != 1 { t.Errorf("every module: %v %v", all, err) } if _, err := run(t, r, view.PackagesReadTool, map[string]any{"module": "github.com/x/y"}); err == nil { t.Error("a path that is no mesh Go module's was read") } }