80
74
class StoreText(TestBase):
81
75
"""Store and retrieve a simple text."""
83
def test_storing_text(self):
85
idx = k.add_lines('text0', [], TEXT_0)
86
self.assertEqual(k.get_lines(idx), TEXT_0)
78
idx = k.add('text0', [], TEXT_0)
79
self.assertEqual(k.get(idx), TEXT_0)
87
80
self.assertEqual(idx, 0)
90
84
class AnnotateOne(TestBase):
93
k.add_lines('text0', [], TEXT_0)
94
self.assertEqual(k.annotate('text0'),
95
[('text0', TEXT_0[0])])
87
k.add('text0', [], TEXT_0)
88
self.assertEqual(k.annotate(0),
98
92
class StoreTwo(TestBase):
102
idx = k.add_lines('text0', [], TEXT_0)
96
idx = k.add('text0', [], TEXT_0)
103
97
self.assertEqual(idx, 0)
105
idx = k.add_lines('text1', [], TEXT_1)
99
idx = k.add('text1', [], TEXT_1)
106
100
self.assertEqual(idx, 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):
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"""
115
k.add_lines('text0', [], 'text0')
116
self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
118
self.assertRaises(errors.RevisionNotPresent,
120
self.assertRaises(errors.RevisionNotPresent,
113
k.add('text0', [], [t], sha1=sha_string(t))
124
117
class InvalidAdd(TestBase):
125
118
"""Try to use invalid version number during add."""
126
119
def runTest(self):
129
self.assertRaises(errors.RevisionNotPresent,
122
self.assertRaises(IndexError,
167
161
def runTest(self):
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),
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),
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')])
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')])
190
184
text3 = ['line 1', 'middle line', 'line 2']
195
189
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
197
191
self.log("k._weave=" + pformat(k._weave))
199
self.assertEqual(k.annotate('text3'),
200
[('text0', 'line 1'),
201
('text3', 'middle line'),
202
('text1', 'line 2')])
193
self.assertEqual(k.annotate(3),
204
198
# now multiple insertions at different places
206
['text0', 'text1', 'text3'],
207
201
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
209
self.assertEqual(k.annotate('text4'),
210
[('text0', 'line 1'),
212
('text3', 'middle line'),
203
self.assertEqual(k.annotate(4),
218
213
class DeleteLines(TestBase):
578
562
['header', '', 'line from 1', 'fixup line', 'line from 2'],
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])
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])
586
570
for i, t in enumerate(texts):
587
self.assertEqual(k.get_lines(i), t)
571
self.assertEqual(k.get(i), t)
589
self.assertEqual(k.annotate('merge'),
590
[('text0', 'header'),
592
('text1', 'line from 1'),
593
('merge', 'fixup line'),
594
('text2', 'line from 2'),
573
self.assertEqual(k.annotate(3),
597
self.assertEqual(list(k.get_ancestry(['merge'])),
598
['text0', 'text1', 'text2', 'merge'])
581
self.assertEqual(list(k.inclusions([3])),
600
584
self.log('k._weave=' + pformat(k._weave))
634
k.add_lines([], ['aaa', 'bbb'])
635
k.add_lines([0], ['111', 'aaa', 'ccc', 'bbb'])
636
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'])
639
652
class Khayyam(TestBase):
640
653
"""Test changes to multi-line texts, and read/write"""
642
def test_multi_line_merge(self):
644
656
"""A Book of Verses underneath the Bough,
645
657
A Jug of Wine, a Loaf of Bread, -- and Thou
674
ver = k.add_lines('text%d' % i,
686
ver = k.add('text%d' % i,
675
687
list(parents), t)
676
parents.add('text%d' % i)
679
691
self.log("k._weave=" + pformat(k._weave))
681
693
for i, t in enumerate(texts):
682
self.assertEqual(k.get_lines(i), t)
694
self.assertEqual(k.get(i), t)
684
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'])
687
797
class JoinWeavesTests(TestBase):
689
799
super(JoinWeavesTests, self).setUp()
690
800
self.weave1 = Weave()
691
801
self.lines1 = ['hello\n']
692
802
self.lines3 = ['hello\n', 'cruel\n', 'world\n']
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)
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)
697
807
def test_join_empty(self):
698
808
"""Join two empty weaves."""
756
866
The source weave contains a different version at index 0."""
757
867
wa = self.weave1.copy()
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'])
869
wb.add('x1', [], ['line from x1\n'])
870
wb.add('v1', [], ['hello\n'])
871
wb.add('v2', ['v1'], ['hello\n', 'world\n'])
763
873
eq = self.assertEquals
764
eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
874
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
765
875
eq(wa.get_text('x1'), 'line from x1\n')
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,
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())