| index d14d19014..59e9566d5 100644 |
| --- a/internal/activator/activator_test.go |
| +++ b/internal/activator/activator_test.go |
| @@ -3051,3 +3051,92 @@ func TestSeedCreatedTheDB(t *testing.T) { |
| }) |
| } |
| } |
| + |
| +// --- Instance-socket mode normalization (issue #103) ------------------------- |
| + |
| +// TestMain lets this test binary double as a fake instance app: Start execs |
| +// the live/<host> symlink, and stageServingLive points that symlink at the |
| +// test binary itself with ACTIVATOR_FAKE_APP=1 in the inherited environment |
| +// (childEnv strips only CARLOS_* vars, so it flows through to the child). |
| +// The shell-script recorders elsewhere in this file can't answer the health |
| +// probe — serving HTTP on a unix socket needs a real program, and the test |
| +// binary is the one program every `go test` run is guaranteed to have. |
| +func TestMain(m *testing.M) { |
| + if os.Getenv("ACTIVATOR_FAKE_APP") == "1" { |
| + fakeAppMain() |
| + return |
| + } |
| + os.Exit(m.Run()) |
| +} |
| + |
| +// fakeAppMain is the app half: bind the --socket, tighten it to 0600 — the |
| +// exact titogo habit that stranded the sibling router in issue #103 — and |
| +// serve 200s until killed. |
| +func fakeAppMain() { |
| + var socket string |
| + for i, a := range os.Args { |
| + if a == "--socket" && i+1 < len(os.Args) { |
| + socket = os.Args[i+1] |
| + } |
| + } |
| + if socket == "" { |
| + os.Exit(2) |
| + } |
| + ln, err := net.Listen("unix", socket) |
| + if err != nil { |
| + os.Exit(1) |
| + } |
| + if err := os.Chmod(socket, 0o600); err != nil { |
| + os.Exit(1) |
| + } |
| + _ = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { |
| + w.WriteHeader(http.StatusOK) |
| + })) |
| +} |
| + |
| +// stageServingLive points live/<host> at the test binary itself (see |
| +// TestMain) so Start spawns a child that actually binds and serves. |
| +func stageServingLive(t *testing.T, b *ExecBackend, host string) { |
| + t.Helper() |
| + self, err := os.Executable() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Setenv("ACTIVATOR_FAKE_APP", "1") |
| + if err := os.MkdirAll(filepath.Join(b.OptDir, "live"), 0o755); err != nil { |
| + t.Fatal(err) |
| + } |
| + os.Remove(b.liveLink(host)) |
| + if err := os.Symlink(self, b.liveLink(host)); err != nil { |
| + t.Fatal(err) |
| + } |
| +} |
| + |
| +// TestExecBackendStartNormalizesSocketMode pins the issue #103 contract: the |
| +// mode an instance socket carries after a successful wake is the PLATFORM's |
| +// (0660 — group members, i.e. enrolled carlos-sockets consumers, may dial), |
| +// never the app's own umask or chmod habit. The fake app deliberately |
| +// tightens its socket to 0600 after binding; Start must win. |
| +func TestExecBackendStartNormalizesSocketMode(t *testing.T) { |
| + requireUnixSockets(t) |
| + b := testExecBackend(t) |
| + host := "mode.example.com" |
| + stageServingLive(t, b, host) |
| + |
| + rt := registry.Route{Host: host, Kind: "instance", Socket: tmpSock(t)} |
| + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| + defer cancel() |
| + proc, _, err := b.Start(ctx, rt) |
| + if err != nil { |
| + t.Fatalf("Start: %v", err) |
| + } |
| + defer proc.Stop(context.Background()) |
| + |
| + fi, err := os.Stat(rt.Socket) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if fi.Mode().Perm() != 0o660 { |
| + t.Fatalf("socket mode after healthy Start = %o, want 660 — an app's own chmod must not strand a sibling consumer", fi.Mode().Perm()) |
| + } |
| +} |