~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_weave.py

  • Committer: Robert Collins
  • Date: 2006-03-01 03:26:23 UTC
  • mto: (1594.2.4 integration)
  • mto: This revision was merged to the branch mainline in revision 1596.
  • Revision ID: robertc@robertcollins.net-20060301032623-9d3c073e102f2239
Move WeaveStore down into bzrlib.store.versioned.weave.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
 
19
19
 
20
 
 
 
20
# TODO: tests regarding version names
 
21
# TODO: rbc 20050108 test that join does not leave an inconsistent weave 
 
22
#       if it fails.
21
23
 
22
24
"""test suite for weave algorithm"""
23
25
 
 
26
from pprint import pformat
24
27
 
25
 
import testsweet
26
 
from bzrlib.weave import Weave, WeaveFormatError
 
28
import bzrlib.errors as errors
 
29
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
27
30
from bzrlib.weavefile import write_weave, read_weave
28
 
from pprint import pformat
29
 
 
30
 
 
31
 
try:
32
 
    set
33
 
    frozenset
34
 
except NameError:
35
 
    from sets import Set, ImmutableSet
36
 
    set = Set
37
 
    frozenset = ImmutableSet
38
 
    del Set, ImmutableSet
39
 
 
 
31
from bzrlib.tests import TestCase
 
32
from bzrlib.osutils import sha_string
40
33
 
41
34
 
42
35
# texts for use in testing
45
38
          "A second line"]
46
39
 
47
40
 
48
 
 
49
 
class TestBase(testsweet.TestBase):
 
41
class TestBase(TestCase):
50
42
    def check_read_write(self, k):
51
43
        """Check the weave k can be written & re-read."""
52
44
        from tempfile import TemporaryFile
60
52
            tf.seek(0)
61
53
            self.log('serialized weave:')
62
54
            self.log(tf.read())
 
55
 
 
56
            self.log('')
 
57
            self.log('parents: %s' % (k._parents == k2._parents))
 
58
            self.log('         %r' % k._parents)
 
59
            self.log('         %r' % k2._parents)
 
60
            self.log('')
63
61
            self.fail('read/write check failed')
64
 
        
65
 
        
 
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('foo', [], TEXT_1)
 
70
        self.assertTrue('foo' in k)
66
71
 
67
72
 
68
73
class Easy(TestBase):
74
79
    """Store and retrieve a simple text."""
75
80
    def runTest(self):
76
81
        k = Weave()
77
 
        idx = k.add([], TEXT_0)
 
82
        idx = k.add('text0', [], TEXT_0)
78
83
        self.assertEqual(k.get(idx), TEXT_0)
79
84
        self.assertEqual(idx, 0)
80
85
 
81
86
 
82
 
 
83
87
class AnnotateOne(TestBase):
84
88
    def runTest(self):
85
89
        k = Weave()
86
 
        k.add([], TEXT_0)
 
90
        k.add('text0', [], TEXT_0)
87
91
        self.assertEqual(k.annotate(0),
88
92
                         [(0, TEXT_0[0])])
89
93
 
92
96
    def runTest(self):
93
97
        k = Weave()
94
98
 
95
 
        idx = k.add([], TEXT_0)
 
99
        idx = k.add('text0', [], TEXT_0)
96
100
        self.assertEqual(idx, 0)
97
101
 
98
 
        idx = k.add([], TEXT_1)
 
102
        idx = k.add('text1', [], TEXT_1)
99
103
        self.assertEqual(idx, 1)
100
104
 
101
105
        self.assertEqual(k.get(0), TEXT_0)
102
106
        self.assertEqual(k.get(1), TEXT_1)
103
107
 
104
 
        k.dump(self.TEST_LOG)
105
 
 
106
 
 
 
108
 
 
109
class AddWithGivenSha(TestBase):
 
110
    def runTest(self):
 
111
        """Add with caller-supplied SHA-1"""
 
112
        k = Weave()
 
113
 
 
114
        t = 'text0'
 
115
        k.add('text0', [], [t], sha1=sha_string(t))
 
