Add some diff generation tests
This commit is contained in:
parent
f45edb3dbe
commit
1b73a2a4ac
4 changed files with 243 additions and 49 deletions
automerge-backend/src
|
@ -59,8 +59,8 @@ impl Backend {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
ActorID, Backend, Change, Clock, DataType, Diff, DiffAction, ElementValue, Key, MapType,
|
||||
ObjectID, Operation, Patch, PrimitiveValue, Conflict,
|
||||
ActorID, Backend, Change, Clock, Conflict, DataType, Diff, DiffAction, ElementValue, Key,
|
||||
MapType, ObjectID, Operation, Patch, PrimitiveValue,
|
||||
};
|
||||
|
||||
struct TestCase {
|
||||
|
@ -71,8 +71,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_diffs() {
|
||||
let actor1 = ActorID::random();
|
||||
let actor2 = ActorID::random();
|
||||
let actor1 = ActorID::from_string("actor1".to_string());
|
||||
let actor2 = ActorID::from_string("actor2".to_string());
|
||||
let testcases = vec![
|
||||
TestCase {
|
||||
name: "Assign to key in map",
|
||||
|
@ -149,55 +149,220 @@ mod tests {
|
|||
}],
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
TestCase {
|
||||
name: "should make a conflict on assignment to the same key",
|
||||
changes: vec![
|
||||
Change {
|
||||
actor_id: actor1.clone(),
|
||||
actor_id: ActorID::from_string("actor1".to_string()),
|
||||
seq: 1,
|
||||
dependencies: Clock::empty(),
|
||||
message: None,
|
||||
operations: vec![Operation::Set{
|
||||
operations: vec![Operation::Set {
|
||||
object_id: ObjectID::Root,
|
||||
key: Key("bird".to_string()),
|
||||
value: PrimitiveValue::Str("magpie".to_string()),
|
||||
datatype: None
|
||||
}]
|
||||
datatype: None,
|
||||
}],
|
||||
},
|
||||
Change {
|
||||
actor_id: actor2.clone(),
|
||||
actor_id: ActorID::from_string("actor2".to_string()),
|
||||
seq: 1,
|
||||
dependencies: Clock::empty(),
|
||||
message: None,
|
||||
operations: vec![Operation::Set{
|
||||
operations: vec![Operation::Set {
|
||||
object_id: ObjectID::Root,
|
||||
key: Key("bird".to_string()),
|
||||
value: PrimitiveValue::Str("blackbird".to_string()),
|
||||
datatype: None
|
||||
}]
|
||||
}
|
||||
datatype: None,
|
||||
}],
|
||||
},
|
||||
],
|
||||
expected_patch: Patch {
|
||||
can_undo: false,
|
||||
can_redo: false,
|
||||
clock: Clock::empty().with_dependency(&actor1, 1).with_dependency(&actor2, 1),
|
||||
deps: Clock::empty().with_dependency(&actor1, 1).with_dependency(&actor2, 1),
|
||||
clock: Clock::empty()
|
||||
.with_dependency(&actor1, 1)
|
||||
.with_dependency(&actor2, 1),
|
||||
deps: Clock::empty()
|
||||
.with_dependency(&actor1, 1)
|
||||
.with_dependency(&actor2, 1),
|
||||
diffs: vec![Diff {
|
||||
action: DiffAction::SetMapKey(
|
||||
ObjectID::Root,
|
||||
MapType::Map,
|
||||
Key("bird".to_string()),
|
||||
ElementValue::Primitive(PrimitiveValue::Str("blackbird".to_string())),
|
||||
None
|
||||
None,
|
||||
),
|
||||
conflicts: vec![Conflict{
|
||||
conflicts: vec![Conflict {
|
||||
actor: actor1.clone(),
|
||||
value: ElementValue::Primitive(PrimitiveValue::Str("magpie".to_string())),
|
||||
value: ElementValue::Primitive(PrimitiveValue::Str(
|
||||
"magpie".to_string(),
|
||||
)),
|
||||
datatype: None,
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}],
|
||||
}],
|
||||
},
|
||||
},
|
||||
TestCase {
|
||||
name: "delete a key from a map",
|
||||
changes: vec![
|
||||
Change {
|
||||
actor_id: actor1.clone(),
|
||||
seq: 1,
|
||||
dependencies: Clock::empty(),
|
||||
message: None,
|
||||
operations: vec![Operation::Set {
|
||||
object_id: ObjectID::Root,
|
||||
key: Key("bird".to_string()),
|
||||
value: PrimitiveValue::Str("magpie".to_string()),
|
||||
datatype: None,
|
||||
}],
|
||||
},
|
||||
Change {
|
||||
actor_id: actor1.clone(),
|
||||
seq: 2,
|
||||
dependencies: Clock::empty(),
|
||||
message: None,
|
||||
operations: vec![Operation::Delete {
|
||||
object_id: ObjectID::Root,
|
||||
key: Key("bird".to_string()),
|
||||
}],
|
||||
},
|
||||
],
|
||||
expected_patch: Patch {
|
||||
can_undo: false,
|
||||
can_redo: false,
|
||||
clock: Clock::empty().with_dependency(&actor1, 2),
|
||||
deps: Clock::empty().with_dependency(&actor1, 2),
|
||||
diffs: vec![Diff {
|
||||
action: DiffAction::RemoveMapKey(
|
||||
ObjectID::Root,
|
||||
MapType::Map,
|
||||
Key("bird".to_string()),
|
||||
),
|
||||
conflicts: Vec::new(),
|
||||
}],
|
||||
},
|
||||
},
|
||||
TestCase {
|
||||
name: "create nested maps",
|
||||
changes: vec![Change {
|
||||
actor_id: actor1.clone(),
|
||||
seq: 1,
|
||||
dependencies: Clock::empty(),
|
||||
message: None,
|
||||
operations: vec![
|
||||
Operation::MakeMap {
|
||||
object_id: ObjectID::ID("birds".to_string()),
|
||||
},
|
||||
Operation::Set {
|
||||
object_id: ObjectID::ID("birds".to_string()),
|
||||
key: Key("wrens".to_string()),
|
||||
value: PrimitiveValue::Number(3.0),
|
||||
datatype: None,
|
||||
},
|
||||
Operation::Link {
|
||||
object_id: ObjectID::Root,
|
||||
key: Key("birds".to_string()),
|
||||
value: ObjectID::ID("birds".to_string()),
|
||||
},
|
||||
],
|
||||
}],
|
||||
expected_patch: Patch {
|
||||
can_undo: false,
|
||||
can_redo: false,
|
||||
clock: Clock::empty().with_dependency(&actor1, 1),
|
||||
deps: Clock::empty().with_dependency(&actor1, 1),
|
||||
diffs: vec![
|
||||
Diff {
|
||||
action: DiffAction::CreateMap(
|
||||
ObjectID::ID("birds".to_string()),
|
||||
MapType::Map,
|
||||
),
|
||||
conflicts: Vec::new(),
|
||||
},
|
||||
Diff {
|
||||
action: DiffAction::SetMapKey(
|
||||
ObjectID::ID("birds".to_string()),
|
||||
MapType::Map,
|
||||
Key("wrens".to_string()),
|
||||
ElementValue::Primitive(PrimitiveValue::Number(3.0)),
|
||||
None,
|
||||
),
|
||||
conflicts: Vec::new(),
|
||||
},
|
||||
Diff {
|
||||
action: DiffAction::SetMapKey(
|
||||
ObjectID::Root,
|
||||
MapType::Map,
|
||||
Key("birds".to_string()),
|
||||
ElementValue::Link(ObjectID::ID("birds".to_string())),
|
||||
None,
|
||||
),
|
||||
conflicts: Vec::new(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
//it('should create lists', () => {
|
||||
//const birds = uuid(), actor = uuid()
|
||||
//const change1 = {actor, seq: 1, deps: {}, ops: [
|
||||
//{action: 'makeList', obj: birds},
|
||||
//{action: 'ins', obj: birds, key: '_head', elem: 1},
|
||||
//{action: 'set', obj: birds, key: `${actor}:1`, value: 'chaffinch'},
|
||||
//{action: 'link', obj: ROOT_ID, key: 'birds', value: birds}
|
||||
//]}
|
||||
//const s0 = Backend.init()
|
||||
//const [s1, patch1] = Backend.applyChanges(s0, [change1])
|
||||
//assert.deepEqual(patch1, {
|
||||
//canUndo: false, canRedo: false, clock: {[actor]: 1}, deps: {[actor]: 1},
|
||||
//diffs: [
|
||||
//{action: 'create', obj: birds, type: 'list'},
|
||||
//{action: 'insert', obj: birds, type: 'list', path: null, index: 0, value: 'chaffinch', elemId: `${actor}:1`},
|
||||
//{action: 'set', obj: ROOT_ID, type: 'map', path: [], key: 'birds', value: birds, link: true}
|
||||
//]
|
||||
//})
|
||||
//})
|
||||
//TestCase {
|
||||
//name: "create lists",
|
||||
//changes: vec![Change{
|
||||
//actor_id: actor1.clone(),
|
||||
//seq: 1,
|
||||
//dependencies: Clock::empty(),
|
||||
//message: None,
|
||||
//operations: vec![
|
||||
//Operation::MakeList{object_id: ObjectID::ID("birds".to_string())},
|
||||
//Operation::Insert{list_id: ObjectID::ID("birds".to_string()), key: ElementID::Head, elem: 1},
|
||||
//Operation::Set{
|
||||
//object_id: ObjectID::ID("birds".to_string()),
|
||||
//key: ElementID::from_actor_and_elem(actor1.clone(), 1).as_key(),
|
||||
//value: PrimitiveValue::Str("chaffinch".to_string()),
|
||||
//datatype: None,
|
||||
//},
|
||||
//Operation::Link{
|
||||
//object_id: ObjectID::Root,
|
||||
//key: Key("birds".to_string()),
|
||||
//value: ObjectID::ID("birds".to_string()),
|
||||
//}
|
||||
//]
|
||||
//}],
|
||||
//expected_patch: Patch {
|
||||
//can_undo: false,
|
||||
//can_redo: false,
|
||||
//clock: Clock::empty().with_dependency(&actor1, 1),
|
||||
//deps: Clock::empty().with_dependency(&actor1, 1),
|
||||
//diffs: vec![
|
||||
//Diff{
|
||||
//action: DiffAction::CreateList(ObjectID::ID("birds".to_string()), SequenceType::List),
|
||||
//conflicts: Vec::new(),
|
||||
//},
|
||||
//Diff{
|
||||
|
||||
//}
|
||||
//]
|
||||
//}
|
||||
//}
|
||||
];
|
||||
|
||||
for testcase in testcases {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::patch::{Conflict, ElementValue};
|
||||
use crate::actor_histories::ActorHistories;
|
||||
use crate::error::AutomergeError;
|
||||
use crate::operation_with_metadata::OperationWithMetadata;
|
||||
use crate::patch::{Conflict, ElementValue};
|
||||
use crate::{DataType, Operation, PrimitiveValue};
|
||||
use std::cmp::PartialOrd;
|
||||
|
||||
|
@ -26,23 +26,28 @@ impl ConcurrentOperations {
|
|||
}
|
||||
|
||||
pub fn conflicts(&self) -> Vec<Conflict> {
|
||||
self.operations.split_first().map(|(_, tail)|{
|
||||
tail.iter().map(|op| {
|
||||
match &op.operation {
|
||||
Operation::Set{value, datatype, ..} => Conflict {
|
||||
actor: op.actor_id.clone(),
|
||||
value: ElementValue::Primitive(value.clone()),
|
||||
datatype: datatype.clone()
|
||||
},
|
||||
Operation::Link{value, ..} => Conflict {
|
||||
actor: op.actor_id.clone(),
|
||||
value: ElementValue::Link(value.clone()),
|
||||
datatype: None
|
||||
},
|
||||
_ => panic!("Invalid operation in concurrent ops")
|
||||
}
|
||||
}).collect()
|
||||
}).unwrap_or_else(||Vec::new())
|
||||
self.operations
|
||||
.split_first()
|
||||
.map(|(_, tail)| {
|
||||
tail.iter()
|
||||
.map(|op| match &op.operation {
|
||||
Operation::Set {
|
||||
value, datatype, ..
|
||||
} => Conflict {
|
||||
actor: op.actor_id.clone(),
|
||||
value: ElementValue::Primitive(value.clone()),
|
||||
datatype: datatype.clone(),
|
||||
},
|
||||
Operation::Link { value, .. } => Conflict {
|
||||
actor: op.actor_id.clone(),
|
||||
value: ElementValue::Link(value.clone()),
|
||||
datatype: None,
|
||||
},
|
||||
_ => panic!("Invalid operation in concurrent ops"),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| Vec::new())
|
||||
}
|
||||
|
||||
/// Updates this set of operations based on a new operation. Returns a diff
|
||||
|
|
|
@ -252,8 +252,12 @@ impl ObjectStore {
|
|||
map_type: MapType::Map,
|
||||
object_id: object_id.clone(),
|
||||
};
|
||||
self.operations_by_object_id.insert(object_id, object);
|
||||
None
|
||||
self.operations_by_object_id
|
||||
.insert(object_id.clone(), object);
|
||||
Some(Diff {
|
||||
action: DiffAction::CreateMap(object_id.clone(), MapType::Map),
|
||||
conflicts: Vec::new(),
|
||||
})
|
||||
}
|
||||
Operation::MakeTable { object_id } => {
|
||||
let object = ObjectHistory::Map {
|
||||
|
@ -261,8 +265,12 @@ impl ObjectStore {
|
|||
map_type: MapType::Table,
|
||||
object_id: object_id.clone(),
|
||||
};
|
||||
self.operations_by_object_id.insert(object_id, object);
|
||||
None
|
||||
self.operations_by_object_id
|
||||
.insert(object_id.clone(), object);
|
||||
Some(Diff {
|
||||
action: DiffAction::CreateMap(object_id.clone(), MapType::Table),
|
||||
conflicts: Vec::new(),
|
||||
})
|
||||
}
|
||||
Operation::MakeList { object_id } => {
|
||||
let object = ObjectHistory::List {
|
||||
|
@ -273,8 +281,12 @@ impl ObjectStore {
|
|||
sequence_type: SequenceType::Text,
|
||||
object_id: object_id.clone(),
|
||||
};
|
||||
self.operations_by_object_id.insert(object_id, object);
|
||||
None
|
||||
self.operations_by_object_id
|
||||
.insert(object_id.clone(), object);
|
||||
Some(Diff {
|
||||
action: DiffAction::CreateList(object_id.clone(), SequenceType::List),
|
||||
conflicts: Vec::new(),
|
||||
})
|
||||
}
|
||||
Operation::MakeText { object_id } => {
|
||||
let object = ObjectHistory::List {
|
||||
|
@ -285,8 +297,12 @@ impl ObjectStore {
|
|||
sequence_type: SequenceType::Text,
|
||||
object_id: object_id.clone(),
|
||||
};
|
||||
self.operations_by_object_id.insert(object_id, object);
|
||||
None
|
||||
self.operations_by_object_id
|
||||
.insert(object_id.clone(), object);
|
||||
Some(Diff {
|
||||
action: DiffAction::CreateList(object_id.clone(), SequenceType::Text),
|
||||
conflicts: Vec::new(),
|
||||
})
|
||||
}
|
||||
Operation::Link {
|
||||
ref object_id,
|
||||
|
|
|
@ -77,6 +77,10 @@ impl ActorID {
|
|||
pub fn random() -> ActorID {
|
||||
ActorID(uuid::Uuid::new_v4().to_string())
|
||||
}
|
||||
|
||||
pub fn from_string(raw: String) -> ActorID {
|
||||
ActorID(raw)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)]
|
||||
|
@ -144,6 +148,10 @@ impl ElementID {
|
|||
ElementID::SpecificElementID(actor_id, elem) => Key(format!("{}:{}", actor_id.0, elem)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_actor_and_elem(actor: ActorID, elem: u32) -> ElementID {
|
||||
ElementID::SpecificElementID(actor, elem)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ElementID {
|
||||
|
|
Loading…
Add table
Reference in a new issue