def test_merge_update_key_collision(): crdt_1, crdt_2 = CRDT(), CRDT() crdt_1.add('a', 1) crdt_2.add('b', 2) crdt_1.update('a', 'b') merged = merge(crdt_1, crdt_2) # In this implementation of LWW the update should overwrite the original key if the key already exists assert merged.data == {'b': 1}
def test_merge_remove_same_key_twice(): crdt_1, crdt_2 = CRDT(), CRDT() crdt_1.add('a', 1) crdt_2.add('a', 1) crdt_1.remove('a') crdt_2.remove('a') # In a merged set, since the key is removed before, the later remove should be ignored merged = merge(crdt_1, crdt_2) assert len(merged.data) == 0
def test_merge_update_same_key_twice(): crdt_1, crdt_2 = CRDT(), CRDT() crdt_1.add('a', 1) crdt_2.add('a', 2) crdt_1.update('a', 'b') crdt_2.update('a', 'c') merged = merge(crdt_1, crdt_2) # In a LWW merge, only the latest update should be present assert merged.data == {'c': 2}
def test_merge_add_only(ans): crdt_1, crdt_2 = CRDT(), CRDT() ans_1, ans_2 = slice_dict(ans, 0, len(ans) // 2), slice_dict(ans, len(ans) // 2, len(ans)) for k, v in ans_1.items(): crdt_1.add(k, v) for k, v in ans_2.items(): crdt_2.add(k, v) merged = merge(crdt_1, crdt_2) assert len(merged.log) == len(crdt_1.log) + len(crdt_2.log) assert merged.data == ans
def test_add_one(): crdt = CRDT() crdt.add('a', 1) assert len(crdt.log) == 1 assert crdt.data == {'a': 1} assert crdt.log[0].op == ADD
def test_merge_add_same_key_diff_values(ans): crdt_1, crdt_2 = CRDT(), CRDT() ans_alt = {k:v + 1 for k, v in ans.items()} for k, v in ans.items(): crdt_1.add(k, v) for k, v in ans_alt.items(): crdt_2.add(k, v) merged = merge(crdt_1, crdt_2) assert len(merged.log) == len(crdt_1.log) + len(crdt_2.log) # Only the values updated later should be present assert merged.data == ans_alt
def test_add_many(ans): crdt = CRDT() for k, v, in ans.items(): crdt.add(k, v) assert len(crdt.log) == len(ans) assert crdt.data == ans
def test_update_one(): crdt = CRDT() crdt.add('a', 1) crdt.update('a', 'b') assert len(crdt.log) == 2 assert crdt.data == {'b': 1} assert crdt.log[1].op == UPDATE
def test_remove_one(): crdt = CRDT() crdt.add('a', 1) crdt.remove('a') assert len(crdt.log) == 2 assert len(crdt.data) == 0 assert crdt.log[1].op == REMOVE
def test_merge_add_remove(ans): crdt_1, crdt_2 = CRDT(), CRDT() ans_1, ans_2 = slice_dict(ans, 0, len(ans) // 2), slice_dict(ans, len(ans) // 2, len(ans)) # Both CRDTs contain the same data initially for k, v in ans.items(): crdt_1.add(k, v) crdt_2.add(k, v) # Remove second half of the ans set in crdt_2 for k in ans_2: crdt_2.remove(k) merged = merge(crdt_1, crdt_2) # Only first half of the ans set should remain assert merged.data == ans_1
def test_merge_add_update(ans, upd): crdt_1, crdt_2 = CRDT(), CRDT() ans_1, ans_2 = slice_dict(ans, 0, len(ans) // 2), slice_dict(ans, len(ans) // 2, len(ans)) for k, v in ans_1.items(): crdt_1.add(k, v) for k, v in ans_2.items(): crdt_2.add(k, v) for k in ans_2: crdt_2.update(k, upd[k]) merged = merge(crdt_1, crdt_2) # Only keys in the second half of the ans set should be updated ans_1.update({upd[k]:v for k, v in ans_2.items()}) assert merged.data == ans_1
def test_update_key_collision(): crdt = CRDT() crdt.add('a', 1) crdt.add('b', 2) crdt.update('a', 'b') # In this implementation of LWW the update should overwrite the original key if the key already exists assert crdt.data == {'b': 1}
def test_merge_add_update_remove(ans, upd): crdt_1, crdt_2 = CRDT(), CRDT() ans_1, ans_2 = slice_dict(ans, 0, len(ans) // 2), slice_dict(ans, len(ans) // 2, len(ans)) for k, v in ans.items(): crdt_1.add(k, v) crdt_2.add(k, v) # Update the elements in the second half of the ans set in crdt_1 (opposite of test_merge_add_remove_update()) for k in ans_2: crdt_1.update(k, upd[k]) # Remove old keys that are updated in crdt_1 in crdt_2 (opposite of test_merge_add_remove_update()) for k in ans_2: crdt_2.remove(k) merged = merge(crdt_1, crdt_2) # Since this is a LWW set, the **removes** will overwrite the **updates** since they're executed later assert merged.data == ans_1
def test_invalid_op(): crdt = CRDT() operation = Operation(ts=datetime.utcnow().timestamp(), key='a', value=1, op=-1) # Append should fail if the operation is invalid try: crdt.append(operation) assert False except AssertionError as e: assert str(e) == INVALID_OP
def test_remove_many(ans): crdt = CRDT() for k, v in ans.items(): crdt.add(k, v) for k in ans: crdt.remove(k) # The same set of keys is added and removed, the length of the log should be twice that of the ans set assert len(crdt.log) == len(ans) * 2 assert len(crdt.data) == 0
def test_update_many(ans, upd): crdt = CRDT() for k, v in ans.items(): crdt.add(k, v) for k, v in upd.items(): crdt.update(k, v) # The keys are added then updated, the length of the log should be the sum of the lengths of the ans and upd set assert len(crdt.log) == len(ans) + len(upd) assert crdt.data == {upd[k]:v for k, v in ans.items()}
def test_add_same_key_twice(): crdt = CRDT() key = 'a' values = [1, 2, 3] for v in values: crdt.add(key, v) assert len(crdt.log) == len(values) # Only the updated value should be present assert crdt.data == {key: values[-1]}
def test_update_after_remove(): crdt = CRDT() crdt.add('a', 1) try: crdt.update('a', 'b') crdt.remove('a') # In the same instance, this operation should fail assert False except KeyError: pass
def test_remove_same_key_twice(): crdt = CRDT() crdt.add('a', 1) try: crdt.remove('a') crdt.remove('a') # In the same instance, this operation should fail assert False except KeyError: pass
def test_out_of_order_append(): crdt = CRDT() crdt.add('a', 1) inserted = crdt.log[-1] operation = Operation(ts=inserted.ts - 1, key=inserted.key, value=inserted.value, op=inserted.op) # Append should fail if the timestamp of the operation is earlier than the last inserted operation try: crdt.append(operation) assert False except AssertionError as e: assert str(e) == INVALID_TS
def __init__(self, mailbox): self.crdt = CRDT() self.lamport = 0 self.siteID = uuid1() self.mailbox = mailbox