~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/weave.py

[merge] Added the uncommit plugin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/python2.4
 
2
 
 
3
# Copyright (C) 2005 by Canonical Ltd
 
4
 
 
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.
 
9
 
 
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.
 
14
 
 
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
 
18
 
 
19
 
 
20
# TODO: tests regarding version names
 
21
# TODO: rbc 20050108 test that join does not leave an inconsistent weave 
 
22
#       if it fails.
 
23
 
 
24
"""test suite for weave algorithm"""
 
25
 
 
26
from pprint import pformat
 
27
 
 
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
 
33
 
 
34
 
 
35
# texts for use in testing
 
36
TEXT_0 = ["Hello world"]
 
37
TEXT_1 = ["Hello world",
 
38
          "A second line"]
 
39
 
 
40
 
 
41
 
 
42
class TestBase(TestCase):
 
43
    def check_read_write(self, k):
 
44
        """Check the weave k can be written & re-read."""
 
45
        from tempfile import TemporaryFile
 
46
        tf = TemporaryFile()
 
47
 
 
48
        write_weave(k, tf)
 
49
        tf.seek(0)
 
50
        k2 = read_weave(tf)
 
51
 
 
52
        if k != k2:
 
53
            tf.seek(0)
 
54
            self.log('serialized weave:')
 
55
            self.log(tf.read())
 
56
 
 
57
            self.log('')
 
58
            self.log('parents: %s' % (k._parents == k2._parents))
 
59
            self.log('         %r' % k._parents)
 
60
            self.log('         %r' % k2._parents)
 
61
            self.log('')
 
62
            self.fail('read/write check failed')
 
63
 
 
64
 
 
65
class WeaveContains(TestBase):
 
66
    """Weave __contains__ operator"""
 
67
    def runTest(self):
 
68
        k = Weave()
 
69
        self.assertFalse('foo' in k)
 
70
        k.add('foo', [], TEXT_1)
 
71
        self.assertTrue('foo' in k)
 
72
 
 
73
 
 
74
class Easy(TestBase):
 
75
    def runTest(self):
 
76
        k = Weave()
 
77
 
 
78
 
 
79
class StoreText(TestBase):
 
80
    """Store and retrieve a simple text."""
 
81
    def runTest(self):
 
82
        k = Weave()
 
83
        idx = k.add('text0', [], TEXT_0)
 
84
        self.assertEqual(k.get(idx), TEXT_0)
 
85
        self.assertEqual(idx, 0)
 
86
 
 
87
 
 
88
 
 
89
class AnnotateOne(TestBase):
 
90
    def runTest(self):
 
91
        k = Weave()
 
92
        k.add('text0', [], TEXT_0)
 
93
        self.assertEqual(k.annotate(0),
 
94
                         [(0, TEXT_0[0])])
 
95
 
 
96
 
 
97
class StoreTwo(TestBase):
 
98
    def runTest(self):
 
99
        k = Weave()
 
100
 
 
101
        idx = k.add('text0', [], TEXT_0)
 
102
        self.assertEqual(idx, 0)
 
103
 
 
104
        idx = k.add('text1', [], TEXT_1)
 
105
        self.assertEqual(idx, 1)
 
106
 
 
107
        self.assertEqual(k.get(0), TEXT_0)
 
108
        self.assertEqual(k.get(1), TEXT_1)
 
109
 
 
110
 
 
111
 
 
112
class AddWithGivenSha(TestBase):
 
113
    def runTest(self):
 
114
        """Add with caller-supplied SHA-1"""
 
115
        k = Weave()
 
116
 
 
117
        t = 'text0'
 
118
        k.add('text0', [], [t], sha1=sha_string(t))
 
119
 
 
120
 
 
121
 
 
122
class InvalidAdd(TestBase):
 
123
    """Try to use invalid version number during add."""
 
124
    def runTest(self):
 
125
        k = Weave()
 
126
 
 
127
        self.assertRaises(IndexError,
 
128
                          k.add,
 
129
                          'text0',
 
130
                          [69],
 
131
                          ['new text!'])
 
132
 
 
133
 
 
134
class RepeatedAdd(TestBase):
 
135
    """Add the same version twice; harmless."""
 
136
    def runTest(self):
 
137
        k = Weave()
 
