mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-12 16:16:55 +00:00
test(replication): pin the scanner existing-object compensation matrix (#5877)
P1-20 (rustfs/backlog#1675 B2, test-only). No prior test wrote objects BEFORE the replication rule arrived, leaving the scanner's existing-object resync pass — the only channel for such objects — without end-to-end coverage, and the enqueue truth table partially unpinned at unit level. e2e (both negative cells are contracts, asserted over multiple fast-scanner cycles next to a replicated control key that proves the scanner and the live path are running): - test_scanner_compensates_existing_objects_across_write_paths: plain PUT, CopyObject and Snowball auto-extract products written pre-rule all converge via scanner compensation; a null-version object (PUT before the bucket became versioned) is pinned as never compensated (the scanner heal gate skips nil-version objects). - test_scanner_never_compensates_when_existing_object_replication_disabled: ExistingObjectReplication=Disabled is a contract, not a delay — existing keys stay absent while post-rule writes replicate normally. Unit truth-table pins (crates/replication): - queue.rs: an empty replicate decision (Disabled existing-object, inbound REPLICA) skips heal queueing for every status; Completed without a resync decision skips. - operation.rs: existing-object resync without a reset replicates exactly the never-replicated (Empty) objects. Helper: put_bucket_replication_with_statuses parameterizes the previously hardcoded ExistingObjectReplication status; the nextest count comments are refreshed to the post-rebase totals.
This commit is contained in:
@@ -360,6 +360,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// P1-20 truth-table pin (rustfs/backlog#1675): when no target replicates
|
||||
/// — the decision is empty because ExistingObjectReplication is Disabled
|
||||
/// for a never-replicated object, or because the object is an inbound
|
||||
/// REPLICA (must_replicate returns an empty decision for those) — the
|
||||
/// heal pass must skip entirely, whatever the recorded status says. The
|
||||
/// scanner never compensates these objects.
|
||||
#[test]
|
||||
fn heal_queue_action_skips_when_no_target_replicates() {
|
||||
for status in [
|
||||
ReplicationStatusType::Empty,
|
||||
ReplicationStatusType::Failed,
|
||||
ReplicationStatusType::Replica,
|
||||
] {
|
||||
let mut roi = ReplicateObjectInfo {
|
||||
bucket: "bucket".to_string(),
|
||||
name: "object".to_string(),
|
||||
replication_status: status,
|
||||
dsc: ReplicateDecision::new(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let action = replication_heal_queue_action(&mut roi);
|
||||
|
||||
assert!(
|
||||
matches!(action, ReplicationHealQueueAction::Skip),
|
||||
"an empty replicate decision must skip heal queueing (status {:?})",
|
||||
roi.replication_status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// P1-20 truth-table pin: a Completed object with no resync decision has
|
||||
/// nothing left to heal — the scanner must not requeue it.
|
||||
#[test]
|
||||
fn heal_queue_action_skips_completed_object_without_resync() {
|
||||
let mut roi = replicate_object_info(ReplicationStatusType::Completed);
|
||||
|
||||
let action = replication_heal_queue_action(&mut roi);
|
||||
|
||||
assert!(matches!(action, ReplicationHealQueueAction::Skip));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heal_queue_action_routes_failed_objects_to_heal_queue() {
|
||||
let mut roi = replicate_object_info(ReplicationStatusType::Failed);
|
||||
|
||||
Reference in New Issue
Block a user