From 40f0274a6686336bd8ca7c15446681449dbc739e Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 5 Nov 2026 05:20:47 +0100 Subject: add pushes match automerge test --- src/document/sync.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/document/sync.rs b/src/document/sync.rs index 4ae41ba..7695b82 100644 --- a/src/document/sync.rs +++ b/src/document/sync.rs @@ -1553,6 +1553,54 @@ mod tests { assert_eq!(ours.get_heads(), theirs.get_heads()); } + /// A push leaves the receiver where Automerge's own message would have: same + /// document, same answer, and the sender's next turn the same. If an Automerge + /// upgrade changes what a sync message's parts mean, this fails, not the swarm. + #[test] + fn pushes_do_what_automerges_messages_do() { + use crate::document::testing::change; + use automerge::{ROOT, transaction::Transactable}; + let (now, bytes) = (Instant::now(), SyncBytes::default()); + for concurrent in [false, true] { + let (mut ours, mut theirs) = (Document::new(), Document::new()); + change(&mut ours, |doc| doc.put(ROOT, "early", 1).unwrap()); + let (a, b) = in_step(&mut ours, &mut theirs); + if concurrent { + change(&mut theirs, |doc| doc.put(ROOT, "theirs", 3).unwrap()); + } + change(&mut ours, |doc| doc.put(ROOT, "news", 2).unwrap()); + + // Each way: what the receiver holds, whether it answers, with what, and + // what the sender holds and says next once it has the answer. + let outcome = |pushing: bool| { + let (mut a, mut b) = (a.clone(), b.clone()); + let (mut ours, mut theirs) = (ours.clone(), theirs.clone()); + let message = if pushing { + let (heads, changes) = a.push(&ours, 1 << 20).expect("news to push"); + Received::pushed(heads, changes).expect("decodes") + } else { + decode(&a.generate(&ours, now, bytes).expect("news")).unwrap() + }; + a.assume_delivered(&ours.get_heads()); + a.send_succeeded(a.id()); + let before = theirs.get_heads(); + b.receive(&mut theirs, message).unwrap(); + b.came_from_peer(&theirs, &before); + let level = b.answered_as_delivered(&theirs, &theirs.get_heads()); + let answer = b.generate(&theirs, now, bytes).map(|m| decode(&m).unwrap()); + let answered = answer.as_ref().map(|m| (m.heads().to_vec(), m.sizes().0)); + if let Some(answer) = answer { + a.receive(&mut ours, answer).unwrap(); + } + let next = a + .generate(&ours, now, bytes) + .map(|m| decode(&m).unwrap().sizes().0); + (theirs.get_heads(), level, answered, ours.get_heads(), next) + }; + assert_eq!(outcome(true), outcome(false), "concurrent: {concurrent}"); + } + } + /// A push without changes, or without heads and changes that don't decode, is no /// message: no heads would say the sender lost all it had. #[test] -- cgit v1.3.1