package release import ( "bytes" "context" "encoding/json" "errors" "fmt" "os" "strings" "testing" ) // putRawRelease writes a release the way a NEWER shipper would: the artifact, // then a manifest whose JSON this binary did not marshal. extra is spliced in // before "artifacts" and must end with a comma when non-empty, for example // `"swap":"hold",`. Returns the manifest bytes exactly as stored. func putRawRelease(t *testing.T, s Store, acct, app, ver string, body []byte, extra string) []byte { t.Helper() ctx := context.Background() if _, err := s.Put(ctx, ArtifactKey(acct, app, ver, adoptPlatform), body, ""); err != nil { t.Fatalf("put artifact: %v", err) } raw := []byte(fmt.Sprintf(`{"schema":1,"app":%q,"version":%q,"kind":"binary",%s"artifacts":{%q:{"file":%q,"sha256":%q,"size":%d}}}`, app, ver, extra, adoptPlatform, adoptPlatform, etagOf(body), len(body))) if _, err := s.Put(ctx, ManifestKey(acct, app, ver), raw, ""); err != nil { t.Fatalf("put manifest: %v", err) } return raw } func TestManifestSwapIsAdditive(t *testing.T) { old := []byte(`{"schema":1,"app":"km","version":"de2766f8","kind":"binary","artifacts":{"linux-arm64":{"file":"linux-arm64","sha256":"` + strings.Repeat("a", 64) + `","size":1}}}`) var m Manifest if err := json.Unmarshal(old, &m); err != nil { t.Fatalf("a manifest written before swap existed no longer parses: %v", err) } if m.Swap != "" { t.Fatalf("absent swap decoded as %q", m.Swap) } out, err := json.MarshalIndent(m, "", " ") if err != nil { t.Fatal(err) } // Byte-identical marshal is what keeps Ship's idempotent re-ship of an // old release working. if bytes.Contains(out, []byte(`"swap"`)) { t.Fatalf("an empty swap leaked into the marshal:\n%s", out) } } func TestShipRecordsAHoldDemand(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() m := Manifest{App: "km", Version: "v1", Kind: "binary", Swap: DemandHold, Artifacts: map[string]Artifact{adoptPlatform: {}}} if err := Ship(ctx, s, "bab", m, map[string][]byte{adoptPlatform: []byte("bin")}); err != nil { t.Fatalf("Ship: %v", err) } got, err := GetManifest(ctx, s, "bab", "km", "v1") if err != nil || got.Swap != DemandHold { t.Fatalf("GetManifest = %+v, %v; want swap %q", got, err, DemandHold) } } func TestShipRefusesAnUnknownSwapDemand(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() m := Manifest{App: "km", Version: "v1", Kind: "binary", Swap: "drain", Artifacts: map[string]Artifact{adoptPlatform: {}}} err := Ship(ctx, s, "bab", m, map[string][]byte{adoptPlatform: []byte("bin")}) if err == nil || !strings.Contains(err.Error(), `"drain"`) { t.Fatalf("Ship with swap drain = %v; want a refusal naming the value", err) } if _, _, gerr := s.Get(ctx, ManifestKey("bab", "km", "v1")); gerr == nil { t.Fatal("a refused ship still wrote its manifest") } if _, _, gerr := s.Get(ctx, ArtifactKey("bab", "km", "v1", adoptPlatform)); gerr == nil { t.Fatal("a refused ship still wrote its artifact") } } // Review Focus 2: a newer shipper's value must not stop a box reading the // manifest, and must act as hold. func TestReadsAcceptAnUnknownSwapDemand(t *testing.T) { s := NewDirStore(t.TempDir()) putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), `"swap":"drain",`) m, err := GetManifest(context.Background(), s, "bab", "km", "v1") if err != nil { t.Fatalf("a manifest from a newer shipper must still read: %v", err) } if got := NormalizeDemand(m.Swap); got != DemandHold { t.Fatalf("NormalizeDemand(%q) = %q; an unknown demand must act as hold", m.Swap, got) } } func TestNormalizeDemand(t *testing.T) { for in, want := range map[string]string{"": "", "hold": "hold", "overlap": "hold", "HOLD": "hold", "drain": "hold"} { if got := NormalizeDemand(in); got != want { t.Errorf("NormalizeDemand(%q) = %q, want %q", in, got, want) } } } // unreadableStore answers every Get with an error that is not "not found": a // bucket this box cannot read right now. type unreadableStore struct{ Store } func (unreadableStore) Get(context.Context, string) ([]byte, string, error) { return nil, "", errors.New("bucket unreachable") } // Spec §12 "Swap mode source": a staged copy without swap, from an older // root, cannot produce an overlap for a hold build. func TestCanonicalDemandReadsTheCanonicalManifestNotTheSibling(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() opt := t.TempDir() putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), `"swap":"hold",`) if err := StageArtifact(opt, "bab", "v1", "km", []byte("bin")); err != nil { t.Fatal(err) } canon, err := GetManifest(ctx, s, "bab", "km", "v1") if err != nil { t.Fatal(err) } lossy := *canon lossy.Swap = "" // what an older root's re-marshal wrote b, err := json.MarshalIndent(lossy, "", " ") if err != nil { t.Fatal(err) } if err := StageManifest(opt, "bab", "v1", "km", b); err != nil { t.Fatal(err) } demand, digest, err := CanonicalDemand(ctx, s, "bab", "km", "v1", adoptPlatform, VersionPath(opt, "bab", "v1", "km")) if err != nil || demand != DemandHold || digest != etagOf([]byte("bin")) { t.Fatalf("CanonicalDemand = %q, %q, %v; want hold from the canonical manifest", demand, digest, err) } } func TestCanonicalDemandWithNoDemand(t *testing.T) { s := NewDirStore(t.TempDir()) opt := t.TempDir() putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), "") if err := StageArtifact(opt, "bab", "v1", "km", []byte("bin")); err != nil { t.Fatal(err) } demand, digest, err := CanonicalDemand(context.Background(), s, "bab", "km", "v1", adoptPlatform, VersionPath(opt, "bab", "v1", "km")) if err != nil || demand != "" || digest != etagOf([]byte("bin")) { t.Fatalf("CanonicalDemand = %q, %q, %v; want no demand", demand, digest, err) } } // Spec §12 "Artifact binding": a re-shipped name with different bytes, plus // an old staged copy, is a staging error. func TestCanonicalDemandRefusesBytesTheReleaseDoesNotName(t *testing.T) { s := NewDirStore(t.TempDir()) opt := t.TempDir() putRawRelease(t, s, "bab", "km", "v1", []byte("new bytes"), "") if err := StageArtifact(opt, "bab", "v1", "km", []byte("old bytes")); err != nil { t.Fatal(err) } demand, digest, err := CanonicalDemand(context.Background(), s, "bab", "km", "v1", adoptPlatform, VersionPath(opt, "bab", "v1", "km")) if !errors.Is(err, ErrStagedDigestMismatch) { t.Fatalf("err = %v, want ErrStagedDigestMismatch", err) } if demand != "" || digest != etagOf([]byte("old bytes")) { t.Fatalf("CanonicalDemand = %q, %q; want no demand and the staged file's own digest", demand, digest) } } // Spec §4.2: if the canonical manifest cannot be read, or the digests cannot // be compared, the attempt runs in hold. Review Focus 2 is the last case. func TestCanonicalDemandIsHoldWhenItCannotCompare(t *testing.T) { ctx := context.Background() staged := func(t *testing.T, opt string) string { if err := StageArtifact(opt, "bab", "v1", "km", []byte("bin")); err != nil { t.Fatal(err) } return VersionPath(opt, "bab", "v1", "km") } t.Run("manifest missing", func(t *testing.T) { opt := t.TempDir() d, sum, err := CanonicalDemand(ctx, NewDirStore(t.TempDir()), "bab", "km", "v1", adoptPlatform, staged(t, opt)) if err != nil || d != DemandHold || sum != etagOf([]byte("bin")) { t.Fatalf("= %q, %q, %v; want hold", d, sum, err) } }) t.Run("store unreadable", func(t *testing.T) { opt := t.TempDir() s := NewDirStore(t.TempDir()) putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), "") d, _, err := CanonicalDemand(ctx, unreadableStore{s}, "bab", "km", "v1", adoptPlatform, staged(t, opt)) if err != nil || d != DemandHold { t.Fatalf("= %q, %v; want hold", d, err) } }) t.Run("no build for this platform", func(t *testing.T) { opt := t.TempDir() s := NewDirStore(t.TempDir()) putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), "") d, _, err := CanonicalDemand(ctx, s, "bab", "km", "v1", "plan9-mips", staged(t, opt)) if err != nil || d != DemandHold { t.Fatalf("= %q, %v; want hold", d, err) } }) t.Run("staged file missing", func(t *testing.T) { s := NewDirStore(t.TempDir()) putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), "") missing := VersionPath(t.TempDir(), "bab", "v1", "km") if _, err := os.Stat(missing); err == nil { t.Fatal("setup: the path exists") } d, sum, err := CanonicalDemand(ctx, s, "bab", "km", "v1", adoptPlatform, missing) if err != nil || d != DemandHold || sum != "" { t.Fatalf("= %q, %q, %v; want hold and no digest", d, sum, err) } }) t.Run("unknown demand", func(t *testing.T) { opt := t.TempDir() s := NewDirStore(t.TempDir()) putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), `"swap":"drain",`) d, _, err := CanonicalDemand(ctx, s, "bab", "km", "v1", adoptPlatform, staged(t, opt)) if err != nil || d != DemandHold { t.Fatalf("= %q, %v; want hold", d, err) } }) }