~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_weave.py

  • Committer: Aaron Bentley
  • Date: 2006-04-17 22:59:54 UTC
  • mto: This revision was merged to the branch mainline in revision 1672.
  • Revision ID: aaron.bentley@utoronto.ca-20060417225954-79d47d850ef690b1
Add failing test case

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 
19
19
 
20
20
# TODO: tests regarding version names
21
 
 
22
 
 
 
21
# TODO: rbc 20050108 test that join does not leave an inconsistent weave 
 
22
#       if it fails.
23
23
 
24
24
"""test suite for weave algorithm"""
25
25
 
 
26
from pprint import pformat
26
27
 
27
 
import testsweet
28
 
from bzrlib.weave import Weave, WeaveFormatError
 
28
import bzrlib.errors as errors
 
29
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
29
30
from bzrlib.weavefile import write_weave, read_weave
30
 
from pprint import pformat
31
 
 
32
 
 
33
 
try:
34
 
    set
35
 
    frozenset
36
 
except NameError:
37
 
    from sets import Set, ImmutableSet
38
 
    set = Set
39
 
    frozenset = ImmutableSet
40
 
    del Set, ImmutableSet
41
 
 
 
31
from bzrlib.tests import TestCase
 
32
from bzrlib.osutils import sha_string
42
33
 
43
34
 
44
35
# texts for use in testing
47
38
          "A second line"]
48
39
 
49
40
 
50
 
 
51
 
class TestBase(testsweet.TestBase):
 
41
class TestBase(TestCase):
52
42
    def check_read_write(self, k):
53
43
        """Check the weave k can be written & re-read."""
54
44
        from tempfile import TemporaryFile
68
58
            self.log('         %r' % k._parents)
69
59
            self.log('         %r' % k2._parents)
70
60
            self.log('')
71
 
 
72
 
            
73
61
            self.fail('read/write check failed')
74
 
        
75
 
        
 
62
 
 
63
 
 
64
class WeaveContains(TestBase):
 
65
    """Weave __contains__ operator"""
 
66
    def runTest(self):
 
67
        k = Weave()
 
68
        self.assertFalse('foo' in k)
 
69
        k.add_lines('foo', [], TEXT_1)
 
70
        self.assertTrue('foo' in k)
76
71
 
77
72
 
78
73
class Easy(TestBase):
82
77
 
83
78
class StoreText(TestBase):
84
79
    """Store and retrieve a simple text."""
85
 
    def runTest(self):
 
80
 
 
81
    def test_storing_text(self):
86
82
        k = Weave()
87
 
        idx = k.add('text0', [], TEXT_0)
88
 
        self.assertEqual(k.get(idx), TEXT_0)
 
83
        idx = k.add_lines('text0', [], TEXT_0)
 
84
        self.assertEqual(k.get_lines(idx), TEXT_0)
89
85
        self.assertEqual(idx, 0)
90
86
 
91
87
 
92
 
 
93
88
class AnnotateOne(TestBase):
94
89
    def runTest(self):
95
90
        k = Weave()
96
 
        k.add('text0', [], TEXT_0)
97
 
        self.assertEqual(k.annotate(0),
98
 
                         [(0, TEXT_0[0])])
 
91
        k.add_lines('text0', [], TEXT_0)
 
92
        self.assertEqual(k.annotate('text0'),
 
93
                         [('text0', TEXT_0[0])])
99
94
 
100
95
 
101
96
class StoreTwo(TestBase):
102
97
    def runTest(self):
103
98
        k = Weave()
104
99
 
105
 
        idx = k.add('text0', [], TEXT_0)
 
100
        idx = k.add_lines('text0', [], TEXT_0)
106
101
        self.assertEqual(idx, 0)
107
102
 
108
 
        idx = k.add('text1', [], TEXT_1)
 
103
        idx = k.add_lines('text1', [], TEXT_1)
109
104
        self.assertEqual(idx, 1)
110
105
 
111
 
        self.assertEqual(k.get(0), TEXT_0)
112
 
        self.assertEqual(k.get(1), TEXT_1)
113
 
 
114
 
        k.dump(self.TEST_LOG)
115
 
 
116
 
 
 
106
        self.assertEqual(k.get_lines(0), TEXT_0)
 
107
        self.assertEqual(k.get_lines(1), TEXT_1)
 
108
 
 
109
 
 
110
class GetSha1(TestBase):
 
111
    def test_get_sha1(self):
 
112
        k = Weave()
 
113
        k.add_lines('text0', [], 'text0')
 
114
        self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
 
115
                         k.get_sha1('text0'))
 
116
        self.assertRaises(errors.RevisionNotPresent,
 
117
                          k.get_sha1, 0)
 
118
        self.assertRaises(errors.RevisionNotPresent,
 
119
                          k.get_sha1, 'text1')
 
120
                        
117
121
 
118
122
class InvalidAdd(TestBase):
119
123
    """Try to use invalid version number during add."""
120
124
    def runTest(self):
121
125
        k = Weave()
122
126
 
