Merge pull request 'mesh-build-go, mesh-build-node: a missing bubblewrap fails the sandbox proofs on a build node instead of skipping them (issue 1309)' (#523) from fix/1309-bwrap-fatal into main

This commit was merged in pull request #523.
This commit is contained in:
2026-10-27 21:00:53 +00:00
11 changed files with 116 additions and 14 deletions
@@ -72,7 +72,7 @@ func (m *Module) integrationHere(ctx context.Context, workspace, tree string, pa
return nil, fmt.Errorf("the integration level's bus: %w", err)
}
defer stopBus()
env := append([]string{"MESH_TEST_POSTGRES=" + postgres, "MESH_TEST_NATS=" + nats}, m.besideEnv(workspace)...)
env := append([]string{"MESH_TEST_POSTGRES=" + postgres, "MESH_TEST_NATS=" + nats}, m.checkEnv(workspace)...)
// Compiling counts as setup: the tests are built once, here, with the addresses a TestMain may read, and the run
// below finds them built. Whether they compile is for the run to say; its failure is not read here.
// Only the directories the selected packages lie in compile (ADR 0536 §3); no selection is every directory.
@@ -228,7 +228,7 @@ func (m *Module) answer(ctx context.Context, spec levelSpec) levelAnswer {
return levelAnswer{Digest: &d}
case stepUnit:
res, err := m.runLevel(ctx, tree, levelOpts{workspace: spec.Workspace, level: LevelUnit, packages: spec.Packages, run: spec.Run,
bounds: Bounds{Run: m.unitBound()}, env: m.besideEnv(spec.Workspace)})
bounds: Bounds{Run: m.unitBound()}, env: m.checkEnv(spec.Workspace)})
if err != nil {
return levelAnswer{Error: err.Error()}
}
@@ -0,0 +1,44 @@
package main
import (
"slices"
"testing"
)
// Issue 1309: a test that runs a step in bubblewrap is proof only where it runs. Where a build module judges (it hands
// the tests MESH_BUILD_NODE=1) a missing bubblewrap is a failure, since every build node declares and has it; a missing
// bubblewrap anywhere else, or any other missing tool, is a skip that says NOT RUN.
func TestAMissingToolSkipsExceptBubblewrapOnABuildNode(t *testing.T) {
for _, c := range []struct {
name string
tool string
onBuildNode bool
want string
}{
{"bubblewrap on a desk", "bwrap", false, "skip"},
{"bubblewrap on a build node", "bwrap", true, "fail"},
{"another tool on a build node", "initdb", true, "skip"},
{"another tool on a desk", "initdb", false, "skip"},
} {
if got := missingToolAction(c.tool, c.onBuildNode); got != c.want {
t.Errorf("%s: %q, want %q", c.name, got, c.want)
}
}
}
// The levels a build module judges hand the tests the build node mark, with the siblings they read.
func TestEveryLevelHandsTheTestsTheBuildNodeMark(t *testing.T) {
m := &Module{Work: t.TempDir()}
if env := m.checkEnv("ws1"); !slices.Contains(env, BuildNodeEnv+"=1") {
t.Errorf("env %v lacks %s=1", env, BuildNodeEnv)
}
}
// missingToolAction is what a test does when tool is not installed: "fail" for bubblewrap where the build module that
// judges says so (BuildNodeEnv), otherwise "skip".
func missingToolAction(tool string, onBuildNode bool) string {
if tool == "bwrap" && onBuildNode {
return "fail"
}
return "skip"
}
@@ -83,7 +83,7 @@ func (m *Module) test(ctx context.Context, args map[string]any) (any, error) {
case runner == RunnerWorkspace && (level == LevelUnit || level == LevelIntegration):
return m.testAsCandidate(ctx, workspace, level, packages, pattern)
case level == LevelUnit:
res, err = m.runLevel(ctx, tree, levelOpts{workspace: workspace, level: LevelUnit, packages: packages, run: pattern, bounds: Bounds{Run: m.unitBound()}, env: m.besideEnv(workspace)})
res, err = m.runLevel(ctx, tree, levelOpts{workspace: workspace, level: LevelUnit, packages: packages, run: pattern, bounds: Bounds{Run: m.unitBound()}, env: m.checkEnv(workspace)})
case level == LevelIntegration:
res, err = m.integration(ctx, workspace, tree, packages, pattern)
default:
@@ -128,6 +128,15 @@ func (m *Module) besideEnv(workspace string) []string {
return []string{"MESH_CHECK_BESIDE=" + dir}
}
// BuildNodeEnv is the mark a build module hands the tests it runs: this is a build node, where bubblewrap is declared
// and present, so a test that needs it fails where it is missing instead of skipping (issue 1309).
const BuildNodeEnv = "MESH_BUILD_NODE"
// checkEnv is what every level hands the tests it runs: the build node mark and the siblings (besideEnv).
func (m *Module) checkEnv(workspace string) []string {
return append([]string{BuildNodeEnv + "=1"}, m.besideEnv(workspace)...)
}
// packagesArg reads the packages argument: a list of package patterns, as []any when it came over the bus as JSON and
// as []string when an ask handed it in process (issue 1925).
func packagesArg(v any) ([]string, error) {
@@ -33,6 +33,9 @@ func needTools(t *testing.T, what string, tools ...string) {
t.Helper()
for _, tool := range tools {
if _, err := exec.LookPath(tool); err != nil {
if missingToolAction(tool, os.Getenv(BuildNodeEnv) == "1") == "fail" {
t.Fatalf("no %s installed on a build node (%s): every build node declares it, so the proof is not skipped here", tool, what)
}
t.Skipf("NOT RUN: no %s installed where this check runs (%s); proven live on a build node", tool, what)
}
}
@@ -24,7 +24,8 @@ import (
// name: an install from the public registry inside a shut-off level only fails, after 70 s of npm's retries.
func realMachine(t *testing.T) (*Module, string) {
t.Helper()
for _, p := range []string{"node", "npm", "bwrap"} { // bwrap: tsc, esbuild and the scripts run shut off (1309)
needBubblewrap(t) // tsc, esbuild and the scripts run shut off (1309)
for _, p := range []string{"node", "npm"} {
if _, err := exec.LookPath(p); err != nil {
t.Skipf("NOT RUN: no %s installed where this check runs (the mesh's Go toolchain image holds none); proven live on a build node", p)
}
@@ -7,7 +7,6 @@ import (
"encoding/json"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
@@ -34,9 +33,7 @@ fs.writeFileSync("seen.json", JSON.stringify(seen));
// resolves no name, reaches no socket of the machine (a TCP port on its loopback, a unix socket), and leaves the
// shared cache unchanged.
func TestARunStepReachesNoKeyLoginNameOrSocketAndLeavesTheCacheUnchanged(t *testing.T) {
if _, err := exec.LookPath("bwrap"); err != nil {
t.Skip("NOT RUN: no bubblewrap installed where this check runs; every build node has it (the module declares it)")
}
needBubblewrap(t)
m, work := realMachine(t)
home := m.Toolchain.Paths.Home
key, login := filepath.Join(home, ".ssh", "mesh_ed25519"), filepath.Join(home, "broker")
@@ -0,0 +1,49 @@
package main
import (
"os"
"os/exec"
"testing"
)
// bubblewrapAction is what a test that runs a step in bubblewrap does: "" when bubblewrap is there, "fail" where a
// build module that judges says this is a build node (MESH_BUILD_NODE=1, which mesh-build-go hands the tests), since
// every build node declares and has it, and "skip" anywhere else (issue 1309).
func bubblewrapAction(found bool, buildNodeMark string) string {
switch {
case found:
return ""
case buildNodeMark == "1":
return "fail"
}
return "skip"
}
// needBubblewrap stops a test that runs a step in bubblewrap where there is none.
func needBubblewrap(t *testing.T) {
t.Helper()
_, err := exec.LookPath("bwrap")
switch bubblewrapAction(err == nil, os.Getenv("MESH_BUILD_NODE")) {
case "fail":
t.Fatal("no bubblewrap installed on a build node: every build node declares it, so the proof is not skipped here")
case "skip":
t.Skip("NOT RUN: no bubblewrap installed where this check runs; every build node has it (the module declares it)")
}
}
func TestAMissingBubblewrapFailsOnABuildNodeAndSkipsElsewhere(t *testing.T) {
for _, c := range []struct {
name string
found bool
mark string
want string
}{
{"there", true, "1", ""},
{"missing on a desk", false, "", "skip"},
{"missing on a build node", false, "1", "fail"},
} {
if got := bubblewrapAction(c.found, c.mark); got != c.want {
t.Errorf("%s: %q, want %q", c.name, got, c.want)
}
}
}
@@ -45,7 +45,8 @@ func forgeRepo(t *testing.T, root, name string, files map[string]string) string
// (its build script and npm pack shut off, nothing published), and a member whose lock names that version with that
// integrity gets it in its node_modules; a member whose lock names it with another integrity fails naming both.
func TestTheFetchPacksTheGroupsPackageAndServesItToAMember(t *testing.T) {
for _, tool := range []string{"node", "npm", "git", "bwrap"} {
needBubblewrap(t)
for _, tool := range []string{"node", "npm", "git"} {
if _, err := exec.LookPath(tool); err != nil {
t.Skipf("NOT RUN: no %s where this check runs; every build node has it", tool)
}
@@ -81,7 +81,8 @@ func (r *npmRegistry) serve(t *testing.T) *httptest.Server {
// the module's credential, once: a second trunk build of the same package finds its file there and publishes nothing;
// a candidate publishes nothing at all.
func TestARealTrunkPackageIsPublishedOnce(t *testing.T) {
for _, tool := range []string{"node", "npm", "bwrap"} {
needBubblewrap(t)
for _, tool := range []string{"node", "npm"} {
if _, err := exec.LookPath(tool); err != nil {
t.Skipf("NOT RUN: no %s where this check runs; every build node has it", tool)
}
@@ -10,7 +10,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@@ -45,9 +44,7 @@ func realStore(t *testing.T, m *Module) string {
// 1587-S10 (ADR 0498's check): a done bundle names a reference whose tail is its digest, and that reference resolves in
// a real artifact store to bytes of that digest. The build is the real tsc's and esbuild's, shut off.
func TestADoneBundleResolvesInARealArtifactStore(t *testing.T) {
if _, err := exec.LookPath("bwrap"); err != nil {
t.Skip("NOT RUN: no bubblewrap installed where this check runs; every build node has it (the module declares it)")
}
needBubblewrap(t)
m, _ := realMachine(t)
addr := realStore(t, m)
out, err := m.build(context.Background(), map[string]any{"workspace": "w1", "module": "modules/m", "artifact": "tools"})