From d1886bbd7bebfa1d1523e74167ebbb0368710e39 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 08:22:45 +0000 Subject: [PATCH] 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). --- go/catalogue/amqp_is_not_a_provision_test.go | 2 +- go/catalogue/bus_snapshot_test.go | 2 +- go/catalogue/catalogue_check_test.go | 2 +- go/catalogue/catrust_manifest_test.go | 2 +- go/catalogue/engine_guard_test.go | 2 +- go/catalogue/foundation_manifests_test.go | 44 ++--------- go/catalogue/mounts_test.go | 2 +- go/catalogue/no_subjects_test.go | 2 +- go/catalogue/provided_test.go | 4 +- go/catalogue/public_issuer_test.go | 83 +------------------- go/catalogue/seats_test.go | 38 --------- go/catalogue/showcase_manifest_test.go | 2 +- go/catalogue/ssh_client_test.go | 2 +- go/internal/beside/beside.go | 46 +++++++++-- go/internal/beside/beside_test.go | 62 +++++++++++++++ go/testdata/beside/CAPTURED | 4 +- 16 files changed, 121 insertions(+), 178 deletions(-) create mode 100644 go/internal/beside/beside_test.go diff --git a/go/catalogue/amqp_is_not_a_provision_test.go b/go/catalogue/amqp_is_not_a_provision_test.go index 932c81d..11d1ad5 100644 --- a/go/catalogue/amqp_is_not_a_provision_test.go +++ b/go/catalogue/amqp_is_not_a_provision_test.go @@ -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 diff --git a/go/catalogue/bus_snapshot_test.go b/go/catalogue/bus_snapshot_test.go index e990435..e767d48 100644 --- a/go/catalogue/bus_snapshot_test.go +++ b/go/catalogue/bus_snapshot_test.go @@ -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 diff --git a/go/catalogue/catalogue_check_test.go b/go/catalogue/catalogue_check_test.go index f853c01..a0d13fd 100644 --- a/go/catalogue/catalogue_check_test.go +++ b/go/catalogue/catalogue_check_test.go @@ -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. diff --git a/go/catalogue/catrust_manifest_test.go b/go/catalogue/catrust_manifest_test.go index dfad82c..d55bdb8 100644 --- a/go/catalogue/catrust_manifest_test.go +++ b/go/catalogue/catrust_manifest_test.go @@ -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 diff --git a/go/catalogue/engine_guard_test.go b/go/catalogue/engine_guard_test.go index 85b7597..2988c9e 100644 --- a/go/catalogue/engine_guard_test.go +++ b/go/catalogue/engine_guard_test.go @@ -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 diff --git a/go/catalogue/foundation_manifests_test.go b/go/catalogue/foundation_manifests_test.go index 88e64de..cc52f6a 100644 --- a/go/catalogue/foundation_manifests_test.go +++ b/go/catalogue/foundation_manifests_test.go @@ -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/ 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/, 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) { diff --git a/go/catalogue/mounts_test.go b/go/catalogue/mounts_test.go index f929a5d..d13c6dc 100644 --- a/go/catalogue/mounts_test.go +++ b/go/catalogue/mounts_test.go @@ -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, diff --git a/go/catalogue/no_subjects_test.go b/go/catalogue/no_subjects_test.go index 23c46c3..a247296 100644 --- a/go/catalogue/no_subjects_test.go +++ b/go/catalogue/no_subjects_test.go @@ -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). diff --git a/go/catalogue/provided_test.go b/go/catalogue/provided_test.go index a4f9413..f09c0e9 100644 --- a/go/catalogue/provided_test.go +++ b/go/catalogue/provided_test.go @@ -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. diff --git a/go/catalogue/public_issuer_test.go b/go/catalogue/public_issuer_test.go index e80ddfb..ec8e992 100644 --- a/go/catalogue/public_issuer_test.go +++ b/go/catalogue/public_issuer_test.go @@ -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). diff --git a/go/catalogue/seats_test.go b/go/catalogue/seats_test.go index beceaab..2b01748 100644 --- a/go/catalogue/seats_test.go +++ b/go/catalogue/seats_test.go @@ -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 { diff --git a/go/catalogue/showcase_manifest_test.go b/go/catalogue/showcase_manifest_test.go index d6ecf33..1ba866c 100644 --- a/go/catalogue/showcase_manifest_test.go +++ b/go/catalogue/showcase_manifest_test.go @@ -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.** diff --git a/go/catalogue/ssh_client_test.go b/go/catalogue/ssh_client_test.go index 8981e31..4abec06 100644 --- a/go/catalogue/ssh_client_test.go +++ b/go/catalogue/ssh_client_test.go @@ -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:}, read from the module's own diff --git a/go/internal/beside/beside.go b/go/internal/beside/beside.go index b64f67c..93a0d40 100644 --- a/go/internal/beside/beside.go +++ b/go/internal/beside/beside.go @@ -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. diff --git a/go/internal/beside/beside_test.go b/go/internal/beside/beside_test.go new file mode 100644 index 0000000..47655eb --- /dev/null +++ b/go/internal/beside/beside_test.go @@ -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...) } diff --git a/go/testdata/beside/CAPTURED b/go/testdata/beside/CAPTURED index 825bcd1..dc56ca5 100644 --- a/go/testdata/beside/CAPTURED +++ b/go/testdata/beside/CAPTURED @@ -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 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 -- 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 :internal/apply/placement_guard.go \ > testdata/beside/mesh-host/internal/apply/placement_guard.go.captured