~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/test_weave.py

- increment version

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#! /usr/bin/python2.4
2
2
 
3
 
# Copyright (C) 2005 Canonical Ltd
4
 
#
 
3
# Copyright (C) 2005 by Canonical Ltd
 
4
 
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
7
7
# the Free Software Foundation; either version 2 of the License, or
8
8
# (at your option) any later version.
9
 
#
 
9
 
10
10
# This program is distributed in the hope that it will be useful,
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
13
# GNU General Public License for more details.
14
 
#
 
14
 
15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
25
 
26
26
from pprint import pformat
27
27
 
28
 
from bzrlib import (
29
 
    errors,
30
 
    )
31
 
from bzrlib.osutils import sha_string
32
 
from bzrlib.tests import TestCase, TestCaseInTempDir
 
28
import bzrlib.errors as errors
33
29
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
34
30
from bzrlib.weavefile import write_weave, read_weave
 
31
from bzrlib.selftest import TestCase
 
32
from bzrlib.osutils import sha_string
35
33
 
36
34
 
37
35
# texts for use in testing
40
38
          "A second line"]
41
39
 
42
40
 
 
41
 
43
42
class TestBase(TestCase):
44
43
    def check_read_write(self, k):
45
44
        """Check the weave k can be written & re-read."""
60
59
            self.log('         %r' % k._parents)
61
60
            self.log('         %r' % k2._parents)
62
61
            self.log('')
 
62
 
 
63
            
63
64
            self.fail('read/write check failed')
64
 
 
65
 
 
66
 
class WeaveContains(TestBase):
67
 
    """Weave __contains__ operator"""
68
 
    def runTest(self):
69
 
        k = Weave()
70
 
        self.assertFalse('foo' in k)
71
 
        k.add_lines('foo', [], TEXT_1)
72
 
        self.assertTrue('foo' in k)
 
65
        
 
66
        
73
67
 
74
68
 
75
69
class Easy(TestBase):
79
73
 
80
74
class StoreText(TestBase):
81
75
    """Store and retrieve a simple text."""
82
 
 
83
 
    def test_storing_text(self):
 
76
    def runTest(self):
84
77
        k = Weave()
85
 
        idx = k.add_lines('text0', [], TEXT_0)
86
 
        self.assertEqual(k.get_lines(idx), TEXT_0)
 
78
        idx = k.add('text0', [], TEXT_0)
 
79
        self.assertEqual(k.get(idx), TEXT_0)
87
80
        self.assertEqual(idx, 0)
88
81
 
89
82
 
 
83
 
90
84
class AnnotateOne(TestBase):
91
85
    def runTest(self):
92
86
        k = Weave()
93
 
        k.add_lines('text0', [], TEXT_0)
94
 
        self.assertEqual(k.annotate('text0'),
95
 
                         [('text0', TEXT_0[0])])
 
87
        k.add('text0', [], TEXT_0)
 
88
        self.assertEqual(k.annotate(0),
 
89
                         [(0, TEXT_0[0])])
96
90
 
97
91
 
98
92
class StoreTwo(TestBase):
99
93
    def runTest(self):
100
94
        k = Weave()
101
95
 
102
 
        idx = k.add_lines('text0', [], TEXT_0)
 
96
        idx = k.add('text0', [], TEXT_0)
103
97
        self.assertEqual(idx, 0)
104
98
 
105
 
        idx = k.add_lines('text1', [], TEXT_1)
 
99
        idx = k.add('text1', [], TEXT_1)
106
100
        self.assertEqual(idx, 1)
107
101
 
108
 
        self.assertEqual(k.get_lines(0), TEXT_0)
109
 
        self.assertEqual(k.get_lines(1), TEXT_1)
110
 
 
111
 
 
112
 
