~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_weave.py

MergeĀ fromĀ jam-storage.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 
19
19
 
20
20
# TODO: tests regarding version names
21
 
 
22
 
 
 
21
# TODO: rbc 20050108 test that join does not leave an inconsistent weave 
 
22
#       if it fails.
23
23
 
24
24
"""test suite for weave algorithm"""
25
25
 
 
26
from pprint import pformat
26
27
 
27
 
import testsweet
28
 
from bzrlib.weave import Weave, WeaveFormatError
 
28
import bzrlib.errors as errors
 
29
from bzrlib.weave import Weave, WeaveFormatError, WeaveError, reweave
29
30
from bzrlib.weavefile import write_weave, read_weave
30
 
from pprint import pformat
31
 
 
32
 
 
33
 
try:
34
 
    set
35
 
    frozenset
36
 
except NameError:
37
 
    from sets import Set, ImmutableSet
38
 
    set = Set
39
 
    frozenset = ImmutableSet
40
 
    del Set, ImmutableSet
41
 
 
 
31
from bzrlib.tests import TestCase
 
32
from bzrlib.osutils import sha_string
42
33
 
43
34
 
44
35
# texts for use in testing
47
38
          "A second line"]
48
39
 
49
40
 
50
 
 
51
 
class TestBase(testsweet.TestBase):
 
41
class TestBase(TestCase):
52
42
    def check_read_write(self, k):
53
43
        """Check the weave k can be written & re-read."""
54
44
        from tempfile import TemporaryFile
68
58
            self.log('         %r' % k._parents)
69
59
            self.log('         %r' % k2._parents)
70
60
            self.log('')
71
 
 
72
 
            
73
61
            self.fail('read/write check failed')
74
 
        
75
 
        
 
62
 
 
63
 
 
64
class WeaveContains(TestBase):
 
65
    """Weave __contains__ operator"""
 
66
    def runTest(self):
 
67
        k = Weave()
 
68
        self.assertFalse('foo' in k)
 
69
        k.add('foo', [], TEXT_1)
 
70
        self.assertTrue('foo' in k)
76
71
 
77
72
 
78
73
class Easy(TestBase):
89
84
        self.assertEqual(idx, 0)
90
85
 
91
86
 
92
 
 
93
87
class AnnotateOne(TestBase):
94
88
    def runTest(self):
95
89
        k = Weave()
111
105
        self.assertEqual(k.get(0), TEXT_0)
112
106
        self.assertEqual(k.get(1), TEXT_1)
113
107
 
114
 
        k.dump(self.TEST_LOG)
115
 
 
116
 
 
 
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.WeaveRevisionNotPresent,
 
125
                          k.get_sha1, 0)
 
126
        self.assertRaises(errors.WeaveRevisionNotPresent,
 
127
                          k.get_sha1, 'text1')
 
128
                        
117
129
 
118
130
class InvalidAdd(TestBase):
119
131
    """Try to use invalid version number during add."""
127
139
                          ['new text!'])
128
140
 
129
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(WeaveError,
 
156
                          k.add,
 
157
                          'text0',
 
158
                          [],
 
159
                          ['not the same text'])
 
160
        self.assertRaises(WeaveError,
 
161
                          k.add,
 
162
                          'text0',
 
163
                          [12],         # not the right parents
 
164
                          TEXT_0)
 
165
        
 
166
 
130
167
class InsertLines(TestBase):
131
168
    """Store a revision that adds one line to the original.
132
169
 
183
220
                          (4, 'ccc')])
184
221
 
185
222
 
186
 
 
187
223
class DeleteLines(TestBase):
188
224
    """Deletion of lines from existing text.
189
225
 
213
249
        for i in range(len(texts)):
214
250
            self.assertEqual(k.get(i+1),
215
251
                             texts[i])
216
 
            
217
 
 
218
252
 
219
253
 
220
254
class SuicideDelete(TestBase):
240
274
                          0)        
241
275
 
242
276
 
243
 
 
244
277
class CannedDelete(TestBase):
245
278
    """Unpack canned weave with deleted lines."""
246
279
    def runTest(self):
257
290
                'last line',
258
291
                ('}', 0),
259
292
                ]
 
293
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
294
                  , sha_string('first linelast line')]
260
295
 
261
296
        self.assertEqual(k.get(0),
262
297
                         ['first line',
270
305
                          ])
271
306
 
272
307
 
273
 
 
274
308
class CannedReplacement(TestBase):
275
309
    """Unpack canned weave with deleted lines."""
276
310
    def runTest(self):
290
324
                'last line',
291
325
                ('}', 0),
292
326
                ]
 
327
        k._sha1s = [sha_string('first lineline to be deletedlast line')
 
328
                  , sha_string('first linereplacement linelast line')]
293
329
 
294
330
        self.assertEqual(k.get(0),
295
331
                         ['first line',
304
340
                          ])
305
341
 
306
342
 
307
 
 
308
343
class BadWeave(TestBase):
309
344
    """Test that we trap an insert which should not occur."""
310
345
    def runTest(self):
390
425
                '}',
391
426
                ('}', 0)]
392
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
 
393
434
        self.assertEqual(k.get(0),
394
435
                         ['foo {',
395
436
                          '}'])
