carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import (
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.
17func 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.
29func 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.
63func 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.
93func 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
113func 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
125func 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
136func 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