133
k.add([0], ['line 1', 'diverged line'])
178
k.add('text2', [0], ['line 1', 'diverged line'])
135
180
self.assertEqual(k.annotate(2),
137
182
(2, 'diverged line')])
140
['line 1', 'middle line', 'line 2'])
184
text3 = ['line 1', 'middle line', 'line 2']
189
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
191
self.log("k._weave=" + pformat(k._weave))
142
193
self.assertEqual(k.annotate(3),
144
195
(3, 'middle line'),
198
# now multiple insertions at different places
201
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
203
self.assertEqual(k.annotate(4),
213
class DeleteLines(TestBase):
214
"""Deletion of lines from existing text.
216
Try various texts all based on a common ancestor."""
220
base_text = ['one', 'two', 'three', 'four']
222
k.add('text0', [], base_text)
224
texts = [['one', 'two', 'three'],
225
['two', 'three', 'four'],
227
['one', 'two', 'three', 'four'],
232
ver = k.add('text%d' % i,
236
self.log('final weave:')
237
self.log('k._weave=' + pformat(k._weave))
239
for i in range(len(texts)):
240
self.assertEqual(k.get(i+1),
246
class SuicideDelete(TestBase):
247
"""Invalid weave which tries to add and delete simultaneously."""
253
k._weave = [('{', 0),
260
################################### SKIPPED
261
# Weave.get doesn't trap this anymore
264
self.assertRaises(WeaveFormatError,
270
class CannedDelete(TestBase):
271
"""Unpack canned weave with deleted lines."""
278
k._weave = [('{', 0),
281
'line to be deleted',
287
self.assertEqual(k.get(0),
289
'line to be deleted',
293
self.assertEqual(k.get(1),
300
class CannedReplacement(TestBase):
301
"""Unpack canned weave with deleted lines."""
305
k._parents = [frozenset(),
308
k._weave = [('{', 0),
311
'line to be deleted',
320
self.assertEqual(k.get(0),
322
'line to be deleted',
326
self.assertEqual(k.get(1),
334
class BadWeave(TestBase):
335
"""Test that we trap an insert which should not occur."""
339
k._parents = [frozenset(),
341
k._weave = ['bad line',
345
' added in version 1',
354
################################### SKIPPED
355
# Weave.get doesn't trap this anymore
359
self.assertRaises(WeaveFormatError,
364
class BadInsert(TestBase):
365
"""Test that we trap an insert which should not occur."""
369
k._parents = [frozenset(),
374
k._weave = [('{', 0),
377
' added in version 1',
385
# this is not currently enforced by get
386
return ##########################################
388
self.assertRaises(WeaveFormatError,
392
self.assertRaises(WeaveFormatError,
397
class InsertNested(TestBase):
398
"""Insertion with nested instructions."""
402
k._parents = [frozenset(),
407
k._weave = [('{', 0),
410
' added in version 1',
419
self.assertEqual(k.get(0),
423
self.assertEqual(k.get(1),
425
' added in version 1',
429
self.assertEqual(k.get(2),
434
self.assertEqual(k.get(3),
436
' added in version 1',
443
class DeleteLines2(TestBase):
444
"""Test recording revisions that delete lines.
446
This relies on the weave having a way to represent lines knocked
447
out by a later revision."""
451
k.add('text0', [], ["line the first",
456
self.assertEqual(len(k.get(0)), 4)
458
k.add('text1', [0], ["line the first",
461
self.assertEqual(k.get(1),
465
self.assertEqual(k.annotate(1),
466
[(0, "line the first"),
149
471
class IncludeVersions(TestBase):
150
472
"""Check texts that are stored across multiple revisions.
152
Here we manually create a knit with particular encoding and make
474
Here we manually create a weave with particular encoding and make
153
475
sure it unpacks properly.
155
477
Text 0 includes nothing; text 1 includes text 0 and adds some
199
530
"alternative second line"])
203
from unittest import TestSuite, TestLoader
208
suite.addTest(tl.loadTestsFromModule(testknit))
210
return int(not testsweet.run_suite(suite)) # for shell 0=true
532
self.assertEqual(list(k.inclusions([2])),
537
class ReplaceLine(TestBase):
541
text0 = ['cheddar', 'stilton', 'gruyere']
542
text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
544
k.add('text0', [], text0)
545
k.add('text1', [0], text1)
547
self.log('k._weave=' + pformat(k._weave))
549
self.assertEqual(k.get(0), text0)
550
self.assertEqual(k.get(1), text1)
554
class Merge(TestBase):
555
"""Storage of versions that merge diverged parents"""
560
['header', '', 'line from 1'],
561
['header', '', 'line from 2', 'more from 2'],
562
['header', '', 'line from 1', 'fixup line', 'line from 2'],
565
k.add('text0', [], texts[0])
566
k.add('text1', [0], texts[1])
567
k.add('text2', [0], texts[2])
568
k.add('merge', [0, 1, 2], texts[3])
570
for i, t in enumerate(texts):
571
self.assertEqual(k.get(i), t)
573
self.assertEqual(k.annotate(3),
581
self.assertEqual(list(k.inclusions([3])),
584
self.log('k._weave=' + pformat(k._weave))
586
self.check_read_write(k)
589
class Conflicts(TestBase):
590
"""Test detection of conflicting regions during a merge.
592
A base version is inserted, then two descendents try to
593
insert different lines in the same place. These should be
594
reported as a possible conflict and forwarded to the user."""
599
k.add([], ['aaa', 'bbb'])
600
k.add([0], ['aaa', '111', 'bbb'])
601
k.add([1], ['aaa', '222', 'bbb'])
603
merged = k.merge([1, 2])
605
self.assertEquals([[['aaa']],
611
class NonConflict(TestBase):
612
"""Two descendants insert compatible changes.
614
No conflict should be reported."""
619
k.add([], ['aaa', 'bbb'])
620
k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
621
k.add([1], ['aaa', 'ccc', 'bbb', '222'])
627
class AutoMerge(TestBase):
631
texts = [['header', 'aaa', 'bbb'],
632
['header', 'aaa', 'line from 1', 'bbb'],
633
['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
636
k.add('text0', [], texts[0])
637
k.add('text1', [0], texts[1])
638
k.add('text2', [0], texts[2])
640
self.log('k._weave=' + pformat(k._weave))
642
m = list(k.mash_iter([0, 1, 2]))
648
'line from 2', 'more from 2'])
652
class Khayyam(TestBase):
653
"""Test changes to multi-line texts, and read/write"""
656
"""A Book of Verses underneath the Bough,
657
A Jug of Wine, a Loaf of Bread, -- and Thou
658
Beside me singing in the Wilderness --
659
Oh, Wilderness were Paradise enow!""",
661
"""A Book of Verses underneath the Bough,
662
A Jug of Wine, a Loaf of Bread, -- and Thou
663
Beside me singing in the Wilderness --
664
Oh, Wilderness were Paradise now!""",
666
"""A Book of poems underneath the tree,
667
A Jug of Wine, a Loaf of Bread,
669
Beside me singing in the Wilderness --
670
Oh, Wilderness were Paradise now!
674
"""A Book of Verses underneath the Bough,
675
A Jug of Wine, a Loaf of Bread,
677
Beside me singing in the Wilderness --
678
Oh, Wilderness were Paradise now!""",
680
texts = [[l.strip() for l in t.split('\n')] for t in rawtexts]
686
ver = k.add('text%d' % i,
691
self.log("k._weave=" + pformat(k._weave))
693
for i, t in enumerate(texts):
694
self.assertEqual(k.get(i), t)
696
self.check_read_write(k)
700
class MergeCases(TestBase):
701
def doMerge(self, base, a, b, mp):
702
from cStringIO import StringIO
703
from textwrap import dedent
709
w.add('text0', [], map(addcrlf, base))
710
w.add('text1', [0], map(addcrlf, a))
711
w.add('text2', [0], map(addcrlf, b))
713
self.log('weave is:')
716
self.log(tmpf.getvalue())
718
self.log('merge plan:')
719
p = list(w.plan_merge(1, 2))
720
for state, line in p:
722
self.log('%12s | %s' % (state, line[:-1]))
726
mt.writelines(w.weave_merge(p))
728
self.log(mt.getvalue())
730
mp = map(addcrlf, mp)
731
self.assertEqual(mt.readlines(), mp)
734
def testOneInsert(self):
740
def testSeparateInserts(self):
741
self.doMerge(['aaa', 'bbb', 'ccc'],
742
['aaa', 'xxx', 'bbb', 'ccc'],
743
['aaa', 'bbb', 'yyy', 'ccc'],
744
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
746
def testSameInsert(self):
747
self.doMerge(['aaa', 'bbb', 'ccc'],
748
['aaa', 'xxx', 'bbb', 'ccc'],
749
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
750
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
752
def testOverlappedInsert(self):
753
self.doMerge(['aaa', 'bbb'],
754
['aaa', 'xxx', 'yyy', 'bbb'],
755
['aaa', 'xxx', 'bbb'],
756
['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
758
# really it ought to reduce this to
759
# ['aaa', 'xxx', 'yyy', 'bbb']
762
def testClashReplace(self):
763
self.doMerge(['aaa'],
766
['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
768
def testNonClashInsert(self):
769
self.doMerge(['aaa'],
772
['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
774
self.doMerge(['aaa'],
780
def testDeleteAndModify(self):
781
"""Clashing delete and modification.
783
If one side modifies a region and the other deletes it then
784
there should be a conflict with one side blank.
787
#######################################
788
# skippd, not working yet
791
self.doMerge(['aaa', 'bbb', 'ccc'],
792
['aaa', 'ddd', 'ccc'],
794
['<<<<', 'aaa', '====', '>>>>', 'ccc'])
797
class JoinWeavesTests(TestBase):
799
super(JoinWeavesTests, self).setUp()
800
self.weave1 = Weave()
801
self.lines1 = ['hello\n']
802
self.lines3 = ['hello\n', 'cruel\n', 'world\n']
803
self.weave1.add('v1', [], self.lines1)
804
self.weave1.add('v2', [0], ['hello\n', 'world\n'])
805
self.weave1.add('v3', [1], self.lines3)
807
def test_join_empty(self):
808
"""Join two empty weaves."""
809
eq = self.assertEqual
813
eq(w1.numversions(), 0)
815
def test_join_empty_to_nonempty(self):
816
"""Join empty weave onto nonempty."""
817
self.weave1.join(Weave())
818
self.assertEqual(len(self.weave1), 3)
820
def test_join_unrelated(self):
821
"""Join two weaves with no history in common."""
823
wb.add('b1', [], ['line from b\n'])
826
eq = self.assertEqual
828
eq(sorted(list(w1.iter_names())),
829
['b1', 'v1', 'v2', 'v3'])
831
def test_join_related(self):
832
wa = self.weave1.copy()
833
wb = self.weave1.copy()
834
wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
835
wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
836
eq = self.assertEquals
841
eq(wa.get_lines('b1'),
842
['hello\n', 'pale blue\n', 'world\n'])
844
def test_join_parent_disagreement(self):
845
"""Cannot join weaves with different parents for a version."""
848
wa.add('v1', [], ['hello\n'])
850
wb.add('v1', ['v0'], ['hello\n'])
851
self.assertRaises(WeaveError,
854
def test_join_text_disagreement(self):
855
"""Cannot join weaves with different texts for a version."""
858
wa.add('v1', [], ['hello\n'])
859
wb.add('v1', [], ['not\n', 'hello\n'])
860
self.assertRaises(WeaveError,
863
def test_join_unordered(self):
864
"""Join weaves where indexes differ.
866
The source weave contains a different version at index 0."""
867
wa = self.weave1.copy()
869
wb.add('x1', [], ['line from x1\n'])
870
wb.add('v1', [], ['hello\n'])
871
wb.add('v2', ['v1'], ['hello\n', 'world\n'])
873
eq = self.assertEquals
874
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
875
eq(wa.get_text('x1'), 'line from x1\n')
877
def test_reweave_with_empty(self):
879
wr = reweave(self.weave1, wb)
880
eq = self.assertEquals
881
eq(sorted(wr.iter_names()), ['v1', 'v2', 'v3'])
882
eq(wr.get_lines('v3'), ['hello\n', 'cruel\n', 'world\n'])
883
self.weave1.reweave(wb)
884
self.assertEquals(wr, self.weave1)
886
def test_join_with_ghosts_raises_parent_mismatch(self):
887
wa = self.weave1.copy()
889
wb.add('x1', [], ['line from x1\n'])
890
wb.add('v1', [], ['hello\n'])
891
wb.add('v2', ['v1', 'x1'], ['hello\n', 'world\n'])
892
self.assertRaises(errors.WeaveParentMismatch, wa.join, wb)
894
def test_reweave_with_ghosts(self):
895
"""Join that inserts parents of an existing revision.
897
This can happen when merging from another branch who
898
knows about revisions the destination does not. In
899
this test the second weave knows of an additional parent of
900
v2. Any revisions which are in common still have to have the
902
wa = self.weave1.copy()
904
wb.add('x1', [], ['line from x1\n'])
905
wb.add('v1', [], ['hello\n'])
906
wb.add('v2', ['v1', 'x1'], ['hello\n', 'world\n'])
908
eq = self.assertEquals
909
eq(sorted(wc.iter_names()), ['v1', 'v2', 'v3', 'x1',])
910
eq(wc.get_text('x1'), 'line from x1\n')
911
eq(wc.get_lines('v2'), ['hello\n', 'world\n'])
912
eq(wc.parent_names('v2'), ['v1', 'x1'])
913
self.weave1.reweave(wb)
914
self.assertEquals(wc, self.weave1)
213
917
if __name__ == '__main__':
920
sys.exit(unittest.main())