116
 
 
117
 
 
118
class GetSha1(TestBase):
 
119
    def test_get_sha1(self):
 
120
        k = Weave()
 
121
        k.add('text0', [], 'text0')
 
122
        self.assertEqual('34dc0e430c642a26c3dd1c2beb7a8b4f4445eb79',
 
123
                         k.get_sha1('text0'))
 
124
        self.assertRaises(errors.RevisionNotPresent,
 
125
                          k.get_sha1, 0)
 
126
        self.assertRaises(errors.RevisionNotPresent,
 
127
                          k.get_sha1, 'text1')
 
128
                        
107
129
 
108
130
class InvalidAdd(TestBase):
109
131
    """Try to use invalid version number during add."""
112
134
 
113
135
        self.assertRaises(IndexError,
114
136
                          k.add,
 
137
                          'text0',
115
138
                          [69],
116
139
                          ['new text!'])
117
140
 
118
141
 
 
142
class RepeatedAdd(TestBase):
 
143
    """Add the same version twice; harmless."""
 
144
    def runTest(self):
 
145
        k = Weave()
 
146
        idx = k.add('text0', [], TEXT_0)
 
147
        idx2 = k.add('text0', [], TEXT_0)
 
148
        self.assertEqual(idx, idx2)
 
149
 
 
150
 
 
151
class InvalidRepeatedAdd(TestBase):
 
152
    def runTest(self):
 
153
        k = Weave()
 
154
        idx = k.add('text0', [], TEXT_0)
 
155
        self.assertRaises(errors.RevisionAlreadyPresent,
 
156
                          k.add,
 
157
                          'text0',
 
158
                          [],
 
159
                          ['not the same text'])
 
160
        self.assertRaises(errors.RevisionAlreadyPresent,
 
161
                          k.add,
 
162
                          'text0',
 
163
                          [12],         # not the right parents
 
164
                          TEXT_0)
 
165
        
 
166
 
119
167
class InsertLines(TestBase):
120
168
    """Store a revision that adds one line to the original.
121
169
 
124
172
    def runTest(self):
125
173
        k = Weave()
126
174
 
127
 
        k.add([], ['line 1'])
128
 
        k.add([0], ['line 1', 'line 2'])
 
175
        k.add('text0', [], ['line 1'])
 
176
        k.add('text1', [0], ['line 1', 'line 2'])
129
177
 
130
178
        self.assertEqual(k.annotate(0),
131
179
                         [(0, 'line 1')])
138
186
                         [(0, 'line 1'),
139
187
                          (1, 'line 2')])
140
188
 
141
 
        k.add([0], ['line 1', 'diverged line'])
 
189
        k.add('text2', [0], ['line 1', 'diverged line'])
142
190
 
143
191
        self.assertEqual(k.annotate(2),
144
192
                         [(0, 'line 1'),
145
193
                          (2, 'diverged line')])
146
194
 
147
195
        text3 = ['line 1', 'middle line', 'line 2']
148
 
        k.add([0, 1],
 
196
        k.add('text3',
 
197
              [0, 1],
149
198
              text3)
150
199
 
151
200
        # self.log("changes to text3: " + pformat(list(k._delta(set([0, 1]), text3))))
158
207
                          (1, 'line 2')])
159
208
 
160
209
        # now multiple insertions at different places
161
 
        k.add([0, 1, 3],
 
210
        k.add('text4',
 
211
              [0, 1, 3],
162
212
              ['line 1', 'aaa', 'middle line', 'bbb', 'line 2', 'ccc'])
163
213
 
164
214
        self.assertEqual(k.annotate(4), 
170
220
                          (4, 'ccc')])
171
221
 
172
222
 
173
 
 
174
223
class DeleteLines(TestBase):
175
224
    """Deletion of lines from existing text.
176
225
 
180
229
 
181
230
        base_text = ['one', 'two', 'three', 'four']
182
231
 
183
 
        k.add([], base_text)
 
232
        k.add('text0', [], base_text)
184
233
        
