extern crate automerge_protocol as amp; use proptest::prelude::*; fn arb_maptype() -> impl Strategy { prop_oneof![Just(amp::MapType::Map), Just(amp::MapType::Table),] } fn arb_seqtype() -> impl Strategy { prop_oneof![Just(amp::SequenceType::List), Just(amp::SequenceType::Text),] } fn arb_objtype() -> impl Strategy { prop_oneof![ arb_maptype().prop_map(amp::ObjType::Map), arb_seqtype().prop_map(amp::ObjType::Sequence), ] } fn arb_scalar_value() -> impl Strategy { prop_oneof![ any::().prop_map(amp::ScalarValue::Str), any::().prop_map(amp::ScalarValue::Int), //This is necessary because we don't support integers larger than i64 in the JSON protocol //any::().prop_map(|i| amp::ScalarValue::Uint(i as u64)), any::().prop_map(amp::ScalarValue::Uint), any::().prop_map(amp::ScalarValue::F64), any::().prop_map(amp::ScalarValue::F32), any::().prop_map(amp::ScalarValue::Counter), any::().prop_map(amp::ScalarValue::Timestamp), any::().prop_map(amp::ScalarValue::Boolean), Just(amp::ScalarValue::Null), ] } fn arb_optype() -> impl Strategy { prop_oneof![ arb_objtype().prop_map(amp::OpType::Make), Just(amp::OpType::Del), any::().prop_map(amp::OpType::Inc), arb_scalar_value().prop_map(amp::OpType::Set), ] } fn arb_actorid() -> impl Strategy { proptest::collection::vec(any::(), 32).prop_map(|bytes| amp::ActorId::from_bytes(&bytes)) } fn arb_opid() -> impl Strategy { (any::(), arb_actorid()).prop_map(|(seq, actor)| amp::OpId::new(seq, &actor)) } fn arb_objid() -> impl Strategy { prop_oneof![ Just(amp::ObjectId::Root), arb_opid().prop_map(amp::ObjectId::Id), ] } fn arb_elemid() -> impl Strategy { prop_oneof![ Just(amp::ElementId::Head), arb_opid().prop_map(amp::ElementId::Id), ] } fn arb_key() -> impl Strategy { prop_oneof![ any::().prop_map(amp::Key::Map), arb_elemid().prop_map(amp::Key::Seq), ] } fn arb_changehash() -> impl Strategy { any::<[u8; 32]>().prop_map(amp::ChangeHash) } prop_compose! { fn arb_op() (insert in any::(), action in arb_optype(), obj in arb_objid(), key in arb_key(), pred in proptest::collection::vec(arb_opid(), 0..10)) -> amp::Op { amp::Op{ action, obj, key, pred, insert, } } } prop_compose! { fn arb_change() (seq in any::(), actor_id in arb_actorid(), start_op in any::(), time in any::(), message in proptest::option::of(any::()), deps in proptest::collection::vec(arb_changehash(), 0..10), extra_bytes in proptest::collection::vec(any::(), 0..10), operations in proptest::collection::vec(arb_op(), 0..10)) -> amp::UncompressedChange { amp::UncompressedChange{ seq, actor_id, start_op, time, hash: None, message, deps, operations, extra_bytes } } } /// We're roundtripping through json, which doesn't have a 32 bit float type or a uint type. /// This means that inputs with f32 values will round trip into 64 bit floats, and any /// positive i64's will round trip into u64's. This function performs that normalisation on an /// existing change so it can be compared with a round tripped change. fn normalize_change(change: &::UncompressedChange) -> amp::UncompressedChange { let mut result = change.clone(); for op in result.operations.iter_mut() { let new_action = match &op.action { amp::OpType::Set(amp::ScalarValue::F32(f)) => { let serialized = serde_json::to_string(f).unwrap(); let deserialized: f64 = serde_json::from_str(&serialized).unwrap(); amp::OpType::Set(amp::ScalarValue::F64(deserialized)) } amp::OpType::Set(amp::ScalarValue::Int(i)) => { let val = if *i > 0 { amp::ScalarValue::Uint((*i) as u64) } else { amp::ScalarValue::Int(*i) }; amp::OpType::Set(val) } //amp::OpType::Set(amp::ScalarValue::Uint(u)) => { //if *u > (i64::max_value() as u64) { //amp::OpType::Set(amp::ScalarValue::Uint(*u)) //} else { //amp::OpType::Set(amp::ScalarValue::Int((*u).try_into().unwrap())) //} //} a => a.clone(), }; op.action = new_action; } result } proptest! { #[test] fn test_round_trip_serialization(change in arb_change()) { let serialized = serde_json::to_string(&change)?; let deserialized: amp::UncompressedChange = serde_json::from_str(&serialized)?; prop_assert_eq!(normalize_change(&change), deserialized); } }