package release import ( "context" "encoding/json" "os" "os/exec" "path/filepath" "strings" "testing" ) type generationRetirementCrashStore struct { generationListingStore phase string } func (s generationRetirementCrashStore) Write(name string, body []byte) error { if err := s.generationListingStore.Write(name, body); err != nil { return err } var r GenerationReservation if json.Unmarshal(body, &r) != nil { return os.ErrPermission } if s.phase == "closed" && r.Closed && r.Retirement == "" { os.Exit(74) } if s.phase == "retired" && r.Retirement != "" { os.Exit(74) } return nil } func TestPlan14GenerationRetirementProcessCrash(t *testing.T) { if dir := os.Getenv("PLAN14_GENERATION_RETIRE_CHILD"); dir != "" { raw, err := os.ReadFile(filepath.Join(dir, "expected")) if err != nil { t.Fatal(err) } var expected GenerationReservation if json.Unmarshal(raw, &expected) != nil { t.Fatal("bad expected record") } store := generationRetirementCrashStore{generationListingStore: generationListingStore{&healthyRetryStore{dir: filepath.Join(dir, "state")}}, phase: os.Getenv("PLAN14_GENERATION_RETIRE_PHASE")} g := &RootGenerations{Store: store} err = g.Retire(t.Context(), expected, func(context.Context, GenerationReservation) error { return nil }, func(context.Context, GenerationReservation) (string, error) { if store.phase == "verify" { os.Exit(74) } return strings.Repeat("d", 64), nil }) t.Fatalf("crash hook not reached: %v", err) } for _, phase := range []string{"closed", "verify", "retired"} { t.Run(phase, func(t *testing.T) { g, store, b := generationFixture(t) r, err := g.Reserve(b) if err != nil { t.Fatal(err) } dir := t.TempDir() if err := os.Rename(store.dir, filepath.Join(dir, "state")); err != nil { t.Fatal(err) } store.dir = filepath.Join(dir, "state") raw, _ := json.Marshal(r) if err := os.WriteFile(filepath.Join(dir, "expected"), raw, 0600); err != nil { t.Fatal(err) } cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14GenerationRetirementProcessCrash$", "-test.count=1") cmd.Dir = dir cmd.Env = append(os.Environ(), "PLAN14_GENERATION_RETIRE_CHILD="+dir, "PLAN14_GENERATION_RETIRE_PHASE="+phase) if output, err := cmd.CombinedOutput(); err == nil { t.Fatalf("no crash: %s", output) } else if code, ok := err.(*exec.ExitError); !ok || code.ExitCode() != 74 { t.Fatalf("wrong crash: %v %s", err, output) } got, err := g.Read(b) if err != nil { t.Fatal(err) } if !got.Closed || (got.Retirement != "") != (phase == "retired") { t.Fatalf("crash lost restriction: %+v", got) } if _, err := g.Reserve(b); err == nil { t.Fatal("crashed retirement revived launch") } if phase != "retired" { if err := g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { t.Fatal("closed authority reopened"); return nil }, func(context.Context, GenerationReservation) (string, error) { return "", os.ErrPermission }); err == nil { t.Fatal("recovery released pin without evidence") } } if err := g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { return nil }, func(context.Context, GenerationReservation) (string, error) { return strings.Repeat("d", 64), nil }); err != nil { t.Fatal(err) } }) } }