185
234
        texts = [['one', 'two', 'three'],
186
235
                 ['two', 'three', 'four'],
188
237
                 ['one', 'two', 'three', 'four'],
189
238
                 ]
190
239
 
 
240
        i = 1
191
241
        for t in texts:
192
 
            ver = k.add([0], t)
 
242
            ver = k.add('text%d' % i,
 
243
                        [0], t)
 
244
            i += 1
193
245
 
194
246
        self.log('final weave:')
195
247
        self.log('k._weave=' + pformat(k._weave))
197
249
        for i in range(len(texts)):
198
250
            self.assertEqual(k.get(i+1),
199
251
                             texts[i])
200
 
            
201
 
 
202
252
 
203
253
 
204
254
class SuicideDelete(TestBase):
224
274
                          0)        
225
275
 
226
276
 
227
 
 
228
277
class CannedDelete(TestBase):
229
278
    """Unpack canned weave with deleted lines."""
230
279
    def runTest(self):
241
290
                'last line',
242
291
                ('}', 0),
243
292
                ]
 
293
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
294
                  , sha_string('first linelast line')]
244
295
 
245
296
        self.assertEqual(k.get(0),
246
297
                         ['first line',
254
305
                          ])
255
306
 
256
307
 
257
 
 
258
308
class CannedReplacement(TestBase):
259
309
    """Unpack canned weave with deleted lines."""
260
310
    def runTest(self):
274
324
                'last line',
275
325
                ('}', 0),
276
326
                ]
 
327
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
328
                  , sha_string('first linereplacement linelast line')]
277
329
 
278
330
        self.assertEqual(k.get(0),
279
331
                         ['first line',
288
340
                          ])
289
341
 
290
342
 
291
 
 
292
343
class BadWeave(TestBase):
293
344
    """Test that we trap an insert which should not occur."""
294
345
    def runTest(self):
374
425
                '}',
375
426
                ('}', 0)]
376
427
 
 
428
        k._sha1s = [sha_string('foo {}')
 
429
                  , sha_string('foo {  added in version 1  also from v1}')
 
430
                  , sha_string('foo {  added in v2}')
 
431
                  , sha_string('foo {  added in version 1  added in v2  also from v1}')
 
432
                  ]
 
433
 
377
434
        self.assertEqual(k.get(0),
378
435
                         ['foo {',
379
436
                          '}'])
397
454
                          '}'])
398
455
                         
399
456
 
400
 
 
401
457
class DeleteLines2(TestBase):
402
458
    """Test recording revisions that delete lines.
403
459
 
406
462
    def runTest(self):
407
463
        k = Weave()
408
464
 
409
 
        k.add([], ["line the first",
 
465
        k.add('text0', [], ["line the first",
410
466
                   "line 2",
411
467
                   "line 3",
412
468
                   "fine"])
413
469
 
414
470
        self.assertEqual(len(k.get(0)), 4)
415
471
 
416
 
        k.add([0], ["line the first",
 
472
        k.add('text1', [0], ["line the first",
417
473
                   "fine"])
418
474
 
419
475
        self.assertEqual(k.get(1),
425
481
                          (0, "fine")])
426
482
 
427
483
 
428
 
 
429
484
class IncludeVersions(TestBase):
430
485
    """Check texts that are stored across multiple revisions.
431
486
 
447
502
                "second line",
448
503
                ('}', 1)]
449
504
 
 
505
        k._sha1s = [sha_string('first line')
 
506
                  , sha_string('first linesecond line')]
 
507
 
450
508
        self.assertEqual(k.get(1),
451
509
                         ["first line",
452
510
                          "second line"])
454
512
        self.assertEqual(k.get(0),
455
513
                         ["first line"])
456
514
 
457
 
        k.dump(self.TEST_LOG)
458
 
 
459
515
 
460
516
class DivergedIncludes(TestBase):
461
517
    """Weave with two diverged texts based on version 0.
462
518
    """
463
519
    def runTest(self):
 
520
        # FIXME make the weave, dont poke at it.
464
521
        k = Weave()
