mirror of
https://github.com/rustfs/rustfs.git
synced 2026-11-01 08:40:23 +00:00
test(listing): expect residue preservation across walks
This commit is contained in:
@@ -229,7 +229,7 @@
|
||||
{
|
||||
"invariant": "list-objects-shared-corpus-orphan-cleanup-requires-authoritative-evidence",
|
||||
"suite": "rustfs-ecstore",
|
||||
"name": "store::list_objects::differential_tests::list_objects_shared_corpus_orphan_cleanup_requires_authoritative_evidence"
|
||||
"name": "store::list_objects::differential_tests::list_objects_shared_corpus_orphan_listing_preserves_inputs"
|
||||
},
|
||||
{
|
||||
"invariant": "list-objects-shared-corpus-consumer-drop-joins-real-disk-producer",
|
||||
|
||||
@@ -5355,7 +5355,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_delimiter_listing_hides_and_purges_committed_delete_residue() {
|
||||
async fn empty_delimiter_listing_hides_and_preserves_committed_delete_residue() {
|
||||
use crate::bucket::metadata_sys::{init_bucket_metadata_sys, test_support::isolated_store_over_temp_disks};
|
||||
use crate::storage_api_contracts::bucket::{BucketOperations as _, MakeBucketOptions};
|
||||
|
||||
@@ -5400,15 +5400,22 @@ mod test {
|
||||
assert!(result.objects.is_empty());
|
||||
assert!(result.prefixes.is_empty(), "delete residue must not surface as a prefix");
|
||||
for dir in &dirs {
|
||||
let residue = dir
|
||||
.path()
|
||||
.join(bucket)
|
||||
.join("metrics/2026/object.parquet")
|
||||
.join(data_dir.to_string());
|
||||
assert_eq!(tokio::fs::read(residue.join("part.1")).await.expect("LIST preserves part"), b"stale");
|
||||
assert!(
|
||||
!dir.path().join(bucket).join("metrics").join("2026").exists(),
|
||||
"the empty delimiter listing should reclaim the committed delete residue under it"
|
||||
residue
|
||||
.join(format!("{}{}", crate::disk::local::DELETE_DATA_DIR_MARKER_PREFIX, transaction))
|
||||
.exists()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_delimiter_listing_of_residue_ancestor_hides_and_purges_whole_tree() {
|
||||
async fn empty_delimiter_listing_of_residue_ancestor_hides_and_preserves_whole_tree() {
|
||||
use crate::bucket::metadata_sys::{init_bucket_metadata_sys, test_support::isolated_store_over_temp_disks};
|
||||
use crate::storage_api_contracts::bucket::{BucketOperations as _, MakeBucketOptions};
|
||||
|
||||
@@ -5441,9 +5448,7 @@ mod test {
|
||||
.expect("committed delete marker should be written");
|
||||
}
|
||||
|
||||
// Browsing an ancestor of the deleted key must not show the empty
|
||||
// date folders, and the empty result reclaims the whole residue tree
|
||||
// in one pass instead of one level per listing.
|
||||
// Browsing an ancestor must not expose the deleted key's date folders.
|
||||
let result = store
|
||||
.clone()
|
||||
.list_objects_generic(bucket, "metrics/", None, Some("/".to_owned()), 1000, false)
|
||||
@@ -5452,15 +5457,22 @@ mod test {
|
||||
assert!(result.objects.is_empty());
|
||||
assert!(result.prefixes.is_empty(), "ancestors of delete residue must not surface as prefixes");
|
||||
for dir in &dirs {
|
||||
let residue = dir
|
||||
.path()
|
||||
.join(bucket)
|
||||
.join("metrics/kubelet/2026/08/28/23/74992556388248657933757.parquet")
|
||||
.join(data_dir.to_string());
|
||||
assert_eq!(tokio::fs::read(residue.join("part.1")).await.expect("LIST preserves part"), b"stale");
|
||||
assert!(
|
||||
!dir.path().join(bucket).join("metrics").exists(),
|
||||
"the empty delimiter listing should reclaim the committed delete residue tree under it"
|
||||
residue
|
||||
.join(format!("{}{}", crate::disk::local::DELETE_DATA_DIR_MARKER_PREFIX, transaction))
|
||||
.exists()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_delimiter_listing_of_mixed_residue_ancestor_reclaims_only_committed_subtree() {
|
||||
async fn empty_delimiter_listing_of_mixed_residue_ancestor_preserves_every_subtree() {
|
||||
use crate::bucket::metadata_sys::{init_bucket_metadata_sys, test_support::isolated_store_over_temp_disks};
|
||||
use crate::storage_api_contracts::bucket::{BucketOperations as _, MakeBucketOptions};
|
||||
|
||||
@@ -5533,9 +5545,18 @@ mod test {
|
||||
assert!(result.prefixes.is_empty(), "residue subtrees may not surface as prefixes");
|
||||
for dir in &dirs {
|
||||
let metrics = dir.path().join(bucket).join("metrics");
|
||||
assert!(
|
||||
!metrics.join("cpu").exists(),
|
||||
"the committed subtree must be reclaimed even though a sibling subtree is blocked"
|
||||
let committed = metrics.join("cpu/2026/08/28/22/a.parquet").join(committed_dir.to_string());
|
||||
assert_eq!(
|
||||
tokio::fs::read(committed.join("part.1"))
|
||||
.await
|
||||
.expect("LIST preserves committed part"),
|
||||
b"stale"
|
||||
);
|
||||
assert_eq!(
|
||||
tokio::fs::read(committed.join(crate::disk::STORAGE_FORMAT_FILE_BACKUP))
|
||||
.await
|
||||
.expect("LIST preserves backup"),
|
||||
b"rollback metadata"
|
||||
);
|
||||
assert!(
|
||||
metrics
|
||||
@@ -5543,7 +5564,7 @@ mod test {
|
||||
.join(unmarked_dir.to_string())
|
||||
.join("part.1")
|
||||
.exists(),
|
||||
"residue without a committed marker must survive the purge"
|
||||
"residue without a committed marker must survive the listing"
|
||||
);
|
||||
assert_eq!(
|
||||
tokio::fs::read(
|
||||
|
||||
@@ -755,7 +755,7 @@ async fn list_objects_shared_corpus_versions_delete_markers_and_free_versions()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_objects_shared_corpus_orphan_cleanup_requires_authoritative_evidence() {
|
||||
async fn list_objects_shared_corpus_orphan_listing_preserves_inputs() {
|
||||
for authoritative in [false, true] {
|
||||
let fixture = Fixture::new(
|
||||
&format!("list-corpus-orphan-{authoritative}"),
|
||||
@@ -774,10 +774,9 @@ async fn list_objects_shared_corpus_orphan_cleanup_requires_authoritative_eviden
|
||||
let result = fixture.page("ghost/", None, None, 1).await;
|
||||
assert!(result.objects.is_empty() && result.prefixes.is_empty() && !result.is_truncated);
|
||||
for dir in &fixture.dirs {
|
||||
assert_eq!(
|
||||
dir.path().join(&fixture.bucket).join("ghost").exists(),
|
||||
!authoritative,
|
||||
"unknown metadata must fail closed"
|
||||
assert!(
|
||||
dir.path().join(&fixture.bucket).join("ghost/nested/leaf").exists(),
|
||||
"LIST must preserve orphan input"
|
||||
);
|
||||
assert!(
|
||||
dir.path()
|
||||
@@ -785,7 +784,7 @@ async fn list_objects_shared_corpus_orphan_cleanup_requires_authoritative_eviden
|
||||
.join("legal/child")
|
||||
.join(STORAGE_FORMAT_FILE)
|
||||
.exists(),
|
||||
"purge must preserve legal siblings"
|
||||
"LIST must preserve legal siblings"
|
||||
);
|
||||
}
|
||||
fixture.assert_walk("legal/", None, None, 1).await;
|
||||
@@ -796,7 +795,7 @@ async fn list_objects_shared_corpus_orphan_cleanup_requires_authoritative_eviden
|
||||
.join("legal/child")
|
||||
.join(STORAGE_FORMAT_FILE)
|
||||
.exists(),
|
||||
"an orphan in the same subtree must not cause the legitimate child to be purged"
|
||||
"an orphan in the same subtree must not affect a legitimate child"
|
||||
);
|
||||
}
|
||||
if authoritative {
|
||||
@@ -811,17 +810,15 @@ async fn list_objects_shared_corpus_orphan_cleanup_requires_authoritative_eviden
|
||||
.bucket_metadata_sys()
|
||||
.expect("authoritative fixture has a metadata system");
|
||||
let guard = metadata_sys.write().await;
|
||||
assert!(
|
||||
timeout(Duration::from_millis(100), fixture.page("timed-out/", None, None, 1))
|
||||
.await
|
||||
.is_err(),
|
||||
"blocked authoritative evidence must time out before cleanup can classify the prefix as empty"
|
||||
);
|
||||
let result = timeout(Duration::from_secs(2), fixture.page("timed-out/", None, None, 1))
|
||||
.await
|
||||
.expect("LIST must not wait for cleanup authorization");
|
||||
assert!(result.objects.is_empty() && result.prefixes.is_empty() && !result.is_truncated);
|
||||
drop(guard);
|
||||
for dir in &fixture.dirs {
|
||||
assert!(
|
||||
dir.path().join(&fixture.bucket).join("timed-out").exists(),
|
||||
"a timed-out evidence check must preserve its orphan input"
|
||||
"LIST must preserve its orphan input while metadata is locked"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user