/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_versionedfile.py

  • Committer: Aaron Bentley
  • Date: 2006-05-20 17:51:13 UTC
  • mfrom: (1718 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1727.
  • Revision ID: aaron.bentley@utoronto.ca-20060520175113-4549e0023f9210bf
Merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
19
 
20
20
 
 
21
# TODO: might be nice to create a versionedfile with some type of corruption
 
22
# considered typical and check that it can be detected/corrected.
 
23
 
 
24
from StringIO import StringIO
 
25
 
21
26
import bzrlib
22
27
import bzrlib.errors as errors
23
28
from bzrlib.errors import (
28
33
from bzrlib.knit import KnitVersionedFile, \
29
34
     KnitAnnotateFactory
30
35
from bzrlib.tests import TestCaseWithTransport
 
36
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
31
37
from bzrlib.trace import mutter
32
38
from bzrlib.transport import get_transport
33
39
from bzrlib.transport.memory import MemoryTransport
 
40
from bzrlib.tsort import topo_sort
34
41
import bzrlib.versionedfile as versionedfile
35
42
from bzrlib.weave import WeaveFile
36
 
from bzrlib.weavefile import read_weave
 
43
from bzrlib.weavefile import read_weave, write_weave
37
44
 
38
45
 
39
46
class VersionedFileTestMixIn(object):
63
70
            self.assertRaises(RevisionAlreadyPresent,
64
71
                f.add_lines, 'r1', [], [])
65
72
        verify_file(f)
66
 
        f = self.reopen_file()
 
73
        # this checks that reopen with create=True does not break anything.
 
74
        f = self.reopen_file(create=True)
67
75
        verify_file(f)
68
76
 
69
77
    def test_adds_with_parent_texts(self):
104
112
        f = self.reopen_file()
105
113
        verify_file(f)
106
114
 
 
115
    def test_add_unicode_content(self):
 
116
        # unicode content is not permitted in versioned files. 
 
117
        # versioned files version sequences of bytes only.
 
118
        vf = self.get_file()
 
119
        self.assertRaises(errors.BzrBadParameterUnicode,
 
120
            vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
 
121
        self.assertRaises(
 
122
            (errors.BzrBadParameterUnicode, NotImplementedError),
 
123
            vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
 
124
 
 
125
    def test_inline_newline_throws(self):
 
126
        # \r characters are not permitted in lines being added
 
127
        vf = self.get_file()
 
128
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
 
129
            vf.add_lines, 'a', [], ['a\n\n'])
 
130
        self.assertRaises(
 
131
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
 
132
            vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
 
133
        # but inline CR's are allowed
 
134
        vf.add_lines('a', [], ['a\r\n'])
 
135
        try:
 
136
            vf.add_lines_with_ghosts('b', [], ['a\r\n'])
 
137
        except NotImplementedError:
 
138
            pass
 
139
 
107
140
    def test_get_delta(self):
108
141
        f = self.get_file()
109
142
        sha1s = self._setup_for_deltas(f)
391
424
        # and should be a list
392
425
        self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
393
426
 
 
427
    def build_graph(self, file, graph):
 
428
        for node in topo_sort(graph.items()):
 
429
            file.add_lines(node, graph[node], [])
 
430
 
394
431
    def test_get_graph(self):
395
432
        f = self.get_file()
396
 
        f.add_lines('v1', [], ['hello\n'])
397
 
        f.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
398
 
        f.add_lines('v3', ['v2'], ['hello\n', 'cruel\n', 'world\n'])
399
 
        self.assertEqual({'v1': [],
400
 
                          'v2': ['v1'],
401
 
                          'v3': ['v2']},
402
 
                         f.get_graph())
 
433
        graph = {
 
434
            'v1': [],
 
435
            'v2': ['v1'],
 
436
            'v3': ['v2']}
 
437
        self.build_graph(f, graph)
 
438
        self.assertEqual(graph, f.get_graph())
 
439
    
 
440
    def test_get_graph_partial(self):
 
441
        f = self.get_file()
 
442
        complex_graph = {}
 
443
        simple_a = {
 
444
            'c': [],
 
445
            'b': ['c'],
 
446
            'a': ['b'],
 
447
            }
 
448
        complex_graph.update(simple_a)
 
449
        simple_b = {
 
450
            'c': [],
 
451
            'b': ['c'],
 
452
            }
 
453
        complex_graph.update(simple_b)
 
454
        simple_gam = {
 
455
            'c': [],
 
456
            'oo': [],
 
457
            'bar': ['oo', 'c'],
 
458
            'gam': ['bar'],
 
459
            }
 
460
        complex_graph.update(simple_gam)
 
461
        simple_b_gam = {}
 
462
        simple_b_gam.update(simple_gam)
 
463
        simple_b_gam.update(simple_b)
 
464
        self.build_graph(f, complex_graph)
 
465
        self.assertEqual(simple_a, f.get_graph(['a']))
 
466
        self.assertEqual(simple_b, f.get_graph(['b']))
 
467
        self.assertEqual(simple_gam, f.get_graph(['gam']))
 
468
        self.assertEqual(simple_b_gam, f.get_graph(['b', 'gam']))
403
469
 
404
470
    def test_get_parents(self):
405
471
        f = self.get_file()
636
702
        self.assertRaises(errors.ReadOnlyError, vf.fix_parents, 'base', [])
637
703
        self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
638
704
        self.assertRaises(errors.ReadOnlyError, vf.clone_text, 'base', 'bar', ['foo'])
 
705
    
 
706
    def test_get_sha1(self):
 
707
        # check the sha1 data is available
 
708
        vf = self.get_file()
 
709
        # a simple file
 
710
        vf.add_lines('a', [], ['a\n'])
 
711
        # the same file, different metadata
 
712
        vf.add_lines('b', ['a'], ['a\n'])
 
713
        # a file differing only in last newline.
 
714
        vf.add_lines('c', [], ['a'])
 
715
        self.assertEqual(
 
716
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('a'))
 
717
        self.assertEqual(
 
718
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('b'))
 
719
        self.assertEqual(
 
720
            '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', vf.get_sha1('c'))
639
721
        
640
722
 
641
723
class TestWeave(TestCaseWithTransport, VersionedFileTestMixIn):
677
759
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
678
760
        return w
679
761
 
680
 
    def reopen_file(self, name='foo'):
681
 
        return WeaveFile(name, get_transport(self.get_url('.')))
 
762
    def reopen_file(self, name='foo', create=False):
 
763
        return WeaveFile(name, get_transport(self.get_url('.')), create=create)
682
764
 
683
765
    def test_no_implicit_create(self):
684
766
        self.assertRaises(errors.NoSuchFile,
705
787
        knit.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
706
788
        return knit
707
789
 
708
 
    def reopen_file(self, name='foo'):
709
 
        return KnitVersionedFile(name, get_transport(self.get_url('.')), delta=True)
 
790
    def reopen_file(self, name='foo', create=False):
 
791
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
 
792
            delta=True,
 
793
            create=create)
710
794
 
711
795
    def test_detection(self):
712
 
        print "TODO for merging: create a corrupted knit."
713
796
        knit = self.get_file()
714
797
        knit.check()
715
798
 
783
866
            versionedfile.InterVersionedFile.unregister_optimiser(InterString)
784
867
        # now we should get the default InterVersionedFile object again.
785
868
        self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
 
869
 
 
870
 
 
871
class TestReadonlyHttpMixin(object):
 
872
 
 
873
    def test_readonly_http_works(self):
 
874
        # we should be able to read from http with a versioned file.
 
875
        vf = self.get_file()
 
876
        # try an empty file access
 
877
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
 
878
        self.assertEqual([], readonly_vf.versions())
 
879
        # now with feeling.
 
880
        vf.add_lines('1', [], ['a\n'])
 
881
        vf.add_lines('2', ['1'], ['b\n', 'a\n'])
 
882
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
 
883
        self.assertEqual(['1', '2'], vf.versions())
 
884
        for version in readonly_vf.versions():
 
885
            readonly_vf.get_lines(version)
 
886
 
 
887
 
 
888
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
 
889
 
 
890
    def get_file(self):
 
891
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
 
892
 
 
893
    def get_factory(self):
 
894
        return WeaveFile
 
895
 
 
896
 
 
897
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
 
898
 
 
899
    def get_file(self):
 
900
        return KnitVersionedFile('foo', get_transport(self.get_url('.')),
 
901
                                 delta=True, create=True)
 
902
 
 
903
    def get_factory(self):
 
904
        return KnitVersionedFile
 
905
 
 
906
 
 
907
class MergeCasesMixin(object):
 
908
 
 
909
    def doMerge(self, base, a, b, mp):
 
910
        from cStringIO import StringIO
 
911
        from textwrap import dedent
 
912
 
 
913
        def addcrlf(x):
 
914
            return x + '\n'
 
915
        
 
916
        w = self.get_file()
 
917
        w.add_lines('text0', [], map(addcrlf, base))
 
918
        w.add_lines('text1', ['text0'], map(addcrlf, a))
 
919
        w.add_lines('text2', ['text0'], map(addcrlf, b))
 
920
 
 
921
        self.log_contents(w)
 
922
 
 
923
        self.log('merge plan:')
 
924
        p = list(w.plan_merge('text1', 'text2'))
 
925
        for state, line in p:
 
926
            if line:
 
927
                self.log('%12s | %s' % (state, line[:-1]))
 
928
 
 
929
        self.log('merge:')
 
930
        mt = StringIO()
 
931
        mt.writelines(w.weave_merge(p))
 
932
        mt.seek(0)
 
933
        self.log(mt.getvalue())
 
934
 
 
935
        mp = map(addcrlf, mp)
 
936
        self.assertEqual(mt.readlines(), mp)
 
937
        
 
938
        
 
939
    def testOneInsert(self):
 
940
        self.doMerge([],
 
941
                     ['aa'],
 
942
                     [],
 
943
                     ['aa'])
 
944
 
 
945
    def testSeparateInserts(self):
 
946
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
947
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
948
                     ['aaa', 'bbb', 'yyy', 'ccc'],
 
949
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
950
 
 
951
    def testSameInsert(self):
 
952
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
953
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
954
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
 
955
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
956
    overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
 
957
    def testOverlappedInsert(self):
 
958
        self.doMerge(['aaa', 'bbb'],
 
959
                     ['aaa', 'xxx', 'yyy', 'bbb'],
 
960
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
 
961
 
 
962
        # really it ought to reduce this to 
 
963
        # ['aaa', 'xxx', 'yyy', 'bbb']
 
964
 
 
965
 
 
966
    def testClashReplace(self):
 
967
        self.doMerge(['aaa'],
 
968
                     ['xxx'],
 
969
                     ['yyy', 'zzz'],
 
970
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
971
                      '>>>>>>> '])
 
972
 
 
973
    def testNonClashInsert1(self):
 
974
        self.doMerge(['aaa'],
 
975
                     ['xxx', 'aaa'],
 
976
                     ['yyy', 'zzz'],
 
977
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
978
                      '>>>>>>> '])
 
979
 
 
980
    def testNonClashInsert2(self):
 
981
        self.doMerge(['aaa'],
 
982
                     ['aaa'],
 
983
                     ['yyy', 'zzz'],
 
984
                     ['yyy', 'zzz'])
 
985
 
 
986
 
 
987
    def testDeleteAndModify(self):
 
988
        """Clashing delete and modification.
 
989
 
 
990
        If one side modifies a region and the other deletes it then
 
991
        there should be a conflict with one side blank.
 
992
        """
 
993
 
 
994
        #######################################
 
995
        # skippd, not working yet
 
996
        return
 
997
        
 
998
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
999
                     ['aaa', 'ddd', 'ccc'],
 
1000
                     ['aaa', 'ccc'],
 
1001
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
 
1002
 
 
1003
    def _test_merge_from_strings(self, base, a, b, expected):
 
1004
        w = self.get_file()
 
1005
        w.add_lines('text0', [], base.splitlines(True))
 
1006
        w.add_lines('text1', ['text0'], a.splitlines(True))
 
1007
        w.add_lines('text2', ['text0'], b.splitlines(True))
 
1008
        self.log('merge plan:')
 
1009
        p = list(w.plan_merge('text1', 'text2'))
 
1010
        for state, line in p:
 
1011
            if line:
 
1012
                self.log('%12s | %s' % (state, line[:-1]))
 
1013
        self.log('merge result:')
 
1014
        result_text = ''.join(w.weave_merge(p))
 
1015
        self.log(result_text)
 
1016
        self.assertEqualDiff(result_text, expected)
 
1017
 
 
1018
    def test_weave_merge_conflicts(self):
 
1019
        # does weave merge properly handle plans that end with unchanged?
 
1020
        result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
 
1021
        self.assertEqual(result, 'hello\n')
 
1022
 
 
1023
    def test_deletion_extended(self):
 
1024
        """One side deletes, the other deletes more.
 
1025
        """
 
1026
        base = """\
 
1027
            line 1
 
1028
            line 2
 
1029
            line 3
 
1030
            """
 
1031
        a = """\
 
1032
            line 1
 
1033
            line 2
 
1034
            """
 
1035
        b = """\
 
1036
            line 1
 
1037
            """
 
1038
        result = """\
 
1039
            line 1
 
1040
            """
 
1041
        self._test_merge_from_strings(base, a, b, result)
 
1042
 
 
1043
    def test_deletion_overlap(self):
 
1044
        """Delete overlapping regions with no other conflict.
 
1045
 
 
1046
        Arguably it'd be better to treat these as agreement, rather than 
 
1047
        conflict, but for now conflict is safer.
 
1048
        """
 
1049
        base = """\
 
1050
            start context
 
1051
            int a() {}
 
1052
            int b() {}
 
1053
            int c() {}
 
1054
            end context
 
1055
            """
 
1056
        a = """\
 
1057
            start context
 
1058
            int a() {}
 
1059
            end context
 
1060
            """
 
1061
        b = """\
 
1062
            start context
 
1063
            int c() {}
 
1064
            end context
 
1065
            """
 
1066
        result = """\
 
1067
            start context
 
1068
<<<<<<< 
 
1069
            int a() {}
 
1070
=======
 
1071
            int c() {}
 
1072
>>>>>>> 
 
1073
            end context
 
1074
            """
 
1075
        self._test_merge_from_strings(base, a, b, result)
 
1076
 
 
1077
    def test_agreement_deletion(self):
 
1078
        """Agree to delete some lines, without conflicts."""
 
1079
        base = """\
 
1080
            start context
 
1081
            base line 1
 
1082
            base line 2
 
1083
            end context
 
1084
            """
 
1085
        a = """\
 
1086
            start context
 
1087
            base line 1
 
1088
            end context
 
1089
            """
 
1090
        b = """\
 
1091
            start context
 
1092
            base line 1
 
1093
            end context
 
1094
            """
 
1095
        result = """\
 
1096
            start context
 
1097
            base line 1
 
1098
            end context
 
1099
            """
 
1100
        self._test_merge_from_strings(base, a, b, result)
 
1101
 
 
1102
    def test_sync_on_deletion(self):
 
1103
        """Specific case of merge where we can synchronize incorrectly.
 
1104
        
 
1105
        A previous version of the weave merge concluded that the two versions
 
1106
        agreed on deleting line 2, and this could be a synchronization point.
 
1107
        Line 1 was then considered in isolation, and thought to be deleted on 
 
1108
        both sides.
 
1109
 
 
1110
        It's better to consider the whole thing as a disagreement region.
 
1111
        """
 
1112
        base = """\
 
1113
            start context
 
1114
            base line 1
 
1115
            base line 2
 
1116
            end context
 
1117
            """
 
1118
        a = """\
 
1119
            start context
 
1120
            base line 1
 
1121
            a's replacement line 2
 
1122
            end context
 
1123
            """
 
1124
        b = """\
 
1125
            start context
 
1126
            b replaces
 
1127
            both lines
 
1128
            end context
 
1129
            """
 
1130
        result = """\
 
1131
            start context
 
1132
<<<<<<< 
 
1133
            base line 1
 
1134
            a's replacement line 2
 
1135
=======
 
1136
            b replaces
 
1137
            both lines
 
1138
>>>>>>> 
 
1139
            end context
 
1140
            """
 
1141
        self._test_merge_from_strings(base, a, b, result)
 
1142
 
 
1143
 
 
1144
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
 
1145
 
 
1146
    def get_file(self, name='foo'):
 
1147
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
 
1148
                                 delta=True, create=True)
 
1149
 
 
1150
    def log_contents(self, w):
 
1151
        pass
 
1152
 
 
1153
 
 
1154
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
 
1155
 
 
1156
    def get_file(self, name='foo'):
 
1157
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
 
1158
 
 
1159
    def log_contents(self, w):
 
1160
        self.log('weave is:')
 
1161
        tmpf = StringIO()
 
1162
        write_weave(w, tmpf)
 
1163
        self.log(tmpf.getvalue())
 
1164
 
 
1165
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
 
1166
                                'xxx', '>>>>>>> ', 'bbb']