465
522
 
 
523
        k._names = ['0', '1', '2']
466
524
        k._parents = [frozenset(),
467
525
                frozenset([0]),
468
526
                frozenset([0]),
478
536
                ('}', 2),                
479
537
                ]
480
538
 
 
539
        k._sha1s = [sha_string('first line')
 
540
                  , sha_string('first linesecond line')
 
541
                  , sha_string('first linealternative second line')]
 
542
 
481
543
        self.assertEqual(k.get(0),
482
544
                         ["first line"])
483
545
 
490
552
                          "alternative second line"])
491
553
 
492
554
        self.assertEqual(list(k.inclusions([2])),
493
 
                         [0, 2])
494
 
 
 
555
                         ['0', '2'])
495
556
 
496
557
 
497
558
class ReplaceLine(TestBase):
501
562
        text0 = ['cheddar', 'stilton', 'gruyere']
502
563
        text1 = ['cheddar', 'blue vein', 'neufchatel', 'chevre']
503
564
        
504
 
        k.add([], text0)
505
 
        k.add([0], text1)
 
565
        k.add('text0', [], text0)
 
566
        k.add('text1', [0], text1)
506
567
 
507
568
        self.log('k._weave=' + pformat(k._weave))
508
569
 
510
571
        self.assertEqual(k.get(1), text1)
511
572
 
512
573
 
513
 
 
514
574
class Merge(TestBase):
515
575
    """Storage of versions that merge diverged parents"""
516
576
    def runTest(self):
522
582
                 ['header', '', 'line from 1', 'fixup line', 'line from 2'],
523
583
                 ]
524
584
 
525
 
        k.add([], texts[0])
526
 
        k.add([0], texts[1])
527
 
        k.add([0], texts[2])
528
 
        k.add([0, 1, 2], texts[3])
 
585
        k.add('text0', [], texts[0])
 
586
        k.add('text1', [0], texts[1])
 
587
        k.add('text2', [0], texts[2])
 
588
        k.add('merge', [0, 1, 2], texts[3])
529
589
 
530
590
        for i, t in enumerate(texts):
531
591
            self.assertEqual(k.get(i), t)
539
599
                          ])
540
600
 
541
601
        self.assertEqual(list(k.inclusions([3])),
542
 
                         [0, 1, 2, 3])
 
602
                         ['text0', 'text1', 'text2', 'merge'])
543
603
 
544
604
        self.log('k._weave=' + pformat(k._weave))
545
605
 
567
627
                           [['bbb']]])
568
628
 
569
629
 
570
 
 
571
630
class NonConflict(TestBase):
572
631
    """Two descendants insert compatible changes.
573
632
 
580
639
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
581
640
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
582
641
 
583
 
    
584
 
    
585
 
 
586
 
 
587
 
class AutoMerge(TestBase):
588
 
    def runTest(self):
589
 
        k = Weave()
590
 
 
591
 
        texts = [['header', 'aaa', 'bbb'],
592
 
                 ['header', 'aaa', 'line from 1', 'bbb'],
593
 
                 ['header', 'aaa', 'bbb', 'line from 2', 'more from 2'],
594
 
                 ]
595
 
 
596
 
        k.add([], texts[0])
597
 
        k.add([0], texts[1])
598
 
        k.add([0], texts[2])
599
 
 
600
 
        self.log('k._weave=' + pformat(k._weave))
601
 
 
602
 
        m = list(k.mash_iter([0, 1, 2]))
603
 
 
604
 
        self.assertEqual(m,
605
 
                         ['header', 'aaa',
606
 
                          'line from 1',
607
 
                          'bbb',
608
 
                          'line from 2', 'more from 2'])
609
 
        
610
 
 
611
642
 
612
643
class Khayyam(TestBase):
613
644
    """Test changes to multi-line texts, and read/write"""
614
 
    def runTest(self):
 
645
 
 
646
    def test_multi_line_merge(self):
615
647
        rawtexts = [
616
648
            """A Book of Verses underneath the Bough,
