def test_has_needed_dependencies(self):
        doc = self.doc0

        cs1 = Changeset(doc.get_id(), 'user', [doc.get_root_changeset()])
        assert doc.has_needed_dependencies(cs1)

        cs2 = Changeset(doc.get_id(), 'user', [cs1])
        assert not doc.has_needed_dependencies(cs2)

        doc.receive_changeset(cs1)
        assert doc.has_needed_dependencies(cs2)

        cs3 = Changeset(doc.get_id(), 'user', [cs1, cs2])
        assert not doc.has_needed_dependencies(cs3)

        doc.receive_changeset(cs2)
        assert doc.has_needed_dependencies(cs3)

        cs4 = Changeset(doc.get_id(), 'user', [cs3, "555"])
        assert not doc.has_needed_dependencies(cs4)

        doc.receive_changeset(cs3)
        assert not doc.has_needed_dependencies(cs4)

        cs5 = Changeset(doc.get_id(), 'user', [cs1])
        cs5.set_id("555")
        doc.receive_changeset(cs5)
        cs4.relink_changesets(doc.all_known_changesets)
        assert cs5 in cs4.get_parents()
        assert cs4.has_full_dependency_info()
        assert doc.has_needed_dependencies(cs4)
    def test_delete_overlaping_ranges_revered(self):
        """
        Same opperations as previous test, but order of branches is reversed
        (B now before A)
        """
        s = '0123456789'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # construct branch A, which deletes all but the first three
        # and last three characters.
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=4, val=2))
        A0.set_id('1A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('sd', [], offset=3, val=2))
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('sd', [], offset=2, val=2))
        doc.receive_changeset(A2)
        assert doc.get_snapshot() == '0189'

        # Branch B has common parent with A. It deletes all but '89'

        # User saw '0123456789' and deleted '3456'
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=3, val=4)
        B0.add_op(opB0)
        B0.set_id('0B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index > b_index
        assert doc.get_snapshot() == '0189'

        # User saw '012789' and deleted '27'
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=2, val=2)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == '0189'

        # Delete Range not known by branch A
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=1, val=2)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == '09'

        # combine these braches again
        C = Changeset(doc.get_id(), 'u2', [A2, B2])
        C.add_op(Op('si', [], offset=1, val='12345678'))
        doc.receive_changeset(C)
        assert doc.get_snapshot() == '0123456789'