class GetSha1(TestBase):
113
 
    def test_get_sha1(self):
 
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"""
114
110
        k = Weave()
115
 
        k.add_lines('text0', [], 'text0')
116
 
        self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
117
 
                         k.get_sha1('text0'))
118
 
        self.assertRaises(errors.RevisionNotPresent,
119
 
                          k.get_sha1, 0)
120
 
        self.assertRaises(errors.RevisionNotPresent,
121
 
                          k.get_sha1, 'text1')
122
 
                        
 
111
 
 
112
        t = 'text0'
 
113
        k.add('text0', [], [t], sha1=sha_string(t))
 
114
 
 
115
 
123
116
 
124
117
class InvalidAdd(TestBase):
125
118
    """Try to use invalid version number during add."""
126
119
    def runTest(self):
127
120
        k = Weave()
128
121
 
129
 
        self.assertRaises(errors.RevisionNotPresent,
130
 
                          k.add_lines,
 
122
        self.assertRaises(IndexError,
 
123
                          k.add,
131
124
                          'text0',
132
 
                          ['69'],
 
125
                          [69],
133
126
                          ['new text!'])
134
127
 
135
128
 
137
130
    """Add the same version twice; harmless."""
138
131
    def runTest(self):
139
132
        k = Weave()
140
 
        idx = k.add_lines('text0', [], TEXT_0)
141
 
        idx2 = k.add_lines('text0', [], TEXT_0)
 
133
        idx = k.add('text0', [], TEXT_0)
 
134
        idx2 = k.add('text0', [], TEXT_0)
142
135
        self.assertEqual(idx, idx2)
143
136
 
144
137
 
 
138
 
145
139
class InvalidRepeatedAdd(TestBase):
146
140
    def runTest(self):
147
141
        k = Weave()
148
 
        k.add_lines('basis', [], TEXT_0)
149
 
        idx = k.add_lines('text0', [], TEXT_0)
150
 
        self.assertRaises(errors.RevisionAlreadyPresent,
151
 
                          k.add_lines,
 
142
        idx = k.add('text0', [], TEXT_0)
 
143
        self.assertRaises(WeaveError,
 
144
                          k.add,
152
145
                          'text0',
153
146
                          [],
154
147
                          ['not the same text'])
155
 
        self.assertRaises(errors.RevisionAlreadyPresent,
156
 
                          k.add_lines,
 
148
        self.assertRaises(WeaveError,
 
149
                          k.add,
157
150
                          'text0',
158
 
                          ['basis'],         # not the right parents
 
151
                          [12],         # not the right parents
159
152
                          TEXT_0)
160
153
        
161
154
 
 
155
 
162
156
class InsertLines(TestBase):
163
157
    """Store a revision that adds one line to the original.
164
158
 
167
161
    def runTest(self):
168
162
        k = Weave()
169
163
 
170
 
        k.add_lines('text0', [], ['line 1'])
171
 
        k.add_lines('text1', ['text0'], ['line 1', 'line 2'])
172
 
 
173
 
        self.assertEqual(k.annotate('text0'),
174
 
                         [('text0', 'line 1')])
175
 
 
176
 
        self.assertEqual(k.get_lines(1),
 
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),
177
171
                         ['line 1',
178
172
                          'line 2'])
179
173
 
180
 
        self.assertEqual(k.annotate('text1'),
181
 
                         [('text0', 'line 1'),
182
 
                          ('text1', 'line 2')])
183
 
 
184
 
        k.add_lines('text2', ['text0'], ['line 1', 'diverged line'])
185
 
 
186
 
        self.assertEqual(k.annotate('text2'),
187
 
                         [('text0', 'line 1'),
188
 
                          ('text2', 'diverged line')])
 
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')])
189
183
 
190
184
        text3 = ['line 1', 'middle line', 'line 2']