138
        idx = k.add('text0', [], TEXT_0)
 
139
        idx2 = k.add('text0', [], TEXT_0)
 
140
        self.assertEqual(idx, idx2)
 
141
 
 
142
 
 
143
 
 
144
class InvalidRepeatedAdd(TestBase):
 
145
    def runTest(self):
 
146
        k = Weave()
 
147
        idx = k.add('text0', [], TEXT_0)
 
148
        self.assertRaises(WeaveError,
 
149
                          k.add,
 
150
                          'text0',
 
151
                          [],
 
152
                          ['not the same text'])
 
153
        self.assertRaises(WeaveError,
 
154
                          k.add,
 
155
                          'text0',
 
156
                          [12],         # not the right parents
 
157
                          TEXT_0)
 
158
        
 
159
 
 
160
 
 
161
class InsertLines(TestBase):
 
162
    """Store a revision that adds one line to the original.
 
163
 
 
164
    Look at the annotations to make sure that the first line is matched
 
165
    and not stored repeatedly."""
 
166
    def runTest(self):
 
167
        k = Weave()
 
168
 
 
169
        k.add('text0', [], ['line 1'])
 
170
        k.add('text1', [0], ['line 1', 'line 2'])
 
171
 
 
172
        self.assertEqual(k.annotate(0),
 
173
                         [(0, 'line 1')])
 
174
 
 
175
        self.assertEqual(k.get(1),
 
176
                         ['line 1',
 
177
                          'line 2'])
 
178
 
 
179
        self.assertEqual(k.annotate(1),
 
180
                         [(0, 'line 1'),
 
181
                          (1, 'line 2')])
 
182
 
 
183
        k.add('text2', [0], ['line 1', 'diverged line'])
 
184
 
 
185
        self.assertEqual(k.annotate(2),
 
186
                         [(0, 'line 1'),
 
187
                          (2, 'diverged line')])
 
188
 
 
189
        text3 = ['line 1', 'middle line', 'line 2']
 
190
        k.add('text3',
 
191
              [0, 1],
 
192
              text3)
 
193
 
 
194
        # self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
 
195
 
 
196
        self.log("k._weave=" + pformat(k._weave))
 
197
 
 
198
        self.assertEqual(k.annotate(3),
 
199
                         [(0, 'line 1'),
 
200
                          (3, 'middle line'),
 
201
                          (1, 'line 2')])
 
202
 
 
203
        # now multiple insertions at different places
 
