~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_weave.py

[merge] jam-integration

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
import bzrlib.errors as errors
29
29
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
30
30
from bzrlib.weavefile import write_weave, read_weave
31
 
from bzrlib.selftest import TestCase
 
31
from bzrlib.tests import TestCase
32
32
from bzrlib.osutils import sha_string
33
33
 
34
34
 
38
38
          "A second line"]
39
39
 
40
40
 
41
 
 
42
41
class TestBase(TestCase):
43
42
    def check_read_write(self, k):
44
43
        """Check the weave k can be written & re-read."""
59
58
            self.log('         %r' % k._parents)
60
59
            self.log('         %r' % k2._parents)
61
60
            self.log('')
62
 
 
63
 
            
64
61
            self.fail('read/write check failed')
65
 
        
66
 
        
 
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)
67
71
 
68
72
 
69
73
class Easy(TestBase):
80
84
        self.assertEqual(idx, 0)
81
85
 
82
86
 
83
 
 
84
87
class AnnotateOne(TestBase):
85
88
    def runTest(self):
86
89
        k = Weave()
103
106
        self.assertEqual(k.get(1), TEXT_1)
104
107
 
105
108
 
106
 
 
107
109
class AddWithGivenSha(TestBase):
108
110
    def runTest(self):
109
111
        """Add with caller-supplied SHA-1"""
113
115
        k.add('text0', [], [t], sha1=sha_string(t))
114
116
 
115
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.WeaveRevisionNotPresent,
 
125
                          k.get_sha1, 0)
 
126
        self.assertRaises(errors.WeaveRevisionNotPresent,
 
127
                          k.get_sha1, 'text1')
 
128
                        
116
129
 
117
130
class InvalidAdd(TestBase):
118
131
    """Try to use invalid version number during add."""
135
148
        self.assertEqual(idx, idx2)
136
149
 
137
150
 
138
 
 
139
151
class InvalidRepeatedAdd(TestBase):
140
152
    def runTest(self):
141
153
        k = Weave()
152
164
                          TEXT_0)
153
165
        
154
166
 
155
 
 
156
167
class InsertLines(TestBase):
157
168
    """Store a revision that adds one line to the original.
158
169
 
209
220
                          (4, 'ccc')])
210
221
 
211
222
 
212
 
 
213
223
class DeleteLines(TestBase):
214
224
    """Deletion of lines from existing text.
215
225
 
239
249
        for i in range(len(texts)):
240
250
            self.assertEqual(k.get(i+1),
241
251
                             texts[i])
242
 
            
243
 
 
244
252
 
245
253
 
246
254
class SuicideDelete(TestBase):
266
274
                          0)        
267
275
 
268
276
 
269
 
 
270
277
class CannedDelete(TestBase):
271
278
    """Unpack canned weave with deleted lines."""
272
279
    def runTest(self):
283
290
                'last line',
284
291
                ('}', 0),
285
292
                ]
 
293
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
294
                  , sha_string('first linelast line')]
286
295
 
287
296
        self.assertEqual(k.get(0),
288
297
                         ['first line',
296
305
                          ])
297
306
 
298
307
 
299
 
 
300
308
class CannedReplacement(TestBase):
301
309
    """Unpack canned weave with deleted lines."""
302
310
    def runTest(self):
316
324
                'last line',
317
325
                ('}', 0),
318
326
                ]
 
327
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
328
                  , sha_string('first linereplacement linelast line')]
319
329
 
320
330
        self.assertEqual(k.get(0),
321
331
                         ['first line',
330
340
                          ])
331
341
 
332
342
 
333
 
 
334
343
class BadWeave(TestBase):
335
344
    """Test that we trap an insert which should not occur."""
336
345
    def runTest(self):
416
425
                '}',
417
426
                ('}', 0)]
418
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
 
419
434
        self.assertEqual(k.get(0),
420
435
                         ['foo {',
421
436
                          '}'])
