package snapshot import ( "context" "crypto/sha256" "fmt" "os" "path/filepath" "testing" "github.com/bnema/vev/internal/domain" "github.com/bnema/vev/internal/ports" codec "github.com/bnema/vev/internal/snapshotcodec" ) func TestRepositoryPublishRevalidatesUnchangedTenThousandObjectHistory(t *testing.T) { const historyObjects = 10_000 repo := NewRepository(privateDir(t)) first, second := largeIncrementalPublications(t, "named", historyObjects) seedCompletePublication(t, repo, first) if err := repo.Publish(context.Background(), second); err != nil { t.Fatal(err) } // Replaying the current generation revalidates every referenced immutable // object before accepting it. That is required for a forward orphan retry: // the catalogue must not advance to a checkpoint whose files were damaged // after its original publication. if err := repo.Publish(context.Background(), second); err != nil { t.Fatal(err) } got, err := loadPublication(context.Background(), repo, second) if err != nil { t.Fatal(err) } if got.Generation != second.Generation { t.Fatalf("published generation = %d, want %d", got.Generation, second.Generation) } } func BenchmarkRepositoryPublishUnchangedTenThousandObjectHistory(b *testing.B) { first, second := largeIncrementalPublications(b, "named", 10_000) repo := NewRepository(filepath.Join(b.TempDir(), "vev")) seedCompletePublication(b, repo, first) b.ReportAllocs() b.ResetTimer() for b.Loop() { if err := repo.Publish(context.Background(), second); err != nil { b.Fatal(err) } } b.StopTimer() } func largeIncrementalPublications(t testing.TB, name string, count int) (ports.SnapshotPublication, ports.SnapshotPublication) { t.Helper() id := testIncarnationID(name) sealed := make([]codec.ObjectRef, 0, count) objects := make([]ports.SnapshotObject, 0, count+2) for i := range count { object, err := codec.MarshalObject(codec.HistoryChunk, canonicalHistoryBlob(t, fmt.Sprintf("history-%d", i))) if err != nil { t.Fatal(err) } sealed = append(sealed, codec.ObjectRef{Kind: codec.HistoryChunk, Digest: object.Digest, Size: uint32(len(object.Data))}) objects = append(objects, object) } makePublication := func(generation uint64, parent *domain.CheckpointRef, tailPayload, transcriptPayload string) ports.SnapshotPublication { t.Helper() tail, err := codec.MarshalObject(codec.HistoryTail, canonicalHistoryBlob(t, tailPayload)) if err != nil { t.Fatal(err) } transcript, err := codec.MarshalObject(codec.RecoveryTranscript, canonicalHistoryBlob(t, transcriptPayload)) if err != nil { t.Fatal(err) } manifest, err := codec.MarshalManifest(codec.Manifest{Generation: generation, IncarnationID: id, ParentCheckpoint: parent, Name: name, Tabs: []codec.ManifestTab{{Cols: 1, Rows: 1, Panes: []codec.ManifestPane{{ID: "p", Sealed: sealed, Tail: codec.ObjectRef{Kind: codec.HistoryTail, Digest: tail.Digest, Size: uint32(len(tail.Data))}, Transcript: codec.ObjectRef{Kind: codec.RecoveryTranscript, Digest: transcript.Digest, Size: uint32(len(transcript.Data))}}}}}}) if err != nil { t.Fatal(err) } return ports.SnapshotPublication{IncarnationID: id, Name: name, Generation: generation, ParentCheckpoint: parent, Manifest: manifest, Objects: append(append([]ports.SnapshotObject(nil), objects...), tail, transcript)} } first := makePublication(1, nil, "tail-1", "transcript-1") parent := &domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(first.Manifest)} return first, makePublication(2, parent, "tail-2", "transcript-2") } func seedCompletePublication(t testing.TB, repo *Repository, publication ports.SnapshotPublication) { t.Helper() if err := repo.ensureSession(publication.IncarnationID); err != nil { t.Fatal(err) } manifest, err := codec.UnmarshalManifest(publication.Manifest) if err != nil { t.Fatal(err) } for _, object := range publication.Objects { if err := os.MkdirAll(filepath.Dir(repo.objectPath(publication.IncarnationID, object.Digest)), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(repo.objectPath(publication.IncarnationID, object.Digest), object.Data, 0o600); err != nil { t.Fatal(err) } } if err := os.WriteFile(repo.manifestPath(publication.IncarnationID, publication.Generation), publication.Manifest, 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(repo.headPath(publication.IncarnationID), marshalHead(publication.Generation, sha256.Sum256(publication.Manifest)), 0o600); err != nil { t.Fatal(err) } if refs := manifestRefs(manifest); refs == nil { t.Fatal("invalid seeded manifest") } }