191
 
        k.add_lines('text3',
192
 
              ['text0', 'text1'],
 
185
        k.add('text3',
 
186
              [0, 1],
193
187
              text3)
194
188
 
195
189
        # self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
196
190
 
197
191
        self.log("k._weave=" + pformat(k._weave))
198
192
 
199
 
        self.assertEqual(k.annotate('text3'),
200
 
                         [('text0', 'line 1'),
201
 
                          ('text3', 'middle line'),
202
 
                          ('text1', 'line 2')])
 
193
        self.assertEqual(k.annotate(3),
 
194
                         [(0, 'line 1'),
 
195
                          (3, 'middle line'),
 
196
                          (1, 'line 2')])
203
197
 
204
198
        # now multiple insertions at different places
205
 
        k.add_lines('text4',
206
 
              ['text0', 'text1', 'text3'],
 
199
        k.add('text4',
 
200
              [0, 1, 3],
207
201
              ['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
208
202
 
209
 
        self.assertEqual(k.annotate('text4'), 
210
 
                         [('text0', 'line 1'),
211
 
                          ('text4', 'aaa'),
212
 
                          ('text3', 'middle line'),
213
 
                          ('text4', 'bbb'),
214
 
                          ('text1', 'line 2'),
215
 
                          ('text4', 'ccc')])
 
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
 
216
211
 
217
212
 
218
213
class DeleteLines(TestBase):
224
219
 
225
220
        base_text = ['one', 'two', 'three', 'four']
226
221
 
227
 
        k.add_lines('text0', [], base_text)
 
222
        k.add('text0', [], base_text)
228
223
        
229
224
        texts = [['one', 'two', 'three'],
230
225
                 ['two', 'three', 'four'],
234
229
 
235
230
        i = 1
236
231
        for t in texts:
237
 
            ver = k.add_lines('text%d' % i,
238
 
                        ['text0'], t)
 
232
            ver = k.add('text%d' % i,
 
233
                        [0], t)
239
234
            i += 1
240
235
 
241
236
        self.log('final weave:')
242
237
        self.log('k._weave=' + pformat(k._weave))
243
238
 
244
239
        for i in range(len(texts)):
245
 
            self.assertEqual(k.get_lines(i+1),
 
240
            self.assertEqual(k.get(i+1),
246
241
                             texts[i])
 
242
            
 
243
 
247
244
 
248
245
 
249
246
class SuicideDelete(TestBase):
265
262
        return 
266
263
 
267
264
        self.assertRaises(WeaveFormatError,
268
 
                          k.get_lines,
 
265
                          k.get,
269
266
                          0)        
270
267
 
271
268
 
 
269
 
272
270
class CannedDelete(TestBase):
273
271
    """Unpack canned weave with deleted lines."""
274
272
    def runTest(self):
285
283
                'last line',
286
284
                ('}', 0),
287
285
                ]
288
 
        k._sha1s = [sha_string('first lineline to be deletedlast line')
289
 
                  , sha_string('first linelast line')]
290
286
 
291
 
        self.assertEqual(k.get_lines(0),
 
287
        self.assertEqual(k.get(0),
292
288
                         ['first line',
293
289
                          'line to be deleted',
294
290
                          'last line',
295
291
                          ])
296
292
 
297
 
        self.assertEqual(k.get_lines(1),
 
293
        self.assertEqual(k.get(1),
298
294
                         ['first line',
299
295
                          'last line',
300
296
                          ])
301
297
 
302
298
 
 
299
 
303
300
class CannedReplacement(TestBase):
304
301
    """Unpack canned weave with deleted lines."""
305
302
    def runTest(self):
319
316
                'last line',
320
317
                ('}', 0),
321
318
                ]
322
 
        k._sha1s = [sha_string('first lineline to be deletedlast line')
323
 
                  , sha_string('first linereplacement linelast line')]
324
319
 
325
 
        self.assertEqual(k.get_lines(0),
 
320
        self.assertEqual(k.get(0),
326
321
                         ['first line',
327
322
                          'line to be deleted',
328
323
                          'last line',
329
324
                          ])
330
325
 
331
 
        self.assertEqual(k.get_lines(1),
 
326
        self.assertEqual(k.get(1),
332
327
                         ['first line',
333
328
                          'replacement line',
334
329
                          'last line',
335
330
                          ])
336
331
 
337
332
 
 
333
 
338
334
class BadWeave(TestBase):
339
335
    """Test that we trap an insert which should not occur."""
340
336
    def runTest(self):
420
416
                '}',
421
417
                ('}', 0)]
422
418
 
423
 
        k._sha1s = [sha_string('foo {}')
424
 
                  , sha_string('foo {  added in version 1  also from v1}')
425
 
                  , sha_string('foo {  added in v2}')
426
 
                  , sha_string('foo {  added in version 1  added in v2  also from v1}')
427
 
                  ]
428
 
 
429
 
        self.assertEqual(k.get_lines(0),
 
419
        self.assertEqual(k.get(0),
430
420
                         ['foo {',
431
421
                          '}'])
432
422
 
433
 
        self.assertEqual(k.get_lines(1),
 
423
        self.assertEqual(k.get(1),
434
424
                         ['foo {',
435
425
                          '  added in version 1',
436
426
                          '  also from v1',
437
427
                          '}'])
438
428
                       
439
 
        self.assertEqual(k.get_lines(2),
 
429
        self.assertEqual(k.get(2),
440
430
                         ['foo {',
441
431
                          '  added in v2',
442
432
                          '}'])
443
433
 
444
 
        self.assertEqual(k.get_lines(3),
 
434
        self.assertEqual(k.get(3),
445
435
                         ['foo {',
446
436
                          '  added in version 1',
447
437
                          '  added in v2',
449
439
                          '}'])
450
440
                         
451
441
 
 
442
 
452
443
class DeleteLines2(TestBase):
453
444
    """Test recording revisions that delete lines.
454
445
 
457
448
    def runTest(self):
458
449
        k = Weave()
459
450
 
460
 
        k.add_lines('text0', [], ["line the first",
 
451
        k.add('text0', [], ["line the first",
461
452
                   "line 2",
462
453
                   "line 3",
463
454
                   "fine"])
464
455
 
465
 
        self.assertEqual(len(k.get_lines(0)), 4)
 
456
        self.assertEqual(len(k.get(0)), 4)
466
457
 
467
 
        k.add_lines('text1', ['text0'], ["line the first",
 
458
        k.add('text1', [0], ["line the first",
468
459
                   "fine"])
469
460
 
470
 
        self.assertEqual(k.get_lines(1),
 
461
        self.assertEqual(k.get(1),
471
462
                         ["line the first",
472
463
                          "fine"])
473
464
 
474
 
        self.assertEqual(k.annotate('text1'),
475
 
                         [('text0', "line the first"),
476
 
                          ('text0', "fine")])
 
465
        self.assertEqual(k.annotate(1),
 
466
                         [(0, "line the first"),
 
467
                          (0, "fine")])
 
468
 
477
469
 
478
470
 
479
471
class IncludeVersions(TestBase):
497
489
                "second line",
498
490
                ('}', 1)]
499
491
 
500
 
        k._sha1s = [sha_string('first line')
501
 
                  , sha_string('first linesecond line')]
502
 
 
503
 
        self.assertEqual(k.get_lines(1),
 
492
        self.assertEqual(k.get(1),
504
493
                         ["first line",
505
494
                          "second line"])
506
495
 
507
 
        self.assertEqual(k.get_lines(0),
 
496
        self.assertEqual(k.get(0),
508
497
                         ["first line"])
509
498
 
510
499
 
512
501
    """Weave with two diverged texts based on version 0.
513
502
    """
514
503
    def runTest(self):
515
 
        # FIXME make the weave, dont poke at it.
516
504
        k = Weave()
517
505
 
518
 
        k._names = ['0', '1', '2']
519
 
        k._name_map = {'0':0, '1':1, '2':2}
520
506
        k._parents = [frozenset(),
521
507
                frozenset([0]),
522
508
                frozenset([0]),
532
518
                ('}', 2),                
533
519
                ]
534
520
 
535
 
        k._sha1s = [sha_string('first line')
536
 
                  , sha_string('first linesecond line')
537
 
                  , sha_string('first linealternative second line')]
538
 
 
539
 
        self.assertEqual(k.get_lines(0),
 
521
        self.assertEqual(k.get(0),
540
522
                         ["first line"])
541
523
 
542
 
        self.assertEqual(k.get_lines(1),
 
524
        self.assertEqual(k.get(1),
543
525
                         ["first line",
544
526
                          "second line"])
545
527
 
546
 
        self.assertEqual(k.get_lines('2'),
 
528
        self.assertEqual(k.get(2),
547
529
                         ["first line",
548
530
                          "alternative second line"])
549
531
 
550
 
        self.assertEqual(list(k.get_ancestry(['2'])),
551
 
                         ['0', '2'])
 
532
        self.assertEqual(list(k.inclusions([2])),
 
533
                         [0, 2])
 
534
 
552
535
 
553
536
 
554
537
class ReplaceLine(TestBase):
558
541
        text0 = ['cheddar', 'stilton', 'gruyere']
559
542
        text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
560
543
        
561
 
        k.add_lines('text0', [], text0)
562
 
        k.add_lines('text1', ['text0'], text1)
 
544
        k.add('text0', [], text0)
 
545
        k.add('text1', [0], text1)
563
546
 
564
547
        self.log('k._weave=' + pformat(k._weave))
565
548
 
566
 
        self.assertEqual(k.get_lines(0), text0)
567
 
        self.assertEqual(k.get_lines(1), text1)
 
549
        self.assertEqual(k.get(0), text0)
 
550
        self.assertEqual(k.get(1), text1)
 
551
 
568
552
 
569
553
 
570
554
class Merge(TestBase):
578
562
                 ['header', '', 'line from 1', 'fixup line', 'line from 2'],
579
563
                 ]
580
564
 
581
 
        k.add_lines('text0', [], texts[0])
582
 
        k.add_lines('text1', ['text0'], texts[1])
583
 
        k.add_lines('text2', ['text0'], texts[2])
584
 
        k.add_lines('merge', ['text0', 'text1', 'text2'], texts[3])
 
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])
585
569
 
586
570
        for i, t in enumerate(texts):
587
 
            self.assertEqual(k.get_lines(i), t)
 
571
            self.assertEqual(k.get(i), t)
588
572
 
589
 
        self.assertEqual(k.annotate('merge'),
590
 
                         [('text0', 'header'),
591
 
                          ('text1', ''),
592
 
                          ('text1', 'line from 1'),
593
 
                          ('merge', 'fixup line'),
594
 
                          ('text2', 'line from 2'),
 
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'),
595
579
                          ])
596
580
 
597
 
        self.assertEqual(list(k.get_ancestry(['merge'])),
598
 
                         ['text0', 'text1', 'text2', 'merge'])
 
581
        self.assertEqual(list(k.inclusions([3])),
 
582
                         [0, 1, 2, 3])
599
583
 
600
584
        self.log('k._weave=' + pformat(k._weave))
601
585
 
612
596
        return  # NOT RUN
613
597
        k = Weave()
614
598
 
615
 
        k.add_lines([], ['aaa', 'bbb'])
616
 
        k.add_lines([0], ['aaa', '111', 'bbb'])
617
 
        k.add_lines([1], ['aaa', '222', 'bbb'])
 
599
        k.add([], ['aaa', 'bbb'])
 
600
        k.add([0], ['aaa', '111', 'bbb'])
 
601
        k.add([1], ['aaa', '222', 'bbb'])
618
602
 
619
603
        merged = k.merge([1, 2])
620
604
 
623
607
                           [['bbb']]])
624
608
 
625
609
 
 
610
 
626
611
class NonConflict(TestBase):
627
612
    """Two descendants insert compatible changes.
628
613
 
631
616
        return  # NOT RUN
632
617
        k = Weave()
633
618
 
634
 
        k.add_lines([], ['aaa', 'bbb'])
635
 
        k.add_lines([0], ['111', 'aaa', 'ccc', 'bbb'])
636
 
        k.add_lines([1], ['aaa', 'ccc', 'bbb', '222'])
 
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
        
637
650
 
638
651
 
639
652
class Khayyam(TestBase):
640
653
    """Test changes to multi-line texts, and read/write"""
641
 
 
642
 
    def test_multi_line_merge(self):
 
654
    def runTest(self):
643
655
        rawtexts = [
644
656
            """A Book of Verses underneath the Bough,
645
657
            A Jug of Wine, a Loaf of Bread, -- and Thou
671
683
        parents = set()
672
684
        i = 0
673
685
        for t in texts:
674
 
            ver = k.add_lines('text%d' % i,
 
686
            ver = k.add('text%d' % i,
675
687
                        list(parents), t)
676
 
            parents.add('text%d' % i)
 
688
            parents.add(ver)
677
689
            i += 1
678
690
 
679
691
        self.log("k._weave=" + pformat(k._weave))
680
692
 
681
693
        for i, t in enumerate(texts):
682
 
            self.assertEqual(k.get_lines(i), t)
 
694
            self.assertEqual(k.get(i), t)
683
695
 
684
696
        self.check_read_write(k)
685
697
 
686
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
 
687
797
class JoinWeavesTests(TestBase):
688
798
    def setUp(self):
689
799
        super(JoinWeavesTests, self).setUp()
690
800
        self.weave1 = Weave()
691
801
        self.lines1 = ['hello\n']
692
802
        self.lines3 = ['hello\n', 'cruel\n', 'world\n']
693
 
        self.weave1.add_lines('v1', [], self.lines1)
694
 
        self.weave1.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
695
 
        self.weave1.add_lines('v3', ['v2'], self.lines3)
 
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)
696
806
        
697
807
    def test_join_empty(self):
698
808
        """Join two empty weaves."""
700
810
        w1 = Weave()
701
811
        w2 = Weave()
702
812
        w1.join(w2)
703
 
        eq(len(w1), 0)
 
813
        eq(w1.numversions(), 0)
704
814
        
705
815
    def test_join_empty_to_nonempty(self):
706
816
        """Join empty weave onto nonempty."""
710
820
    def test_join_unrelated(self):
711
821
        """Join two weaves with no history in common."""
712
822
        wb = Weave()
713
 
        wb.add_lines('b1', [], ['line from b\n'])
 
823
        wb.add('b1', [], ['line from b\n'])
714
824
        w1 = self.weave1
715
825
        w1.join(wb)
716
826
        eq = self.assertEqual
717
827
        eq(len(w1), 4)
718
 
        eq(sorted(w1.versions()),
 
828
        eq(sorted(list(w1.iter_names())),
719
829
           ['b1', 'v1', 'v2', 'v3'])
720
830
 
721
831
    def test_join_related(self):
722
832
        wa = self.weave1.copy()
723
833
        wb = self.weave1.copy()
724
 
        wa.add_lines('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
725
 
        wb.add_lines('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
 
834
        wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
 
835
        wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
726
836
        eq = self.assertEquals
727
837
        eq(len(wa), 4)
728
838
        eq(len(wb), 4)
732
842
           ['hello\n', 'pale blue\n', 'world\n'])
733
843
 
734
844
    def test_join_parent_disagreement(self):
735
 
        #join reconciles differening parents into a union.
 
845
        """Cannot join weaves with different parents for a version."""
736
846
        wa = Weave()
737
847
        wb = Weave()
738
 
        wa.add_lines('v1', [], ['hello\n'])
739
 
        wb.add_lines('v0', [], [])
740
 
        wb.add_lines('v1', ['v0'], ['hello\n'])
741
 
        wa.join(wb)
742
 
        self.assertEqual(['v0'], wa.get_parents('v1'))
 
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)
743
853
 
744
854
    def test_join_text_disagreement(self):
745
855
        """Cannot join weaves with different texts for a version."""
746
856
        wa = Weave()
747
857
        wb = Weave()
748
 
        wa.add_lines('v1', [], ['hello\n'])
749
 
        wb.add_lines('v1', [], ['not\n', 'hello\n'])
 
858
        wa.add('v1', [], ['hello\n'])
 
859
        wb.add('v1', [], ['not\n', 'hello\n'])
750
860
        self.assertRaises(WeaveError,
751
861
                          wa.join, wb)
752
862
 
756
866
        The source weave contains a different version at index 0."""
757
867
        wa = self.weave1.copy()
758
868
        wb = Weave()
759
 
        wb.add_lines('x1', [], ['line from x1\n'])
760
 
        wb.add_lines('v1', [], ['hello\n'])
761
 
        wb.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
 
869
        wb.add('x1', [], ['line from x1\n'])
 
870
        wb.add('v1', [], ['hello\n'])
 
871
        wb.add('v2', ['v1'], ['hello\n', 'world\n'])
762
872
        wa.join(wb)
763
873
        eq = self.assertEquals
764
 
        eq(sorted(wa.versions()), ['v1', 'v2', 'v3', 'x1',])
 
874
        eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
765
875
        eq(wa.get_text('x1'), 'line from x1\n')
766
876
 
767
 
    def test_written_detection(self):
768
 
        # Test detection of weave file corruption.
769
 
        #
770
 
        # Make sure that we can detect if a weave file has
771
 
        # been corrupted. This doesn't test all forms of corruption,
772
 
        # but it at least helps verify the data you get, is what you want.
773
 
        from cStringIO import StringIO
774
 
 
775
 
        w = Weave()
776
 
        w.add_lines('v1', [], ['hello\n'])
777
 
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
778
 
 
779
 
        tmpf = StringIO()
780
 
        write_weave(w, tmpf)
781
 
 
782
 
        # Because we are corrupting, we need to make sure we have the exact text
783
 
        self.assertEquals('# bzr weave file v5\n'
784
 
                          'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
785
 
                          'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
786
 
                          'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
787
 
                          tmpf.getvalue())
788
 
 
789
 
        # Change a single letter
790
 
        tmpf = StringIO('# bzr weave file v5\n'
791
 
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
792
 
                        'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
793
 
                        'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
794
 
 
795
 
        w = read_weave(tmpf)
796
 
 
797
 
        self.assertEqual('hello\n', w.get_text('v1'))
798
 
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
799
 
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
800
 
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
801
 
 
802
 
        # Change the sha checksum
803
 
        tmpf = StringIO('# bzr weave file v5\n'
804
 
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
805
 
                        'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
806
 
                        'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
807
 
 
808
 
        w = read_weave(tmpf)
809
 
 
810
 
        self.assertEqual('hello\n', w.get_text('v1'))
811
 
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
812
 
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
813
 
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
814
 
 
815
 
 
816
 
class InstrumentedWeave(Weave):
817
 
    """Keep track of how many times functions are called."""
818
 
    
819
 
    def __init__(self, weave_name=None):
820
 
        self._extract_count = 0
821
 
        Weave.__init__(self, weave_name=weave_name)
822
 
 
823
 
    def _extract(self, versions):
824
 
        self._extract_count += 1
825
 
        return Weave._extract(self, versions)
826
 
 
827
 
 
828
 
class JoinOptimization(TestCase):
829
 
    """Test that Weave.join() doesn't extract all texts, only what must be done."""
830
 
 
831
 
    def test_join(self):
832
 
        w1 = InstrumentedWeave()
833
 
        w2 = InstrumentedWeave()
834
 
 
835
 
        txt0 = ['a\n']
836
 
        txt1 = ['a\n', 'b\n']
837
 
        txt2 = ['a\n', 'c\n']
838
 
        txt3 = ['a\n', 'b\n', 'c\n']
839
 
 
840
 
        w1.add_lines('txt0', [], txt0) # extract 1a
841
 
        w2.add_lines('txt0', [], txt0) # extract 1b
842
 
        w1.add_lines('txt1', ['txt0'], txt1)# extract 2a
843
 
        w2.add_lines('txt2', ['txt0'], txt2)# extract 2b
844
 
        w1.join(w2) # extract 3a to add txt2 
845
 
        w2.join(w1) # extract 3b to add txt1 
846
 
 
847
 
        w1.add_lines('txt3', ['txt1', 'txt2'], txt3) # extract 4a 
848
 
        w2.add_lines('txt3', ['txt2', 'txt1'], txt3) # extract 4b
849
 
        # These secretly have inverted parents
850
 
 
851
 
        # This should not have to do any extractions
852
 
        w1.join(w2) # NO extract, texts already present with same parents
853
 
        w2.join(w1) # NO extract, texts already present with same parents
854
 
 
855
 
        self.assertEqual(4, w1._extract_count)
856
 
        self.assertEqual(4, w2._extract_count)
857
 
 
858
 
    def test_double_parent(self):
859
 
        # It should not be considered illegal to add
860
 
        # a revision with the same parent twice
861
 
        w1 = InstrumentedWeave()
862
 
        w2 = InstrumentedWeave()
863
 
 
864
 
        txt0 = ['a\n']
865
 
        txt1 = ['a\n', 'b\n']
866
 
        txt2 = ['a\n', 'c\n']
867
 
        txt3 = ['a\n', 'b\n', 'c\n']
868
 
 
869
 
        w1.add_lines('txt0', [], txt0)
870
 
        w2.add_lines('txt0', [], txt0)
871
 
        w1.add_lines('txt1', ['txt0'], txt1)
872
 
        w2.add_lines('txt1', ['txt0', 'txt0'], txt1)
873
 
        # Same text, effectively the same, because the
874
 
        # parent is only repeated
875
 
        w1.join(w2) # extract 3a to add txt2 
876
 
        w2.join(w1) # extract 3b to add txt1 
877
 
 
878
 
 
879
 
class TestNeedsReweave(TestCase):
880
 
    """Internal corner cases for when reweave is needed."""
881
 
 
882
 
    def test_compatible_parents(self):
883
 
        w1 = Weave('a')
884
 
        my_parents = set([1, 2, 3])
885
 
        # subsets are ok
886
 
        self.assertTrue(w1._compatible_parents(my_parents, set([3])))
887
 
        # same sets
888
 
        self.assertTrue(w1._compatible_parents(my_parents, set(my_parents)))
889
 
        # same empty corner case
890
 
        self.assertTrue(w1._compatible_parents(set(), set()))
891
 
        # other cannot contain stuff my_parents does not
892
 
        self.assertFalse(w1._compatible_parents(set(), set([1])))
893
 
        self.assertFalse(w1._compatible_parents(my_parents, set([1, 2, 3, 4])))
894
 
        self.assertFalse(w1._compatible_parents(my_parents, set([4])))
895
 
 
896
 
 
897
 
class TestWeaveFile(TestCaseInTempDir):
898
 
    
899
 
    def test_empty_file(self):
900
 
        f = open('empty.weave', 'wb+')
901
 
        try:
902
 
            self.assertRaises(errors.WeaveFormatError,
903
 
                              read_weave, f)
904
 
        finally:
905
 
            f.close()
 
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