test: harden harness lifecycle recovery
This commit is contained in:
+57
-5
@@ -548,6 +548,7 @@ func (h *harness) environmentCommand(command string, args []string) error {
|
||||
flags := flag.NewFlagSet(command, flag.ContinueOnError)
|
||||
flags.SetOutput(io.Discard)
|
||||
runID := flags.String("run-id", "", "run ID")
|
||||
newRunID := flags.String("new-run-id", "", "fresh run ID for reuse")
|
||||
all := flags.Bool("all", false, "select every eligible run")
|
||||
execute := flags.Bool("execute", false, "perform a destructive cleanup")
|
||||
yes := flags.Bool("yes", false, "confirm destructive cleanup in noninteractive use")
|
||||
@@ -557,6 +558,9 @@ func (h *harness) environmentCommand(command string, args []string) error {
|
||||
if command != "purge" && (*all || *execute || *yes) {
|
||||
return fmt.Errorf("%s does not accept cleanup options", command)
|
||||
}
|
||||
if command != "reuse" && *newRunID != "" {
|
||||
return fmt.Errorf("%s does not accept --new-run-id", command)
|
||||
}
|
||||
if command == "purge" {
|
||||
if (*runID == "") == (!*all) {
|
||||
return errors.New("purge requires exactly one of --run-id or --all")
|
||||
@@ -590,24 +594,72 @@ func (h *harness) environmentCommand(command string, args []string) error {
|
||||
case "collect":
|
||||
return h.collect(current)
|
||||
case "recover":
|
||||
if err := h.verifyResume(current); err != nil {
|
||||
return err
|
||||
}
|
||||
if current.Phase != "interrupted" && current.Phase != "stopped" && current.Phase != "running" && current.Phase != "failed" {
|
||||
return fmt.Errorf("run in phase %q does not need recovery", current.Phase)
|
||||
}
|
||||
return h.transition(current, "ready", "recover", "run recovered and ready to resume")
|
||||
case "reuse":
|
||||
if current.Phase != "reset" && current.Phase != "completed" && current.Phase != "ready" {
|
||||
return fmt.Errorf("run in phase %q cannot be reused", current.Phase)
|
||||
}
|
||||
return h.transition(current, "ready", "reuse", "run retained for reuse")
|
||||
return h.reuse(current, *newRunID)
|
||||
case "stop":
|
||||
return h.transition(current, "stopped", "stop", "owned runtime resources stopped")
|
||||
case "reset":
|
||||
return h.transition(current, "reset", "reset", "owned runtime state reset; manifest and journal retained")
|
||||
return h.reset(current)
|
||||
default:
|
||||
return usageError()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *harness) reuse(previous *manifest, newRunID string) error {
|
||||
if previous.Phase != "reset" && previous.Phase != "completed" {
|
||||
return fmt.Errorf("run in phase %q cannot be reused; reset or complete it first", previous.Phase)
|
||||
}
|
||||
if err := h.verifyResume(previous); err != nil {
|
||||
return err
|
||||
}
|
||||
fresh, err := h.create(newRunID, previous.Layer, previous.Suite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fresh.TestCase = previous.TestCase
|
||||
if err := h.writeManifest(fresh); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.appendJournal(fresh.RunID, journalEntry{At: h.now(), Step: "reuse", Status: "passed", Detail: "fresh run created from immutable prior definitions", Data: map[string]any{"prior_run_id": previous.RunID}}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(h.out, "reused_from=%s new_run_id=%s\n", previous.RunID, fresh.RunID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *harness) reset(current *manifest) error {
|
||||
if current.Phase == "running" {
|
||||
return errors.New("refusing to reset a running run; stop it first")
|
||||
}
|
||||
if len(current.OwnedResources) != 0 {
|
||||
return errors.New("refusing to reset a run with owned runtime resources; stop and reconcile them first")
|
||||
}
|
||||
for _, name := range []string{"runtime", "pki", "scratch"} {
|
||||
path := filepath.Join(h.runDir(current.RunID), name)
|
||||
info, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return fmt.Errorf("refusing unsafe reset path %s", path)
|
||||
}
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return h.transition(current, "reset", "reset", "owned runtime scratch reset; manifest, journal, and reports retained")
|
||||
}
|
||||
|
||||
func (h *harness) purgeCommand(runID string, all, execute, yes bool) error {
|
||||
var candidates []*manifest
|
||||
if all {
|
||||
|
||||
Reference in New Issue
Block a user