| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "os" |
| 8 | "os/exec" |
| 9 | "path/filepath" |
| 10 | "strings" |
| 11 | "testing" |
| 12 | |
| 13 | "github.com/carlosframework/platform/internal/release" |
| 14 | "github.com/carlosframework/platform/internal/rootparticipation" |
| 15 | ) |
| 16 | |
| 17 | func TestPlan14GenerationInventoryProcessCrash(t *testing.T) { |
| 18 | t.Parallel() |
| 19 | if dir := os.Getenv("PLAN14_E24_INVENTORY_CHILD"); dir != "" { |
| 20 | disk := &rollbackDisk{dir: dir} |
| 21 | ledger := &release.RootGenerations{Store: disk} |
| 22 | v := generationReconciler{read: func(context.Context) (generationInventory, error) { |
| 23 | return generationInventory{Census: rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}}, nil |
| 24 | }, kernel: func(context.Context) (string, string, error) { os.Exit(76); return "", "", nil }} |
| 25 | if _, err := ledger.Certify(t.Context(), []release.GenerationReservation{}, v.reconcile); err != nil { |
| 26 | t.Fatal(err) |
| 27 | } |
| 28 | t.Fatal("crash boundary not reached") |
| 29 | } |
| 30 | dir := t.TempDir() |
| 31 | disk := &rollbackDisk{dir: dir} |
| 32 | ledger := &release.RootGenerations{Store: disk} |
| 33 | evidence := release.GenerationEvidence{Box: "box", Enrollment: "en", Migration: 1, Boot: "boot", Installations: strings.Repeat("a", 64), Launches: strings.Repeat("b", 64), Kernel: strings.Repeat("c", 64)} |
| 34 | if _, err := ledger.Certify(t.Context(), []release.GenerationReservation{}, func(context.Context, []release.GenerationReservation) (release.GenerationEvidence, error) { |
| 35 | return evidence, nil |
| 36 | }); err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14GenerationInventoryProcessCrash$", "-test.count=1") |
| 40 | cmd.Dir = dir |
| 41 | cmd.Env = append(os.Environ(), "PLAN14_E24_INVENTORY_CHILD="+dir) |
| 42 | out, err := cmd.CombinedOutput() |
| 43 | var exit *exec.ExitError |
| 44 | if !errors.As(err, &exit) || exit.ExitCode() != 76 { |
| 45 | t.Fatalf("child: %v %s", err, out) |
| 46 | } |
| 47 | c, err := ledger.Completeness() |
| 48 | if err != nil || c.Complete { |
| 49 | t.Fatalf("stale certificate survived crash: %+v %v", c, err) |
| 50 | } |
| 51 | raw, err := os.ReadFile(filepath.Join(dir, "completeness.json")) |
| 52 | if err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | var saved release.GenerationCompleteness |
| 56 | if json.Unmarshal(raw, &saved) != nil || saved.Complete { |
| 57 | t.Fatal("durable invalidation missing") |
| 58 | } |
| 59 | if _, err := ledger.Certify(t.Context(), []release.GenerationReservation{}, func(context.Context, []release.GenerationReservation) (release.GenerationEvidence, error) { |
| 60 | return evidence, nil |
| 61 | }); err != nil { |
| 62 | t.Fatal(err) |
| 63 | } |
| 64 | } |
| 65 | |