package activator import ( "math" "path/filepath" "strings" "testing" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" ) // withGenerations sets the dormant switch for one test and restores it after. // Every test that builds a Service with the switch on goes through here: the // switch is package state, so a test that forgot to restore it would turn // generations on for every test after it. func withGenerations(t *testing.T, on bool) { t.Helper() prev := GenerationsEnabled() SetGenerations(on) t.Cleanup(func() { SetGenerations(prev) }) } // Rev 19, rule 4: a nested generation's socket pair fits a unix socket // address for any host and any generation number, because the instance // identifier is bounded. A flat one fits for every number exactly when // flatGenFits says so, and a flat host that does not fit has no generation // path at all. func TestGenerationSocketsFitSockaddrUn(t *testing.T) { for _, host := range []string{ "h", "shop.example.com", strings.Repeat("a", 59-len(".example.com")) + ".example.com", // 59 bytes: the longest that fits strings.Repeat("b", 60-len(".example.com")) + ".example.com", // 60 bytes strings.Repeat("c", 113) + ".bab.io", // 120 bytes strings.Repeat("d", 253), } { fits := flatGenFits(host) if want := len(host) <= 59; fits != want { t.Errorf("flatGenFits(%d-byte host) = %v, want %v", len(host), fits, want) } for _, n := range []uint64{1, 42, math.MaxUint64} { nested := NestedGenSocket(host, n) paths := []string{nested, nested + ".control"} if fits { flat := GenSocketPath(host, n) paths = append(paths, flat, flat+".control") } for _, p := range paths { if len(p) > provision.MaxUnixPath { t.Errorf("%q is %d bytes, over the %d a unix socket address holds", p, len(p), provision.MaxUnixPath) } } } if !fits && len(GenSocketPath(host, math.MaxUint64)+".control") <= provision.MaxUnixPath { t.Errorf("%d-byte host refused, yet its longest generation control socket fits", len(host)) } } } // Rev 19, rule 1: a generation socket's file name is the flag-off one, in a // directory of the generation's own. func TestGenSocketPathShape(t *testing.T) { host := "shop.example.com" if got, want := GenSocketPath(host, 7), "/run/carlos/g/7/shop.example.com.sock"; got != want { t.Fatalf("GenSocketPath = %q, want %q", got, want) } if got, want := filepath.Base(GenSocketPath(host, 7)), filepath.Base(provision.DefaultSocket(host)); got != want { t.Fatalf("flat generation file name %q, want the flag-off %q", got, want) } if got, want := NestedGenSocket(host, 7), "/run/carlos/instances/"+provision.InstanceID(host)+"/g7/app.sock"; got != want { t.Fatalf("NestedGenSocket = %q, want %q", got, want) } if got, want := filepath.Base(NestedGenSocket(host, 7)), filepath.Base(provision.NestedSocket(host)); got != want { t.Fatalf("nested generation file name %q, want the flag-off %q", got, want) } for _, pair := range [][2]string{ {GenSocketPath(host, 7), GenSocketPath(host, 8)}, {GenSocketPath(host, 7), GenSocketPath("other.example.com", 7)}, {NestedGenSocket(host, 7), NestedGenSocket(host, 8)}, {NestedGenSocket(host, 7), provision.NestedSocket(host)}, } { if pair[0] == pair[1] { t.Fatalf("generation paths collide: %q", pair[0]) } } if got := filepath.Dir(NestedGenSocket(host, 7)); filepath.Dir(got) != provision.InstanceRunDir(host) { t.Fatalf("nested generation directory %q is not directly in the run dir %q", got, provision.InstanceRunDir(host)) } } // Rev 19, rule 4: genLayout reads only an over-long flat row as foreign. func TestGenLayoutReadsAnOverLongFlatRowAsForeign(t *testing.T) { short, long := "shop.example.com", strings.Repeat("a", 60-len(".example.com"))+".example.com" for _, tc := range []struct { l Layout host string want Layout }{ {LayoutFlat, short, LayoutFlat}, {LayoutFlat, long, LayoutForeign}, {LayoutNested, long, LayoutNested}, {LayoutForeign, short, LayoutForeign}, {LayoutFenced, long, LayoutFenced}, {LayoutConflict, long, LayoutConflict}, } { if got := genLayout(tc.l, tc.host); got != tc.want { t.Errorf("genLayout(%s, %d-byte host) = %s, want %s", tc.l, len(tc.host), got, tc.want) } } } func TestGenCounterIsProcessWideAndNeverRepeats(t *testing.T) { seen := map[uint64]bool{} var last uint64 for i := 0; i < 1000; i++ { n := nextGen() if n == 0 || seen[n] || n <= last { t.Fatalf("nextGen returned %d after %d (seen=%v); want strictly increasing, never 0", n, last, seen[n]) } seen[n], last = true, n } } func TestCanonicalGenKeepsTheRouteSocket(t *testing.T) { rt := registry.Route{Host: "h.example.com", Socket: "/run/carlos/h.example.com.sock"} g := canonicalGen(rt, 3, "v1") if g.N != 3 || g.Socket != rt.Socket || g.Control != rt.Socket+".control" || g.Build != "v1" { t.Fatalf("canonicalGen = %+v", g) } if g := canonicalGen(registry.Route{Host: "w"}, 4, ""); g.Control != "" { t.Fatalf("a socketless route got control %q", g.Control) } } func TestGenerationsSwitchDefaultsOff(t *testing.T) { if GenerationsEnabled() { t.Fatal("generations are on by default; CARLOS_SWAP unset must leave them off (spec ยง15)") } }