Move the catalogue tests that read mesh-catalog into go/catalogue, and lay the captured copy out once per process (issue 1379)
Imports rewritten to mesh-sdk's (beside, catalogue, overlay). Left in mesh-controller, which holds their modules: TestTheBuilderRequiresTheRegistryTheNpmSeatDelivers (foundation_manifests_test.go), TestGenesisAssignsTheBuildAgentWithNothingProvidingTheRegistry (genesis_registry_test.go, whole file), TestRouteProxyKeepsItsPublicAccountDirectory (public_issuer_test.go), TestEveryManifestInUseClaimsASeatTheMeshDefines (seats_test.go); with controller_is_a_process_test.go and genesis_image_test.go. moduleTree reads mesh-catalog only: mesh-sdk has no modules directory of its own. beside.Dir lays a repository's captured copy out once per process (a mutex and a map, os.MkdirTemp), new test TestTheCapturedCopyIsLaidOutOnce failed first (two directories). The captured copy is refreshed from the mains: mesh-catalog bb0fe93b, mesh-host cdcba9ca, only what these tests read; CAPTURED says how to move it. Unit level 1.16 s (bound 2 s).
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// **The word does not come back through a manifest** (novox/hq ADR 0131). A module that wants
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// The module holding mesh-broker is the bus, and its account is granted the bus's snapshot API and
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// TestEveryCatalogueManifestParses runs the real catalogue through the real gate.
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// **A machine trusts the mesh's authority because a module put its root there** (novox/hq ADR
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// The controller refuses a definition's resolved path by the node-engine's own rules, no more (novox/hq issue
|
||||
|
||||
@@ -3,13 +3,12 @@ package catalogue
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// The catalogue's foundation modules as they are — in a merge check the catalogue cloned beside it,
|
||||
@@ -17,15 +16,12 @@ import (
|
||||
// real parser (novox/hq ADR 0100):
|
||||
// the store and the broker say which of their ports the mesh guards on an adopted node, and the
|
||||
// filter module loads its table through a unit of its own whose stop deletes only that table.
|
||||
// moduleTree is where a module is read from: this repository's own modules/<module> when it lives whole here
|
||||
// (novox/hq ADR 0420 §1, issue 1019: build-agent and route-proxy), with this repository as the tree root;
|
||||
// otherwise the catalogue beside, with the catalogue as the root.
|
||||
// moduleTree is where a module is read from: the catalogue beside, with the catalogue as the root. In
|
||||
// mesh-controller it first looked in the repository's own modules/<module>, where build-agent and
|
||||
// route-proxy live whole (novox/hq ADR 0420 §1, issue 1019); mesh-sdk has no such directory, and the
|
||||
// tests that read those two modules stay in mesh-controller (issue 1379).
|
||||
func moduleTree(t *testing.T, module string) (root, dir string) {
|
||||
t.Helper()
|
||||
own := filepath.Join("..", "..", "modules", module)
|
||||
if _, err := os.Stat(filepath.Join(own, "module.json")); err == nil {
|
||||
return filepath.Join("..", ".."), own
|
||||
}
|
||||
return beside.Dir(t, "mesh-catalog"), filepath.Join(beside.Catalogue(t), module)
|
||||
}
|
||||
|
||||
@@ -132,36 +128,6 @@ func TestTheForgesPortIsGivenLikeAnyOtherProvidersPort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// **The builder requires the registry the npm seat delivers, and carries no binding of its own.**
|
||||
//
|
||||
// It used to carry a hand-written binding because nothing provided a package registry to resolve
|
||||
// one from at genesis. The catalogue now requires it like any consumer, and ADR 0110 makes the
|
||||
// seat's holder the answer when more than one module provides it — so a carried copy would be a
|
||||
// second answer to the same question, free to drift from the first. Asserted gone, not merely
|
||||
// unused.
|
||||
//
|
||||
// The builder is the build-agent on every machine since novox/hq ADR 0190; this read the retired
|
||||
// `builder` and, finding no manifest, skipped unseen from then until novox/hq issue 432.
|
||||
func TestTheBuilderRequiresTheRegistryTheNpmSeatDelivers(t *testing.T) {
|
||||
builder := catalogueManifest(t, "build-agent")
|
||||
seat, _ := SeatNamed("npm-package-registry")
|
||||
var requires bool
|
||||
for _, r := range builder.Requires {
|
||||
requires = requires || r == seat.Delivers
|
||||
}
|
||||
if !requires {
|
||||
t.Fatalf("the builder does not require %q: %v", seat.Delivers, builder.Requires)
|
||||
}
|
||||
if builder.Binds[seat.Delivers] == "" {
|
||||
t.Errorf("the builder is not told where the registry is: binds %v", builder.Binds)
|
||||
}
|
||||
for _, r := range builder.Resources {
|
||||
if fmt.Sprint(r["id"]) == "package-binding" {
|
||||
t.Fatal("the builder carries its own package binding beside the one the mesh resolves")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The forge holds the seats it answers for (novox/hq ADR 0110, 0111), parsed by the real parser —
|
||||
// which refuses a delivering seat claimed by a module that does not provide what it delivers.
|
||||
func TestTheForgeHoldsTheNpmAndGitSeats(t *testing.T) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// A bind mount the module never declared is refused where it is written (novox/hq 04-ISSUES/026,
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// **A manifest holds no subject** (novox/hq design 29 §1).
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
"github.com/novox/mesh-controller/internal/overlay"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
)
|
||||
|
||||
// The manifests the control plane actually ships, resolved.
|
||||
|
||||
@@ -5,90 +5,9 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// The public issuer is the proxy's own fact (novox/hq ADR 0226), and the folding of it must not
|
||||
// move the proxy's account.
|
||||
//
|
||||
// route-proxy keeps each ACME authority's account and certificates in a directory named after a
|
||||
// digest of the directory URL and of the root bundle its `trust` container copies in
|
||||
// (examples/route-proxy, forThisAuthority). Until ADR 0226 the URL was rendered from a binding to
|
||||
// `public-acme`'s `acme-ca`, spelled with the port. A URL spelled any other way — even the same
|
||||
// authority without `:443` — or a root bundle from another image is a new authority to the proxy:
|
||||
// a new account, and every routed name ordered again, on two machines at once, against Let's
|
||||
// Encrypt's rate limits. So what the module now states is held to exactly what the binding rendered.
|
||||
|
||||
// renderedBeforeTheFold is route-proxy's acme.env as the binding to public-acme rendered it on every
|
||||
// machine running the proxy, read from the controller's plan on 2026-10-06.
|
||||
const renderedBeforeTheFold = "ACME_DIRECTORY=https://acme-v02.api.letsencrypt.org:443/directory\n" +
|
||||
"ACME_ROOTS=https://acme-v02.api.letsencrypt.org:443\n" +
|
||||
"ACME_ROOTS_PATH=\n"
|
||||
|
||||
// trustImage is the image whose system bundle becomes the public root the account directory is
|
||||
// named after. Moving it renames that directory.
|
||||
const trustImage = "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b"
|
||||
|
||||
func TestRouteProxyKeepsItsPublicAccountDirectory(t *testing.T) {
|
||||
m := catalogueManifest(t, "route-proxy")
|
||||
for _, r := range m.Requires {
|
||||
if r == "acme-ca" {
|
||||
t.Fatal("route-proxy still asks for acme-ca; the public issuer is its own fact (ADR 0226)")
|
||||
}
|
||||
}
|
||||
|
||||
// As a build would leave it: each artifact a container names resolved to an image. Which image
|
||||
// does not matter to the file checked here.
|
||||
for _, res := range m.Resources {
|
||||
if artifact, ok := res["artifact"].(string); ok {
|
||||
delete(res, "artifact")
|
||||
res["image"] = "registry.invalid/route-proxy/" + artifact +
|
||||
"@sha256:1111111111111111111111111111111111111111111111111111111111111111"
|
||||
}
|
||||
}
|
||||
r := Resolution{
|
||||
Node: "anchor",
|
||||
Modules: []Manifest{m},
|
||||
Needs: []Needed{{
|
||||
Name: "internal-acme-ca", From: "home", At: "home.internal", For: "route-proxy",
|
||||
Serves: map[string]any{
|
||||
"port": float64(9000), "path": "/acme/acme/directory", "roots": "/roots.pem",
|
||||
},
|
||||
}},
|
||||
}
|
||||
// Its own broker credential, sealed as the mesh would; what it is does not matter here.
|
||||
out, err := r.Declaration(Rendering{Needed: map[string]map[string]string{
|
||||
"route-proxy": {"broker": "sealed"}}})
|
||||
if err != nil {
|
||||
t.Fatalf("route-proxy could not be composed for a machine: %v", err)
|
||||
}
|
||||
env := fileNamed(out, "route-proxy.acme-env")
|
||||
if env == nil {
|
||||
t.Fatalf("nothing writes the public issuer's environment: %v", out)
|
||||
}
|
||||
if got, _ := env["content"].(string); got != renderedBeforeTheFold {
|
||||
t.Fatalf("acme.env changed, which moves the proxy's account and reorders every certificate:\n"+
|
||||
"got %q\nwant %q", got, renderedBeforeTheFold)
|
||||
}
|
||||
if fileNamed(out, "route-proxy.bound-acme-ca") != nil {
|
||||
t.Error("a binding to acme-ca is still written")
|
||||
}
|
||||
|
||||
var trust string
|
||||
if m.Build != nil {
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a.Name == "trust" {
|
||||
trust = a.From
|
||||
}
|
||||
}
|
||||
}
|
||||
if trust != trustImage {
|
||||
t.Errorf("the trust image is %q, not %q: its system bundle is the public root the account "+
|
||||
"directory is named after, so moving it reorders every certificate. Move it only with a "+
|
||||
"plan for the account (ADR 0226)", trust, trustImage)
|
||||
}
|
||||
}
|
||||
|
||||
// The modules ADR 0226 retired stay retired, and nothing asks for what they provided. A module is
|
||||
// in the catalogue when its manifest is: a directory left behind with no manifest in it (a build's
|
||||
// leftovers, untracked by git) is not a module, and failed this on a desktop (novox/hq issue 432).
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
)
|
||||
|
||||
// Defends novox/hq ADR 0110: a seat is a module assignment from a closed set.
|
||||
@@ -194,39 +189,6 @@ func TestAClaimThatIsMalformedIsRefusedOnceForThat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Every module in use claims a seat in the set, so closing it refuses nothing that runs.
|
||||
//
|
||||
// Read from the catalogue (internal/beside) and from this repository's own manifest, the two places a
|
||||
// manifest lives (ADR 0069). The private-network module's manifest is composed in code, and its claim
|
||||
// is checked where it is composed.
|
||||
func TestEveryManifestInUseClaimsASeatTheMeshDefines(t *testing.T) {
|
||||
paths, _ := filepath.Glob(filepath.Join(beside.Catalogue(t), "*", "module.json"))
|
||||
if len(paths) == 0 {
|
||||
t.Fatal("no manifests in the catalogue, so this proved nothing")
|
||||
}
|
||||
paths = append(paths, "../../module.json")
|
||||
var checked int
|
||||
for _, path := range paths {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Leniently, so a manifest refused for something unrelated is not reported as a seat
|
||||
// problem, and the seat check below is the only thing this test holds a module to.
|
||||
var m Manifest
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatalf("%s: %v", path, err)
|
||||
}
|
||||
for _, problem := range claimProblems(m) {
|
||||
t.Errorf("%s: %s", path, problem)
|
||||
}
|
||||
checked += len(m.Claims)
|
||||
}
|
||||
if checked == 0 {
|
||||
t.Fatal("no claims were checked, so this proved nothing")
|
||||
}
|
||||
}
|
||||
|
||||
// The holder of a seat answers among several providers.
|
||||
|
||||
func registryShelf() map[string]Manifest {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// **The showcase module is parsed by the real parser, in the real test suite.**
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/beside"
|
||||
"git.novox.be/novox/mesh-sdk/go/internal/beside"
|
||||
)
|
||||
|
||||
// boundKeys are every key the files of a module ask of ${bound:forge-account:<key>}, read from the module's own
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Package beside is where a test finds another repository of the mesh it reads: the catalogue's manifests,
|
||||
// the node-engine's genesis template (novox/hq issue 432), the SDK's conformance fixtures (issue 449).
|
||||
//
|
||||
// Copied from mesh-controller internal/beside at commit 6c2a8d62 (R4b, issue 1379); the one change is that Dir
|
||||
// lays out the captured copy of a repository once per process, not once per test (see laidOut). In mesh-sdk the
|
||||
// catalogue's tests read mesh-catalog and mesh-host only; the captured copy holds just what they read.
|
||||
//
|
||||
// A test used to read the checkout beside this one, `../../../mesh-catalog`, so its verdict depended on
|
||||
// whatever sat on the machine running it: a stale or dirty checkout failed it on a desktop, and where none
|
||||
// was beside it skipped and said nothing. Now there are two inputs, both named, and no third:
|
||||
@@ -30,6 +34,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -57,8 +62,37 @@ func Dir(t testing.TB, repository string) string {
|
||||
if _, err := os.Stat(from); err != nil {
|
||||
t.Fatalf("no captured copy of %s at %s: %v", repository, from, err)
|
||||
}
|
||||
dir := filepath.Join(t.TempDir(), repository)
|
||||
err := filepath.WalkDir(from, func(path string, d fs.DirEntry, err error) error {
|
||||
dir, err := layOut(repository, from)
|
||||
if err != nil {
|
||||
t.Fatalf("the captured copy of %s could not be laid out: %v", repository, err)
|
||||
}
|
||||
t.Logf("judged against the copy of %s captured in testdata/beside (see CAPTURED); a merge check "+
|
||||
"judges it against the repository's own clone", repository)
|
||||
return dir
|
||||
}
|
||||
|
||||
// laidOut is the captured copies already put back in place in this process, by repository. A copy is
|
||||
// read-only to every test, so it is laid out once per process and not once per test: with a copy per test
|
||||
// the catalogue's tests alone took 2.14 s, over mesh-sdk's unit bound of 2 s, and with one 0.81 s (measured
|
||||
// 2026-10-20). The directories are in the temp directory, which the system clears; a process cannot remove
|
||||
// them while another test may still read them.
|
||||
var (
|
||||
laidOutMu sync.Mutex
|
||||
laidOut = map[string]string{}
|
||||
)
|
||||
|
||||
func layOut(repository, from string) (string, error) {
|
||||
laidOutMu.Lock()
|
||||
defer laidOutMu.Unlock()
|
||||
if dir, ok := laidOut[repository]; ok {
|
||||
return dir, nil
|
||||
}
|
||||
root, err := os.MkdirTemp("", "beside-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dir := filepath.Join(root, repository)
|
||||
err = filepath.WalkDir(from, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -74,11 +108,11 @@ func Dir(t testing.TB, repository string) string {
|
||||
return os.WriteFile(into, raw, 0o644)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("the captured copy of %s could not be laid out: %v", repository, err)
|
||||
os.RemoveAll(root)
|
||||
return "", err
|
||||
}
|
||||
t.Logf("judged against the copy of %s captured in testdata/beside (see CAPTURED); a merge check "+
|
||||
"judges it against the repository's own clone", repository)
|
||||
return dir
|
||||
laidOut[repository] = dir
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Catalogue is the catalogue's modules directory, as Dir finds the catalogue.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package beside
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The captured copy of a repository is laid out once per test process: a second call, in another test,
|
||||
// gets the same directory (issue 1379, AC3). Laying it out per test made go/catalogue's tests take 2.14 s
|
||||
// against the unit level's 2 s bound; once, 0.81 s.
|
||||
func TestTheCapturedCopyIsLaidOutOnce(t *testing.T) {
|
||||
t.Setenv(Env, "")
|
||||
var first, second string
|
||||
t.Run("one", func(t *testing.T) { first = Dir(t, "mesh-catalog") })
|
||||
t.Run("two", func(t *testing.T) { second = Dir(t, "mesh-catalog") })
|
||||
if first == "" || first != second {
|
||||
t.Fatalf("the captured copy was laid out twice: %q and %q", first, second)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(first, "modules")); err != nil {
|
||||
t.Fatalf("the copy holds no modules directory: %v", err)
|
||||
}
|
||||
// And it outlives the test that first asked, which a t.TempDir would not.
|
||||
if _, err := os.Stat(first); err != nil {
|
||||
t.Fatalf("the copy is gone after the first test ended: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// With MESH_CHECK_BESIDE naming a directory that lacks the repository, the test fails naming it (AC4; the
|
||||
// rule is beside's own, unchanged). Dir fails the test it is given, so it is given a recorder.
|
||||
func TestABesideDirectoryWithoutTheRepositoryFailsNamingIt(t *testing.T) {
|
||||
empty := t.TempDir()
|
||||
t.Setenv(Env, empty)
|
||||
r := &recorder{TB: t}
|
||||
func() {
|
||||
defer func() { _ = recover() }()
|
||||
Dir(r, "mesh-catalog")
|
||||
}()
|
||||
if !r.failed {
|
||||
t.Fatal("a repository missing beside the check did not fail the test")
|
||||
}
|
||||
if want := "mesh-catalog is not beside this check in " + Env + "=" + empty; len(r.msg) < len(want) || r.msg[:len(want)] != want {
|
||||
t.Fatalf("the failure does not name the repository and the directory: %q", r.msg)
|
||||
}
|
||||
}
|
||||
|
||||
type recorder struct {
|
||||
testing.TB
|
||||
failed bool
|
||||
msg string
|
||||
}
|
||||
|
||||
func (r *recorder) Fatalf(format string, args ...any) {
|
||||
r.failed = true
|
||||
r.msg = sprintf(format, args...)
|
||||
panic(stop{})
|
||||
}
|
||||
|
||||
type stop struct{}
|
||||
|
||||
func sprintf(format string, args ...any) string { return fmt.Sprintf(format, args...) }
|
||||
Vendored
+2
-2
@@ -17,8 +17,8 @@ To move them, from this repository's go/ directory, with the two repositories ch
|
||||
|
||||
rm -rf testdata/beside/mesh-catalog testdata/beside/mesh-host
|
||||
mkdir -p testdata/beside/mesh-catalog testdata/beside/mesh-host/internal/apply
|
||||
git -C ../../mesh-catalog archive <commit> modules predefined.json | tar -x -C testdata/beside/mesh-catalog --wildcards \
|
||||
predefined.json "modules/*/module.json" "modules/*/files/*" "modules/*/templates/*"
|
||||
git -C ../../mesh-catalog archive <commit> -- predefined.json ':(glob)modules/*/module.json' \
|
||||
':(glob)modules/*/files/**' ':(glob)modules/*/templates/**' | tar -x -C testdata/beside/mesh-catalog
|
||||
find testdata/beside -name module.json -exec mv {} {}.captured \;
|
||||
git -C ../../mesh-host show <commit>:internal/apply/placement_guard.go \
|
||||
> testdata/beside/mesh-host/internal/apply/placement_guard.go.captured
|
||||
|
||||
Reference in New Issue
Block a user