204
        k.add('text4',
 
205
              [0, 1, 3],
 
206
              ['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
 
207
 
 
208
        self.assertEqual(k.annotate(4), 
 
209
                         [(0, 'line 1'),
 
210
                          (4, 'aaa'),
 
211
                          (3, 'middle line'),
 
212
                          (4, 'bbb'),
 
213
                          (1, 'line 2'),
 
214
                          (4, 'ccc')])
 
215
 
 
216
 
 
217
 
 
218
class DeleteLines(TestBase):
 
219
    """Deletion of lines from existing text.
 
220
 
 
221
    Try various texts all based on a common ancestor."""
 
222
    def runTest(self):
 
223
        k = Weave()
 
224
 
 
225
        base_text = ['one', 'two', 'three', 'four']
 
226
 
 
227
        k.add('text0', [], base_text)
 
228
        
 
229
        texts = [['one', 'two', 'three'],
 
230
                 ['two', 'three', 'four'],
 
231
                 ['one', 'four'],
 
232
                 ['one', 'two', 'three', 'four'],
 
233
                 ]
 
234
 
 
235
        i = 1
 
236
        for t in texts:
 
237
            ver = k.add('text%d' % i,
 
238
                        [0], t)
 
239
            i += 1
 
240
 
 
241
        self.log('final weave:')
 
242
        self.log('k._weave=' + pformat(k._weave))
 
243
 
 
244
        for i in range(len(texts)):
 
245
            self.assertEqual(k.get(i+1),
 
246
                             texts[i])
 
247
            
 
248
 
 
249
 
 
250
 
 
251
class SuicideDelete(TestBase):
 
252
    """Invalid weave which tries to add and delete simultaneously."""
 
253
    def runTest(self):
 
254
        k = Weave()
 
255
 
 
256
        k._parents = [(),
 
257
                ]
 
258
        k._weave = [('{', 0),
 
259
                'first line',
 
260
                ('[', 0),
 
261
                'deleted in 0',
 
262
                (']', 0),
 
263
                ('}', 0),
 
264
                ]
 
265
        ################################### SKIPPED
 
266
        # Weave.get doesn't trap this anymore
 
267
        return 
 
268
 
 
269
        self.assertRaises(WeaveFormatError,
 
270
                          k.get,
 
271
                          0)        
 
272
 
 
273
 
 
274
 
 
275
class CannedDelete(TestBase):
 
276
    """Unpack canned weave with deleted lines."""
 
277
    def runTest(self):
 
278
        k = Weave()
 
279
 
 
280
        k._parents = [(),
 
281
                frozenset([0]),
 
282
                ]
 
283
        k._weave = [('{', 0),
 
284
                'first line',
 
285
                ('[', 1),
 
286
                'line to be deleted',
 
287
                (']', 1),
 
288
                'last line',
 
289
                ('}', 0),
 
290
                ]
 
291
 
 
292
        self.assertEqual(k.get(0),
 
293
                         ['first line',
 
294
                          'line to be deleted',
 
295
                          'last line',
 
296
                          ])
 
297
 
 
298
        self.assertEqual(k.get(1),
 
299
                         ['first line',
 
300
                          'last line',
 
301
                          ])
 
302
 
 
303
 
 
304
 
 
305
class CannedReplacement(TestBase):
 
306
    """Unpack canned weave with deleted lines."""
 
307
    def runTest(self):
 
308
        k = Weave()
 
309
 
 
310
        k._parents = [frozenset(),
 
311
                frozenset([0]),
 
312
                ]
 
313
        k._weave = [('{', 0),
 
314
                'first line',
 
315
                ('[', 1),
 
316
                'line to be deleted',
 
317
                (']', 1),
 
318
                ('{', 1),
 
319
                'replacement line',                
 
320
                ('}', 1),
 
321
                'last line',
 
322
                ('}', 0),
 
323
                ]
 
324
 
 
325
        self.assertEqual(k.get(0),
 
326
                         ['first line',
 
327
                          'line to be deleted',
 
328
                          'last line',
 
329
                          ])
 
330
 
 
331
        self.assertEqual(k.get(1),
 
332
                         ['first line',
 
333
                          'replacement line',
 
334
                          'last line',
 
335
                          ])
 
336
 
 
337
 
 
338
 
 
339
class BadWeave(TestBase):
 
340
    """Test that we trap an insert which should not occur."""
 
341
    def runTest(self):
 
342
        k = Weave()
 
343
 
 
344
        k._parents = [frozenset(),
 
345
                ]
 
346
        k._weave = ['bad line',
 
347
                ('{', 0),
 
348
                'foo {',
 
349
                ('{', 1),
 
350
                '  added in version 1',
 
351
                ('{', 2),
 
352
                '  added in v2',
 
353
                ('}', 2),
 
354
                '  also from v1',
 
355
                ('}', 1),
 
356
                '}',
 
357
                ('}', 0)]
 
358
 
 
359
        ################################### SKIPPED
 
360
        # Weave.get doesn't trap this anymore
 
361
        return 
 
362
 
 
363
 
 
364
        self.assertRaises(WeaveFormatError,
 
365
                          k.get,
 
366
                          0)
 
367
 
 
368
 
 
369
class BadInsert(TestBase):
 
370
    """Test that we trap an insert which should not occur."""
 
371
    def runTest(self):
 
372
        k = Weave()
 
373
 
 
374
        k._parents = [frozenset(),
 
375
                frozenset([0]),
 
376
                frozenset([0]),
 
377
                frozenset([0,1,2]),
 
378
                ]
 
379
        k._weave = [('{', 0),
 
380
                'foo {',
 
381
                ('{', 1),
 
382
                '  added in version 1',
 
383
                ('{', 1),
 
384
                '  more in 1',
 
385
                ('}', 1),
 
386
                ('}', 1),
 
387
                ('}', 0)]
 
388
 
 
389
 
 
390
        # this is not currently enforced by get
 
391
        return  ##########################################
 
392
 
 
393
        self.assertRaises(WeaveFormatError,
 
394
                          k.get,
 
395
                          0)
 
396
 
 
397
        self.assertRaises(WeaveFormatError,
 
398
                          k.get,
 
399
                          1)
 
400
 
 
401
 
 
402
class InsertNested(TestBase):
 
403
    """Insertion with nested instructions."""
 
404
    def runTest(self):
 
405
        k = Weave()
 
406
 
 
407
        k._parents = [frozenset(),
 
408
                frozenset([0]),
 
409
                frozenset([0]),
 
410
                frozenset([0,1,2]),
 
411
                ]
 
412
        k._weave = [('{', 0),
 
413
                'foo {',
 
414
                ('{', 1),
 
415
                '  added in version 1',
 
416
                ('{', 2),
 
417
                '  added in v2',
 
418
                ('}', 2),
 
419
                '  also from v1',
 
420
                ('}', 1),
 
421
                '}',
 
422
                ('}', 0)]
 
423
 
 
424
        self.assertEqual(k.get(0),
 
425
                         ['foo {',
 
426
                          '}'])
 
427
 
 
428
        self.assertEqual(k.get(1),
 
429
                         ['foo {',
 
430
                          '  added in version 1',
 
431
                          '  also from v1',
 
432
                          '}'])
 
433
                       
 
434
        self.assertEqual(k.get(2),
 
435
                         ['foo {',
 
436
                          '  added in v2',
 
437
                          '}'])
 
438
 
 
439
        self.assertEqual(k.get(3),
 
440
                         ['foo {',
 
441
                          '  added in version 1',
 
442
                          '  added in v2',
 
443
                          '  also from v1',
 
444
                          '}'])
 
445
                         
 
446
 
 
447
 
 
448
class DeleteLines2(TestBase):
 
449
    """Test recording revisions that delete lines.
 
450
 
 
451
    This relies on the weave having a way to represent lines knocked
 
452
    out by a later revision."""
 
453
    def runTest(self):
 
454
        k = Weave()
 
455
 
 
456
        k.add('text0', [], ["line the first",
 
457
                   "line 2",
 
458
                   "line 3",
 
459
                   "fine"])
 
460
 
 
461
        self.assertEqual(len(k.get(0)), 4)
 
462
 
 
463
        k.add('text1', [0], ["line the first",
 
464
                   "fine"])
 
465
 
 
466
        self.assertEqual(k.get(1),
 
467
                         ["line the first",
 
468
                          "fine"])
 
469
 
 
470
        self.assertEqual(k.annotate(1),
 
471
                         [(0, "line the first"),
 
472
                          (0, "fine")])
 
473
 
 
474
 
 
475
 
 
476
class IncludeVersions(TestBase):
 
477
    """Check texts that are stored across multiple revisions.
 
478
 
 
479
    Here we manually create a weave with particular encoding and make
 
480
    sure it unpacks properly.
 
481
 
 
482
    Text 0 includes nothing; text 1 includes text 0 and adds some
 
483
    lines.
 
484
    """
 
485
 
 
486
    def runTest(self):
 
487
        k = Weave()
 
488
 
 
489
        k._parents = [frozenset(), frozenset([0])]
 
490
        k._weave = [('{', 0),
 
491
                "first line",
 
492
                ('}', 0),
 
493
                ('{', 1),
 
494
                "second line",
 
495
                ('}', 1)]
 
496
 
 
497
        self.assertEqual(k.get(1),
 
498
                         ["first line",
 
499
                          "second line"])
 
500
 
 
501
        self.assertEqual(k.get(0),
 
502
                         ["first line"])
 
503
 
 
504
 
 
505
class DivergedIncludes(TestBase):
 
506
    """Weave with two diverged texts based on version 0.
 
507
    """
 
508
    def runTest(self):
 
509
        k = Weave()
 
510
 
 
511
        k._parents = [frozenset(),
 
512
                frozenset([0]),
 
513
                frozenset([0]),
 
514
                ]
 
515
        k._weave = [('{', 0),
 
516
                "first line",
 
517
                ('}', 0),
 
518
                ('{', 1),
 
519
                "second line",
 
520
                ('}', 1),
 
521
                ('{', 2),
 
522
                "alternative second line",
 
523
                ('}', 2),                
 
524
                ]
 
525
 
 
526
        self.assertEqual(k.get(0),
 
527
                         ["first line"])
 
528
 
 
529
        self.assertEqual(k.get(1),
 
530
                         ["first line",
 
531
                          "second line"])
 
532
 
 
533
        self.assertEqual(k.get(2),
 
534
                         ["first line",
 
535
                          "alternative second line"])
 
536
 
 
537
        self.assertEqual(list(k.inclusions([2])),
 
538
                         [0, 2])
 
539
 
 
540
 
 
541
 
 
542
class ReplaceLine(TestBase):
 
543
    def runTest(self):
 
544
        k = Weave()
 
545
 
 
546
        text0 = ['cheddar', 'stilton', 'gruyere']
 
547
        text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
 
548
        
 
549
        k.add('text0', [], text0)
 
550
        k.add('text1', [0], text1)
 
551
 
 
552
        self.log('k._weave=' + pformat(k._weave))
 
553
 
 
554
        self.assertEqual(k.get(0), text0)
 
555
        self.assertEqual(k.get(1), text1)
 
556
 
 
557
 
 
558
 
 
559
class Merge(TestBase):
 
560
    """Storage of versions that merge diverged parents"""
 
561
    def runTest(self):
 
562
        k = Weave()
 
563
 
 
564
        texts = [['header'],
 
565
                 ['header', '', 'line from 1'],
 
566
                 ['header', '', 'line from 2', 'more from 2'],
 
567
                 ['header', '', 'line from 1', 'fixup line', 'line from 2'],
 
568
                 ]
 
569
 
 
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])
 
