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
84
92
[('text0', TEXT_0[0])])
95
class StoreTwo(TestBase):
99
idx = k.add_lines('text0', [], TEXT_0)
100
self.assertEqual(idx, 0)
102
idx = k.add_lines('text1', [], TEXT_1)
103
self.assertEqual(idx, 1)
105
self.assertEqual(k.get_lines(0), TEXT_0)
106
self.assertEqual(k.get_lines(1), TEXT_1)
109
class GetSha1(TestBase):
110
def test_get_sha1(self):
112
k.add_lines('text0', [], 'text0')
113
self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
115
self.assertRaises(errors.RevisionNotPresent,
117
self.assertRaises(errors.RevisionNotPresent,
87
121
class InvalidAdd(TestBase):
88
122
"""Try to use invalid version number during add."""
89
123
def runTest(self):
648
681
self.check_read_write(k)
684
class MergeCases(TestBase):
685
def doMerge(self, base, a, b, mp):
686
from cStringIO import StringIO
687
from textwrap import dedent
693
w.add_lines('text0', [], map(addcrlf, base))
694
w.add_lines('text1', ['text0'], map(addcrlf, a))
695
w.add_lines('text2', ['text0'], map(addcrlf, b))
697
self.log('weave is:')
700
self.log(tmpf.getvalue())
702
self.log('merge plan:')
703
p = list(w.plan_merge('text1', 'text2'))
704
for state, line in p:
706
self.log('%12s | %s' % (state, line[:-1]))
710
mt.writelines(w.weave_merge(p))
712
self.log(mt.getvalue())
714
mp = map(addcrlf, mp)
715
self.assertEqual(mt.readlines(), mp)
718
def testOneInsert(self):
724
def testSeparateInserts(self):
725
self.doMerge(['aaa', 'bbb', 'ccc'],
726
['aaa', 'xxx', 'bbb', 'ccc'],
727
['aaa', 'bbb', 'yyy', 'ccc'],
728
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
730
def testSameInsert(self):
731
self.doMerge(['aaa', 'bbb', 'ccc'],
732
['aaa', 'xxx', 'bbb', 'ccc'],
733
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
734
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
736
def testOverlappedInsert(self):
737
self.doMerge(['aaa', 'bbb'],
738
['aaa', 'xxx', 'yyy', 'bbb'],
739
['aaa', 'xxx', 'bbb'],
740
['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 'xxx',
743
# really it ought to reduce this to
744
# ['aaa', 'xxx', 'yyy', 'bbb']
747
def testClashReplace(self):
748
self.doMerge(['aaa'],
751
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
754
def testNonClashInsert(self):
755
self.doMerge(['aaa'],
758
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
761
self.doMerge(['aaa'],
767
def testDeleteAndModify(self):
768
"""Clashing delete and modification.
770
If one side modifies a region and the other deletes it then
771
there should be a conflict with one side blank.
774
#######################################
775
# skippd, not working yet
778
self.doMerge(['aaa', 'bbb', 'ccc'],
779
['aaa', 'ddd', 'ccc'],
781
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
651
784
class JoinWeavesTests(TestBase):
653
786
super(JoinWeavesTests, self).setUp()
657
790
self.weave1.add_lines('v1', [], self.lines1)
658
791
self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
659
792
self.weave1.add_lines('v3', ['v2'], self.lines3)
794
def test_join_empty(self):
795
"""Join two empty weaves."""
796
eq = self.assertEqual
802
def test_join_empty_to_nonempty(self):
803
"""Join empty weave onto nonempty."""
804
self.weave1.join(Weave())
805
self.assertEqual(len(self.weave1), 3)
807
def test_join_unrelated(self):
808
"""Join two weaves with no history in common."""
810
wb.add_lines('b1', [], ['line from b\n'])
813
eq = self.assertEqual
815
eq(sorted(w1.versions()),
816
['b1', 'v1', 'v2', 'v3'])
818
def test_join_related(self):
819
wa = self.weave1.copy()
820
wb = self.weave1.copy()
821
wa.add_lines('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
822
wb.add_lines('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
823
eq = self.assertEquals
828
eq(wa.get_lines('b1'),
829
['hello\n', 'pale blue\n', 'world\n'])
831
def test_join_parent_disagreement(self):
832
#join reconciles differening parents into a union.
835
wa.add_lines('v1', [], ['hello\n'])
836
wb.add_lines('v0', [], [])
837
wb.add_lines('v1', ['v0'], ['hello\n'])
839
self.assertEqual(['v0'], wa.get_parents('v1'))
841
def test_join_text_disagreement(self):
842
"""Cannot join weaves with different texts for a version."""
845
wa.add_lines('v1', [], ['hello\n'])
846
wb.add_lines('v1', [], ['not\n', 'hello\n'])
847
self.assertRaises(WeaveError,
850
def test_join_unordered(self):
851
"""Join weaves where indexes differ.
853
The source weave contains a different version at index 0."""
854
wa = self.weave1.copy()
856
wb.add_lines('x1', [], ['line from x1\n'])
857
wb.add_lines('v1', [], ['hello\n'])
858
wb.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
860
eq = self.assertEquals
861
eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
862
eq(wa.get_text('x1'), 'line from x1\n')
661
864
def test_written_detection(self):
662
865
# Test detection of weave file corruption.
707
910
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
913
class InstrumentedWeave(Weave):
726
914
"""Keep track of how many times functions are called."""
728
916
def __init__(self, weave_name=None):
729
917
self._extract_count = 0
730
918
Weave.__init__(self, weave_name=weave_name)
734
922
return Weave._extract(self, versions)
737
class TestNeedsReweave(TestCase):
925
class JoinOptimization(TestCase):
926
"""Test that Weave.join() doesn't extract all texts, only what must be done."""
929
w1 = InstrumentedWeave()
930
w2 = InstrumentedWeave()
933
txt1 = ['a\n', 'b\n']
934
txt2 = ['a\n', 'c\n']
935
txt3 = ['a\n', 'b\n', 'c\n']
937
w1.add_lines('txt0', [], txt0) # extract 1a
938
w2.add_lines('txt0', [], txt0) # extract 1b
939
w1.add_lines('txt1', ['txt0'], txt1)# extract 2a
940
w2.add_lines('txt2', ['txt0'], txt2)# extract 2b
941
w1.join(w2) # extract 3a to add txt2
942
w2.join(w1) # extract 3b to add txt1
944
w1.add_lines('txt3', ['txt1', 'txt2'], txt3) # extract 4a
945
w2.add_lines('txt3', ['txt2', 'txt1'], txt3) # extract 4b
946
# These secretly have inverted parents
948
# This should not have to do any extractions
949
w1.join(w2) # NO extract, texts already present with same parents
950
w2.join(w1) # NO extract, texts already present with same parents
952
self.assertEqual(4, w1._extract_count)
953
self.assertEqual(4, w2._extract_count)
955
def test_double_parent(self):
956
# It should not be considered illegal to add
957
# a revision with the same parent twice
958
w1 = InstrumentedWeave()
959
w2 = InstrumentedWeave()
962
txt1 = ['a\n', 'b\n']
963
txt2 = ['a\n', 'c\n']
964
txt3 = ['a\n', 'b\n', 'c\n']
966
w1.add_lines('txt0', [], txt0)
967
w2.add_lines('txt0', [], txt0)
968
w1.add_lines('txt1', ['txt0'], txt1)
969
w2.add_lines('txt1', ['txt0', 'txt0'], txt1)
970
# Same text, effectively the same, because the
971
# parent is only repeated
972
w1.join(w2) # extract 3a to add txt2
973
w2.join(w1) # extract 3b to add txt1
976
class TestNeedsRweave(TestCase):
738
977
"""Internal corner cases for when reweave is needed."""
740
979
def test_compatible_parents(self):