~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/test_weave.py

- stub for revision properties

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