Eight lists of seat verbs (checker, enrolment, artifact sweep, delivery, node-resolver, node-nfs-server, node-mounts, mesh-config's input) and their schema helpers were read by nothing but one test, and drifted from their seats (B0's verb input never reached the checker's copy). A guard row in the seats test finds any list of the catalogue's verbs written in the node-engine's code. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
168 lines
5.7 KiB
Go
168 lines
5.7 KiB
Go
package resolution
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
func registryShelf() map[string]mcat.Manifest {
|
|
return shelf(mcat.Manifest{Module: "gitea", Version: "1", Provides: mcat.FromAnywhere("npm-package-registry"),
|
|
Claims: []mcat.Claim{{Name: "npm-package-registry", Scope: mcat.ScopeMesh}}}, mcat.Manifest{Module: "verdaccio", Version: "1", Provides: mcat.FromAnywhere("npm-package-registry")}, mcat.Manifest{Module: "builder", Version: "1", Requires: []string{"npm-package-registry"}},
|
|
)
|
|
}
|
|
|
|
func twoRegistries() map[string][]mcat.Provider {
|
|
return map[string][]mcat.Provider{"npm-package-registry": {
|
|
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
|
|
{Node: "archive", At: "archive.internal", Module: "verdaccio"},
|
|
}}
|
|
}
|
|
|
|
func giteaHoldsTheSeat() []mcat.Held {
|
|
return []mcat.Held{{Claim: "npm-package-registry", Scope: mcat.ScopeMesh, Node: "anchor", Module: "gitea"}}
|
|
}
|
|
|
|
func TestTheSeatsHolderAnswersWhenSeveralProvide(t *testing.T) {
|
|
// The whole point: a second registry beside the holder harms nothing, and nobody pins.
|
|
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(), mcat.World{Offered: twoRegistries(), Held: giteaHoldsTheSeat()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Needs) != 1 || got.Needs[0].From != "anchor" {
|
|
t.Fatalf("the seat's holder did not answer: %v", got.Needs)
|
|
}
|
|
}
|
|
|
|
func TestAPinStillWinsOverTheSeat(t *testing.T) {
|
|
// A consumer coupled to one provider's contents has said so, and the seat does not overrule it.
|
|
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(), mcat.World{Offered: twoRegistries(), Held: giteaHoldsTheSeat(),
|
|
Pinned: map[string]mcat.Chosen{"npm-package-registry": {Node: "archive", Module: "verdaccio"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Needs) != 1 || got.Needs[0].From != "archive" {
|
|
t.Fatalf("the pin was overruled by the seat: %v", got.Needs)
|
|
}
|
|
}
|
|
|
|
func TestWithTheSeatUnheldSeveralProvidersAreStillRefused(t *testing.T) {
|
|
// No seat held is no choice made, and ADR 0009's rule stands: never guessed.
|
|
_, err := Resolve(registryShelf(), []string{"builder"}, reachable(), mcat.World{Offered: twoRegistries()})
|
|
if err == nil {
|
|
t.Fatal("one of two registries was picked with nobody holding the seat")
|
|
}
|
|
if !strings.Contains(err.Error(), "pin") {
|
|
t.Fatalf("the refusal does not say how to choose: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTheHolderIsTheModuleNotTheMachine(t *testing.T) {
|
|
// Two modules on one machine could provide the same thing; only the one holding the seat
|
|
// answers. A holder matched by node alone would send consumers to whichever came first.
|
|
providers := []mcat.Provider{
|
|
{Node: "anchor", At: "anchor.internal", Module: "verdaccio"},
|
|
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
|
|
}
|
|
holder, held := HolderAmong("npm-package-registry", providers, giteaHoldsTheSeat())
|
|
if !held || holder.Module != "gitea" {
|
|
t.Fatalf("the holder was not told apart from a neighbour: %+v", holder)
|
|
}
|
|
|
|
// A seat's verbs are its definition's, read from the bus (novox/hq ADR 0635 §1): the node-engine keeps no copy of
|
|
// them, so no copy can drift from the seat as the seven it kept did (issue 1921). A copy is a list of the catalogue's
|
|
// verbs written in the code; the guard proves it finds one, then finds none in the node-engine.
|
|
t.Run("no seat's verbs are copied into the node-engine", func(t *testing.T) {
|
|
copied := `package x
|
|
|
|
import mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
|
|
func someVerbs() []mcat.Verb { return []mcat.Verb{{Name: "routes"}} }
|
|
`
|
|
if got := verbCopies(t, "copied.go", copied); len(got) == 0 {
|
|
t.Fatal("the guard finds no copy in a file that holds one, so it proves nothing")
|
|
}
|
|
var found []string
|
|
root := filepath.Join("..", "..", "..")
|
|
err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() && (d.Name() == "testdata" || strings.HasPrefix(d.Name(), ".")) && p != root {
|
|
return filepath.SkipDir
|
|
}
|
|
if d.IsDir() || !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") {
|
|
return nil
|
|
}
|
|
found = append(found, verbCopies(t, p, nil)...)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(found) > 0 {
|
|
t.Errorf("the node-engine copies a seat's verbs instead of reading the seat list from the bus: %v", found)
|
|
}
|
|
})
|
|
}
|
|
|
|
// verbCopies is where one Go file writes the catalogue's verbs itself: a function answering a list of them, or a verb
|
|
// written as a literal.
|
|
func verbCopies(t *testing.T, name string, src any) []string {
|
|
t.Helper()
|
|
fset := token.NewFileSet()
|
|
f, err := parser.ParseFile(fset, name, src, parser.SkipObjectResolution)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
alias := ""
|
|
for _, imp := range f.Imports {
|
|
if p, _ := strconv.Unquote(imp.Path.Value); p == "git.novox.be/novox/mesh-sdk/go/catalogue" {
|
|
alias = "catalogue"
|
|
if imp.Name != nil {
|
|
alias = imp.Name.Name
|
|
}
|
|
}
|
|
}
|
|
if alias == "" {
|
|
return nil
|
|
}
|
|
isVerb := func(e ast.Expr) bool {
|
|
if a, ok := e.(*ast.ArrayType); ok {
|
|
e = a.Elt
|
|
}
|
|
sel, ok := e.(*ast.SelectorExpr)
|
|
if !ok || sel.Sel.Name != "Verb" {
|
|
return false
|
|
}
|
|
x, ok := sel.X.(*ast.Ident)
|
|
return ok && x.Name == alias
|
|
}
|
|
var out []string
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
switch n := n.(type) {
|
|
case *ast.FuncDecl:
|
|
if n.Type.Results != nil {
|
|
for _, r := range n.Type.Results.List {
|
|
if isVerb(r.Type) {
|
|
out = append(out, fset.Position(n.Pos()).String()+" "+n.Name.Name)
|
|
}
|
|
}
|
|
}
|
|
case *ast.CompositeLit:
|
|
if n.Type != nil && isVerb(n.Type) {
|
|
out = append(out, fset.Position(n.Pos()).String())
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
return out
|
|
}
|