3
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005-2009, 2011 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
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
682
654
self.check_read_write(k)
685
class MergeCases(TestBase):
686
def doMerge(self, base, a, b, mp):
687
from cStringIO import StringIO
688
from textwrap import dedent
694
w.add_lines('text0', [], map(addcrlf, base))
695
w.add_lines('text1', ['text0'], map(addcrlf, a))
696
w.add_lines('text2', ['text0'], map(addcrlf, b))
698
self.log('weave is:')
701
self.log(tmpf.getvalue())
703
self.log('merge plan:')
704
p = list(w.plan_merge('text1', 'text2'))
705
for state, line in p:
707
self.log('%12s | %s' % (state, line[:-1]))
711
mt.writelines(w.weave_merge(p))
713
self.log(mt.getvalue())
715
mp = map(addcrlf, mp)
716
self.assertEqual(mt.readlines(), mp)
719
def testOneInsert(self):
725
def testSeparateInserts(self):
726
self.doMerge(['aaa', 'bbb', 'ccc'],
727
['aaa', 'xxx', 'bbb', 'ccc'],
728
['aaa', 'bbb', 'yyy', 'ccc'],
729
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
731
def testSameInsert(self):
732
self.doMerge(['aaa', 'bbb', 'ccc'],
733
['aaa', 'xxx', 'bbb', 'ccc'],
734
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
735
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
737
def testOverlappedInsert(self):
738
self.doMerge(['aaa', 'bbb'],
739
['aaa', 'xxx', 'yyy', 'bbb'],
740
['aaa', 'xxx', 'bbb'],
741
['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 'xxx',
744
# really it ought to reduce this to
745
# ['aaa', 'xxx', 'yyy', 'bbb']
748
def testClashReplace(self):
749
self.doMerge(['aaa'],
752
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
755
def testNonClashInsert(self):
756
self.doMerge(['aaa'],
759
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
762
self.doMerge(['aaa'],
768
def testDeleteAndModify(self):
769
"""Clashing delete and modification.
771
If one side modifies a region and the other deletes it then
772
there should be a conflict with one side blank.
775
#######################################
776
# skippd, not working yet
779
self.doMerge(['aaa', 'bbb', 'ccc'],
780
['aaa', 'ddd', 'ccc'],
782
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
784
def _test_merge_from_strings(self, base, a, b, expected):
786
w.add_lines('text0', [], base.splitlines(True))
787
w.add_lines('text1', ['text0'], a.splitlines(True))
788
w.add_lines('text2', ['text0'], b.splitlines(True))
789
self.log('merge plan:')
790
p = list(w.plan_merge('text1', 'text2'))
791
for state, line in p:
793
self.log('%12s | %s' % (state, line[:-1]))
794
self.log('merge result:')
795
result_text = ''.join(w.weave_merge(p))
796
self.log(result_text)
797
self.assertEqualDiff(result_text, expected)
799
def test_deletion_extended(self):
800
"""One side deletes, the other deletes more.
817
self._test_merge_from_strings(base, a, b, result)
819
def test_deletion_overlap(self):
820
"""Delete overlapping regions with no other conflict.
822
Arguably it'd be better to treat these as agreement, rather than
823
conflict, but for now conflict is safer.
851
self._test_merge_from_strings(base, a, b, result)
853
def test_agreement_deletion(self):
854
"""Agree to delete some lines, without conflicts."""
876
self._test_merge_from_strings(base, a, b, result)
878
def test_sync_on_deletion(self):
879
"""Specific case of merge where we can synchronize incorrectly.
881
A previous version of the weave merge concluded that the two versions
882
agreed on deleting line 2, and this could be a synchronization point.
883
Line 1 was then considered in isolation, and thought to be deleted on
886
It's better to consider the whole thing as a disagreement region.
897
a's replacement line 2
910
a's replacement line 2
917
self._test_merge_from_strings(base, a, b, result)
920
657
class JoinWeavesTests(TestBase):
922
660
super(JoinWeavesTests, self).setUp()
923
661
self.weave1 = Weave()
926
664
self.weave1.add_lines('v1', [], self.lines1)
927
665
self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
928
666
self.weave1.add_lines('v3', ['v2'], self.lines3)
930
def test_join_empty(self):
931
"""Join two empty weaves."""
932
eq = self.assertEqual
938
def test_join_empty_to_nonempty(self):
939
"""Join empty weave onto nonempty."""
940
self.weave1.join(Weave())
941
self.assertEqual(len(self.weave1), 3)
943
def test_join_unrelated(self):
944
"""Join two weaves with no history in common."""
946
wb.add_lines('b1', [], ['line from b\n'])
949
eq = self.assertEqual
951
eq(sorted(w1.versions()),
952
['b1', 'v1', 'v2', 'v3'])
954
def test_join_related(self):
955
wa = self.weave1.copy()
956
wb = self.weave1.copy()
957
wa.add_lines('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
958
wb.add_lines('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
959
eq = self.assertEquals
964
eq(wa.get_lines('b1'),
965
['hello\n', 'pale blue\n', 'world\n'])
967
def test_join_parent_disagreement(self):
968
#join reconciles differening parents into a union.
971
wa.add_lines('v1', [], ['hello\n'])
972
wb.add_lines('v0', [], [])
973
wb.add_lines('v1', ['v0'], ['hello\n'])
975
self.assertEqual(['v0'], wa.get_parents('v1'))
977
def test_join_text_disagreement(self):
978
"""Cannot join weaves with different texts for a version."""
981
wa.add_lines('v1', [], ['hello\n'])
982
wb.add_lines('v1', [], ['not\n', 'hello\n'])
983
self.assertRaises(WeaveError,
986
def test_join_unordered(self):
987
"""Join weaves where indexes differ.
989
The source weave contains a different version at index 0."""
990
wa = self.weave1.copy()
992
wb.add_lines('x1', [], ['line from x1\n'])
993
wb.add_lines('v1', [], ['hello\n'])
994
wb.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
996
eq = self.assertEquals
997
eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
998
eq(wa.get_text('x1'), 'line from x1\n')
1000
668
def test_written_detection(self):
1001
669
# Test detection of weave file corruption.
1046
714
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
717
class TestWeave(TestCase):
719
def test_allow_reserved_false(self):
720
w = Weave('name', allow_reserved=False)
721
# Add lines is checked at the WeaveFile level, not at the Weave level
722
w.add_lines('name:', [], TEXT_1)
723
# But get_lines is checked at this level
724
self.assertRaises(errors.ReservedId, w.get_lines, 'name:')
726
def test_allow_reserved_true(self):
727
w = Weave('name', allow_reserved=True)
728
w.add_lines('name:', [], TEXT_1)
729
self.assertEqual(TEXT_1, w.get_lines('name:'))
1049
732
class InstrumentedWeave(Weave):
1050
733
"""Keep track of how many times functions are called."""
1052
735
def __init__(self, weave_name=None):
1053
736
self._extract_count = 0
1054
737
Weave.__init__(self, weave_name=weave_name)
1058
741
return Weave._extract(self, versions)
1061
class JoinOptimization(TestCase):
1062
"""Test that Weave.join() doesn't extract all texts, only what must be done."""
1064
def test_join(self):
1065
w1 = InstrumentedWeave()
1066
w2 = InstrumentedWeave()
1069
txt1 = ['a\n', 'b\n']
1070
txt2 = ['a\n', 'c\n']
1071
txt3 = ['a\n', 'b\n', 'c\n']
1073
w1.add_lines('txt0', [], txt0) # extract 1a
1074
w2.add_lines('txt0', [], txt0) # extract 1b
1075
w1.add_lines('txt1', ['txt0'], txt1)# extract 2a
1076
w2.add_lines('txt2', ['txt0'], txt2)# extract 2b
1077
w1.join(w2) # extract 3a to add txt2
1078
w2.join(w1) # extract 3b to add txt1
1080
w1.add_lines('txt3', ['txt1', 'txt2'], txt3) # extract 4a
1081
w2.add_lines('txt3', ['txt2', 'txt1'], txt3) # extract 4b
1082
# These secretly have inverted parents
1084
# This should not have to do any extractions
1085
w1.join(w2) # NO extract, texts already present with same parents
1086
w2.join(w1) # NO extract, texts already present with same parents
1088
self.assertEqual(4, w1._extract_count)
1089
self.assertEqual(4, w2._extract_count)
1091
def test_double_parent(self):
1092
# It should not be considered illegal to add
1093
# a revision with the same parent twice
1094
w1 = InstrumentedWeave()
1095
w2 = InstrumentedWeave()
1098
txt1 = ['a\n', 'b\n']
1099
txt2 = ['a\n', 'c\n']
1100
txt3 = ['a\n', 'b\n', 'c\n']
1102
w1.add_lines('txt0', [], txt0)
1103
w2.add_lines('txt0', [], txt0)
1104
w1.add_lines('txt1', ['txt0'], txt1)
1105
w2.add_lines('txt1', ['txt0', 'txt0'], txt1)
1106
# Same text, effectively the same, because the
1107
# parent is only repeated
1108
w1.join(w2) # extract 3a to add txt2
1109
w2.join(w1) # extract 3b to add txt1
1112
744
class TestNeedsReweave(TestCase):
1113
745
"""Internal corner cases for when reweave is needed."""