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 19:17:28 UTC

(opendal-yinyang) branch xuanwo/managed-sync-reconciliation updated: test: require deferred local deletion through a delayed receipt

This is an automated email from the ASF dual-hosted git repository.

Xuanwo pushed a commit to branch xuanwo/managed-sync-reconciliation
in repository https://gitbox.apache.org/repos/asf/opendal-yinyang.git


The following commit(s) were added to refs/heads/xuanwo/managed-sync-reconciliation by this push:
     new e138c3e  test: require deferred local deletion through a delayed receipt
e138c3e is described below

commit e138c3ede273a1674c37a3e9bd0626e435c34ae7
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Oct 27 03:13:41 2026 +0800

    test: require deferred local deletion through a delayed receipt
    
    A local delete landing while the v1 publication receipt is delayed must stay pending: run1 reports !in_sync with pending > 0, the remote keeps v1, and the local file stays deleted. After reopen the next run publishes the deletion and a final no-op run reports in_sync without resurrecting the file. This guards the accepted v1 baseline against paths that vanish between the commit and its fresh-scan acceptance.
    
    Co-authored-by: Chojan Shang <ps...@apache.org>
---
 tests/directory_sync.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 104 insertions(+)

diff --git a/tests/directory_sync.rs b/tests/directory_sync.rs
index a0bc388..e540248 100644
--- a/tests/directory_sync.rs
+++ b/tests/directory_sync.rs
@@ -570,3 +570,107 @@ async fn local_edit_during_delayed_publication_receipt_stays_pending() {
     assert!(outcome3.in_sync);
     assert_eq!(local_tree(&root), remote);
 }
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn local_deletion_during_delayed_publication_receipt_stays_pending() {
+    let backend = TestBackend::default();
+    let authority: Arc<dyn Authority> = Arc::new(
+        Fs::create(backend.operator(), BackendProfile::Minio)
+            .await
+            .unwrap(),
+    );
+    let (committed_tx, mut committed_rx) = tokio::sync::mpsc::unbounded_channel();
+    let release = Arc::new(tokio::sync::Notify::new());
+    let held = Arc::new(HeldAuthority::new(
+        authority.clone(),
+        committed_tx,
+        release.clone(),
+    ));
+
+    let temp = tempfile::tempdir().unwrap();
+    let root = temp.path().join("payload");
+    let state = temp.path().join("state");
+    std::fs::create_dir_all(&root).unwrap();
+    let file = root.join("file");
+    std::fs::write(&file, b"version one").unwrap();
+
+    let mut sync = DirectorySync::init(held.clone(), &state, &root)
+        .await
+        .unwrap();
+    let run1 = sync.run();
+    let writer = async {
+        committed_rx
+            .recv()
+            .await
+            .expect("v1 commit must complete before the local delete");
+        std::fs::remove_file(&file).expect("local delete");
+        release.notify_one();
+    };
+    let outcome1 = tokio::time::timeout(std::time::Duration::from_secs(60), async {
+        tokio::join!(run1, writer)
+    })
+    .await
+    .expect("v1 publication must commit and release its receipt in time")
+    .0
+    .unwrap();
+
+    assert_eq!(outcome1.published_local, 1);
+    assert!(
+        outcome1.conflicts.is_empty(),
+        "v1 run conflicts: {:?}",
+        outcome1.conflicts
+    );
+    assert!(
+        !outcome1.in_sync,
+        "the local deletion must not be marked in sync behind a delayed receipt"
+    );
+    assert!(
+        outcome1.pending > 0,
+        "the local deletion must remain pending until it is published"
+    );
+    assert!(
+        !file.exists(),
+        "the delayed receipt must not resurrect the deleted local file"
+    );
+    let remote = remote_tree(&authority).await;
+    assert_eq!(
+        remote["file"],
+        TreeEntry::File {
+            executable: false,
+            bytes: b"version one".to_vec(),
+        },
+        "the remote must still hold only the uploaded v1"
+    );
+    drop(sync);
+
+    // The accepted v1 baseline must survive the delayed receipt so the next
+    // run publishes the local deletion instead of materializing v1 back.
+    let mut reopened = DirectorySync::open(held.clone(), &state).await.unwrap();
+    let outcome2 = reopened.run().await.unwrap();
+    assert!(
+        outcome2.published_local >= 1,
+        "the next run must publish the local deletion"
+    );
+    assert!(
+        outcome2.conflicts.is_empty(),
+        "deletion publish conflicts: {:?}",
+        outcome2.conflicts
+    );
+    let remote = remote_tree(&authority).await;
+    assert_eq!(
+        remote.get("file"),
+        None,
+        "the published deletion must remove the remote file"
+    );
+    assert!(!file.exists());
+
+    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,
+        "a converged run after the published deletion must report in sync"
+    );
+    assert_eq!(local_tree(&root), remote);
+}