carlos / platform Public

genselect: the model's selection witnesses and refusal cases as table tests (claim Plan 7b Task 1)

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codex agent, operated by paul@keymail.dev pushed by claude agent, operated by paul@keymail.dev be741e8c7c8f0f490294f8c135ef0f8cfbac4367 parent bfd82e1
1 file changed, +561 −0
  • internal/genselect/select_test.go +561 −0
diff --git a/internal/genselect/select_test.go b/internal/genselect/select_test.go
new file mode 100644
index 000000000..3552a892c
--- /dev/null
+++ b/internal/genselect/select_test.go
@@ -0,0 +1,561 @@
+package genselect
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/lease/leasetest"
+ "github.com/superfly/ltx"
+)
+
+type fixture struct {
+ t *testing.T
+ b *leasetest.VersionedBucket
+ req Request
+ marker Marker
+ markerVersion string
+ pin File
+ store Reader
+}
+
+func newFixture(t *testing.T) *fixture {
+ b := leasetest.NewVersionedBucket()
+ b.AddBucket("data")
+ b.PageSize = 1
+ f := &fixture{t: t, b: b, store: b, req: Request{Bucket: "data", Region: "ie", Host: "h",
+ Holder: hostclaim.Holder{Incarnation: "inc", Location: "instances/h/inc", Generation: "g0"}}}
+ f.pin = f.file("g0", 9, 1, 11)
+ f.file("g0", 0, 11, 11)
+ f.marker = Marker{Op: "g0", Incarnation: "inc", Host: "h", Box: "box", InstalledAt: time.Unix(1, 0).UTC(),
+ ManifestSHA256: strings.Repeat("a", 64), Pin: Pin{SnapshotKey: f.pin.Key, SnapshotVersion: f.pin.VersionID, SnapshotTXID: 11}}
+ f.publish()
+ return f
+}
+
+func header(t *testing.T, min, max uint64) []byte {
+ t.Helper()
+ h := ltx.Header{Version: ltx.Version, Flags: ltx.HeaderFlagNoChecksum, PageSize: 4096,
+ MinTXID: ltx.TXID(min), MaxTXID: ltx.TXID(max)}
+ b, err := h.MarshalBinary()
+ if err != nil {
+ t.Fatal(err)
+ }
+ return b
+}
+
+func (f *fixture) file(gen string, level int, min, max uint64) File {
+ f.t.Helper()
+ key := fmt.Sprintf("%s%04x/%016x-%016x.ltx", prefix(strings.TrimSuffix(f.req.Holder.Location, "/")+"/", origin(gen)), level, min, max)
+ b := header(f.t, min, max)
+ id := f.b.Put("data", key, b)
+ return File{Key: key, VersionID: id, Level: level, MinTXID: min, MaxTXID: max, Size: int64(len(b))}
+}
+
+func (f *fixture) publish() {
+ f.t.Helper()
+ b, err := json.Marshal(f.marker)
+ if err != nil {
+ f.t.Fatal(err)
+ }
+ f.markerVersion = f.b.Put("data", f.req.Holder.Location+"/generation.json", b)
+}
+
+func (f *fixture) chain(base string) {
+ f.req.Holder.Generation = "g2"
+ f.req.Holder.Admissions = []hostclaim.AdmissionRecord{
+ {Generation: "g1", Predecessor: base, Region: "ie", PredecessorRegion: "ie", Mode: "cold"},
+ {Generation: "g2", Predecessor: "g1", Region: "ie", PredecessorRegion: "ie", Mode: "cold"},
+ }
+}
+
+func (f *fixture) origin() {
+ f.req.Holder.Generation = ""
+ if err := f.b.DeleteVersion(f.t.Context(), "data", f.req.Holder.Location+"/generation.json", f.markerVersion); err != nil {
+ f.t.Fatal(err)
+ }
+ f.markerVersion = ""
+}
+
+// These are pure-selection ports, not simulations of wake, park or replication.
+// Paths refer to platform-moves-design/internal/sim/hostlife at rev 7.13.
+func TestModelWitnesses(t *testing.T) {
+ tests := []struct {
+ name, source string
+ setup func(*fixture) []File
+ gen string
+ wantErr error
+ }{
+ {"wRestoreOverlap", "witness_test.go:1204", func(f *fixture) []File {
+ return []File{f.pin, f.file("g0", 1, 10, 13)}
+ }, "g0", nil},
+ {"wRestoreGap", "witness_test.go:1233", func(f *fixture) []File {
+ f.file("g0", 0, 13, 13)
+ return nil
+ }, "", ErrGap},
+ {"wRootAdvance", "witness_test.go:1295", func(f *fixture) []File {
+ f.file("g0", 0, 13, 13)
+ root := f.file("g0", 9, 1, 20)
+ f.b.BareDelete("data", f.pin.Key)
+ return []File{root, f.file("g0", 0, 21, 21)}
+ }, "g0", nil},
+ {"wOriginSwept/origin", "witness_test.go:2373", func(f *fixture) []File {
+ f.origin()
+ return []File{f.file("", 0, 1, 1), f.file("", 1, 2, 5)}
+ }, hostclaim.RootGeneration, nil},
+ {"wOriginSwept/unfinished_chain", "witness_test.go:2373; protocol_test.go:1188", func(f *fixture) []File {
+ f.origin()
+ f.chain(hostclaim.RootGeneration)
+ return []File{f.file("", 0, 1, 3)}
+ }, hostclaim.RootGeneration, nil},
+ {"wBaselineParked/all_empty", "witness_test.go:2168; protocol_test.go:1154", func(f *fixture) []File {
+ f.chain("g0")
+ return []File{f.pin}
+ }, "g0", nil},
+ {"wBaselineParked/newest_nonempty", "witness_test.go:2168; protocol_test.go:1172", func(f *fixture) []File {
+ f.chain("g0")
+ return []File{f.file("g1", 0, 1, 1)}
+ }, "g1", nil},
+ {"wBaselineParked/holder_nonempty", "protocol_test.go:1161", func(f *fixture) []File {
+ f.chain("g0")
+ f.file("g1", 0, 1, 9)
+ return []File{f.file("g2", 0, 1, 1)}
+ }, "g2", nil},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Log("port of", tt.source)
+ f := newFixture(t)
+ want := tt.setup(f)
+ before := f.b.Snapshot()
+ f.b.FailOn = func(op, key string) error {
+ if op != "list" && op != "get" {
+ t.Fatalf("mutation %s %s", op, key)
+ }
+ return nil
+ }
+ got, err := Select(t.Context(), f.store, f.req)
+ if !errors.Is(err, tt.wantErr) {
+ t.Fatalf("got %v, want %v", err, tt.wantErr)
+ }
+ if f.b.Snapshot() != before {
+ t.Fatal("selection mutated storage")
+ }
+ if tt.wantErr != nil {
+ if got != nil {
+ t.Fatal("partial plan")
+ }
+ return
+ }
+ if got.Generation != tt.gen || !reflect.DeepEqual(got.Files, want) {
+ t.Fatalf("got %+v, want %s %+v", got, tt.gen, want)
+ }
+ if got.MarkerVersion != f.markerVersion || got.Root == nil || *got.Root != want[0] || got.MaxTXID != want[len(want)-1].MaxTXID {
+ t.Fatalf("root, marker or final TXID: %+v", got)
+ }
+ })
+ }
+}
+
+type altered struct {
+ Reader
+ list func([]lease.ObjectVersion) ([]lease.ObjectVersion, error)
+ open func(context.Context, string, string, string) (io.ReadCloser, error)
+}
+
+func (s altered) ListVersions(ctx context.Context, bucket, base string) ([]lease.ObjectVersion, error) {
+ vs, err := s.Reader.ListVersions(ctx, bucket, base)
+ if err != nil {
+ return nil, err
+ }
+ if s.list != nil {
+ return s.list(vs)
+ }
+ return vs, nil
+}
+func (s altered) OpenVersion(ctx context.Context, bucket, key, id string) (io.ReadCloser, error) {
+ if s.open != nil {
+ return s.open(ctx, bucket, key, id)
+ }
+ return s.Reader.OpenVersion(ctx, bucket, key, id)
+}
+
+func TestRefusals(t *testing.T) {
+ tests := []struct {
+ name string
+ setup func(*fixture)
+ want error
+ }{
+ {"marker_holder_disagreement", func(f *fixture) { f.req.Holder.Generation = "unknown" }, ErrDisagreement},
+ {"nonempty_unexplained_disagreement", func(f *fixture) { f.req.Holder.Generation = "unknown"; f.file("unknown", 0, 1, 1) }, ErrDisagreement},
+ {"covered_chain", func(f *fixture) { f.chain("g0"); f.req.Holder.Admissions[0].Covered = true }, ErrDisagreement},
+ {"broken_chain", func(f *fixture) { f.chain("g0"); f.req.Holder.Admissions[1].Predecessor = "missing" }, ErrDisagreement},
+ {"cyclic_chain", func(f *fixture) { f.chain("g0"); f.req.Holder.Admissions[0].Predecessor = "g2" }, ErrDisagreement},
+ {"foreign_baseline", func(f *fixture) { f.chain("g0"); f.req.Holder.Admissions[0].PredecessorRegion = "us" }, ErrRegion},
+ {"incomplete_listing", func(f *fixture) { f.b.FailListAfterPages = 1 }, ErrListing},
+ {"partial_data_with_error", func(f *fixture) {
+ f.store = altered{Reader: f.b, list: func(vs []lease.ObjectVersion) ([]lease.ObjectVersion, error) { return vs, io.ErrUnexpectedEOF }}
+ }, ErrListing},
+ {"incomplete_unfinished_listing", func(f *fixture) { f.chain("g0"); f.b.FailListAfterPages = 1 }, ErrListing},
+ {"pinned_version_missing", func(f *fixture) {
+ f.b.Put("data", f.pin.Key, header(f.t, 1, 11))
+ if err := f.b.DeleteVersion(f.t.Context(), "data", f.pin.Key, f.pin.VersionID); err != nil {
+ f.t.Fatal(err)
+ }
+ }, ErrPinnedMissing},
+ {"pinned_version_vanished_after_listing", func(f *fixture) {
+ f.store = altered{Reader: f.b, open: func(context.Context, string, string, string) (io.ReadCloser, error) {
+ return nil, lease.ErrMirrorNotFound
+ }}
+ }, ErrPinnedMissing},
+ {"pinned_header_mismatch", func(f *fixture) {
+ f.marker.SnapshotVersion = f.b.Put("data", f.pin.Key, header(f.t, 1, 12))
+ f.publish()
+ }, ErrPinnedMismatch},
+ {"pinned_key_mismatch", func(f *fixture) { f.marker.SnapshotTXID = 12; f.publish() }, ErrPinnedMismatch},
+ {"pinned_delete_marker", func(f *fixture) { f.marker.SnapshotVersion = f.b.BareDelete("data", f.pin.Key); f.publish() }, ErrDeleteMarker},
+ {"selected_delete_marker_on_open", func(f *fixture) {
+ f.store = altered{Reader: f.b, open: func(context.Context, string, string, string) (io.ReadCloser, error) {
+ return nil, lease.ErrIsDeleteMarker
+ }}
+ }, ErrDeleteMarker},
+ {"marker_delete_marker", func(f *fixture) { f.b.BareDelete("data", f.req.Holder.Location+"/generation.json") }, ErrDeleteMarker},
+ {"marker_read_error", func(f *fixture) {
+ f.b.FailOn = func(op, key string) error {
+ if op == "get" {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+ }
+ }, ErrMarker},
+ {"marker_disappears", func(f *fixture) {
+ f.b.FailOn = func(op, key string) error {
+ if op == "get" {
+ return lease.ErrMirrorNotFound
+ }
+ return nil
+ }
+ }, ErrMarker},
+ {"marker_incarnation", func(f *fixture) { f.marker.Incarnation = "previous"; f.publish() }, ErrIncarnation},
+ {"marker_host", func(f *fixture) { f.marker.Host = "other"; f.publish() }, ErrMarker},
+ {"malformed_marker", func(f *fixture) { f.b.Put("data", f.req.Holder.Location+"/generation.json", []byte("{")) }, ErrMarker},
+ {"trailing_marker_json", func(f *fixture) {
+ b, _ := json.Marshal(f.marker)
+ f.b.Put("data", f.req.Holder.Location+"/generation.json", append(b, []byte(" {}")...))
+ }, ErrMarker},
+ {"empty_pin_version", func(f *fixture) { f.marker.SnapshotVersion = ""; f.publish() }, ErrVersionID},
+ {"null_pin_version", func(f *fixture) { f.marker.SnapshotVersion = "null"; f.publish() }, ErrVersionID},
+ {"empty_current_version", func(f *fixture) { alterVersion(f, "") }, ErrVersionID},
+ {"null_current_version", func(f *fixture) { alterVersion(f, "null") }, ErrVersionID},
+ {"tombstoned_unfinished_is_not_empty", func(f *fixture) {
+ f.chain("g0")
+ dead := f.file("g2", 0, 1, 1)
+ f.b.BareDelete("data", dead.Key)
+ }, ErrEmpty},
+ {"metadata_only_unfinished", func(f *fixture) {
+ f.chain("g0")
+ f.b.Put("data", f.req.Holder.Location+"/g-g2/build-intent.json", []byte("{}"))
+ }, ErrEmpty},
+ {"empty_published_generation", func(f *fixture) {
+ f.marker.Op = "empty"
+ f.marker.Pin = Pin{}
+ f.req.Holder.Generation = "empty"
+ f.publish()
+ }, ErrEmpty},
+ {"bad_successor_no_fallback", func(f *fixture) { r := f.file("g0", 9, 1, 20); f.b.Put("data", r.Key, header(f.t, 1, 19)) }, ErrRoot},
+ {"missing_snapshot_txid", func(f *fixture) {
+ b, _ := json.Marshal(f.marker)
+ var fields map[string]any
+ json.Unmarshal(b, &fields)
+ delete(fields, "snapshot_txid")
+ b, _ = json.Marshal(fields)
+ f.b.Put("data", f.req.Holder.Location+"/generation.json", b)
+ }, ErrMarker},
+ {"malformed_current_ltx", func(f *fixture) { f.b.Put("data", f.req.Holder.Location+"/g-g0/0000/not-a-range.ltx", []byte("bad")) }, ErrListing},
+ {"invalid_snapshot_min", func(f *fixture) { f.file("g0", 9, 2, 20) }, ErrRoot},
+ {"gap_without_root", func(f *fixture) { f.origin(); f.file("", 0, 2, 2) }, ErrGap},
+ {"location_escape", func(f *fixture) { f.req.Holder.Location = "instances/h/../other" }, ErrInput},
+ {"holder_root_sentinel", func(f *fixture) { f.req.Holder.Generation = hostclaim.RootGeneration }, ErrInput},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ f := newFixture(t)
+ tt.setup(f)
+ before := f.b.Snapshot()
+ got, err := Select(t.Context(), f.store, f.req)
+ if got != nil || !errors.Is(err, tt.want) {
+ t.Fatalf("got %+v / %v, want %v", got, err, tt.want)
+ }
+ if f.b.Snapshot() != before {
+ t.Fatal("mutated storage")
+ }
+ })
+ }
+}
+
+func alterVersion(f *fixture, id string) {
+ f.file("g0", 0, 12, 12)
+ f.store = altered{Reader: f.b, list: func(vs []lease.ObjectVersion) ([]lease.ObjectVersion, error) {
+ for i := range vs {
+ if strings.Contains(vs[i].Key, "0000/000000000000000c-") {
+ vs[i].VersionID = id
+ }
+ }
+ return vs, nil
+ }}
+}
+
+func TestSelectionDetails(t *testing.T) {
+ tests := []struct {
+ name string
+ setup func(*fixture) []File
+ }{
+ {"pinned_noncurrent_duplicate", func(f *fixture) []File {
+ f.b.Put("data", f.pin.Key, header(f.t, 1, 11))
+ return []File{f.pin}
+ }},
+ {"pinned_under_retention_tombstone", func(f *fixture) []File {
+ f.b.BareDelete("data", f.pin.Key)
+ return []File{f.pin}
+ }},
+ {"retention_tombstones_ignored", func(f *fixture) []File {
+ f.b.BareDelete("data", f.file("g0", 0, 13, 13).Key)
+ return []File{f.pin}
+ }},
+ {"max_then_min_then_level", func(f *fixture) []File {
+ f.file("g0", 8, 12, 19)
+ f.file("g0", 8, 12, 20)
+ f.file("g0", 1, 10, 20)
+ return []File{f.pin, f.file("g0", 2, 10, 20)}
+ }},
+ {"origin_snapshot_and_isolation", func(f *fixture) []File {
+ f.origin()
+ f.file("other", 9, 1, 999)
+ f.b.Put("data", f.req.Holder.Location+"/child/0009/0000000000000001-000000000000ffff.ltx", header(f.t, 1, 65535))
+ return []File{f.file("", 9, 1, 20), f.file("", 0, 21, 21)}
+ }},
+ {"legacy_location", func(f *fixture) []File {
+ f.req.Holder.Location = "instances/h"
+ f.req.Holder.Generation = ""
+ return []File{f.file("", 0, 1, 1)}
+ }},
+ {"fresh_zero_pin_marker", func(f *fixture) []File {
+ f.marker.Op = "fresh"
+ f.marker.Pin = Pin{}
+ f.req.Holder.Generation = "fresh"
+ f.publish()
+ return []File{f.file("fresh", 0, 1, 1)}
+ }},
+ {"warm_record_in_unfinished_run", func(f *fixture) []File {
+ f.chain("g0")
+ f.req.Holder.Admissions = append(f.req.Holder.Admissions, hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g2", Region: "ie", PredecessorRegion: "ie", Mode: "warm"})
+ return []File{f.pin}
+ }},
+ {"root_advance_past_missing_old_pin", func(f *fixture) []File {
+ if err := f.b.DeleteVersion(f.t.Context(), "data", f.pin.Key, f.pin.VersionID); err != nil {
+ f.t.Fatal(err)
+ }
+ return []File{f.file("g0", 9, 1, 30)}
+ }},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ f := newFixture(t)
+ want := tt.setup(f)
+ got, err := Select(t.Context(), f.store, f.req)
+ if err != nil || !reflect.DeepEqual(got.Files, want) {
+ t.Fatalf("got %+v / %v, want %+v", got, err, want)
+ }
+ })
+ }
+}
+
+// Port of resumeOpen's prepared branch (protocol_test.go:658) and
+// restorePinned/restoreWith (:183/:187), moves §5.1 / §7.14.
+func TestPreparedRestore(t *testing.T) {
+ tests := []struct {
+ name string
+ setup func(*fixture, *PreparedPin, File) []File
+ want error
+ ordinaryRefuses bool
+ }{
+ {"absent_target_marker_covered_source", func(f *fixture, p *PreparedPin, root File) []File {
+ f.origin()
+ f.req.Holder.Generation = "g2"
+ return []File{root, f.file("g2", 1, 29, 33)}
+ }, nil, true},
+ {"absent_target_marker_trimmed_source", func(f *fixture, p *PreparedPin, root File) []File {
+ f.origin()
+ f.req.Holder.Generation = "g2"
+ f.req.Holder.Admissions = f.req.Holder.Admissions[1:]
+ return []File{root}
+ }, nil, true},
+ {"unpublished_historical_pin", func(f *fixture, p *PreparedPin, root File) []File {
+ f.b.Put("data", root.Key, header(f.t, 1, 30))
+ return []File{root}
+ }, nil, false},
+ {"marker_pin_disagreement_ignored", func(f *fixture, p *PreparedPin, root File) []File {
+ f.marker.Op = "g2" // Its old g0 pin cannot override the authorized pin.
+ f.publish()
+ return []File{root}
+ }, nil, false},
+ {"malformed_marker_ignored", func(f *fixture, p *PreparedPin, root File) []File {
+ f.b.Put("data", f.req.Holder.Location+"/generation.json", []byte("{"))
+ return []File{root}
+ }, nil, false},
+ {"marker_tombstone_ignored", func(f *fixture, p *PreparedPin, root File) []File {
+ f.b.BareDelete("data", f.req.Holder.Location+"/generation.json")
+ return []File{root}
+ }, nil, false},
+ {"missing_prepared_root_no_current_key_fallback", func(f *fixture, p *PreparedPin, root File) []File {
+ f.b.Put("data", root.Key, header(f.t, 1, 30))
+ deletePreparedRoot(f, root)
+ return nil
+ }, ErrPinnedMissing, false},
+ {"empty_prepared_generation_never_older_baseline", func(f *fixture, p *PreparedPin, root File) []File {
+ f.chain("g0")
+ deletePreparedRoot(f, root)
+ versions, err := f.b.ListVersions(f.t.Context(), "data", prefix(f.req.Holder.Location+"/", "g2"))
+ if err != nil {
+ f.t.Fatal(err)
+ }
+ for _, v := range versions {
+ if err := f.b.DeleteVersion(f.t.Context(), "data", v.Key, v.VersionID); err != nil {
+ f.t.Fatal(err)
+ }
+ }
+ baseline, err := Select(f.t.Context(), f.b, f.req)
+ if err != nil || baseline.Generation != "g0" {
+ f.t.Fatalf("ordinary baseline: %+v / %v", baseline, err)
+ }
+ return nil
+ }, ErrPinnedMissing, false},
+ {"zero_txid_prepared_pin_refuses", func(f *fixture, p *PreparedPin, root File) []File {
+ *p = PreparedPin{Generation: "empty"}
+ return nil
+ }, ErrPinnedMismatch, false},
+ {"prepared_pin_key_mismatch", func(f *fixture, p *PreparedPin, root File) []File {
+ p.SnapshotTXID++
+ return nil
+ }, ErrPinnedMismatch, false},
+ {"prepared_pin_other_generation", func(f *fixture, p *PreparedPin, root File) []File {
+ p.SnapshotKey = f.pin.Key
+ p.SnapshotVersion = f.pin.VersionID
+ p.SnapshotTXID = f.pin.MaxTXID
+ return nil
+ }, ErrPinnedMismatch, false},
+ {"prepared_pin_header_mismatch", func(f *fixture, p *PreparedPin, root File) []File {
+ p.SnapshotVersion = f.b.Put("data", root.Key, header(f.t, 1, 29))
+ return nil
+ }, ErrPinnedMismatch, false},
+ {"prepared_pin_empty_version", func(f *fixture, p *PreparedPin, root File) []File {
+ p.SnapshotVersion = ""
+ return nil
+ }, ErrVersionID, false},
+ {"prepared_pin_deleted_version", func(f *fixture, p *PreparedPin, root File) []File {
+ p.SnapshotVersion = f.b.BareDelete("data", root.Key)
+ return nil
+ }, ErrDeleteMarker, false},
+ {"prepared_root_advance_past_missing_pin", func(f *fixture, p *PreparedPin, root File) []File {
+ deletePreparedRoot(f, root)
+ f.file("other", 9, 1, 999)
+ return []File{f.file("g2", 9, 1, 40), f.file("g2", 0, 41, 41)}
+ }, nil, false},
+ {"prepared_bad_successor_no_fallback", func(f *fixture, p *PreparedPin, root File) []File {
+ r := f.file("g2", 9, 1, 40)
+ f.b.Put("data", r.Key, header(f.t, 1, 39))
+ return nil
+ }, ErrRoot, false},
+ {"prepared_incomplete_listing", func(f *fixture, p *PreparedPin, root File) []File {
+ f.b.FailListAfterPages = 1
+ return nil
+ }, ErrListing, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ f := newFixture(t)
+ f.req.Holder.Generation = "g2"
+ f.req.Holder.Admissions = []hostclaim.AdmissionRecord{
+ {Generation: "source", Predecessor: "older", Region: "us", PredecessorRegion: "us", Mode: "cold", Covered: true},
+ {Generation: "g2", Predecessor: "source", Region: "ie", PredecessorRegion: "us", Mode: "cold"},
+ }
+ root := f.file("g2", 9, 1, 30)
+ f.file("g2", 0, 30, 30)
+ prepared := PreparedPin{Generation: "g2", SnapshotKey: root.Key, SnapshotVersion: root.VersionID, SnapshotTXID: 30, SnapshotCRC64: 123}
+ want := tt.setup(f, &prepared, root)
+ if tt.ordinaryRefuses {
+ got, err := Select(t.Context(), f.store, f.req)
+ if got != nil || !errors.Is(err, ErrDisagreement) {
+ t.Fatalf("ordinary selection: %+v / %v", got, err)
+ }
+ }
+ before := f.b.Snapshot()
+ holderBefore, _ := json.Marshal(f.req.Holder)
+ f.b.FailOn = func(op, key string) error {
+ if op != "list" && op != "get" {
+ t.Fatalf("mutation %s %s", op, key)
+ }
+ return nil
+ }
+ reader := preparedReader{Reader: f.store, t: t, base: prefix(f.req.Holder.Location+"/", prepared.Generation)}
+ got, err := SelectPrepared(t.Context(), reader, f.req, prepared)
+ holderAfter, _ := json.Marshal(f.req.Holder)
+ if f.b.Snapshot() != before || string(holderBefore) != string(holderAfter) {
+ t.Fatal("selection mutated inputs or storage")
+ }
+ if !errors.Is(err, tt.want) {
+ t.Fatalf("got %+v / %v, want %v", got, err, tt.want)
+ }
+ if tt.want != nil {
+ if got != nil {
+ t.Fatal("partial plan")
+ }
+ return
+ }
+ wantPin := Pin{SnapshotKey: prepared.SnapshotKey, SnapshotVersion: prepared.SnapshotVersion, SnapshotTXID: prepared.SnapshotTXID, SnapshotCRC64: prepared.SnapshotCRC64}
+ if got == nil || got.Generation != prepared.Generation || !reflect.DeepEqual(got.Files, want) ||
+ got.Root == nil || *got.Root != want[0] || got.MaxTXID != want[len(want)-1].MaxTXID ||
+ got.Marker != nil || got.MarkerVersion != "" || got.Pin == nil || *got.Pin != wantPin {
+ t.Fatalf("got %+v, want prepared %s %+v", got, prepared.Generation, want)
+ }
+ })
+ }
+}
+
+func deletePreparedRoot(f *fixture, root File) {
+ f.t.Helper()
+ if err := f.b.DeleteVersion(f.t.Context(), "data", root.Key, root.VersionID); err != nil {
+ f.t.Fatal(err)
+ }
+}
+
+type preparedReader struct {
+ Reader
+ t *testing.T
+ base string
+}
+
+func (s preparedReader) ListVersions(ctx context.Context, bucket, base string) ([]lease.ObjectVersion, error) {
+ if base != s.base {
+ s.t.Fatalf("prepared listing escaped generation: %s", base)
+ }
+ return s.Reader.ListVersions(ctx, bucket, base)
+}
+func (s preparedReader) GetVersion(context.Context, string, string, string) ([]byte, error) {
+ s.t.Fatal("prepared restore must not read a marker")
+ return nil, ErrMarker
+}
+func (s preparedReader) OpenVersion(ctx context.Context, bucket, key, id string) (io.ReadCloser, error) {
+ if !strings.HasPrefix(key, s.base) {
+ s.t.Fatalf("prepared open escaped generation: %s", key)
+ }
+ return s.Reader.OpenVersion(ctx, bucket, key, id)
+}