/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: Martin Pool
  • Date: 2006-10-06 02:04:17 UTC
  • mfrom: (1908.10.1 bench_usecases.merge2)
  • mto: This revision was merged to the branch mainline in revision 2068.
  • Revision ID: mbp@sourcefrog.net-20061006020417-4949ca86f4417a4d
merge additional fix from cfbolz

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
# it under the terms of the GNU General Public License as published by
8
8
# the Free Software Foundation; either version 2 of the License, or
9
9
# (at your option) any later version.
10
 
 
 
10
#
11
11
# This program is distributed in the hope that it will be useful,
12
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
14
# GNU General Public License for more details.
15
 
 
 
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
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
 
import bzrlib.errors as errors
 
27
from bzrlib import (
 
28
    errors,
 
29
    progress,
 
30
    )
23
31
from bzrlib.errors import (
24
32
                           RevisionNotPresent, 
25
33
                           RevisionAlreadyPresent,
28
36
from bzrlib.knit import KnitVersionedFile, \
29
37
     KnitAnnotateFactory
30
38
from bzrlib.tests import TestCaseWithTransport
 
39
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
31
40
from bzrlib.trace import mutter
32
41
from bzrlib.transport import get_transport
33
42
from bzrlib.transport.memory import MemoryTransport
 
43
from bzrlib.tsort import topo_sort
34
44
import bzrlib.versionedfile as versionedfile
35
45
from bzrlib.weave import WeaveFile
36
 
from bzrlib.weavefile import read_weave
 
46
from bzrlib.weavefile import read_weave, write_weave
37
47
 
38
48
 
39
49
class VersionedFileTestMixIn(object):
63
73
            self.assertRaises(RevisionAlreadyPresent,
64
74
                f.add_lines, 'r1', [], [])
65
75
        verify_file(f)
66
 
        f = self.reopen_file()
 
76
        # this checks that reopen with create=True does not break anything.
 
77
        f = self.reopen_file(create=True)
67
78
        verify_file(f)
68
79
 
69
80
    def test_adds_with_parent_texts(self):
104
115
        f = self.reopen_file()
105
116
        verify_file(f)
106
117
 
 
118
    def test_add_unicode_content(self):
 
119
        # unicode content is not permitted in versioned files. 
 
120
        # versioned files version sequences of bytes only.
 
121
        vf = self.get_file()
 
122
        self.assertRaises(errors.BzrBadParameterUnicode,
 
123
            vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
 
124
        self.assertRaises(
 
125
            (errors.BzrBadParameterUnicode, NotImplementedError),
 
126
            vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
 
127
 
 
128
    def test_inline_newline_throws(self):
 
129
        # \r characters are not permitted in lines being added
 
130
        vf = self.get_file()
 
131
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
 
132
            vf.add_lines, 'a', [], ['a\n\n'])
 
133
        self.assertRaises(
 
134
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
 
135
            vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
 
136
        # but inline CR's are allowed
 
137
        vf.add_lines('a', [], ['a\r\n'])
 
138
        try:
 
139
            vf.add_lines_with_ghosts('b', [], ['a\r\n'])
 
140
        except NotImplementedError:
 
141
            pass
 
142
 
107
143
    def test_get_delta(self):
108
144
        f = self.get_file()
109
145
        sha1s = self._setup_for_deltas(f)
391
427
        # and should be a list
392
428
        self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
393
429
 
 
430
    def build_graph(self, file, graph):
 
431
        for node in topo_sort(graph.items()):
 
432
            file.add_lines(node, graph[node], [])
 
433
 
394
434
    def test_get_graph(self):
395
435
        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())
 
436
        graph = {
 
437
            'v1': [],
 
438
            'v2': ['v1'],
 
439
            'v3': ['v2']}
 
440
        self.build_graph(f, graph)
 
441
        self.assertEqual(graph, f.get_graph())
 
442
    
 
443
    def test_get_graph_partial(self):
 
444
        f = self.get_file()
 
445
        complex_graph = {}
 
446
        simple_a = {
 
447
            'c': [],
 
448
            'b': ['c'],
 
449
            'a': ['b'],
 
450
            }
 
451
        complex_graph.update(simple_a)
 
452
        simple_b = {
 
453
            'c': [],
 
454
            'b': ['c'],
 
455
            }
 
456
        complex_graph.update(simple_b)
 
457
        simple_gam = {
 
458
            'c': [],
 
459
            'oo': [],
 
460
            'bar': ['oo', 'c'],
 
461
            'gam': ['bar'],
 
462
            }
 
463
        complex_graph.update(simple_gam)
 
464
        simple_b_gam = {}
 
465
        simple_b_gam.update(simple_gam)
 
466
        simple_b_gam.update(simple_b)
 
467
        self.build_graph(f, complex_graph)
 
468
        self.assertEqual(simple_a, f.get_graph(['a']))
 
469
        self.assertEqual(simple_b, f.get_graph(['b']))
 
470
        self.assertEqual(simple_gam, f.get_graph(['gam']))
 
471
        self.assertEqual(simple_b_gam, f.get_graph(['b', 'gam']))
403
472
 
404
473
    def test_get_parents(self):
405
474
        f = self.get_file()
477
546
        # versions in the weave 
478
547
        # the ordering here is to make a tree so that dumb searches have
479
548
        # more changes to muck up.
 
549
 
 
550
        class InstrumentedProgress(progress.DummyProgress):
 
551
 
 
552
            def __init__(self):
 
553
 
 
554
                progress.DummyProgress.__init__(self)
 
555
                self.updates = []
 
556
 
 
557
            def update(self, msg=None, current=None, total=None):
 
558
                self.updates.append((msg, current, total))
 
559
 
480
560
        vf = self.get_file()
481
561
        # add a base to get included
482
562
        vf.add_lines('base', [], ['base\n'])
490
570
        vf.add_lines('otherchild',
491
571
                     ['lancestor', 'base'],
492
572
                     ['base\n', 'lancestor\n', 'otherchild\n'])
493
 
        def iter_with_versions(versions):
 
573
        def iter_with_versions(versions, expected):
494
574
            # now we need to see what lines are returned, and how often.
495
575
            lines = {'base\n':0,
496
576
                     'lancestor\n':0,
498
578
                     'child\n':0,
499
579
                     'otherchild\n':0,
500
580
                     }
 
581
            progress = InstrumentedProgress()
501
582
            # iterate over the lines
502
 
            for line in vf.iter_lines_added_or_present_in_versions(versions):
 
583
            for line in vf.iter_lines_added_or_present_in_versions(versions, 
 
584
                pb=progress):
503
585
                lines[line] += 1
 
586
            if []!= progress.updates: 
 
587
                self.assertEqual(expected, progress.updates)
504
588
            return lines
505
 
        lines = iter_with_versions(['child', 'otherchild'])
 
589
        lines = iter_with_versions(['child', 'otherchild'], 
 
590
                                   [('Walking content.', 0, 2), 
 
591
                                    ('Walking content.', 0, 2), 
 
592
                                    ('Walking content.', 3, 2), 
 
593
                                    ('Walking content.', 2, 2)])
506
594
        # we must see child and otherchild
507
595
        self.assertTrue(lines['child\n'] > 0)
508
596
        self.assertTrue(lines['otherchild\n'] > 0)
509
597
        # we dont care if we got more than that.
510
598
        
511
599
        # test all lines
512
 
        lines = iter_with_versions(None)
 
600
        lines = iter_with_versions(None, [('Walking content.', 0, 5), 
 
601
                                          ('Walking content.', 0, 5), 
 
602
                                          ('Walking content.', 1, 5), 
 
603
                                          ('Walking content.', 2, 5), 
 
604
                                          ('Walking content.', 2, 5), 
 
605
                                          ('Walking content.', 3, 5), 
 
606
                                          ('Walking content.', 5, 5)])
513
607
        # all lines must be seen at least once
514
608
        self.assertTrue(lines['base\n'] > 0)
515
609
        self.assertTrue(lines['lancestor\n'] > 0)
636
730
        self.assertRaises(errors.ReadOnlyError, vf.fix_parents, 'base', [])
637
731
        self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
638
732
        self.assertRaises(errors.ReadOnlyError, vf.clone_text, 'base', 'bar', ['foo'])
 
733
    
 
734
    def test_get_sha1(self):
 
735
        # check the sha1 data is available
 
736
        vf = self.get_file()
 
737
        # a simple file
 
738
        vf.add_lines('a', [], ['a\n'])
 
739
        # the same file, different metadata
 
740
        vf.add_lines('b', ['a'], ['a\n'])
 
741
        # a file differing only in last newline.
 
742
        vf.add_lines('c', [], ['a'])
 
743
        self.assertEqual(
 
744
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('a'))
 
745
        self.assertEqual(
 
746
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('b'))
 
747
        self.assertEqual(
 
748
            '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', vf.get_sha1('c'))
639
749
        
640
750
 
641
751
class TestWeave(TestCaseWithTransport, VersionedFileTestMixIn):
677
787
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
678
788
        return w
679
789
 
680
 
    def reopen_file(self, name='foo'):
681
 
        return WeaveFile(name, get_transport(self.get_url('.')))
 
790
    def reopen_file(self, name='foo', create=False):
 
791
        return WeaveFile(name, get_transport(self.get_url('.')), create=create)
682
792
 
683
793
    def test_no_implicit_create(self):
684
794
        self.assertRaises(errors.NoSuchFile,
705
815
        knit.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
706
816
        return knit
707
817
 
708
 
    def reopen_file(self, name='foo'):
709
 
        return KnitVersionedFile(name, get_transport(self.get_url('.')), delta=True)
 
818
    def reopen_file(self, name='foo', create=False):
 
819
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
 
820
            delta=True,
 
821
            create=create)
710
822
 
711
823
    def test_detection(self):
712
 
        print "TODO for merging: create a corrupted knit."
713
824
        knit = self.get_file()
714
825
        knit.check()
715
826
 
783
894
            versionedfile.InterVersionedFile.unregister_optimiser(InterString)
784
895
        # now we should get the default InterVersionedFile object again.
785
896
        self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
 
897
 
 
898
 
 
899
class TestReadonlyHttpMixin(object):
 
900
 
 
901
    def test_readonly_http_works(self):
 
902
        # we should be able to read from http with a versioned file.
 
903
        vf = self.get_file()
 
904
        # try an empty file access
 
905
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
 
906
        self.assertEqual([], readonly_vf.versions())
 
907
        # now with feeling.
 
908
        vf.add_lines('1', [], ['a\n'])
 
909
        vf.add_lines('2', ['1'], ['b\n', 'a\n'])
 
910
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
 
911
        self.assertEqual(['1', '2'], vf.versions())
 
912
        for version in readonly_vf.versions():
 
913
            readonly_vf.get_lines(version)
 
914
 
 
915
 
 
916
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
 
917
 
 
918
    def get_file(self):
 
919
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
 
920
 
 
921
    def get_factory(self):
 
922
        return WeaveFile
 
923
 
 
924
 
 
925
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
 
926
 
 
927
    def get_file(self):
 
928
        return KnitVersionedFile('foo', get_transport(self.get_url('.')),
 
929
                                 delta=True, create=True)
 
930
 
 
931
    def get_factory(self):
 
932
        return KnitVersionedFile
 
933
 
 
934
 
 
935
class MergeCasesMixin(object):
 
936
 
 
937
    def doMerge(self, base, a, b, mp):
 
938
        from cStringIO import StringIO
 
939
        from textwrap import dedent
 
940
 
 
941
        def addcrlf(x):
 
942
            return x + '\n'
 
943
        
 
944
        w = self.get_file()
 
945
        w.add_lines('text0', [], map(addcrlf, base))
 
946
        w.add_lines('text1', ['text0'], map(addcrlf, a))
 
947
        w.add_lines('text2', ['text0'], map(addcrlf, b))
 
948
 
 
949
        self.log_contents(w)
 
950
 
 
951
        self.log('merge plan:')
 
952
        p = list(w.plan_merge('text1', 'text2'))
 
953
        for state, line in p:
 
954
            if line:
 
955
                self.log('%12s | %s' % (state, line[:-1]))
 
956
 
 
957
        self.log('merge:')
 
958
        mt = StringIO()
 
959
        mt.writelines(w.weave_merge(p))
 
960
        mt.seek(0)
 
961
        self.log(mt.getvalue())
 
962
 
 
963
        mp = map(addcrlf, mp)
 
964
        self.assertEqual(mt.readlines(), mp)
 
965
        
 
966
        
 
967
    def testOneInsert(self):
 
968
        self.doMerge([],
 
969
                     ['aa'],
 
970
                     [],
 
971
                     ['aa'])
 
972
 
 
973
    def testSeparateInserts(self):
 
974
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
975
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
976
                     ['aaa', 'bbb', 'yyy', 'ccc'],
 
977
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
978
 
 
979
    def testSameInsert(self):
 
980
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
981
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
982
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
 
983
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
984
    overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
 
985
    def testOverlappedInsert(self):
 
986
        self.doMerge(['aaa', 'bbb'],
 
987
                     ['aaa', 'xxx', 'yyy', 'bbb'],
 
988
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
 
989
 
 
990
        # really it ought to reduce this to 
 
991
        # ['aaa', 'xxx', 'yyy', 'bbb']
 
992
 
 
993
 
 
994
    def testClashReplace(self):
 
995
        self.doMerge(['aaa'],
 
996
                     ['xxx'],
 
997
                     ['yyy', 'zzz'],
 
998
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
999
                      '>>>>>>> '])
 
1000
 
 
1001
    def testNonClashInsert1(self):
 
1002
        self.doMerge(['aaa'],
 
1003
                     ['xxx', 'aaa'],
 
1004
                     ['yyy', 'zzz'],
 
1005
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
1006
                      '>>>>>>> '])
 
1007
 
 
1008
    def testNonClashInsert2(self):
 
1009
        self.doMerge(['aaa'],
 
1010
                     ['aaa'],
 
1011
                     ['yyy', 'zzz'],
 
1012
                     ['yyy', 'zzz'])
 
1013
 
 
1014
 
 
1015
    def testDeleteAndModify(self):
 
1016
        """Clashing delete and modification.
 
1017
 
 
1018
        If one side modifies a region and the other deletes it then
 
1019
        there should be a conflict with one side blank.
 
1020
        """
 
1021
 
 
1022
        #######################################
 
1023
        # skippd, not working yet
 
1024
        return
 
1025
        
 
1026
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
1027
                     ['aaa', 'ddd', 'ccc'],
 
1028
                     ['aaa', 'ccc'],
 
1029
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
 
1030
 
 
1031
    def _test_merge_from_strings(self, base, a, b, expected):
 
1032
        w = self.get_file()
 
1033
        w.add_lines('text0', [], base.splitlines(True))
 
1034
        w.add_lines('text1', ['text0'], a.splitlines(True))
 
1035
        w.add_lines('text2', ['text0'], b.splitlines(True))
 
1036
        self.log('merge plan:')
 
1037
        p = list(w.plan_merge('text1', 'text2'))
 
1038
        for state, line in p:
 
1039
            if line:
 
1040
                self.log('%12s | %s' % (state, line[:-1]))
 
1041
        self.log('merge result:')
 
1042
        result_text = ''.join(w.weave_merge(p))
 
1043
        self.log(result_text)
 
1044
        self.assertEqualDiff(result_text, expected)
 
1045
 
 
1046
    def test_weave_merge_conflicts(self):
 
1047
        # does weave merge properly handle plans that end with unchanged?
 
1048
        result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
 
1049
        self.assertEqual(result, 'hello\n')
 
1050
 
 
1051
    def test_deletion_extended(self):
 
1052
        """One side deletes, the other deletes more.
 
1053
        """
 
1054
        base = """\
 
1055
            line 1
 
1056
            line 2
 
1057
            line 3
 
1058
            """
 
1059
        a = """\
 
1060
            line 1
 
1061
            line 2
 
1062
            """
 
1063
        b = """\
 
1064
            line 1
 
1065
            """
 
1066
        result = """\
 
1067
            line 1
 
1068
            """
 
1069
        self._test_merge_from_strings(base, a, b, result)
 
1070
 
 
1071
    def test_deletion_overlap(self):
 
1072
        """Delete overlapping regions with no other conflict.
 
1073
 
 
1074
        Arguably it'd be better to treat these as agreement, rather than 
 
1075
        conflict, but for now conflict is safer.
 
1076
        """
 
1077
        base = """\
 
1078
            start context
 
1079
            int a() {}
 
1080
            int b() {}
 
1081
            int c() {}
 
1082
            end context
 
1083
            """
 
1084
        a = """\
 
1085
            start context
 
1086
            int a() {}
 
1087
            end context
 
1088
            """
 
1089
        b = """\
 
1090
            start context
 
1091
            int c() {}
 
1092
            end context
 
1093
            """
 
1094
        result = """\
 
1095
            start context
 
1096
<<<<<<< 
 
1097
            int a() {}
 
1098
=======
 
1099
            int c() {}
 
1100
>>>>>>> 
 
1101
            end context
 
1102
            """
 
1103
        self._test_merge_from_strings(base, a, b, result)
 
1104
 
 
1105
    def test_agreement_deletion(self):
 
1106
        """Agree to delete some lines, without conflicts."""
 
1107
        base = """\
 
1108
            start context
 
1109
            base line 1
 
1110
            base line 2
 
1111
            end context
 
1112
            """
 
1113
        a = """\
 
1114
            start context
 
1115
            base line 1
 
1116
            end context
 
1117
            """
 
1118
        b = """\
 
1119
            start context
 
1120
            base line 1
 
1121
            end context
 
1122
            """
 
1123
        result = """\
 
1124
            start context
 
1125
            base line 1
 
1126
            end context
 
1127
            """
 
1128
        self._test_merge_from_strings(base, a, b, result)
 
1129
 
 
1130
    def test_sync_on_deletion(self):
 
1131
        """Specific case of merge where we can synchronize incorrectly.
 
1132
        
 
1133
        A previous version of the weave merge concluded that the two versions
 
1134
        agreed on deleting line 2, and this could be a synchronization point.
 
1135
        Line 1 was then considered in isolation, and thought to be deleted on 
 
1136
        both sides.
 
1137
 
 
1138
        It's better to consider the whole thing as a disagreement region.
 
1139
        """
 
1140
        base = """\
 
1141
            start context
 
1142
            base line 1
 
1143
            base line 2
 
1144
            end context
 
1145
            """
 
1146
        a = """\
 
1147
            start context
 
1148
            base line 1
 
1149
            a's replacement line 2
 
1150
            end context
 
1151
            """
 
1152
        b = """\
 
1153
            start context
 
1154
            b replaces
 
1155
            both lines
 
1156
            end context
 
1157
            """
 
1158
        result = """\
 
1159
            start context
 
1160
<<<<<<< 
 
1161
            base line 1
 
1162
            a's replacement line 2
 
1163
=======
 
1164
            b replaces
 
1165
            both lines
 
1166
>>>>>>> 
 
1167
            end context
 
1168
            """
 
1169
        self._test_merge_from_strings(base, a, b, result)
 
1170
 
 
1171
 
 
1172
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
 
1173
 
 
1174
    def get_file(self, name='foo'):
 
1175
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
 
1176
                                 delta=True, create=True)
 
1177
 
 
1178
    def log_contents(self, w):
 
1179
        pass
 
1180
 
 
1181
 
 
1182
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
 
1183
 
 
1184
    def get_file(self, name='foo'):
 
1185
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
 
1186
 
 
1187
    def log_contents(self, w):
 
1188
        self.log('weave is:')
 
1189
        tmpf = StringIO()
 
1190
        write_weave(w, tmpf)
 
1191
        self.log(tmpf.getvalue())
 
1192
 
 
1193
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
 
1194
                                'xxx', '>>>>>>> ', 'bbb']