3
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005 Canonical Ltd
5
3
# This program is free software; you can redistribute it and/or modify
6
4
# it under the terms of the GNU General Public License as published by
7
5
# the Free Software Foundation; either version 2 of the License, or
8
6
# (at your option) any later version.
10
8
# This program is distributed in the hope that it will be useful,
11
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
11
# GNU General Public License for more details.
15
13
# You should have received a copy of the GNU General Public License
16
14
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
18
# TODO: tests regarding version names
21
# TODO: rbc 20050108 test that join does not leave an inconsistent weave
19
# TODO: rbc 20050108 test that join does not leave an inconsistent weave
24
22
"""test suite for weave algorithm"""
26
24
from pprint import pformat
28
import bzrlib.errors as errors
29
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
29
from bzrlib.osutils import sha_string
30
from bzrlib.tests import TestCase, TestCaseInTempDir
31
from bzrlib.weave import Weave, WeaveFormatError, WeaveError
30
32
from bzrlib.weavefile import write_weave, read_weave
31
from bzrlib.tests import TestCase
32
from bzrlib.osutils import sha_string
35
35
# texts for use in testing
78
class StoreText(TestBase):
79
"""Store and retrieve a simple text."""
82
idx = k.add('text0', [], TEXT_0)
83
self.assertEqual(k.get(idx), TEXT_0)
84
self.assertEqual(idx, 0)
87
79
class AnnotateOne(TestBase):
90
k.add('text0', [], TEXT_0)
91
self.assertEqual(k.annotate(0),
95
class StoreTwo(TestBase):
99
idx = k.add('text0', [], TEXT_0)
100
self.assertEqual(idx, 0)
102
idx = k.add('text1', [], TEXT_1)
103
self.assertEqual(idx, 1)
105
self.assertEqual(k.get(0), TEXT_0)
106
self.assertEqual(k.get(1), TEXT_1)
109
class AddWithGivenSha(TestBase):
111
"""Add with caller-supplied SHA-1"""
115
k.add('text0', [], [t], sha1=sha_string(t))
118
class GetSha1(TestBase):
119
def test_get_sha1(self):
121
k.add('text0', [], 'text0')
122
self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
124
self.assertRaises(errors.WeaveRevisionNotPresent,
126
self.assertRaises(errors.WeaveRevisionNotPresent,
82
k.add_lines('text0', [], TEXT_0)
83
self.assertEqual(k.annotate('text0'),
84
[('text0', TEXT_0[0])])
130
87
class InvalidAdd(TestBase):
131
88
"""Try to use invalid version number during add."""
132
89
def runTest(self):
135
self.assertRaises(IndexError,
92
self.assertRaises(errors.RevisionNotPresent,
142
99
class RepeatedAdd(TestBase):
143
100
"""Add the same version twice; harmless."""
102
def test_duplicate_add(self):
146
idx = k.add('text0', [], TEXT_0)
147
idx2 = k.add('text0', [], TEXT_0)
104
idx = k.add_lines('text0', [], TEXT_0)
105
idx2 = k.add_lines('text0', [], TEXT_0)
148
106
self.assertEqual(idx, idx2)
151
109
class InvalidRepeatedAdd(TestBase):
152
110
def runTest(self):
154
idx = k.add('text0', [], TEXT_0)
155
self.assertRaises(WeaveError,
112
k.add_lines('basis', [], TEXT_0)
113
idx = k.add_lines('text0', [], TEXT_0)
114
self.assertRaises(errors.RevisionAlreadyPresent,
159
118
['not the same text'])
160
self.assertRaises(WeaveError,
119
self.assertRaises(errors.RevisionAlreadyPresent,
163
[12], # not the right parents
122
['basis'], # not the right parents
167
126
class InsertLines(TestBase):
168
127
"""Store a revision that adds one line to the original.
172
131
def runTest(self):
175
k.add('text0', [], ['line 1'])
176
k.add('text1', [0], ['line 1', 'line 2'])
178
self.assertEqual(k.annotate(0),
181
self.assertEqual(k.get(1),
134
k.add_lines('text0', [], ['line 1'])
135
k.add_lines('text1', ['text0'], ['line 1', 'line 2'])
137
self.assertEqual(k.annotate('text0'),
138
[('text0', 'line 1')])
140
self.assertEqual(k.get_lines(1),
185
self.assertEqual(k.annotate(1),
189
k.add('text2', [0], ['line 1', 'diverged line'])
191
self.assertEqual(k.annotate(2),
193
(2, 'diverged line')])
144
self.assertEqual(k.annotate('text1'),
145
[('text0', 'line 1'),
146
('text1', 'line 2')])
148
k.add_lines('text2', ['text0'], ['line 1', 'diverged line'])
150
self.assertEqual(k.annotate('text2'),
151
[('text0', 'line 1'),
152
('text2', 'diverged line')])
195
154
text3 = ['line 1', 'middle line', 'line 2']
200
159
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
202
161
self.log("k._weave=" + pformat(k._weave))
204
self.assertEqual(k.annotate(3),
163
self.assertEqual(k.annotate('text3'),
164
[('text0', 'line 1'),
165
('text3', 'middle line'),
166
('text1', 'line 2')])
209
168
# now multiple insertions at different places
170
['text0', 'text1', 'text3'],
212
171
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
214
self.assertEqual(k.annotate(4),
173
self.assertEqual(k.annotate('text4'),
174
[('text0', 'line 1'),
176
('text3', 'middle line'),
223
182
class DeleteLines(TestBase):
580
542
['header', '', 'line from 1', 'fixup line', 'line from 2'],
583
k.add('text0', [], texts[0])
584
k.add('text1', [0], texts[1])
585
k.add('text2', [0], texts[2])
586
k.add('merge', [0, 1, 2], texts[3])
545
k.add_lines('text0', [], texts[0])
546
k.add_lines('text1', ['text0'], texts[1])
547
k.add_lines('text2', ['text0'], texts[2])
548
k.add_lines('merge', ['text0', 'text1', 'text2'], texts[3])
588
550
for i, t in enumerate(texts):
589
self.assertEqual(k.get(i), t)
551
self.assertEqual(k.get_lines(i), t)
591
self.assertEqual(k.annotate(3),
553
self.assertEqual(k.annotate('merge'),
554
[('text0', 'header'),
556
('text1', 'line from 1'),
557
('merge', 'fixup line'),
558
('text2', 'line from 2'),
599
self.assertEqual(list(k.inclusions([3])),
561
self.assertEqual(list(k.get_ancestry(['merge'])),
562
['text0', 'text1', 'text2', 'merge'])
602
564
self.log('k._weave=' + pformat(k._weave))
636
k.add([], ['aaa', 'bbb'])
637
k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
638
k.add([1], ['aaa', 'ccc', 'bbb', '222'])
641
class AutoMerge(TestBase):
645
texts = [['header', 'aaa', 'bbb'],
646
['header', 'aaa', 'line from 1', 'bbb'],
647
['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
650
k.add('text0', [], texts[0])
651
k.add('text1', [0], texts[1])
652
k.add('text2', [0], texts[2])
654
self.log('k._weave=' + pformat(k._weave))
656
m = list(k.mash_iter([0, 1, 2]))
662
'line from 2', 'more from 2'])
598
k.add_lines([], ['aaa', 'bbb'])
599
k.add_lines([0], ['111', 'aaa', 'ccc', 'bbb'])
600
k.add_lines([1], ['aaa', 'ccc', 'bbb', '222'])
665
603
class Khayyam(TestBase):
666
604
"""Test changes to multi-line texts, and read/write"""
606
def test_multi_line_merge(self):
669
608
"""A Book of Verses underneath the Bough,
670
609
A Jug of Wine, a Loaf of Bread, -- and Thou
671
610
Beside me singing in the Wilderness --
672
611
Oh, Wilderness were Paradise enow!""",
674
613
"""A Book of Verses underneath the Bough,
675
614
A Jug of Wine, a Loaf of Bread, -- and Thou
676
615
Beside me singing in the Wilderness --
699
ver = k.add('text%d' % i,
638
ver = k.add_lines('text%d' % i,
700
639
list(parents), t)
640
parents.add('text%d' % i)
704
643
self.log("k._weave=" + pformat(k._weave))
706
645
for i, t in enumerate(texts):
707
self.assertEqual(k.get(i), t)
646
self.assertEqual(k.get_lines(i), t)
709
648
self.check_read_write(k)
712
class MergeCases(TestBase):
713
def doMerge(self, base, a, b, mp):
714
from cStringIO import StringIO
715
from textwrap import dedent
721
w.add('text0', [], map(addcrlf, base))
722
w.add('text1', [0], map(addcrlf, a))
723
w.add('text2', [0], map(addcrlf, b))
725
self.log('weave is:')
728
self.log(tmpf.getvalue())
730
self.log('merge plan:')
731
p = list(w.plan_merge(1, 2))
732
for state, line in p:
734
self.log('%12s | %s' % (state, line[:-1]))
738
mt.writelines(w.weave_merge(p))
740
self.log(mt.getvalue())
742
mp = map(addcrlf, mp)
743
self.assertEqual(mt.readlines(), mp)
746
def testOneInsert(self):
752
def testSeparateInserts(self):
753
self.doMerge(['aaa', 'bbb', 'ccc'],
754
['aaa', 'xxx', 'bbb', 'ccc'],
755
['aaa', 'bbb', 'yyy', 'ccc'],
756
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
758
def testSameInsert(self):
759
self.doMerge(['aaa', 'bbb', 'ccc'],
760
['aaa', 'xxx', 'bbb', 'ccc'],
761
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
762
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
764
def testOverlappedInsert(self):
765
self.doMerge(['aaa', 'bbb'],
766
['aaa', 'xxx', 'yyy', 'bbb'],
767
['aaa', 'xxx', 'bbb'],
768
['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 'xxx',
771
# really it ought to reduce this to
772
# ['aaa', 'xxx', 'yyy', 'bbb']
775
def testClashReplace(self):
776
self.doMerge(['aaa'],
779
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
782
def testNonClashInsert(self):
783
self.doMerge(['aaa'],
786
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
789
self.doMerge(['aaa'],
795
def testDeleteAndModify(self):
796
"""Clashing delete and modification.
798
If one side modifies a region and the other deletes it then
799
there should be a conflict with one side blank.
802
#######################################
803
# skippd, not working yet
806
self.doMerge(['aaa', 'bbb', 'ccc'],
807
['aaa', 'ddd', 'ccc'],
809
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
812
651
class JoinWeavesTests(TestBase):
814
653
super(JoinWeavesTests, self).setUp()
815
654
self.weave1 = Weave()
816
655
self.lines1 = ['hello\n']
817
656
self.lines3 = ['hello\n', 'cruel\n', 'world\n']
818
self.weave1.add('v1', [], self.lines1)
819
self.weave1.add('v2', [0], ['hello\n', 'world\n'])
820
self.weave1.add('v3', [1], self.lines3)
822
def test_join_empty(self):
823
"""Join two empty weaves."""
824
eq = self.assertEqual
828
eq(w1.numversions(), 0)
830
def test_join_empty_to_nonempty(self):
831
"""Join empty weave onto nonempty."""
832
self.weave1.join(Weave())
833
self.assertEqual(len(self.weave1), 3)
835
def test_join_unrelated(self):
836
"""Join two weaves with no history in common."""
838
wb.add('b1', [], ['line from b\n'])
841
eq = self.assertEqual
843
eq(sorted(list(w1.iter_names())),
844
['b1', 'v1', 'v2', 'v3'])
846
def test_join_related(self):
847
wa = self.weave1.copy()
848
wb = self.weave1.copy()
849
wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
850
wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
851
eq = self.assertEquals
856
eq(wa.get_lines('b1'),
857
['hello\n', 'pale blue\n', 'world\n'])
859
def test_join_parent_disagreement(self):
860
"""Cannot join weaves with different parents for a version."""
863
wa.add('v1', [], ['hello\n'])
865
wb.add('v1', ['v0'], ['hello\n'])
866
self.assertRaises(WeaveError,
869
def test_join_text_disagreement(self):
870
"""Cannot join weaves with different texts for a version."""
873
wa.add('v1', [], ['hello\n'])
874
wb.add('v1', [], ['not\n', 'hello\n'])
875
self.assertRaises(WeaveError,
878
def test_join_unordered(self):
879
"""Join weaves where indexes differ.
881
The source weave contains a different version at index 0."""
882
wa = self.weave1.copy()
884
wb.add('x1', [], ['line from x1\n'])
885
wb.add('v1', [], ['hello\n'])
886
wb.add('v2', ['v1'], ['hello\n', 'world\n'])
888
eq = self.assertEquals
889
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
890
eq(wa.get_text('x1'), 'line from x1\n')
893
class Corruption(TestCase):
895
def test_detection(self):
896
# Test weaves detect corruption.
898
# Weaves contain a checksum of their texts.
899
# When a text is extracted, this checksum should be
903
w.add('v1', [], ['hello\n'])
904
w.add('v2', ['v1'], ['hello\n', 'there\n'])
906
# We are going to invasively corrupt the text
907
# Make sure the internals of weave are the same
908
self.assertEqual([('{', 0)
916
self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
917
, '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
922
w._weave[4] = 'There\n'
924
self.assertEqual('hello\n', w.get_text('v1'))
925
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
926
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
927
self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
928
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
931
w._weave[4] = 'there\n'
932
self.assertEqual('hello\nthere\n', w.get_text('v2'))
934
#Invalid checksum, first digit changed
935
w._sha1s[1] = 'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
937
self.assertEqual('hello\n', w.get_text('v1'))
938
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
939
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
940
self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
941
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
657
self.weave1.add_lines('v1', [], self.lines1)
658
self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
659
self.weave1.add_lines('v3', ['v2'], self.lines3)
943
661
def test_written_detection(self):
944
662
# Test detection of weave file corruption.
1003
734
return Weave._extract(self, versions)
1006
class JoinOptimization(TestCase):
1007
"""Test that Weave.join() doesn't extract all texts, only what must be done."""
1009
def test_join(self):
1010
w1 = InstrumentedWeave()
1011
w2 = InstrumentedWeave()
1014
txt1 = ['a\n', 'b\n']
1015
txt2 = ['a\n', 'c\n']
1016
txt3 = ['a\n', 'b\n', 'c\n']
1018
w1.add('txt0', [], txt0) # extract 1a
1019
w2.add('txt0', [], txt0) # extract 1b
1020
w1.add('txt1', [0], txt1)# extract 2a
1021
w2.add('txt2', [0], txt2)# extract 2b
1022
w1.join(w2) # extract 3a to add txt2
1023
w2.join(w1) # extract 3b to add txt1
1025
w1.add('txt3', [1, 2], txt3) # extract 4a
1026
w2.add('txt3', [1, 2], txt3) # extract 4b
1027
# These secretly have inverted parents
1029
# This should not have to do any extractions
1030
w1.join(w2) # NO extract, texts already present with same parents
1031
w2.join(w1) # NO extract, texts already present with same parents
1033
self.assertEqual(4, w1._extract_count)
1034
self.assertEqual(4, w2._extract_count)
1036
def test_double_parent(self):
1037
# It should not be considered illegal to add
1038
# a revision with the same parent twice
1039
w1 = InstrumentedWeave()
1040
w2 = InstrumentedWeave()
1043
txt1 = ['a\n', 'b\n']
1044
txt2 = ['a\n', 'c\n']
1045
txt3 = ['a\n', 'b\n', 'c\n']
1047
w1.add('txt0', [], txt0)
1048
w2.add('txt0', [], txt0)
1049
w1.add('txt1', [0], txt1)
1050
w2.add('txt1', [0,0], txt1)
1051
# Same text, effectively the same, because the
1052
# parent is only repeated
1053
w1.join(w2) # extract 3a to add txt2
1054
w2.join(w1) # extract 3b to add txt1
1057
class MismatchedTexts(TestCase):
1058
"""Test that merging two weaves with different texts fails."""
1060
def test_reweave(self):
737
class TestNeedsReweave(TestCase):
738
"""Internal corner cases for when reweave is needed."""
740
def test_compatible_parents(self):
1064
w1.add('txt0', [], ['a\n'])
1065
w2.add('txt0', [], ['a\n'])
1066
w1.add('txt1', [0], ['a\n', 'b\n'])
1067
w2.add('txt1', [0], ['a\n', 'c\n'])
1069
self.assertRaises(errors.WeaveTextDiffers, w1.reweave, w2)
742
my_parents = set([1, 2, 3])
744
self.assertTrue(w1._compatible_parents(my_parents, set([3])))
746
self.assertTrue(w1._compatible_parents(my_parents, set(my_parents)))
747
# same empty corner case
748
self.assertTrue(w1._compatible_parents(set(), set()))
749
# other cannot contain stuff my_parents does not
750
self.assertFalse(w1._compatible_parents(set(), set([1])))
751
self.assertFalse(w1._compatible_parents(my_parents, set([1, 2, 3, 4])))
752
self.assertFalse(w1._compatible_parents(my_parents, set([4])))
755
class TestWeaveFile(TestCaseInTempDir):
757
def test_empty_file(self):
758
f = open('empty.weave', 'wb+')
760
self.assertRaises(errors.WeaveFormatError,