413
454
                          '}'])
414
455
                         
415
456
 
416
 
 
417
457
class DeleteLines2(TestBase):
418
458
    """Test recording revisions that delete lines.
419
459
 
441
481
                          (0, "fine")])
442
482
 
443
483
 
444
 
 
445
484
class IncludeVersions(TestBase):
446
485
    """Check texts that are stored across multiple revisions.
447
486
 
463
502
                "second line",
464
503
                ('}', 1)]
465
504
 
 
505
        k._sha1s = [sha_string('first line')
 
506
                  , sha_string('first linesecond line')]
 
507
 
466
508
        self.assertEqual(k.get(1),
467
509
                         ["first line",
468
510
                          "second line"])
470
512
        self.assertEqual(k.get(0),
471
513
                         ["first line"])
472
514
 
473
 
        k.dump(self.TEST_LOG)
474
 
 
475
515
 
476
516
class DivergedIncludes(TestBase):
477
517
    """Weave with two diverged texts based on version 0.
494
534
                ('}', 2),                
495
535
                ]
496
536
 
 
537
        k._sha1s = [sha_string('first line')
 
538
                  , sha_string('first linesecond line')
 
539
                  , sha_string('first linealternative second line')]
 
540
 
497
541
        self.assertEqual(k.get(0),
498
542
                         ["first line"])
499
543
 
509
553
                         [0, 2])
510
554
 
511
555
 
512
 
 
513
556
class ReplaceLine(TestBase):
514
557
    def runTest(self):
515
558
        k = Weave()
526
569
        self.assertEqual(k.get(1), text1)
527
570
 
528
571
 
529
 
 
530
572
class Merge(TestBase):
531
573
    """Storage of versions that merge diverged parents"""
532
574
    def runTest(self):
583
625
                           [['bbb']]])
584
626
 
585
627
 
586
 
 
587
628
class NonConflict(TestBase):
588
629
    """Two descendants insert compatible changes.
589
630
 
596
637
        k.add([0], ['111', 'aaa', 'ccc', 'bbb'])
597
638
        k.add([1], ['aaa', 'ccc', 'bbb', '222'])
598
639
 
599
 
    
600
 
    
601
 
 
602
640
 
603
641
class AutoMerge(TestBase):
604
642
    def runTest(self):
622
660
                          'line from 1',
623
661
                          'bbb',
624
662
                          'line from 2', 'more from 2'])
625
 
        
626
663
 
627
664
 
628
665
class Khayyam(TestBase):
672
709
        self.check_read_write(k)
673
710
 
674
711
 
675
 
 
676
712
class MergeCases(TestBase):
677
713
    def doMerge(self, base, a, b, mp):
678
714
        from cStringIO import StringIO
729
765
        self.doMerge(['aaa', 'bbb'],
730
766
                     ['aaa', 'xxx', 'yyy', 'bbb'],
731
767
                     ['aaa', 'xxx', 'bbb'],
732
 
                     ['aaa', '<<<<', 'xxx', 'yyy', '====', 'xxx', '>>>>', 'bbb'])
 
768
                     ['aaa', '<<<<<<<', 'xxx', 'yyy', '=======', 'xxx', 
 
769
                      '>>>>>>>', 'bbb'])
733
770
 
734
771
        # really it ought to reduce this to 
735
772
        # ['aaa', 'xxx', 'yyy', 'bbb']
739
776
        self.doMerge(['aaa'],
740
777
                     ['xxx'],
741
778
                     ['yyy', 'zzz'],
742
 
                     ['<<<<', 'xxx', '====', 'yyy', 'zzz', '>>>>'])
 
779
                     ['<<<<<<<', 'xxx', '=======', 'yyy', 'zzz', 
 
780
                      '>>>>>>>'])
743
781
 
744
782
    def testNonClashInsert(self):
745
783
        self.doMerge(['aaa'],
746
784
                     ['xxx', 'aaa'],
747
785
                     ['yyy', 'zzz'],
748
 
                     ['<<<<', 'xxx', 'aaa', '====', 'yyy', 'zzz', '>>>>'])
 
786
                     ['<<<<<<<', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
787
                      '>>>>>>>'])
749
788
 
750
789
        self.doMerge(['aaa'],
751
790
                     ['aaa'],
767
806
        self.doMerge(['aaa', 'bbb', 'ccc'],
768
807
                     ['aaa', 'ddd', 'ccc'],
769
808
                     ['aaa', 'ccc'],
770
 
                     ['<<<<', 'aaa', '====', '>>>>', 'ccc'])
771
 
    
772
 
 
773
 
 
774
 
def testweave():
775
 
    import testsweet
776
 
    from unittest import TestSuite, TestLoader
777
 
    import testweave
778
 
 
779
 
    tl = TestLoader()
780
 
    suite = TestSuite()
781
 
    suite.addTest(tl.loadTestsFromModule(testweave))
782
 
    
783
 
    return int(not testsweet.run_suite(suite)) # for shell 0=true
784
 
 
785
 
 
786
 
if __name__ == '__main__':
787
 
    import sys
788
 
    sys.exit(testweave())
789
 
    
 
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