Files
mesh-controller/cmd/mesh-controller/selfexec_integration_test.go
T

139 lines
3.9 KiB
Go

//go:build integration
package main
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/novox/mesh-controller/internal/link"
)
func TestSelfExecServer(t *testing.T) {
if os.Getenv(selfExecRole) != "server" {
t.Skip("run by TestAVerbRunsWhileItsBuildIsMovedOrDeleted")
}
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
if strings.TrimSpace(line) != "go" {
t.Fatalf("told %q", line)
}
if err := os.Setenv(selfExecRole, "verb"); err != nil {
t.Fatal(err)
}
answer, err := runVerb(t.Context(), []string{"-test.run", "^TestSelfExecVerb$", "-test.v"})
said := map[string]any{"ok": answer.OK, "output": answer.Output}
if err != nil {
said["error"] = err.Error()
}
body, _ := json.Marshal(said)
fmt.Println("ANSWER " + string(body))
}
func TestAVerbRunsWhileItsBuildIsMovedOrDeleted(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("the image is named through /proc on Linux only")
}
self, err := os.Executable()
if err != nil {
t.Fatal(err)
}
for _, how := range []string{"moved into a closed directory", "deleted"} {
t.Run(how, func(t *testing.T) {
root := t.TempDir()
placed := filepath.Join(root, "mesh-controller", "mesh-controller")
if err := os.MkdirAll(filepath.Dir(placed), 0o755); err != nil {
t.Fatal(err)
}
copyFile(t, self, placed)
server := exec.Command(placed, "-test.run", "^TestSelfExecServer$", "-test.v")
server.Env = append(os.Environ(), selfExecRole+"=server")
stdin, err := server.StdinPipe()
if err != nil {
t.Fatal(err)
}
stdout, err := server.StdoutPipe()
if err != nil {
t.Fatal(err)
}
server.Stderr = os.Stderr
if err := server.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = server.Process.Kill(); _ = server.Wait() })
// What the witness does to a running build, once the process runs.
switch how {
case "deleted":
if err := os.RemoveAll(filepath.Dir(placed)); err != nil {
t.Fatal(err)
}
default:
kept := filepath.Join(root, ".witness", "mesh-controller", "previous")
if err := os.MkdirAll(filepath.Dir(kept), 0o700); err != nil {
t.Fatal(err)
}
if err := os.Rename(filepath.Dir(placed), kept); err != nil {
t.Fatal(err)
}
if err := os.Chmod(filepath.Join(root, ".witness"), 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chmod(filepath.Join(root, ".witness"), 0o700) })
}
if _, err := io.WriteString(stdin, "go\n"); err != nil {
t.Fatal(err)
}
out, _ := io.ReadAll(stdout)
_ = server.Wait()
var said struct {
OK bool `json:"ok"`
Output string `json:"output"`
Error string `json:"error"`
}
found := false
for _, line := range strings.Split(string(out), "\n") {
if rest, ok := strings.CutPrefix(line, "ANSWER "); ok {
found = json.Unmarshal([]byte(rest), &said) == nil
}
}
if !found {
t.Fatalf("the controller standing in answered nothing:\n%s", out)
}
if said.Error != "" || !said.OK || !strings.Contains(said.Output, "the verb ran") {
t.Fatalf("the verb did not run from the controller's own image after its build was %s: %+v", how, said)
}
})
}
}
func TestAVerbThatCannotRunIsRefusedAsAHandover(t *testing.T) {
closed := filepath.Join(t.TempDir(), "closed")
if err := os.MkdirAll(closed, 0o700); err != nil {
t.Fatal(err)
}
was := ownImage
ownImage = func() string { return filepath.Join(closed, "gone", "mesh-controller") }
t.Cleanup(func() { ownImage = was })
_, err := runVerb(t.Context(), []string{"status"})
if !errors.Is(err, link.ErrHandingOver) {
t.Fatalf("a build that is not there was answered %v, not a handover", err)
}
ownImage = was
handingOver.Store(true)
t.Cleanup(func() { handingOver.Store(false) })
if _, err := runVerb(t.Context(), []string{"-test.run", "^$"}); !errors.Is(err, link.ErrHandingOver) {
t.Fatalf("a controller stopping ran a verb: %v", err)
}
}