~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/test_weave.py

  • Committer: Martin Pool
  • Date: 2005-09-16 09:56:24 UTC
  • Revision ID: mbp@sourcefrog.net-20050916095623-ca0dff452934f21f
- make progress bar more tolerant of out-of-range values

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
22
 
23
23
 
24
24
"""test suite for weave algorithm"""
25
25
 
 
26
 
 
27
import testsweet
 
28
from bzrlib.weave import Weave, WeaveFormatError, WeaveError
 
29
from bzrlib.weavefile import write_weave, read_weave
 
30
from bzrlib.selftest import TestCase
26
31
from pprint import pformat
27
32
 
28
 
import bzrlib.errors as errors
29
 
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
30
 
from bzrlib.weavefile import write_weave, read_weave
31
 
from bzrlib.tests import TestCase
32
 
from bzrlib.osutils import sha_string
 
33
 
 
34
try:
 
35
    set
 
36
    frozenset
 
37
except NameError:
 
38
    from sets import Set, ImmutableSet
 
39
    set = Set
 
40
    frozenset = ImmutableSet
 
41
    del Set, ImmutableSet
 
42
 
33
43
 
34
44
 
35
45
# texts for use in testing
38
48
          "A second line"]
39
49
 
40
50
 
 
51
 
41
52
class TestBase(TestCase):
42
53
    def check_read_write(self, k):
43
54
        """Check the weave k can be written & re-read."""
58
69
            self.log('         %r' % k._parents)
59
70
            self.log('         %r' % k2._parents)
60
71
            self.log('')
 
72
 
 
73
            
61
74
            self.fail('read/write check failed')
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)
 
75
        
 
76
        
71
77
 
72
78
 
73
79
class Easy(TestBase):
84
90
        self.assertEqual(idx, 0)
85
91
 
86
92
 
 
93
 
87
94
class AnnotateOne(TestBase):
88
95
    def runTest(self):
89
96
        k = Weave()
106
113
        self.assertEqual(k.get(1), TEXT_1)
107
114
 
108
115
 
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.WeaveRevisionNotPresent,
125
 
                          k.get_sha1, 0)
126
 
        self.assertRaises(errors.WeaveRevisionNotPresent,
127
 
                          k.get_sha1, 'text1')
128
 
                        
129
116
 
130
117
class InvalidAdd(TestBase):
131
118
    """Try to use invalid version number during add."""
148
135
        self.assertEqual(idx, idx2)
149
136
 
150
137
 
 
138
 
151
139
class InvalidRepeatedAdd(TestBase):
152
140
    def runTest(self):
153
141
        k = Weave()
164
152
                          TEXT_0)
165
153
        
166
154
 
 
155
 
167
156
class InsertLines(TestBase):
168
157
    """Store a revision that adds one line to the original.
169
158
 
220
209
                          (4, 'ccc')])
221
210
 
222
211
 
 
212
 
223
213
class DeleteLines(TestBase):
224
214
    """Deletion of lines from existing text.
225
215
 
249
239
        for i in range(len(texts)):
250
240
            self.assertEqual(k.get(i+1),
251
241
                             texts[i])
 
242
            
 
243
 
252
244
 
253
245
 
254
246
class SuicideDelete(TestBase):
274
266
                          0)        
275
267
 
276
268
 
 
269
 
277
270
class CannedDelete(TestBase):
278
271
    """Unpack canned weave with deleted lines."""
279
272
    def runTest(self):
290
283
                'last line',
291
284
                ('}', 0),
292
285
                ]
293
 
        k._sha1s = [sha_string('first lineline to be deletedlast line')
294
 
                  , sha_string('first linelast line')]
295
286
 
296
287
        self.assertEqual(k.get(0),
297
288
                         ['first line',
305
296
                          ])
306
297
 
307
298
 
 
299
 
308
300
class CannedReplacement(TestBase):
309
301
    """Unpack canned weave with deleted lines."""
310
302
    def runTest(self):
324
316
                'last line',
325
317
                ('}', 0),
326
318
                ]
327
 
        k._sha1s = [sha_string('first lineline to be deletedlast line')
328
 
                  , sha_string('first linereplacement linelast line')]
329
319
 
