def test_transform_delete_insert(self): engine = Engine(1) # Starting with the buffer "The very quickly brouwn fox" sequence1 = convert_delete_list( [ # delete the "e" from "the" DeleteOperation(2, 1), # delete the "e" from "very" DeleteOperation(4, 1), # delete the "ui" from "quickly" DeleteOperation(8, 2), # delete the "ou" from "brouwn" DeleteOperation(15, 2), # delete the "o" from "fox" DeleteOperation(19, 1), ], 1) # after sequence1 is applied, we would have "Th vry qckly brwn fx" sequence2 = convert_insert_list( [ # Add an "ee" after "the" InsertOperation(3, "ee"), # Add another "k" on the end of "quickly" InsertOperation(18, "k"), # Add "wnwnwn" to the end of "brouwn" InsertOperation(26, "wnwnwn"), # Add "xx!" to the end of "fox" InsertOperation(36, "xx!"), ], 2) # After sequence2 is applied, we will have "Theee very quicklyk brouwnwnwnwn foxxx!" results = engine._transform_delete_insert(sequence1, sequence2).to_list() self.assertEqual( results, [ # delete the first "e" from "theee" DeleteOperation(2, 1), # delete the "e" from "very" DeleteOperation(6, 1), # delete the "ui" from "quicklyk" DeleteOperation(10, 2), # delete the "ou" from "brouwnwnwnwn" DeleteOperation(18, 2), # delete the "o" from "foxxx!" DeleteOperation(28, 1), ])
def from_message(cls, message): if message['starting_state']: starting_state = State.__new__(State) starting_state.__setstate__(message['starting_state']) else: starting_state = None if len(message['inserts']) > 0: inserts = InsertOperationNode( InsertOperation(message['inserts'][0]['position'], message['inserts'][0]['value'])) state = State.__new__(State) state.__setstate__(message['inserts'][0]['state']) inserts.value.state = state inode = inserts for insert in message['inserts'][1:]: inode.next = InsertOperationNode( InsertOperation(insert['position'], insert['value'])) state = State.__new__(State) state.__setstate__(insert['state']) inode.value.state = state inode = inode.next else: inserts = None if len(message['deletes']) > 0: deletes = DeleteOperationNode( DeleteOperation(message['deletes'][0]['position'], message['deletes'][0]['length'])) state = State.__new__(State) state.__setstate__(message['deletes'][0]['state']) deletes.value.state = state dnode = deletes for delete in message['deletes'][1:]: dnode.next = DeleteOperationNode( DeleteOperation(delete['position'], delete['length'])) state = State.__new__(State) state.__setstate__(delete['state']) dnode.value.state = state dnode = dnode.next else: deletes = None return TransactionSequence(starting_state, inserts, deletes)
def test_swap_sequence_delete_insert(self): engine = Engine(1) # Starting with the buffer "The quick brown fox" sequence1 = convert_insert_list( [ # insert "very " after "t " InsertOperation(2, "very "), # insert "ly" after "quick" InsertOperation(12, "ly"), # insert "u" before the 'w' in "wn" InsertOperation(15, "u"), ], 1) # After this runs, we will have "T very quickly uwn ox" sequence2 = convert_delete_list( [ # Delete the "he" from "the" DeleteOperation(1, 2), # Delete "bro" from "brown" DeleteOperation(8, 3), # Delete "f" from "fox" DeleteOperation(11, 1) ], 2) # After this runs, we will have "T quick wn ox" updated_sq1, updated_sq2 = engine._swap_sequence_delete_insert( sequence2, sequence1) self.assertEqual( updated_sq1.to_list(), [ # insert "very " after "the " InsertOperation(4, "very "), # insert "ly" after "quick" InsertOperation(14, "ly"), # insert "u" after the 'o' in "brown" InsertOperation(20, "u"), ]) # After this runs, we will have "The very quickly brouwn fox" self.assertEqual( updated_sq2.to_list(), [ # Delete the "he" from "the" DeleteOperation(1, 2), # Delete "bro" from "brouwn" DeleteOperation(15, 3), # Delete "f" from "fox" DeleteOperation(19, 1) ])
def _swap_sequence_delete_delete(sequence2, sequence1): """ Swaps the execution order of the two input sequences. That is, previously sequence2 was executed before sequence1, now it is exectuted afterwards :param DeleteOperationNode sequence2: :param DeleteOperationNode sequence1: :return: A tuple with the two sequence's order of execution swapped. They are in the order sequence1', sequence2' :rtype: (DeleteOperationNode, DeleteOperationNode): """ new_sequence1 = None new_sequence2 = None new_node1 = None new_node2 = None node1 = sequence1 node2 = sequence2 size1 = 0 size2 = 0 while node1 and node2: if node2.value.position <= node1.value.position + size1: if new_sequence2: new_node2.next = copy(node2) new_node2 = new_node2.next else: new_node2 = copy(node2) new_sequence2 = new_node2 new_node2.value.position -= size1 size2 -= node2.value.get_increment() node2 = node2.next else: if new_sequence1: new_node1.next = copy(node1) new_node1 = new_node1.next else: new_node1 = copy(node1) new_sequence1 = new_node1 next_node = node1.next if node1.value.position + size1 + node1.value.length > node2.value.position: new_node1.value.length = node2.value.position - node1.value.position - size1 next_node = DeleteOperationNode( DeleteOperation(node1.value.position, node1.value.length - new_node1.value.length)) next_node.next = node1.next next_node.value.state = copy(node1.value.state) new_node1.value.position += size2 size1 -= new_node1.value.get_increment() node1 = next_node while node1: if new_sequence1: new_node1.next = copy(node1) new_node1 = new_node1.next else: new_node1 = copy(node1) new_sequence1 = new_node1 new_node1.value.position += size2 size1 -= node1.value.get_increment() node1 = node1.next while node2: if new_sequence2: new_node2.next = copy(node2) new_node2 = new_node2.next else: new_node2 = copy(node2) new_sequence2 = new_node2 new_node2.value.position += size1 size2 -= node2.value.get_increment() node2 = node2.next return new_sequence1, new_sequence2
def _transform_delete_delete(incoming_sequence, existing_sequence): """ Performs inclusive transformation on sequence1 with sequence2, meaning that the effects of `existing sequence` are incorporated in `incoming_sequence` :param pyote.utils.DeleteOperationNode incoming_sequence: The sequence that will be transformed :param pyote.utils.DeleteOperationNode existing_sequence: The sequence with operations that will perform the transformation :returns: The incoming sequence with the operations in the existing sequence taken into account :rtype: pyote.utils.DeleteOperationNode """ existing_value_size = 0 incoming_value_size = 0 transformed_sequence = None transformed_head = None incoming_node = incoming_sequence existing_node = existing_sequence existing_end_point = 0 double_count_amount = 0 # Walk through both sequences, one at a time. while existing_node and incoming_node: # Calculate what the position of the operation would be if it were performed now, rather than # after all other operations before it in the sequence. existing_pos = existing_node.value.position + existing_value_size incoming_pos = incoming_node.value.position + incoming_value_size double_delta = 0 # If the position of the insert in the existing sequence comes before the insert in the incoming sequence, # then record how much it would move the insertion position forward. if existing_pos < incoming_pos: existing_value_size += existing_node.value.length existing_end_point = existing_pos + existing_node.value.length existing_node = existing_node.next elif existing_pos == incoming_pos and \ existing_node.value.state.site_id < \ incoming_node.value.state.site_id: existing_value_size += existing_node.value.length existing_end_point = existing_pos + existing_node.value.length existing_node = existing_node.next else: # Otherwise, update the position of the incoming sequence's operation, and record how much it would # move the position forward after it's applied. if transformed_sequence: transformed_sequence.next = copy(incoming_node) transformed_sequence = transformed_sequence.next else: transformed_sequence = copy(incoming_node) transformed_head = transformed_sequence next_node = incoming_node.next # There are three possible situations: either the incoming operation overlaps with # the existing operation before it, it overlaps with the existing operation after it, or it overlaps # with neither. # We begin by checking if the preceding existing operation overlaps with it if existing_end_point > incoming_pos: # Now, either this delete is contained completely within the preceding delete, or it isn't. # In either case, we set the start of the incoming delete to the same point as the position # of the preceding delete, and set the length to be whatever is left after the preceding delete # has completed, which could be 0 transformed_sequence.value.position = existing_end_point - incoming_value_size transformed_sequence.value.length = max(0, incoming_node.value.length - existing_end_point + incoming_pos) # We now check if the next existing operation overlaps with this one if incoming_pos + incoming_node.value.length > existing_pos: # If so, then either the incoming operation ends within the existing operation, or it continues past # the end. if incoming_pos + incoming_node.value.length < existing_pos + existing_node.value.length: # If it ends early, then shorten the incoming operation so that it ends at the start of the # existing operation transformed_sequence.value.length = existing_pos - incoming_pos elif incoming_pos != existing_pos + existing_node.value.length: # Otherwise, shorten the operation AND create a new operation # which starts after the existing operation transformed_sequence.value.length -= incoming_pos + incoming_node.value.length - existing_pos next_node = DeleteOperationNode(DeleteOperation(existing_pos + existing_node.value.length, incoming_node.value.length + incoming_pos - existing_pos - existing_node.value.length)) next_node.next = incoming_node.next next_node.value.state = copy(incoming_node.value.state) # Because we are inserting a new node in the incoming sequence, we will double count it when # calculating the amount of deleting that we've done so far, so we subtract the size of the # newly created node (it will be re-added on the next iteration) incoming_value_size -= next_node.value.length double_delta = -next_node.value.length next_node.value.position -= incoming_value_size + incoming_node.value.length transformed_sequence.value.position -= existing_value_size - double_count_amount double_count_amount += incoming_node.value.length - transformed_sequence.value.length + double_delta incoming_value_size += incoming_node.value.length incoming_node = next_node # Take care of any elements that weren't handled in the above. while incoming_node: incoming_pos = incoming_node.value.position + incoming_value_size if transformed_sequence: transformed_sequence.next = copy(incoming_node) transformed_sequence = transformed_sequence.next else: transformed_sequence = copy(incoming_node) transformed_head = transformed_sequence if existing_end_point > incoming_pos: # Now, either this delete is contained completely within the preceding delete, or it isn't. # In either case, we set the start of the incoming delete to the same point as the position # of the preceding delete, and set the length to be whatever is left after the preceding delete # has completed, which could be 0 transformed_sequence.value.position = existing_end_point - incoming_value_size transformed_sequence.value.length = max(0, incoming_node.value.length - existing_end_point + incoming_pos) transformed_sequence.value.position -= existing_value_size - double_count_amount double_count_amount += incoming_node.value.length - transformed_sequence.value.length incoming_value_size += incoming_node.value.length incoming_node = incoming_node.next return transformed_head
def test_process_transaction(self): engine = Engine(1) engine._inserts = convert_insert_list( [ InsertOperation(0, "The quick brown fox"), # insert "very " after "the" InsertOperation(4, "very "), # insert "ly" after "quick" InsertOperation(14, "ly"), # insert "u" after the 'o' in "brown" InsertOperation(20, "u"), ], 1) # After the inserts are applied, we would have "The very quickly brouwn fox" engine._deletes = convert_delete_list( [ # delete the "e" from "the" DeleteOperation(2, 1), # delete the "e" from "very" DeleteOperation(4, 1), # delete the "ui" from "quickly" DeleteOperation(8, 2), # delete the "ou" from "brouwn" DeleteOperation(15, 2), # delete the "o" from "fox" DeleteOperation(19, 1), ], 1) # After the deletes are applied, we would have "Th vry qckly brwn fx" sequence = TransactionSequence( State(1, 0, 0), InsertOperationNode.from_list([ # Add an "ee" after "th" InsertOperation(2, "ee"), # Add another "k" on the end of "quickly" InsertOperation(14, "k"), # Add "wnwnwn" to the end of "brown" InsertOperation(20, "wnwnwn"), # Add "xx!" to the end of "fox" InsertOperation(29, "xx!"), # After the inserts, we would have "Thee vry qcklyk brwnwnwnwn fxxx!" ]), DeleteOperationNode.from_list([ # Delete the "h" from "thee" DeleteOperation(1, 1), # Delete "br" from "brwnwnwnwn" DeleteOperation(15, 2), # Delete "f" from "foxxx!" DeleteOperation(24, 1) ])) # After the deletes, we would have "Tee vry qcklyk wnwnwnwn oxxx!" new_transaction = engine.process_transaction(sequence) self.assertListEqual( new_transaction.inserts.to_list(), [ # Add an "ee" after "th" InsertOperation(3, "ee"), # Add a "k" after "qckly" InsertOperation(18, "k"), # Add a "wnwnwn" after "brwn" InsertOperation(26, "wnwnwn"), # Add "xx!" after "fx" InsertOperation(36, "xx!") ]) # After the inserts are applied, we would have "Theee very quicklyk brouwnwnwnwn foxxx!" self.assertListEqual( new_transaction.deletes.to_list(), [ # Delete the "h" in "theee" DeleteOperation(1, 1), # Delete "br" DeleteOperation(19, 2), # Delete "f" DeleteOperation(30, 1) ]) # After these are applied, we would have "Teee very quicklyk ouwnwnwnwn oxxx!" self.assertEqual( engine._inserts.to_list(), [ InsertOperation(0, "The quick brown fox"), # Add an "ee" after "the" InsertOperation(3, "ee"), # insert "very " after "theee " InsertOperation(6, "very "), # insert "ly" after "quick" InsertOperation(16, "ly"), # Add another "k" on the end of "quick" InsertOperation(18, "k"), # insert "u" after the 'o' in "brown" InsertOperation(23, "u"), # Add "wnwnwn" to the end of "brown" InsertOperation(26, "wnwnwn"), # Add "xx!" to the end of "fox" InsertOperation(36, "xx!"), ]) # After all the inserts are applied, we should have "Theee very quicklyk brouwnwnwnwn foxxx!" self.assertEqual( engine._deletes.to_list(), [ # Delete the "h" from "thee" DeleteOperation(1, 1), # delete the first "e" from "teee" DeleteOperation(1, 1), # delete the "e" from "very" DeleteOperation(5, 1), # delete the "ui" from "quicklyk" DeleteOperation(9, 2), # Delete "br" from "brouwnwnwnwn" DeleteOperation(15, 2), # delete the "ou" from "brouwn" DeleteOperation(15, 2), # Delete "f" from "foxxx!" DeleteOperation(24, 1), # delete the "o" from "oxxx!" DeleteOperation(24, 1), ])
def test_swap_delete_delete_with_overlap(self): engine = Engine(1) # starting with buffer "The quick brown fox jumped over the lazy dog" sequence1 = convert_delete_list( [ # Delete "The brown" DeleteOperation(0, 10), # Delete "jumped the dog" DeleteOperation(2, 16), ], 1) # After these operations run, we will have " " sequence2 = convert_delete_list( [ # Delete "quick" DeleteOperation(4, 5), # Delete "fox" DeleteOperation(11, 3), # Delete "over" DeleteOperation(19, 4), # Delete "lazy" DeleteOperation(24, 4), ], 2) # After these operations, we will have "The brown jumped the dog" updated_sq1, updated_sq2 = engine._swap_sequence_delete_delete( sequence2, sequence1) self.assertEqual( updated_sq1.to_list(), [ # Delete "The " DeleteOperation(0, 4), # Delete " brown" DeleteOperation(5, 6), # Delete "jumped " DeleteOperation(10, 7), # Delete " the " DeleteOperation(14, 5), # Delete " dog" DeleteOperation(18, 4), ]) # After these, we will have "quick fox overlazy" self.assertEqual( updated_sq2.to_list(), [ # Delete "quick" DeleteOperation(0, 5), # Delete "fox" DeleteOperation(1, 3), # Delete "over" DeleteOperation(2, 4), # Delete "lazy" DeleteOperation(2, 4), ])
def test_integrate_sequences_on_empty_engine(self): engine = Engine(1) # After the deletes are applied, we would have "Th vry qckly brwn fx" sequence = TransactionSequence( None, convert_insert_list( [ # Add an "ee" after "the" InsertOperation(3, "ee"), # Add another "k" on the end of "quick" InsertOperation(11, "k"), # Add "wnwnwn" to the end of "brown" InsertOperation(18, "wnwnwn"), # Add "xx!" to the end of "fox" InsertOperation(28, "xx!"), # After the inserts, we would have "Theee quickk brownwnwnwn foxxx!" ], 2), convert_delete_list( [ # Delete the "he" from "theee" DeleteOperation(1, 2), # Delete "bro" from "brownwnwnwn" DeleteOperation(11, 3), # Delete "f" from "foxxx" DeleteOperation(20, 1) ], 2)) # After the deletes, we would have "Tee quickk wnwnwnwn oxxx!" new_transaction = engine.integrate_remote(sequence) self.assertListEqual( new_transaction.inserts.to_list(), [ # Add an "ee" after "the" InsertOperation(3, "ee"), # Add another "k" on the end of "quick" InsertOperation(11, "k"), # Add "wnwnwn" to the end of "brown" InsertOperation(18, "wnwnwn"), # Add "xx!" to the end of "fox" InsertOperation(28, "xx!"), ]) # After these are applied, we would have "Thee vry qcklyk brwnwnwnwn fxxx!" self.assertListEqual( new_transaction.deletes.to_list(), [ # Delete the "he" from "theee" DeleteOperation(1, 2), # Delete "bro" from "brownwnwnwn" DeleteOperation(11, 3), # Delete "f" from "foxxx" DeleteOperation(20, 1) ]) # After these are applied, we would have "Tee vry qcklyk wnwnwnwn xxx!" self.assertEqual( engine._inserts.to_list(), [ # Add an "ee" after "the" InsertOperation(3, "ee"), # Add another "k" on the end of "quick" InsertOperation(11, "k"), # Add "wnwnwn" to the end of "brown" InsertOperation(18, "wnwnwn"), # Add "xx!" to the end of "fox" InsertOperation(28, "xx!"), ]) # After all the inserts are applied, we should have "Theee very quicklyk brouwnwnwnwn foxxx!" self.assertEqual( engine._deletes.to_list(), [ # Delete the "he" from "theee" DeleteOperation(1, 2), # Delete "bro" from "brownwnwnwn" DeleteOperation(11, 3), # Delete "f" from "foxxx" DeleteOperation(20, 1) ])
def test_transform_delete_delete_simple(self): engine = Engine(1) # starting with buffer "The quick brown fox jumped over the lazy dog" sequence1 = convert_delete_list( [ # Delete "The" DeleteOperation(0, 3), # Delete "brown" DeleteOperation(7, 5), # Delete "jumped" DeleteOperation(12, 6), # Delete "the" DeleteOperation(18, 3), # Delete "dog" DeleteOperation(24, 3), ], 1) # After these operations run, we will have " quick fox over lazy " sequence2 = convert_delete_list( [ # Delete "quick" DeleteOperation(4, 5), # Delete "fox" DeleteOperation(11, 3), # Delete "over" DeleteOperation(19, 4), # Delete "lazy" DeleteOperation(24, 4), ], 2) # After these operations, we will have "The brown jumped the dog" result = engine._transform_delete_delete(sequence1, sequence2).to_list() self.assertEqual( result, [ # Delete "The" DeleteOperation(0, 3), # Delete "brown" DeleteOperation(2, 5), # Delete "jumped" DeleteOperation(4, 6), # Delete "the" DeleteOperation(6, 3), # Delete "dog" DeleteOperation(8, 3), ]) result = engine._transform_delete_delete(sequence2, sequence1).to_list() self.assertEqual( result, [ # Delete "quick" DeleteOperation(1, 5), # Delete "fox" DeleteOperation(3, 3), # Delete "over" DeleteOperation(5, 4), # Delete "lazy" DeleteOperation(7, 4), ])
def test_transform_delete_delete_with_0_length_deletes(self): engine = Engine(1) # Starting with buffer "The quick brown fox jumped over the lazy dog" sequence1 = convert_delete_list( [ # Delete "h" DeleteOperation(1, 1), # Delete "" after "T" DeleteOperation(1, 0), # Delete "ck " DeleteOperation(6, 3), # Delete "" after "n" DeleteOperation(11, 0), ], 2) # After sequence1 is applied, we will have "Te quibrown fox jumped over the lazy dog" sequence2 = convert_delete_list( [ # Delete "e" DeleteOperation(2, 1), # Delete "c" DeleteOperation(6, 1), # Delete "ow" DeleteOperation(10, 2), # Delete "mp" DeleteOperation(18, 2), # Detete " " after "the DeleteOperation(28, 1), ], 1) # After sequence2 is applied, we will have "Th quik brn fox jued over thelazy dog" result = engine._transform_delete_delete(sequence2, sequence1).to_list() self.assertEqual( result, [ # Delete "e" DeleteOperation(1, 1), # Delete "" (was delete "c") DeleteOperation(5, 0), # Delete "wn" DeleteOperation(7, 2), # Delete "mp" DeleteOperation(15, 2), # Delete " " after "the" DeleteOperation(25, 1), ])
def test_transform_delete_delete(self): engine = Engine(1) # Starting with buffer "The quick brown fox jumped over the lazy dog" sequence1 = convert_delete_list( [ # Delete "quick bro" DeleteOperation(4, 9), # Delete "ed over" DeleteOperation(15, 7), # Delete "laz" DeleteOperation(20, 3), ], 2) # After sequence1 is applied, we will have "The wn fox jump the y dog" sequence2 = convert_delete_list( [ # Delete "he qu" DeleteOperation(1, 5), # Delete "ck" DeleteOperation(2, 2), # Delete "rown" DeleteOperation(4, 4), # Delete "the lazy dog" DeleteOperation(21, 12), ], 1) # After sequence2 is applied, we will have "Ti b fox jumped over" self.assertEqual( engine._transform_delete_delete(sequence1, sequence2).to_list(), [ # Delete "i" DeleteOperation(1, 1), # Delete " b" DeleteOperation(1, 2), # Delete "ed over" DeleteOperation(10, 7), # Delete "" DeleteOperation(11, 0), ]) # After both sequences are applied, we will have "T fox jump " self.assertEqual( engine._transform_delete_delete(sequence2, sequence1).to_list(), [ # Delete "he " DeleteOperation(1, 3), # Delete "" DeleteOperation(1, 0), # Delete "wn" DeleteOperation(1, 2), # Delete "the " DeleteOperation(11, 4), # Delete "y dog" DeleteOperation(11, 5), ])