示例#3
0
def build_changesets_from_tuples(css_data, doc):
    """
    When testing its easiest to write the desired changesets as tuples, then
    this will convert that list of tuples into a list of changesets for the
    desired doc.
    """
    css = []
    for cs_data in css_data:
        action, offset, val, deps, _id = cs_data
        deps = [doc.get_root_changeset() if dep == 'root' else dep
                for dep in deps]
        cs = Changeset(doc.get_id(), 'u1', deps)
        cs.add_op(Op(action, [], offset=offset, val=val))
        cs.set_id(_id)
        css.append(cs)
    return css
    def test_two_deletes_in_first_branch_reverse(self):
        """
        Same opperations as previous test, but order of branches is reversed
        (B now before A)
        """
        s = 'ab123cd456gh789ik'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=7, val=2))
        A0.set_id('1A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('sd', [], offset=10, val=3))
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [], offset=7, val='ef'))
        doc.receive_changeset(A2)
        assert doc.get_snapshot() == 'ab123cdef6ghik'

        # Branch B has common parent with A. First it deletes a
        # partialy overlaping range in A
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=8, val=2)
        B0.add_op(opB0)
        B0.set_id('0B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index > b_index
        assert doc.get_snapshot() == 'ab123cdefghik'

        # Delete range partially overlaping range in A
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=10, val=2)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == 'ab123cdefghik'

        # Delete Range unaffected by branch A
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=2, val=3)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == 'abcdefghik'

        # combine these braches again
        C = Changeset(doc.get_id(), 'u2', [A2, B2])
        C.add_op(Op('si', [], offset=9, val='j'))
        doc.receive_changeset(C)
        assert doc.get_snapshot() == 'abcdefghijk'
    def test_two_deletes_in_first_branch(self):
        """  01234567890123456 (helpful index of characters in doc)"""
        s = 'ab123cd456gh789ik'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # construct branch A, which has two string deletes, then
        # adds text
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=7, val=2))
        A0.set_id('A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('sd', [], offset=10, val=3))
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [], offset=7, val='ef'))
        doc.receive_changeset(A2)
        assert doc.get_snapshot() == 'ab123cdef6ghik'

        # Branch B has common parent with A. First it deletes a
        # partialy overlaping range in A
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=8, val=2)
        B0.add_op(opB0)
        B0.set_id('B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index < b_index
        assert doc.get_snapshot() == 'ab123cdefghik'

        # Delete range partially overlaping range in A
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=10, val=2)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == 'ab123cdefghik'

        # Delete Range unaffected by branch A
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=2, val=3)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == 'abcdefghik'

        # combine these braches again
        C = Changeset(doc.get_id(), 'u2', [A2, B2])
        C.add_op(Op('si', [], offset=9, val='j'))
        doc.receive_changeset(C)
        assert doc.get_snapshot() == 'abcdefghijk'
    def test_multiple_dependencies(self):
        """
             -- B ---- E
            /         /
        root -- A -- D
            \       /
             -- C --
        """
        doc = Document(snapshot='')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()
        B = Changeset(doc.get_id(), "user0", [root])
        B.set_id('b')
        doc.receive_changeset(B)

        A = Changeset(doc.get_id(), "user1", [root])
        A.set_id('a')
        doc.receive_changeset(A)
        assert A.get_unaccounted_changesets() == []
        assert B.get_unaccounted_changesets() == [A]
        
        C = Changeset(doc.get_id(), "user2", [root])
        C.set_id('c')
        doc.receive_changeset(C)
        assert A.get_unaccounted_changesets() == []
        assert B.get_unaccounted_changesets() == [A]
        assert C.get_unaccounted_changesets() == [A,B]


        # test_multiple_dependencies_common_base
        D = Changeset(doc.get_id(), "user0", [C,A])
        D.set_id('d')
        doc.receive_changeset(D)
        assert A.get_unaccounted_changesets() == []
        assert B.get_unaccounted_changesets() == [A]
        assert C.get_unaccounted_changesets() == [A,B]
        assert D.get_unaccounted_changesets() == [B]

        E = Changeset(doc.get_id(), 'user1', [B, D])
        E.set_id('e')
        doc.receive_changeset(E)
        assert A.get_unaccounted_changesets() == []
        assert B.get_unaccounted_changesets() == [A]
        assert C.get_unaccounted_changesets() == [A,B]
        assert D.get_unaccounted_changesets() == [B]
        assert E.get_unaccounted_changesets() == []
    def test_one_delete_in_first_branch(self):
        """
        There is one delete in the first branch, multiple in branch B, then
        each has more string inserts.
        """
        doc = Document(snapshot='123abcde789')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # construct branch A, which begins with a string delete, then
        # adds text
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=3, val=5))
        A0.set_id('A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('si', [], offset=3, val='456'))
        doc.receive_changeset(A1)
        assert doc.get_snapshot() == '123456789'

        # Branch B has common parent with A. B has three deletes, some of which
        # overlap the delete in branch A
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=4, val=2)
        B0.add_op(opB0)
        B0.set_id('B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index < b_index
        assert doc.get_snapshot() == '123456789'

        # Partially overlaping delete
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=6, val=2)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == '1234569'

        # Delete Range unaffected by branch A
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=1, val=1)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == '134569'

        # combine these braches again
        C = Changeset(doc.get_id(), 'u2', [A1, B2])
        C.add_op(Op('si', [], offset=1, val='2'))
        C.add_op(Op('si', [], offset=6, val='78'))
        doc.receive_changeset(C)
        assert doc.get_snapshot() == '123456789'
    def test_multiple_dependencies(self):
        """
             -- B ---- E
            /         /
        root -- A -- D
            \       /
             -- C --
        """
        doc = Document(snapshot='')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()
        B = Changeset(doc.get_id(), "user0", [root])
        B.set_id('b')
        doc.receive_changeset(B)

        A = Changeset(doc.get_id(), "user1", [root])
        A.set_id('a')
        doc.receive_changeset(A)
        assert set(doc.get_dependencies()) == set([B,A])
        assert doc.get_ordered_changesets() == [root, A, B]

        C = Changeset(doc.get_id(), "user2", [root])
        C.set_id('c')
        doc.receive_changeset(C)
        assert set(doc.get_dependencies()) == set([B,A,C])
        assert doc.get_ordered_changesets() == [root, A, B, C]
        assert doc.get_ordered_changesets() == doc.tree_to_list()


        # test_multiple_dependencies_common_base
        D = Changeset(doc.get_id(), "user0", [C,A])
        D.set_id('d')
        doc.receive_changeset(D)
        assert set(doc.get_dependencies()) == set([B,D])
        assert doc.get_ordered_changesets() == [root, A, B, C, D]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        E = Changeset(doc.get_id(), 'user1', [B, D])
        E.set_id('e')
        doc.receive_changeset(E)
        assert doc.get_dependencies() == [E]
        assert doc.get_ordered_changesets() == [root, A, B, C, D, E]
        assert doc.get_ordered_changesets() == doc.tree_to_list()
    def xtest_relink_dependency(self):
        dep = Changeset('doc_id', 'user_id', [])
        dep.set_id('defined_id')

        # a cs with no dependencies should never relink
        assert not self.cs0.relink_changesets(dep)

        # cs does not need given dep
        cs1 = Changeset('doc_id', 'user_id', [self.cs0])
        assert not cs1.relink_changesets(dep)
        assert cs1.get_dependencies() == [self.cs0]

        # cs already has given dep info
        cs2 = Changeset('doc_id', 'user_id', [self.cs0, dep])
        assert not cs2.relink_changesets(dep)
        assert cs2.get_dependencies() == [self.cs0, dep]

        # cs needed and relinked given dep
        cs3 = Changeset('doc_id', 'user_id', [self.cs0, 'defined_id'])
        assert cs3.relink_changesets(dep)
        assert cs3.get_dependencies() == [self.cs0, dep]
    def test_one_delete_in_first_branch_reversed(self):
        """
        Same opperations as previous test, but order of branches is reversed
        (B now before A)
        """
        doc = Document(snapshot='123abcde789')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=3, val=5))
        A0.set_id('1A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('si', [], offset=3, val='456'))
        doc.receive_changeset(A1)

        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=4, val=2)
        B0.add_op(opB0)
        B0.set_id('0B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index > b_index
        assert doc.get_snapshot() == '123456789'

        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=6, val=2)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == '1234569'

        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=1, val=1)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == '134569'

        # combine these braches again
        C = Changeset(doc.get_id(), 'u2', [A1, B2])
        C.add_op(Op('si', [], offset=1, val='2'))
        C.add_op(Op('si', [], offset=6, val='78'))
        doc.receive_changeset(C)
        assert doc.get_snapshot() == '123456789'
    def test_delete_in_second_branch(self):
        """
        """
        doc = Document(snapshot='abcdefghij')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # construct branch A, which begins with a string delete
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=5, val=5))
        A0.set_id('2A')
        doc.receive_changeset(A0)
        assert doc.get_snapshot() == 'abcde'

        # Branch B has common parent with A. It inserts strings at the end
        # which should not be deleted.
        B0 = Changeset(doc.get_id(), 'u2', [root])
        for i in xrange(10):
            op = Op('si', [], offset=10 + i, val=str(i))
            B0.add_op(op)
        B0.set_id('1B')
        doc.receive_changeset(B0)
        assert doc.get_snapshot() == 'abcde0123456789'
    def test_consecutive_inserts(self):
        doc = self.doc1
        root = self.root1

        # Branch A
        A0 = Changeset(doc.get_id(), 'u1', [root])
        vA0 = ['1', '2']
        A0.add_op(Op('ai', [], offset=2, val=vA0))
        A0.set_id('A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        vA1 = ['3', '4']
        A1.add_op(Op('ai', [], offset=4, val=vA1))
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        vA2 = ['8', '9']
        A2.add_op(Op('ai', [], offset=9, val=vA2))
        doc.receive_changeset(A2)

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        vA3 = ['0']
        A3.add_op(Op('ai', [], offset=11, val=vA3))
        doc.receive_changeset(A3)
        result = ['ABCD',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Now B has a series of inserts
        B0 = Changeset(doc.get_id(), 'u1', [root])
        vB0 = ['1b', '2b']
        B0.add_op(Op('ai', [], offset=1, val=vB0))
        B0.set_id('B')
        doc.receive_changeset(B0)
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B1 = Changeset(doc.get_id(), 'u1', [B0])
        vB1 = ['3b', '4b']
        B1.add_op(Op('ai', [], offset=5, val=vB1))
        doc.receive_changeset(B1)
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  '3b', '4b',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B2 = Changeset(doc.get_id(), 'u1', [B1])
        vB2 = ['8b', '9b', '0b']
        B2.add_op(Op('ai', [], offset=7, val=vB2))
        doc.receive_changeset(B2)
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  '3b', '4b', '8b', '9b', '0b',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B3 = Changeset(doc.get_id(), 'u1', [B2])
        vB3 = ['BBBB']
        B3.add_op(Op('ai', [], offset=12, val=vB3))
        doc.receive_changeset(B3)
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  '3b', '4b', '8b', '9b', '0b',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'BBBB',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result
    def test_consecutive_inserts_reversed(self):
        """
        Same as the previous test except branch B gets applied before branch A.
        """
        doc = self.doc1
        root = self.root1

        # Branch A
        A0 = Changeset(doc.get_id(), 'u1', [root])
        vA0 = ['1', '2']
        A0.add_op(Op('ai', [], offset=2, val=vA0))
        A0.set_id('1A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        vA1 = ['3', '4']
        A1.add_op(Op('ai', [], offset=4, val=vA1))
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        vA2 = ['8', '9']
        A2.add_op(Op('ai', [], offset=9, val=vA2))
        doc.receive_changeset(A2)

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        vA3 = ['0']
        A3.add_op(Op('ai', [], offset=11, val=vA3))
        doc.receive_changeset(A3)
        result = ['ABCD',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Now B has a series of inserts
        B0 = Changeset(doc.get_id(), 'u1', [root])
        vB0 = ['1b', '2b']
        B0.add_op(Op('ai', [], offset=1, val=vB0))
        B0.set_id('0B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index > b_index
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B1 = Changeset(doc.get_id(), 'u1', [B0])
        vB1 = ['3b', '4b']
        B1.add_op(Op('ai', [], offset=5, val=vB1))
        doc.receive_changeset(B1)
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  '3b', '4b',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B2 = Changeset(doc.get_id(), 'u1', [B1])
        vB2 = ['8b', '9b', '0b']
        B2.add_op(Op('ai', [], offset=7, val=vB2))
        doc.receive_changeset(B2)
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  '3b', '4b', '8b', '9b', '0b',
                  'MNOP',
                  'QRST',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B3 = Changeset(doc.get_id(), 'u1', [B2])
        vB3 = ['BBBB']
        B3.add_op(Op('ai', [], offset=12, val=vB3))
        doc.receive_changeset(B3)
        result = ['ABCD',
                  '1b', '2b',
                  'EFGH',
                  '1', '2', '3', '4',
                  'IJKL',
                  '3b', '4b', '8b', '9b', '0b',
                  'MNOP',
                  'QRST',
                  'BBBB',
                  '8', '9', '0',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result
    def setup_method(self, method):
        s1 = ['ABCD',
              'EFGH',
              'IJKL',
              'MNOP',
              'QRST',
              'UVWX',
              'YZ']
        doc = Document(snapshot=s1)
        doc.HAS_EVENT_LOOP = False
        self.doc = doc
        self.root = self.doc.get_root_changeset()

        # establish the reusable changesets

        # Branch A inserts two new array elements, and add text to those two.
        # It also inserts text into three existing elements, before any array
        # insert, in the middle, and after the last array insert.
        A0 = Changeset(doc.get_id(), 'u1', [self.root])
        vA0 = ['02', '4']
        A0.add_op(Op('ai', [], offset=2, val=vA0))
        A0.set_id('A')
        self.A0 = A0

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        vA1 = ['5', '89']
        A1.add_op(Op('ai', [], offset=6, val=vA1))
        self.A1 = A1

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [2], offset=1, val='1'))
        A2.add_op(Op('si', [3], offset=0, val='3'))
        self.A2 = A2

        A3 = Changeset(doc.get_id(), 'u1', [self.A2])
        A3.add_op(Op('si', [6], offset=1, val='67'))
        self.A3 = A3

        A4 = Changeset(doc.get_id(), 'u1', [A3])
        A4.add_op(Op('si', [0], offset=0, val='a'))
        A4.add_op(Op('si', [4], offset=0, val='b'))
        self.A4 = A4

        A5 = Changeset(doc.get_id(), 'u1', [A4])
        A5.add_op(Op('si', [9], offset=0, val='c'))
        self.A5 = A5

        # Branch B performs similar actions to branch A.
        B0 = Changeset(doc.get_id(), 'u2', [self.root])
        vB0a, vB0b = ['lm', 'ipsm'], ['or', 't']
        opB0a = Op('ai', [], offset=2, val=vB0a)
        B0.add_op(opB0a)
        opB0b = Op('ai', [], offset=6, val=vB0b)
        B0.add_op(opB0b)
        B0.set_id('B')
        self.B0 = B0

        # Now B inserts strings into the elements it created
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        B1.add_op(Op('si', [2], offset=1, val='ore'))
        B1.add_op(Op('si', [3], offset=3, val='u'))
        B1.add_op(Op('si', [6], offset=0, val='dol'))
        B1.add_op(Op('si', [7], offset=0, val='si'))
        B1.set_id('B1')
        self.B1 = B1

        # B inserts strings into each original element, including elements A
        # had edited
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        B2.add_op(Op('si', [0], offset=0, val='t'))
        B2.add_op(Op('si', [1], offset=0, val='u'))
        B2.add_op(Op('si', [4], offset=0, val='v'))
        B2.add_op(Op('si', [5], offset=0, val='w'))
        B2.add_op(Op('si', [8], offset=0, val='x'))
        B2.add_op(Op('si', [9], offset=0, val='y'))
        B2.add_op(Op('si', [10], offset=0, val='z'))
        B2.set_id('B2')
        self.B2 = B2
    def test_one_delete_in_first_branch_reversed(self):
        """
        Same test as above, except branch B gets applied before branch A.
        """
        doc = self.doc1
        root = self.root1

        # construct branch A, which begins with a string delete, then
        # adds text
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('ad', [], offset=3, val=2))
        A0.set_id('1A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        v1 = ['0123', '4567']
        A1.add_op(Op('ai', [], offset=3, val=v1))
        doc.receive_changeset(A1)
        result = ['ABCD',
                  'EFGH',
                  'IJKL',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Branch B has common parent with A. B has three deletes, some of which
        # overlap the delete in branch A
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('ad', [], offset=2, val=2)
        B0.add_op(opB0)
        B0.set_id('0B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index > b_index
        result = ['ABCD',
                  'EFGH',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Partially overlaping delete
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('ad', [], offset=2, val=1)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        result = ['ABCD',
                  'EFGH',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Delete Range unaffected by branch A
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('ad', [], offset=1, val=1)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        result = ['ABCD',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B3 = Changeset(doc.get_id(), 'u2', [B2])
        vB3 = ['BBBBB', 'CCCCC']
        opB3 = Op('ai', [], offset=1, val=vB3)
        B3.add_op(opB3)
        B3.set_id('B3')
        doc.receive_changeset(B3)
        result = ['ABCD',
                  'BBBBB',
                  'CCCCC',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        B4 = Changeset(doc.get_id(), 'u2', [B3])
        vB4 = ['DDDDD', 'EEEEE']
        opB4 = Op('ai', [], offset=3, val=vB4)
        B4.add_op(opB4)
        B4.set_id('B4')
        doc.receive_changeset(B4)
        result = ['ABCD',
                  'BBBBB',
                  'CCCCC',
                  'DDDDD',
                  'EEEEE',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result
    def setup_method(self, method):
        s1 = ['ABCD',
              'EFGH',
              'IJKL',
              'MNOP',
              'QRST',
              'UVWX',
              'YZ']
        doc = Document(snapshot=s1)
        doc.HAS_EVENT_LOOP = False
        self.doc = doc
        self.root = self.doc.get_root_changeset()

        # establish the reusable changesets

        # Branch A does 1) String inserts 2) Array deletes, and 3) string
        # inserts
        A0 = Changeset(doc.get_id(), 'u1', [self.root])
        A0.add_op(Op('si', [0], offset=2, val='0123'))
        A0.set_id('A')
        self.A0 = A0

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('si', [0], offset=6, val='4'))
        self.A1 = A1

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [4], offset=1, val='567'))
        A2.add_op(Op('si', [6], offset=0, val='89'))
        self.A2 = A2

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        A3.add_op(Op('ad', [], offset=1, val=2))
        self.A3 = A3

        A4 = Changeset(doc.get_id(), 'u1', [A3])
        A4.add_op(Op('ad', [], offset=3, val=1))
        A4.add_op(Op('si', [1], offset=1, val='ab'))
        self.A4 = A4

        A5 = Changeset(doc.get_id(), 'u1', [A4])
        A5.add_op(Op('si', [3], offset=4, val='c'))
        self.A5 = A5

        # Branch B Also does string inserts, then array delets, then string
        # inserts
        B0 = Changeset(doc.get_id(), 'u2', [self.root])
        B0.add_op(Op('si', [4], offset=2, val='fg'))
        B0.add_op(Op('si', [6], offset=0, val='h'))
        B0.add_op(Op('si', [1], offset=4, val='ij'))
        B0.add_op(Op('si', [2], offset=2, val='k'))
        B0.add_op(Op('si', [5], offset=1, val='l'))
        B0.add_op(Op('si', [0], offset=3, val='m'))
        B0.add_op(Op('si', [3], offset=0, val='nop'))
        B0.set_id('B')
        self.B0 = B0

        # Now B deletes array elements
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        B1.add_op(Op('ad', [], offset=0, val=2))
        B1.add_op(Op('ad', [], offset=4, val=1))
        B1.set_id('B1')
        self.B1 = B1

        # B inserts strings into each original element, including elements A
        # had edited
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        B2.add_op(Op('si', [0], offset=3, val='qr'))
        B2.add_op(Op('si', [1], offset=4, val='st'))
        B2.add_op(Op('si', [2], offset=5, val='uv'))
        B2.add_op(Op('si', [3], offset=0, val='wxyz'))
        B2.set_id('B2')
        self.B2 = B2
    def setup_method(self, method):
        s1 = ['ABCD',
              'EFGH',
              'IJKL',
              'MNOP',
              'QRST',
              'UVWX',
              'YZ']
        doc = Document(snapshot=s1)
        doc.HAS_EVENT_LOOP = False
        self.doc = doc
        self.root = self.doc.get_root_changeset()

        # establish the reusable changesets

        # Branch A 1) deletes some from three strings in the array, 2) deletes
        # array elements and 3) deletes more partial strings
        A0 = Changeset(doc.get_id(), 'u1', [self.root])
        A0.add_op(Op('sd', [1], offset=2, val=2))  # deletes 'GH'
        A0.set_id('A')
        self.A0 = A0

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('sd', [3], offset=0, val=3))  # deletes 'MNO'
        self.A1 = A1

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('sd', [5], offset=1, val=1))  # 'V'
        self.A2 = A2

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        A3.add_op(Op('ad', [], offset=1, val=2))  # ['EF', 'IJKL']
        self.A3 = A3

        A4 = Changeset(doc.get_id(), 'u1', [A3])
        A4.add_op(Op('ad', [], offset=2, val=1))  # ['QRST']
        A4.add_op(Op('sd', [2], offset=2, val=1))  # 'X'
        A4.set_id('A4')
        self.A4 = A4

        A5 = Changeset(doc.get_id(), 'u1', [A4])
        A5.add_op(Op('sd', [3], offset=0, val=1))  # Deletes 'Y'
        self.A5 = A5

        # Branch B performs similar actions to branch A. It deletes text, some
        # of which is overlapping, 2) deletes elements, some overlap, and 3)
        # deletes more text
        B0 = Changeset(doc.get_id(), 'u2', [self.root])
        B0.add_op(Op('sd', [3], offset=1, val=2))  # Deletes 'NO'
        B0.add_op(Op('sd', [5], offset=1, val=2))  # Deletes 'VW'
        B0.add_op(Op('sd', [2], offset=2, val=1))  # 'K'
        B0.set_id('B')
        self.B0 = B0

        # Now B deletes strings
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        B1.add_op(Op('sd', [4], offset=0, val=2))  # 'QR'
        B1.add_op(Op('sd', [0], offset=2, val=1))  # 'C'
        B1.add_op(Op('sd', [6], offset=0, val=1))  # 'Y'
        B1.set_id('B1')
        self.B1 = B1

        # B deletes elements
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        B2.add_op(Op('ad', [], offset=0, val=2))  # ['ABD', 'EFGH']
        B2.add_op(Op('ad', [], offset=4, val=1))  # ['Z']
        B2.set_id('B2')
        self.B2 = B2

        # B deletes a bit more text
        B3 = Changeset(doc.get_id(), 'u2', [B2])
        B3.add_op(Op('sd', [2], offset=0, val=1))  # 'S'
        B3.add_op(Op('sd', [3], offset=0, val=2))  # 'UX'
        B3.set_id('B3')
        self.B3 = B3
    def test_complex_tree(self):
        """
        Some complex tree.

               C -- G -- H -------- K
              /         /            \
        A -- B -- D -- F -- J -- L -- M--
                   \       /
                    E --- I

        Should be:
        A B C G D E I F H K J L M
        """

        doc = Document(snapshot='')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()
        A = Changeset(doc.get_id(), "user0", [root])
        A.set_id('A')
        doc.receive_changeset(A)
        assert doc.get_ordered_changesets() == [root, A]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        B = Changeset(doc.get_id(),"user1",[A])
        B.set_id('b')
        doc.receive_changeset(B)
        assert doc.get_ordered_changesets() == [root,A,B]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        C = Changeset(doc.get_id(),"user3",[B])
        C.set_id('c')
        doc.receive_changeset(C)
        assert doc.get_ordered_changesets() == [root,A,B,C]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        D = Changeset(doc.get_id(),"user4",[B])
        D.set_id('d')
        doc.receive_changeset(D)
        assert doc.get_ordered_changesets() == [root,A,B,C,D]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        E = Changeset(doc.get_id(),"user5",[D])
        E.set_id('e')
        doc.receive_changeset(E)
        assert doc.get_ordered_changesets() == [root,A,B,C,D,E]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        F = Changeset(doc.get_id(),"user6",[D])
        F.set_id('f')
        doc.receive_changeset(F)
        assert doc.get_ordered_changesets() == [root,A,B,C,D,E,F]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        G = Changeset(doc.get_id(),"user5",[C])
        G.set_id('g')
        doc.receive_changeset(G)
        assert doc.get_ordered_changesets() == [root,A,B,C,G,D,E,F]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        H = Changeset(doc.get_id(),"user5",[G,F])
        H.set_id('h')
        doc.receive_changeset(H)
        assert doc.get_ordered_changesets() == [root,A,B,C,G,D,E,F,H]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        I = Changeset(doc.get_id(),"user6",[E])
        I.set_id('i')
        doc.receive_changeset(I)
        assert doc.get_ordered_changesets() == [root,A,B,C,G,D,E,I,F,H]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        J = Changeset(doc.get_id(),"user5",[I,F])
        J.set_id('j')
        doc.receive_changeset(J)
        assert doc.get_ordered_changesets() == [root,A,B,C,G,D,E,I,F,H,J]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        K = Changeset(doc.get_id(),"user5",[H])
        K.set_id('k')
        doc.receive_changeset(K)
        assert doc.get_ordered_changesets() == [root,A,B,C,G,D,E,I,F,H,K,J]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        L = Changeset(doc.get_id(),"user5",[J])
        L.set_id('l')
        doc.receive_changeset(L)
        assert doc.get_ordered_changesets() == [root,A,B,C,G,D,E,I,F,H,K,J,L]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        M = Changeset(doc.get_id(),"user5",[K,L])
        M.set_id('m')
        doc.receive_changeset(M)
        assert doc.get_ordered_changesets() == [root,A,B,C,G,D,E,I,F,H,K,J,L,M]
        assert doc.get_ordered_changesets() == doc.tree_to_list()
    def test_delete_overlaping_ranges2_reversed(self):
        """
        Same opperations as previous test, but order of branches is reversed
        (B now before A)
        """
        s = '0123456789TARGET'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # branch deletes the '234567' in three ops.
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=4, val=2))
        A0.set_id('1A0')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('sd', [], offset=3, val=2))
        A1.set_id('A1')
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('sd', [], offset=2, val=2))
        A2.set_id('A2')
        doc.receive_changeset(A2)
        assert doc.get_snapshot() == '0189TARGET'

        # Branch B has common parent with A. It deletes all but
        # 'TARGET' in three ops.

        # User Saw '0123456789TARGET', deleted '234', which branch A
        # already did.
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=2, val=3)
        B0.add_op(opB0)
        B0.set_id('0B0')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index > b_index
        assert doc.get_snapshot() == '0189TARGET'

        # User Saw '0156789TARGET', deleted '567', which branch A
        # already did.
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=2, val=3)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == '0189TARGET'

        # User Saw '0189TARGET', deleted '0189', which branch A has
        # NOT done.
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=0, val=4)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == 'TARGET'
 def test_set_id(self):
     cs0 = Changeset('doc_id', 'user_id', [])
     assert cs0.set_id('randomid')
     assert cs0.get_id() == 'randomid'
    def test_one_delete_in_first_branch(self):
        """
        There is one delete in the first branch, multiple in branch B, then
        each has more string inserts.
        """
        doc = self.doc1
        root = self.root1

        # construct branch A, which begins with a string delete, then
        # adds text
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('ad', [], offset=3, val=2))
        A0.set_id('A')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        v1 = ['0123', '4567']
        A1.add_op(Op('ai', [], offset=3, val=v1))
        doc.receive_changeset(A1)
        result = ['ABCD',
                  'EFGH',
                  'IJKL',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Branch B has common parent with A. B has three deletes, some of which
        # overlap the delete in branch A
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('ad', [], offset=2, val=2)
        B0.add_op(opB0)
        B0.set_id('B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index < b_index
        assert opB0.t_offset == 2
        assert opB0.t_val == 1
        result = ['ABCD',
                  'EFGH',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Partially overlaping delete
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('ad', [], offset=2, val=1)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        result = ['ABCD',
                  'EFGH',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result
        assert opB1.t_val == 0
        assert opB1.is_noop()

        # Delete Range unaffected by branch A
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('ad', [], offset=1, val=1)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        result = ['ABCD',
                  '0123',
                  '4567',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result
        assert opB2.t_offset == 1
        assert opB2.t_val == 1

        # Insert before the Delete Range
        B3 = Changeset(doc.get_id(), 'u2', [B2])
        vB3 = ['BBBBB', 'CCCCC']
        opB3 = Op('ai', [], offset=1, val=vB3)
        B3.add_op(opB3)
        B3.set_id('B3')
        doc.receive_changeset(B3)
        result = ['ABCD',
                  '0123',
                  '4567',
                  'BBBBB',
                  'CCCCC',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result

        # Insert After the Delete Range
        B4 = Changeset(doc.get_id(), 'u2', [B3])
        vB4 = ['DDDDD', 'EEEEE']
        opB4 = Op('ai', [], offset=3, val=vB4)
        B4.add_op(opB4)
        B4.set_id('B4')
        doc.receive_changeset(B4)
        result = ['ABCD',
                  '0123',
                  '4567',
                  'BBBBB',
                  'CCCCC',
                  'DDDDD',
                  'EEEEE',
                  'UVWX',
                  'YZ']
        assert doc.get_snapshot() == result
    def test_missing_changesets(self):
        doc = Document(snapshot='')
        doc.HAS_EVENT_LOOP = False
        assert doc.missing_changesets == set([])
        assert doc.pending_new_changesets == []
        
        root = doc.get_root_changeset()
        A = Changeset(doc.get_id(), "dummyuser", [root])
        doc.receive_changeset(A)
        assert doc.missing_changesets == set([])
        assert doc.pending_new_changesets == []

        # Just one Changeset gets put in pending list
        B = Changeset(doc.get_id(), "user1", ["C"])
        B.set_id("B")
        doc.receive_changeset(B)
        assert doc.get_ordered_changesets() == [root, A]
        assert doc.missing_changesets == set(["C"])
        assert doc.pending_new_changesets == [B]

        C = Changeset(doc.get_id(), "user1", [A])
        C.set_id("C")
        doc.receive_changeset(C)
        assert doc.missing_changesets == set([])
        assert doc.pending_new_changesets == []
        assert B.get_parents() == [C]
        assert doc.get_ordered_changesets() == [root, A, C, B]

        # Now a string of changesets put on pending list
        D = Changeset(doc.get_id(), "user1", ["G"])
        D.set_id("D")
        doc.receive_changeset(D)
        assert doc.missing_changesets == set(["G"])
        assert doc.pending_new_changesets == [D]
        assert doc.get_ordered_changesets() == [root, A, C, B]

        E = Changeset(doc.get_id(), "user1", ["D"])
        E.set_id("E")
        doc.receive_changeset(E)
        assert E.get_parents() == [D]
        assert doc.missing_changesets == set(["G"])
        assert doc.pending_new_changesets == [D, E]
        assert doc.get_ordered_changesets() == [root, A, C, B]

        F = Changeset(doc.get_id(), "user1", ["E"])
        F.set_id("F")
        doc.receive_changeset(F)
        assert doc.missing_changesets ==set( ["G"])
        assert doc.pending_new_changesets == [D, E, F]
        assert doc.get_ordered_changesets() == [root, A, C, B]

        G = Changeset(doc.get_id(), "user1", ["C"])
        G.set_id("G")
        doc.receive_changeset(G)
        assert doc.missing_changesets == set([])
        assert doc.pending_new_changesets == []
        assert doc.get_ordered_changesets() == [root, A, C, B, G, D, E, F]
        assert doc.get_ordered_changesets() == doc.tree_to_list()
    def setup_method(self, method):
        s1 = ['ABC',
              'DEF',
              'GHI',
              'JKL',
              'MNO',
              'PQR',
              'STU',
              'VWX',
              'YZ']
        doc = Document(snapshot=s1)
        doc.HAS_EVENT_LOOP = False
        self.doc = doc
        self.root = self.doc.get_root_changeset()

        # establish the reusable changesets

        # Branch A does 1) Array inserts 2) Array deletes 3) string inserts,
        # including into elements it created, 4) string deletes, including into
        # elements it created.
        A0 = Changeset(doc.get_id(), 'u1', [self.root])
        A0.add_op(Op('ai', [], offset=1, val=['012']))
        A0.add_op(Op('ai', [], offset=5, val=['345', '678']))
        A0.set_id('A')
        self.A0 = A0

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('ad', [], offset=2, val=1))  # Deletes ['DEF']
        A1.add_op(Op('ad', [], offset=3, val=2))  # Deletes ['JKL', '345']
        self.A1 = A1

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [3], offset=3, val='90'))  # to create '67890'
        A2.add_op(Op('si', [4], offset=1, val='abc'))   # to create 'MabcNo'
        self.A2 = A2

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        A3.add_op(Op('sd', [1], offset=1, val=1))  # to create '02'
        A3.add_op(Op('sd', [6], offset=0, val=2))  # to create 'U'
        self.A3 = A3

        # Branch B makes different edits, but of the same type and order as
        # branch A
        B0 = Changeset(doc.get_id(), 'u1', [self.root])
        B0.add_op(Op('ai', [], offset=0, val=['ghi', 'jkl']))
        B0.add_op(Op('ai', [], offset=5, val=['lmn', 'opq']))
        B0.set_id('B')
        self.B0 = B0

        B1 = Changeset(doc.get_id(), 'u1', [B0])
        B1.add_op(Op('ad', [], offset=1, val=2))  # Deletes ['jkl', 'ABC']
        B1.add_op(Op('ad', [], offset=10, val=1))  # Deletes ['YZ']
        self.B1 = B1

        B2 = Changeset(doc.get_id(), 'u1', [B1])
        B2.add_op(Op('sd', [4], offset=0, val=2))
        B2.add_op(Op('si', [4], offset=1, val='rst'))
        B2.add_op(Op('si', [4], offset=0, val='op'))  # to create 'opqrst'
        B2.add_op(Op('si', [5], offset=0, val='xyz'))  # to create 'xyzJKL'
        B2.add_op(Op('si', [6], offset=1, val='uvw'))  # to create 'MuvwNo'
        self.B2 = B2

        B3 = Changeset(doc.get_id(), 'u1', [B2])
        B3.add_op(Op('sd', [0], offset=2, val=1))  # to create 'gh'
        B3.add_op(Op('sd', [8], offset=1, val=1))  # to create 'SU'
        self.B3 = B3

        # Branch C does a different mix of ops. 1) string insert, 2) array
        # delete 3) array insert, 4) string delete
        C0 = Changeset(doc.get_id(), 'u1', [self.root])
        C0.add_op(Op('si', [2], offset=1, val='XXX'))  # create 'GXXXHI'
        C0.add_op(Op('si', [4], offset=2, val='YYY'))  # create 'MNYYYO'
        C0.set_id('C')
        self.C0 = C0

        C1 = Changeset(doc.get_id(), 'u1', [C0])
        C1.add_op(Op('ad', [], offset=4, val=2))  # Deletes ['MNYYYO', 'PQR']
        C1.add_op(Op('ad', [], offset=6, val=1))  # Deletes ['YZ']
        self.C1 = C1

        C2 = Changeset(doc.get_id(), 'u1', [C1])
        C2.add_op(Op('ai', [], offset=0, val=['lorem']))
        C2.add_op(Op('ai', [], offset=6, val=['ipsum']))
        self.C2 = C2

        C3 = Changeset(doc.get_id(), 'u1', [C2])
        C3.add_op(Op('sd', [5], offset=1, val=2))  # to delete 'TU'
        C3.add_op(Op('sd', [6], offset=0, val=2))  # to create 'sum'
        C3.add_op(Op('sd', [7], offset=2, val=1))  # to create 'VW'
        self.C3 = C3
    def test_overlaping_deletes_then_string_insert(self):
        """
        Both branches delete all the A's from the document. Additionally, one
        branch deletes all the Z's, the other all the X's. Then one inserts
        'The Quick ', the other branch 'Brown Fox.' Each uses several ops to
        achieve this.

        NOTE: This example is slightly convoluted and hard to follow. The test
        is only here because it was a reproducable failure that had no solution
        at the time.
        """
        s = 'ZZZZZZZAAAAAAAAAAAAAAAAAAAAAAAAAXXXXX'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=0, val=32))
        A0.add_op(Op('si', [], offset=0, val='T'))
        A0.set_id('A0')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('si', [], offset=1, val="h"))
        A1.add_op(Op('si', [], offset=2, val="e"))
        A1.add_op(Op('si', [], offset=3, val=" "))
        A1.set_id('A1')
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [], offset=4, val="Q"))
        A2.add_op(Op('si', [], offset=5, val="u"))
        A2.add_op(Op('si', [], offset=6, val="i"))
        A2.set_id('A2')
        doc.receive_changeset(A2)

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        A3.add_op(Op('si', [], offset=7, val="c"))
        A3.add_op(Op('si', [], offset=8, val="k"))
        A3.add_op(Op('si', [], offset=9, val=" "))
        A3.set_id('A3')
        doc.receive_changeset(A3)
        assert doc.get_snapshot() == 'The Quick XXXXX'

        # Branch B has common parent with A. It saw the original document and
        # deleted everything but the Y's
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=7, val=30)
        B0.add_op(opB0)
        B0.set_id('B0')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index < b_index
        assert doc.get_snapshot() == 'The Quick '

        # User B saw only the Y's and inserted text at the end.
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('si', [], offset=7, val='Brown Fox.')
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == 'The Quick Brown Fox.'
    def test_partial_overlaping_deletes(self):
        """
        At the same time, two users delete the same text, then insert
        different text. The union of the deletion ranges should be
        deleted and the two sets of inserted text should appear in
        full, side by side.

        Note: This test is fairly redundant. Just don't want to delete an old
        test.
        """
        doc = Document(snapshot='')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # create a common parrent for two divergent branches
        common_parent = Changeset(doc.get_id(), 'u1', [root])
        common_parent.add_op(Op('si', [], offset=0, val='12345678'))
        doc.receive_changeset(common_parent)
        assert doc.get_snapshot() == '12345678'

        # construct branch A, which begins with a string delete, then
        # inserts 'abcde' in three changesets.
        A0 = Changeset(doc.get_id(), 'u1', [common_parent])
        A0.add_op(Op('sd', [], offset=5, val=3))
        A0.add_op(Op('si', [], offset=5, val='abc'))
        A0.set_id('A')
        doc.receive_changeset(A0)
        assert doc.get_snapshot() == '12345abc'

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('si', [], offset=8, val='d'))
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [], offset=9, val='e'))
        doc.receive_changeset(A2)
        assert doc.get_snapshot() == '12345abcde'

        # Branch B has common parent with A. Insert some text at the
        # end, delete the same range branch A deleted, then insert
        # more text
        B0 = Changeset(doc.get_id(), 'u2', [common_parent])
        B0.add_op(Op('si', [], offset=8, val='f'))
        B0.set_id('B')
        doc.receive_changeset(B0)
        assert doc.get_snapshot() == '12345abcdef'

        # CS with overlapping delete range
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        B1.add_op(Op('sd', [], offset=0, val=8))
        B1.add_op(Op('si', [], offset=1, val='g'))
        B1.add_op(Op('si', [], offset=2, val='hi'))
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == 'abcdefghi'

        B2 = Changeset(doc.get_id(), 'u2', [B1])
        B2.add_op(Op('si', [], offset=4, val='jkl'))
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == 'abcdefghijkl'

        # combine these braches again
        C = Changeset(doc.get_id(), 'u2', [A2, B2])
        C.add_op(Op('si', [], offset=6, val=' XYZ '))
        doc.receive_changeset(C)
        assert doc.get_snapshot() == 'abcdef XYZ ghijkl'
    def test_overlaping_deletes_then_string_insert_reversed(self):
        """
        Same test as the previous one, except the order of the branches is
        reversed.
        """
        s = 'ZZZZZZZAAAAAAAAAAAAAAAAAAAAAAAAAXXXXX'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=0, val=32))
        A0.add_op(Op('si', [], offset=0, val='T'))
        A0.set_id('2')
        doc.receive_changeset(A0)

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('si', [], offset=1, val="h"))
        A1.add_op(Op('si', [], offset=2, val="e"))
        A1.add_op(Op('si', [], offset=3, val=" "))
        A1.set_id('A1')
        doc.receive_changeset(A1)

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('si', [], offset=4, val="Q"))
        A2.add_op(Op('si', [], offset=5, val="u"))
        A2.add_op(Op('si', [], offset=6, val="i"))
        A2.set_id('A2')
        doc.receive_changeset(A2)

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        A3.add_op(Op('si', [], offset=7, val="c"))
        A3.add_op(Op('si', [], offset=8, val="k"))
        A3.add_op(Op('si', [], offset=9, val=" "))
        A3.set_id('A3')
        doc.receive_changeset(A3)
        assert doc.get_snapshot() == 'The Quick XXXXX'

        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=7, val=30)
        B0.add_op(opB0)
        B0.set_id('1')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index > b_index
        assert doc.get_snapshot() == 'The Quick '

        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('si', [], offset=7, val='Brown Fox.')
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == 'Brown Fox.The Quick '
    def test_multiple_css_with_same_multiple_dependencies(self):
        """
        A -- B -- D--F -- G
              \    \/   /
               \   /\  /
                - C--E-
        Both F and E depend on D and C
        """
        doc = Document(snapshot='')
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()
        A = Changeset(doc.get_id(), "user0", [root])
        A.set_id('a')
        doc.receive_changeset(A)
        assert doc.get_ordered_changesets() == [root, A]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        B = Changeset(doc.get_id(), "user1", [A])
        B.set_id('b')
        doc.receive_changeset(B)
        assert doc.get_ordered_changesets() == [root, A, B]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        C = Changeset(doc.get_id(), "user3", [B])
        C.set_id('c')
        doc.receive_changeset(C)
        assert doc.get_ordered_changesets() == [root, A, B, C]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        D = Changeset(doc.get_id(), "user4", [B])
        D.set_id('d')
        doc.receive_changeset(D)
        assert doc.get_ordered_changesets() == [root, A, B, C, D]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        E = Changeset(doc.get_id(), "user5", [C, D])
        E.set_id('e')
        doc.receive_changeset(E)
        assert doc.get_ordered_changesets() == [root, A, B, C, D, E]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        F = Changeset(doc.get_id(), "user6", [C, D])
        F.set_id('f')
        doc.receive_changeset(F)
        assert set(doc.get_dependencies()) == set([E,F])
        assert doc.get_ordered_changesets() == [root, A,B,C,D,E,F]
        assert doc.get_ordered_changesets() == doc.tree_to_list()

        G = Changeset(doc.get_id(), "user5", [E,F])
        doc.receive_changeset(G)
        assert doc.get_dependencies() == [G]
        assert doc.get_ordered_changesets() == [root, A, B, C, D, E, F, G]
        assert doc.get_ordered_changesets() == doc.tree_to_list()
    def test_delete_overlaping_ranges(self):
        """
        Branch A and B independently delete the same characters in the middle
        of the string using different combinations of opperations. Then they
        perform other string operations that need to keep synced.
        """
        s = '0123456789'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # construct branch A, which deletes all but the first three
        # and last three characters.
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=4, val=2))
        A0.set_id('A')
        doc.receive_changeset(A0)
        assert doc.get_snapshot() == '01236789'

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('sd', [], offset=3, val=2))
        doc.receive_changeset(A1)
        assert doc.get_snapshot() == '012789'

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('sd', [], offset=2, val=2))
        doc.receive_changeset(A2)
        assert doc.get_snapshot() == '0189'

        # Branch B has common parent with A. It deletes all but '89'

        # User saw '0123456789' and deleted '3456', which was already
        # deleted in branch A.
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=3, val=4)
        B0.add_op(opB0)
        B0.set_id('B')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index < b_index
        assert doc.get_snapshot() == '0189'

        # User saw '012789' and deleted '27', which was already
        # deleted in branch A.
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=2, val=2)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == '0189'

        # Delete Range not known by branch A
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=1, val=2)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == '09'

        # combine these braches again
        C = Changeset(doc.get_id(), 'u2', [A2, B2])
        C.add_op(Op('si', [], offset=1, val='12345678'))
        doc.receive_changeset(C)
        assert doc.get_snapshot() == '0123456789'
    def setup_method(self, method):
        s1 = ['ABCD',
              'EFGH',
              'IJKL',
              'MNOP',
              'QRST',
              'UVWX',
              'YZ']
        doc = Document(snapshot=s1)
        doc.HAS_EVENT_LOOP = False
        self.doc = doc
        self.root = self.doc.get_root_changeset()

        # establish the reusable changesets

        # Branch A inserts new array elements, and deletes text from them.
        # It also deletes text from three existing elements, before any array
        # insert, in the middle, and after the last array insert.
        A0 = Changeset(doc.get_id(), 'u1', [self.root])
        vA0 = ['01234', '56789']
        A0.add_op(Op('ai', [], offset=2, val=vA0))
        A0.set_id('A')
        self.A0 = A0

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        vA1 = ['abcde', 'fghij']
        A1.add_op(Op('ai', [], offset=6, val=vA1))
        self.A1 = A1

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('sd', [2], offset=1, val=4))
        A2.add_op(Op('sd', [3], offset=0, val=3))
        self.A2 = A2

        A3 = Changeset(doc.get_id(), 'u1', [A2])
        A3.add_op(Op('sd', [6], offset=3, val=1))
        self.A3 = A3

        A4 = Changeset(doc.get_id(), 'u1', [A3])
        A4.add_op(Op('sd', [0], offset=0, val=3))
        A4.add_op(Op('sd', [4], offset=2, val=2))
        self.A4 = A4

        A5 = Changeset(doc.get_id(), 'u1', [A4])
        A5.add_op(Op('sd', [9], offset=1, val=2))
        self.A5 = A5

        # Branch B performs similar actions to branch A.
        # First inserts array elements
        B0 = Changeset(doc.get_id(), 'u2', [self.root])
        vB0a, vB0b = ['lorem', 'ipsum'], ['dolor', 'sit']
        opB0a = Op('ai', [], offset=2, val=vB0a)
        B0.add_op(opB0a)
        opB0b = Op('ai', [], offset=6, val=vB0b)
        B0.add_op(opB0b)
        B0.set_id('B')
        self.B0 = B0

        # Now B deletes parts of strings in the elements it created
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        B1.add_op(Op('sd', [2], offset=3, val=2))
        B1.add_op(Op('sd', [3], offset=2, val=2))
        B1.add_op(Op('sd', [6], offset=1, val=1))
        B1.add_op(Op('sd', [7], offset=0, val=3))
        B1.set_id('B1')
        self.B1 = B1

        # B deletes strings from each original element, including elements A
        # had edited
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        B2.add_op(Op('sd', [0], offset=1, val=3))
        B2.add_op(Op('sd', [1], offset=2, val=2))
        B2.add_op(Op('sd', [4], offset=0, val=1))
        B2.add_op(Op('sd', [5], offset=1, val=2))
        B2.add_op(Op('sd', [8], offset=2, val=1))
        B2.add_op(Op('sd', [9], offset=3, val=1))
        B2.add_op(Op('sd', [10], offset=0, val=1))
        B2.set_id('B2')
        self.B2 = B2
    def test_delete_overlaping_ranges2(self):
        """
        Branch A and B independently delete the same characters in the middle
        of the string using different combinations of opperations. Then they
        perform other string operations that need to keep synced.
        """
        s = '0123456789TARGET'
        doc = Document(snapshot=s)
        doc.HAS_EVENT_LOOP = False
        root = doc.get_root_changeset()

        # branch deletes the '234567' in three ops.
        A0 = Changeset(doc.get_id(), 'u1', [root])
        A0.add_op(Op('sd', [], offset=4, val=2))
        A0.set_id('A0')
        doc.receive_changeset(A0)
        assert doc.get_snapshot() == '01236789TARGET'

        A1 = Changeset(doc.get_id(), 'u1', [A0])
        A1.add_op(Op('sd', [], offset=3, val=2))
        A1.set_id('A1')
        doc.receive_changeset(A1)
        assert doc.get_snapshot() == '012789TARGET'

        A2 = Changeset(doc.get_id(), 'u1', [A1])
        A2.add_op(Op('sd', [], offset=2, val=2))
        A2.set_id('A2')
        doc.receive_changeset(A2)
        assert doc.get_snapshot() == '0189TARGET'

        # Branch B has common parent with A. It deletes all but
        # 'TARGET' in three ops.

        # User Saw '0123456789TARGET', deleted '234', which branch A
        # already did.
        B0 = Changeset(doc.get_id(), 'u2', [root])
        opB0 = Op('sd', [], offset=2, val=3)
        B0.add_op(opB0)
        B0.set_id('B0')
        doc.receive_changeset(B0)
        a_index = doc.get_ordered_changesets().index(A0)
        b_index = doc.get_ordered_changesets().index(B0)
        assert a_index < b_index
        assert doc.get_snapshot() == '0189TARGET'

        # User Saw '0156789TARGET', deleted '567', which branch A
        # already did.
        B1 = Changeset(doc.get_id(), 'u2', [B0])
        opB1 = Op('sd', [], offset=2, val=3)
        B1.add_op(opB1)
        B1.set_id('B1')
        doc.receive_changeset(B1)
        assert doc.get_snapshot() == '0189TARGET'

        # User Saw '0189TARGET', deleted '0189', which branch A has
        # NOT done.
        B2 = Changeset(doc.get_id(), 'u2', [B1])
        opB2 = Op('sd', [], offset=0, val=4)
        B2.add_op(opB2)
        B2.set_id('B2')
        doc.receive_changeset(B2)
        assert doc.get_snapshot() == 'TARGET'