574
 
 
575
        for i, t in enumerate(texts):
 
576
            self.assertEqual(k.get(i), t)
 
577
 
 
578
        self.assertEqual(k.annotate(3),
 
579
                         [(0, 'header'),
 
580
                          (1, ''),
 
581
                          (1, 'line from 1'),
 
582
                          (3, 'fixup line'),
 
583
                          (2, 'line from 2'),
 
584
                          ])
 
585
 
 
586
        self.assertEqual(list(k.inclusions([3])),
 
587
                         [0, 1, 2, 3])
 
588
 
 
589
        self.log('k._weave=' + pformat(k._weave))
 
590
 
 
591
        self.check_read_write(k)
 
592
 
 
593
 
 
594
class Conflicts(TestBase):
 
595
    """Test detection of conflicting regions during a merge.
 
596
 
 
597
    A base version is inserted, then two descendents try to
 
598
    insert different lines in the same place.  These should be
 
599
    reported as a possible conflict and forwarded to the user."""
 
600
    def runTest(self):
 
601
        return  # NOT RUN
 
602
        k = Weave()
 
603
 
 
604
        k.add([], ['aaa', 'bbb'])
 
605
        k.add([0], ['aaa', '111', 'bbb'])
 
606
        k.add([1], ['aaa', '222', 'bbb'])
 
