carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "os"
7 "testing"
8
9 "github.com/carlosframework/platform/internal/hostwrite"
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func TestPlan14HistoryUnavailableAfterCaptureCannotBlockRestart(t *testing.T) {
14 t.Parallel()
15 e, rt, u, c, pub := plan14AdoptionFixture(t)
16 h := seedHostRootHistory(t, rt)
17 deps := boundRootHistory(h, c.resolve, e.root.obs)
18 c.history = func() (*release.RootHistoryPass, error) { return nil, os.ErrPermission }
19 linkRootHistory(deps, c, u.opt, func(string) string { return "5s" })
20 prepare := deps.Prepare
21 prepared := false
22 deps.Prepare = func(ctx context.Context, b release.HistoryBinding, a release.HistoryAttempt, id hostwrite.Identity) error {
23 if err := prepare(ctx, b, a, id); err != nil {
24 return err
25 }
26 prepared = true
27 return nil
28 }
29 err := release.Pass(t.Context(), e.root.reg, e.store, pub, u.opt,
30 rootRestartRouteWith(e.root.reg, u, u.opt, func(string) string { return "5s" }, nil, c),
31 release.WithRootHistory(deps), release.WithRestartFailurePropagation())
32 if err != nil || !prepared || len(u.effects) != 1 || u.exe != release.VersionPath(u.opt, rt.Account, "v2", rt.App) {
33 t.Fatalf("history blocked captured restart: prepared=%v effects=%v error=%v", prepared, u.effects, err)
34 }
35}
36
37func TestPlan14HistoryUnavailableCannotBlockCapturedRollback(t *testing.T) {
38 t.Parallel()
39 for _, fault := range []string{"unavailable", "missing", "malformed", "write failure"} {
40 t.Run(fault, func(t *testing.T) {
41 e, rt, u, c, pub := plan14AdoptionFixture(t)
42 h := seedHostRootHistory(t, rt)
43 deps := boundRootHistory(h, c.resolve, e.root.obs)
44 c.history = func() (*release.RootHistoryPass, error) { return deps, nil }
45 linkRootHistory(deps, c, u.opt, func(string) string { return "5s" })
46 crash := errors.New("crash after restart")
47 u.crashNew = true
48 u.afterEffect = func() { panic(crash) }
49 func() {
50 defer func() {
51 if got := recover(); got != crash {
52 t.Fatalf("restart crash: %v", got)
53 }
54 }()
55 _ = release.Pass(t.Context(), e.root.reg, e.store, pub, u.opt,
56 rootRestartRouteWith(e.root.reg, u, u.opt, func(string) string { return "5s" }, nil, c), release.WithRootHistory(deps))
57 }()
58 u.afterEffect = nil
59 mem := h.Store.(*rollbackMemory)
60 switch fault {
61 case "unavailable":
62 c.history = func() (*release.RootHistoryPass, error) { return nil, os.ErrPermission }
63 case "missing":
64 mem.files = map[string][]byte{}
65 case "malformed":
66 for key := range mem.files {
67 mem.files[key] = []byte("{")
68 }
69 case "write failure":
70 mem.failWrite = true
71 }
72 before := len(u.effects)
73 err := recoverRootRestarts(t.Context(), u, u.opt, func(string) string { return "5s" }, c, hostwrite.Config{})
74 if len(u.effects) != before+2 || u.props.MainPID <= 0 || u.exe != release.VersionPath(u.opt, rt.Account, "v1", rt.App) {
75 t.Fatalf("history prevented authorized rollback: effects=%v error=%v", u.effects, err)
76 }
77 if err == nil {
78 t.Fatal("unavailable history was reported as settled")
79 }
80 before = len(u.effects)
81 _ = recoverRootRestarts(t.Context(), u, u.opt, func(string) string { return "5s" }, c, hostwrite.Config{})
82 if len(u.effects) != before {
83 t.Fatal("history retry repeated recovery effects")
84 }
85 })
86 }
87}
88