| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "net" |
| 5 | "path/filepath" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/registry" |
| 9 | ) |
| 10 | |
| 11 | func TestWarnExternalSocketsShapeAndConditions(t *testing.T) { |
| 12 | sock := func(h string) string { return "/run/carlos/" + h + ".sock" } |
| 13 | cases := []struct { |
| 14 | name string |
| 15 | rt registry.Route |
| 16 | serving, owns bool |
| 17 | warn bool |
| 18 | }{ |
| 19 | {"hibernate=0 live socket warns", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x")}, true, false, true}, |
| 20 | {"hibernate=0 dead socket quiet", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x")}, false, false, false}, |
| 21 | {"hibernate=1 unowned live socket warns", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x"), Hibernate: true}, true, false, true}, |
| 22 | {"hibernate=1 owned live socket quiet", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x"), Hibernate: true}, true, true, false}, |
| 23 | {"hibernate=0 live socket ready status warns", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x"), Status: "ready"}, true, false, true}, |
| 24 | {"addr route (issue #18) never warns", registry.Route{Kind: "instance", App: "a", Host: "x", Addr: "127.0.0.1:9000"}, true, false, false}, |
| 25 | {"unit backing belongs to #14 advisory", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x"), Backing: registry.BackingUnit}, true, false, false}, |
| 26 | {"platform route out of scope", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x"), Platform: true}, true, false, false}, |
| 27 | {"appless route out of scope", registry.Route{Kind: "instance", Host: "x", Socket: sock("x")}, true, false, false}, |
| 28 | {"non-ready status out of scope", registry.Route{Kind: "instance", App: "a", Host: "x", Socket: sock("x"), Status: "draining"}, true, false, false}, |
| 29 | } |
| 30 | for _, c := range cases { |
| 31 | t.Run(c.name, func(t *testing.T) { |
| 32 | serving := func(string) bool { return c.serving } |
| 33 | owns := func(string) bool { return c.owns } |
| 34 | got := warnExternalSockets([]registry.Route{c.rt}, serving, owns) |
| 35 | if (len(got) == 1) != c.warn { |
| 36 | t.Errorf("warn = %v, want %v", len(got) == 1, c.warn) |
| 37 | } |
| 38 | }) |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | // The probe distinguishes a live LISTENER from a stale socket FILE and a |
| 43 | // missing path — only the first is "served". |
| 44 | func TestSocketServingProbe(t *testing.T) { |
| 45 | dir := t.TempDir() |
| 46 | live := filepath.Join(dir, "live.sock") |
| 47 | l, err := net.Listen("unix", live) |
| 48 | if err != nil { |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | defer l.Close() |
| 52 | go func() { |
| 53 | for { |
| 54 | c, err := l.Accept() |
| 55 | if err != nil { |
| 56 | return |
| 57 | } |
| 58 | c.Close() |
| 59 | } |
| 60 | }() |
| 61 | if !socketServing(live) { |
| 62 | t.Error("live listener not reported as serving") |
| 63 | } |
| 64 | stale := filepath.Join(dir, "stale.sock") |
| 65 | l2, err := net.Listen("unix", stale) |
| 66 | if err != nil { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | l2.(*net.UnixListener).SetUnlinkOnClose(false) |
| 70 | l2.Close() |
| 71 | if socketServing(stale) { |
| 72 | t.Error("stale socket file reported as serving") |
| 73 | } |
| 74 | if socketServing(filepath.Join(dir, "absent.sock")) { |
| 75 | t.Error("missing path reported as serving") |
| 76 | } |
| 77 | } |
| 78 | |