The Rust API has so far grown somewhat organically driven by the needs of the javascript implementation. This has led to an API which is quite awkward and unfamiliar to Rust programmers. Additionally there is no documentation to speak of. This commit is the first movement towards cleaning things up a bit. We touch a lot of files but the changes are all very mechanical. We introduce a few traits to abstract over the common operations between `Automerge` and `AutoCommit`, and add a whole bunch of documentation. * Add a `ReadDoc` trait to describe methods which read value from a document. make `Transactable` extend `ReadDoc` * Add a `SyncDoc` trait to describe methods necessary for synchronizing documents. * Put the `SyncDoc` implementation for `AutoCommit` behind `AutoCommit::sync` to ensure that any open transactions are closed before taking part in the sync protocol * Split `OpObserver` into two traits: `OpObserver` + `BranchableObserver`. `BranchableObserver` captures the methods which are only needed for observing transactions. * Add a whole bunch of documentation. The main changes Rust users will need to make is: * Import the `ReadDoc` trait wherever you are using the methods which have been moved to it. Optionally change concrete paramters on functions to `ReadDoc` constraints. * Likewise import the `SyncDoc` trait wherever you are doing synchronisation work * If you are using the `AutoCommit::*_sync_message` methods you will need to add a call to `AutoCommit::sync()` first. E.g. `doc.generate_sync_message` becomes `doc.sync().generate_sync_message` * If you have an implementation of `OpObserver` which you are using in an `AutoCommit` then split it into an implementation of `OpObserver` and `BranchableObserver`
1456 lines
39 KiB
Rust
1456 lines
39 KiB
Rust
use automerge::transaction::Transactable;
|
|
use automerge::{
|
|
ActorId, AutoCommit, Automerge, AutomergeError, Change, ExpandedChange, ObjType, ReadDoc,
|
|
ScalarValue, VecOpObserver, ROOT,
|
|
};
|
|
use std::fs;
|
|
|
|
// set up logging for all the tests
|
|
//use test_log::test;
|
|
|
|
#[allow(unused_imports)]
|
|
use automerge_test::{
|
|
assert_doc, assert_obj, list, map, mk_counter, new_doc, new_doc_with_actor, pretty_print,
|
|
realize, realize_obj, sorted_actors, RealizedObject,
|
|
};
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn no_conflict_on_repeated_assignment() {
|
|
let mut doc = AutoCommit::new();
|
|
doc.put(&automerge::ROOT, "foo", 1).unwrap();
|
|
doc.put(&automerge::ROOT, "foo", 2).unwrap();
|
|
assert_doc!(
|
|
&doc,
|
|
map! {
|
|
"foo" => { 2 },
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn repeated_map_assignment_which_resolves_conflict_not_ignored() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
doc1.put(&automerge::ROOT, "field", 123).unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc2.put(&automerge::ROOT, "field", 456).unwrap();
|
|
doc1.put(&automerge::ROOT, "field", 789).unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
assert_eq!(doc1.get_all(&automerge::ROOT, "field").unwrap().len(), 2);
|
|
|
|
doc1.put(&automerge::ROOT, "field", 123).unwrap();
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"field" => { 123 }
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn repeated_list_assignment_which_resolves_conflict_not_ignored() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "list", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list_id, 0, 123).unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc2.put(&list_id, 0, 456).unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
doc1.put(&list_id, 0, 789).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"list" => {
|
|
list![
|
|
{ 789 },
|
|
]
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn list_deletion() {
|
|
let mut doc = new_doc();
|
|
let list_id = doc
|
|
.put_object(&automerge::ROOT, "list", ObjType::List)
|
|
.unwrap();
|
|
doc.insert(&list_id, 0, 123).unwrap();
|
|
doc.insert(&list_id, 1, 456).unwrap();
|
|
doc.insert(&list_id, 2, 789).unwrap();
|
|
doc.delete(&list_id, 1).unwrap();
|
|
assert_doc!(
|
|
&doc,
|
|
map! {
|
|
"list" => { list![
|
|
{ 123 },
|
|
{ 789 },
|
|
]}
|
|
}
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn merge_concurrent_map_prop_updates() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
doc1.put(&automerge::ROOT, "foo", "bar").unwrap();
|
|
doc2.put(&automerge::ROOT, "hello", "world").unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
assert_eq!(
|
|
doc1.get(&automerge::ROOT, "foo").unwrap().unwrap().0,
|
|
"bar".into()
|
|
);
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"foo" => { "bar" },
|
|
"hello" => { "world" },
|
|
}
|
|
);
|
|
doc2.merge(&mut doc1).unwrap();
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"foo" => { "bar" },
|
|
"hello" => { "world" },
|
|
}
|
|
);
|
|
assert_eq!(realize(doc1.document()), realize(doc2.document()));
|
|
}
|
|
|
|
#[test]
|
|
fn add_concurrent_increments_of_same_property() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
doc1.put(&automerge::ROOT, "counter", mk_counter(0))
|
|
.unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc1.increment(&automerge::ROOT, "counter", 1).unwrap();
|
|
doc2.increment(&automerge::ROOT, "counter", 2).unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"counter" => {
|
|
mk_counter(3)
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn add_increments_only_to_preceeded_values() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
|
|
doc1.put(&automerge::ROOT, "counter", mk_counter(0))
|
|
.unwrap();
|
|
doc1.increment(&automerge::ROOT, "counter", 1).unwrap();
|
|
|
|
// create a counter in doc2
|
|
doc2.put(&automerge::ROOT, "counter", mk_counter(0))
|
|
.unwrap();
|
|
doc2.increment(&automerge::ROOT, "counter", 3).unwrap();
|
|
|
|
// The two values should be conflicting rather than added
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"counter" => {
|
|
mk_counter(1),
|
|
mk_counter(3),
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_updates_of_same_field() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
doc1.put(&automerge::ROOT, "field", "one").unwrap();
|
|
doc2.put(&automerge::ROOT, "field", "two").unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"field" => {
|
|
"one",
|
|
"two",
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_updates_of_same_list_element() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "birds", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list_id, 0, "finch").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc1.put(&list_id, 0, "greenfinch").unwrap();
|
|
doc2.put(&list_id, 0, "goldfinch").unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"birds" => {
|
|
list![{
|
|
"greenfinch",
|
|
"goldfinch",
|
|
}]
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn assignment_conflicts_of_different_types() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
let mut doc3 = new_doc();
|
|
doc1.put(&automerge::ROOT, "field", "string").unwrap();
|
|
doc2.put_object(&automerge::ROOT, "field", ObjType::List)
|
|
.unwrap();
|
|
doc3.put_object(&automerge::ROOT, "field", ObjType::Map)
|
|
.unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
doc1.merge(&mut doc3).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"field" => {
|
|
"string",
|
|
list!{},
|
|
map!{},
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn changes_within_conflicting_map_field() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
doc1.put(&automerge::ROOT, "field", "string").unwrap();
|
|
let map_id = doc2
|
|
.put_object(&automerge::ROOT, "field", ObjType::Map)
|
|
.unwrap();
|
|
doc2.put(&map_id, "innerKey", 42).unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"field" => {
|
|
"string",
|
|
map!{
|
|
"innerKey" => {
|
|
42,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn changes_within_conflicting_list_element() {
|
|
let (actor1, actor2) = sorted_actors();
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
let mut doc2 = new_doc_with_actor(actor2);
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "list", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list_id, 0, "hello").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
let map_in_doc1 = doc1.put_object(&list_id, 0, ObjType::Map).unwrap();
|
|
doc1.put(&map_in_doc1, "map1", true).unwrap();
|
|
doc1.put(&map_in_doc1, "key", 1).unwrap();
|
|
|
|
let map_in_doc2 = doc2.put_object(&list_id, 0, ObjType::Map).unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
doc2.put(&map_in_doc2, "map2", true).unwrap();
|
|
doc2.put(&map_in_doc2, "key", 2).unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"list" => {
|
|
list![
|
|
{
|
|
map!{
|
|
"map2" => { true },
|
|
"key" => { 2 },
|
|
},
|
|
map!{
|
|
"key" => { 1 },
|
|
"map1" => { true },
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrently_assigned_nested_maps_should_not_merge() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
|
|
let doc1_map_id = doc1
|
|
.put_object(&automerge::ROOT, "config", ObjType::Map)
|
|
.unwrap();
|
|
doc1.put(&doc1_map_id, "background", "blue").unwrap();
|
|
|
|
let doc2_map_id = doc2
|
|
.put_object(&automerge::ROOT, "config", ObjType::Map)
|
|
.unwrap();
|
|
doc2.put(&doc2_map_id, "logo_url", "logo.png").unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"config" => {
|
|
map!{
|
|
"background" => {"blue"}
|
|
},
|
|
map!{
|
|
"logo_url" => {"logo.png"}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_insertions_at_different_list_positions() {
|
|
let (actor1, actor2) = sorted_actors();
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
let mut doc2 = new_doc_with_actor(actor2);
|
|
assert!(doc1.get_actor() < doc2.get_actor());
|
|
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "list", ObjType::List)
|
|
.unwrap();
|
|
|
|
doc1.insert(&list_id, 0, "one").unwrap();
|
|
doc1.insert(&list_id, 1, "three").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc1.splice(&list_id, 1, 0, vec!["two".into()]).unwrap();
|
|
doc2.insert(&list_id, 2, "four").unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"list" => {
|
|
list![
|
|
{"one"},
|
|
{"two"},
|
|
{"three"},
|
|
{"four"},
|
|
]
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_insertions_at_same_list_position() {
|
|
let (actor1, actor2) = sorted_actors();
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
let mut doc2 = new_doc_with_actor(actor2);
|
|
assert!(doc1.get_actor() < doc2.get_actor());
|
|
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "birds", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list_id, 0, "parakeet").unwrap();
|
|
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc1.insert(&list_id, 1, "starling").unwrap();
|
|
doc2.insert(&list_id, 1, "chaffinch").unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"birds" => {
|
|
list![
|
|
{
|
|
"parakeet",
|
|
},
|
|
{
|
|
"chaffinch",
|
|
},
|
|
{
|
|
"starling",
|
|
},
|
|
]
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_assignment_and_deletion_of_a_map_entry() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
doc1.put(&automerge::ROOT, "bestBird", "robin").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc1.delete(&automerge::ROOT, "bestBird").unwrap();
|
|
doc2.put(&automerge::ROOT, "bestBird", "magpie").unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"bestBird" => {
|
|
"magpie",
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_assignment_and_deletion_of_list_entry() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "birds", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list_id, 0, "blackbird").unwrap();
|
|
doc1.insert(&list_id, 1, "thrush").unwrap();
|
|
doc1.insert(&list_id, 2, "goldfinch").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc1.put(&list_id, 1, "starling").unwrap();
|
|
doc2.delete(&list_id, 1).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"birds" => {list![
|
|
{"blackbird"},
|
|
{"goldfinch"},
|
|
]}
|
|
}
|
|
);
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"birds" => {list![
|
|
{ "blackbird" },
|
|
{ "starling" },
|
|
{ "goldfinch" },
|
|
]}
|
|
}
|
|
);
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"birds" => {list![
|
|
{ "blackbird" },
|
|
{ "starling" },
|
|
{ "goldfinch" },
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn insertion_after_a_deleted_list_element() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "birds", ObjType::List)
|
|
.unwrap();
|
|
|
|
doc1.insert(&list_id, 0, "blackbird").unwrap();
|
|
doc1.insert(&list_id, 1, "thrush").unwrap();
|
|
doc1.insert(&list_id, 2, "goldfinch").unwrap();
|
|
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
doc1.splice(&list_id, 1, 2, Vec::new()).unwrap();
|
|
|
|
doc2.splice(&list_id, 2, 0, vec!["starling".into()])
|
|
.unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"birds" => {list![
|
|
{ "blackbird" },
|
|
{ "starling" }
|
|
]}
|
|
}
|
|
);
|
|
|
|
doc2.merge(&mut doc1).unwrap();
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"birds" => {list![
|
|
{ "blackbird" },
|
|
{ "starling" }
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_deletion_of_same_list_element() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
let list_id = doc1
|
|
.put_object(&automerge::ROOT, "birds", ObjType::List)
|
|
.unwrap();
|
|
|
|
doc1.insert(&list_id, 0, "albatross").unwrap();
|
|
doc1.insert(&list_id, 1, "buzzard").unwrap();
|
|
doc1.insert(&list_id, 2, "cormorant").unwrap();
|
|
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
doc1.delete(&list_id, 1).unwrap();
|
|
|
|
doc2.delete(&list_id, 1).unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"birds" => {list![
|
|
{ "albatross" },
|
|
{ "cormorant" }
|
|
]}
|
|
}
|
|
);
|
|
|
|
doc2.merge(&mut doc1).unwrap();
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"birds" => {list![
|
|
{ "albatross" },
|
|
{ "cormorant" }
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_updates_at_different_levels() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
|
|
let animals = doc1
|
|
.put_object(&automerge::ROOT, "animals", ObjType::Map)
|
|
.unwrap();
|
|
let birds = doc1.put_object(&animals, "birds", ObjType::Map).unwrap();
|
|
doc1.put(&birds, "pink", "flamingo").unwrap();
|
|
doc1.put(&birds, "black", "starling").unwrap();
|
|
|
|
let mammals = doc1.put_object(&animals, "mammals", ObjType::List).unwrap();
|
|
doc1.insert(&mammals, 0, "badger").unwrap();
|
|
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
doc1.put(&birds, "brown", "sparrow").unwrap();
|
|
|
|
doc2.delete(&animals, "birds").unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_obj!(
|
|
&doc1,
|
|
&automerge::ROOT,
|
|
"animals",
|
|
map! {
|
|
"mammals" => {
|
|
list![{ "badger" }],
|
|
}
|
|
}
|
|
);
|
|
|
|
assert_obj!(
|
|
doc2.document(),
|
|
&automerge::ROOT,
|
|
"animals",
|
|
map! {
|
|
"mammals" => {
|
|
list![{ "badger" }],
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn concurrent_updates_of_concurrently_deleted_objects() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
|
|
let birds = doc1
|
|
.put_object(&automerge::ROOT, "birds", ObjType::Map)
|
|
.unwrap();
|
|
let blackbird = doc1.put_object(&birds, "blackbird", ObjType::Map).unwrap();
|
|
doc1.put(&blackbird, "feathers", "black").unwrap();
|
|
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
doc1.delete(&birds, "blackbird").unwrap();
|
|
|
|
doc2.put(&blackbird, "beak", "orange").unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"birds" => {
|
|
map!{},
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn does_not_interleave_sequence_insertions_at_same_position() {
|
|
let (actor1, actor2) = sorted_actors();
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
let mut doc2 = new_doc_with_actor(actor2);
|
|
|
|
let wisdom = doc1
|
|
.put_object(&automerge::ROOT, "wisdom", ObjType::List)
|
|
.unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
doc1.splice(
|
|
&wisdom,
|
|
0,
|
|
0,
|
|
vec![
|
|
"to".into(),
|
|
"be".into(),
|
|
"is".into(),
|
|
"to".into(),
|
|
"do".into(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
doc2.splice(
|
|
&wisdom,
|
|
0,
|
|
0,
|
|
vec![
|
|
"to".into(),
|
|
"do".into(),
|
|
"is".into(),
|
|
"to".into(),
|
|
"be".into(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc1,
|
|
map! {
|
|
"wisdom" => {list![
|
|
{"to"},
|
|
{"do"},
|
|
{"is"},
|
|
{"to"},
|
|
{"be"},
|
|
{"to"},
|
|
{"be"},
|
|
{"is"},
|
|
{"to"},
|
|
{"do"},
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn mutliple_insertions_at_same_list_position_with_insertion_by_greater_actor_id() {
|
|
let (actor1, actor2) = sorted_actors();
|
|
assert!(actor2 > actor1);
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
let mut doc2 = new_doc_with_actor(actor2);
|
|
|
|
let list = doc1
|
|
.put_object(&automerge::ROOT, "list", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list, 0, "two").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
doc2.insert(&list, 0, "one").unwrap();
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"list" => { list![
|
|
{ "one" },
|
|
{ "two" },
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn mutliple_insertions_at_same_list_position_with_insertion_by_lesser_actor_id() {
|
|
let (actor2, actor1) = sorted_actors();
|
|
assert!(actor2 < actor1);
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
let mut doc2 = new_doc_with_actor(actor2);
|
|
|
|
let list = doc1
|
|
.put_object(&automerge::ROOT, "list", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list, 0, "two").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
|
|
doc2.insert(&list, 0, "one").unwrap();
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"list" => { list![
|
|
{ "one" },
|
|
{ "two" },
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn insertion_consistent_with_causality() {
|
|
let mut doc1 = new_doc();
|
|
let mut doc2 = new_doc();
|
|
|
|
let list = doc1
|
|
.put_object(&automerge::ROOT, "list", ObjType::List)
|
|
.unwrap();
|
|
doc1.insert(&list, 0, "four").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc2.insert(&list, 0, "three").unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
doc1.insert(&list, 0, "two").unwrap();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc2.insert(&list, 0, "one").unwrap();
|
|
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"list" => { list![
|
|
{"one"},
|
|
{"two"},
|
|
{"three" },
|
|
{"four"},
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn save_and_restore_empty() {
|
|
let mut doc = new_doc();
|
|
let loaded = Automerge::load(&doc.save()).unwrap();
|
|
|
|
assert_doc!(&loaded, map! {});
|
|
}
|
|
|
|
#[test]
|
|
fn save_restore_complex() {
|
|
let mut doc1 = new_doc();
|
|
let todos = doc1
|
|
.put_object(&automerge::ROOT, "todos", ObjType::List)
|
|
.unwrap();
|
|
|
|
let first_todo = doc1.insert_object(&todos, 0, ObjType::Map).unwrap();
|
|
doc1.put(&first_todo, "title", "water plants").unwrap();
|
|
doc1.put(&first_todo, "done", false).unwrap();
|
|
|
|
let mut doc2 = new_doc();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc2.put(&first_todo, "title", "weed plants").unwrap();
|
|
|
|
doc1.put(&first_todo, "title", "kill plants").unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
let reloaded = Automerge::load(&doc1.save()).unwrap();
|
|
|
|
assert_doc!(
|
|
&reloaded,
|
|
map! {
|
|
"todos" => {list![
|
|
{map!{
|
|
"title" => {
|
|
"weed plants",
|
|
"kill plants",
|
|
},
|
|
"done" => {false},
|
|
}}
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn handle_repeated_out_of_order_changes() -> Result<(), automerge::AutomergeError> {
|
|
let mut doc1 = new_doc();
|
|
let list = doc1.put_object(ROOT, "list", ObjType::List)?;
|
|
doc1.insert(&list, 0, "a")?;
|
|
let mut doc2 = doc1.fork();
|
|
doc1.insert(&list, 1, "b")?;
|
|
doc1.commit();
|
|
doc1.insert(&list, 2, "c")?;
|
|
doc1.commit();
|
|
doc1.insert(&list, 3, "d")?;
|
|
doc1.commit();
|
|
let changes = doc1
|
|
.get_changes(&[])
|
|
.unwrap()
|
|
.into_iter()
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
doc2.apply_changes(changes[2..].to_vec())?;
|
|
doc2.apply_changes(changes[2..].to_vec())?;
|
|
doc2.apply_changes(changes)?;
|
|
assert_eq!(doc1.save(), doc2.save());
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn save_restore_complex_transactional() {
|
|
let mut doc1 = Automerge::new();
|
|
let first_todo = doc1
|
|
.transact::<_, _, automerge::AutomergeError>(|d| {
|
|
let todos = d.put_object(&automerge::ROOT, "todos", ObjType::List)?;
|
|
let first_todo = d.insert_object(&todos, 0, ObjType::Map)?;
|
|
d.put(&first_todo, "title", "water plants")?;
|
|
d.put(&first_todo, "done", false)?;
|
|
Ok(first_todo)
|
|
})
|
|
.unwrap()
|
|
.result;
|
|
|
|
let mut doc2 = Automerge::new();
|
|
doc2.merge(&mut doc1).unwrap();
|
|
doc2.transact::<_, _, automerge::AutomergeError>(|tx| {
|
|
tx.put(&first_todo, "title", "weed plants")?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
|
|
doc1.transact::<_, _, automerge::AutomergeError>(|tx| {
|
|
tx.put(&first_todo, "title", "kill plants")?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
let reloaded = Automerge::load(&doc1.save()).unwrap();
|
|
|
|
assert_doc!(
|
|
&reloaded,
|
|
map! {
|
|
"todos" => {list![
|
|
{map!{
|
|
"title" => {
|
|
"weed plants",
|
|
"kill plants",
|
|
},
|
|
"done" => {false},
|
|
}}
|
|
]}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn list_counter_del() -> Result<(), automerge::AutomergeError> {
|
|
let mut v = vec![ActorId::random(), ActorId::random(), ActorId::random()];
|
|
v.sort();
|
|
let actor1 = v[0].clone();
|
|
let actor2 = v[1].clone();
|
|
let actor3 = v[2].clone();
|
|
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
|
|
let list = doc1.put_object(ROOT, "list", ObjType::List)?;
|
|
doc1.insert(&list, 0, "a")?;
|
|
doc1.insert(&list, 1, "b")?;
|
|
doc1.insert(&list, 2, "c")?;
|
|
|
|
let mut doc2 = AutoCommit::load(&doc1.save())?;
|
|
doc2.set_actor(actor2);
|
|
|
|
let mut doc3 = AutoCommit::load(&doc1.save())?;
|
|
doc3.set_actor(actor3);
|
|
|
|
doc1.put(&list, 1, ScalarValue::counter(0))?;
|
|
doc2.put(&list, 1, ScalarValue::counter(10))?;
|
|
doc3.put(&list, 1, ScalarValue::counter(100))?;
|
|
|
|
doc1.put(&list, 2, ScalarValue::counter(0))?;
|
|
doc2.put(&list, 2, ScalarValue::counter(10))?;
|
|
doc3.put(&list, 2, 100)?;
|
|
|
|
doc1.increment(&list, 1, 1)?;
|
|
doc1.increment(&list, 2, 1)?;
|
|
|
|
doc1.merge(&mut doc2).unwrap();
|
|
doc1.merge(&mut doc3).unwrap();
|
|
|
|
assert_obj!(
|
|
doc1.document(),
|
|
&automerge::ROOT,
|
|
"list",
|
|
list![
|
|
{
|
|
"a",
|
|
},
|
|
{
|
|
ScalarValue::counter(1),
|
|
ScalarValue::counter(10),
|
|
ScalarValue::counter(100)
|
|
},
|
|
{
|
|
ScalarValue::Int(100),
|
|
ScalarValue::counter(1),
|
|
ScalarValue::counter(10),
|
|
}
|
|
]
|
|
);
|
|
|
|
doc1.increment(&list, 1, 1)?;
|
|
doc1.increment(&list, 2, 1)?;
|
|
|
|
assert_obj!(
|
|
doc1.document(),
|
|
&automerge::ROOT,
|
|
"list",
|
|
list![
|
|
{
|
|
"a",
|
|
},
|
|
{
|
|
ScalarValue::counter(2),
|
|
ScalarValue::counter(11),
|
|
ScalarValue::counter(101)
|
|
},
|
|
{
|
|
ScalarValue::counter(2),
|
|
ScalarValue::counter(11),
|
|
}
|
|
]
|
|
);
|
|
|
|
doc1.delete(&list, 2)?;
|
|
|
|
assert_eq!(doc1.length(&list), 2);
|
|
|
|
let doc4 = AutoCommit::load(&doc1.save())?;
|
|
|
|
assert_eq!(doc4.length(&list), 2);
|
|
|
|
doc1.delete(&list, 1)?;
|
|
|
|
assert_eq!(doc1.length(&list), 1);
|
|
|
|
let doc5 = AutoCommit::load(&doc1.save())?;
|
|
|
|
assert_eq!(doc5.length(&list), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn observe_counter_change_application() {
|
|
let mut doc = AutoCommit::new();
|
|
doc.put(ROOT, "counter", ScalarValue::counter(1)).unwrap();
|
|
doc.increment(ROOT, "counter", 2).unwrap();
|
|
doc.increment(ROOT, "counter", 5).unwrap();
|
|
let changes = doc.get_changes(&[]).unwrap().into_iter().cloned();
|
|
|
|
let mut doc = AutoCommit::new().with_observer(VecOpObserver::default());
|
|
doc.apply_changes(changes).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn increment_non_counter_map() {
|
|
let mut doc = AutoCommit::new();
|
|
// can't increment nothing
|
|
assert!(matches!(
|
|
doc.increment(ROOT, "nothing", 2),
|
|
Err(AutomergeError::MissingCounter)
|
|
));
|
|
|
|
// can't increment a non-counter
|
|
doc.put(ROOT, "non-counter", "mystring").unwrap();
|
|
assert!(matches!(
|
|
doc.increment(ROOT, "non-counter", 2),
|
|
Err(AutomergeError::MissingCounter)
|
|
));
|
|
|
|
// can increment a counter still
|
|
doc.put(ROOT, "counter", ScalarValue::counter(1)).unwrap();
|
|
assert!(matches!(doc.increment(ROOT, "counter", 2), Ok(())));
|
|
|
|
// can increment a counter that is part of a conflict
|
|
let mut doc1 = AutoCommit::new();
|
|
doc1.set_actor(ActorId::from([1]));
|
|
let mut doc2 = AutoCommit::new();
|
|
doc2.set_actor(ActorId::from([2]));
|
|
|
|
doc1.put(ROOT, "key", ScalarValue::counter(1)).unwrap();
|
|
doc2.put(ROOT, "key", "mystring").unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert!(matches!(doc1.increment(ROOT, "key", 2), Ok(())));
|
|
}
|
|
|
|
#[test]
|
|
fn increment_non_counter_list() {
|
|
let mut doc = AutoCommit::new();
|
|
let list = doc.put_object(ROOT, "list", ObjType::List).unwrap();
|
|
|
|
// can't increment a non-counter
|
|
doc.insert(&list, 0, "mystring").unwrap();
|
|
assert!(matches!(
|
|
doc.increment(&list, 0, 2),
|
|
Err(AutomergeError::MissingCounter)
|
|
));
|
|
|
|
// can increment a counter
|
|
doc.insert(&list, 0, ScalarValue::counter(1)).unwrap();
|
|
assert!(matches!(doc.increment(&list, 0, 2), Ok(())));
|
|
|
|
// can increment a counter that is part of a conflict
|
|
let mut doc1 = AutoCommit::new();
|
|
doc1.set_actor(ActorId::from([1]));
|
|
let list = doc1.put_object(ROOT, "list", ObjType::List).unwrap();
|
|
doc1.insert(&list, 0, ()).unwrap();
|
|
let mut doc2 = doc1.fork();
|
|
doc2.set_actor(ActorId::from([2]));
|
|
|
|
doc1.put(&list, 0, ScalarValue::counter(1)).unwrap();
|
|
doc2.put(&list, 0, "mystring").unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
|
|
assert!(matches!(doc1.increment(&list, 0, 2), Ok(())));
|
|
}
|
|
|
|
#[test]
|
|
fn test_local_inc_in_map() {
|
|
let mut v = vec![ActorId::random(), ActorId::random(), ActorId::random()];
|
|
v.sort();
|
|
let actor1 = v[0].clone();
|
|
let actor2 = v[1].clone();
|
|
let actor3 = v[2].clone();
|
|
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
doc1.put(&automerge::ROOT, "hello", "world").unwrap();
|
|
|
|
let mut doc2 = AutoCommit::load(&doc1.save()).unwrap();
|
|
doc2.set_actor(actor2);
|
|
|
|
let mut doc3 = AutoCommit::load(&doc1.save()).unwrap();
|
|
doc3.set_actor(actor3);
|
|
|
|
doc1.put(ROOT, "cnt", 20_u64).unwrap();
|
|
doc2.put(ROOT, "cnt", ScalarValue::counter(0)).unwrap();
|
|
doc3.put(ROOT, "cnt", ScalarValue::counter(10)).unwrap();
|
|
doc1.merge(&mut doc2).unwrap();
|
|
doc1.merge(&mut doc3).unwrap();
|
|
|
|
assert_doc! {doc1.document(), map!{
|
|
"cnt" => {
|
|
20_u64,
|
|
ScalarValue::counter(0),
|
|
ScalarValue::counter(10),
|
|
},
|
|
"hello" => {"world"},
|
|
}};
|
|
|
|
doc1.increment(ROOT, "cnt", 5).unwrap();
|
|
|
|
assert_doc! {doc1.document(), map!{
|
|
"cnt" => {
|
|
ScalarValue::counter(5),
|
|
ScalarValue::counter(15),
|
|
},
|
|
"hello" => {"world"},
|
|
}};
|
|
let mut doc4 = AutoCommit::load(&doc1.save()).unwrap();
|
|
assert_eq!(doc4.save(), doc1.save());
|
|
}
|
|
|
|
#[test]
|
|
fn test_merging_test_conflicts_then_saving_and_loading() {
|
|
let (actor1, actor2) = sorted_actors();
|
|
|
|
let mut doc1 = new_doc_with_actor(actor1);
|
|
let text = doc1.put_object(ROOT, "text", ObjType::Text).unwrap();
|
|
doc1.splice_text(&text, 0, 0, "hello").unwrap();
|
|
|
|
let mut doc2 = AutoCommit::load(&doc1.save()).unwrap();
|
|
doc2.set_actor(actor2);
|
|
|
|
assert_doc! {&doc2, map!{
|
|
"text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"o"}]},
|
|
}};
|
|
|
|
doc2.splice_text(&text, 4, 1, "").unwrap();
|
|
doc2.splice_text(&text, 4, 0, "!").unwrap();
|
|
doc2.splice_text(&text, 5, 0, " ").unwrap();
|
|
doc2.splice_text(&text, 6, 0, "world").unwrap();
|
|
|
|
assert_doc!(
|
|
&doc2,
|
|
map! {
|
|
"text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"!"}, {" "}, {"w"} , {"o"}, {"r"}, {"l"}, {"d"}]}
|
|
}
|
|
);
|
|
|
|
let doc3 = AutoCommit::load(&doc2.save()).unwrap();
|
|
|
|
assert_doc!(
|
|
&doc3,
|
|
map! {
|
|
"text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"!"}, {" "}, {"w"} , {"o"}, {"r"}, {"l"}, {"d"}]}
|
|
}
|
|
);
|
|
}
|
|
|
|
/// Surfaces an error which occurs when loading a document with a change which only contains a
|
|
/// delete operation. In this case the delete operation doesn't appear in the encoded document
|
|
/// operations except as a succ, so the max_op was calculated incorectly.
|
|
#[test]
|
|
fn delete_only_change() {
|
|
let actor = automerge::ActorId::random();
|
|
let mut doc1 = automerge::Automerge::new().with_actor(actor.clone());
|
|
let list = doc1
|
|
.transact::<_, _, automerge::AutomergeError>(|d| {
|
|
let l = d.put_object(&automerge::ROOT, "list", ObjType::List)?;
|
|
d.insert(&l, 0, 'a')?;
|
|
Ok(l)
|
|
})
|
|
.unwrap()
|
|
.result;
|
|
|
|
let mut doc2 = automerge::Automerge::load(&doc1.save())
|
|
.unwrap()
|
|
.with_actor(actor.clone());
|
|
doc2.transact::<_, _, automerge::AutomergeError>(|d| d.delete(&list, 0))
|
|
.unwrap();
|
|
|
|
let mut doc3 = automerge::Automerge::load(&doc2.save())
|
|
.unwrap()
|
|
.with_actor(actor.clone());
|
|
doc3.transact(|d| d.insert(&list, 0, "b")).unwrap();
|
|
|
|
let doc4 = automerge::Automerge::load(&doc3.save())
|
|
.unwrap()
|
|
.with_actor(actor);
|
|
|
|
let changes = doc4.get_changes(&[]).unwrap();
|
|
assert_eq!(changes.len(), 3);
|
|
let c = changes[2];
|
|
assert_eq!(c.start_op().get(), 4);
|
|
}
|
|
|
|
/// Expose an error where a document which contained a create operation without any subsequent
|
|
/// operations targeting the created object did not load the object correctly.
|
|
#[test]
|
|
fn save_and_reload_create_object() {
|
|
let actor = automerge::ActorId::random();
|
|
let mut doc = automerge::Automerge::new().with_actor(actor);
|
|
|
|
// Create a change containing an object but no other operations
|
|
let list = doc
|
|
.transact::<_, _, automerge::AutomergeError>(|d| {
|
|
d.put_object(&automerge::ROOT, "foo", ObjType::List)
|
|
})
|
|
.unwrap()
|
|
.result;
|
|
|
|
// Save and load the change
|
|
let mut doc2 = automerge::Automerge::load(&doc.save()).unwrap();
|
|
doc2.transact::<_, _, automerge::AutomergeError>(|d| {
|
|
d.insert(&list, 0, 1_u64)?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
|
|
assert_doc!(&doc2, map! {"foo" => { list! [{1_u64}]}});
|
|
|
|
let _doc3 = automerge::Automerge::load(&doc2.save()).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_compressed_changes() {
|
|
let mut doc = new_doc();
|
|
// crate::storage::DEFLATE_MIN_SIZE is 250, so this should trigger compression
|
|
doc.put(ROOT, "bytes", ScalarValue::Bytes(vec![10; 300]))
|
|
.unwrap();
|
|
let mut change = doc.get_last_local_change().unwrap().clone();
|
|
let uncompressed = change.raw_bytes().to_vec();
|
|
assert!(uncompressed.len() > 256);
|
|
let compressed = change.bytes().to_vec();
|
|
assert!(compressed.len() < uncompressed.len());
|
|
|
|
let reloaded = automerge::Change::try_from(&compressed[..]).unwrap();
|
|
assert_eq!(change.raw_bytes(), reloaded.raw_bytes());
|
|
}
|
|
|
|
#[test]
|
|
fn test_compressed_doc_cols() {
|
|
// In this test, the keyCtr column is long enough for deflate compression to kick in, but the
|
|
// keyStr column is short. Thus, the deflate bit gets set for keyCtr but not for keyStr.
|
|
// When checking whether the columns appear in ascending order, we must ignore the deflate bit.
|
|
let mut doc = new_doc();
|
|
let list = doc.put_object(ROOT, "list", ObjType::List).unwrap();
|
|
let mut expected = Vec::new();
|
|
for i in 0..200 {
|
|
doc.insert(&list, i, i as u64).unwrap();
|
|
expected.push(i as u64);
|
|
}
|
|
let uncompressed = doc.save_nocompress();
|
|
let compressed = doc.save();
|
|
assert!(compressed.len() < uncompressed.len());
|
|
let loaded = automerge::Automerge::load(&compressed).unwrap();
|
|
assert_doc!(
|
|
&loaded,
|
|
map! {
|
|
"list" => { expected}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_change_encoding_expanded_change_round_trip() {
|
|
let change_bytes: Vec<u8> = vec![
|
|
0x85, 0x6f, 0x4a, 0x83, // magic bytes
|
|
0xb2, 0x98, 0x9e, 0xa9, // checksum
|
|
1, 61, 0, 2, 0x12, 0x34, // chunkType: change, length, deps, actor '1234'
|
|
1, 1, 252, 250, 220, 255, 5, // seq, startOp, time
|
|
14, 73, 110, 105, 116, 105, 97, 108, 105, 122, 97, 116, 105, 111,
|
|
110, // message: 'Initialization'
|
|
0, 6, // actor list, column count
|
|
0x15, 3, 0x34, 1, 0x42, 2, // keyStr, insert, action
|
|
0x56, 2, 0x57, 1, 0x70, 2, // valLen, valRaw, predNum
|
|
0x7f, 1, 0x78, // keyStr: 'x'
|
|
1, // insert: false
|
|
0x7f, 1, // action: set
|
|
0x7f, 19, // valLen: 1 byte of type uint
|
|
1, // valRaw: 1
|
|
0x7f, 0, // predNum: 0
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // 10 trailing bytes
|
|
];
|
|
let change = automerge::Change::try_from(&change_bytes[..]).unwrap();
|
|
assert_eq!(change.raw_bytes(), change_bytes);
|
|
let expanded = automerge::ExpandedChange::from(&change);
|
|
let unexpanded: automerge::Change = expanded.try_into().unwrap();
|
|
assert_eq!(unexpanded.raw_bytes(), change_bytes);
|
|
}
|
|
|
|
#[test]
|
|
fn save_and_load_incremented_counter() {
|
|
let mut doc = AutoCommit::new();
|
|
doc.put(ROOT, "counter", ScalarValue::counter(1)).unwrap();
|
|
doc.commit();
|
|
doc.increment(ROOT, "counter", 1).unwrap();
|
|
doc.commit();
|
|
let changes1: Vec<Change> = doc.get_changes(&[]).unwrap().into_iter().cloned().collect();
|
|
let json: Vec<_> = changes1
|
|
.iter()
|
|
.map(|c| serde_json::to_string(&c.decode()).unwrap())
|
|
.collect();
|
|
let changes2: Vec<Change> = json
|
|
.iter()
|
|
.map(|j| serde_json::from_str::<ExpandedChange>(j).unwrap().into())
|
|
.collect();
|
|
|
|
assert_eq!(changes1, changes2);
|
|
}
|
|
|
|
#[test]
|
|
fn load_incremental_with_corrupted_tail() {
|
|
let mut doc = AutoCommit::new();
|
|
doc.put(ROOT, "key", ScalarValue::Str("value".into()))
|
|
.unwrap();
|
|
doc.commit();
|
|
let mut bytes = doc.save();
|
|
bytes.extend_from_slice(&[1, 2, 3, 4]);
|
|
let mut loaded = Automerge::new();
|
|
let loaded_len = loaded.load_incremental(&bytes).unwrap();
|
|
assert_eq!(loaded_len, 1);
|
|
assert_doc!(
|
|
&loaded,
|
|
map! {
|
|
"key" => { "value" },
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn load_doc_with_deleted_objects() {
|
|
// Reproduces an issue where a document with deleted objects failed to load
|
|
let mut doc = AutoCommit::new();
|
|
doc.put_object(ROOT, "list", ObjType::List).unwrap();
|
|
doc.put_object(ROOT, "text", ObjType::Text).unwrap();
|
|
doc.put_object(ROOT, "map", ObjType::Map).unwrap();
|
|
doc.put_object(ROOT, "table", ObjType::Table).unwrap();
|
|
doc.delete(&ROOT, "list").unwrap();
|
|
doc.delete(&ROOT, "text").unwrap();
|
|
doc.delete(&ROOT, "map").unwrap();
|
|
doc.delete(&ROOT, "table").unwrap();
|
|
let saved = doc.save();
|
|
Automerge::load(&saved).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn insert_after_many_deletes() {
|
|
let mut doc = AutoCommit::new();
|
|
let obj = doc.put_object(&ROOT, "object", ObjType::Map).unwrap();
|
|
for i in 0..100 {
|
|
doc.put(&obj, i.to_string(), i).unwrap();
|
|
doc.delete(&obj, i.to_string()).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn simple_bad_saveload() {
|
|
let mut doc = Automerge::new();
|
|
doc.transact::<_, _, AutomergeError>(|d| {
|
|
d.put(ROOT, "count", 0)?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
|
|
doc.transact::<_, _, AutomergeError>(|_d| Ok(())).unwrap();
|
|
|
|
doc.transact::<_, _, AutomergeError>(|d| {
|
|
d.put(ROOT, "count", 0)?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
|
|
let bytes = doc.save();
|
|
Automerge::load(&bytes).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn ops_on_wrong_objets() -> Result<(), AutomergeError> {
|
|
let mut doc = AutoCommit::new();
|
|
let list = doc.put_object(&automerge::ROOT, "list", ObjType::List)?;
|
|
doc.insert(&list, 0, "a")?;
|
|
doc.insert(&list, 1, "b")?;
|
|
let e1 = doc.put(&list, "a", "AAA");
|
|
assert_eq!(e1, Err(AutomergeError::InvalidOp(ObjType::List)));
|
|
let e2 = doc.splice_text(&list, 0, 0, "hello world");
|
|
assert_eq!(e2, Err(AutomergeError::InvalidOp(ObjType::List)));
|
|
let map = doc.put_object(&automerge::ROOT, "map", ObjType::Map)?;
|
|
doc.put(&map, "a", "AAA")?;
|
|
doc.put(&map, "b", "BBB")?;
|
|
let e3 = doc.insert(&map, 0, "b");
|
|
assert_eq!(e3, Err(AutomergeError::InvalidOp(ObjType::Map)));
|
|
let e4 = doc.splice_text(&map, 0, 0, "hello world");
|
|
assert_eq!(e4, Err(AutomergeError::InvalidOp(ObjType::Map)));
|
|
let text = doc.put_object(&automerge::ROOT, "text", ObjType::Text)?;
|
|
doc.splice_text(&text, 0, 0, "hello world")?;
|
|
let e5 = doc.put(&text, "a", "AAA");
|
|
assert_eq!(e5, Err(AutomergeError::InvalidOp(ObjType::Text)));
|
|
//let e6 = doc.insert(&text, 0, "b");
|
|
//assert_eq!(e6, Err(AutomergeError::InvalidOp(ObjType::Text)));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn fuzz_crashers() {
|
|
let paths = fs::read_dir("./tests/fuzz-crashers").unwrap();
|
|
|
|
for path in paths {
|
|
// uncomment this line to figure out which fixture is crashing:
|
|
// println!("{:?}", path.as_ref().unwrap().path().display());
|
|
let bytes = fs::read(path.as_ref().unwrap().path());
|
|
let res = Automerge::load(&bytes.unwrap());
|
|
assert!(res.is_err());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn negative_64() {
|
|
let mut doc = Automerge::new();
|
|
assert!(doc.transact(|d| { d.put(ROOT, "a", -64_i64) }).is_ok())
|
|
}
|
|
|
|
#[test]
|
|
fn bad_change_on_optree_node_boundary() {
|
|
let mut doc = Automerge::new();
|
|
doc.transact::<_, _, AutomergeError>(|d| {
|
|
d.put(ROOT, "a", "z")?;
|
|
d.put(ROOT, "b", 0)?;
|
|
d.put(ROOT, "c", 0)?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
let iterations = 15_u64;
|
|
for i in 0_u64..iterations {
|
|
doc.transact::<_, _, AutomergeError>(|d| {
|
|
let s = "a".repeat(i as usize);
|
|
d.put(ROOT, "a", s)?;
|
|
d.put(ROOT, "b", i + 1)?;
|
|
d.put(ROOT, "c", i + 1)?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
}
|
|
let mut doc2 = Automerge::load(doc.save().as_slice()).unwrap();
|
|
doc.transact::<_, _, AutomergeError>(|d| {
|
|
let i = iterations + 2;
|
|
let s = "a".repeat(i as usize);
|
|
d.put(ROOT, "a", s)?;
|
|
d.put(ROOT, "b", i)?;
|
|
d.put(ROOT, "c", i)?;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
let change = doc.get_changes(&doc2.get_heads()).unwrap();
|
|
doc2.apply_changes(change.into_iter().cloned().collect::<Vec<_>>())
|
|
.unwrap();
|
|
Automerge::load(doc2.save().as_slice()).unwrap();
|
|
}
|