use crdts::{mvreg::Op, *};
#[test]
fn test_apply() {
let mut reg = MVReg::new();
let clock = VClock::from(Dot::new(2, 1));
reg.apply(Op::Put {
clock: clock.clone(),
val: 71,
});
assert_eq!(reg.read().add_clock, clock);
assert_eq!(reg.read().val, vec![71]);
}
#[test]
fn test_write_should_not_mutate_reg() {
let reg = MVReg::new();
let ctx = reg.read().derive_add_ctx("A");
let op = reg.write(32, ctx);
assert_eq!(reg, MVReg::new());
let mut reg = reg;
reg.apply(op);
assert_eq!(reg.read().val, vec![32]);
assert_eq!(reg.read().add_clock, VClock::from(Dot::new("A", 1)));
}
#[test]
fn test_concurrent_update_with_same_value_dont_collapse_on_merge() {
let mut r1 = MVReg::new();
let mut r2 = MVReg::new();
r1.apply(r1.write(23, r1.read().derive_add_ctx("A")));
r2.apply(r2.write(23, r2.read().derive_add_ctx("B")));
r1.merge(r2);
assert_eq!(r1.read().val, vec![23, 23]);
assert_eq!(
r1.read().add_clock,
vec![Dot::new("A", 1), Dot::new("B", 1)]
.into_iter()
.collect()
);
}
#[test]
fn test_concurrent_update_with_same_value_dont_collapse_on_apply() {
let mut r1 = MVReg::new();
let r2 = MVReg::new();
r1.apply(r1.write(23, r1.read().derive_add_ctx("A")));
r1.apply(r2.write(23, r2.read().derive_add_ctx("B")));
assert_eq!(r1.read().val, vec![23, 23]);
assert_eq!(
r1.read().add_clock,
vec![Dot::new("A", 1), Dot::new("B", 1)]
.into_iter()
.collect()
);
}
#[test]
fn test_multi_val() {
let mut r1 = MVReg::new();
let mut r2 = MVReg::new();
r1.apply(r1.write(32, r1.read().derive_add_ctx("A")));
r2.apply(r2.write(82, r2.read().derive_add_ctx("B")));
r1.merge(r2);
assert!(r1.read().val == vec![32, 82] || r1.read().val == vec![82, 32]);
}
#[test]
fn test_op_commute_quickcheck1() {
let mut reg1 = MVReg::new();
let mut reg2 = MVReg::new();
let op1 = Op::Put {
clock: Dot::new("A", 1).into(),
val: 1,
};
let op2 = Op::Put {
clock: Dot::new("B", 1).into(),
val: 2,
};
reg2.apply(op2.clone());
reg2.apply(op1.clone());
reg1.apply(op1);
reg1.apply(op2);
assert_eq!(reg1, reg2);
}
#[cfg(feature = "quickcheck")]
mod prop_tests {
use super::*;
use quickcheck::TestResult;
use quickcheck_macros::quickcheck;
#[derive(Debug, Clone)]
struct TestReg {
reg: MVReg<u8, u8>,
ops: Vec<Op<u8, u8>>,
}
#[quickcheck]
fn prop_set_with_ctx_from_read(r_ops: Vec<(u8, u8)>, a: u8) -> bool {
let mut reg = build_test_reg(r_ops).reg;
let write_ctx = reg.read().derive_add_ctx(a);
reg.apply(reg.write(23, write_ctx));
let next_read_ctx = reg.read();
next_read_ctx.val == vec![23]
}
#[quickcheck]
fn prop_merge_idempotent(r_ops: Vec<(u8, u8)>) -> bool {
let mut r = build_test_reg(r_ops).reg;
let r_snapshot = r.clone();
r.merge(r_snapshot.clone());
assert_eq!(r, r_snapshot);
true
}
#[quickcheck]
fn prop_merge_commutative(r1_ops: Vec<(u8, u8)>, r2_ops: Vec<(u8, u8)>) -> TestResult {
if ops_are_not_compatible(&[&r1_ops, &r2_ops]) {
return TestResult::discard();
}
let r1 = build_test_reg(r1_ops);
let r2 = build_test_reg(r2_ops);
let mut r1 = r1.reg;
let mut r2 = r2.reg;
let r1_snapshot = r1.clone();
r1.merge(r2.clone());
r2.merge(r1_snapshot);
assert_eq!(r1, r2);
TestResult::from_bool(true)
}
#[quickcheck]
fn prop_merge_associative(
r1_ops: Vec<(u8, u8)>,
r2_ops: Vec<(u8, u8)>,
r3_ops: Vec<(u8, u8)>,
) -> TestResult {
if ops_are_not_compatible(&[&r1_ops, &r2_ops, &r3_ops]) {
return TestResult::discard();
}
let mut r1 = build_test_reg(r1_ops).reg;
let mut r2 = build_test_reg(r2_ops).reg;
let r3 = build_test_reg(r3_ops).reg;
let r1_snapshot = r1.clone();
r1.merge(r2.clone());
r1.merge(r3.clone());
r2.merge(r3);
r2.merge(r1_snapshot);
assert_eq!(r1, r2);
TestResult::from_bool(true)
}
#[quickcheck]
fn prop_reset_remove(r_ops: Vec<(u8, u8)>) -> bool {
let mut r = build_test_reg(r_ops).reg;
let r_snapshot = r.clone();
r.reset_remove(&VClock::new());
assert_eq!(r, r_snapshot);
let clock = r.read().add_clock;
r.reset_remove(&clock);
assert_eq!(r, MVReg::new());
true
}
#[quickcheck]
fn prop_op_idempotent(r_ops: Vec<(u8, u8)>) -> TestResult {
let test = build_test_reg(r_ops);
let mut r = test.reg;
let r_snapshot = r.clone();
for op in test.ops.into_iter() {
r.apply(op);
}
assert_eq!(r, r_snapshot);
TestResult::from_bool(true)
}
#[quickcheck]
fn prop_op_commutative(o1_ops: Vec<(u8, u8)>, o2_ops: Vec<(u8, u8)>) -> TestResult {
if ops_are_not_compatible(&[&o1_ops, &o2_ops]) {
return TestResult::discard();
}
let o1 = build_test_reg(o1_ops);
let o2 = build_test_reg(o2_ops);
let mut r1 = o1.reg;
let mut r2 = o2.reg;
for op in o2.ops.into_iter() {
r1.apply(op);
}
for op in o1.ops.into_iter() {
r2.apply(op);
}
assert_eq!(r1, r2);
TestResult::from_bool(true)
}
#[quickcheck]
fn prop_op_associative(
o1_ops: Vec<(u8, u8)>,
o2_ops: Vec<(u8, u8)>,
o3_ops: Vec<(u8, u8)>,
) -> TestResult {
if ops_are_not_compatible(&[&o1_ops, &o2_ops, &o3_ops]) {
return TestResult::discard();
}
let o1 = build_test_reg(o1_ops);
let o2 = build_test_reg(o2_ops);
let o3 = build_test_reg(o3_ops);
let mut r1 = o1.reg;
let mut r2 = o2.reg;
for op in o2.ops.into_iter() {
r1.apply(op);
}
for op in o3.ops.iter().cloned() {
r1.apply(op);
}
for op in o3.ops.into_iter() {
r2.apply(op);
}
for op in o1.ops.into_iter() {
r2.apply(op);
}
assert_eq!(r1, r2);
TestResult::from_bool(true)
}
fn build_test_reg(prim_ops: Vec<(u8, u8)>) -> TestReg {
let mut reg = MVReg::default();
let mut ops = Vec::new();
for (val, actor) in prim_ops {
let ctx = reg.read().derive_add_ctx(actor);
let op = reg.write(val, ctx);
reg.apply(op.clone());
ops.push(op);
}
TestReg { reg, ops }
}
fn ops_are_not_compatible(opss: &[&Vec<(u8, u8)>]) -> bool {
for a_ops in opss.iter() {
for b_ops in opss.iter().filter(|o| o != &a_ops) {
let mut a_clock = VClock::new();
let mut b_clock = VClock::new();
for ((_, a_actor), (_, b_actor)) in a_ops.iter().zip(b_ops.iter()) {
a_clock.apply(a_clock.inc(*a_actor));
b_clock.apply(b_clock.inc(*b_actor));
if b_clock.get(a_actor) == a_clock.get(a_actor) {
return true;
}
}
}
}
false
}
}