74
80
class StoreText(TestBase):
75
81
"""Store and retrieve a simple text."""
83
def test_storing_text(self):
78
idx = k.add('text0', [], TEXT_0)
79
self.assertEqual(k.get(idx), TEXT_0)
85
idx = k.add_lines('text0', [], TEXT_0)
86
self.assertEqual(k.get_lines(idx), TEXT_0)
80
87
self.assertEqual(idx, 0)
84
90
class AnnotateOne(TestBase):
87
k.add('text0', [], TEXT_0)
88
self.assertEqual(k.annotate(0),
93
k.add_lines('text0', [], TEXT_0)
94
self.assertEqual(k.annotate('text0'),
95
[('text0', TEXT_0[0])])
92
98
class StoreTwo(TestBase):
96
idx = k.add('text0', [], TEXT_0)
102
idx = k.add_lines('text0', [], TEXT_0)
97
103
self.assertEqual(idx, 0)
99
idx = k.add('text1', [], TEXT_1)
105
idx = k.add_lines('text1', [], TEXT_1)
100
106
self.assertEqual(idx, 1)
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"""
108
self.assertEqual(k.get_lines(0), TEXT_0)
109
self.assertEqual(k.get_lines(1), TEXT_1)
112
class GetSha1(TestBase):
113
def test_get_sha1(self):
113
k.add('text0', [], [t], sha1=sha_string(t))
115
k.add_lines('text0', [], 'text0')
116
self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
118
self.assertRaises(errors.RevisionNotPresent,
120
self.assertRaises(errors.RevisionNotPresent,
117
124
class InvalidAdd(TestBase):
118
125
"""Try to use invalid version number during add."""
119
126
def runTest(self):
122
self.assertRaises(IndexError,
129
self.assertRaises(errors.RevisionNotPresent,
161
167
def runTest(self):
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),
170
k.add_lines('text0', [], ['line 1'])
171
k.add_lines('text1', ['text0'], ['line 1', 'line 2'])
173
self.assertEqual(k.annotate('text0'),
174
[('text0', 'line 1')])
176
self.assertEqual(k.get_lines(1),
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')])
180
self.assertEqual(k.annotate('text1'),
181
[('text0', 'line 1'),
182
('text1', 'line 2')])
184
k.add_lines('text2', ['text0'], ['line 1', 'diverged line'])
186
self.assertEqual(k.annotate('text2'),
187
[('text0', 'line 1'),
188
('text2', 'diverged line')])
184
190
text3 = ['line 1', 'middle line', 'line 2']
189
195
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
191
197
self.log("k._weave=" + pformat(k._weave))
193
self.assertEqual(k.annotate(3),
199
self.assertEqual(k.annotate('text3'),
200
[('text0', 'line 1'),
201
('text3', 'middle line'),
202
('text1', 'line 2')])
198
204
# now multiple insertions at different places
206
['text0', 'text1', 'text3'],
201
207
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
203
self.assertEqual(k.annotate(4),
209
self.assertEqual(k.annotate('text4'),
210
[('text0', 'line 1'),
212
('text3', 'middle line'),
213
218
class DeleteLines(TestBase):
562
578
['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])
581
k.add_lines('text0', [], texts[0])
582
k.add_lines('text1', ['text0'], texts[1])
583
k.add_lines('text2', ['text0'], texts[2])
584
k.add_lines('merge', ['text0', 'text1', 'text2'], texts[3])
570
586
for i, t in enumerate(texts):
571
self.assertEqual(k.get(i), t)
587
self.assertEqual(k.get_lines(i), t)
573
self.assertEqual(k.annotate(3),
589
self.assertEqual(k.annotate('merge'),
590
[('text0', 'header'),
592
('text1', 'line from 1'),
593
('merge', 'fixup line'),
594
('text2', 'line from 2'),
581
self.assertEqual(list(k.inclusions([3])),
597
self.assertEqual(list(k.get_ancestry(['merge'])),
598
['text0', 'text1', 'text2', 'merge'])
584
600
self.log('k._weave=' + pformat(k._weave))
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'])
634
k.add_lines([], ['aaa', 'bbb'])
635
k.add_lines([0], ['111', 'aaa', 'ccc', 'bbb'])
636
k.add_lines([1], ['aaa', 'ccc', 'bbb', '222'])
652
639
class Khayyam(TestBase):
653
640
"""Test changes to multi-line texts, and read/write"""
642
def test_multi_line_merge(self):
656
644
"""A Book of Verses underneath the Bough,
657
645
A Jug of Wine, a Loaf of Bread, -- and Thou
686
ver = k.add('text%d' % i,
674
ver = k.add_lines('text%d' % i,
687
675
list(parents), t)
676
parents.add('text%d' % i)
691
679
self.log("k._weave=" + pformat(k._weave))
693
681
for i, t in enumerate(texts):
694
self.assertEqual(k.get(i), t)
682
self.assertEqual(k.get_lines(i), t)
696
684
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
687
class JoinWeavesTests(TestBase):
799
689
super(JoinWeavesTests, self).setUp()
800
690
self.weave1 = Weave()
801
691
self.lines1 = ['hello\n']
802
692
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)
693
self.weave1.add_lines('v1', [], self.lines1)
694
self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
695
self.weave1.add_lines('v3', ['v2'], self.lines3)
807
697
def test_join_empty(self):
808
698
"""Join two empty weaves."""
866
756
The source weave contains a different version at index 0."""
867
757
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'])
759
wb.add_lines('x1', [], ['line from x1\n'])
760
wb.add_lines('v1', [], ['hello\n'])
761
wb.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
873
763
eq = self.assertEquals
874
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
764
eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
875
765
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)
917
if __name__ == '__main__':
920
sys.exit(unittest.main())
767
def test_written_detection(self):
768
# Test detection of weave file corruption.
770
# Make sure that we can detect if a weave file has
771
# been corrupted. This doesn't test all forms of corruption,
772
# but it at least helps verify the data you get, is what you want.
773
from cStringIO import StringIO
776
w.add_lines('v1', [], ['hello\n'])
777
w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
782
# Because we are corrupting, we need to make sure we have the exact text
783
self.assertEquals('# bzr weave file v5\n'
784
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
785
'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
786
'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
789
# Change a single letter
790
tmpf = StringIO('# bzr weave file v5\n'
791
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
792
'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
793
'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
797
self.assertEqual('hello\n', w.get_text('v1'))
798
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
799
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
800
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
802
# Change the sha checksum
803
tmpf = StringIO('# bzr weave file v5\n'
804
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
805
'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
806
'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
810
self.assertEqual('hello\n', w.get_text('v1'))
811
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
812
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
813
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
816
class InstrumentedWeave(Weave):
817
"""Keep track of how many times functions are called."""
819
def __init__(self, weave_name=None):
820
self._extract_count = 0
821
Weave.__init__(self, weave_name=weave_name)
823
def _extract(self, versions):
824
self._extract_count += 1
825
return Weave._extract(self, versions)
828
class JoinOptimization(TestCase):
829
"""Test that Weave.join() doesn't extract all texts, only what must be done."""
832
w1 = InstrumentedWeave()
833
w2 = InstrumentedWeave()
836
txt1 = ['a\n', 'b\n']
837
txt2 = ['a\n', 'c\n']
838
txt3 = ['a\n', 'b\n', 'c\n']
840
w1.add_lines('txt0', [], txt0) # extract 1a
841
w2.add_lines('txt0', [], txt0) # extract 1b
842
w1.add_lines('txt1', ['txt0'], txt1)# extract 2a
843
w2.add_lines('txt2', ['txt0'], txt2)# extract 2b
844
w1.join(w2) # extract 3a to add txt2
845
w2.join(w1) # extract 3b to add txt1
847
w1.add_lines('txt3', ['txt1', 'txt2'], txt3) # extract 4a
848
w2.add_lines('txt3', ['txt2', 'txt1'], txt3) # extract 4b
849
# These secretly have inverted parents
851
# This should not have to do any extractions
852
w1.join(w2) # NO extract, texts already present with same parents
853
w2.join(w1) # NO extract, texts already present with same parents
855
self.assertEqual(4, w1._extract_count)
856
self.assertEqual(4, w2._extract_count)
858
def test_double_parent(self):
859
# It should not be considered illegal to add
860
# a revision with the same parent twice
861
w1 = InstrumentedWeave()
862
w2 = InstrumentedWeave()
865
txt1 = ['a\n', 'b\n']
866
txt2 = ['a\n', 'c\n']
867
txt3 = ['a\n', 'b\n', 'c\n']
869
w1.add_lines('txt0', [], txt0)
870
w2.add_lines('txt0', [], txt0)
871
w1.add_lines('txt1', ['txt0'], txt1)
872
w2.add_lines('txt1', ['txt0', 'txt0'], txt1)
873
# Same text, effectively the same, because the
874
# parent is only repeated
875
w1.join(w2) # extract 3a to add txt2
876
w2.join(w1) # extract 3b to add txt1
879
class TestNeedsReweave(TestCase):
880
"""Internal corner cases for when reweave is needed."""
882
def test_compatible_parents(self):
884
my_parents = set([1, 2, 3])
886
self.assertTrue(w1._compatible_parents(my_parents, set([3])))
888
self.assertTrue(w1._compatible_parents(my_parents, set(my_parents)))
889
# same empty corner case
890
self.assertTrue(w1._compatible_parents(set(), set()))
891
# other cannot contain stuff my_parents does not
892
self.assertFalse(w1._compatible_parents(set(), set([1])))
893
self.assertFalse(w1._compatible_parents(my_parents, set([1, 2, 3, 4])))
894
self.assertFalse(w1._compatible_parents(my_parents, set([4])))
897
class TestWeaveFile(TestCaseInTempDir):
899
def test_empty_file(self):
900
f = open('empty.weave', 'wb+')
902
self.assertRaises(errors.WeaveFormatError,