607
 
 
608
        merged = k.merge([1, 2])
 
609
 
 
610
        self.assertEquals([[['aaa']],
 
611
                           [['111'], ['222']],
 
612
                           [['bbb']]])
 
613
 
 
614
 
 
615
 
 
616
class NonConflict(TestBase):
 
617
    """Two descendants insert compatible changes.
 
618
 
 
619
    No conflict should be reported."""
 
620
    def runTest(self):
 
621
        return  # NOT RUN
 
622
        k = Weave()
 
623
 
 
624
        k.add([], ['aaa', 'bbb'])
 
625
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
 
626
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
 
627
 
 
628
    
 
629
    
 
630
 
 
631
 
 
632
class AutoMerge(TestBase):
 
633
    def runTest(self):
 
634
        k = Weave()
 
635
 
 
636
        texts = [['header', 'aaa', 'bbb'],
 
637
                 ['header', 'aaa', 'line from 1', 'bbb'],
 
638
                 ['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
 
639
                 ]
 
640
 
 
641
        k.add('text0', [], texts[0])
 
642
        k.add('text1', [0], texts[1])
 
643
        k.add('text2', [0], texts[2])
 
644
 
 
645
        self.log('k._weave=' + pformat(k._weave))
 
646
 
 
647
        m = list(k.mash_iter([0, 1, 2]))
 
648
 
 
649
        self.assertEqual(m,
 
650
                         ['header', 'aaa',
 
651
                          'line from 1',
 
652
                          'bbb',
 
653
                          'line from 2', 'more from 2'])
 
654
        
 
655
 
 
656
 
 
657
class Khayyam(TestBase):
 
658
    """Test changes to multi-line texts, and read/write"""
 
659
    def runTest(self):
 
660
        rawtexts = [
 
661
            """A Book of Verses underneath the Bough,
 
662
            A Jug of Wine, a Loaf of Bread, -- and Thou
 
663
            Beside me singing in the Wilderness --
 
664
            Oh, Wilderness were Paradise enow!""",
 
665
            
 
666
            """A Book of Verses underneath the Bough,
 
667
            A Jug of Wine, a Loaf of Bread, -- and Thou
 
668
            Beside me singing in the Wilderness --
 
669
            Oh, Wilderness were Paradise now!""",
 
670
 
 
671
            """A Book of poems underneath the tree,
 
672
            A Jug of Wine, a Loaf of Bread,
 
673
            and Thou
 
674
            Beside me singing in the Wilderness --
 
675
            Oh, Wilderness were Paradise now!
 
676
 
 
677
            -- O. Khayyam""",
 
678
 
 
679
            """A Book of Verses underneath the Bough,
 
680
            A Jug of Wine, a Loaf of Bread,
 
681
            and Thou
 
682
            Beside me singing in the Wilderness --
 
683
            Oh, Wilderness were Paradise now!""",
 
684
            ]
 
685
        texts = [[l.strip() for l in t.split('\n')] for t in rawtexts]
 
686
 
 
687
        k = Weave()
 
688
        parents = set()
 
689
        i = 0
 
690
        for t in texts:
 
691
            ver = k.add('text%d' % i,
 
692
                        list(parents), t)
 
693
            parents.add(ver)
 
694
            i += 1
 
695
 
 
696
        self.log("k._weave=" + pformat(k._weave))
 
697
 
 
698
        for i, t in enumerate(texts):
 
699
            self.assertEqual(k.get(i), t)
 
700
 
 
701
        self.check_read_write(k)
 
702
 
 
703
 
 
704
 
 
705
class MergeCases(TestBase):
 
706
    def doMerge(self, base, a, b, mp):
 
707
        from cStringIO import StringIO
 
708
        from textwrap import dedent
 
709
 
 
710
        def addcrlf(x):
 
711
            return x + '\n'
 
712
        
 
713
        w = Weave()
 
714
        w.add('text0', [], map(addcrlf, base))
 
715
        w.add('text1', [0], map(addcrlf, a))
 
716
        w.add('text2', [0], map(addcrlf, b))
 
717
 
 
718
        self.log('weave is:')
 
719
        tmpf = StringIO()
 
720
        write_weave(w, tmpf)
 
721
        self.log(tmpf.getvalue())
 
722
 
 
723
        self.log('merge plan:')
 
724
        p = list(w.plan_merge(1, 2))
 
725
        for state, line in p:
 
726
            if line:
 
727
                self.log('%12s | %s' % (state, line[:-1]))
 
728
 
 
729
        self.log('merge:')
 
730
        mt = StringIO()
 
731
        mt.writelines(w.weave_merge(p))
 
732
        mt.seek(0)
 
733
        self.log(mt.getvalue())
 
734
 
 
735
        mp = map(addcrlf, mp)
 
736
        self.assertEqual(mt.readlines(), mp)
 
737
        
 
738
        
 
739
    def testOneInsert(self):
 
740
        self.doMerge([],
 
741
                     ['aa'],
 
742
                     [],
 
743
                     ['aa'])
 
744
 
 
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'])
 
750
 
 
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'])
 
756
 
 
757
    def testOverlappedInsert(self):
 
758
        self.doMerge(['aaa', 'bbb'],
 
759
                     ['aaa', 'xxx', 'yyy', 'bbb'],
 
760
                     ['aaa', 'xxx', 'bbb'],
 
761
                     ['aaa', '<<<<<<<', 'xxx', 'yyy', '=======', 'xxx', 
 
762
                      '>>>>>>>', 'bbb'])
 
763
 
 
764
        # really it ought to reduce this to 
 
765
        # ['aaa', 'xxx', 'yyy', 'bbb']
 
766
 
 
767
 
 
768
    def testClashReplace(self):
 
769
        self.doMerge(['aaa'],
 
770
                     ['xxx'],
 
771
                     ['yyy', 'zzz'],
 
772
                     ['<<<<<<<', 'xxx', '=======', 'yyy', 'zzz', 
 
773
                      '>>>>>>>'])
 
774
 
 
775
    def testNonClashInsert(self):
 
776
        self.doMerge(['aaa'],
 
777
                     ['xxx', 'aaa'],
 
778
                     ['yyy', 'zzz'],
 
779
                     ['<<<<<<<', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
780
                      '>>>>>>>'])
 
781
 
 
782
        self.doMerge(['aaa'],
 
783
                     ['aaa'],
 
784
                     ['yyy', 'zzz'],
 
785
                     ['yyy', 'zzz'])
 
786
 
 
787
 
 
788
    def testDeleteAndModify(self):
 
789
        """Clashing delete and modification.
 
790
 
 
791
        If one side modifies a region and the other deletes it then
 
792
        there should be a conflict with one side blank.
 
793
        """
 
794
 
 
795
        #######################################
 
796
        # skippd, not working yet
 
797
        return
 
798
        
 
799
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
800
                     ['aaa', 'ddd', 'ccc'],
 
801
                     ['aaa', 'ccc'],
 
802
                     ['<<<<<<<<', 'aaa', '=======', '>>>>>>>', 'ccc'])
 