439
454
                          '}'])
440
455
                         
441
456
 
442
 
 
443
457
class DeleteLines2(TestBase):
444
458
    """Test recording revisions that delete lines.
445
459
 
467
481
                          (0, "fine")])
468
482
 
469
483
 
470
 
 
471
484
class IncludeVersions(TestBase):
472
485
    """Check texts that are stored across multiple revisions.
473
486
 
489
502
                "second line",
490
503
                ('}', 1)]
491
504
 
 
505
        k._sha1s = [sha_string('first line')
 
506
                  , sha_string('first linesecond line')]
 
507
 
492
508
        self.assertEqual(k.get(1),
493
509
                         ["first line",
494
510
                          "second line"])
518
534
                ('}', 2),                
519
535
                ]
520
536
 
 
537
        k._sha1s = [sha_string('first line')
 
538
                  , sha_string('first linesecond line')
 
539
                  , sha_string('first linealternative second line')]
 
540
 
521
541
        self.assertEqual(k.get(0),
522
542
                         ["first line"])
523
543
 
533
553
                         [0, 2])
534
554
 
535
555
 
536
 
 
537
556
class ReplaceLine(TestBase):
538
557
    def runTest(self):
539
558
        k = Weave()
550
569
        self.assertEqual(k.get(1), text1)
551
570
 
552
571
 
553
 
 
554
572
class Merge(TestBase):
555
573
    """Storage of versions that merge diverged parents"""
556
574
    def runTest(self):
607
625
                           [['bbb']]])
608
626
 
609
627
 
