automerge/automerge/tests/test.rs
2022-09-29 15:59:08 -05:00

1345 lines
36 KiB
Rust

use automerge::transaction::Transactable;
use automerge::{
ActorId, AutoCommit, Automerge, AutomergeError, Change, ExpandedChange, ObjType, ScalarValue,
VecOpObserver, ROOT,
};
// set up logging for all the tests
use test_log::test;
mod helpers;
#[allow(unused_imports)]
use helpers::{
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
map! {
"foo" => { "bar" },
"hello" => { "world" },
}
);
doc2.merge(&mut doc1).unwrap();
assert_doc!(
doc2.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
map! {
"birds" => {list![
{"blackbird"},
{"goldfinch"},
]}
}
);
assert_doc!(
doc1.document(),
map! {
"birds" => {list![
{ "blackbird" },
{ "starling" },
{ "goldfinch" },
]}
}
);
doc1.merge(&mut doc2).unwrap();
assert_doc!(
doc1.document(),
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.document(),
map! {
"birds" => {list![
{ "blackbird" },
{ "starling" }
]}
}
);
doc2.merge(&mut doc1).unwrap();
assert_doc!(
doc2.document(),
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.document(),
map! {
"birds" => {list![
{ "albatross" },
{ "cormorant" }
]}
}
);
doc2.merge(&mut doc1).unwrap();
assert_doc!(
doc2.document(),
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.document(),
&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.document(),
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.document(),
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.document(),
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.document(),
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.document(),
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, 0, 0, "hello".chars().map(|c| c.to_string().into()))
.unwrap();
let mut doc2 = AutoCommit::load(&doc1.save()).unwrap();
doc2.set_actor(actor2);
assert_doc! {doc2.document(), map!{
"text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"o"}]},
}};
doc2.splice(&text, 4, 1, Vec::new()).unwrap();
doc2.splice(&text, 4, 0, vec!["!".into()]).unwrap();
doc2.splice(&text, 5, 0, vec![" ".into()]).unwrap();
doc2.splice(&text, 6, 0, "world".chars().map(|c| c.into()))
.unwrap();
assert_doc!(
doc2.document(),
map! {
"text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"!"}, {" "}, {"w"} , {"o"}, {"r"}, {"l"}, {"d"}]}
}
);
let mut doc3 = AutoCommit::load(&doc2.save()).unwrap();
assert_doc!(
doc3.document(),
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();
}