803
 
 
804
 
 
805
class JoinWeavesTests(TestBase):
 
806
    def setUp(self):
 
807
        super(JoinWeavesTests, self).setUp()
 
808
        self.weave1 = Weave()
 
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)
 
814
        
 
815
    def test_join_empty(self):
 
816
        """Join two empty weaves."""
 
817
        eq = self.assertEqual
 
818
        w1 = Weave()
 
819
        w2 = Weave()
 
820
        w1.join(w2)
 
821
        eq(w1.numversions(), 0)
 
822
        
 
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)
 
827
 
 
828
    def test_join_unrelated(self):
 
829
        """Join two weaves with no history in common."""
 
830
        wb = Weave()
 
831
        wb.add('b1', [], ['line from b\n'])
 
832
        w1 = self.weave1
 
833
        w1.join(wb)
 
834
        eq = self.assertEqual
 
835
        eq(len(w1), 4)
 
836
        eq(sorted(list(w1.iter_names())),
 
837
           ['b1', 'v1', 'v2', 'v3'])
 
838
 
 
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
 
845
        eq(len(wa), 4)
 
846
        eq(len(wb), 4)
 
847
        wa.join(wb)
 
848
        eq(len(wa), 5)
 
849
        eq(wa.get_lines('b1'),
 
850
           ['hello\n', 'pale blue\n', 'world\n'])
 
