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
93
[('text0', TEXT_0[0])])
96
class StoreTwo(TestBase):
100
idx = k.add_lines('text0', [], TEXT_0)
101
self.assertEqual(idx, 0)
103
idx = k.add_lines('text1', [], TEXT_1)
104
self.assertEqual(idx, 1)
106
self.assertEqual(k.get_lines(0), TEXT_0)
107
self.assertEqual(k.get_lines(1), TEXT_1)
110
class GetSha1(TestBase):
111
def test_get_sha1(self):
113
k.add_lines('text0', [], 'text0')
114
self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
116
self.assertRaises(errors.RevisionNotPresent,
118
self.assertRaises(errors.RevisionNotPresent,
87
122
class InvalidAdd(TestBase):
88
123
"""Try to use invalid version number during add."""
89
124
def runTest(self):
648
682
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'])
651
785
class JoinWeavesTests(TestBase):
653
787
super(JoinWeavesTests, self).setUp()
657
791
self.weave1.add_lines('v1', [], self.lines1)
658
792
self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
659
793
self.weave1.add_lines('v3', ['v2'], self.lines3)
795
def test_join_empty(self):
796
"""Join two empty weaves."""
797
eq = self.assertEqual
803
def test_join_empty_to_nonempty(self):
804
"""Join empty weave onto nonempty."""
805
self.weave1.join(Weave())
806
self.assertEqual(len(self.weave1), 3)
808
def test_join_unrelated(self):
809
"""Join two weaves with no history in common."""
811
wb.add_lines('b1', [], ['line from b\n'])
814
eq = self.assertEqual
816
eq(sorted(w1.versions()),
817
['b1', 'v1', 'v2', 'v3'])
819
def test_join_related(self):
820
wa = self.weave1.copy()
821
wb = self.weave1.copy()
822
wa.add_lines('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
823
wb.add_lines('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
824
eq = self.assertEquals
829
eq(wa.get_lines('b1'),
830
['hello\n', 'pale blue\n', 'world\n'])
832
def test_join_parent_disagreement(self):
833
#join reconciles differening parents into a union.
836
wa.add_lines('v1', [], ['hello\n'])
837
wb.add_lines('v0', [], [])
838
wb.add_lines('v1', ['v0'], ['hello\n'])
840
self.assertEqual(['v0'], wa.get_parents('v1'))
842
def test_join_text_disagreement(self):
843
"""Cannot join weaves with different texts for a version."""
846
wa.add_lines('v1', [], ['hello\n'])
847
wb.add_lines('v1', [], ['not\n', 'hello\n'])
848
self.assertRaises(WeaveError,
851
def test_join_unordered(self):
852
"""Join weaves where indexes differ.
854
The source weave contains a different version at index 0."""
855
wa = self.weave1.copy()
857
wb.add_lines('x1', [], ['line from x1\n'])
858
wb.add_lines('v1', [], ['hello\n'])
859
wb.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
861
eq = self.assertEquals
862
eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
863
eq(wa.get_text('x1'), 'line from x1\n')
661
865
def test_written_detection(self):
662
866
# Test detection of weave file corruption.
707
911
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
914
class InstrumentedWeave(Weave):
726
915
"""Keep track of how many times functions are called."""
728
917
def __init__(self, weave_name=None):
729
918
self._extract_count = 0
730
919
Weave.__init__(self, weave_name=weave_name)
734
923
return Weave._extract(self, versions)
737
class TestNeedsReweave(TestCase):
926
class JoinOptimization(TestCase):
927
"""Test that Weave.join() doesn't extract all texts, only what must be done."""
930
w1 = InstrumentedWeave()
931
w2 = InstrumentedWeave()
934
txt1 = ['a\n', 'b\n']
935
txt2 = ['a\n', 'c\n']
936
txt3 = ['a\n', 'b\n', 'c\n']
938
w1.add_lines('txt0', [], txt0) # extract 1a
939
w2.add_lines('txt0', [], txt0) # extract 1b
940
w1.add_lines('txt1', ['txt0'], txt1)# extract 2a
941
w2.add_lines('txt2', ['txt0'], txt2)# extract 2b
942
w1.join(w2) # extract 3a to add txt2
943
w2.join(w1) # extract 3b to add txt1
945
w1.add_lines('txt3', ['txt1', 'txt2'], txt3) # extract 4a
946
w2.add_lines('txt3', ['txt2', 'txt1'], txt3) # extract 4b
947
# These secretly have inverted parents
949
# This should not have to do any extractions
950
w1.join(w2) # NO extract, texts already present with same parents
951
w2.join(w1) # NO extract, texts already present with same parents
953
self.assertEqual(4, w1._extract_count)
954
self.assertEqual(4, w2._extract_count)
956
def test_double_parent(self):
957
# It should not be considered illegal to add
958
# a revision with the same parent twice
959
w1 = InstrumentedWeave()
960
w2 = InstrumentedWeave()
963
txt1 = ['a\n', 'b\n']
964
txt2 = ['a\n', 'c\n']
965
txt3 = ['a\n', 'b\n', 'c\n']
967
w1.add_lines('txt0', [], txt0)
968
w2.add_lines('txt0', [], txt0)
969
w1.add_lines('txt1', ['txt0'], txt1)
970
w2.add_lines('txt1', ['txt0', 'txt0'], txt1)
971
# Same text, effectively the same, because the
972
# parent is only repeated
973
w1.join(w2) # extract 3a to add txt2
974
w2.join(w1) # extract 3b to add txt1
977
class TestNeedsRweave(TestCase):
738
978
"""Internal corner cases for when reweave is needed."""
740
980
def test_compatible_parents(self):