1
# Copyright (C) 2005-2011, 2016 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
29
from ..osutils import sha_string
30
from ..sixish import (
33
from . import TestCase, TestCaseInTempDir
34
from ..bzr.weave import Weave, WeaveFormatError, WeaveInvalidChecksum
35
from ..bzr.weavefile import write_weave, read_weave
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.selftest import TestCase
32
from bzrlib.osutils import sha_string
38
35
# texts for use in testing
39
TEXT_0 = [b"Hello world"]
40
TEXT_1 = [b"Hello world",
36
TEXT_0 = ["Hello world"]
37
TEXT_1 = ["Hello world",
44
42
class TestBase(TestCase):
46
43
def check_read_write(self, k):
47
44
"""Check the weave k can be written & re-read."""
48
45
from tempfile import TemporaryFile
68
65
class WeaveContains(TestBase):
69
66
"""Weave __contains__ operator"""
72
k = Weave(get_scope=lambda: None)
73
self.assertFalse(b'foo' in k)
74
k.add_lines(b'foo', [], TEXT_1)
75
self.assertTrue(b'foo' in k)
69
self.assertFalse('foo' in k)
70
k.add('foo', [], TEXT_1)
71
self.assertTrue('foo' in k)
78
74
class Easy(TestBase):
79
class StoreText(TestBase):
80
"""Store and retrieve a simple text."""
83
idx = k.add('text0', [], TEXT_0)
84
self.assertEqual(k.get(idx), TEXT_0)
85
self.assertEqual(idx, 0)
84
89
class AnnotateOne(TestBase):
88
k.add_lines(b'text0', [], TEXT_0)
89
self.assertEqual(k.annotate(b'text0'),
90
[(b'text0', TEXT_0[0])])
92
k.add('text0', [], TEXT_0)
93
self.assertEqual(k.annotate(0),
97
class StoreTwo(TestBase):
101
idx = k.add('text0', [], TEXT_0)
102
self.assertEqual(idx, 0)
104
idx = k.add('text1', [], TEXT_1)
105
self.assertEqual(idx, 1)
107
self.assertEqual(k.get(0), TEXT_0)
108
self.assertEqual(k.get(1), TEXT_1)
112
class AddWithGivenSha(TestBase):
114
"""Add with caller-supplied SHA-1"""
118
k.add('text0', [], [t], sha1=sha_string(t))
93
122
class InvalidAdd(TestBase):
94
123
"""Try to use invalid version number during add."""
96
124
def runTest(self):
99
self.assertRaises(errors.RevisionNotPresent,
127
self.assertRaises(IndexError,
106
134
class RepeatedAdd(TestBase):
107
135
"""Add the same version twice; harmless."""
109
def test_duplicate_add(self):
111
idx = k.add_lines(b'text0', [], TEXT_0)
112
idx2 = k.add_lines(b'text0', [], TEXT_0)
138
idx = k.add('text0', [], TEXT_0)
139
idx2 = k.add('text0', [], TEXT_0)
113
140
self.assertEqual(idx, idx2)
116
144
class InvalidRepeatedAdd(TestBase):
118
145
def runTest(self):
120
k.add_lines(b'basis', [], TEXT_0)
121
k.add_lines(b'text0', [], TEXT_0)
122
self.assertRaises(errors.RevisionAlreadyPresent,
147
idx = k.add('text0', [], TEXT_0)
148
self.assertRaises(WeaveError,
126
[b'not the same text'])
127
self.assertRaises(errors.RevisionAlreadyPresent,
130
[b'basis'], # not the right parents
152
['not the same text'])
153
self.assertRaises(WeaveError,
156
[12], # not the right parents
134
161
class InsertLines(TestBase):
137
164
Look at the annotations to make sure that the first line is matched
138
165
and not stored repeatedly."""
140
166
def runTest(self):
143
k.add_lines(b'text0', [], [b'line 1'])
144
k.add_lines(b'text1', [b'text0'], [b'line 1', b'line 2'])
146
self.assertEqual(k.annotate(b'text0'),
147
[(b'text0', b'line 1')])
149
self.assertEqual(k.get_lines(1),
153
self.assertEqual(k.annotate(b'text1'),
154
[(b'text0', b'line 1'),
155
(b'text1', b'line 2')])
157
k.add_lines(b'text2', [b'text0'], [b'line 1', b'diverged line'])
159
self.assertEqual(k.annotate(b'text2'),
160
[(b'text0', b'line 1'),
161
(b'text2', b'diverged line')])
163
text3 = [b'line 1', b'middle line', b'line 2']
164
k.add_lines(b'text3',
165
[b'text0', b'text1'],
168
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]),
169
k.add('text0', [], ['line 1'])
170
k.add('text1', [0], ['line 1', 'line 2'])
172
self.assertEqual(k.annotate(0),
175
self.assertEqual(k.get(1),
179
self.assertEqual(k.annotate(1),
183
k.add('text2', [0], ['line 1', 'diverged line'])
185
self.assertEqual(k.annotate(2),
187
(2, 'diverged line')])
189
text3 = ['line 1', 'middle line', 'line 2']
194
# self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
171
196
self.log("k._weave=" + pformat(k._weave))
173
self.assertEqual(k.annotate(b'text3'),
174
[(b'text0', b'line 1'),
175
(b'text3', b'middle line'),
176
(b'text1', b'line 2')])
198
self.assertEqual(k.annotate(3),
178
203
# now multiple insertions at different places
180
b'text4', [b'text0', b'text1', b'text3'],
181
[b'line 1', b'aaa', b'middle line', b'bbb', b'line 2', b'ccc'])
183
self.assertEqual(k.annotate(b'text4'),
184
[(b'text0', b'line 1'),
186
(b'text3', b'middle line'),
188
(b'text1', b'line 2'),
206
['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
208
self.assertEqual(k.annotate(4),
192
218
class DeleteLines(TestBase):
193
219
"""Deletion of lines from existing text.
195
221
Try various texts all based on a common ancestor."""
197
222
def runTest(self):
200
base_text = [b'one', b'two', b'three', b'four']
202
k.add_lines(b'text0', [], base_text)
204
texts = [[b'one', b'two', b'three'],
205
[b'two', b'three', b'four'],
207
[b'one', b'two', b'three', b'four'],
225
base_text = ['one', 'two', 'three', 'four']
227
k.add('text0', [], base_text)
229
texts = [['one', 'two', 'three'],
230
['two', 'three', 'four'],
232
['one', 'two', 'three', 'four'],
212
k.add_lines(b'text%d' % i, [b'text0'], t)
237
ver = k.add('text%d' % i,
215
241
self.log('final weave:')
216
242
self.log('k._weave=' + pformat(k._weave))
218
244
for i in range(len(texts)):
219
self.assertEqual(k.get_lines(i + 1),
245
self.assertEqual(k.get(i+1),
223
251
class SuicideDelete(TestBase):
224
252
"""Invalid weave which tries to add and delete simultaneously."""
226
253
def runTest(self):
229
256
k._parents = [(),
231
k._weave = [(b'{', 0),
258
k._weave = [('{', 0),
265
################################### SKIPPED
239
266
# Weave.get doesn't trap this anymore
242
269
self.assertRaises(WeaveFormatError,
247
275
class CannedDelete(TestBase):
248
276
"""Unpack canned weave with deleted lines."""
250
277
def runTest(self):
253
280
k._parents = [(),
256
k._weave = [(b'{', 0),
259
b'line to be deleted',
265
sha_string(b'first lineline to be deletedlast line'),
266
sha_string(b'first linelast line')]
268
self.assertEqual(k.get_lines(0),
270
b'line to be deleted',
274
self.assertEqual(k.get_lines(1),
283
k._weave = [('{', 0),
286
'line to be deleted',
292
self.assertEqual(k.get(0),
294
'line to be deleted',
298
self.assertEqual(k.get(1),
280
305
class CannedReplacement(TestBase):
281
306
"""Unpack canned weave with deleted lines."""
283
307
def runTest(self):
286
310
k._parents = [frozenset(),
289
k._weave = [(b'{', 0),
292
b'line to be deleted',
301
sha_string(b'first lineline to be deletedlast line'),
302
sha_string(b'first linereplacement linelast line')]
304
self.assertEqual(k.get_lines(0),
306
b'line to be deleted',
310
self.assertEqual(k.get_lines(1),
313
k._weave = [('{', 0),
316
'line to be deleted',
325
self.assertEqual(k.get(0),
327
'line to be deleted',
331
self.assertEqual(k.get(1),
317
339
class BadWeave(TestBase):
318
340
"""Test that we trap an insert which should not occur."""
320
341
def runTest(self):
323
344
k._parents = [frozenset(),
325
k._weave = [b'bad line',
329
b' added in version 1',
346
k._weave = ['bad line',
350
' added in version 1',
359
################################### SKIPPED
339
360
# Weave.get doesn't trap this anymore
342
364
self.assertRaises(WeaveFormatError,
475
489
k._parents = [frozenset(), frozenset([0])]
476
k._weave = [(b'{', 0),
483
k._sha1s = [sha_string(b'first line'), sha_string(
484
b'first linesecond line')]
486
self.assertEqual(k.get_lines(1),
490
self.assertEqual(k.get_lines(0),
490
k._weave = [('{', 0),
497
self.assertEqual(k.get(1),
501
self.assertEqual(k.get(0),
494
505
class DivergedIncludes(TestBase):
495
506
"""Weave with two diverged texts based on version 0.
498
508
def runTest(self):
499
# FIXME make the weave, dont poke at it.
502
k._names = [b'0', b'1', b'2']
503
k._name_map = {b'0': 0, b'1': 1, b'2': 2}
504
511
k._parents = [frozenset(),
508
k._weave = [(b'{', 0),
515
b"alternative second line",
520
sha_string(b'first line'),
521
sha_string(b'first linesecond line'),
522
sha_string(b'first linealternative second line')]
524
self.assertEqual(k.get_lines(0),
527
self.assertEqual(k.get_lines(1),
531
self.assertEqual(k.get_lines(b'2'),
533
b"alternative second line"])
535
self.assertEqual(list(k.get_ancestry([b'2'])),
515
k._weave = [('{', 0),
522
"alternative second line",
526
self.assertEqual(k.get(0),
529
self.assertEqual(k.get(1),
533
self.assertEqual(k.get(2),
535
"alternative second line"])
537
self.assertEqual(list(k.inclusions([2])),
539
542
class ReplaceLine(TestBase):
540
543
def runTest(self):
543
text0 = [b'cheddar', b'stilton', b'gruyere']
544
text1 = [b'cheddar', b'blue vein', b'neufchatel', b'chevre']
546
k.add_lines(b'text0', [], text0)
547
k.add_lines(b'text1', [b'text0'], text1)
546
text0 = ['cheddar', 'stilton', 'gruyere']
547
text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
549
k.add('text0', [], text0)
550
k.add('text1', [0], text1)
549
552
self.log('k._weave=' + pformat(k._weave))
551
self.assertEqual(k.get_lines(0), text0)
552
self.assertEqual(k.get_lines(1), text1)
554
self.assertEqual(k.get(0), text0)
555
self.assertEqual(k.get(1), text1)
555
559
class Merge(TestBase):
556
560
"""Storage of versions that merge diverged parents"""
558
561
def runTest(self):
563
[b'header', b'', b'line from 1'],
564
[b'header', b'', b'line from 2', b'more from 2'],
565
[b'header', b'', b'line from 1', b'fixup line', b'line from 2'],
565
['header', '', 'line from 1'],
566
['header', '', 'line from 2', 'more from 2'],
567
['header', '', 'line from 1', 'fixup line', 'line from 2'],
568
k.add_lines(b'text0', [], texts[0])
569
k.add_lines(b'text1', [b'text0'], texts[1])
570
k.add_lines(b'text2', [b'text0'], texts[2])
571
k.add_lines(b'merge', [b'text0', b'text1', b'text2'], texts[3])
570
k.add('text0', [], texts[0])
571
k.add('text1', [0], texts[1])
572
k.add('text2', [0], texts[2])
573
k.add('merge', [0, 1, 2], texts[3])
573
575
for i, t in enumerate(texts):
574
self.assertEqual(k.get_lines(i), t)
576
self.assertEqual(k.get(i), t)
576
self.assertEqual(k.annotate(b'merge'),
577
[(b'text0', b'header'),
579
(b'text1', b'line from 1'),
580
(b'merge', b'fixup line'),
581
(b'text2', b'line from 2'),
578
self.assertEqual(k.annotate(3),
584
self.assertEqual(list(k.get_ancestry([b'merge'])),
585
[b'text0', b'text1', b'text2', b'merge'])
586
self.assertEqual(list(k.inclusions([3])),
587
589
self.log('k._weave=' + pformat(k._weave))
595
597
A base version is inserted, then two descendents try to
596
598
insert different lines in the same place. These should be
597
599
reported as a possible conflict and forwarded to the user."""
599
600
def runTest(self):
603
k.add_lines([], [b'aaa', b'bbb'])
604
k.add_lines([0], [b'aaa', b'111', b'bbb'])
605
k.add_lines([1], [b'aaa', b'222', b'bbb'])
609
self.assertEqual([[[b'aaa']],
610
[[b'111'], [b'222']],
604
k.add([], ['aaa', 'bbb'])
605
k.add([0], ['aaa', '111', 'bbb'])
606
k.add([1], ['aaa', '222', 'bbb'])
608
merged = k.merge([1, 2])
610
self.assertEquals([[['aaa']],
614
616
class NonConflict(TestBase):
615
617
"""Two descendants insert compatible changes.
617
619
No conflict should be reported."""
619
620
def runTest(self):
623
k.add_lines([], [b'aaa', b'bbb'])
624
k.add_lines([0], [b'111', b'aaa', b'ccc', b'bbb'])
625
k.add_lines([1], [b'aaa', b'ccc', b'bbb', b'222'])
624
k.add([], ['aaa', 'bbb'])
625
k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
626
k.add([1], ['aaa', 'ccc', 'bbb', '222'])
632
class AutoMerge(TestBase):
636
texts = [['header', 'aaa', 'bbb'],
637
['header', 'aaa', 'line from 1', 'bbb'],
638
['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
641
k.add('text0', [], texts[0])
642
k.add('text1', [0], texts[1])
643
k.add('text2', [0], texts[2])
645
self.log('k._weave=' + pformat(k._weave))
647
m = list(k.mash_iter([0, 1, 2]))
653
'line from 2', 'more from 2'])
628
657
class Khayyam(TestBase):
629
658
"""Test changes to multi-line texts, and read/write"""
631
def test_multi_line_merge(self):
633
b"""A Book of Verses underneath the Bough,
661
"""A Book of Verses underneath the Bough,
634
662
A Jug of Wine, a Loaf of Bread, -- and Thou
635
663
Beside me singing in the Wilderness --
636
664
Oh, Wilderness were Paradise enow!""",
638
b"""A Book of Verses underneath the Bough,
666
"""A Book of Verses underneath the Bough,
639
667
A Jug of Wine, a Loaf of Bread, -- and Thou
640
668
Beside me singing in the Wilderness --
641
669
Oh, Wilderness were Paradise now!""",
643
b"""A Book of poems underneath the tree,
671
"""A Book of poems underneath the tree,
644
672
A Jug of Wine, a Loaf of Bread,
646
674
Beside me singing in the Wilderness --
649
677
-- O. Khayyam""",
651
b"""A Book of Verses underneath the Bough,
679
"""A Book of Verses underneath the Bough,
652
680
A Jug of Wine, a Loaf of Bread,
654
682
Beside me singing in the Wilderness --
655
683
Oh, Wilderness were Paradise now!""",
657
texts = [[l.strip() for l in t.split(b'\n')] for t in rawtexts]
685
texts = [[l.strip() for l in t.split('\n')] for t in rawtexts]
663
k.add_lines(b'text%d' % i, list(parents), t)
664
parents.add(b'text%d' % i)
691
ver = k.add('text%d' % i,
667
696
self.log("k._weave=" + pformat(k._weave))
669
698
for i, t in enumerate(texts):
670
self.assertEqual(k.get_lines(i), t)
699
self.assertEqual(k.get(i), t)
672
701
self.check_read_write(k)
705
class MergeCases(TestBase):
706
def doMerge(self, base, a, b, mp):
707
from cStringIO import StringIO
708
from textwrap import dedent
714
w.add('text0', [], map(addcrlf, base))
715
w.add('text1', [0], map(addcrlf, a))
716
w.add('text2', [0], map(addcrlf, b))
718
self.log('weave is:')
721
self.log(tmpf.getvalue())
723
self.log('merge plan:')
724
p = list(w.plan_merge(1, 2))
725
for state, line in p:
727
self.log('%12s | %s' % (state, line[:-1]))
731
mt.writelines(w.weave_merge(p))
733
self.log(mt.getvalue())
735
mp = map(addcrlf, mp)
736
self.assertEqual(mt.readlines(), mp)
739
def testOneInsert(self):
745
def testSeparateInserts(self):
746
self.doMerge(['aaa', 'bbb', 'ccc'],
747
['aaa', 'xxx', 'bbb', 'ccc'],
748
['aaa', 'bbb', 'yyy', 'ccc'],
749
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
751
def testSameInsert(self):
752
self.doMerge(['aaa', 'bbb', 'ccc'],
753
['aaa', 'xxx', 'bbb', 'ccc'],
754
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
755
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
757
def testOverlappedInsert(self):
758
self.doMerge(['aaa', 'bbb'],
759
['aaa', 'xxx', 'yyy', 'bbb'],
760
['aaa', 'xxx', 'bbb'],
761
['aaa', '<<<<<<<', 'xxx', 'yyy', '=======', 'xxx',
764
# really it ought to reduce this to
765
# ['aaa', 'xxx', 'yyy', 'bbb']
768
def testClashReplace(self):
769
self.doMerge(['aaa'],
772
['<<<<<<<', 'xxx', '=======', 'yyy', 'zzz',
775
def testNonClashInsert(self):
776
self.doMerge(['aaa'],
779
['<<<<<<<', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
782
self.doMerge(['aaa'],
788
def testDeleteAndModify(self):
789
"""Clashing delete and modification.
791
If one side modifies a region and the other deletes it then
792
there should be a conflict with one side blank.
795
#######################################
796
# skippd, not working yet
799
self.doMerge(['aaa', 'bbb', 'ccc'],
800
['aaa', 'ddd', 'ccc'],
802
['<<<<<<<<', 'aaa', '=======', '>>>>>>>', 'ccc'])
675
805
class JoinWeavesTests(TestBase):
678
807
super(JoinWeavesTests, self).setUp()
679
808
self.weave1 = Weave()
680
self.lines1 = [b'hello\n']
681
self.lines3 = [b'hello\n', b'cruel\n', b'world\n']
682
self.weave1.add_lines(b'v1', [], self.lines1)
683
self.weave1.add_lines(b'v2', [b'v1'], [b'hello\n', b'world\n'])
684
self.weave1.add_lines(b'v3', [b'v2'], self.lines3)
686
def test_written_detection(self):
687
# Test detection of weave file corruption.
689
# Make sure that we can detect if a weave file has
690
# been corrupted. This doesn't test all forms of corruption,
691
# but it at least helps verify the data you get, is what you want.
694
w.add_lines(b'v1', [], [b'hello\n'])
695
w.add_lines(b'v2', [b'v1'], [b'hello\n', b'there\n'])
700
# Because we are corrupting, we need to make sure we have the exact
703
b'# bzr weave file v5\n'
704
b'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
705
b'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
706
b'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
709
# Change a single letter
711
b'# bzr weave file v5\n'
712
b'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
713
b'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
714
b'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
718
self.assertEqual(b'hello\n', w.get_text(b'v1'))
719
self.assertRaises(WeaveInvalidChecksum, w.get_text, b'v2')
720
self.assertRaises(WeaveInvalidChecksum, w.get_lines, b'v2')
721
self.assertRaises(WeaveInvalidChecksum, w.check)
723
# Change the sha checksum
725
b'# bzr weave file v5\n'
726
b'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
727
b'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
728
b'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
732
self.assertEqual(b'hello\n', w.get_text(b'v1'))
733
self.assertRaises(WeaveInvalidChecksum, w.get_text, b'v2')
734
self.assertRaises(WeaveInvalidChecksum, w.get_lines, b'v2')
735
self.assertRaises(WeaveInvalidChecksum, w.check)
738
class TestWeave(TestCase):
740
def test_allow_reserved_false(self):
741
w = Weave('name', allow_reserved=False)
742
# Add lines is checked at the WeaveFile level, not at the Weave level
743
w.add_lines(b'name:', [], TEXT_1)
744
# But get_lines is checked at this level
745
self.assertRaises(errors.ReservedId, w.get_lines, b'name:')
747
def test_allow_reserved_true(self):
748
w = Weave('name', allow_reserved=True)
749
w.add_lines(b'name:', [], TEXT_1)
750
self.assertEqual(TEXT_1, w.get_lines(b'name:'))
753
class InstrumentedWeave(Weave):
754
"""Keep track of how many times functions are called."""
756
def __init__(self, weave_name=None):
757
self._extract_count = 0
758
Weave.__init__(self, weave_name=weave_name)
760
def _extract(self, versions):
761
self._extract_count += 1
762
return Weave._extract(self, versions)
765
class TestNeedsReweave(TestCase):
766
"""Internal corner cases for when reweave is needed."""
768
def test_compatible_parents(self):
770
my_parents = {1, 2, 3}
772
self.assertTrue(w1._compatible_parents(my_parents, {3}))
774
self.assertTrue(w1._compatible_parents(my_parents, set(my_parents)))
775
# same empty corner case
776
self.assertTrue(w1._compatible_parents(set(), set()))
777
# other cannot contain stuff my_parents does not
778
self.assertFalse(w1._compatible_parents(set(), {1}))
779
self.assertFalse(w1._compatible_parents(my_parents, {1, 2, 3, 4}))
780
self.assertFalse(w1._compatible_parents(my_parents, {4}))
783
class TestWeaveFile(TestCaseInTempDir):
785
def test_empty_file(self):
786
with open('empty.weave', 'wb+') as f:
787
self.assertRaises(WeaveFormatError, read_weave, f)
809
self.lines1 = ['hello\n']
810
self.lines3 = ['hello\n', 'cruel\n', 'world\n']
811
self.weave1.add('v1', [], self.lines1)
812
self.weave1.add('v2', [0], ['hello\n', 'world\n'])
813
self.weave1.add('v3', [1], self.lines3)
815
def test_join_empty(self):
816
"""Join two empty weaves."""
817
eq = self.assertEqual
821
eq(w1.numversions(), 0)
823
def test_join_empty_to_nonempty(self):
824
"""Join empty weave onto nonempty."""
825
self.weave1.join(Weave())
826
self.assertEqual(len(self.weave1), 3)
828
def test_join_unrelated(self):
829
"""Join two weaves with no history in common."""
831
wb.add('b1', [], ['line from b\n'])
834
eq = self.assertEqual
836
eq(sorted(list(w1.iter_names())),
837
['b1', 'v1', 'v2', 'v3'])
839
def test_join_related(self):
840
wa = self.weave1.copy()
841
wb = self.weave1.copy()
842
wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
843
wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
844
eq = self.assertEquals
849
eq(wa.get_lines('b1'),
850
['hello\n', 'pale blue\n', 'world\n'])
852
def test_join_parent_disagreement(self):
853
"""Cannot join weaves with different parents for a version."""
856
wa.add('v1', [], ['hello\n'])
858
wb.add('v1', ['v0'], ['hello\n'])
859
self.assertRaises(WeaveError,
862
def test_join_text_disagreement(self):
863
"""Cannot join weaves with different texts for a version."""
866
wa.add('v1', [], ['hello\n'])
867
wb.add('v1', [], ['not\n', 'hello\n'])
868
self.assertRaises(WeaveError,
871
def test_join_unordered(self):
872
"""Join weaves where indexes differ.
874
The source weave contains a different version at index 0."""
875
wa = self.weave1.copy()
877
wb.add('x1', [], ['line from x1\n'])
878
wb.add('v1', [], ['hello\n'])
879
wb.add('v2', ['v1'], ['hello\n', 'world\n'])
881
eq = self.assertEquals
882
eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
883
eq(wa.get_text('x1'), 'line from x1\n')