Example #1
0
 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),
         ])
Example #2
0
    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)
Example #3
0
 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)
         ])
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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),
            ])
Example #7
0
    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),
            ])
Example #8
0
    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)
            ])
Example #9
0
    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),
            ])
Example #10
0
 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),
         ])
Example #11
0
    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),
            ])