Files
mesh-lab/replays/bed_test.go
T
jochen eecbfd6693 Prove every changed health check on a bed before the catalogue merges (hq ADR 0240, to-be 48 Phase D)
Two of nineteen image checks read unhealthy while working in the mesh's hands;
adopted without proof they would have put back two good builds. The replays
every catalogue merge check runs now start each long-running container whose
declared check or image the change touches, alone, with what its module
declares and nothing of the mesh's, and require the check to see the program
within its grace: a program that stays up while its check does not see it
fails the change; one that does not stay up alone is said and left to the
first machine's gate; a check needing a provider must only reach the program.
R-studio replays the studio's false unhealthy — a server bound to HOSTNAME's
address and an image check asking localhost — failed on the bed, and proved
with HOSTNAME=0.0.0.0.
2026-10-07 15:12:26 +02:00

162 lines
7.0 KiB
Go

package replays
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
// **The catalogue's bed** (novox/hq ADR 0240 rule 7, to-be 48 §8, Phase D): every long-running resource whose
// declared `health` or image the change touches is started alone and its check must see the program within
// its grace. Run by every merge check of the catalogue on the build seat, with the change's catalogue at
// MESH_REPLAY_CATALOGUE; by hand against the catalogue beside the lab. What changed is against
// MESH_BED_BASE (origin/main by default) in that checkout.
func TestBedProvesEveryChangedHealthCheck(t *testing.T) {
root := orDefault(os.Getenv("MESH_REPLAY_CATALOGUE"), filepath.Join("..", "..", "mesh-catalog"))
if _, err := os.Stat(filepath.Join(root, "modules")); err != nil {
t.Skipf("no catalogue at %s (MESH_REPLAY_CATALOGUE names it)", root)
}
base := orDefault(os.Getenv("MESH_BED_BASE"), "origin/main")
show := func(ref, path string) ([]byte, error) {
return exec.Command("git", "-c", "safe.directory=*", "-C", root, "show", ref+":"+path).Output()
}
if _, err := exec.Command("git", "-c", "safe.directory=*", "-C", root, "rev-parse", "--verify", base).Output(); err != nil {
// What changed cannot be told, so every declared check is proved: the bed never passes what it did
// not look at.
t.Logf("%s is not in the catalogue at %s (%v): every declared check is proved", base, root, err)
}
changed, err := ChangedHealth(root, base, show)
if err != nil {
t.Fatal(err)
}
if len(changed) == 0 {
t.Logf("no declared check or image of a long-running resource changed against %s: nothing to prove", base)
return
}
docker, ok := DockerFromEnv()
if !ok {
t.Fatalf("%d changed check(s) and no container runtime to prove them on", len(changed))
}
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Minute)
defer cancel()
for _, r := range changed {
manifest, _ := os.ReadFile(filepath.Join(root, "modules", r.Module, "module.json"))
v := docker.Prove(ctx, r, FilesOf(manifest))
switch {
case v.Failed:
t.Errorf("FAILS ON THE BED: %s", v.Said)
case v.Proved:
t.Logf("proved: %s", v.Said)
default:
t.Logf("NOT PROVED HERE: %s", v.Said)
}
}
}
// **R-studio — a check that asks an address the program does not bind fails the bed, not a machine**
// (novox/hq ADR 0240 Phase D, done when). The hosted database suite's studio binds the address the runtime
// puts in HOSTNAME, so it answered only on its network address while its image's own check asked localhost:
// unhealthy for ever while working, until the module set HOSTNAME=0.0.0.0. Adopted by name without proof, it
// would have put back a good build.
//
// The replay is that image in miniature: a web server that binds $HOSTNAME's address, and an image check that
// asks localhost. Adopted as the module declared it before the fix, the bed fails it; with the fix, it proves
// it. The image is built here and removed after, with everything the bed made.
func TestBedFailsTheStudiosFalseUnhealthy(t *testing.T) {
docker, ok := DockerFromEnv()
if !ok {
t.Skip("no container runtime: the studio is a container")
}
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Minute)
defer cancel()
if err := docker.Pull(ctx, "busybox:1.36"); err != nil {
t.Fatal(err)
}
tag := fmt.Sprintf("mesh-replay-studio:%d", time.Now().UnixNano())
if err := docker.Build(ctx, tag, StudioDockerfile); err != nil {
t.Fatal(err)
}
defer docker.RemoveImage(context.Background(), tag)
was := BedFloor
BedFloor = 15 * time.Second
defer func() { BedFloor = was }()
studio := func(env map[string]any) BedResource {
r := map[string]any{"id": "studio", "type": "container", "name": "supabase-studio", "image": tag,
"health": map[string]any{"kind": "runtime", "grace": "10s"}}
if env != nil {
r["env"] = env
}
return BedResource{Module: "supabase", ID: "studio", Resource: r, Listens: map[string]int{}}
}
before := docker.Prove(ctx, studio(nil), nil)
if !before.Failed || !strings.Contains(before.Said, "does not see it") {
t.Fatalf("the studio's false unhealthy was not failed on the bed: %+v", before)
}
t.Logf("before the fix: %s", before.Said)
after := docker.Prove(ctx, studio(map[string]any{"HOSTNAME": "0.0.0.0"}), nil)
if !after.Proved {
t.Fatalf("the studio with HOSTNAME=0.0.0.0 was not proved: %+v", after)
}
t.Logf("with the fix: %s", after.Said)
}
// StudioDockerfile is the studio in miniature: it serves on the address HOSTNAME names, and its own check
// asks localhost, as the studio's image does.
const StudioDockerfile = `FROM busybox:1.36
RUN mkdir -p /www && echo studio > /www/index.html
HEALTHCHECK --interval=5s --timeout=2s --retries=2 CMD wget -q -O /dev/null http://localhost:3000/ || exit 1
CMD addr=$(grep -w "$HOSTNAME" /etc/hosts | head -n 1 | cut -f 1); exec httpd -f -p "${addr:-$HOSTNAME}:3000" -h /www
`
// What the bed proves is what the change touches: a long-running container whose declared check or image
// changed, or that is new; never a step, never one left as it was.
func TestTheBedProvesWhatTheChangeTouches(t *testing.T) {
root := t.TempDir()
git := func(args ...string) {
t.Helper()
cmd := exec.Command("git", append([]string{"-C", root, "-c", "user.email=lab@example", "-c", "user.name=lab"}, args...)...)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v %s", args, err, out)
}
}
write := func(module, body string) {
t.Helper()
if err := os.MkdirAll(filepath.Join(root, "modules", module), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "modules", module, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
git("init", "-q", "-b", "main")
write("web", `{"module":"web","resources":[{"id":"server","type":"container","image":"web@sha256:a","health":{"kind":"runtime"}},
{"id":"seed","type":"container","image":"web@sha256:a","run-once":true,"health":{"kind":"runtime"}}]}`)
write("db", `{"module":"db","resources":[{"id":"server","type":"container","image":"db@sha256:a","health":{"kind":"runtime"}}]}`)
git("add", "-A")
git("commit", "-qm", "base")
write("web", `{"module":"web","resources":[{"id":"server","type":"container","image":"web@sha256:b","health":{"kind":"runtime"}},
{"id":"seed","type":"container","image":"web@sha256:b","run-once":true,"health":{"kind":"runtime"}}]}`)
write("cache", `{"module":"cache","resources":[{"id":"server","type":"container","image":"cache@sha256:a","health":{"kind":"tcp","endpoint":"redis"}},
{"id":"plain","type":"container","image":"cache@sha256:a"}]}`)
show := func(ref, path string) ([]byte, error) {
return exec.Command("git", "-C", root, "show", ref+":"+path).Output()
}
changed, err := ChangedHealth(root, "main", show)
if err != nil {
t.Fatal(err)
}
var got []string
for _, r := range changed {
got = append(got, r.Module+"."+r.ID)
}
if strings.Join(got, ",") != "cache.server,web.server" {
t.Fatalf("the bed would prove %v; want the new module's checked container and the one whose image moved", got)
}
}