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:16:00 UTC
(opendal-yinyang) branch xuanwo/managed-sync-acceptance updated: test: preserve live edits during content resolution
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch xuanwo/managed-sync-acceptance
in repository https://gitbox.apache.org/repos/asf/opendal-yinyang.git
The following commit(s) were added to refs/heads/xuanwo/managed-sync-acceptance by this push:
new 2bf52ad test: preserve live edits during content resolution
2bf52ad is described below
commit 2bf52ad850ce7fd77cc139f9aee61a6e30caf430
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Oct 27 04:15:53 2026 +0800
test: preserve live edits during content resolution
Co-authored-by: Chojan Shang <ps...@apache.org>
---
tests/linux_sync/acceptance.rs | 79 ++++++++++++++++++++++++++++++++++++++++++
tests/linux_sync/harness.rs | 25 +++++++++++--
2 files changed, 102 insertions(+), 2 deletions(-)
diff --git a/tests/linux_sync/acceptance.rs b/tests/linux_sync/acceptance.rs
index 139cd21..d43b40d 100644
--- a/tests/linux_sync/acceptance.rs
+++ b/tests/linux_sync/acceptance.rs
@@ -353,6 +353,85 @@ fn managed_sync_content_conflicts_export_and_explicit_resolution_both_authoritie
});
}
+#[test]
+#[ignore = "requires isolated S3 and the Managed Sync CLI; run with cargo x test-managed-sync"]
+fn managed_sync_resolve_content_preserves_concurrent_local_v4() {
+ let fixture = Fixture::new(Publication::Service);
+ fixture.remote_create_file("resolve-race.txt", b"base", false);
+ let left = fixture.sync_client("resolve-race-left");
+ let right = fixture.sync_client("resolve-race-right");
+ fixture.sync_run_ok(&left);
+ fixture.sync_run_ok(&right);
+
+ write_local(&left.directory.join("resolve-race.txt"), b"left-v2");
+ write_local(&right.directory.join("resolve-race.txt"), b"right-v2");
+ fixture.sync_run_ok(&left);
+ let _ = fixture.sync_run(&right);
+ let status = fixture.sync_status(&right);
+ let conflict = status.conflict("resolve-race.txt").clone();
+
+ let gate = fixture.service_gate(false);
+ let right_gated = fixture.sync_paths_at("resolve-race-right", Some(gate.address()));
+ let chosen = fixture.path("resolve-race-chosen");
+ write_local(&chosen, b"chosen-v3");
+ let running = fixture.spawn_sync_resolve_content(&right_gated, &conflict.id, &chosen);
+ if let Err(error) = gate.wait_for_commit_response(Duration::from_secs(120)) {
+ let output = running.wait(Duration::from_secs(130));
+ panic!(
+ "gate did not observe the content-resolution commit ({error}):\n{}",
+ captured_text(&output)
+ );
+ }
+
+ assert_eq!(
+ remote_file_bytes(&fixture, "resolve-race.txt"),
+ b"chosen-v3"
+ );
+ let during = fixture.sync_status(&right_gated);
+ assert!(
+ during
+ .conflicts
+ .iter()
+ .any(|conflict| conflict.id == status.conflict("resolve-race.txt").id),
+ "the original conflict must remain until its commit response is accepted: {during:#?}"
+ );
+
+ write_local(&right_gated.directory.join("resolve-race.txt"), b"local-v4");
+ gate.release();
+ let output = running.wait(Duration::from_secs(120));
+ assert_eq!(
+ fs::read(right_gated.directory.join("resolve-race.txt")).unwrap(),
+ b"local-v4",
+ "resolve completion must not overwrite the live v4 payload:\n{}",
+ captured_text(&output)
+ );
+ let remote_after = remote_file_bytes(&fixture, "resolve-race.txt");
+ assert!(
+ remote_after.as_slice() == b"chosen-v3" || remote_after.as_slice() == b"local-v4",
+ "resolve may leave v3 remote with local pending, or converge v4: {remote_after:?}"
+ );
+ if remote_after.as_slice() == b"chosen-v3" {
+ let status = fixture.sync_status(&right_gated);
+ assert!(!status.summary.in_sync, "{status:#?}");
+ assert_eq!(
+ status.entry("resolve-race.txt").state,
+ EntryState::LocalPending
+ );
+ }
+
+ fixture.sync_run_ok(&right_gated);
+ assert_eq!(remote_file_bytes(&fixture, "resolve-race.txt"), b"local-v4");
+ assert_eq!(
+ fs::read(right_gated.directory.join("resolve-race.txt")).unwrap(),
+ b"local-v4"
+ );
+ assert_in_sync(&fixture, &right_gated);
+ let converged = fixture.remote_file_version("resolve-race.txt");
+ fixture.sync_run_ok(&right_gated);
+ assert_eq!(fixture.remote_file_version("resolve-race.txt"), converged);
+ assert_in_sync(&fixture, &right_gated);
+}
+
#[test]
#[ignore = "requires isolated S3 and the Managed Sync CLI; run with cargo x test-managed-sync"]
fn managed_sync_offline_edits_survive_process_and_authority_restart() {
diff --git a/tests/linux_sync/harness.rs b/tests/linux_sync/harness.rs
index 6da45ee..5efeabd 100644
--- a/tests/linux_sync/harness.rs
+++ b/tests/linux_sync/harness.rs
@@ -647,8 +647,8 @@ impl Fixture {
self.yy_with_env(&["--volume", text(&paths.config), "sync", "run"], overrides)
}
- pub fn spawn_sync_run(&self, paths: &SyncPaths) -> RunningSync {
- let mut command = self.yy_command(&["--volume", text(&paths.config), "sync", "run"]);
+ pub fn spawn_yy(&self, args: &[&str]) -> RunningSync {
+ let mut command = self.yy_command(args);
command
.stdin(Stdio::null())
.stdout(Stdio::piped())
@@ -665,6 +665,10 @@ impl Fixture {
}
}
+ pub fn spawn_sync_run(&self, paths: &SyncPaths) -> RunningSync {
+ self.spawn_yy(&["--volume", text(&paths.config), "sync", "run"])
+ }
+
pub fn sync_run_ok(&self, paths: &SyncPaths) -> Captured {
let output = self.sync_run(paths);
output.success_or(&format!("yy sync run for {}", paths.directory.display()));
@@ -785,6 +789,23 @@ impl Fixture {
]);
}
+ pub fn spawn_sync_resolve_content(
+ &self,
+ paths: &SyncPaths,
+ conflict: &str,
+ content: &Path,
+ ) -> RunningSync {
+ self.spawn_yy(&[
+ "--volume",
+ text(&paths.config),
+ "sync",
+ "resolve",
+ conflict,
+ "--content",
+ text(content),
+ ])
+ }
+
fn spawn_service(&mut self) {
let address = self
.last_service_address