610
 
 
611
628
class NonConflict(TestBase):
612
629
    """Two descendants insert compatible changes.
613
630
 
620
637
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
621
638
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
622
639
 
623
 
    
624
 
    
625
 
 
626
640
 
627
641
class AutoMerge(TestBase):
628
642
    def runTest(self):
646
660
                          'line from 1',
647
661
                          'bbb',
648
662
                          'line from 2', 'more from 2'])
649
 
        
650
663
 
651
664
 
652
665
class Khayyam(TestBase):
696
709
        self.check_read_write(k)
697
710
 
698
711
 
699
 
 
700
712
class MergeCases(TestBase):
701
713
    def doMerge(self, base, a, b, mp):
702
714
        from cStringIO import StringIO
753
765
        self.doMerge(['aaa', 'bbb'],
754
766
                     ['aaa', 'xxx', 'yyy', 'bbb'],
755
767
                     ['aaa', 'xxx', 'bbb'],
756
 
                     ['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
 
768
                     ['aaa', '<<<<<<<', 'xxx', 'yyy', '=======', 'xxx', 
 
769
                      '>>>>>>>', 'bbb'])
757
770
 
758
771
        # really it ought to reduce this to 
759
772
        # ['aaa', 'xxx', 'yyy', 'bbb']
763
776
        self.doMerge(['aaa'],
764
777
                     ['xxx'],
765
778
                     ['yyy', 'zzz'],
766
 
                     ['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
 
779
                     ['<<<<<<<', 'xxx', '=======', 'yyy', 'zzz', 
 
780
                      '>>>>>>>'])
767
781
 
768
782
    def testNonClashInsert(self):
769
783
        self.doMerge(['aaa'],
770
784
                     ['xxx', 'aaa'],
771
785
                     ['yyy', 'zzz'],
772
 
                     ['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
 
786
                     ['<<<<<<<', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
787
                      '>>>>>>>'])
773
788
 
774
789
        self.doMerge(['aaa'],
775
790
                     ['aaa'],
791
806
        self.doMerge(['aaa', 'bbb', 'ccc'],
792
807
                     ['aaa', 'ddd', 'ccc'],
793
808
                     ['aaa', 'ccc'],
794
 
                     ['<<<<', 'aaa', '====', '>>>>', 'ccc'])
 
809
                     ['<<<<<<<<', 'aaa', '=======', '>>>>>>>', 'ccc'])
795
810
 
796
811
 
797
812
class JoinWeavesTests(TestBase):
874
889
        eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
875
890
        eq(wa.get_text('x1'), 'line from x1\n')
876
891
 
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())
 
892
 
 
893
class Corruption(TestCase):
 
894
 
 
895
    def test_detection(self):
 
896
        # Test weaves detect corruption.
 
897
        #
 
898
        # Weaves contain a checksum of their texts.
 
899
        # When a text is extracted, this checksum should be
 
900
        # verified.
 
901
 
 
902
        w = Weave()
 
903
        w.add('v1', [], ['hello\n'])
 
904
        w.add('v2', ['v1'], ['hello\n', 'there\n'])
 
905
 
 
906
        # We are going to invasively corrupt the text
 
907
        # Make sure the internals of weave are the same
 
908
        self.assertEqual([('{', 0)
 
909
                        , 'hello\n'
 
910
                        , ('}', None)
 
911
                        , ('{', 1)
 
912
                        , 'there\n'
 
913
                        , ('}', None)
 
914
                        ], w._weave)
 
915
 
 
916
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
 
917
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
 
918
                        ], w._sha1s)
 
919
        w.check()
 
920
 
 
921
        # Corrupted
 
922
        w._weave[4] = 'There\n'
 
923
 
 
924
        self.assertEqual('hello\n', w.get_text('v1'))
 
925
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
926
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
927
        self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
 
928
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
929
 
 
930
        # Corrected
 
931
        w._weave[4] = 'there\n'
 
932
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
 
933
 
 
934
        #Invalid checksum, first digit changed
 
935
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
 
936
 
 
937
        self.assertEqual('hello\n', w.get_text('v1'))
 
938
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
939
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
940
        self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
 
941
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
942
 
 
943
    def test_written_detection(self):
 
944
        # Test detection of weave file corruption.
 
945
        #
 
946
        # Make sure that we can detect if a weave file has
 
947
        # been corrupted. This doesn't test all forms of corruption,
 
948
        # but it at least helps verify the data you get, is what you want.
 
949
        from cStringIO import StringIO
 
950
 
 
951
        w = Weave()
 
952
        w.add('v1', [], ['hello\n'])
 
953
        w.add('v2', ['v1'], ['hello\n', 'there\n'])
 
954
 
 
955
        tmpf = StringIO()
 
956
        write_weave(w, tmpf)
 
957
 
 
958
        # Because we are corrupting, we need to make sure we have the exact text
 
959
        self.assertEquals('# bzr weave file v5\n'
 
960
                          'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
961
                          'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
962
                          'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n',
 
963
                          tmpf.getvalue())
 
964
 
 
965
        # Change a single letter
 
966
        tmpf = StringIO('# bzr weave file v5\n'
 
967
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
968
                        'i 0\n1 90f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
969
                        'w\n{ 0\n. hello\n}\n{ 1\n. There\n}\nW\n')
 
970
 
 
971
        w = read_weave(tmpf)
 
972
 
 
973
        self.assertEqual('hello\n', w.get_text('v1'))
 
974
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
975
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
976
        self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
 
977
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
978
 
 
979
        # Change the sha checksum
 
980
        tmpf = StringIO('# bzr weave file v5\n'
 
981
                        'i\n1 f572d396fae9206628714fb2ce00f72e94f2258f\nn v1\n\n'
 
982
                        'i 0\n1 f0f265c6e75f1c8f9ab76dcf85528352c5f215ef\nn v2\n\n'
 
983
                        'w\n{ 0\n. hello\n}\n{ 1\n. there\n}\nW\n')
 
984
 
 
985
        w = read_weave(tmpf)
 
986
 
 
987
        self.assertEqual('hello\n', w.get_text('v1'))
 
988
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
 
989
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
 
990
        self.assertRaises(errors.WeaveInvalidChecksum, list, w.get_iter('v2'))
 
991
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
 
992
 
 
993
 
 
994
class InstrumentedWeave(Weave):
 
995
    """Keep track of how many times functions are called."""
921
996
    
 
997
    def __init__(self, weave_name=None):
 
998
        self._extract_count = 0
 
999
        Weave.__init__(self, weave_name=weave_name)
 
1000
 
 
1001
    def _extract(self, versions):
 
1002
        self._extract_count += 1
 
1003
        return Weave._extract(self, versions)
 
1004
 
 
1005
 
 
1006
class JoinOptimization(TestCase):
 
1007
    """Test that Weave.join() doesn't extract all texts, only what must be done."""
 
