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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
# TODO: tests regarding version names
19
# TODO: rbc 20050108 test that join does not leave an inconsistent weave
22
22
"""test suite for weave algorithm"""
24
from pprint import pformat
26
from bzrlib.weave import Weave, WeaveFormatError
29
from bzrlib.osutils import sha_string
30
from bzrlib.tests import TestCase, TestCaseInTempDir
31
from bzrlib.weave import Weave, WeaveFormatError, WeaveError
27
32
from bzrlib.weavefile import write_weave, read_weave
28
from pprint import pformat
35
from sets import Set, ImmutableSet
37
frozenset = ImmutableSet
42
35
# texts for use in testing
124
131
def runTest(self):
127
k.add([], ['line 1'])
128
k.add([0], ['line 1', 'line 2'])
130
self.assertEqual(k.annotate(0),
133
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),
137
self.assertEqual(k.annotate(1),
141
k.add([0], ['line 1', 'diverged line'])
143
self.assertEqual(k.annotate(2),
145
(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')])
147
154
text3 = ['line 1', 'middle line', 'line 2']
151
159
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
153
161
self.log("k._weave=" + pformat(k._weave))
155
self.assertEqual(k.annotate(3),
163
self.assertEqual(k.annotate('text3'),
164
[('text0', 'line 1'),
165
('text3', 'middle line'),
166
('text1', 'line 2')])
160
168
# now multiple insertions at different places
170
['text0', 'text1', 'text3'],
162
171
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
164
self.assertEqual(k.annotate(4),
173
self.assertEqual(k.annotate('text4'),
174
[('text0', 'line 1'),
176
('text3', 'middle line'),
174
182
class DeleteLines(TestBase):
522
542
['header', '', 'line from 1', 'fixup line', 'line from 2'],
528
k.add([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])
530
550
for i, t in enumerate(texts):
531
self.assertEqual(k.get(i), t)
551
self.assertEqual(k.get_lines(i), t)
533
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'),
541
self.assertEqual(list(k.inclusions([3])),
561
self.assertEqual(list(k.get_ancestry(['merge'])),
562
['text0', 'text1', 'text2', 'merge'])
544
564
self.log('k._weave=' + pformat(k._weave))
579
k.add([], ['aaa', 'bbb'])
580
k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
581
k.add([1], ['aaa', 'ccc', 'bbb', '222'])
587
class AutoMerge(TestBase):
591
texts = [['header', 'aaa', 'bbb'],
592
['header', 'aaa', 'line from 1', 'bbb'],
593
['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
600
self.log('k._weave=' + pformat(k._weave))
602
m = list(k.mash_iter([0, 1, 2]))
608
'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'])
612
603
class Khayyam(TestBase):
613
604
"""Test changes to multi-line texts, and read/write"""
606
def test_multi_line_merge(self):
616
608
"""A Book of Verses underneath the Bough,
617
609
A Jug of Wine, a Loaf of Bread, -- and Thou
645
ver = k.add(list(parents), t)
638
ver = k.add_lines('text%d' % i,
640
parents.add('text%d' % i)
648
643
self.log("k._weave=" + pformat(k._weave))
650
645
for i, t in enumerate(texts):
651
self.assertEqual(k.get(i), t)
646
self.assertEqual(k.get_lines(i), t)
653
648
self.check_read_write(k)
651
class JoinWeavesTests(TestBase):
653
super(JoinWeavesTests, self).setUp()
654
self.weave1 = Weave()
655
self.lines1 = ['hello\n']
656
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)
657
class MergeCases(TestBase):
658
def doMerge(self, base, a, b, mp):
661
def test_written_detection(self):
662
# Test detection of weave file corruption.
664
# Make sure that we can detect if a weave file has
665
# been corrupted. This doesn't test all forms of corruption,
666
# but it at least helps verify the data you get, is what you want.
659
667
from cStringIO import StringIO
660
from textwrap import dedent
666
w.add([], map(addcrlf, base))
667
w.add([0], map(addcrlf, a))
668
w.add([0], map(addcrlf, b))
670
w.add_lines('v1', [], ['hello\n'])
671
w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
670
self.log('weave is:')
671
673
tmpf = StringIO()
672
674
write_weave(w, tmpf)
673
self.log(tmpf.getvalue())
675
self.log('merge plan:')
676
p = list(w.plan_merge(1, 2))
677
for state, line in p:
679
self.log('%12s | %s' % (state, line[:-1]))
683
mt.writelines(w.weave_merge(p))
685
self.log(mt.getvalue())
687
mp = map(addcrlf, mp)
688
self.assertEqual(mt.readlines(), mp)
691
def testOneInsert(self):
697
def testSeparateInserts(self):
698
self.doMerge(['aaa', 'bbb', 'ccc'],
699
['aaa', 'xxx', 'bbb', 'ccc'],
700
['aaa', 'bbb', 'yyy', 'ccc'],
701
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
703
def testSameInsert(self):
704
self.doMerge(['aaa', 'bbb', 'ccc'],
705
['aaa', 'xxx', 'bbb', 'ccc'],
706
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
707
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
709
def testOverlappedInsert(self):
710
self.doMerge(['aaa', 'bbb'],
711
['aaa', 'xxx', 'yyy', 'bbb'],
712
['aaa', 'xxx', 'bbb'],
713
['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
715
# really it ought to reduce this to
716
# ['aaa', 'xxx', 'yyy', 'bbb']
719
def testClashReplace(self):
720
self.doMerge(['aaa'],
723
['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
725
def testNonClashInsert(self):
726
self.doMerge(['aaa'],
729
['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
731
self.doMerge(['aaa'],
737
def testDeleteAndModify(self):
738
"""Clashing delete and modification.
740
If one side modifies a region and the other deletes it then
741
there should be a conflict with one side blank.
744
#######################################
745
# skippd, not working yet
748
self.doMerge(['aaa', 'bbb', 'ccc'],
749
['aaa', 'ddd', 'ccc'],
751
['<<<<', 'aaa', '====', '>>>>', 'ccc'])
757
from unittest import TestSuite, TestLoader
762
suite.addTest(tl.loadTestsFromModule(testweave))
764
return int(not testsweet.run_suite(suite)) # for shell 0=true
767
if __name__ == '__main__':
769
sys.exit(testweave())
676
# Because we are corrupting, we need to make sure we have the exact text
677
self.assertEquals('# bzr weave file v5\n'
678
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
679
'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
680
'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
683
# Change a single letter
684
tmpf = StringIO('# bzr weave file v5\n'
685
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
686
'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
687
'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
691
self.assertEqual('hello\n', w.get_text('v1'))
692
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
693
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
694
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
696
# Change the sha checksum
697
tmpf = StringIO('# bzr weave file v5\n'
698
'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
699
'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
700
'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
704
self.assertEqual('hello\n', w.get_text('v1'))
705
self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
706
self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
707
self.assertRaises(errors.WeaveInvalidChecksum, w.check)
710
class InstrumentedWeave(Weave):
711
"""Keep track of how many times functions are called."""
713
def __init__(self, weave_name=None):
714
self._extract_count = 0
715
Weave.__init__(self, weave_name=weave_name)
717
def _extract(self, versions):
718
self._extract_count += 1
719
return Weave._extract(self, versions)
722
class TestNeedsReweave(TestCase):
723
"""Internal corner cases for when reweave is needed."""
725
def test_compatible_parents(self):
727
my_parents = set([1, 2, 3])
729
self.assertTrue(w1._compatible_parents(my_parents, set([3])))
731
self.assertTrue(w1._compatible_parents(my_parents, set(my_parents)))
732
# same empty corner case
733
self.assertTrue(w1._compatible_parents(set(), set()))
734
# other cannot contain stuff my_parents does not
735
self.assertFalse(w1._compatible_parents(set(), set([1])))
736
self.assertFalse(w1._compatible_parents(my_parents, set([1, 2, 3, 4])))
737
self.assertFalse(w1._compatible_parents(my_parents, set([4])))
740
class TestWeaveFile(TestCaseInTempDir):
742
def test_empty_file(self):
743
f = open('empty.weave', 'wb+')
745
self.assertRaises(errors.WeaveFormatError,