Files
mesh-catalog/modules/mesh-build-go/cmd/mesh-build-go/isolate_test.go
T
jschoubben eed6be0f4a mesh-build-go: run go test, go build and go list in bubblewrap, so a change's code reaches no key, login or network (issue 1309, S1-S6, ADR 0448)
Each run step goes through mesh-sdk's buildstep.Isolated: no network, none of the account,
the workspace read-write, the Go caches beneath a throwaway layer unless the build step is
told publish (a trunk build). The integration level runs its store, its bus and its tests in
one shut-off process (this program again, level <spec>), so they reach each other and
nothing else. build_go_test takes a -run pattern (a try's tests). The module declares the
bubblewrap package and refuses a step, unhealthy, while bwrap is missing or older than 0.10.
The proof test runs a probe test through the real unit level.
2026-10-22 18:12:01 +02:00

296 lines
11 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"path/filepath"
"slices"
"strings"
"sync"
"testing"
"git.novox.be/novox/mesh-sdk/go/buildstep"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// shutOff is one command a step ran in bubblewrap: the bubblewrap words before "--", what it bound read-write, read-only
// and beneath a throwaway layer, and the command with its environment.
type shutOff struct {
flags []string
binds, roBinds, overlays []string
env []string
command string
chdir string
}
// bwrapArity is how many words each bubblewrap option the SDK writes takes.
var bwrapArity = map[string]int{"--unshare-all": 0, "--die-with-parent": 0, "--new-session": 0, "--clearenv": 0,
"--proc": 1, "--dev": 1, "--tmpfs": 1, "--dir": 1, "--chdir": 1, "--overlay-src": 1, "--tmp-overlay": 1,
"--ro-bind": 2, "--ro-bind-try": 2, "--bind": 2, "--symlink": 2, "--setenv": 2}
// throughBubblewrap is the fake runner of a step's tests: a bubblewrap command is read back (recorded in seen) and its
// command handed to run with the environment bubblewrap was told to set, the way it runs in the step; any other command
// (the fetch's git, postgres --version) goes to run as it is.
func throughBubblewrap(t *testing.T, run forgekey.Runner, seen *[]shutOff) forgekey.Runner {
t.Helper()
var mu sync.Mutex
return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
if name != buildstep.Bubblewrap {
return run(ctx, env, name, args...)
}
var s shutOff
i := 0
for ; i < len(args) && args[i] != "--"; i++ {
n, ok := bwrapArity[args[i]]
if !ok || i+n >= len(args) {
t.Errorf("bubblewrap was given %q, which the test does not know: %v", args[i], args)
return forgekey.Ran{Status: 1, Err: "unknown bubblewrap option"}
}
s.flags = append(s.flags, args[i])
switch args[i] {
case "--bind":
s.binds = append(s.binds, args[i+1])
case "--ro-bind", "--ro-bind-try":
s.roBinds = append(s.roBinds, args[i+1])
case "--overlay-src":
s.overlays = append(s.overlays, args[i+1])
case "--setenv":
s.env = append(s.env, args[i+1]+"="+args[i+2])
case "--chdir":
s.chdir = args[i+1]
}
i += n
}
if i+1 >= len(args) {
t.Errorf("bubblewrap was given no command: %v", args)
return forgekey.Ran{Status: 1, Err: "no command"}
}
s.command = args[i+1] + " " + strings.Join(args[i+2:], " ")
mu.Lock()
*seen = append(*seen, s)
mu.Unlock()
return run(ctx, s.env, args[i+1], args[i+2:]...)
}
}
// checkShutOff fails the test unless s ran with no network and none of the account: every namespace unshared, the
// environment cleared, nothing of the home or the state bound, the caches beneath a throwaway layer unless write.
func checkShutOff(t *testing.T, m *Module, s shutOff, write bool) {
t.Helper()
for _, f := range []string{"--unshare-all", "--clearenv", "--die-with-parent"} {
if !slices.Contains(s.flags, f) {
t.Errorf("%s ran without %s: %v", s.command, f, s.flags)
}
}
for _, b := range append(append([]string{}, s.binds...), s.roBinds...) {
if m.Home != "" && (b == m.Home || strings.HasPrefix(b, m.Home+"/")) {
t.Errorf("%s was given the account's home: %s", s.command, b)
}
if strings.HasPrefix(b, "/run") {
t.Errorf("%s was given the runtime directory: %s", s.command, b)
}
}
for _, e := range s.env {
if strings.HasPrefix(e, "HOME="+m.Home) && m.Home != "" {
t.Errorf("%s was told the account's home: %s", s.command, e)
}
}
caches := m.caches()
for _, c := range caches {
bound, layered := slices.Contains(s.binds, c), slices.Contains(s.overlays, c)
switch {
case write && (!bound || layered):
t.Errorf("a trunk build's %s does not write the shared cache %s (bound %v, layered %v)", s.command, c, bound, layered)
case !write && (bound || !layered):
t.Errorf("%s may write the shared cache %s (bound %v, layered %v): only a trunk build does", s.command, c, bound, layered)
}
}
if want := filepath.Join(m.Work, "ws1"); !slices.Contains(s.binds, want) {
t.Errorf("%s was not given its workspace %s read-write: %v", s.command, want, s.binds)
}
}
// 1309-S2: every go test of the unit level runs shut off in bubblewrap: no network, none of the account, the caches
// beneath a throwaway layer.
func TestTheUnitLevelRunsEveryGoTestShutOff(t *testing.T) {
m, states, ws, calls := workspaceFor(t, "failing")
var seen []shutOff
m.Run = throughBubblewrap(t, m.Run, &seen)
if _, st := runUnit(t, m, states, ws); st.State != delivery.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
ran := 0
for _, c := range *calls {
if strings.HasPrefix(c.line, "go ") {
ran++
}
}
if ran == 0 || len(seen) != ran {
t.Fatalf("%d go commands ran, %d of them shut off: %v", ran, len(seen), *calls)
}
for _, s := range seen {
checkShutOff(t, m, s, false)
}
}
// 1309-S2: a try's tests: build_go_test's run pattern reaches go test as one -run word; a pattern that is a flag is refused.
func TestTheRunPatternReachesGoTestAndAFlagIsRefused(t *testing.T) {
m, states, ws, calls := workspaceFor(t, "failing")
var seen []shutOff
m.Run = throughBubblewrap(t, m.Run, &seen)
run := stepTool(t, m, "build_go_test")
if _, err := run(map[string]any{"workspace": ws, "level": "unit", "run": "TestA|TestB"}); err != nil {
t.Fatal(err)
}
states.waitEnd(t)
found := false
for _, c := range *calls {
found = found || strings.Contains(c.line, " -run TestA|TestB ")
}
if !found {
t.Errorf("no go test was given -run TestA|TestB: %v", *calls)
}
for _, bad := range []string{"-exec=/bin/sh", "a b", "(unclosed"} {
if runPatternOK(bad) {
t.Errorf("the run pattern %q was taken", bad)
}
}
}
// 1309-S3 and S4: a candidate's go build runs shut off with the caches beneath a throwaway layer; a trunk build
// (publish) writes them.
func TestABuildRunsShutOffAndOnlyATrunkBuildWritesTheCaches(t *testing.T) {
for _, publish := range []bool{false, true} {
m, states, _ := buildModule(t, manifestWith(goBundle), "binary")
var seen []shutOff
m.Run = throughBubblewrap(t, m.Run, &seen)
run := stepTool(t, m, "build_go_build")
args := map[string]any{"workspace": "ws1", "module": "modules/demo", "artifact": "tools"}
if publish {
args["publish"] = true
}
if _, err := run(args); err != nil {
t.Fatal(err)
}
if st := states.waitEnd(t); st.State != delivery.StepPassed {
t.Fatalf("publish %v: the step ended %s: %s", publish, st.State, st.Reason)
}
if len(seen) != 1 || !strings.HasPrefix(seen[0].command, "go ") || !strings.Contains(seen[0].command, " build ") {
t.Fatalf("publish %v: shut off %v, want the one go build", publish, seen)
}
checkShutOff(t, m, seen[0], publish)
}
}
// 1309-S3: the digest's go list runs shut off.
func TestTheDigestListsPackagesShutOff(t *testing.T) {
m, _ := digestFixture(t)
var seen []shutOff
m.Run = throughBubblewrap(t, m.Run, &seen)
digests(t, m, LevelUnit)
if len(seen) == 0 {
t.Fatal("the digest ran no command shut off")
}
for _, s := range seen {
if !strings.HasPrefix(s.command, "go ") || !strings.Contains(s.command, " list ") {
t.Errorf("the digest shut off %q, want go list", s.command)
}
checkShutOff(t, m, s, false)
}
}
// 1309-S5: the integration level runs its store, its bus and its tests in ONE shut-off process (this program again,
// `level <spec>`), so they reach each other and nothing else; its answer is the level's result. Nothing of the level
// runs outside but reading postgres's version.
func TestTheIntegrationLevelRunsInOneShutOffProcess(t *testing.T) {
m, states, ws, _ := workspaceFor(t, "failing")
m.Executable = "/opt/bundle/mesh-build-go"
want := TestResult{Level: LevelIntegration, Passed: true, Tests: 3, Packages: []PackageResult{}, Store: "postgres 18"}
var outside []string
var seen []shutOff
m.Run = throughBubblewrap(t, func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
switch {
case name == "postgres" && len(args) == 1 && args[0] == "--version":
return forgekey.Ran{Stdout: "postgres (PostgreSQL) 18.6\n"}
case name == m.Executable && len(args) == 2 && args[0] == levelArg:
var spec levelSpec
if err := json.Unmarshal([]byte(args[1]), &spec); err != nil || spec.Workspace != ws || spec.Work != m.Work {
t.Errorf("the level's spec %s: %v", args[1], err)
}
raw, _ := json.Marshal(levelAnswer{Result: &want})
return forgekey.Ran{Stdout: string(raw) + "\n"}
}
outside = append(outside, name+" "+strings.Join(args, " "))
return forgekey.Ran{Status: 1, Err: "not expected here"}
}, &seen)
run := stepTool(t, m, "build_go_test")
if _, err := run(map[string]any{"workspace": ws, "level": LevelIntegration}); err != nil {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
var got TestResult
if err := json.Unmarshal(st.Result, &got); err != nil || got.Tests != 3 || !got.Passed || got.Store != "postgres 18" {
t.Errorf("the result %s (%v), want the shut-off process's", st.Result, err)
}
if len(outside) > 0 {
t.Errorf("ran outside the shut-off process: %v", outside)
}
if len(seen) != 1 {
t.Fatalf("shut off %d processes, want one: %v", len(seen), seen)
}
checkShutOff(t, m, seen[0], false)
if !slices.Contains(seen[0].roBinds, m.Executable) || slices.Contains(seen[0].roBinds, filepath.Dir(m.Executable)) {
t.Errorf("the program is bound %v; want the program alone, read-only", seen[0].roBinds)
}
}
// 1309-S1: a step is refused, and the module is unhealthy, while bubblewrap is missing or older than 0.10, naming it.
func TestAnOldOrMissingBubblewrapRefusesTheStepsAndIsUnhealthy(t *testing.T) {
for _, c := range []struct {
version string
err error
ok bool
}{{"0.13.0", nil, true}, {"0.10.0", nil, true}, {"0.9.0", nil, false}, {"", errors.New("bwrap: not found"), false}} {
m := moduleWith(newStates(), "go1.27.1")
m.Bubblewrap = func(context.Context) (string, error) { return c.version, c.err }
err := m.Check(context.Background())
h := m.Health(context.Background())
raw, _ := json.Marshal(h)
if c.ok {
if err != nil || strings.Contains(string(raw), "unhealthy") {
t.Errorf("bubblewrap %q: refused %v, health %s", c.version, err, raw)
}
continue
}
var ref *buildstep.Refusal
if !errors.As(err, &ref) || !strings.Contains(err.Error(), "bubblewrap") {
t.Errorf("bubblewrap %q %v: Check answered %v, want a refusal naming bubblewrap", c.version, c.err, err)
}
if !strings.Contains(string(raw), "unhealthy") || !strings.Contains(string(raw), "bubblewrap") {
t.Errorf("bubblewrap %q %v: health %s, want unhealthy naming bubblewrap", c.version, c.err, raw)
}
}
for out, want := range map[string]string{"bubblewrap 0.13.0\n": "0.13.0", "bubblewrap 0.10": "0.10"} {
if got, err := parseBubblewrapVersion(out); err != nil || got != want {
t.Errorf("%q read as %q, %v", out, got, err)
}
}
}
// stepTool is the Run of the named tool.
func stepTool(t *testing.T, m *Module, name string) func(map[string]any) (any, error) {
t.Helper()
for _, tool := range m.Tools(m.StepTools()) {
if tool.Name == name {
return tool.Run
}
}
t.Fatalf("no tool %s", name)
return nil
}