1
# Copyright (C) 2005 Canonical Ltd
3
# Copyright (C) 2005 by Canonical Ltd
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
5
7
# the Free Software Foundation; either version 2 of the License, or
6
8
# (at your option) any later version.
8
10
# This program is distributed in the hope that it will be useful,
9
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
13
# GNU General Public License for more details.
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
20
# TODO: tests regarding version names
19
# TODO: rbc 20050108 test that join does not leave an inconsistent weave
21
# TODO: rbc 20050108 test that join does not leave an inconsistent weave
22
24
"""test suite for weave algorithm"""
24
26
from pprint import pformat
28
import bzrlib.errors as errors
29
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
30
from bzrlib.weavefile import write_weave, read_weave
31
from bzrlib.tests import TestCase
29
32
from bzrlib.osutils import sha_string
30
from bzrlib.tests import TestCase, TestCaseInTempDir
31
from bzrlib.weave import Weave, WeaveFormatError, WeaveError
32
from bzrlib.weavefile import write_weave, read_weave
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)
79
87
class AnnotateOne(TestBase):
82
k.add_lines('text0', [], TEXT_0)
83
self.assertEqual(k.annotate('text0'),
84
[('text0', TEXT_0[0])])
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.RevisionNotPresent,
126
self.assertRaises(errors.RevisionNotPresent,
87
130
class InvalidAdd(TestBase):
88
131
"""Try to use invalid version number during add."""
89
132
def runTest(self):
92
self.assertRaises(errors.RevisionNotPresent,
135
self.assertRaises(IndexError,
99
142
class RepeatedAdd(TestBase):
100
143
"""Add the same version twice; harmless."""
102
def test_duplicate_add(self):
104
idx = k.add_lines('text0', [], TEXT_0)
105
idx2 = k.add_lines('text0', [], TEXT_0)
146
idx = k.add('text0', [], TEXT_0)
147
idx2 = k.add('text0', [], TEXT_0)
106
148
self.assertEqual(idx, idx2)
109
151
class InvalidRepeatedAdd(TestBase):
110
152
def runTest(self):
112
k.add_lines('basis', [], TEXT_0)
113
idx = k.add_lines('text0', [], TEXT_0)
154
idx = k.add('text0', [], TEXT_0)
114
155
self.assertRaises(errors.RevisionAlreadyPresent,
118
159
['not the same text'])
119
160
self.assertRaises(errors.RevisionAlreadyPresent,
122
['basis'], # not the right parents
163
[12], # not the right parents
126
167
class InsertLines(TestBase):
127
168
"""Store a revision that adds one line to the original.
131
172
def runTest(self):
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),
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),
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')])
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')])
154
195
text3 = ['line 1', 'middle line', 'line 2']
159
200
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
161
202
self.log("k._weave=" + pformat(k._weave))
163
self.assertEqual(k.annotate('text3'),
164
[('text0', 'line 1'),
165
('text3', 'middle line'),
166
('text1', 'line 2')])
204
self.assertEqual(k.annotate(3),
168
209
# now multiple insertions at different places
170
['text0', 'text1', 'text3'],
171
212
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
173
self.assertEqual(k.annotate('text4'),
174
[('text0', 'line 1'),
176
('text3', 'middle line'),
214
self.assertEqual(k.annotate(4),
182
223
class DeleteLines(TestBase):
542
582
['header', '', 'line from 1', 'fixup line', 'line from 2'],
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])
585
k.add('text0', [], texts[0])
586
k.add('text1', [0], texts[1])
587
k.add('text2', [0], texts[2])
588
k.add('merge', [0, 1, 2], texts[3])
550
590
for i, t in enumerate(texts):
551
self.assertEqual(k.get_lines(i), t)
591
self.assertEqual(k.get(i), t)
553
self.assertEqual(k.annotate('merge'),
554
[('text0', 'header'),
556
('text1', 'line from 1'),
557
('merge', 'fixup line'),
558
('text2', 'line from 2'),
593
self.assertEqual(k.annotate(3),
561
self.assertEqual(list(k.get_ancestry(['merge'])),
601
self.assertEqual(list(k.inclusions([3])),
562
602
['text0', 'text1', 'text2', 'merge'])
564
604
self.log('k._weave=' + pformat(k._weave))
638
ver = k.add_lines('text%d' % i,
678
ver = k.add('text%d' % i,
639
679
list(parents), t)
640
parents.add('text%d' % i)
643
683
self.log("k._weave=" + pformat(k._weave))
645
685
for i, t in enumerate(texts):
646
self.assertEqual(k.get_lines(i), t)
686
self.assertEqual(k.get(i), t)
648
688
self.check_read_write(k)
691
class MergeCases(TestBase):
692
def doMerge(self, base, a, b, mp):
693
from cStringIO import StringIO
694
from textwrap import dedent
700
w.add('text0', [], map(addcrlf, base))
701
w.add('text1', [0], map(addcrlf, a))
702
w.add('text2', [0], map(addcrlf, b))
704
self.log('weave is:')
707
self.log(tmpf.getvalue())
709
self.log('merge plan:')
710
p = list(w.plan_merge(1, 2))
711
for state, line in p:
713
self.log('%12s | %s' % (state, line[:-1]))
717
mt.writelines(w.weave_merge(p))
719
self.log(mt.getvalue())
721
mp = map(addcrlf, mp)
722
self.assertEqual(mt.readlines(), mp)
725
def testOneInsert(self):
731
def testSeparateInserts(self):
732
self.doMerge(['aaa', 'bbb', 'ccc'],
733
['aaa', 'xxx', 'bbb', 'ccc'],
734
['aaa', 'bbb', 'yyy', 'ccc'],
735
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
737
def testSameInsert(self):
738
self.doMerge(['aaa', 'bbb', 'ccc'],
739
['aaa', 'xxx', 'bbb', 'ccc'],
740
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
741
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
743
def testOverlappedInsert(self):
744
self.doMerge(['aaa', 'bbb'],
745
['aaa', 'xxx', 'yyy', 'bbb'],
746
['aaa', 'xxx', 'bbb'],
747
['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 'xxx',
750
# really it ought to reduce this to
751
# ['aaa', 'xxx', 'yyy', 'bbb']
754
def testClashReplace(self):
755
self.doMerge(['aaa'],
758
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
761
def testNonClashInsert(self):
762
self.doMerge(['aaa'],
765
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
768
self.doMerge(['aaa'],
774
def testDeleteAndModify(self):
775
"""Clashing delete and modification.
777
If one side modifies a region and the other deletes it then
778
there should be a conflict with one side blank.
781
#######################################
782
# skippd, not working yet
785
self.doMerge(['aaa', 'bbb', 'ccc'],
786
['aaa', 'ddd', 'ccc'],
788
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
651
791
class JoinWeavesTests(TestBase):
653
793
super(JoinWeavesTests, self).setUp()
654
794
self.weave1 = Weave()
655
795
self.lines1 = ['hello\n']
656
796
self.lines3 = ['hello\n', 'cruel\n', 'world\n']
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)
797
self.weave1.add('v1', [], self.lines1)
798
self.weave1.add('v2', [0], ['hello\n', 'world\n'])
799
self.weave1.add('v3', [1], self.lines3)
801
def test_join_empty(self):
802
"""Join two empty weaves."""
803
eq = self.assertEqual
807
eq(w1.numversions(), 0)
809
def test_join_empty_to_nonempty(self):
810
"""Join empty weave onto nonempty."""
811
self.weave1.join(Weave())
812
self.assertEqual(len(self.weave1), 3)
814
def test_join_unrelated(self):
815
"""Join two weaves with no history in common."""
817
wb.add('b1', [], ['line from b\n'])
820
eq = self.assertEqual
822
eq(sorted(list(w1.iter_names())),
823
['b1', 'v1', 'v2', 'v3'])
825
def test_join_related(self):
826
wa = self.weave1.copy()
827
wb = self.weave1.copy()
828
wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
829
wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
830
eq = self.assertEquals
835
eq(wa.get_lines('b1'),
836
['hello\n', 'pale blue\n', 'world\n'])
838
def test_join_parent_disagreement(self):
839
#join reconciles differening parents into a union.
842
wa.add('v1', [], ['hello\n'])
844
wb.add('v1', ['v0'], ['hello\n'])
846
self.assertEqual(['v0'], wa.get_parents('v1'))
848
def test_join_text_disagreement(self):
849
"""Cannot join weaves with different texts for a version."""
852
wa.add('v1', [], ['hello\n'])
853
wb.add('v1', [], ['not\n', 'hello\n'])
854
self.assertRaises(WeaveError,
857
def test_join_unordered(self):
858
"""Join weaves where indexes differ.
860
The source weave contains a different version at index 0."""
861
wa = self.weave1.copy()
863
wb.add('x1', [], ['line from x1\n'])
864
wb.add('v1', [], ['hello\n'])
865
wb.add('v2', ['v1'], ['hello\n', 'world\n'])
867
eq = self.assertEquals
868
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
869
eq(wa.get_text('x1'), 'line from x1\n')
661
871
def test_written_detection(self):
662
872
# Test detection of weave file corruption.
704
915
self.assertEqual('hello\n', w.get_text('v1'))
705
916
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
706
917
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
918
self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
707
919
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
710
class TestWeave(TestCase):
712
def test_allow_reserved_false(self):
713
w = Weave('name', allow_reserved=False)
714
# Add lines is checked at the WeaveFile level, not at the Weave level
715
w.add_lines('name:', [], TEXT_1)
716
# But get_lines is checked at this level
717
self.assertRaises(errors.ReservedId, w.get_lines, 'name:')
719
def test_allow_reserved_true(self):
720
w = Weave('name', allow_reserved=True)
721
w.add_lines('name:', [], TEXT_1)
722
self.assertEqual(TEXT_1, w.get_lines('name:'))
725
922
class InstrumentedWeave(Weave):
726
923
"""Keep track of how many times functions are called."""
728
925
def __init__(self, weave_name=None):
729
926
self._extract_count = 0
730
927
Weave.__init__(self, weave_name=weave_name)
734
931
return Weave._extract(self, versions)
737
class TestNeedsReweave(TestCase):
738
"""Internal corner cases for when reweave is needed."""
740
def test_compatible_parents(self):
934
class JoinOptimization(TestCase):
935
"""Test that Weave.join() doesn't extract all texts, only what must be done."""
938
w1 = InstrumentedWeave()
939
w2 = InstrumentedWeave()
942
txt1 = ['a\n', 'b\n']
943
txt2 = ['a\n', 'c\n']
944
txt3 = ['a\n', 'b\n', 'c\n']
946
w1.add('txt0', [], txt0) # extract 1a
947
w2.add('txt0', [], txt0) # extract 1b
948
w1.add('txt1', [0], txt1)# extract 2a
949
w2.add('txt2', [0], txt2)# extract 2b
950
w1.join(w2) # extract 3a to add txt2
951
w2.join(w1) # extract 3b to add txt1
953
w1.add('txt3', [1, 2], txt3) # extract 4a
954
w2.add('txt3', [1, 2], txt3) # extract 4b
955
# These secretly have inverted parents
957
# This should not have to do any extractions
958
w1.join(w2) # NO extract, texts already present with same parents
959
w2.join(w1) # NO extract, texts already present with same parents
961
self.assertEqual(4, w1._extract_count)
962
self.assertEqual(4, w2._extract_count)
964
def test_double_parent(self):
965
# It should not be considered illegal to add
966
# a revision with the same parent twice
967
w1 = InstrumentedWeave()
968
w2 = InstrumentedWeave()
971
txt1 = ['a\n', 'b\n']
972
txt2 = ['a\n', 'c\n']
973
txt3 = ['a\n', 'b\n', 'c\n']
975
w1.add('txt0', [], txt0)
976
w2.add('txt0', [], txt0)
977
w1.add('txt1', [0], txt1)
978
w2.add('txt1', [0,0], txt1)
979
# Same text, effectively the same, because the
980
# parent is only repeated
981
w1.join(w2) # extract 3a to add txt2
982
w2.join(w1) # extract 3b to add txt1
985
class MismatchedTexts(TestCase):
986
"""Test that merging two weaves with different texts fails."""
988
def test_reweave(self):
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,
992
w1.add('txt0', [], ['a\n'])
993
w2.add('txt0', [], ['a\n'])
994
w1.add('txt1', [0], ['a\n', 'b\n'])
995
w2.add('txt1', [0], ['a\n', 'c\n'])
997
self.assertRaises(errors.WeaveTextDiffers, w1.reweave, w2)