mesh/delivery merged: source.main moved to novox/mesh-sdk@2341e8e98377
mesh/delivery-group group feat/bus-state merged: every member is merged
mesh/check pass f8bd12e4b7b1 on 7c015b652d36 | unit 6460 run, 262 kept, 27 skipped | integration 6639 run, 264 kept, 35 skipped | over bound: unit, in…
A raw value holding "." collided with a longer path, and a space is refused by the bus: every byte outside [A-Za-z0-9_-] of a field value or an instance path part is written =XX (review round 1 of issue 1966 B1).
165 lines
6.3 KiB
Go
165 lines
6.3 KiB
Go
package stdio
|
|
|
|
import (
|
|
"encoding/json"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"reflect"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
|
)
|
|
|
|
// meshSDKPackages is every package mesh-sdk holds (novox/hq ADR 0593 §1): what every module shares, the value shapes
|
|
// and names more than one part reads, and the seat contracts it holds until they move into SDK modules (issue 167).
|
|
// Logic only some modules need is its owner module's package. A package added here is a decision to say in the pull
|
|
// request: does every module share it, or is it a shape more than one part reads?
|
|
var meshSDKPackages = []string{
|
|
".", "announce", "announcetest", "asker", "asks", "buildstep", "buscalls", "buscompose", "busretry", "calladdress", "catalogue",
|
|
"composer", "declaration", "delivery", "forgekey", "handacts", "health", "internal/beside", "kvlimit", "membership",
|
|
"objectlink", "outward", "overlay", "pernode", "process", "provider", "provider/harness", "provisions",
|
|
"sdkversion", "seal", "testbus",
|
|
}
|
|
|
|
// computesNot are the functions whose computation is one owner's alone (ADR 0593 §2, §7): the indexer computes a
|
|
// fingerprint, its named tree and a package's version; the node-engine composes.
|
|
var computesNot = []string{"SourceFingerprint", "SourceFingerprint2", "NamedTree", "MeshPackagesOf", "PackageVersionsOf",
|
|
"OtherPackages", "Compose", "CompositionsShadow", "JudgeMove", "Run"}
|
|
|
|
// composesNot are the methods of the composition, the node-engine's alone since its rules moved there (ADR 0593 §7):
|
|
// mesh-sdk renders no declaration and refuses no composition.
|
|
var composesNot = []string{"Resolution.Compose", "Resolution.Declaration", "Resolution.Rules", "Resolution.LeftOut"}
|
|
|
|
// ADR 0593 §1's check: mesh-sdk holds exactly the packages it keeps, and none of them computes what one owner alone
|
|
// computes.
|
|
func TestMeshSDKHoldsOnlyWhatEveryModuleShares(t *testing.T) {
|
|
var found []string
|
|
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
if n := d.Name(); n == "testdata" || (strings.HasPrefix(n, ".") && path != ".") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
dir := filepath.ToSlash(filepath.Dir(path))
|
|
if !slices.Contains(found, dir) {
|
|
found = append(found, dir)
|
|
}
|
|
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.SkipObjectResolution)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// 1966/AC5: the calls package imports nothing of mesh-sdk but the catalogue's types, so a change here rebuilds
|
|
// only its importers (ADR 0595 §9).
|
|
if dir == "buscalls" {
|
|
for _, imp := range file.Imports {
|
|
p, _ := strconv.Unquote(imp.Path.Value)
|
|
if strings.HasPrefix(p, sdkModule) && p != sdkModule+"/catalogue" {
|
|
t.Errorf("%s imports %s: the calls package imports nothing of mesh-sdk but catalogue (ADR 0595 §9)", path, p)
|
|
}
|
|
}
|
|
}
|
|
for _, decl := range file.Decls {
|
|
fn, ok := decl.(*ast.FuncDecl)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if fn.Recv == nil && slices.Contains(computesNot, fn.Name.Name) {
|
|
t.Errorf("%s: %s computes what its owner alone computes (ADR 0593 §2, §7)", path, fn.Name.Name)
|
|
}
|
|
if fn.Recv != nil {
|
|
recv := fn.Recv.List[0].Type
|
|
if star, isStar := recv.(*ast.StarExpr); isStar {
|
|
recv = star.X
|
|
}
|
|
if id, isID := recv.(*ast.Ident); isID && slices.Contains(composesNot, id.Name+"."+fn.Name.Name) {
|
|
t.Errorf("%s: %s.%s composes, which the node-engine alone does (ADR 0593 §7)", path, id.Name, fn.Name.Name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
slices.Sort(found)
|
|
for _, dir := range found {
|
|
if !slices.Contains(meshSDKPackages, dir) {
|
|
t.Errorf("mesh-sdk holds the package %s, which it does not keep (ADR 0593 §1): it is its owner module's package", dir)
|
|
}
|
|
}
|
|
for _, want := range meshSDKPackages {
|
|
if !slices.Contains(found, want) {
|
|
t.Errorf("mesh-sdk's list keeps %s, which is gone: take it off the list", want)
|
|
}
|
|
}
|
|
|
|
// 1966/AC5: the call an apply names is the id the tool runner hands under `_meta`'s mesh/call.
|
|
if buscalls.MetaCall != "mesh/call" {
|
|
t.Errorf("the tool call's id is handed under %q, not mesh/call", buscalls.MetaCall)
|
|
}
|
|
// 1966/AC5: an index's key is its fields' values, then the instance's path, joined by "."; its entry names the
|
|
// instance and the sequence.
|
|
for _, c := range []struct {
|
|
fields []string
|
|
instance string
|
|
want string
|
|
}{
|
|
{[]string{"17950", "volvo"}, "listing/a17", "17950.volvo.listing.a17"},
|
|
// A value holding "." and one that does not meet in no key.
|
|
{[]string{"a.b"}, "c", "a=2Eb.c"},
|
|
{[]string{"a"}, "b/c", "a.b.c"},
|
|
// A title with spaces is a token the bus takes; "=" itself is encoded.
|
|
{[]string{"How it is checked", "x=y"}, "decision/0290", "How=20it=20is=20checked.x=3Dy.decision.0290"},
|
|
} {
|
|
k := buscalls.IndexKey(c.fields, c.instance)
|
|
if k != c.want {
|
|
t.Errorf("the index key of %q and %q is %q, want %q", c.fields, c.instance, k, c.want)
|
|
}
|
|
for _, token := range strings.Split(k, ".") {
|
|
if token == "" || strings.ContainsAny(token, " *>\t") {
|
|
t.Errorf("the index key %q holds a token the bus refuses: %q", k, token)
|
|
}
|
|
}
|
|
}
|
|
if buscalls.IndexKey([]string{"a.b"}, "c") == buscalls.IndexKey([]string{"a"}, "b/c") {
|
|
t.Error("two different index keys meet")
|
|
}
|
|
entry, _ := json.Marshal(buscalls.IndexEntry{Instance: "listing/a17", Sequence: 3})
|
|
if string(entry) != `{"instance":"listing/a17","sequence":3}` {
|
|
t.Errorf("an index entry is %s", entry)
|
|
}
|
|
// 1966/AC5: an apply naming the tool call it serves, and its answer with the state behind, round-trip.
|
|
for _, c := range []struct {
|
|
name string
|
|
in, back any
|
|
}{
|
|
{"apply", &buscalls.ApplyRequest{Ledger: "records", Instance: "decision/0290", Operation: "submit",
|
|
Body: json.RawMessage(`{"why":"x"}`), Call: "call-17"}, &buscalls.ApplyRequest{}},
|
|
{"applied", &buscalls.Applied{Taken: true, Sequence: 5, StateBehind: true}, &buscalls.Applied{}},
|
|
} {
|
|
raw, err := json.Marshal(c.in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(raw, c.back); err != nil || !reflect.DeepEqual(c.in, c.back) {
|
|
t.Errorf("%s read back as %+v from %s (%v)", c.name, c.back, raw, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// sdkModule is mesh-sdk's Go module path.
|
|
const sdkModule = "git.novox.be/novox/mesh-sdk/go"
|