617
649
            A Jug of Wine, a Loaf of Bread, -- and Thou
641
673
 
642
674
        k = Weave()
643
675
        parents = set()
 
676
        i = 0
644
677
        for t in texts:
645
 
            ver = k.add(list(parents), t)
 
678
            ver = k.add('text%d' % i,
 
679
                        list(parents), t)
646
680
            parents.add(ver)
 
681
            i += 1
647
682
 
648
683
        self.log("k._weave=" + pformat(k._weave))
649
684
 
653
688
        self.check_read_write(k)
654
689
 
655
690
 
656
 
 
657
691
class MergeCases(TestBase):
658
692
    def doMerge(self, base, a, b, mp):
659
693
        from cStringIO import StringIO
663
697
            return x + '\n'
664
698
        
665
699
        w = Weave()
666
 
        w.add([], map(addcrlf, base))
667
 
        w.add([0], map(addcrlf, a))
668
 
        w.add([0], map(addcrlf, b))
 
700
        w.add('text0', [], map(addcrlf, base))
 
701
        w.add('text1', [0], map(addcrlf, a))
 
702
        w.add('text2', [0], map(addcrlf, b))
669
703
 
670
704
        self.log('weave is:')
671
705
        tmpf = StringIO()
710
744
        self.doMerge(['aaa', 'bbb'],
711
745
                     ['aaa', 'xxx', 'yyy', 'bbb'],
712
746
                     ['aaa', 'xxx', 'bbb'],
713
 
                     ['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
 
747
                     ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 'xxx', 
 
748
                      '>>>>>>> ', 'bbb'])
714
749
 
715
750
        # really it ought to reduce this to 
716
751
        # ['aaa', 'xxx', 'yyy', 'bbb']
720
755
        self.doMerge(['aaa'],
721
756
                     ['xxx'],
722
757
                     ['yyy', 'zzz'],
723
 
                     ['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
 
758
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
759
                      '>>>>>>> '])
724
760
 
725
761
    def testNonClashInsert(self):
726
762
        self.doMerge(['aaa'],
727
763
                     ['xxx', 'aaa'],
728
764
                     ['yyy', 'zzz'],
729
 
                     ['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
 
765
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
766
                      '>>>>>>> '])
730
767
 