330
320
        self.assertEqual(k.get(0),
331
321
                         ['first line',
340
330
                          ])
341
331
 
342
332
 
 
333
 
343
334
class BadWeave(TestBase):
344
335
    """Test that we trap an insert which should not occur."""
345
336
    def runTest(self):
425
416
                '}',
426
417
                ('}', 0)]
427
418
 
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
 
 
434
419
        self.assertEqual(k.get(0),
435
420
                         ['foo {',
436
421
                          '}'])
454
439
                          '}'])
455
440
                         
456
441
 
 
442
 
457
443
class DeleteLines2(TestBase):
458
444
    """Test recording revisions that delete lines.
459
445
 
481
467
                          (0, "fine")])
482
468
 
483
469
 
 
470
 
484
471
class IncludeVersions(TestBase):
485
472
    """Check texts that are stored across multiple revisions.
486
473
 
502
489
                "second line",
503
490
                ('}', 1)]
504
491
 
505
 
        k._sha1s = [sha_string('first line')
506
 
                  , sha_string('first linesecond line')]
507
 
 
508
492
        self.assertEqual(k.get(1),
509
493
                         ["first line",
510
494
                          "second line"])
534
518
                ('}', 2),                
535
519
                ]
536
520
 
537
 
        k._sha1s = [sha_string('first line')
538
 
                  , sha_string('first linesecond line')
539
 
                  , sha_string('first linealternative second line')]
540
 
 
541
521
        self.assertEqual(k.get(0),
542
522
                         ["first line"])
543
523
 
553
533
                         [0, 2])
554
534
 
555
535
 
 
536
 
556
537
class ReplaceLine(TestBase):
557
538
    def runTest(self):
558
539
        k = Weave()
569
550
        self.assertEqual(k.get(1), text1)
570
551
 
571
552
 
 
553
 
572
554
class Merge(TestBase):
573
555
    """Storage of versions that merge diverged parents"""
574
556
    def runTest(self):
625
607
                           [['bbb']]])
626
608
 
627
609
 
 
610
 
