You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/10/26 20:06:57 UTC
(opendal-yinyang) branch xuanwo/managed-sync-regressions updated: test: trim rename interleave to the audited committed schedule
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch xuanwo/managed-sync-regressions
in repository https://gitbox.apache.org/repos/asf/opendal-yinyang.git
The following commit(s) were added to refs/heads/xuanwo/managed-sync-regressions by this push:
new 13cf560 test: trim rename interleave to the audited committed schedule
13cf560 is described below
commit 13cf560ea6cd8164bbc332aeee6569b878cf5877
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Oct 27 04:06:52 2026 +0800
test: trim rename interleave to the audited committed schedule
An Err outcome is not evidence of a legal conflict and a bidirectional in_sync/tree equivalence overstates the contract, so the namespace-only rename regression now requires the successful, conflict-free commit the Planner predicates guarantee: retain remote v2 and the untouched local v1, assert in_sync only implies identical real trees, then let a reopened finite run materialize v2 and a final no-op report in sync. The error-as-conflict fallback and the bounded retry loop are removed [...]
Co-authored-by: Chojan Shang <ps...@apache.org>
---
tests/directory_sync.rs | 174 ++++++++++++++++++------------------------------
1 file changed, 66 insertions(+), 108 deletions(-)
diff --git a/tests/directory_sync.rs b/tests/directory_sync.rs
index ed6db42..3d68458 100644
--- a/tests/directory_sync.rs
+++ b/tests/directory_sync.rs
@@ -367,7 +367,6 @@ async fn service_authority() -> (
(Arc::new(client), temp, server)
}
-#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn initial_nested_sync_converges_in_object_and_service_modes() {
exercise_initial_pull(Arc::new(
Fs::create(TestBackend::default().operator(), BackendProfile::Minio)
@@ -829,6 +828,7 @@ async fn local_deletion_during_delayed_publication_receipt_stays_pending() {
assert_eq!(local_tree(&root), remote);
}
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn namespace_only_rename_receipt_keeps_interleaved_content() {
let backend = TestBackend::default();
@@ -874,7 +874,8 @@ async fn namespace_only_rename_receipt_keeps_interleaved_content() {
.expect("the rename commit must reach the pre-commit barrier");
// The interleaved content edit goes through the independent raw
// authority while the namespace-only rename is parked. The rename
- // predicates guard links and entries, never the inner file content.
+ // predicates guard links and entries, never the inner file content,
+ // so this exact schedule commits and later materializes v2.
let external = tempfile::tempdir().unwrap();
let seed = Sync::open(authority.clone(), external.path().join("seed"))
.await
@@ -903,120 +904,77 @@ async fn namespace_only_rename_receipt_keeps_interleaved_content() {
seed.publish(edit).await.unwrap();
release.notify_one();
};
- let run_result = tokio::time::timeout(std::time::Duration::from_secs(60), async {
+ let outcome1 = tokio::time::timeout(std::time::Duration::from_secs(60), async {
tokio::join!(run, writer)
})
.await
.expect("the interleaved rename run must finish within the timeout")
- .0;
+ .0
+ .unwrap_or_else(|error| panic!("the audited rename interleave must commit: {error}"));
- if let Ok(outcome) = &run_result {
- // Core invariant: in_sync may only ever claim byte-identical real
- // trees, so the namespace rename receipt cannot fold the unobserved
- // v2 into a common baseline with the local v1.
- assert_eq!(
- outcome.in_sync,
- local_tree(&root) == remote_tree(&authority).await,
- "in_sync must imply identical real trees"
- );
- assert!(
- remote_tree(&authority).await.get("dir").is_none(),
- "the accepted rename must move the directory out of the old name"
- );
- assert_eq!(
- remote_tree(&authority).await["moved/file"],
- TreeEntry::File {
- executable: false,
- bytes: b"v2-was-never-seen-locally".to_vec(),
- },
- "the namespace rename receipt must not swallow the interleaved external content"
- );
- } else {
- // A legal core conflict retains both sides: the external bytes stay
- // remote under one of the two names and the local v1 stays put.
- let external = TreeEntry::File {
+ assert!(
+ outcome1.conflicts.is_empty(),
+ "the audited rename interleave must not conflict: {:?}",
+ outcome1.conflicts
+ );
+ let local1 = local_tree(&root);
+ let remote1 = remote_tree(&authority).await;
+ assert!(
+ remote1.get("dir").is_none(),
+ "the accepted rename must move the directory out of the old name"
+ );
+ assert_eq!(
+ remote1["moved/file"],
+ TreeEntry::File {
executable: false,
bytes: b"v2-was-never-seen-locally".to_vec(),
- };
- let remote = remote_tree(&authority).await;
- assert!(
- remote.get("dir/file") == Some(&external)
- || remote.get("moved/file") == Some(&external),
- "the interleaved external bytes must be retained"
- );
- assert_eq!(
- local_tree(&root)["moved/file"],
- TreeEntry::File {
- executable: false,
- bytes: b"v1".to_vec(),
- },
- "the local v1 must be retained on a legal conflict"
- );
- }
+ },
+ "the namespace rename receipt must not swallow the interleaved external content"
+ );
+ assert_eq!(
+ local1["moved/file"],
+ TreeEntry::File {
+ executable: false,
+ bytes: b"v1".to_vec(),
+ },
+ "the local v1 must stay untouched until the next run materializes v2"
+ );
+ assert!(
+ !outcome1.in_sync || local1 == remote1,
+ "in_sync must imply identical real trees"
+ );
+ assert!(
+ !outcome1.in_sync,
+ "the unobserved remote v2 must stay pending behind the rename receipt"
+ );
- // After the namespace receipt, the machine either converges the subtree
- // to the interleaved v2 or keeps a legal conflict; it may never report in
- // sync while the real trees still differ.
- let mut legal_conflict = run_result.is_err();
- for _ in 0..8 {
- let local = local_tree(&root);
- let remote = remote_tree(&authority).await;
- if local == remote {
- break;
- }
- match sync.run().await {
- Ok(outcome) => {
- legal_conflict |= !outcome.conflicts.is_empty();
- assert_eq!(
- outcome.in_sync,
- local_tree(&root) == remote_tree(&authority).await,
- "in_sync must imply identical real trees"
- );
- }
- Err(_) => legal_conflict = true,
- }
- }
- let local = local_tree(&root);
- let remote = remote_tree(&authority).await;
- if !legal_conflict {
- assert_eq!(
- local, remote,
- "the moved subtree must converge to the interleaved external content"
- );
- assert_eq!(
- local["moved/file"],
- TreeEntry::File {
- executable: false,
- bytes: b"v2-was-never-seen-locally".to_vec(),
- }
- );
- assert!(local.get("dir").is_none());
- let stable = sync.run().await.unwrap();
- assert_eq!(stable.published_local, 0);
- assert_eq!(stable.materialized_remote, 0);
- assert!(stable.conflicts.is_empty());
- assert!(
- stable.in_sync,
- "the converged renamed subtree must report in sync"
- );
- assert_eq!(local_tree(&root), remote_tree(&authority).await);
- } else {
- let external = TreeEntry::File {
+ // A reopened sync materializes the retained v2 and then runs stable.
+ drop(sync);
+ let mut reopened = DirectorySync::open(held.clone(), &state).await.unwrap();
+ let outcome2 = reopened.run().await.unwrap();
+ assert!(outcome2.conflicts.is_empty());
+ let local2 = local_tree(&root);
+ let remote2 = remote_tree(&authority).await;
+ assert_eq!(
+ local2, remote2,
+ "the next run must converge the moved subtree to the interleaved v2"
+ );
+ assert_eq!(
+ local2["moved/file"],
+ TreeEntry::File {
executable: false,
bytes: b"v2-was-never-seen-locally".to_vec(),
- };
- assert_eq!(
- local["moved/file"],
- TreeEntry::File {
- executable: false,
- bytes: b"v1".to_vec(),
- },
- "local v1 must survive a legal conflict"
- );
- assert!(
- remote.get("dir/file") == Some(&external)
- || remote.get("moved/file") == Some(&external),
- "the interleaved external bytes must be retained on a legal conflict"
- );
- }
+ }
+ );
+ assert!(local2.get("dir").is_none());
+
+ let outcome3 = reopened.run().await.unwrap();
+ assert_eq!(outcome3.published_local, 0);
+ assert_eq!(outcome3.materialized_remote, 0);
+ assert!(outcome3.conflicts.is_empty());
+ assert!(
+ outcome3.in_sync,
+ "the converged renamed subtree must report in sync"
+ );
+ assert_eq!(local_tree(&root), remote_tree(&authority).await);
}