3
# Copyright (C) 2005 by Canonical Ltd
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
# TODO: tests regarding version names
24
"""test suite for weave algorithm"""
26
from pprint import pformat
28
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
29
from bzrlib.weavefile import write_weave, read_weave
30
from bzrlib.selftest import TestCase
31
from bzrlib.osutils import sha_string
34
# texts for use in testing
35
TEXT_0 = ["Hello world"]
36
TEXT_1 = ["Hello world",
41
class TestBase(TestCase):
42
def check_read_write(self, k):
43
"""Check the weave k can be written & re-read."""
44
from tempfile import TemporaryFile
53
self.log('serialized weave:')
57
self.log('parents: %s' % (k._parents == k2._parents))
58
self.log(' %r' % k._parents)
59
self.log(' %r' % k2._parents)
63
self.fail('read/write check failed')
73
class StoreText(TestBase):
74
"""Store and retrieve a simple text."""
77
idx = k.add('text0', [], TEXT_0)
78
self.assertEqual(k.get(idx), TEXT_0)
79
self.assertEqual(idx, 0)
83
class AnnotateOne(TestBase):
86
k.add('text0', [], TEXT_0)
87
self.assertEqual(k.annotate(0),
91
class StoreTwo(TestBase):
95
idx = k.add('text0', [], TEXT_0)
96
self.assertEqual(idx, 0)
98
idx = k.add('text1', [], TEXT_1)
99
self.assertEqual(idx, 1)
101
self.assertEqual(k.get(0), TEXT_0)
102
self.assertEqual(k.get(1), TEXT_1)
106
class AddWithGivenSha(TestBase):
108
"""Add with caller-supplied SHA-1"""
112
k.add('text0', [], [t], sha1=sha_string(t))
116
class InvalidAdd(TestBase):
117
"""Try to use invalid version number during add."""
121
self.assertRaises(IndexError,
128
class RepeatedAdd(TestBase):
129
"""Add the same version twice; harmless."""
132
idx = k.add('text0', [], TEXT_0)
133
idx2 = k.add('text0', [], TEXT_0)
134
self.assertEqual(idx, idx2)
138
class InvalidRepeatedAdd(TestBase):
141
idx = k.add('text0', [], TEXT_0)
142
self.assertRaises(WeaveError,
146
['not the same text'])
147
self.assertRaises(WeaveError,
150
[12], # not the right parents
155
class InsertLines(TestBase):
156
"""Store a revision that adds one line to the original.
158
Look at the annotations to make sure that the first line is matched
159
and not stored repeatedly."""
163
k.add('text0', [], ['line 1'])
164
k.add('text1', [0], ['line 1', 'line 2'])
166
self.assertEqual(k.annotate(0),
169
self.assertEqual(k.get(1),
173
self.assertEqual(k.annotate(1),
177
k.add('text2', [0], ['line 1', 'diverged line'])
179
self.assertEqual(k.annotate(2),
181
(2, 'diverged line')])
183
text3 = ['line 1', 'middle line', 'line 2']
188
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
190
self.log("k._weave=" + pformat(k._weave))
192
self.assertEqual(k.annotate(3),
197
# now multiple insertions at different places
200
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
202
self.assertEqual(k.annotate(4),
212
class DeleteLines(TestBase):
213
"""Deletion of lines from existing text.
215
Try various texts all based on a common ancestor."""
219
base_text = ['one', 'two', 'three', 'four']
221
k.add('text0', [], base_text)
223
texts = [['one', 'two', 'three'],
224
['two', 'three', 'four'],
226
['one', 'two', 'three', 'four'],
231
ver = k.add('text%d' % i,
235
self.log('final weave:')
236
self.log('k._weave=' + pformat(k._weave))
238
for i in range(len(texts)):
239
self.assertEqual(k.get(i+1),
245
class SuicideDelete(TestBase):
246
"""Invalid weave which tries to add and delete simultaneously."""
252
k._weave = [('{', 0),
259
################################### SKIPPED
260
# Weave.get doesn't trap this anymore
263
self.assertRaises(WeaveFormatError,
269
class CannedDelete(TestBase):
270
"""Unpack canned weave with deleted lines."""
277
k._weave = [('{', 0),
280
'line to be deleted',
286
self.assertEqual(k.get(0),
288
'line to be deleted',
292
self.assertEqual(k.get(1),
299
class CannedReplacement(TestBase):
300
"""Unpack canned weave with deleted lines."""
304
k._parents = [frozenset(),
307
k._weave = [('{', 0),
310
'line to be deleted',
319
self.assertEqual(k.get(0),
321
'line to be deleted',
325
self.assertEqual(k.get(1),
333
class BadWeave(TestBase):
334
"""Test that we trap an insert which should not occur."""
338
k._parents = [frozenset(),
340
k._weave = ['bad line',
344
' added in version 1',
353
################################### SKIPPED
354
# Weave.get doesn't trap this anymore
358
self.assertRaises(WeaveFormatError,
363
class BadInsert(TestBase):
364
"""Test that we trap an insert which should not occur."""
368
k._parents = [frozenset(),
373
k._weave = [('{', 0),
376
' added in version 1',
384
# this is not currently enforced by get
385
return ##########################################
387
self.assertRaises(WeaveFormatError,
391
self.assertRaises(WeaveFormatError,
396
class InsertNested(TestBase):
397
"""Insertion with nested instructions."""
401
k._parents = [frozenset(),
406
k._weave = [('{', 0),
409
' added in version 1',
418
self.assertEqual(k.get(0),
422
self.assertEqual(k.get(1),
424
' added in version 1',
428
self.assertEqual(k.get(2),
433
self.assertEqual(k.get(3),
435
' added in version 1',
442
class DeleteLines2(TestBase):
443
"""Test recording revisions that delete lines.
445
This relies on the weave having a way to represent lines knocked
446
out by a later revision."""
450
k.add('text0', [], ["line the first",
455
self.assertEqual(len(k.get(0)), 4)
457
k.add('text1', [0], ["line the first",
460
self.assertEqual(k.get(1),
464
self.assertEqual(k.annotate(1),
465
[(0, "line the first"),
470
class IncludeVersions(TestBase):
471
"""Check texts that are stored across multiple revisions.
473
Here we manually create a weave with particular encoding and make
474
sure it unpacks properly.
476
Text 0 includes nothing; text 1 includes text 0 and adds some
483
k._parents = [frozenset(), frozenset([0])]
484
k._weave = [('{', 0),
491
self.assertEqual(k.get(1),
495
self.assertEqual(k.get(0),
499
class DivergedIncludes(TestBase):
500
"""Weave with two diverged texts based on version 0.
505
k._parents = [frozenset(),
509
k._weave = [('{', 0),
516
"alternative second line",
520
self.assertEqual(k.get(0),
523
self.assertEqual(k.get(1),
527
self.assertEqual(k.get(2),
529
"alternative second line"])
531
self.assertEqual(list(k.inclusions([2])),
536
class ReplaceLine(TestBase):
540
text0 = ['cheddar', 'stilton', 'gruyere']
541
text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
543
k.add('text0', [], text0)
544
k.add('text1', [0], text1)
546
self.log('k._weave=' + pformat(k._weave))
548
self.assertEqual(k.get(0), text0)
549
self.assertEqual(k.get(1), text1)
553
class Merge(TestBase):
554
"""Storage of versions that merge diverged parents"""
559
['header', '', 'line from 1'],
560
['header', '', 'line from 2', 'more from 2'],
561
['header', '', 'line from 1', 'fixup line', 'line from 2'],
564
k.add('text0', [], texts[0])
565
k.add('text1', [0], texts[1])
566
k.add('text2', [0], texts[2])
567
k.add('merge', [0, 1, 2], texts[3])
569
for i, t in enumerate(texts):
570
self.assertEqual(k.get(i), t)
572
self.assertEqual(k.annotate(3),
580
self.assertEqual(list(k.inclusions([3])),
583
self.log('k._weave=' + pformat(k._weave))
585
self.check_read_write(k)
588
class Conflicts(TestBase):
589
"""Test detection of conflicting regions during a merge.
591
A base version is inserted, then two descendents try to
592
insert different lines in the same place. These should be
593
reported as a possible conflict and forwarded to the user."""
598
k.add([], ['aaa', 'bbb'])
599
k.add([0], ['aaa', '111', 'bbb'])
600
k.add([1], ['aaa', '222', 'bbb'])
602
merged = k.merge([1, 2])
604
self.assertEquals([[['aaa']],
610
class NonConflict(TestBase):
611
"""Two descendants insert compatible changes.
613
No conflict should be reported."""
618
k.add([], ['aaa', 'bbb'])
619
k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
620
k.add([1], ['aaa', 'ccc', 'bbb', '222'])
626
class AutoMerge(TestBase):
630
texts = [['header', 'aaa', 'bbb'],
631
['header', 'aaa', 'line from 1', 'bbb'],
632
['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
635
k.add('text0', [], texts[0])
636
k.add('text1', [0], texts[1])
637
k.add('text2', [0], texts[2])
639
self.log('k._weave=' + pformat(k._weave))
641
m = list(k.mash_iter([0, 1, 2]))
647
'line from 2', 'more from 2'])
651
class Khayyam(TestBase):
652
"""Test changes to multi-line texts, and read/write"""
655
"""A Book of Verses underneath the Bough,
656
A Jug of Wine, a Loaf of Bread, -- and Thou
657
Beside me singing in the Wilderness --
658
Oh, Wilderness were Paradise enow!""",
660
"""A Book of Verses underneath the Bough,
661
A Jug of Wine, a Loaf of Bread, -- and Thou
662
Beside me singing in the Wilderness --
663
Oh, Wilderness were Paradise now!""",
665
"""A Book of poems underneath the tree,
666
A Jug of Wine, a Loaf of Bread,
668
Beside me singing in the Wilderness --
669
Oh, Wilderness were Paradise now!
673
"""A Book of Verses underneath the Bough,
674
A Jug of Wine, a Loaf of Bread,
676
Beside me singing in the Wilderness --
677
Oh, Wilderness were Paradise now!""",
679
texts = [[l.strip() for l in t.split('\n')] for t in rawtexts]
685
ver = k.add('text%d' % i,
690
self.log("k._weave=" + pformat(k._weave))
692
for i, t in enumerate(texts):
693
self.assertEqual(k.get(i), t)
695
self.check_read_write(k)
699
class MergeCases(TestBase):
700
def doMerge(self, base, a, b, mp):
701
from cStringIO import StringIO
702
from textwrap import dedent
708
w.add('text0', [], map(addcrlf, base))
709
w.add('text1', [0], map(addcrlf, a))
710
w.add('text2', [0], map(addcrlf, b))
712
self.log('weave is:')
715
self.log(tmpf.getvalue())
717
self.log('merge plan:')
718
p = list(w.plan_merge(1, 2))
719
for state, line in p:
721
self.log('%12s | %s' % (state, line[:-1]))
725
mt.writelines(w.weave_merge(p))
727
self.log(mt.getvalue())
729
mp = map(addcrlf, mp)
730
self.assertEqual(mt.readlines(), mp)
733
def testOneInsert(self):
739
def testSeparateInserts(self):
740
self.doMerge(['aaa', 'bbb', 'ccc'],
741
['aaa', 'xxx', 'bbb', 'ccc'],
742
['aaa', 'bbb', 'yyy', 'ccc'],
743
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
745
def testSameInsert(self):
746
self.doMerge(['aaa', 'bbb', 'ccc'],
747
['aaa', 'xxx', 'bbb', 'ccc'],
748
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
749
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
751
def testOverlappedInsert(self):
752
self.doMerge(['aaa', 'bbb'],
753
['aaa', 'xxx', 'yyy', 'bbb'],
754
['aaa', 'xxx', 'bbb'],
755
['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
757
# really it ought to reduce this to
758
# ['aaa', 'xxx', 'yyy', 'bbb']
761
def testClashReplace(self):
762
self.doMerge(['aaa'],
765
['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
767
def testNonClashInsert(self):
768
self.doMerge(['aaa'],
771
['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
773
self.doMerge(['aaa'],
779
def testDeleteAndModify(self):
780
"""Clashing delete and modification.
782
If one side modifies a region and the other deletes it then
783
there should be a conflict with one side blank.
786
#######################################
787
# skippd, not working yet
790
self.doMerge(['aaa', 'bbb', 'ccc'],
791
['aaa', 'ddd', 'ccc'],
793
['<<<<', 'aaa', '====', '>>>>', 'ccc'])
796
class JoinWeavesTests(TestBase):
798
super(JoinWeavesTests, self).setUp()
799
self.weave1 = Weave()
800
self.lines1 = ['hello\n']
801
self.lines3 = ['hello\n', 'cruel\n', 'world\n']
802
self.weave1.add('v1', [], self.lines1)
803
self.weave1.add('v2', [0], ['hello\n', 'world\n'])
804
self.weave1.add('v3', [1], self.lines3)
806
def test_join_empty(self):
807
"""Join two empty weaves."""
808
eq = self.assertEqual
812
eq(w1.numversions(), 0)
814
def test_join_empty_to_nonempty(self):
815
"""Join empty weave onto nonempty."""
816
self.weave1.join(Weave())
817
self.assertEqual(len(self.weave1), 3)
819
def test_join_unrelated(self):
820
"""Join two weaves with no history in common."""
822
wb.add('b1', [], ['line from b\n'])
825
eq = self.assertEqual
827
eq(sorted(list(w1.iter_names())),
828
['b1', 'v1', 'v2', 'v3'])
830
def test_join_related(self):
831
wa = self.weave1.copy()
832
wb = self.weave1.copy()
833
wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
834
wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
835
eq = self.assertEquals
840
eq(wa.get_lines('b1'),
841
['hello\n', 'pale blue\n', 'world\n'])
843
def test_join_parent_disagreement(self):
844
"""Cannot join weaves with different parents for a version."""
847
wa.add('v1', [], ['hello\n'])
849
wb.add('v1', ['v0'], ['hello\n'])
850
self.assertRaises(WeaveError,
853
def test_join_text_disagreement(self):
854
"""Cannot join weaves with different texts for a version."""
857
wa.add('v1', [], ['hello\n'])
858
wb.add('v1', [], ['not\n', 'hello\n'])
859
self.assertRaises(WeaveError,
862
def test_join_unordered(self):
863
"""Join weaves where indexes differ.
865
The source weave contains a different version at index 0."""
866
wa = self.weave1.copy()
868
wb.add('x1', [], ['line from x1\n'])
869
wb.add('v1', [], ['hello\n'])
870
wb.add('v2', ['v1'], ['hello\n', 'world\n'])
872
eq = self.assertEquals
873
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
874
eq(wa.get_text('x1'), 'line from x1\n')
876
def test_reweave_with_empty(self):
878
wr = reweave(self.weave1, wb)
879
eq = self.assertEquals
880
eq(sorted(wr.iter_names()), ['v1', 'v2', 'v3'])
881
eq(wr.get_lines('v3'), ['hello\n', 'cruel\n', 'world\n'])
883
def test_reweave_with_ghosts(self):
884
"""Join that inserts parents of an existing revision.
886
This can happen when merging from another branch who
887
knows about revisions the destination does not. In
888
this test the second weave knows of an additional parent of
889
v2. Any revisions which are in common still have to have the
891
wa = self.weave1.copy()
893
wb.add('x1', [], ['line from x1\n'])
894
wb.add('v1', [], ['hello\n'])
895
wb.add('v2', ['v1', 'x1'], ['hello\n', 'world\n'])
897
eq = self.assertEquals
898
eq(sorted(wc.iter_names()), ['v1', 'v2', 'v3', 'x1',])
899
eq(wc.get_text('x1'), 'line from x1\n')
900
eq(wc.get_lines('v2'), ['hello\n', 'world\n'])
901
eq(wc.parent_names('v2'), ['v1', 'x1'])
904
if __name__ == '__main__':
907
sys.exit(unittest.main())