628
611
class NonConflict(TestBase):
629
612
    """Two descendants insert compatible changes.
630
613
 
637
620
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
638
621
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
639
622
 
 
623
    
 
624
    
 
625
 
640
626
 
641
627
class AutoMerge(TestBase):
642
628
    def runTest(self):
660
646
                          'line from 1',
661
647
                          'bbb',
662
648
                          'line from 2', 'more from 2'])
 
649
        
663
650
 
664
651
 
665
652
class Khayyam(TestBase):
709
696
        self.check_read_write(k)
710
697
 
711
698
 
 
699
 
712
700
class MergeCases(TestBase):
713
701
    def doMerge(self, base, a, b, mp):
714
702
        from cStringIO import StringIO
765
753
        self.doMerge(['aaa', 'bbb'],
766
754
                     ['aaa', 'xxx', 'yyy', 'bbb'],
767
755
                     ['aaa', 'xxx', 'bbb'],
768
 
                     ['aaa', '<<<<<<<', 'xxx', 'yyy', '=======', 'xxx', 
769
 
                      '>>>>>>>', 'bbb'])
 
756
                     ['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
770
757
 
771
758
        # really it ought to reduce this to 
772
759
        # ['aaa', 'xxx', 'yyy', 'bbb']
776
763
        self.doMerge(['aaa'],
777
764
                     ['xxx'],
778
765
                     ['yyy', 'zzz'],
779
 
                     ['<<<<<<<', 'xxx', '=======', 'yyy', 'zzz', 
780
 
                      '>>>>>>>'])
 
766
                     ['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
781
767
 
782
768
    def testNonClashInsert(self):
783
769
        self.doMerge(['aaa'],
784
770
                     ['xxx', 'aaa'],
785
771
                     ['yyy', 'zzz'],
786
 
                     ['<<<<<<<', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
787
 
                      '>>>>>>>'])
 
772
                     ['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
788
773
 
789
774
        self.doMerge(['aaa'],
790
775
                     ['aaa'],
806
791
        self.doMerge(['aaa', 'bbb', 'ccc'],
807
792
                     ['aaa', 'ddd', 'ccc'],
808
793
                     ['aaa', 'ccc'],
809
 
                     ['<<<<<<<<', 'aaa', '=======', '>>>>>>>', 'ccc'])
810
 
 
811
 
 
812
 
class JoinWeavesTests(TestBase):
813
 
    def setUp(self):
814
 
        super(JoinWeavesTests, self).setUp()
815
 
        self.weave1 = Weave()
816
 
        self.lines1 = ['hello\n']
817
 
        self.lines3 = ['hello\n', 'cruel\n', 'world\n']
818
 
        self.weave1.add('v1', [], self.lines1)
819
 
        self.weave1.add('v2', [0], ['hello\n', 'world\n'])
820
 
        self.weave1.add('v3', [1], self.lines3)
821
 
        
822
 
    def test_join_empty(self):
823
 
        """Join two empty weaves."""
824
 
        eq = self.assertEqual
825
 
        w1 = Weave()
826
 
        w2 = Weave()
827
 
        w1.join(w2)
828
 
        eq(w1.numversions(), 0)
829
 
        
830
 
    def test_join_empty_to_nonempty(self):
831
 
        """Join empty weave onto nonempty."""
832
 
        self.weave1.join(Weave())
833
 
        self.assertEqual(len(self.weave1), 3)
834
 
 
835
 
    def test_join_unrelated(self):
836
 
        """Join two weaves with no history in common."""
837
 
        wb = Weave()
838
 
        wb.add('b1', [], ['line from b\n'])
839
 
        w1 = self.weave1
840
 
        w1.join(wb)
841
 
        eq = self.assertEqual
842
 
        eq(len(w1), 4)
843
 
        eq(sorted(list(w1.iter_names())),
844
 
           ['b1', 'v1', 'v2', 'v3'])
845
 
 
846
 
    def test_join_related(self):
847
 
        wa = self.weave1.copy()
848
 
        wb = self.weave1.copy()
849
 
        wa.add('a1', ['v3'], ['hello\n', 'sweet\n', 'world\n'])
850
 
        wb.add('b1', ['v3'], ['hello\n', 'pale blue\n', 'world\n'])
851
 
        eq = self.assertEquals
852
 
        eq(len(wa), 4)
853
 
        eq(len(wb), 4)
854
 
        wa.join(wb)
855
 
        eq(len(wa), 5)
856
 
        eq(wa.get_lines('b1'),
857
 
           ['hello\n', 'pale blue\n', 'world\n'])
858
 
 
859
 
    def test_join_parent_disagreement(self):
860
 
        """Cannot join weaves with different parents for a version."""
861
 
        wa = Weave()
862
 
        wb = Weave()
863
 
        wa.add('v1', [], ['hello\n'])
864
 
        wb.add('v0', [], [])
865
 
        wb.add('v1', ['v0'], ['hello\n'])
866
 
        self.assertRaises(WeaveError,
867
 
                          wa.join, wb)
868
 
 
869
 
    def test_join_text_disagreement(self):
870
 
        """Cannot join weaves with different texts for a version."""
871
 
        wa = Weave()
872
 
        wb = Weave()
873
 
        wa.add('v1', [], ['hello\n'])
874
 
        wb.add('v1', [], ['not\n', 'hello\n'])
875
 
        self.assertRaises(WeaveError,
876
 
                          wa.join, wb)
877
 
 
878
 
    def test_join_unordered(self):
879
 
        """Join weaves where indexes differ.
880
 
        
881
 
        The source weave contains a different version at index 0."""
882
 
        wa = self.weave1.copy()
883
 
        wb = Weave()
884
 
        wb.add('x1', [], ['line from x1\n'])
885
 
        wb.add('v1', [], ['hello\n'])
886
 
        wb.add('v2', ['v1'], ['hello\n', 'world\n'])
887
 
        wa.join(wb)
888
 
        eq = self.assertEquals
889
 
        eq(sorted(wa.iter_names()), ['v1', 'v2', 'v3', 'x1',])
890
 
        eq(wa.get_text('x1'), 'line from x1\n')
891
 
 
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."""
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
 
 
 
794
                     ['<<<<', 'aaa', '====', '>>>>', 'ccc'])
 
795
    
 
796
 
 
797
 
 
798
if __name__ == '__main__':
 
799
    import sys
 
800
    import unittest
 
801
    sys.exit(unittest.main())
 
802