1008
 
 
1009
    def test_join(self):
 
1010
        w1 = InstrumentedWeave()
 
1011
        w2 = InstrumentedWeave()
 
1012
 
 
1013
        txt0 = ['a\n']
 
1014
        txt1 = ['a\n', 'b\n']
 
1015
        txt2 = ['a\n', 'c\n']
 
1016
        txt3 = ['a\n', 'b\n', 'c\n']
 
1017
 
 
1018
        w1.add('txt0', [], txt0) # extract 1a
 
1019
        w2.add('txt0', [], txt0) # extract 1b
 
1020
        w1.add('txt1', [0], txt1)# extract 2a
 
1021
        w2.add('txt2', [0], txt2)# extract 2b
 
1022
        w1.join(w2) # extract 3a to add txt2 
 
1023
        w2.join(w1) # extract 3b to add txt1 
 
1024
 
 
1025
        w1.add('txt3', [1, 2], txt3) # extract 4a 
 
1026
        w2.add('txt3', [1, 2], txt3) # extract 4b
 
1027
        # These secretly have inverted parents
 
1028
 
 
1029
        # This should not have to do any extractions
 
1030
        w1.join(w2) # NO extract, texts already present with same parents
 
1031
        w2.join(w1) # NO extract, texts already present with same parents
 
1032
 
 
1033
        self.assertEqual(4, w1._extract_count)
 
1034
        self.assertEqual(4, w2._extract_count)
 
1035
 
 
1036
    def test_double_parent(self):
 
1037
        # It should not be considered illegal to add
 
1038
        # a revision with the same parent twice
 
1039
        w1 = InstrumentedWeave()
 
1040
        w2 = InstrumentedWeave()
 
1041
 
 
1042
        txt0 = ['a\n']
 
1043
        txt1 = ['a\n', 'b\n']
 
1044
        txt2 = ['a\n', 'c\n']
 
1045
        txt3 = ['a\n', 'b\n', 'c\n']
 
1046
 
 
1047
        w1.add('txt0', [], txt0)
 
1048
        w2.add('txt0', [], txt0)
 
1049
        w1.add('txt1', [0], txt1)
 
1050
        w2.add('txt1', [0,0], txt1)
 
1051
        # Same text, effectively the same, because the
 
1052
        # parent is only repeated
 
1053
        w1.join(w2) # extract 3a to add txt2 
 
1054
        w2.join(w1) # extract 3b to add txt1 
 
1055
 
 
1056
 
 
1057
class MismatchedTexts(TestCase):
 
1058
    """Test that merging two weaves with different texts fails."""
 
1059
 
 
1060
    def test_reweave(self):
 
1061
        w1 = Weave('a')
 
1062
        w2 = Weave('b')
 
1063
 
 
1064
        w1.add('txt0', [], ['a\n'])
 
1065
        w2.add('txt0', [], ['a\n'])
 
1066
        w1.add('txt1', [0], ['a\n', 'b\n'])
 
1067
        w2.add('txt1', [0], ['a\n', 'c\n'])
 
1068
 
 
1069
        self.assertRaises(errors.WeaveTextDiffers, w1.reweave, w2)
 
1070
 
 
1071