| 1 | package activator |
| 2 | |
| 3 | import ( |
| 4 | "math" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/provision" |
| 10 | "github.com/carlosframework/platform/internal/registry" |
| 11 | ) |
| 12 | |
| 13 | // withGenerations sets the dormant switch for one test and restores it after. |
| 14 | // Every test that builds a Service with the switch on goes through here: the |
| 15 | // switch is package state, so a test that forgot to restore it would turn |
| 16 | // generations on for every test after it. |
| 17 | func withGenerations(t *testing.T, on bool) { |
| 18 | t.Helper() |
| 19 | prev := GenerationsEnabled() |
| 20 | SetGenerations(on) |
| 21 | t.Cleanup(func() { SetGenerations(prev) }) |
| 22 | } |
| 23 | |
| 24 | // Rev 19, rule 4: a nested generation's socket pair fits a unix socket |
| 25 | // address for any host and any generation number, because the instance |
| 26 | // identifier is bounded. A flat one fits for every number exactly when |
| 27 | // flatGenFits says so, and a flat host that does not fit has no generation |
| 28 | // path at all. |
| 29 | func TestGenerationSocketsFitSockaddrUn(t *testing.T) { |
| 30 | for _, host := range []string{ |
| 31 | "h", |
| 32 | "shop.example.com", |
| 33 | strings.Repeat("a", 59-len(".example.com")) + ".example.com", // 59 bytes: the longest that fits |
| 34 | strings.Repeat("b", 60-len(".example.com")) + ".example.com", // 60 bytes |
| 35 | strings.Repeat("c", 113) + ".bab.io", // 120 bytes |
| 36 | strings.Repeat("d", 253), |
| 37 | } { |
| 38 | fits := flatGenFits(host) |
| 39 | if want := len(host) <= 59; fits != want { |
| 40 | t.Errorf("flatGenFits(%d-byte host) = %v, want %v", len(host), fits, want) |
| 41 | } |
| 42 | for _, n := range []uint64{1, 42, math.MaxUint64} { |
| 43 | nested := NestedGenSocket(host, n) |
| 44 | paths := []string{nested, nested + ".control"} |
| 45 | if fits { |
| 46 | flat := GenSocketPath(host, n) |
| 47 | paths = append(paths, flat, flat+".control") |
| 48 | } |
| 49 | for _, p := range paths { |
| 50 | if len(p) > provision.MaxUnixPath { |
| 51 | t.Errorf("%q is %d bytes, over the %d a unix socket address holds", p, len(p), provision.MaxUnixPath) |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | if !fits && len(GenSocketPath(host, math.MaxUint64)+".control") <= provision.MaxUnixPath { |
| 56 | t.Errorf("%d-byte host refused, yet its longest generation control socket fits", len(host)) |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // Rev 19, rule 1: a generation socket's file name is the flag-off one, in a |
| 62 | // directory of the generation's own. |
| 63 | func TestGenSocketPathShape(t *testing.T) { |
| 64 | host := "shop.example.com" |
| 65 | if got, want := GenSocketPath(host, 7), "/run/carlos/g/7/shop.example.com.sock"; got != want { |
| 66 | t.Fatalf("GenSocketPath = %q, want %q", got, want) |
| 67 | } |
| 68 | if got, want := filepath.Base(GenSocketPath(host, 7)), filepath.Base(provision.DefaultSocket(host)); got != want { |
| 69 | t.Fatalf("flat generation file name %q, want the flag-off %q", got, want) |
| 70 | } |
| 71 | if got, want := NestedGenSocket(host, 7), "/run/carlos/instances/"+provision.InstanceID(host)+"/g7/app.sock"; got != want { |
| 72 | t.Fatalf("NestedGenSocket = %q, want %q", got, want) |
| 73 | } |
| 74 | if got, want := filepath.Base(NestedGenSocket(host, 7)), filepath.Base(provision.NestedSocket(host)); got != want { |
| 75 | t.Fatalf("nested generation file name %q, want the flag-off %q", got, want) |
| 76 | } |
| 77 | for _, pair := range [][2]string{ |
| 78 | {GenSocketPath(host, 7), GenSocketPath(host, 8)}, |
| 79 | {GenSocketPath(host, 7), GenSocketPath("other.example.com", 7)}, |
| 80 | {NestedGenSocket(host, 7), NestedGenSocket(host, 8)}, |
| 81 | {NestedGenSocket(host, 7), provision.NestedSocket(host)}, |
| 82 | } { |
| 83 | if pair[0] == pair[1] { |
| 84 | t.Fatalf("generation paths collide: %q", pair[0]) |
| 85 | } |
| 86 | } |
| 87 | if got := filepath.Dir(NestedGenSocket(host, 7)); filepath.Dir(got) != provision.InstanceRunDir(host) { |
| 88 | t.Fatalf("nested generation directory %q is not directly in the run dir %q", got, provision.InstanceRunDir(host)) |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | // Rev 19, rule 4: genLayout reads only an over-long flat row as foreign. |
| 93 | func TestGenLayoutReadsAnOverLongFlatRowAsForeign(t *testing.T) { |
| 94 | short, long := "shop.example.com", strings.Repeat("a", 60-len(".example.com"))+".example.com" |
| 95 | for _, tc := range []struct { |
| 96 | l Layout |
| 97 | host string |
| 98 | want Layout |
| 99 | }{ |
| 100 | {LayoutFlat, short, LayoutFlat}, |
| 101 | {LayoutFlat, long, LayoutForeign}, |
| 102 | {LayoutNested, long, LayoutNested}, |
| 103 | {LayoutForeign, short, LayoutForeign}, |
| 104 | {LayoutFenced, long, LayoutFenced}, |
| 105 | {LayoutConflict, long, LayoutConflict}, |
| 106 | } { |
| 107 | if got := genLayout(tc.l, tc.host); got != tc.want { |
| 108 | t.Errorf("genLayout(%s, %d-byte host) = %s, want %s", tc.l, len(tc.host), got, tc.want) |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | func TestGenCounterIsProcessWideAndNeverRepeats(t *testing.T) { |
| 114 | seen := map[uint64]bool{} |
| 115 | var last uint64 |
| 116 | for i := 0; i < 1000; i++ { |
| 117 | n := nextGen() |
| 118 | if n == 0 || seen[n] || n <= last { |
| 119 | t.Fatalf("nextGen returned %d after %d (seen=%v); want strictly increasing, never 0", n, last, seen[n]) |
| 120 | } |
| 121 | seen[n], last = true, n |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | func TestCanonicalGenKeepsTheRouteSocket(t *testing.T) { |
| 126 | rt := registry.Route{Host: "h.example.com", Socket: "/run/carlos/h.example.com.sock"} |
| 127 | g := canonicalGen(rt, 3, "v1") |
| 128 | if g.N != 3 || g.Socket != rt.Socket || g.Control != rt.Socket+".control" || g.Build != "v1" { |
| 129 | t.Fatalf("canonicalGen = %+v", g) |
| 130 | } |
| 131 | if g := canonicalGen(registry.Route{Host: "w"}, 4, ""); g.Control != "" { |
| 132 | t.Fatalf("a socketless route got control %q", g.Control) |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | func TestGenerationsSwitchDefaultsOff(t *testing.T) { |
| 137 | if GenerationsEnabled() { |
| 138 | t.Fatal("generations are on by default; CARLOS_SWAP unset must leave them off (spec §15)") |
| 139 | } |
| 140 | } |
| 141 | |