78
74
class StoreText(TestBase):
79
75
"""Store and retrieve a simple text."""
81
def test_storing_text(self):
83
idx = k.add_lines('text0', [], TEXT_0)
84
self.assertEqual(k.get_lines(idx), TEXT_0)
78
idx = k.add('text0', [], TEXT_0)
79
self.assertEqual(k.get(idx), TEXT_0)
85
80
self.assertEqual(idx, 0)
88
84
class AnnotateOne(TestBase):
91
k.add_lines('text0', [], TEXT_0)
92
self.assertEqual(k.annotate('text0'),
93
[('text0', TEXT_0[0])])
87
k.add('text0', [], TEXT_0)
88
self.assertEqual(k.annotate(0),
96
92
class StoreTwo(TestBase):
100
idx = k.add_lines('text0', [], TEXT_0)
96
idx = k.add('text0', [], TEXT_0)
101
97
self.assertEqual(idx, 0)
103
idx = k.add_lines('text1', [], TEXT_1)
99
idx = k.add('text1', [], TEXT_1)
104
100
self.assertEqual(idx, 1)
106
self.assertEqual(k.get_lines(0), TEXT_0)
107
self.assertEqual(k.get_lines(1), TEXT_1)
110
class GetSha1(TestBase):
111
def test_get_sha1(self):
102
self.assertEqual(k.get(0), TEXT_0)
103
self.assertEqual(k.get(1), TEXT_1)
107
class AddWithGivenSha(TestBase):
109
"""Add with caller-supplied SHA-1"""
113
k.add_lines('text0', [], 'text0')
114
self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
116
self.assertRaises(errors.RevisionNotPresent,
118
self.assertRaises(errors.RevisionNotPresent,
113
k.add('text0', [], [t], sha1=sha_string(t))
122
117
class InvalidAdd(TestBase):
123
118
"""Try to use invalid version number during add."""
124
119
def runTest(self):
127
self.assertRaises(errors.RevisionNotPresent,
122
self.assertRaises(IndexError,
165
161
def runTest(self):
168
k.add_lines('text0', [], ['line 1'])
169
k.add_lines('text1', ['text0'], ['line 1', 'line 2'])
171
self.assertEqual(k.annotate('text0'),
172
[('text0', 'line 1')])
174
self.assertEqual(k.get_lines(1),
164
k.add('text0', [], ['line 1'])
165
k.add('text1', [0], ['line 1', 'line 2'])
167
self.assertEqual(k.annotate(0),
170
self.assertEqual(k.get(1),
178
self.assertEqual(k.annotate('text1'),
179
[('text0', 'line 1'),
180
('text1', 'line 2')])
182
k.add_lines('text2', ['text0'], ['line 1', 'diverged line'])
184
self.assertEqual(k.annotate('text2'),
185
[('text0', 'line 1'),
186
('text2', 'diverged line')])
174
self.assertEqual(k.annotate(1),
178
k.add('text2', [0], ['line 1', 'diverged line'])
180
self.assertEqual(k.annotate(2),
182
(2, 'diverged line')])
188
184
text3 = ['line 1', 'middle line', 'line 2']
193
189
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
195
191
self.log("k._weave=" + pformat(k._weave))
197
self.assertEqual(k.annotate('text3'),
198
[('text0', 'line 1'),
199
('text3', 'middle line'),
200
('text1', 'line 2')])
193
self.assertEqual(k.annotate(3),
202
198
# now multiple insertions at different places
204
['text0', 'text1', 'text3'],
205
201
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
207
self.assertEqual(k.annotate('text4'),
208
[('text0', 'line 1'),
210
('text3', 'middle line'),
203
self.assertEqual(k.annotate(4),
216
213
class DeleteLines(TestBase):
576
562
['header', '', 'line from 1', 'fixup line', 'line from 2'],
579
k.add_lines('text0', [], texts[0])
580
k.add_lines('text1', ['text0'], texts[1])
581
k.add_lines('text2', ['text0'], texts[2])
582
k.add_lines('merge', ['text0', 'text1', 'text2'], texts[3])
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])
584
570
for i, t in enumerate(texts):
585
self.assertEqual(k.get_lines(i), t)
571
self.assertEqual(k.get(i), t)
587
self.assertEqual(k.annotate('merge'),
588
[('text0', 'header'),
590
('text1', 'line from 1'),
591
('merge', 'fixup line'),
592
('text2', 'line from 2'),
573
self.assertEqual(k.annotate(3),
595
self.assertEqual(list(k.get_ancestry(['merge'])),
596
['text0', 'text1', 'text2', 'merge'])
581
self.assertEqual(list(k.inclusions([3])),
598
584
self.log('k._weave=' + pformat(k._weave))
632
k.add_lines([], ['aaa', 'bbb'])
633
k.add_lines([0], ['111', 'aaa', 'ccc', 'bbb'])
634
k.add_lines([1], ['aaa', 'ccc', 'bbb', '222'])
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'])
637
652
class Khayyam(TestBase):
638
653
"""Test changes to multi-line texts, and read/write"""
640
def test_multi_line_merge(self):
642
656
"""A Book of Verses underneath the Bough,
643
657
A Jug of Wine, a Loaf of Bread, -- and Thou
672
ver = k.add_lines('text%d' % i,
686
ver = k.add('text%d' % i,
673
687
list(parents), t)
674
parents.add('text%d' % i)
677
691
self.log("k._weave=" + pformat(k._weave))
679
693
for i, t in enumerate(texts):
680
self.assertEqual(k.get_lines(i), t)
694
self.assertEqual(k.get(i), t)
682
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'])
685
797
class JoinWeavesTests(TestBase):
687
799
super(JoinWeavesTests, self).setUp()
688
800
self.weave1 = Weave()
689
801
self.lines1 = ['hello\n']
690
802
self.lines3 = ['hello\n', 'cruel\n', 'world\n']
691
self.weave1.add_lines('v1', [], self.lines1)
692
self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
693
self.weave1.add_lines('v3', ['v2'], self.lines3)
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)
695
807
def test_join_empty(self):
696
808
"""Join two empty weaves."""
754
866
The source weave contains a different version at index 0."""
755
867
wa = self.weave1.copy()
757
wb.add_lines('x1', [], ['line from x1\n'])
758
wb.add_lines('v1', [], ['hello\n'])
759
wb.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
869
wb.add('x1', [], ['line from x1\n'])
870
wb.add('v1', [], ['hello\n'])
871
wb.add('v2', ['v1'], ['hello\n', 'world\n'])
761
873
eq = self.assertEquals
762
eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
874
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
763
875
eq(wa.get_text('x1'), 'line from x1\n')
765
def test_written_detection(self):
766
# Test detection of weave file corruption.
768
# Make sure that we can detect if a weave file has
769
# been corrupted. This doesn't test all forms of corruption,
770
# but it at least helps verify the data you get, is what you want.
771
from cStringIO import StringIO
774
w.add_lines('v1', [], ['hello\n'])
775
w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
780
# Because we are corrupting, we need to make sure we have the exact text
781
self.assertEquals('# bzr weave file v5\n'
782
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
783
'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
784
'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
787
# Change a single letter
788
tmpf = StringIO('# bzr weave file v5\n'
789
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
790
'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
791
'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
795
self.assertEqual('hello\n', w.get_text('v1'))
796
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
797
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
798
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
800
# Change the sha checksum
801
tmpf = StringIO('# bzr weave file v5\n'
802
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
803
'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
804
'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
808
self.assertEqual('hello\n', w.get_text('v1'))
809
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
810
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
811
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
814
class InstrumentedWeave(Weave):
815
"""Keep track of how many times functions are called."""
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)
917
if __name__ == '__main__':
920
sys.exit(unittest.main())
817
def __init__(self, weave_name=None):
818
self._extract_count = 0
819
Weave.__init__(self, weave_name=weave_name)
821
def _extract(self, versions):
822
self._extract_count += 1
823
return Weave._extract(self, versions)
826
class JoinOptimization(TestCase):
827
"""Test that Weave.join() doesn't extract all texts, only what must be done."""
830
w1 = InstrumentedWeave()
831
w2 = InstrumentedWeave()
834
txt1 = ['a\n', 'b\n']
835
txt2 = ['a\n', 'c\n']
836
txt3 = ['a\n', 'b\n', 'c\n']
838
w1.add_lines('txt0', [], txt0) # extract 1a
839
w2.add_lines('txt0', [], txt0) # extract 1b
840
w1.add_lines('txt1', ['txt0'], txt1)# extract 2a
841
w2.add_lines('txt2', ['txt0'], txt2)# extract 2b
842
w1.join(w2) # extract 3a to add txt2
843
w2.join(w1) # extract 3b to add txt1
845
w1.add_lines('txt3', ['txt1', 'txt2'], txt3) # extract 4a
846
w2.add_lines('txt3', ['txt2', 'txt1'], txt3) # extract 4b
847
# These secretly have inverted parents
849
# This should not have to do any extractions
850
w1.join(w2) # NO extract, texts already present with same parents
851
w2.join(w1) # NO extract, texts already present with same parents
853
self.assertEqual(4, w1._extract_count)
854
self.assertEqual(4, w2._extract_count)
856
def test_double_parent(self):
857
# It should not be considered illegal to add
858
# a revision with the same parent twice
859
w1 = InstrumentedWeave()
860
w2 = InstrumentedWeave()
863
txt1 = ['a\n', 'b\n']
864
txt2 = ['a\n', 'c\n']
865
txt3 = ['a\n', 'b\n', 'c\n']
867
w1.add_lines('txt0', [], txt0)
868
w2.add_lines('txt0', [], txt0)
869
w1.add_lines('txt1', ['txt0'], txt1)
870
w2.add_lines('txt1', ['txt0', 'txt0'], txt1)
871
# Same text, effectively the same, because the
872
# parent is only repeated
873
w1.join(w2) # extract 3a to add txt2
874
w2.join(w1) # extract 3b to add txt1
877
class TestNeedsReweave(TestCase):
878
"""Internal corner cases for when reweave is needed."""
880
def test_compatible_parents(self):
882
my_parents = set([1, 2, 3])
884
self.assertTrue(w1._compatible_parents(my_parents, set([3])))
886
self.assertTrue(w1._compatible_parents(my_parents, set(my_parents)))
887
# same empty corner case
888
self.assertTrue(w1._compatible_parents(set(), set()))
889
# other cannot contain stuff my_parents does not
890
self.assertFalse(w1._compatible_parents(set(), set([1])))
891
self.assertFalse(w1._compatible_parents(my_parents, set([1, 2, 3, 4])))
892
self.assertFalse(w1._compatible_parents(my_parents, set([4])))