From 470339aa212b007cb1aa6656d84ad08cff5109a1 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 9 May 2026 23:05:41 -0700 Subject: [PATCH] test(s3/lifecycle): scrub bucket lifecycle config + versions on cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests share one weed mini server. Two pollution modes were producing order-dependent failures: - A later test's shard sweep would still load the prior test's lifecycle config (the worker reads every bucket's XML from filer state, and DeleteBucket alone doesn't drop lifecycle config cleanly on this codebase). - Versioned-bucket tests left versions + delete markers behind that ListObjectsV2 can't see, so the existing best-effort empty-then- delete didn't actually empty those buckets. - The AbortMPU test intentionally leaves an in-flight upload; without an explicit AbortMultipartUpload the bucket DELETE hits NotEmpty. Cleanup now runs DeleteBucketLifecycle, ListObjectVersions → DeleteObject(versionId), ListObjectsV2 → DeleteObject (catches what ListObjectVersions missed), ListMultipartUploads → AbortMultipartUpload, then DeleteBucket. Best-effort throughout so a half-torn-down bucket doesn't fail the cleanup chain. --- test/s3/lifecycle/s3_lifecycle_test.go | 43 ++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/test/s3/lifecycle/s3_lifecycle_test.go b/test/s3/lifecycle/s3_lifecycle_test.go index 29cac7445..78ec7db3c 100644 --- a/test/s3/lifecycle/s3_lifecycle_test.go +++ b/test/s3/lifecycle/s3_lifecycle_test.go @@ -90,13 +90,50 @@ func mustCreateBucket(t *testing.T, c *s3.Client, name string) { _, err := c.CreateBucket(context.Background(), &s3.CreateBucketInput{Bucket: aws.String(name)}) require.NoError(t, err) t.Cleanup(func() { - // Best effort: empty + delete. - listOut, _ := c.ListObjectsV2(context.Background(), &s3.ListObjectsV2Input{Bucket: aws.String(name)}) + // Drop the lifecycle configuration first so any subsequent + // shell-driven shard sweep stops loading rules for this bucket + // — without this, a later test's run-shard would pick up the + // dead bucket's config and produce phantom dispatches. + c.DeleteBucketLifecycle(context.Background(), &s3.DeleteBucketLifecycleInput{Bucket: aws.String(name)}) + + // Empty every version + delete marker (versioning-aware buckets + // hold state that ListObjectsV2 doesn't surface). Best-effort: + // errors are tolerated because the bucket might already be + // half-torn-down. + listOut, _ := c.ListObjectVersions(context.Background(), &s3.ListObjectVersionsInput{Bucket: aws.String(name)}) if listOut != nil { - for _, o := range listOut.Contents { + for _, v := range listOut.Versions { + c.DeleteObject(context.Background(), &s3.DeleteObjectInput{ + Bucket: aws.String(name), Key: v.Key, VersionId: v.VersionId, + }) + } + for _, m := range listOut.DeleteMarkers { + c.DeleteObject(context.Background(), &s3.DeleteObjectInput{ + Bucket: aws.String(name), Key: m.Key, VersionId: m.VersionId, + }) + } + } + + // Catch any non-versioned objects ListObjectVersions missed. + objs, _ := c.ListObjectsV2(context.Background(), &s3.ListObjectsV2Input{Bucket: aws.String(name)}) + if objs != nil { + for _, o := range objs.Contents { c.DeleteObject(context.Background(), &s3.DeleteObjectInput{Bucket: aws.String(name), Key: o.Key}) } } + + // Abort any in-flight multipart uploads — the AbortMPU lifecycle + // test leaves these intentionally; without an explicit Abort the + // bucket DELETE refuses with NotEmpty. + mpus, _ := c.ListMultipartUploads(context.Background(), &s3.ListMultipartUploadsInput{Bucket: aws.String(name)}) + if mpus != nil { + for _, u := range mpus.Uploads { + c.AbortMultipartUpload(context.Background(), &s3.AbortMultipartUploadInput{ + Bucket: aws.String(name), Key: u.Key, UploadId: u.UploadId, + }) + } + } + c.DeleteBucket(context.Background(), &s3.DeleteBucketInput{Bucket: aws.String(name)}) }) }