731
768
        self.doMerge(['aaa'],
732
769
                     ['aaa'],
748
785
        self.doMerge(['aaa', 'bbb', 'ccc'],
749
786
                     ['aaa', 'ddd', 'ccc'],
750
787
                     ['aaa', 'ccc'],
751
 
                     ['<<<<', 'aaa', '====', '>>>>', 'ccc'])
752
 
    
753
 
 
754
 
 
755
 
def testweave():
756
 
    import testsweet
757
 
    from unittest import TestSuite, TestLoader
758
 
    import testweave
759
 
 
760
 
    tl = TestLoader()
761
 
    suite = TestSuite()
762
 
    suite.addTest(tl.loadTestsFromModule(testweave))
763
 
    
764
 
    return int(not testsweet.run_suite(suite)) # for shell 0=true
765
 
 
766
 
 
767
 
if __name__ == '__main__':
768
 
    import sys
769
 
    sys.exit(testweave())
770
 
    
 
788
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
 
789
 
 
790
 
 
791
class JoinWeavesTests(TestBase):
 
792
    def setUp(self):
 
793
        super(JoinWeavesTests, self).setUp()
 
794
        self.weave1 = Weave()
 
795
        self.lines1 = ['hello\n']
 
796
        self.lines3 = ['hello\n', 'cruel\n', 'world\n']
 
797
        self.weave1.add('v1', [], self.lines1)
 
798
        self.weave1.add('v2', [0], ['hello\n', 'world\n'])
 
799
        self.weave1.add('v3', [1], self.lines3)
 
800
        
 
801
    def test_join_empty(self):
 
802
        """Join two empty weaves."""
 
803
        eq = self.assertEqual
 
804
        w1 = Weave()
 
805
        w2 = Weave()
 
806
        w1.join(w2)
 
807
        eq(w1.numversions(), 0)
 
808
        
 
809
    def test_join_empty_to_nonempty(self):
 
810
        """Join empty weave onto nonempty."""
 
811
        self.weave1.join(Weave())
 
812
        self.assertEqual(len(self.weave1), 3)
 
813
 
 
814
    def test_join_unrelated(self):
 
815
        """Join two weaves with no history in common."""
 
816
        wb = Weave()
 
817
        wb.add('b1', [], ['line from b\n'])
 
818
        w1 = self.weave1
 
819
        w1.join(wb)
 
820
        eq = self.assertEqual
 
821
        eq(len(w1), 4)
 
822
        eq(sorted(list(w1.iter_names())),
 
823
           ['b1', 'v1', 'v2', 'v3'])
 
824
 
 
825
    def test_join_related(self):
 
826
        wa = self.weave1.copy()
 
827
        wb = self.weave1.copy()
 
828
        wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
 
829
        wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
 
830
        eq = self.assertEquals
 
831
        eq(len(wa), 4)
 
832
        eq(len(wb), 4)
 
833
        wa.join(wb)
 
834
        eq(len(wa), 5)
 
835
        eq(wa.get_lines('b1'),
 
836
           ['hello\n', 'pale blue\n', 'world\n'])
 
837
 
 
838
    def test_join_parent_disagreement(self):
 
839
        """Cannot join weaves with different parents for a version."""
 
840
        wa = Weave()
 
841
        wb = Weave()
 
842
        wa.add('v1', [], ['hello\n'])
 
843
        wb.add('v0', [], [])
 
844
        wb.add('v1', ['v0'], ['hello\n'])
 
845
        self.assertRaises(WeaveError,
 
846
                          wa.join, wb)
 
847
 
 
848
    def test_join_text_disagreement(self):
 
849
        """Cannot join weaves with different texts for a version."""
 
850
        wa = Weave()
 
851
        wb = Weave()
 
852
        wa.add('v1', [], ['hello\n'])
 
853
        wb.add('v1', [], ['not\n', 'hello\n'])
 
854
        self.assertRaises(WeaveError,
 
855
                          wa.join, wb)
 
856
 
 
857
    def test_join_unordered(self):
 
858
        """Join weaves where indexes differ.
 
859
        
 
860
        The source weave contains a different version at index 0."""
 
861
        wa = self.weave1.copy()
 
862
        wb = Weave()
 
863
        wb.add('x1', [], ['line from x1\n'])
 
864
        wb.add('v1', [], ['hello\n'])
 
865
        wb.add('v2', ['v1'], ['hello\n', 'world\n'])
 
866
        wa.join(wb)
 
867
        eq = self.assertEquals
 
868
        eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
 
869
        eq(wa.get_text('x1'), 'line from x1\n')
 
870
 
 
871
    def test_written_detection(self):
 
872
        # Test detection of weave file corruption.
 
873
        #
 
874
        # Make sure that we can detect if a weave file has
 
875
        # been corrupted. This doesn't test all forms of corruption,
 
876
        # but it at least helps verify the data you get, is what you want.
 
877
        from cStringIO import StringIO
 
878
 
 
879
        w = Weave()
 
880
        w.add('v1', [], ['hello\n'])
 
881
        w.add('v2', ['v1'], ['hello\n', 'there\n'])
 
882
 
 
883
        tmpf = StringIO()
 
884
        write_weave(w, tmpf)
 
885
 
 
886
        # Because we are corrupting, we need to make sure we have the exact text
 
887
        self.assertEquals('# bzr weave file v5\n'
 
888
                          'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
889
                          'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
890
                          'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
 
891
                          tmpf.getvalue())
 
892
 
 
893
        # Change a single letter
 
894
        tmpf = StringIO('# bzr weave file v5\n'
 
895
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
896
                        'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
897
                        'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
 
898
 
 
899
        w = read_weave(tmpf)
 
900
 
 
901
        self.assertEqual('hello\n', w.get_text('v1'))
 
902
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
903
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
904
        self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
 
905
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
906
 
 
907
        # Change the sha checksum
 
908
        tmpf = StringIO('# bzr weave file v5\n'
 
909
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
910
                        'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
911
                        'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
 
912
 
 
913
        w = read_weave(tmpf)
 
914
 
 
915
        self.assertEqual('hello\n', w.get_text('v1'))
 
916
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
917
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
918
        self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
 
919
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
920
 
 
921
 
 
922
class InstrumentedWeave(Weave):
 
923
    """Keep track of how many times functions are called."""
 
924
    
 
925
    def __init__(self, weave_name=None):
 
926
        self._extract_count = 0
 
927
        Weave.__init__(self, weave_name=weave_name)
 
928
 
 
929
    def _extract(self, versions):
 
930
        self._extract_count += 1
 
931
        return Weave._extract(self, versions)
 
932
 
 
933
 
 
934
class JoinOptimization(TestCase):
 
935
    """Test that Weave.join() doesn't extract all texts, only what must be done."""
 
936
 
 
937
    def test_join(self):
 
938
        w1 = InstrumentedWeave()
 
939
        w2 = InstrumentedWeave()
 
940
 
 
941
        txt0 = ['a\n']
 
942
        txt1 = ['a\n', 'b\n']
 
943
        txt2 = ['a\n', 'c\n']
 
944
        txt3 = ['a\n', 'b\n', 'c\n']
 
945
 
 
946
        w1.add('txt0', [], txt0) # extract 1a
 
947
        w2.add('txt0', [], txt0) # extract 1b
 
948
        w1.add('txt1', [0], txt1)# extract 2a
 
949
        w2.add('txt2', [0], txt2)# extract 2b
 
950
        w1.join(w2) # extract 3a to add txt2 
 
951
        w2.join(w1) # extract 3b to add txt1 
 
952
 
 
953
        w1.add('txt3', [1, 2], txt3) # extract 4a 
 
954
        w2.add('txt3', [1, 2], txt3) # extract 4b
 
955
        # These secretly have inverted parents
 
956
 
 
957
        # This should not have to do any extractions
 
958
        w1.join(w2) # NO extract, texts already present with same parents
 
959
        w2.join(w1) # NO extract, texts already present with same parents
 
960
 
 
961
        self.assertEqual(4, w1._extract_count)
 
962
        self.assertEqual(4, w2._extract_count)
 
963
 
 
964
    def test_double_parent(self):
 
965
        # It should not be considered illegal to add
 
966
        # a revision with the same parent twice
 
967
        w1 = InstrumentedWeave()
 
968
        w2 = InstrumentedWeave()
 
969
 
 
970
        txt0 = ['a\n']
 
971
        txt1 = ['a\n', 'b\n']
 
972
        txt2 = ['a\n', 'c\n']
 
973
        txt3 = ['a\n', 'b\n', 'c\n']
 
974
 
 
975
        w1.add('txt0', [], txt0)
 
976
        w2.add('txt0', [], txt0)
 
977
        w1.add('txt1', [0], txt1)
 
978
        w2.add('txt1', [0,0], txt1)
 
979
        # Same text, effectively the same, because the
 
980
        # parent is only repeated
 
981
        w1.join(w2) # extract 3a to add txt2 
 
982
        w2.join(w1) # extract 3b to add txt1 
 
983
 
 
984
 
 
985
class MismatchedTexts(TestCase):
 
986
    """Test that merging two weaves with different texts fails."""
 
987
 
 
988
    def test_reweave(self):
 
989
        w1 = Weave('a')
 
990
        w2 = Weave('b')
 
991
 
 
992
        w1.add('txt0', [], ['a\n'])
 
993
        w2.add('txt0', [], ['a\n'])
 
994
        w1.add('txt1', [0], ['a\n', 'b\n'])
 
995
        w2.add('txt1', [0], ['a\n', 'c\n'])
 
996
 
 
997
        self.assertRaises(errors.WeaveTextDiffers, w1.reweave, w2)
 
998
 
 
999