export(ExportMode::shallow_snapshot(&frontiers)) returns Ok, but the bytes it produces fail to import into a new LoroDoc when the frontiers have an odd number of heads greater than one. A single head round-trips, even head counts round-trip, state_only at the same frontiers round-trips, and the failing bytes import fine into a document that already has a local commit of its own:
use loro::{ExportMode, LoroDoc};
fn merged(peers: u64) -> LoroDoc {
let doc = LoroDoc::new();
doc.set_peer_id(1).unwrap();
doc.get_text("text").insert(0, "a").unwrap();
doc.commit();
for p in 2..=peers {
let other = LoroDoc::new();
other.set_peer_id(p).unwrap();
other.get_text("text").insert(0, "b").unwrap();
other.commit();
doc.import(&other.export(ExportMode::all_updates()).unwrap())
.unwrap();
}
doc
}
fn empty_doc() -> LoroDoc {
let doc = LoroDoc::new();
doc.set_peer_id(100).unwrap();
doc
}
fn doc_with_one_commit() -> LoroDoc {
let doc = empty_doc();
doc.get_text("other").insert(0, "z").unwrap();
doc.commit();
doc
}
fn import_into(doc: LoroDoc, bytes: &[u8]) -> String {
match doc.import(bytes) {
Ok(_) => format!("Ok({:?})", doc.get_text("text").to_string()),
Err(e) => format!("Err({e:?})"),
}
}
fn main() {
for peers in 1..=7 {
let doc = merged(peers);
let frontiers = doc.oplog_frontiers();
let shallow = doc
.export(ExportMode::shallow_snapshot(&frontiers))
.unwrap();
let state_only = doc
.export(ExportMode::state_only(Some(&frontiers)))
.unwrap();
println!(
"{} heads (source doc {:?}): shallow into empty doc {}, shallow into doc with one commit {}, state_only into empty doc {}",
frontiers.len(),
doc.get_text("text").to_string(),
import_into(empty_doc(), &shallow),
import_into(doc_with_one_commit(), &shallow),
import_into(empty_doc(), &state_only),
);
}
}
Output:
1 heads (source doc "a"): shallow into empty doc Ok("a"), shallow into doc with one commit Ok("a"), state_only into empty doc Ok("a")
2 heads (source doc "ab"): shallow into empty doc Ok("ab"), shallow into doc with one commit Ok("ab"), state_only into empty doc Ok("ab")
3 heads (source doc "abb"): shallow into empty doc Err(SwitchToVersionBeforeShallowRoot), shallow into doc with one commit Ok("abb"), state_only into empty doc Ok("abb")
4 heads (source doc "abbb"): shallow into empty doc Ok("abbb"), shallow into doc with one commit Ok("abbb"), state_only into empty doc Ok("abbb")
5 heads (source doc "abbbb"): shallow into empty doc Err(SwitchToVersionBeforeShallowRoot), shallow into doc with one commit Ok("abbbb"), state_only into empty doc Ok("abbbb")
6 heads (source doc "abbbbb"): shallow into empty doc Ok("abbbbb"), shallow into doc with one commit Ok("abbbbb"), state_only into empty doc Ok("abbbbb")
7 heads (source doc "abbbbbb"): shallow into empty doc Err(SwitchToVersionBeforeShallowRoot), shallow into doc with one commit Ok("abbbbbb"), state_only into empty doc Ok("abbbbbb")
Either the export should reject frontiers it cannot produce an importable snapshot for, or the snapshot should import. Returning Ok and failing at import time is the wrong outcome either way.
Tested on loro 1.13.9 and on main at 5c6b9f6.
BTW, this bug was found using hegel. Happy to contribute the tests if you're interested.
export(ExportMode::shallow_snapshot(&frontiers))returnsOk, but the bytes it produces fail to import into a newLoroDocwhen the frontiers have an odd number of heads greater than one. A single head round-trips, even head counts round-trip,state_onlyat the same frontiers round-trips, and the failing bytes import fine into a document that already has a local commit of its own:Output:
Either the export should reject frontiers it cannot produce an importable snapshot for, or the snapshot should import. Returning
Okand failing at import time is the wrong outcome either way.Tested on loro 1.13.9 and on
mainat 5c6b9f6.BTW, this bug was found using hegel. Happy to contribute the tests if you're interested.