987263b25d
* Add SortedVec struct to ensure preds are sorted * Add into_iter for sortedvec
690 lines
20 KiB
Rust
690 lines
20 KiB
Rust
extern crate automerge_backend;
|
|
use std::{convert::TryInto, num::NonZeroU32};
|
|
|
|
use amp::{RootDiff, SortedVec};
|
|
use automerge_backend::{Backend, Change};
|
|
use automerge_protocol as amp;
|
|
use automerge_protocol::{
|
|
ActorId, Diff, DiffEdit, ElementId, ListDiff, MapDiff, ObjectId, Op, Patch, ScalarValue,
|
|
};
|
|
use maplit::hashmap;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn test_include_most_recent_value_for_key() {
|
|
let actor: ActorId = "ec28cfbcdb9e4f32ad24b3c776e651b0".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
deps: Vec::new(),
|
|
message: None,
|
|
hash: None,
|
|
operations: vec![Op {
|
|
action: amp::OpType::Set("magpie".into()),
|
|
key: "bird".into(),
|
|
obj: ObjectId::Root,
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
}],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let change2: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 2,
|
|
start_op: 2,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: vec![change1.hash],
|
|
operations: vec![Op {
|
|
obj: ObjectId::Root,
|
|
action: amp::OpType::Set("blackbird".into()),
|
|
key: "bird".into(),
|
|
pred: vec![actor.op_id_at(1)].into(),
|
|
insert: false,
|
|
}],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
actor: None,
|
|
seq: None,
|
|
max_op: 2,
|
|
pending_changes: 0,
|
|
clock: hashmap! {
|
|
actor.clone() => 2,
|
|
},
|
|
deps: vec![change2.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"bird".into() => hashmap!{
|
|
actor.op_id_at(2) => Diff::Value("blackbird".into()),
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1, change2]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_includes_conflicting_values_for_key() {
|
|
let actor1: ActorId = "111111".try_into().unwrap();
|
|
let actor2: ActorId = "222222".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor1.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
deps: Vec::new(),
|
|
message: None,
|
|
hash: None,
|
|
operations: vec![Op {
|
|
action: amp::OpType::Set("magpie".into()),
|
|
obj: ObjectId::Root,
|
|
key: "bird".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
}],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let change2: Change = amp::Change {
|
|
actor_id: actor2.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![Op {
|
|
action: amp::OpType::Set("blackbird".into()),
|
|
key: "bird".into(),
|
|
obj: ObjectId::Root,
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
}],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
clock: hashmap! {
|
|
actor1.clone() => 1,
|
|
actor2.clone() => 1,
|
|
},
|
|
max_op: 1,
|
|
pending_changes: 0,
|
|
seq: None,
|
|
actor: None,
|
|
deps: vec![change1.hash, change2.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"bird".into() => hashmap!{
|
|
actor1.op_id_at(1) => Diff::Value("magpie".into()),
|
|
actor2.op_id_at(1) => Diff::Value("blackbird".into()),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1, change2]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_handles_counter_increment_at_keys_in_a_map() {
|
|
let actor: ActorId = "46c92088e4484ae5945dc63bf606a4a5".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![Op {
|
|
action: amp::OpType::Set(ScalarValue::Counter(1)),
|
|
obj: ObjectId::Root,
|
|
key: "counter".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
}],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let change2: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 2,
|
|
start_op: 2,
|
|
time: 0,
|
|
deps: vec![change1.hash],
|
|
message: None,
|
|
hash: None,
|
|
operations: vec![Op {
|
|
action: amp::OpType::Inc(2),
|
|
obj: ObjectId::Root,
|
|
key: "counter".into(),
|
|
pred: vec![actor.op_id_at(1)].into(),
|
|
insert: false,
|
|
}],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
seq: None,
|
|
actor: None,
|
|
clock: hashmap! {
|
|
actor.clone() => 2,
|
|
},
|
|
max_op: 2,
|
|
pending_changes: 0,
|
|
deps: vec![change2.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"counter".into() => hashmap!{
|
|
actor.op_id_at(1) => Diff::Value(ScalarValue::Counter(3))
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1, change2]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_creates_nested_maps() {
|
|
let actor: ActorId = "06148f9422cb40579fd02f1975c34a51".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![
|
|
Op {
|
|
action: amp::OpType::Make(amp::ObjType::Map),
|
|
obj: ObjectId::Root,
|
|
key: "birds".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
Op {
|
|
action: amp::OpType::Set(ScalarValue::F64(3.0)),
|
|
key: "wrens".into(),
|
|
obj: ObjectId::from(actor.op_id_at(1)),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let change2: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 2,
|
|
start_op: 3,
|
|
time: 0,
|
|
deps: vec![change1.hash],
|
|
message: None,
|
|
hash: None,
|
|
operations: vec![
|
|
Op {
|
|
obj: ObjectId::from(actor.op_id_at(1)),
|
|
action: amp::OpType::Del(NonZeroU32::new(1).unwrap()),
|
|
key: "wrens".into(),
|
|
pred: vec![actor.op_id_at(2)].into(),
|
|
insert: false,
|
|
},
|
|
Op {
|
|
obj: ObjectId::from(actor.op_id_at(1)),
|
|
action: amp::OpType::Set(ScalarValue::F64(15.0)),
|
|
key: "sparrows".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
clock: hashmap! {
|
|
actor.clone() => 2,
|
|
},
|
|
actor: None,
|
|
seq: None,
|
|
max_op: 4,
|
|
pending_changes: 0,
|
|
deps: vec![change2.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"birds".into() => hashmap!{
|
|
actor.op_id_at(1) => Diff::Map(MapDiff{
|
|
object_id: ObjectId::from(actor.op_id_at(1)),
|
|
props: hashmap!{
|
|
"sparrows".into() => hashmap!{
|
|
actor.op_id_at(4) => Diff::Value(ScalarValue::F64(15.0))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1, change2]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_lists() {
|
|
let actor: ActorId = "90bf7df682f747fa82ac604b35010906".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![
|
|
Op {
|
|
action: amp::OpType::Make(amp::ObjType::List),
|
|
obj: ObjectId::Root,
|
|
key: "birds".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
Op {
|
|
obj: ObjectId::from(actor.op_id_at(1)),
|
|
action: amp::OpType::Set("chaffinch".into()),
|
|
key: ElementId::Head.into(),
|
|
insert: true,
|
|
pred: SortedVec::new(),
|
|
},
|
|
],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
clock: hashmap! {
|
|
actor.clone() => 1,
|
|
},
|
|
max_op: 2,
|
|
pending_changes: 0,
|
|
actor: None,
|
|
seq: None,
|
|
deps: vec![change1.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"birds".into() => hashmap!{
|
|
actor.op_id_at(1) => Diff::List(ListDiff{
|
|
object_id: ObjectId::from(actor.op_id_at(1)),
|
|
edits: vec![DiffEdit::SingleElementInsert {
|
|
index: 0,
|
|
elem_id: actor.op_id_at(2).into(),
|
|
op_id: actor.op_id_at(2),
|
|
value: Diff::Value("chaffinch".into()),
|
|
}],
|
|
})
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_includes_latests_state_of_list() {
|
|
let actor: ActorId = "6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![
|
|
Op {
|
|
action: amp::OpType::Make(amp::ObjType::List),
|
|
obj: ObjectId::Root,
|
|
key: "todos".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
Op {
|
|
action: amp::OpType::Make(amp::ObjType::Map),
|
|
obj: ObjectId::from(actor.op_id_at(1)),
|
|
key: ElementId::Head.into(),
|
|
insert: true,
|
|
pred: SortedVec::new(),
|
|
},
|
|
Op {
|
|
obj: ObjectId::from(actor.op_id_at(2)),
|
|
action: amp::OpType::Set("water plants".into()),
|
|
key: "title".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
Op {
|
|
obj: ObjectId::from(actor.op_id_at(2)),
|
|
action: amp::OpType::Set(false.into()),
|
|
key: "done".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
clock: hashmap! {
|
|
actor.clone() => 1
|
|
},
|
|
max_op: 4,
|
|
pending_changes: 0,
|
|
actor: None,
|
|
seq: None,
|
|
deps: vec![change1.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"todos".into() => hashmap!{
|
|
actor.op_id_at(1) => Diff::List(ListDiff{
|
|
object_id: ObjectId::from(actor.op_id_at(1)),
|
|
edits: vec![DiffEdit::SingleElementInsert{
|
|
index: 0,
|
|
elem_id: actor.op_id_at(2).into(),
|
|
op_id: actor.op_id_at(2),
|
|
value: Diff::Map(MapDiff{
|
|
object_id: actor.op_id_at(2).into(),
|
|
props: hashmap!{
|
|
"title".into() => hashmap!{
|
|
actor.op_id_at(3) => Diff::Value("water plants".into()),
|
|
},
|
|
"done".into() => hashmap!{
|
|
actor.op_id_at(4) => Diff::Value(false.into())
|
|
}
|
|
}
|
|
})
|
|
}],
|
|
})
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_includes_date_objects_at_root() {
|
|
let actor: ActorId = "90f5dd5d4f524e95ad5929e08d1194f1".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![Op {
|
|
obj: ObjectId::Root,
|
|
action: amp::OpType::Set(ScalarValue::Timestamp(1_586_541_033_457)),
|
|
key: "now".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
}],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
clock: hashmap! {
|
|
actor.clone() => 1,
|
|
},
|
|
max_op: 1,
|
|
pending_changes: 0,
|
|
actor: None,
|
|
seq: None,
|
|
deps: vec![change1.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"now".into() => hashmap!{
|
|
actor.op_id_at(1) => Diff::Value(ScalarValue::Timestamp(1_586_541_033_457))
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_includes_date_objects_in_a_list() {
|
|
let actor: ActorId = "08b050f976a249349021a2e63d99c8e8".try_into().unwrap();
|
|
let change1: Change = amp::Change {
|
|
actor_id: actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![
|
|
Op {
|
|
obj: ObjectId::Root,
|
|
action: amp::OpType::Make(amp::ObjType::List),
|
|
key: "list".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
Op {
|
|
obj: ObjectId::from(actor.op_id_at(1)),
|
|
action: amp::OpType::Set(ScalarValue::Timestamp(1_586_541_089_595)),
|
|
key: ElementId::Head.into(),
|
|
insert: true,
|
|
pred: SortedVec::new(),
|
|
},
|
|
],
|
|
extra_bytes: Vec::new(),
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let expected_patch = Patch {
|
|
clock: hashmap! {
|
|
actor.clone() => 1,
|
|
},
|
|
max_op: 2,
|
|
pending_changes: 0,
|
|
actor: None,
|
|
seq: None,
|
|
deps: vec![change1.hash],
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"list".into() => hashmap!{
|
|
actor.op_id_at(1) => Diff::List(ListDiff{
|
|
object_id: ObjectId::from(actor.op_id_at(1)),
|
|
edits: vec![DiffEdit::SingleElementInsert {
|
|
index: 0,
|
|
elem_id: actor.op_id_at(2).into(),
|
|
op_id: actor.op_id_at(2),
|
|
value: Diff::Value(ScalarValue::Timestamp(1_586_541_089_595)),
|
|
}],
|
|
})
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.load_changes(vec![change1]).unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|
|
|
|
#[test]
|
|
fn test_includes_updates_for_conflicting_list_elements() {
|
|
let local_actor = ActorId::random();
|
|
let actor1: ActorId = "da45d93f2b18456f8318c723d1430563".try_into().unwrap();
|
|
let actor2: ActorId = "6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap();
|
|
let local_change = amp::Change {
|
|
actor_id: local_actor.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: Vec::new(),
|
|
operations: vec![
|
|
Op {
|
|
action: amp::OpType::Make(amp::ObjType::List),
|
|
obj: ObjectId::Root,
|
|
key: "list".into(),
|
|
pred: SortedVec::new(),
|
|
insert: false,
|
|
},
|
|
Op {
|
|
action: amp::OpType::Set("local".into()),
|
|
obj: local_actor.op_id_at(1).into(),
|
|
key: amp::ElementId::Head.into(),
|
|
pred: SortedVec::new(),
|
|
insert: true,
|
|
},
|
|
],
|
|
extra_bytes: Vec::new(),
|
|
};
|
|
let binchange: Change = local_change.clone().try_into().unwrap();
|
|
|
|
let remote_change_1: Change = amp::Change {
|
|
actor_id: actor1.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: vec![binchange.hash],
|
|
extra_bytes: Vec::new(),
|
|
operations: vec![Op {
|
|
action: amp::OpType::Set("remote1".into()),
|
|
obj: local_actor.op_id_at(1).into(),
|
|
key: local_actor.op_id_at(2).into(),
|
|
pred: vec![local_actor.op_id_at(2)].into(),
|
|
insert: false,
|
|
}],
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let remote_change_2: Change = amp::Change {
|
|
actor_id: actor2.clone(),
|
|
seq: 1,
|
|
start_op: 1,
|
|
time: 0,
|
|
message: None,
|
|
hash: None,
|
|
deps: vec![binchange.hash],
|
|
extra_bytes: Vec::new(),
|
|
operations: vec![Op {
|
|
action: amp::OpType::Set("remote2".into()),
|
|
obj: local_actor.op_id_at(1).into(),
|
|
key: local_actor.op_id_at(2).into(),
|
|
pred: vec![local_actor.op_id_at(2)].into(),
|
|
insert: false,
|
|
}],
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
|
|
let mut deps = vec![remote_change_2.hash, remote_change_1.hash];
|
|
deps.sort();
|
|
|
|
let expected_patch = Patch {
|
|
clock: hashmap! {
|
|
local_actor.clone() => 1,
|
|
actor1.clone() => 1,
|
|
actor2.clone() => 1,
|
|
},
|
|
max_op: 2,
|
|
actor: None,
|
|
seq: None,
|
|
deps,
|
|
diffs: RootDiff {
|
|
props: hashmap! {
|
|
"list".into() => hashmap!{
|
|
local_actor.op_id_at(1) => Diff::List(ListDiff{
|
|
object_id: ObjectId::from(local_actor.op_id_at(1)),
|
|
edits: vec![
|
|
DiffEdit::SingleElementInsert{
|
|
index: 0,
|
|
elem_id: local_actor.op_id_at(2).into(),
|
|
op_id: actor1.op_id_at(1),
|
|
value: Diff::Value("remote1".into()),
|
|
},
|
|
DiffEdit::Update{
|
|
index: 0,
|
|
op_id: actor2.op_id_at(1),
|
|
value: Diff::Value("remote2".into())
|
|
},
|
|
],
|
|
})
|
|
}
|
|
},
|
|
},
|
|
pending_changes: 0,
|
|
};
|
|
|
|
let mut backend = Backend::new();
|
|
backend.apply_local_change(local_change).unwrap();
|
|
backend
|
|
.load_changes(vec![remote_change_1, remote_change_2])
|
|
.unwrap();
|
|
let patch = backend.get_patch().unwrap();
|
|
assert_eq!(patch, expected_patch)
|
|
}
|