mesh-build-go, mesh-build-node: a missing bubblewrap fails the sandbox proofs on a build node instead of skipping them (issue 1309) #523
@@ -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"})
|
||||
|
||||
Reference in New Issue
Block a user