851
 
 
852
    def test_join_parent_disagreement(self):
 
853
        """Cannot join weaves with different parents for a version."""
 
854
        wa = Weave()
 
855
        wb = Weave()
 
856
        wa.add('v1', [], ['hello\n'])
 
857
        wb.add('v0', [], [])
 
858
        wb.add('v1', ['v0'], ['hello\n'])
 
859
        self.assertRaises(WeaveError,
 
860
                          wa.join, wb)
 
861
 
 
862
    def test_join_text_disagreement(self):
 
863
        """Cannot join weaves with different texts for a version."""
 
864
        wa = Weave()
 
865
        wb = Weave()
 
866
        wa.add('v1', [], ['hello\n'])
 
867
        wb.add('v1', [], ['not\n', 'hello\n'])
 
868
        self.assertRaises(WeaveError,
 
869
                          wa.join, wb)
 
870
 
 
871
    def test_join_unordered(self):
 
872
        """Join weaves where indexes differ.
 
873
        
 
874
        The source weave contains a different version at index 0."""
 
875
        wa = self.weave1.copy()
 
876
        wb = Weave()
 
877
        wb.add('x1', [], ['line from x1\n'])
 
878
        wb.add('v1', [], ['hello\n'])
 
879
        wb.add('v2', ['v1'], ['hello\n', 'world\n'])
 
880
        wa.join(wb)
 
881
        eq = self.assertEquals
 
882
        eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
 
883
        eq(wa.get_text('x1'), 'line from x1\n')