automerge/rust/automerge/tests/test.rs
Conrad Irwin 2cd7427f35 Use our leb128 parser for values
This ensures that values in automerge documents are encoded correctly,
and that no extra data is smuggled in any LEB fields.
2023-02-09 15:46:22 +00:00

1470 lines
40 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());
}
}
fn fixture(name: &str) -> Vec<u8> {
fs::read("./tests/fixtures/".to_owned() + name).unwrap()
}
#[test]
fn overlong_leb() {
// the value metadata says "2", but the LEB is only 1-byte long and there's an extra 0
assert!(Automerge::load(&fixture("counter_value_has_incorrect_meta.automerge")).is_err());
// the LEB is overlong (using 2 bytes where one would have sufficed)
assert!(Automerge::load(&fixture("counter_value_is_overlong.automerge")).is_err());
// the LEB is correct
assert!(Automerge::load(&fixture("counter_value_is_ok.automerge")).is_ok());
}
#[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();
}