def test_overlaping_deletes(delete_index, single_first): original_snapshot = 'abcdefghijklmnopqrstuvwxyz' doc = Document(snapshot=original_snapshot) doc.HAS_EVENT_LOOP = False z_index = 14 if delete_index >= 5 and delete_index < 15: z_index = 15 if delete_index == 25: z_index = 15 single_id = 'A' if single_first else 'B' range_id = 'B' if single_first else 'A' css_data = [ ('sd', delete_index, 1, ['root'], single_id), ('sd', 5, 10, ['root'], range_id), # delete 'fghijklmno' ('si', z_index, 'Z', ['A', 'B'], 'C'), ('sd', 10, 10, ['root'], '0'), # delete 'klmnopqrst' ] css = build_changesets_from_tuples(css_data, doc) for cs in css: doc.receive_changeset(cs) resulting_snapshot = 'abcdeuvwxyZz' l = original_snapshot[delete_index] resulting_snapshot = resulting_snapshot.replace(l, '') assert doc.get_snapshot() == resulting_snapshot
def test_array_delete(self): doc0 = Document() doc0.snapshot.set_snapshot([]) doc1 = self.doc1 doc2 = self.doc2 # can technically delete nothing from empty list. why not op1 = Op('ad', [], offset=0, val=0) doc0.apply_op(op1) assert doc0.get_snapshot() == [] # remove one from list op2 = Op('ad', [], offset=1, val=1) doc2.apply_op(op2) assert doc2.get_value([1]) == 'normal, ol string' # from nested lists op3 = Op('ad', [2], offset=1, val=1) doc2.apply_op(op3) assert doc2.get_value([2]) == [['multi'],['array']] # delete multiple elements op4 = Op('ad', [], offset=0, val=4) doc2.apply_op(op4) assert doc2.get_snapshot() == [None, 42] # delete last in list: op5 = Op('ad', [], offset=1, val=1) doc2.apply_op(op5) assert doc2.get_snapshot() == [None] # in dicts op6 = Op('ad', ['fifth'], offset=2, val=2) doc1.apply_op(op6) assert doc1.get_value(['fifth']) == [55,66]
def build_random_initial_document(self): snapshot = ''.join(random.sample(self.remaining_chars, 100)) doc = Document(snapshot=snapshot) doc.get_ordered_changesets()[0].ops[0].cheat = "" for i, char in enumerate(snapshot): before = list(snapshot[:i]) after = list(snapshot[i + 1:]) self.results[char]['before'] = before self.results[char]['after'] = after self.remaining_chars = self.remaining_chars.replace(char, '') doc.HAS_EVENT_LOOP = False self.doc = doc return doc
def test_insert_within_many_overlaping_deletes(): doc = Document(snapshot='abcdefghij') doc.HAS_EVENT_LOOP = False css_data = [ ('si', 5, 'X', ['root'], 'A'), # should result in abcdeXfghij ('sd', 4, 2, ['root'], 'B'), # deletes 'ef' ('sd', 3, 4, ['root'], 'C'), # deletes 'defg' ('si', 5, 'J', ['B', 'C'], 'D'), # insert 'J' right before 'j' ('sd', 3, 4, ['root'], 'E') # deletes 'defg' ] css = build_changesets_from_tuples(css_data, doc) for cs in css: doc.receive_changeset(cs) assert doc.get_snapshot() == 'abchiJj'
def test_initial_dependency(self): doc = Document(snapshot='') doc.HAS_EVENT_LOOP = False assert doc.get_open_changeset() == None assert doc.get_ordered_changesets() == [doc.get_root_changeset()] assert doc.get_dependencies() == [doc.get_root_changeset()]
def test_boolean_negation(self): doc0 = Document() doc0.snapshot.set_snapshot(False) doc1 = self.doc1 doc2 = self.doc2 # whole document is a boolean. Just change that op1 = Op('bn', []) doc0.apply_op(op1) assert doc0.get_snapshot() is True doc0.apply_op(op1) assert doc0.get_snapshot() is False # boolean at some key/index op2 = Op('bn', [4]) doc2.apply_op(op2) assert doc2.get_value([4]) == False doc2.apply_op(op2) assert doc2.get_value([4]) == True # boolean along some path path3 = ['fifth',2,'sixth'] doc1.apply_op(Op('set', path3, val=True)) op3 = Op('bn', path3) doc1.apply_op(op3) assert doc1.get_value(path3) == False doc1.apply_op(op3) assert doc1.get_value(path3) == True
def setup_method(self, method): s1 = ['ABCD', 'EFGH', 'IJKL', 'MNOP', 'QRST', 'UVWX', 'YZ'] self.doc1 = Document(snapshot=s1) self.doc1.HAS_EVENT_LOOP = False self.root1 = self.doc1.get_root_changeset()
def test_number_add(self): doc0 = Document() doc0.snapshot.set_snapshot(0) doc1 = self.doc1 doc2 = self.doc2 # whole document is just a number. Alter it. op1 = Op('na', [], val=5) doc0.apply_op(op1) assert doc0.get_snapshot() == 5 # number deeper in doc op2 = Op('na', ['fifth',1], val=-100) doc1.apply_op(op2) assert doc1.get_value(['fifth',1]) == -34 # funkier numbers accepted by JSON # int frac op3 = Op('na', ['fifth',1], val=34.5) doc1.apply_op(op3) assert doc1.get_value(['fifth',1]) == 0.5
def test_insert_within_overlaping_deletes(insert_index, resulting_index, insert_first, low_index_in_0_branch): doc = Document(snapshot='abcdefghijklmnopqrstuvwxyz') doc.HAS_EVENT_LOOP = False first_delete_index = 10 if low_index_in_0_branch else 5 zero_branch_index = 5 if low_index_in_0_branch else 10 z_index = 16 if insert_index > first_delete_index and \ insert_index < first_delete_index + 10: z_index = 15 if insert_index == 26: z_index = 15 insert_id = 'A' if insert_first else 'B' delete_id = 'B' if insert_first else 'A' css_data = [ ('si', insert_index, 'X', ['root'], insert_id), # insert letter X ('sd', first_delete_index, 10, ['root'], delete_id), # delete 'fghijklmno' ('si', z_index, 'Z', ['A', 'B'], 'C'), # at a 'Z' right before the # existing 'z' ('sd', zero_branch_index, 10, ['root'], '0'), # delete 'klmnopqrst' ] css = build_changesets_from_tuples(css_data, doc) for cs in css: doc.receive_changeset(cs) resulting_snapshot = 'abcdeuvwxyZz' if not resulting_index is None: rs = resulting_snapshot ri = resulting_index resulting_snapshot = rs[:ri] + 'X' + rs[ri:] assert doc.get_snapshot() == resulting_snapshot
def test_random(self): """ Randomly insert the changsets into new documents. """ NUMBER_OF_ITERATIONS = 40 iteration = 0 while iteration < NUMBER_OF_ITERATIONS: doc = Document(snapshot='0123456789') self.create_changesets(doc) css = [self.A1, self.A0, self.B1, self.B0, self.C1, self.C0] while css: cs = random.choice(css) doc.receive_changeset(cs) css.remove(cs) # this document will not pull automaticly. flip a coin to see # if it should be pulled on this loop if random.random() > .5: doc.pull_from_pending_list() # pull any remaining changesets from pending list doc.pull_from_pending_list() # This is the base of the resulting document. Then build up the A's # B's and C's. result = '01289' for cs in doc.get_ordered_changesets(): if cs is self.A1: result += 'AAAAA' elif cs is self.B1: result += 'BBBBB' elif cs is self.C1: result += 'CCCCC' assert doc.get_snapshot() == result iteration += 1
def setup_method(self, method): doc = Document(snapshot='0123') doc.HAS_EVENT_LOOP = False self.doc = doc root = doc.get_root_changeset() # A0 = Changeset(doc.get_id(), 'u1', [root]) A0.add_op(Op('si', [], offset=1, val='ab')) self.A0 = A0 B0 = Changeset(doc.get_id(), 'u2', [root]) B0.add_op(Op('si', [], offset=1, val='cd')) self.B0 = B0 A1 = Changeset(doc.get_id(), 'u1', [A0]) A1.add_op(Op('sd', [], offset=2, val=2)) self.A1 = A1
def test_random_changesets(self): """ Create a bunch of changesets with random dependencies, add them all to the doc, and make sure the resulting order is the same as when done with tree_to_list(). """ # NOTE: Testing large numbers of changesets is slow, so dropping # the number for normal testing. # NUMBER_OF_CHANGESETS = 5000 NUMBER_OF_CHANGESETS = 200 doc = Document(snapshot='') doc.HAS_EVENT_LOOP = False assert doc.get_ordered_changesets() == doc.tree_to_list() i = 1 while i < NUMBER_OF_CHANGESETS: i += 1 doc, cs = add_random_changeset(doc) assert len(doc.get_ordered_changesets()) == i assert doc.get_ordered_changesets() == doc.tree_to_list()
def setup_method(self, method): doc = Document(snapshot='0123') doc.HAS_EVENT_LOOP = False self.doc = doc root = doc.get_root_changeset() # A0 = Changeset(doc.get_id(), 'u1', [root]) A0.add_op(Op('si', [], offset=1, val='ab')) self.A0 = A0 B0 = Changeset(doc.get_id(), 'u2', [root]) B0.add_op(Op('si', [], offset=1, val='cd')) self.B0 = B0 # instead of deleting range, A will delete character one at a time. A1 = Changeset(doc.get_id(), 'u1', [A0]) A1.add_op(Op('sd', [], offset=2, val=1)) A1.add_op(Op('sd', [], offset=2, val=1)) self.A1 = A1
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_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_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_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_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_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'
class TestArraysInTwoBranches: def setup_method(self, method): s1 = ['ABCD', 'EFGH', 'IJKL', 'MNOP', 'QRST', 'UVWX', 'YZ'] self.doc1 = Document(snapshot=s1) self.doc1.HAS_EVENT_LOOP = False self.root1 = self.doc1.get_root_changeset() 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_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 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 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_hazard_for_insert_when_future_insert_is_deleted(self): doc = Document(snapshot="05IiYTALOC") doc.HAS_EVENT_LOOP = False css_data = [ ("si", 8, "3Z", ["root"], "ba4"), # 05IiYTAL 3Z OC ("si", 2, "XkGu", ["ba4"], "179"), # 05 XkGu IiYTAL3ZOC ("si", 9, "Mpb", ["179"], "9b5"), # 05XkGuIiY Mpb TAL3ZOC ("si", 18, "6wc2", ["9b5"], "133"), # 05XkGuIiYMpbTAL3ZO 6wc2 C ("si", 4, "xUg", ["133"], "36f"), # 05Xk xUg GuIiYMpbTAL3ZO6wc2C ("si", 4, "NdKa", ["36f"], "6ad"), # 05Xk NdKa xUgGuIiYMpbTAL3ZO6wc2C ("si", 15, "hE", ["6ad"], "ebe"), # 05XkNdKaxUgGuIi hE YMpbTAL3ZO6wc2C ("si", 23, "H7", ["ebe"], "2b6"), # 05XkNdKaxUgGuIihEYMpbTA H7 L3ZO6wc2C ("si", 34, "yD", ["2b6"], "c0c"), # 05XkNdKaxUgGuIihEYMpbTAH7L3ZO6wc2C yD ("sd", 5, 5, ["ba4"], "b31"), # delete TAL3Z ("si", 4, "9R", ["b31"], "96a"), # 05Ii 9R YOC ("sd", 2, 4, ["96a"], "74e"), # delete Ii9R ("si", 1, "41", ["74e"], "2f0"), # 0 41 5YOC ("si", 0, "oQmB", ["2f0"], "ffc"), # oQmB 0415YOC ("sd", 2, 4, ["ffc"], "da8"), # delete mB04 ("si", 3, "rn", ["da8"], "36d"), # oQ1 rn 5YOC ("si", 0, "qtWS", ["74e"], "d96"), # qtWS 05YOC' ("si", 2, "Psj", ["d96"], "319"), # qt Psj WS05YOC ("si", 6, "FV", ["319"], "20e"), # qtPsjW FV S05YOC ("si", 9, "lJve", ["20e"], "5ef"), # qtPsjWFVS lJve 05YOC ("si", 8, "zf8", ["5ef", "36d"], "c73"), # oQ1rn5YO zf8 C ("sd", 29, 3, ["c0c", "c73"], "5f3"), ] self.css = build_changesets_from_tuples(css_data, doc) get_cs = self.get_cs doc.receive_changeset(self.css[0]) assert doc.get_snapshot() == "05IiYTAL3ZOC" if True: cs = get_cs("179") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkGuIiYTAL3ZOC" cs = get_cs("9b5") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkGuIiYMpbTAL3ZOC" cs = get_cs("133") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkGuIiYMpbTAL3ZO6wc2C" cs = get_cs("36f") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkxUgGuIiYMpbTAL3ZO6wc2C" cs = get_cs("6ad") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkNdKaxUgGuIiYMpbTAL3ZO6wc2C" cs = get_cs("ebe") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkNdKaxUgGuIihEYMpbTAL3ZO6wc2C" cs = get_cs("2b6") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkNdKaxUgGuIihEYMpbTAH7L3ZO6wc2C" cs = get_cs("c0c") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkNdKaxUgGuIihEYMpbTAH7L3ZO6wc2CyD" cs = get_cs("b31") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkNdKaxUgGuIihEYMpbO6wc2CyD" cs = get_cs("96a") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkNdKaxUgGuIihE9RYMpbO6wc2CyD" cs = get_cs("74e") doc.receive_changeset(cs) assert doc.get_snapshot() == "05XkNdKaxUgGuYMpbO6wc2CyD" if True: cs = get_cs("2f0") doc.receive_changeset(cs) assert doc.get_snapshot() == "0415XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("ffc") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQmB0415XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("da8") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQ15XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("36d") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQ1rn5XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("d96") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQ1rn5XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("319") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQ1rn5XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("20e") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQ1rn5XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("5ef") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQ1rn5XkNdKaxUgGuYMpbO6wc2CyD" cs = get_cs("c73") doc.receive_changeset(cs) assert doc.get_snapshot() == "oQ1rn5XkNdKaxUgGuYMpbO6wc2zf8CyD"
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 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_move_in_root(self): doc = Document() doc.snapshot.set_snapshot('ABCDEFGHIJKLMNOPQRS') # move from higher index to lower op1 = Op('sm', [], offset=10, val=4, dest_path=[], dest_offset=2) doc.apply_op(op1) assert doc.get_snapshot() == 'ABKLMNCDEFGHIJOPQRS' # move from lower index to higher op2 = Op('sm', [], offset=2, val=4, dest_path=[], dest_offset=10) doc.apply_op(op2) assert doc.get_snapshot() == 'ABCDEFGHIJKLMNOPQRS' # original value # move by one index op3 = Op('sm', [], offset=0, val=6, dest_path=[], dest_offset=1) doc.apply_op(op3) assert doc.get_snapshot() == 'GABCDEFHIJKLMNOPQRS' op4 = Op('sm', [], offset=10, val=9, dest_path=[], dest_offset=0) doc.apply_op(op4) assert doc.get_snapshot() == 'KLMNOPQRSGABCDEFHIJ'
def test_sequential_changeset(self): doc = Document(snapshot='') doc.HAS_EVENT_LOOP = False root = doc.get_root_changeset() cs0 = Changeset(doc.get_id(), "dummyuser", [root]) rid = root.get_id() assert doc.receive_changeset(cs0) assert rid == doc.get_root_changeset().get_id() assert root.get_children() == [cs0] assert doc.get_dependencies() == [cs0] assert doc.get_ordered_changesets() == [root, cs0] cs1 = Changeset(doc.get_id(), "user1", [cs0]) assert doc.receive_changeset(cs1) assert doc.get_ordered_changesets() == [root, cs0, cs1] assert doc.get_dependencies() == [cs1] cs2 = Changeset(doc.get_id(), "user1", [cs1]) assert doc.receive_changeset(cs2) assert doc.get_ordered_changesets() == [root, cs0, cs1, cs2] assert doc.get_dependencies() == [cs2] assert doc.get_ordered_changesets() == doc.tree_to_list()
def test_get_id(self): doc = Document('abc456') assert doc.get_id() == 'abc456'
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_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 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