package host import ( "context" "errors" "os" "sync" "testing" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/release" ) func TestPlan14RollbackCrashResume(t *testing.T) { t.Parallel() for _, boundary := range []string{"forward restart", "rollback pending", "rollback running", "operator stopped", "changed link"} { t.Run(boundary, func(t *testing.T) { e, rt, u, c, pub := plan14AdoptionFixture(t) u.crashNew = true forward := rootRestartRouteWith(e.root.reg, u, u.opt, func(string) string { return "5s" }, nil, c) crashed := errors.New("root process exited") func() { defer func() { if got := recover(); got != crashed { t.Fatalf("crash boundary: %v", got) } }() _ = release.Pass(t.Context(), e.root.reg, e.store, pub, u.opt, func(host string) error { if err := forward(host); err == nil { t.Fatal("failed update succeeded") } panic(crashed) }, release.BakeDir(e.bake), release.WithRestartFailurePropagation()) }() s, err := c.attempts() if err != nil { t.Fatal(err) } a, err := readRollbackAttempt(s, rt.Host) if err != nil { t.Fatal(err) } if boundary == "rollback pending" || boundary == "rollback running" { a.Recovering = true if err := writeRollbackState(s, rollbackJournalKey(rt.Host), a); err != nil { t.Fatal(err) } if err := release.PointLive(u.opt, rt.Host, rt.Account, "v1", rt.App); err != nil { t.Fatal(err) } if boundary == "rollback running" { if err := u.Restart(rt.Unit); err != nil { t.Fatal(err) } } } if boundary == "operator stopped" { u.props.ActiveState, u.props.SubState = "inactive", "dead" } if boundary == "changed link" { if err := os.Remove(release.LiveLink(u.opt, rt.Host)); err != nil { t.Fatal(err) } } before := len(u.effects) c.started = &sync.Map{} err = recoverRootRestarts(context.Background(), u, u.opt, func(string) string { return "5s" }, c, hostwrite.Config{}) if boundary == "operator stopped" || boundary == "changed link" { if err == nil || len(u.effects) != before { t.Fatalf("unsafe resume: %v, %v", err, u.effects) } return } if err != nil { t.Fatal(err) } if u.exe != release.VersionPath(u.opt, rt.Account, "v1", rt.App) || u.props.MainPID <= 0 { t.Fatalf("resume did not restore the old process: %+v", u) } if boundary == "rollback running" && len(u.effects) != before { t.Fatal("resumed verification restarted an already recovered unit") } a, err = readRollbackAttempt(s, rt.Host) if err != nil || !a.Settled { t.Fatalf("resume did not settle: %+v, %v", a, err) } if _, err := s.Read(rollbackCapabilityKey(rt.Host, a.ID)); !errors.Is(err, os.ErrNotExist) { t.Fatal("resume retained capability") } }) } }