123
 
        self.assertRaises(IndexError,
124
 
                          k.add,
 
127
        self.assertRaises(errors.RevisionNotPresent,
 
128
                          k.add_lines,
125
129
                          'text0',
126
 
                          [69],
 
130
                          ['69'],
127
131
                          ['new text!'])
128
132
 
129
133
 
 
134
class RepeatedAdd(TestBase):
 
135
    """Add the same version twice; harmless."""
 
136
    def runTest(self):
 
137
        k = Weave()
 
138
        idx = k.add_lines('text0', [], TEXT_0)
 
139
        idx2 = k.add_lines('text0', [], TEXT_0)
 
140
        self.assertEqual(idx, idx2)
 
141
 
 
142
 
 
143
class InvalidRepeatedAdd(TestBase):
 
144
    def runTest(self):
 
145
        k = Weave()
 
146
        k.add_lines('basis', [], TEXT_0)
 
147
        idx = k.add_lines('text0', [], TEXT_0)
 
148
        self.assertRaises(errors.RevisionAlreadyPresent,
 
149
                          k.add_lines,
 
150
                          'text0',
 
151
                          [],
 
152
                          ['not the same text'])
 
153
        self.assertRaises(errors.RevisionAlreadyPresent,
 
154
                          k.add_lines,
 
155
                          'text0',
 
156
                          ['basis'],         # not the right parents
 
157
                          TEXT_0)
 
158
        
 
159
 
130
160
class InsertLines(TestBase):
131
161
    """Store a revision that adds one line to the original.
132
162
 
135
165
    def runTest(self):
136
166
        k = Weave()
137
167
 
138
 
        k.add('text0', [], ['line 1'])
139
 
        k.add('text1', [0], ['line 1', 'line 2'])
140
 
 
141
 
        self.assertEqual(k.annotate(0),
142
 
                         [(0, 'line 1')])
143
 
 
144
 
        self.assertEqual(k.get(1),
 
168
        k.add_lines('text0', [], ['line 1'])
 
169
        k.add_lines('text1', ['text0'], ['line 1', 'line 2'])
 
170
 
 
171
        self.assertEqual(k.annotate('text0'),
 
172
                         [('text0', 'line 1')])
 
173
 
 
174
        self.assertEqual(k.get_lines(1),
145
175
                         ['line 1',
146
176
                          'line 2'])
147
177
 
148
 
        self.assertEqual(k.annotate(1),
149
 
                         [(0, 'line 1'),
150
 
                          (1, 'line 2')])
151
 
 
152
 
        k.add('text2', [0], ['line 1', 'diverged line'])
153
 
 
154
 
        self.assertEqual(k.annotate(2),
155
 
                         [(0, 'line 1'),
156
 
                          (2, 'diverged line')])
 
178
        self.assertEqual(k.annotate('text1'),
 
179
                         [('text0', 'line 1'),
 
180
                          ('text1', 'line 2')])
 
181
 
 
182
        k.add_lines('text2', ['text0'], ['line 1', 'diverged line'])
 
183
 
 
184
        self.assertEqual(k.annotate('text2'),
 
185
                         [('text0', 'line 1'),
 
186
                          ('text2', 'diverged line')])
157
187
 
158
188
        text3 = ['line 1', 'middle line', 'line 2']
159
 
        k.add('text3',
160
 
              [0, 1],
 
189
        k.add_lines('text3',
 
190
              ['text0', 'text1'],
161
191
              text3)
162
192
 
163
193
        # self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
164
194
 
165
195
        self.log("k._weave=" + pformat(k._weave))
166
196
 
167
 
        self.assertEqual(k.annotate(3),
168
 
                         [(0, 'line 1'),
169
 
                          (3, 'middle line'),
170
 
                          (1, 'line 2')])
 
197
        self.assertEqual(k.annotate('text3'),
 
198
                         [('text0', 'line 1'),
 
199
                          ('text3', 'middle line'),
 
200
                          ('text1', 'line 2')])
171
201
 
172
202
        # now multiple insertions at different places
173
 
        k.add('text4',
174
 
              [0, 1, 3],
 
203
        k.add_lines('text4',
 
204
              ['text0', 'text1', 'text3'],
175
205
              ['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
176
206
 
177
 
        self.assertEqual(k.annotate(4), 
178
 
                         [(0, 'line 1'),
179
 
                          (4, 'aaa'),
180
 
                          (3, 'middle line'),
181
 
                          (4, 'bbb'),
182
 
                          (1, 'line 2'),
183
 
                          (4, 'ccc')])
184
 
 
 
207
        self.assertEqual(k.annotate('text4'), 
 
208
                         [('text0', 'line 1'),
 
209
                          ('text4', 'aaa'),
 
210
                          ('text3', 'middle line'),
 
211
                          ('text4', 'bbb'),
 
212
                          ('text1', 'line 2'),
 
213
                          ('text4', 'ccc')])
185
214
 
186
215
 
187
216
class DeleteLines(TestBase):
193
222
 
194
223
        base_text = ['one', 'two', 'three', 'four']
195
224
 
196
 
        k.add('text0', [], base_text)
 
225
        k.add_lines('text0', [], base_text)
197
226
        
198
227
        texts = [['one', 'two', 'three'],
199
228
                 ['two', 'three', 'four'],
203
232
 
204
233
        i = 1
205
234
        for t in texts:
206
 
            ver = k.add('text%d' % i,
207
 
                        [0], t)
 
235
            ver = k.add_lines('text%d' % i,
 
236
                        ['text0'], t)
208
237
            i += 1
209
238
 
210
239
        self.log('final weave:')
211
240
        self.log('k._weave=' + pformat(k._weave))
212
241
 
213
242
        for i in range(len(texts)):
214
 
            self.assertEqual(k.get(i+1),
 
243
            self.assertEqual(k.get_lines(i+1),
215
244
                             texts[i])
216
 
            
217
 
 
218
245
 
219
246
 
220
247
class SuicideDelete(TestBase):
236
263
        return 
237
264
 
238
265
        self.assertRaises(WeaveFormatError,
239
 
                          k.get,
 
266
                          k.get_lines,
240
267
                          0)        
241
268
 
242
269
 
243
 
 
244
270
class CannedDelete(TestBase):
245
271
    """Unpack canned weave with deleted lines."""
246
272
    def runTest(self):
257
283
                'last line',
258
284
                ('}', 0),
259
285
                ]
 
286
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
287
                  , sha_string('first linelast line')]
260
288
 
261
 
        self.assertEqual(k.get(0),
 
289
        self.assertEqual(k.get_lines(0),
262
290
                         ['first line',
263
291
                          'line to be deleted',
264
292
                          'last line',
265
293
                          ])
266
294
 
267
 
        self.assertEqual(k.get(1),
 
295
        self.assertEqual(k.get_lines(1),
268
296
                         ['first line',
269
297
                          'last line',
270
298
                          ])
271
299
 
272
300
 
273
 
 
274
301
class CannedReplacement(TestBase):
275
302
    """Unpack canned weave with deleted lines."""
276
303
    def runTest(self):
290
317
                'last line',
291
318
                ('}', 0),
292
319
                ]
 
320
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
321
                  , sha_string('first linereplacement linelast line')]
293
322
 
294
 
        self.assertEqual(k.get(0),
 
323
        self.assertEqual(k.get_lines(0),
295
324
                         ['first line',
296
325
                          'line to be deleted',
297
326
                          'last line',
298
327
                          ])
299
328
 
300
 
        self.assertEqual(k.get(1),
 
329
        self.assertEqual(k.get_lines(1),
301
330
                         ['first line',
302
331
                          'replacement line',
303
332
                          'last line',
304
333
                          ])
305
334
 
306
335
 
307
 
 
308
336
class BadWeave(TestBase):
309
337
    """Test that we trap an insert which should not occur."""
310
338
    def runTest(self):
390
418
                '}',
391
419
                ('}', 0)]
392
420
 
393
 
        self.assertEqual(k.get(0),
 
421
        k._sha1s = [sha_string('foo {}')
 
422
                  , sha_string('foo {  added in version 1  also from v1}')
 
423
                  , sha_string('foo {  added in v2}')
 
424
                  , sha_string('foo {  added in version 1  added in v2  also from v1}')
 
425
                  ]
 
426
 
 
427
        self.assertEqual(k.get_lines(0),
394
428
                         ['foo {',
395
429
                          '}'])
396
430
 
397
 
        self.assertEqual(k.get(1),
 
431
        self.assertEqual(k.get_lines(1),
398
432
                         ['foo {',
399
433
                          '  added in version 1',
400
434
                          '  also from v1',
401
435
                          '}'])
402
436
                       
403
 
        self.assertEqual(k.get(2),
 
437
        self.assertEqual(k.get_lines(2),
404
438
                         ['foo {',
405
439
                          '  added in v2',
406
440
                          '}'])
407
441
 
408
 
        self.assertEqual(k.get(3),
 
442
        self.assertEqual(k.get_lines(3),
409
443
                         ['foo {',
410
444
                          '  added in version 1',
411
445
                          '  added in v2',
413
447
                          '}'])
414
448
                         
415
449
 
416
 
 
417
450
class DeleteLines2(TestBase):
418
451
    """Test recording revisions that delete lines.
419
452
 
422
455
    def runTest(self):
423
456
        k = Weave()
424
457
 
425
 
        k.add('text0', [], ["line the first",
 
458
        k.add_lines('text0', [], ["line the first",
426
459
                   "line 2",
427
460
                   "line 3",
428
461
                   "fine"])
429
462
 
430
 
        self.assertEqual(len(k.get(0)), 4)
 
463
        self.assertEqual(len(k.get_lines(0)), 4)
431
464
 
432
 
        k.add('text1', [0], ["line the first",
 
465
        k.add_lines('text1', ['text0'], ["line the first",
433
466
                   "fine"])
434
467
 
435
 
        self.assertEqual(k.get(1),
 
468
        self.assertEqual(k.get_lines(1),
436
469
                         ["line the first",
437
470
                          "fine"])
438
471
 
439
 
        self.assertEqual(k.annotate(1),
440
 
                         [(0, "line the first"),
441
 
                          (0, "fine")])
442
 
 
 
472
        self.assertEqual(k.annotate('text1'),
 
473
                         [('text0', "line the first"),
 
474
                          ('text0', "fine")])
443
475
 
444
476
 
445
477
class IncludeVersions(TestBase):
463
495
                "second line",
464
496
                ('}', 1)]
465
497
 
466
 
        self.assertEqual(k.get(1),
 
498
        k._sha1s = [sha_string('first line')
 
499
                  , sha_string('first linesecond line')]
 
500
 
 
501
        self.assertEqual(k.get_lines(1),
467
502
                         ["first line",
468
503
                          "second line"])
469
504
 
470
 
        self.assertEqual(k.get(0),
 
505
        self.assertEqual(k.get_lines(0),
471
506
                         ["first line"])
472
507
 
473
 
        k.dump(self.TEST_LOG)
474
 
 
475
508
 
476
509
class DivergedIncludes(TestBase):
477
510
    """Weave with two diverged texts based on version 0.
478
511
    """
479
512
    def runTest(self):
 
513
        # FIXME make the weave, dont poke at it.
480
514
        k = Weave()
481
515
 
 
516
        k._names = ['0', '1', '2']
 
517
        k._name_map = {'0':0, '1':1, '2':2}
482
518
        k._parents = [frozenset(),
483
519
                frozenset([0]),
484
520
                frozenset([0]),
494
530
                ('}', 2),                
495
531
                ]
496
532
 
497
 
        self.assertEqual(k.get(0),
 
533
        k._sha1s = [sha_string('first line')
 
534
                  , sha_string('first linesecond line')
 
535
                  , sha_string('first linealternative second line')]
 
536
 
 
537
        self.assertEqual(k.get_lines(0),
498
538
                         ["first line"])
499
539
 
500
 
        self.assertEqual(k.get(1),
 
540
        self.assertEqual(k.get_lines(1),
501
541
                         ["first line",
502
542
                          "second line"])
503
543
 
504
 
        self.assertEqual(k.get(2),
 
544
        self.assertEqual(k.get_lines('2'),
505
545
                         ["first line",
506
546
                          "alternative second line"])
507
547
 
508
 
        self.assertEqual(list(k.inclusions([2])),
509
 
                         [0, 2])
510
 
 
 
548
        self.assertEqual(list(k.get_ancestry(['2'])),
 
549
                         ['0', '2'])
511
550
 
512
551
 
513
552
class ReplaceLine(TestBase):
517
556
        text0 = ['cheddar', 'stilton', 'gruyere']
518
557
        text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
519
558
        
520
 
        k.add('text0', [], text0)
521
 
        k.add('text1', [0], text1)
 
559
        k.add_lines('text0', [], text0)
 
560
        k.add_lines('text1', ['text0'], text1)
522
561
 
523
562
        self.log('k._weave=' + pformat(k._weave))
524
563
 
525
 
        self.assertEqual(k.get(0), text0)
526
 
        self.assertEqual(k.get(1), text1)
527
 
 
 
564
        self.assertEqual(k.get_lines(0), text0)
 
565
        self.assertEqual(k.get_lines(1), text1)
528
566
 
529
567
 
530
568
class Merge(TestBase):
538
576
                 ['header', '', 'line from 1', 'fixup line', 'line from 2'],
539
577
                 ]
540
578
 
541
 
        k.add('text0', [], texts[0])
542
 
        k.add('text1', [0], texts[1])
543
 
        k.add('text2', [0], texts[2])
544
 
        k.add('merge', [0, 1, 2], texts[3])
 
579
        k.add_lines('text0', [], texts[0])
 
580
        k.add_lines('text1', ['text0'], texts[1])
 
581
        k.add_lines('text2', ['text0'], texts[2])
 
582
        k.add_lines('merge', ['text0', 'text1', 'text2'], texts[3])
545
583
 
546
584
        for i, t in enumerate(texts):
547
 
            self.assertEqual(k.get(i), t)
 
585
            self.assertEqual(k.get_lines(i), t)
548
586
 
549
 
        self.assertEqual(k.annotate(3),
550
 
                         [(0, 'header'),
551
 
                          (1, ''),
552
 
                          (1, 'line from 1'),
553
 
                          (3, 'fixup line'),
554
 
                          (2, 'line from 2'),
 
587
        self.assertEqual(k.annotate('merge'),
 
588
                         [('text0', 'header'),
 
589
                          ('text1', ''),
 
590
                          ('text1', 'line from 1'),
 
591
                          ('merge', 'fixup line'),
 
592
                          ('text2', 'line from 2'),
555
593
                          ])
556
594
 
557
 
        self.assertEqual(list(k.inclusions([3])),
558
 
                         [0, 1, 2, 3])
 
595
        self.assertEqual(list(k.get_ancestry(['merge'])),
 
596
                         ['text0', 'text1', 'text2', 'merge'])
559
597
 
560
598
        self.log('k._weave=' + pformat(k._weave))
561
599
 
572
610
        return  # NOT RUN
573
611
        k = Weave()
574
612
 
575
 
        k.add([], ['aaa', 'bbb'])
576
 
        k.add([0], ['aaa', '111', 'bbb'])
577
 
        k.add([1], ['aaa', '222', 'bbb'])
 
613
        k.add_lines([], ['aaa', 'bbb'])
 
614
        k.add_lines([0], ['aaa', '111', 'bbb'])
 
615
        k.add_lines([1], ['aaa', '222', 'bbb'])
578
616
 
579
617
        merged = k.merge([1, 2])
580
618
 
583
621
                           [['bbb']]])
584
622
 
585
623
 
586
 
 
587
624
class NonConflict(TestBase):
588
625
    """Two descendants insert compatible changes.
589
626
 
592
629
        return  # NOT RUN
593
630
        k = Weave()
594
631
 
595
 
        k.add([], ['aaa', 'bbb'])
596
 
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
597
 
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
598
 
 
599
 
    
600
 
    
601
 
 
602
 
 
603
 
class AutoMerge(TestBase):
604
 
    def runTest(self):
605
 
        k = Weave()
606
 
 
607
 
        texts = [['header', 'aaa', 'bbb'],
608
 
                 ['header', 'aaa', 'line from 1', 'bbb'],
609
 
                 ['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
610
 
                 ]
611
 
 
612
 
        k.add('text0', [], texts[0])
613
 
        k.add('text1', [0], texts[1])
614
 
        k.add('text2', [0], texts[2])
615
 
 
616
 
        self.log('k._weave=' + pformat(k._weave))
617
 
 
618
 
        m = list(k.mash_iter([0, 1, 2]))
619
 
 
620
 
        self.assertEqual(m,
621
 
                         ['header', 'aaa',
622
 
                          'line from 1',
623
 
                          'bbb',
624
 
                          'line from 2', 'more from 2'])
625
 
        
 
632
        k.add_lines([], ['aaa', 'bbb'])
 
633
        k.add_lines([0], ['111', 'aaa', 'ccc', 'bbb'])
 
634
        k.add_lines([1], ['aaa', 'ccc', 'bbb', '222'])
626
635
 
627
636
 
628
637
class Khayyam(TestBase):
629
638
    """Test changes to multi-line texts, and read/write"""
630
 
    def runTest(self):
 
639
 
 
640
    def test_multi_line_merge(self):
631
641
        rawtexts = [
632
642
            """A Book of Verses underneath the Bough,
633
643
            A Jug of Wine, a Loaf of Bread, -- and Thou
659
669
        parents = set()
660
670
        i = 0
661
671
        for t in texts:
662
 
            ver = k.add('text%d' % i,
 
672
            ver = k.add_lines('text%d' % i,
663
673
                        list(parents), t)
664
 
            parents.add(ver)
 
674
            parents.add('text%d' % i)
665
675
            i += 1
666
676
 
667
677
        self.log("k._weave=" + pformat(k._weave))
668
678
 
669
679
        for i, t in enumerate(texts):
670
 
            self.assertEqual(k.get(i), t)
 
680
            self.assertEqual(k.get_lines(i), t)
671
681
 
672
682
        self.check_read_write(k)
673
683
 
674
684
 
675
 
 
676
685
class MergeCases(TestBase):
677
686
    def doMerge(self, base, a, b, mp):
678
687
        from cStringIO import StringIO
682
691
            return x + '\n'
683
692
        
684
693
        w = Weave()
685
 
        w.add('text0', [], map(addcrlf, base))
686
 
        w.add('text1', [0], map(addcrlf, a))
687
 
        w.add('text2', [0], map(addcrlf, b))
 
694
        w.add_lines('text0', [], map(addcrlf, base))
 
695
        w.add_lines('text1', ['text0'], map(addcrlf, a))
 
696
        w.add_lines('text2', ['text0'], map(addcrlf, b))
688
697
 
689
698
        self.log('weave is:')
690
699
        tmpf = StringIO()
692
701
        self.log(tmpf.getvalue())
693
702
 
694
703
        self.log('merge plan:')
695
 
        p = list(w.plan_merge(1, 2))
 
704
        p = list(w.plan_merge('text1', 'text2'))
696
705
        for state, line in p:
697
706
            if line:
698
707
                self.log('%12s | %s' % (state, line[:-1]))
729
738
        self.doMerge(['aaa', 'bbb'],
730
739
                     ['aaa', 'xxx', 'yyy', 'bbb'],
731
740
                     ['aaa', 'xxx', 'bbb'],
732
 
                     ['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
 
741
                     ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 'xxx', 
 
742
                      '>>>>>>> ', 'bbb'])
733
743
 
734
744
        # really it ought to reduce this to 
735
745
        # ['aaa', 'xxx', 'yyy', 'bbb']
739
749
        self.doMerge(['aaa'],
740
750
                     ['xxx'],
741
751
                     ['yyy', 'zzz'],
742
 
                     ['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
 
752
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
753
                      '>>>>>>> '])
743
754
 
744
755
    def testNonClashInsert(self):
745
756
        self.doMerge(['aaa'],
746
757
                     ['xxx', 'aaa'],
747
758
                     ['yyy', 'zzz'],
748
 
                     ['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
 
759
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
760
                      '>>>>>>> '])
749
761
 
750
762
        self.doMerge(['aaa'],
751
763
                     ['aaa'],
767
779
        self.doMerge(['aaa', 'bbb', 'ccc'],
768
780
                     ['aaa', 'ddd', 'ccc'],
769
781
                     ['aaa', 'ccc'],
770
 
                     ['<<<<', 'aaa', '====', '>>>>', 'ccc'])
771
 
    
772
 
 
773
 
 
774
 
def testweave():
775
 
    import testsweet
776
 
    from unittest import TestSuite, TestLoader
777
 
    import testweave
778
 
 
779
 
    tl = TestLoader()
780
 
    suite = TestSuite()
781
 
    suite.addTest(tl.loadTestsFromModule(testweave))
782
 
    
783
 
    return int(not testsweet.run_suite(suite)) # for shell 0=true
784
 
 
785
 
 
786
 
if __name__ == '__main__':
787
 
    import sys
788
 
    sys.exit(testweave())
789
 
    
 
782
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
 
783
 
 
784
    def _test_merge_from_strings(self, base, a, b, expected):
 
785
        w = Weave()
 
786
        w.add_lines('text0', [], base.splitlines(True))
 
787
        w.add_lines('text1', ['text0'], a.splitlines(True))
 
788
        w.add_lines('text2', ['text0'], b.splitlines(True))
 
789
        self.log('merge plan:')
 
790
        p = list(w.plan_merge('text1', 'text2'))
 
791
        for state, line in p:
 
792
            if line:
 
793
                self.log('%12s | %s' % (state, line[:-1]))
 
794
        self.log('merge result:')
 
795
        result_text = ''.join(w.weave_merge(p))
 
796
        self.log(result_text)
 
797
        self.assertEqualDiff(result_text, expected)
 
798
 
 
799
    def test_deletion_extended(self):
 
800
        """One side deletes, the other deletes more.
 
801
        """
 
802
        base = """\
 
803
            line 1
 
804
            line 2
 
805
            line 3
 
806
            """
 
807
        a = """\
 
808
            line 1
 
809
            line 2
 
810
            """
 
811
        b = """\
 
812
            line 1
 
813
            """
 
814
        result = """\
 
815
            line 1
 
816
            """
 
817
        self._test_merge_from_strings(base, a, b, result)
 
818
 
 
819
    def test_deletion_overlap(self):
 
820
        """Delete overlapping regions with no other conflict.
 
821
 
 
822
        Arguably it'd be better to treat these as agreement, rather than 
 
823
        conflict, but for now conflict is safer.
 
824
        """
 
825
        base = """\
 
826
            start context
 
827
            int a() {}
 
828
            int b() {}
 
829
            int c() {}
 
830
            end context
 
831
            """
 
832
        a = """\
 
833
            start context
 
834
            int a() {}
 
835
            end context
 
836
            """
 
837
        b = """\
 
838
            start context
 
839
            int c() {}
 
840
            end context
 
841
            """
 
842
        result = """\
 
843
            start context
 
844
<<<<<<< 
 
845
            int a() {}
 
846
=======
 
847
            int c() {}
 
848
>>>>>>> 
 
849
            end context
 
850
            """
 
851
        self._test_merge_from_strings(base, a, b, result)
 
852
 
 
853
    def test_agreement_deletion(self):
 
854
        """Agree to delete some lines, without conflicts."""
 
855
        base = """\
 
856
            start context
 
857
            base line 1
 
858
            base line 2
 
859
            end context
 
860
            """
 
861
        a = """\
 
862
            start context
 
863
            base line 1
 
864
            end context
 
865
            """
 
866
        b = """\
 
867
            start context
 
868
            base line 1
 
869
            end context
 
870
            """
 
871
        result = """\
 
872
            start context
 
873
            base line 1
 
874
            end context
 
875
            """
 
876
        self._test_merge_from_strings(base, a, b, result)
 
877
 
 
878
    def test_sync_on_deletion(self):
 
879
        """Specific case of merge where we can synchronize incorrectly.
 
880
        
 
881
        A previous version of the weave merge concluded that the two versions
 
882
        agreed on deleting line 2, and this could be a synchronization point.
 
883
        Line 1 was then considered in isolation, and thought to be deleted on 
 
884
        both sides.
 
885
 
 
886
        It's better to consider the whole thing as a disagreement region.
 
887
        """
 
888
        base = """\
 
889
            start context
 
890
            base line 1
 
891
            base line 2
 
892
            end context
 
893
            """
 
894
        a = """\
 
895
            start context
 
896
            base line 1
 
897
            a's replacement line 2
 
898
            end context
 
899
            """
 
900
        b = """\
 
901
            start context
 
902
            b replaces
 
903
            both lines
 
904
            end context
 
905
            """
 
906
        result = """\
 
907
            start context
 
908
<<<<<<< 
 
909
            base line 1
 
910
            a's replacement line 2
 
911
=======
 
912
            b replaces
 
913
            both lines
 
914
>>>>>>> 
 
915
            end context
 
916
            """
 
917
        self._test_merge_from_strings(base, a, b, result)
 
918
 
 
919
 
 
920
class JoinWeavesTests(TestBase):
 
921
    def setUp(self):
 
922
        super(JoinWeavesTests, self).setUp()
 
923
        self.weave1 = Weave()
 
924
        self.lines1 = ['hello\n']
 
925
        self.lines3 = ['hello\n', 'cruel\n', 'world\n']
 
926
        self.weave1.add_lines('v1', [], self.lines1)
 
927
        self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
 
928
        self.weave1.add_lines('v3', ['v2'], self.lines3)
 
929
        
 
930
    def test_join_empty(self):
 
931
        """Join two empty weaves."""
 
932
        eq = self.assertEqual
 
933
        w1 = Weave()
 
934
        w2 = Weave()
 
935
        w1.join(w2)
 
936
        eq(len(w1), 0)
 
937
        
 
938
    def test_join_empty_to_nonempty(self):
 
939
        """Join empty weave onto nonempty."""
 
940
        self.weave1.join(Weave())
 
941
        self.assertEqual(len(self.weave1), 3)
 
942
 
 
943
    def test_join_unrelated(self):
 
944
        """Join two weaves with no history in common."""
 
945
        wb = Weave()
 
946
        wb.add_lines('b1', [], ['line from b\n'])
 
947
        w1 = self.weave1
 
948
        w1.join(wb)
 
949
        eq = self.assertEqual
 
950
        eq(len(w1), 4)
 
951
        eq(sorted(w1.versions()),
 
952
           ['b1', 'v1', 'v2', 'v3'])
 
953
 
 
954
    def test_join_related(self):
 
955
        wa = self.weave1.copy()
 
956
        wb = self.weave1.copy()
 
957
        wa.add_lines('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
 
958
        wb.add_lines('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
 
959
        eq = self.assertEquals
 
960
        eq(len(wa), 4)
 
961
        eq(len(wb), 4)
 
962
        wa.join(wb)
 
963
        eq(len(wa), 5)
 
964
        eq(wa.get_lines('b1'),
 
965
           ['hello\n', 'pale blue\n', 'world\n'])
 
966
 
 
967
    def test_join_parent_disagreement(self):
 
968
        #join reconciles differening parents into a union.
 
969
        wa = Weave()
 
970
        wb = Weave()
 
971
        wa.add_lines('v1', [], ['hello\n'])
 
972
        wb.add_lines('v0', [], [])
 
973
        wb.add_lines('v1', ['v0'], ['hello\n'])
 
974
        wa.join(wb)
 
975
        self.assertEqual(['v0'], wa.get_parents('v1'))
 
976
 
 
977
    def test_join_text_disagreement(self):
 
978
        """Cannot join weaves with different texts for a version."""
 
979
        wa = Weave()
 
980
        wb = Weave()
 
981
        wa.add_lines('v1', [], ['hello\n'])
 
982
        wb.add_lines('v1', [], ['not\n', 'hello\n'])
 
983
        self.assertRaises(WeaveError,
 
984
                          wa.join, wb)
 
985
 
 
986
    def test_join_unordered(self):
 
987
        """Join weaves where indexes differ.
 
988
        
 
989
        The source weave contains a different version at index 0."""
 
990
        wa = self.weave1.copy()
 
991
        wb = Weave()
 
992
        wb.add_lines('x1', [], ['line from x1\n'])
 
993
        wb.add_lines('v1', [], ['hello\n'])
 
994
        wb.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
 
995
        wa.join(wb)
 
996
        eq = self.assertEquals
 
997
        eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
 
998
        eq(wa.get_text('x1'), 'line from x1\n')
 
999
 
 
1000
    def test_written_detection(self):
 
1001
        # Test detection of weave file corruption.
 
1002
        #
 
1003
        # Make sure that we can detect if a weave file has
 
1004
        # been corrupted. This doesn't test all forms of corruption,
 
1005
        # but it at least helps verify the data you get, is what you want.
 
1006
        from cStringIO import StringIO
 
1007
 
 
1008
        w = Weave()
 
1009
        w.add_lines('v1', [], ['hello\n'])
 
1010
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
 
1011
 
 
1012
        tmpf = StringIO()
 
1013
        write_weave(w, tmpf)
 
1014
 
 
1015
        # Because we are corrupting, we need to make sure we have the exact text
 
1016
        self.assertEquals('# bzr weave file v5\n'
 
1017
                          'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
1018
                          'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
1019
                          'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
 
1020
                          tmpf.getvalue())
 
1021
 
 
1022
        # Change a single letter
 
1023
        tmpf = StringIO('# bzr weave file v5\n'
 
1024
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
1025
                        'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
1026
                        'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
 
1027
 
 
1028
        w = read_weave(tmpf)
 
1029
 
 
1030
        self.assertEqual('hello\n', w.get_text('v1'))
 
1031
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
1032
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
1033
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
1034
 
 
1035
        # Change the sha checksum
 
1036
        tmpf = StringIO('# bzr weave file v5\n'
 
1037
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
1038
                        'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
1039
                        'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
 
1040
 
 
1041
        w = read_weave(tmpf)
 
1042
 
 
1043
        self.assertEqual('hello\n', w.get_text('v1'))
 
1044
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
1045
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
1046
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
1047
 
 
1048
 
 
1049
class InstrumentedWeave(Weave):
 
1050
    """Keep track of how many times functions are called."""
 
1051
    
 
1052
    def __init__(self, weave_name=None):
 
1053
        self._extract_count = 0
 
1054
        Weave.__init__(self, weave_name=weave_name)
 
1055
 
 
1056
    def _extract(self, versions):
 
1057
        self._extract_count += 1
 
1058
        return Weave._extract(self, versions)
 
1059
 
 
1060
 
 
1061
class JoinOptimization(TestCase):
 
1062
    """Test that Weave.join() doesn't extract all texts, only what must be done."""
 
1063
 
 
1064
    def test_join(self):
 
1065
        w1 = InstrumentedWeave()
 
1066
        w2 = InstrumentedWeave()
 
1067
 
 
1068
        txt0 = ['a\n']
 
1069
        txt1 = ['a\n', 'b\n']
 
1070
        txt2 = ['a\n', 'c\n']
 
1071
        txt3 = ['a\n', 'b\n', 'c\n']
 
1072
 
 
1073
        w1.add_lines('txt0', [], txt0) # extract 1a
 
1074
        w2.add_lines('txt0', [], txt0) # extract 1b
 
1075
        w1.add_lines('txt1', ['txt0'], txt1)# extract 2a
 
1076
        w2.add_lines('txt2', ['txt0'], txt2)# extract 2b
 
1077
        w1.join(w2) # extract 3a to add txt2 
 
1078
        w2.join(w1) # extract 3b to add txt1 
 
1079
 
 
1080
        w1.add_lines('txt3', ['txt1', 'txt2'], txt3) # extract 4a 
 
1081
        w2.add_lines('txt3', ['txt2', 'txt1'], txt3) # extract 4b
 
1082
        # These secretly have inverted parents
 
1083
 
 
1084
        # This should not have to do any extractions
 
1085
        w1.join(w2) # NO extract, texts already present with same parents
 
1086
        w2.join(w1) # NO extract, texts already present with same parents
 
1087
 
 
1088
        self.assertEqual(4, w1._extract_count)
 
1089
        self.assertEqual(4, w2._extract_count)
 
1090
 
 
1091
    def test_double_parent(self):
 
1092
        # It should not be considered illegal to add
 
1093
        # a revision with the same parent twice
 
1094
        w1 = InstrumentedWeave()
 
1095
        w2 = InstrumentedWeave()
 
1096
 
 
1097
        txt0 = ['a\n']
 
1098
        txt1 = ['a\n', 'b\n']
 
1099
        txt2 = ['a\n', 'c\n']
 
1100
        txt3 = ['a\n', 'b\n', 'c\n']
 
1101
 
 
1102
        w1.add_lines('txt0', [], txt0)
 
1103
        w2.add_lines('txt0', [], txt0)
 
1104
        w1.add_lines('txt1', ['txt0'], txt1)
 
1105
        w2.add_lines('txt1', ['txt0', 'txt0'], txt1)
 
1106
        # Same text, effectively the same, because the
 
1107
        # parent is only repeated
 
1108
        w1.join(w2) # extract 3a to add txt2 
 
1109
        w2.join(w1) # extract 3b to add txt1 
 
1110
 
 
1111
 
 
1112
class TestNeedsReweave(TestCase):
 
1113
    """Internal corner cases for when reweave is needed."""
 
1114
 
 
1115
    def test_compatible_parents(self):
 
1116
        w1 = Weave('a')
 
1117
        my_parents = set([1, 2, 3])
 
1118
        # subsets are ok
 
1119
        self.assertTrue(w1._compatible_parents(my_parents, set([3])))
 
1120
        # same sets
 
1121
        self.assertTrue(w1._compatible_parents(my_parents, set(my_parents)))
 
1122
        # same empty corner case
 
1123
        self.assertTrue(w1._compatible_parents(set(), set()))
 
1124
        # other cannot contain stuff my_parents does not
 
1125
        self.assertFalse(w1._compatible_parents(set(), set([1])))
 
1126
        self.assertFalse(w1._compatible_parents(my_parents, set([1, 2, 3, 4])))
 
1127
        self.assertFalse(w1._compatible_parents(my_parents, set([4])))