| index 51d40c418..3b3b1dc57 100644 |
| --- a/internal/release/programhistory_test.go |
| +++ b/internal/release/programhistory_test.go |
| @@ -149,6 +149,66 @@ func TestBoxAdoptsAConsentedChangeItMissedTheFirstReleaseOf(t *testing.T) { |
| } |
| } |
| |
| +// Ruling G3: an authorised B build is already staged, its manifest sibling is |
| +// missing, and the store cannot answer for the manifest. Whether it carries |
| +// --program-change is unknown, so the box neither points it live nor writes a |
| +// refusal; once the store recovers, the next pass reads the manifest and |
| +// adopts it, with the promotion unchanged. |
| +func TestBoxDefersAWrongProgramWhoseManifestItCannotRead(t *testing.T) { |
| + host := "console.example.com" |
| + base := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt, bake := t.TempDir(), t.TempDir() |
| + reg := testRegistry(t) |
| + a, b := tinyProgram(t, "example.com/console"), tinyProgram(t, "example.com/carlos") |
| + if err := shipWithProgram(t, base, "va", a, false); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := shipWithProgram(t, base, "vb", b, true); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, host, "console", "canary/x", false) |
| + restart := func(string) error { return nil } |
| + if err := Promote(ctx, base, priv, "bab", "console", "canary/x", "va"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := Pass(ctx, reg, base, pub, opt, restart, BakeDir(bake)); err != nil { |
| + t.Fatalf("Pass va: %v", err) |
| + } |
| + // vb's binary is staged with no manifest sibling (staged by a binary |
| + // older than the sibling, or a sibling write that failed). |
| + if err := StageArtifact(opt, "bab", "vb", "console", b); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := Promote(ctx, base, priv, "bab", "console", "canary/x", "vb"); err != nil { |
| + t.Fatal(err) |
| + } |
| + broken := &failingStore{Store: base, failKey: ManifestKey("bab", "console", "vb")} |
| + var out bytes.Buffer |
| + withCapturedStdout(t, &out, func() { |
| + if err := Pass(ctx, reg, broken, pub, opt, restart, BakeDir(bake)); err != nil { |
| + t.Fatalf("Pass with the manifest unreadable: %v", err) |
| + } |
| + }) |
| + if v, _ := LiveVersion(opt, host); v != "va" { |
| + t.Fatalf("live = %q, want va: an unknown override is never pointed live", v) |
| + } |
| + if !strings.Contains(out.String(), "could not be read to check for --program-change; not pointed live, will retry") { |
| + t.Fatalf("printed %q, want the will-retry line", out.String()) |
| + } |
| + if r, err := ReadRefusal(bake, host); err != nil || r != nil { |
| + t.Fatalf("refusal = %+v, %v; want none: an unreadable manifest is not a refusal", r, err) |
| + } |
| + // The store recovers; the same promotion is adopted. |
| + if err := Pass(ctx, reg, base, pub, opt, restart, BakeDir(bake)); err != nil { |
| + t.Fatalf("Pass after recovery: %v", err) |
| + } |
| + if v, _ := LiveVersion(opt, host); v != "vb" { |
| + t.Fatalf("live = %q, want vb once its manifest (program_change) is readable", v) |
| + } |
| +} |
| + |
| func TestBoxSeedsHistoryFromTheLiveBinary(t *testing.T) { |
| bake := t.TempDir() |
| if got := ReadPrograms(bake, "console.example.com"); len(got) != 0 { |