103 lines
4.4 KiB
Go
103 lines
4.4 KiB
Go
//go:build integration && isolation
|
|
|
|
package buildstep
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
|
)
|
|
|
|
// 1309/S19, the issue's proof: a run step, in bubblewrap as Isolated runs it, reads no key and no login, resolves no
|
|
// name, reaches no TCP or unix socket of the machine, finds no home of the account, and leaves the shared cache it
|
|
// wrote unchanged; the workspace it writes keeps what it wrote. It needs bubblewrap and unprivileged user namespaces
|
|
// (issue 1308) on the machine that runs it, and fails naming them when they are not there.
|
|
func TestARunStepReachesNothingAndLeavesTheCachesUnchanged(t *testing.T) {
|
|
if _, err := exec.LookPath(Bubblewrap); err != nil {
|
|
t.Fatalf("bubblewrap is not installed here, so the isolation of a run step cannot be proved (issue 1309): %v", err)
|
|
}
|
|
root := t.TempDir()
|
|
account := filepath.Join(root, "account")
|
|
key, login := filepath.Join(account, ".ssh", "id_ed25519"), filepath.Join(account, "bus-login.json")
|
|
writeFile(t, filepath.Dir(key), filepath.Base(key), "PRIVATE KEY")
|
|
writeFile(t, account, "bus-login.json", `{"password":"secret"}`)
|
|
cacheDir := filepath.Join(root, "cache")
|
|
writeFile(t, cacheDir, "warm", "warm\n")
|
|
workspace := filepath.Join(root, "work", "tree")
|
|
if err := os.MkdirAll(workspace, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
probe := filepath.Join(workspace, "probe")
|
|
if out, err := exec.Command("go", "build", "-o", probe, "./testdata/isolated-probe").CombinedOutput(); err != nil {
|
|
t.Fatalf("building the probe: %v\n%s", err, out)
|
|
}
|
|
tcp, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tcp.Close()
|
|
socket := filepath.Join(account, "bus.sock")
|
|
unix, err := net.Listen("unix", socket)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer unix.Close()
|
|
|
|
run := Isolated(forgekey.ExecRunnerFor(0), Isolation{Workspace: workspace, Caches: []string{cacheDir}})
|
|
r := run(context.Background(), []string{"PATH=/usr/bin:/bin"}, probe, key, login, tcp.Addr().String(), socket, "localhost.localdomain.invalid", filepath.Join(cacheDir, "warm"))
|
|
if r.Status != 0 || r.Err != "" || strings.TrimSpace(r.Stdout) != "shut off" {
|
|
t.Fatalf("the run step was not shut off (status %d, %s):\n%s%s", r.Status, r.Err, r.Stdout, r.Stderr)
|
|
}
|
|
if b, _ := os.ReadFile(filepath.Join(cacheDir, "warm")); string(b) != "warm\n" {
|
|
t.Fatalf("the step changed the shared cache: %q", b)
|
|
}
|
|
|
|
// The workspace is the step's own: what it writes there stays.
|
|
r = run(context.Background(), nil, "/bin/sh", "-c", "echo built > out")
|
|
if b, err := os.ReadFile(filepath.Join(workspace, "out")); r.Status != 0 || err != nil || string(b) != "built\n" {
|
|
t.Fatalf("the step's write to its workspace did not stay (status %d, %s%s): %q %v", r.Status, r.Stderr, r.Err, b, err)
|
|
}
|
|
}
|
|
|
|
// 1921/S2: in one ask (one cache layer), a second go build of the same package does not compile it again, and the
|
|
// shared cache is unchanged afterwards.
|
|
func TestASecondBuildOfTheAskFindsTheFirstsCompiledAndTheSharedCacheIsUnchanged(t *testing.T) {
|
|
if _, err := exec.LookPath(Bubblewrap); err != nil {
|
|
t.Fatalf("bubblewrap is not installed here (issue 1309): %v", err)
|
|
}
|
|
goBin, err := exec.LookPath("go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := t.TempDir()
|
|
shared := filepath.Join(root, "gobuild")
|
|
if err := os.MkdirAll(shared, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
workspace := filepath.Join(root, "ws")
|
|
tree := filepath.Join(workspace, "tree")
|
|
writeFile(t, tree, "go.mod", "module probe\n\ngo 1.22\n")
|
|
writeFile(t, tree, "probe.go", "package probe\n\nfunc Probe() int { return 42 }\n")
|
|
run := Isolated(forgekey.ExecRunnerFor(0), Isolation{Workspace: workspace, Caches: []string{shared},
|
|
CacheLayer: filepath.Join(workspace, "cachelayer"), ReadOnly: []string{filepath.Dir(filepath.Dir(goBin))}})
|
|
env := []string{"PATH=" + filepath.Dir(goBin) + ":/usr/bin:/bin", "GOCACHE=" + shared, "GOTOOLCHAIN=local", "GOFLAGS=-mod=mod"}
|
|
for i, want := range []bool{true, false} {
|
|
r := run(context.Background(), env, "go", "-C", tree, "build", "-x", "./...")
|
|
if r.Status != 0 || r.Err != "" {
|
|
t.Fatalf("build %d: status %d %s\n%s", i+1, r.Status, r.Err, r.Stderr)
|
|
}
|
|
if compiled := strings.Contains(r.Stderr, "/compile "); compiled != want {
|
|
t.Errorf("build %d compiled the package: %v, want %v\n%s", i+1, compiled, want, r.Stderr)
|
|
}
|
|
}
|
|
if entries, _ := os.ReadDir(shared); len(entries) != 0 {
|
|
t.Errorf("the shared cache was written: %d entries", len(entries))
|
|
}
|
|
}
|