/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: 2008-06-18 05:35:02 UTC
  • mto: This revision was merged to the branch mainline in revision 3510.
  • Revision ID: mbp@sourcefrog.net-20080618053502-9ogi5d5tx7w5ybf6
Change stray pdb calls to exceptions

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
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
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
19
 
20
20
 
21
21
# TODO: might be nice to create a versionedfile with some type of corruption
22
22
# considered typical and check that it can be detected/corrected.
23
23
 
24
 
from itertools import chain, izip
 
24
from itertools import chain
25
25
from StringIO import StringIO
26
26
 
 
27
import bzrlib
27
28
from bzrlib import (
28
29
    errors,
29
 
    graph as _mod_graph,
30
 
    groupcompress,
31
 
    knit as _mod_knit,
32
30
    osutils,
33
31
    progress,
34
 
    ui,
35
32
    )
36
33
from bzrlib.errors import (
37
34
                           RevisionNotPresent,
38
35
                           RevisionAlreadyPresent,
39
36
                           WeaveParentMismatch
40
37
                           )
 
38
from bzrlib import knit as _mod_knit
41
39
from bzrlib.knit import (
42
40
    cleanup_pack_knit,
43
41
    make_file_factory,
45
43
    KnitAnnotateFactory,
46
44
    KnitPlainFactory,
47
45
    )
 
46
from bzrlib.symbol_versioning import one_four, one_five
48
47
from bzrlib.tests import (
49
 
    TestCase,
50
48
    TestCaseWithMemoryTransport,
51
 
    TestNotApplicable,
 
49
    TestScenarioApplier,
52
50
    TestSkipped,
53
51
    condition_isinstance,
54
52
    split_suite_by_condition,
55
 
    multiply_tests,
 
53
    iter_suite_tests,
56
54
    )
57
55
from bzrlib.tests.http_utils import TestCaseWithWebserver
58
56
from bzrlib.trace import mutter
65
63
    ConstantMapper,
66
64
    HashEscapedPrefixMapper,
67
65
    PrefixMapper,
68
 
    VirtualVersionedFiles,
69
66
    make_versioned_files_factory,
70
67
    )
71
68
from bzrlib.weave import WeaveFile
76
73
    """Parameterize VersionedFiles tests for different implementations."""
77
74
    to_adapt, result = split_suite_by_condition(
78
75
        standard_tests, condition_isinstance(TestVersionedFiles))
 
76
    len_one_adapter = TestScenarioApplier()
 
77
    len_two_adapter = TestScenarioApplier()
79
78
    # We want to be sure of behaviour for:
80
79
    # weaves prefix layout (weave texts)
81
80
    # individually named weaves (weave inventories)
86
85
    # individual graph knits in packs (inventories)
87
86
    # individual graph nocompression knits in packs (revisions)
88
87
    # plain text knits in packs (texts)
89
 
    len_one_scenarios = [
 
88
    len_one_adapter.scenarios = [
90
89
        ('weave-named', {
91
90
            'cleanup':None,
92
91
            'factory':make_versioned_files_factory(WeaveFile,
93
92
                ConstantMapper('inventory')),
94
93
            'graph':True,
95
94
            'key_length':1,
96
 
            'support_partial_insertion': False,
97
95
            }),
98
96
        ('named-knit', {
99
97
            'cleanup':None,
100
98
            'factory':make_file_factory(False, ConstantMapper('revisions')),
101
99
            'graph':True,
102
100
            'key_length':1,
103
 
            'support_partial_insertion': False,
104
101
            }),
105
 
        ('named-nograph-nodelta-knit-pack', {
 
102
        ('named-nograph-knit-pack', {
106
103
            'cleanup':cleanup_pack_knit,
107
104
            'factory':make_pack_factory(False, False, 1),
108
105
            'graph':False,
109
106
            'key_length':1,
110
 
            'support_partial_insertion': False,
111
107
            }),
112
108
        ('named-graph-knit-pack', {
113
109
            'cleanup':cleanup_pack_knit,
114
110
            'factory':make_pack_factory(True, True, 1),
115
111
            'graph':True,
116
112
            'key_length':1,
117
 
            'support_partial_insertion': True,
118
113
            }),
119
114
        ('named-graph-nodelta-knit-pack', {
120
115
            'cleanup':cleanup_pack_knit,
121
116
            'factory':make_pack_factory(True, False, 1),
122
117
            'graph':True,
123
118
            'key_length':1,
124
 
            'support_partial_insertion': False,
125
 
            }),
126
 
        ('groupcompress-nograph', {
127
 
            'cleanup':groupcompress.cleanup_pack_group,
128
 
            'factory':groupcompress.make_pack_factory(False, False, 1),
129
 
            'graph': False,
130
 
            'key_length':1,
131
 
            'support_partial_insertion':False,
132
119
            }),
133
120
        ]
134
 
    len_two_scenarios = [
 
121
    len_two_adapter.scenarios = [
135
122
        ('weave-prefix', {
136
123
            'cleanup':None,
137
124
            'factory':make_versioned_files_factory(WeaveFile,
138
125
                PrefixMapper()),
139
126
            'graph':True,
140
127
            'key_length':2,
141
 
            'support_partial_insertion': False,
142
128
            }),
143
129
        ('annotated-knit-escape', {
144
130
            'cleanup':None,
145
131
            'factory':make_file_factory(True, HashEscapedPrefixMapper()),
146
132
            'graph':True,
147
133
            'key_length':2,
148
 
            'support_partial_insertion': False,
149
134
            }),
150
135
        ('plain-knit-pack', {
151
136
            'cleanup':cleanup_pack_knit,
152
137
            'factory':make_pack_factory(True, True, 2),
153
138
            'graph':True,
154
139
            'key_length':2,
155
 
            'support_partial_insertion': True,
156
 
            }),
157
 
        ('groupcompress', {
158
 
            'cleanup':groupcompress.cleanup_pack_group,
159
 
            'factory':groupcompress.make_pack_factory(True, False, 1),
160
 
            'graph': True,
161
 
            'key_length':1,
162
 
            'support_partial_insertion':False,
163
140
            }),
164
141
        ]
165
 
    scenarios = len_one_scenarios + len_two_scenarios
166
 
    return multiply_tests(to_adapt, scenarios, result)
 
142
    for test in iter_suite_tests(to_adapt):
 
143
        result.addTests(len_one_adapter.adapt(test))
 
144
        result.addTests(len_two_adapter.adapt(test))
 
145
    return result
167
146
 
168
147
 
169
148
def get_diamond_vf(f, trailing_eol=True, left_only=False):
170
149
    """Get a diamond graph to exercise deltas and merges.
171
 
 
 
150
    
172
151
    :param trailing_eol: If True end the last line with \n.
173
152
    """
174
153
    parents = {
195
174
 
196
175
 
197
176
def get_diamond_files(files, key_length, trailing_eol=True, left_only=False,
198
 
    nograph=False, nokeys=False):
 
177
    nograph=False):
199
178
    """Get a diamond graph to exercise deltas and merges.
200
179
 
201
180
    This creates a 5-node graph in files. If files supports 2-length keys two
202
181
    graphs are made to exercise the support for multiple ids.
203
 
 
 
182
    
204
183
    :param trailing_eol: If True end the last line with \n.
205
184
    :param key_length: The length of keys in files. Currently supports length 1
206
185
        and 2 keys.
208
187
    :param nograph: If True, do not provide parents to the add_lines calls;
209
188
        this is useful for tests that need inserted data but have graphless
210
189
        stores.
211
 
    :param nokeys: If True, pass None is as the key for all insertions.
212
 
        Currently implies nograph.
213
190
    :return: The results of the add_lines calls.
214
191
    """
215
 
    if nokeys:
216
 
        nograph = True
217
192
    if key_length == 1:
218
193
        prefixes = [()]
219
194
    else:
230
205
        else:
231
206
            result = [prefix + suffix for suffix in suffix_list]
232
207
            return result
233
 
    def get_key(suffix):
234
 
        if nokeys:
235
 
            return (None, )
236
 
        else:
237
 
            return (suffix,)
238
208
    # we loop over each key because that spreads the inserts across prefixes,
239
209
    # which is how commit operates.
240
210
    for prefix in prefixes:
241
 
        result.append(files.add_lines(prefix + get_key('origin'), (),
 
211
        result.append(files.add_lines(prefix + ('origin',), (),
242
212
            ['origin' + last_char]))
243
213
    for prefix in prefixes:
244
 
        result.append(files.add_lines(prefix + get_key('base'),
 
214
        result.append(files.add_lines(prefix + ('base',),
245
215
            get_parents([('origin',)]), ['base' + last_char]))
246
216
    for prefix in prefixes:
247
 
        result.append(files.add_lines(prefix + get_key('left'),
 
217
        result.append(files.add_lines(prefix + ('left',),
248
218
            get_parents([('base',)]),
249
219
            ['base\n', 'left' + last_char]))
250
220
    if not left_only:
251
221
        for prefix in prefixes:
252
 
            result.append(files.add_lines(prefix + get_key('right'),
 
222
            result.append(files.add_lines(prefix + ('right',),
253
223
                get_parents([('base',)]),
254
224
                ['base\n', 'right' + last_char]))
255
225
        for prefix in prefixes:
256
 
            result.append(files.add_lines(prefix + get_key('merged'),
 
226
            result.append(files.add_lines(prefix + ('merged',),
257
227
                get_parents([('left',), ('right',)]),
258
228
                ['base\n', 'left\n', 'right\n', 'merged' + last_char]))
259
229
    return result
285
255
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
286
256
            self.assertEqual(2, len(f))
287
257
            self.assertEqual(2, f.num_versions())
288
 
 
 
258
    
289
259
            self.assertRaises(RevisionNotPresent,
290
260
                f.add_lines, 'r2', ['foo'], [])
291
261
            self.assertRaises(RevisionAlreadyPresent,
330
300
        verify_file(f)
331
301
 
332
302
    def test_add_unicode_content(self):
333
 
        # unicode content is not permitted in versioned files.
 
303
        # unicode content is not permitted in versioned files. 
334
304
        # versioned files version sequences of bytes only.
335
305
        vf = self.get_file()
336
306
        self.assertRaises(errors.BzrBadParameterUnicode,
359
329
    def test_inline_newline_throws(self):
360
330
        # \r characters are not permitted in lines being added
361
331
        vf = self.get_file()
362
 
        self.assertRaises(errors.BzrBadParameterContainsNewline,
 
332
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
363
333
            vf.add_lines, 'a', [], ['a\n\n'])
364
334
        self.assertRaises(
365
335
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
531
501
        for version in multiparent.topo_iter(vf):
532
502
            mpdiff = vf.make_mpdiffs([version])[0]
533
503
            new_vf.add_mpdiffs([(version, vf.get_parent_map([version])[version],
534
 
                                 vf.get_sha1s([version])[version], mpdiff)])
 
504
                                 vf.get_sha1s([version])[0], mpdiff)])
535
505
            self.assertEqualDiff(vf.get_text(version),
536
506
                                 new_vf.get_text(version))
537
507
 
564
534
        f.add_lines('noeolbase', [], ['line'])
565
535
        # noeol preceeding its leftmost parent in the output:
566
536
        # this is done by making it a merge of two parents with no common
567
 
        # anestry: noeolbase and noeol with the
 
537
        # anestry: noeolbase and noeol with the 
568
538
        # later-inserted parent the leftmost.
569
539
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
570
540
        # two identical eol texts
651
621
        self._transaction = 'after'
652
622
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
653
623
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
654
 
 
 
624
        
655
625
    def test_copy_to(self):
656
626
        f = self.get_file()
657
627
        f.add_lines('0', [], ['a\n'])
730
700
 
731
701
    def test_iter_lines_added_or_present_in_versions(self):
732
702
        # test that we get at least an equalset of the lines added by
733
 
        # versions in the weave
 
703
        # versions in the weave 
734
704
        # the ordering here is to make a tree so that dumb searches have
735
705
        # more changes to muck up.
736
706
 
737
 
        class InstrumentedProgress(progress.ProgressTask):
 
707
        class InstrumentedProgress(progress.DummyProgress):
738
708
 
739
709
            def __init__(self):
740
 
                progress.ProgressTask.__init__(self)
 
710
 
 
711
                progress.DummyProgress.__init__(self)
741
712
                self.updates = []
742
713
 
743
714
            def update(self, msg=None, current=None, total=None):
769
740
                self.assertEqual(expected, progress.updates)
770
741
            return lines
771
742
        lines = iter_with_versions(['child', 'otherchild'],
772
 
                                   [('Walking content', 0, 2),
773
 
                                    ('Walking content', 1, 2),
774
 
                                    ('Walking content', 2, 2)])
 
743
                                   [('Walking content.', 0, 2),
 
744
                                    ('Walking content.', 1, 2),
 
745
                                    ('Walking content.', 2, 2)])
775
746
        # we must see child and otherchild
776
747
        self.assertTrue(lines[('child\n', 'child')] > 0)
777
748
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
778
749
        # we dont care if we got more than that.
779
 
 
 
750
        
780
751
        # test all lines
781
 
        lines = iter_with_versions(None, [('Walking content', 0, 5),
782
 
                                          ('Walking content', 1, 5),
783
 
                                          ('Walking content', 2, 5),
784
 
                                          ('Walking content', 3, 5),
785
 
                                          ('Walking content', 4, 5),
786
 
                                          ('Walking content', 5, 5)])
 
752
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
 
753
                                          ('Walking content.', 1, 5),
 
754
                                          ('Walking content.', 2, 5),
 
755
                                          ('Walking content.', 3, 5),
 
756
                                          ('Walking content.', 4, 5),
 
757
                                          ('Walking content.', 5, 5)])
787
758
        # all lines must be seen at least once
788
759
        self.assertTrue(lines[('base\n', 'base')] > 0)
789
760
        self.assertTrue(lines[('lancestor\n', 'lancestor')] > 0)
859
830
                          'base',
860
831
                          [],
861
832
                          [])
862
 
 
 
833
    
863
834
    def test_get_sha1s(self):
864
835
        # check the sha1 data is available
865
836
        vf = self.get_file()
869
840
        vf.add_lines('b', ['a'], ['a\n'])
870
841
        # a file differing only in last newline.
871
842
        vf.add_lines('c', [], ['a'])
872
 
        self.assertEqual({
873
 
            'a': '3f786850e387550fdab836ed7e6dc881de23001b',
874
 
            'c': '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
875
 
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
876
 
            },
877
 
            vf.get_sha1s(['a', 'c', 'b']))
878
 
 
 
843
        self.assertEqual(['3f786850e387550fdab836ed7e6dc881de23001b',
 
844
                          '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
 
845
                          '3f786850e387550fdab836ed7e6dc881de23001b'],
 
846
                          vf.get_sha1s(['a', 'c', 'b']))
 
847
        
879
848
 
880
849
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
881
850
 
888
857
            get_scope=self.get_transaction)
889
858
        w.add_lines('v1', [], ['hello\n'])
890
859
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
891
 
 
 
860
        
892
861
        # We are going to invasively corrupt the text
893
862
        # Make sure the internals of weave are the same
894
863
        self.assertEqual([('{', 0)
898
867
                        , 'there\n'
899
868
                        , ('}', None)
900
869
                        ], w._weave)
901
 
 
 
870
        
902
871
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
903
872
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
904
873
                        ], w._sha1s)
905
874
        w.check()
906
 
 
 
875
        
907
876
        # Corrupted
908
877
        w._weave[4] = 'There\n'
909
878
        return w
913
882
        # Corrected
914
883
        w._weave[4] = 'there\n'
915
884
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
916
 
 
 
885
        
917
886
        #Invalid checksum, first digit changed
918
887
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
919
888
        return w
1028
997
 
1029
998
        def addcrlf(x):
1030
999
            return x + '\n'
1031
 
 
 
1000
        
1032
1001
        w = self.get_file()
1033
1002
        w.add_lines('text0', [], map(addcrlf, base))
1034
1003
        w.add_lines('text1', ['text0'], map(addcrlf, a))
1050
1019
 
1051
1020
        mp = map(addcrlf, mp)
1052
1021
        self.assertEqual(mt.readlines(), mp)
1053
 
 
1054
 
 
 
1022
        
 
1023
        
1055
1024
    def testOneInsert(self):
1056
1025
        self.doMerge([],
1057
1026
                     ['aa'],
1075
1044
                     ['aaa', 'xxx', 'yyy', 'bbb'],
1076
1045
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1077
1046
 
1078
 
        # really it ought to reduce this to
 
1047
        # really it ought to reduce this to 
1079
1048
        # ['aaa', 'xxx', 'yyy', 'bbb']
1080
1049
 
1081
1050
 
1083
1052
        self.doMerge(['aaa'],
1084
1053
                     ['xxx'],
1085
1054
                     ['yyy', 'zzz'],
1086
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
 
1055
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
1087
1056
                      '>>>>>>> '])
1088
1057
 
1089
1058
    def testNonClashInsert1(self):
1090
1059
        self.doMerge(['aaa'],
1091
1060
                     ['xxx', 'aaa'],
1092
1061
                     ['yyy', 'zzz'],
1093
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
 
1062
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
1094
1063
                      '>>>>>>> '])
1095
1064
 
1096
1065
    def testNonClashInsert2(self):
1110
1079
        #######################################
1111
1080
        # skippd, not working yet
1112
1081
        return
1113
 
 
 
1082
        
1114
1083
        self.doMerge(['aaa', 'bbb', 'ccc'],
1115
1084
                     ['aaa', 'ddd', 'ccc'],
1116
1085
                     ['aaa', 'ccc'],
1153
1122
            """
1154
1123
        result = """\
1155
1124
            line 1
1156
 
<<<<<<<\x20
1157
 
            line 2
1158
 
=======
1159
 
>>>>>>>\x20
1160
1125
            """
1161
1126
        self._test_merge_from_strings(base, a, b, result)
1162
1127
 
1163
1128
    def test_deletion_overlap(self):
1164
1129
        """Delete overlapping regions with no other conflict.
1165
1130
 
1166
 
        Arguably it'd be better to treat these as agreement, rather than
 
1131
        Arguably it'd be better to treat these as agreement, rather than 
1167
1132
        conflict, but for now conflict is safer.
1168
1133
        """
1169
1134
        base = """\
1185
1150
            """
1186
1151
        result = """\
1187
1152
            start context
1188
 
<<<<<<<\x20
 
1153
<<<<<<< 
1189
1154
            int a() {}
1190
1155
=======
1191
1156
            int c() {}
1192
 
>>>>>>>\x20
 
1157
>>>>>>> 
1193
1158
            end context
1194
1159
            """
1195
1160
        self._test_merge_from_strings(base, a, b, result)
1221
1186
 
1222
1187
    def test_sync_on_deletion(self):
1223
1188
        """Specific case of merge where we can synchronize incorrectly.
1224
 
 
 
1189
        
1225
1190
        A previous version of the weave merge concluded that the two versions
1226
1191
        agreed on deleting line 2, and this could be a synchronization point.
1227
 
        Line 1 was then considered in isolation, and thought to be deleted on
 
1192
        Line 1 was then considered in isolation, and thought to be deleted on 
1228
1193
        both sides.
1229
1194
 
1230
1195
        It's better to consider the whole thing as a disagreement region.
1249
1214
            """
1250
1215
        result = """\
1251
1216
            start context
1252
 
<<<<<<<\x20
 
1217
<<<<<<< 
1253
1218
            base line 1
1254
1219
            a's replacement line 2
1255
1220
=======
1256
1221
            b replaces
1257
1222
            both lines
1258
 
>>>>>>>\x20
 
1223
>>>>>>> 
1259
1224
            end context
1260
1225
            """
1261
1226
        self._test_merge_from_strings(base, a, b, result)
1272
1237
        write_weave(w, tmpf)
1273
1238
        self.log(tmpf.getvalue())
1274
1239
 
1275
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
 
1240
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1276
1241
                                'xxx', '>>>>>>> ', 'bbb']
1277
1242
 
1278
1243
 
1310
1275
        # origin is a fulltext
1311
1276
        entries = f.get_record_stream([('origin',)], 'unordered', False)
1312
1277
        base = entries.next()
1313
 
        ft_data = ft_adapter.get_bytes(base)
 
1278
        ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
1314
1279
        # merged is both a delta and multiple parents.
1315
1280
        entries = f.get_record_stream([('merged',)], 'unordered', False)
1316
1281
        merged = entries.next()
1317
 
        delta_data = delta_adapter.get_bytes(merged)
 
1282
        delta_data = delta_adapter.get_bytes(merged,
 
1283
            merged.get_bytes_as(merged.storage_kind))
1318
1284
        return ft_data, delta_data
1319
1285
 
1320
1286
    def test_deannotation_noeol(self):
1387
1353
 
1388
1354
    def test_unannotated_to_fulltext(self):
1389
1355
        """Test adapting unannotated knits to full texts.
1390
 
 
 
1356
        
1391
1357
        This is used for -> weaves, and for -> annotated knits.
1392
1358
        """
1393
1359
        # we need a full text, and a delta
1406
1372
 
1407
1373
    def test_unannotated_to_fulltext_no_eol(self):
1408
1374
        """Test adapting unannotated knits to full texts.
1409
 
 
 
1375
        
1410
1376
        This is used for -> weaves, and for -> annotated knits.
1411
1377
        """
1412
1378
        # we need a full text, and a delta
1469
1435
            transport.mkdir('.')
1470
1436
        files = self.factory(transport)
1471
1437
        if self.cleanup is not None:
1472
 
            self.addCleanup(self.cleanup, files)
 
1438
            self.addCleanup(lambda:self.cleanup(files))
1473
1439
        return files
1474
1440
 
1475
 
    def get_simple_key(self, suffix):
1476
 
        """Return a key for the object under test."""
1477
 
        if self.key_length == 1:
1478
 
            return (suffix,)
1479
 
        else:
1480
 
            return ('FileA',) + (suffix,)
1481
 
 
1482
 
    def test_add_lines(self):
1483
 
        f = self.get_versionedfiles()
1484
 
        key0 = self.get_simple_key('r0')
1485
 
        key1 = self.get_simple_key('r1')
1486
 
        key2 = self.get_simple_key('r2')
1487
 
        keyf = self.get_simple_key('foo')
1488
 
        f.add_lines(key0, [], ['a\n', 'b\n'])
1489
 
        if self.graph:
1490
 
            f.add_lines(key1, [key0], ['b\n', 'c\n'])
1491
 
        else:
1492
 
            f.add_lines(key1, [], ['b\n', 'c\n'])
1493
 
        keys = f.keys()
1494
 
        self.assertTrue(key0 in keys)
1495
 
        self.assertTrue(key1 in keys)
1496
 
        records = []
1497
 
        for record in f.get_record_stream([key0, key1], 'unordered', True):
1498
 
            records.append((record.key, record.get_bytes_as('fulltext')))
1499
 
        records.sort()
1500
 
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
1501
 
 
1502
 
    def test__add_text(self):
1503
 
        f = self.get_versionedfiles()
1504
 
        key0 = self.get_simple_key('r0')
1505
 
        key1 = self.get_simple_key('r1')
1506
 
        key2 = self.get_simple_key('r2')
1507
 
        keyf = self.get_simple_key('foo')
1508
 
        f._add_text(key0, [], 'a\nb\n')
1509
 
        if self.graph:
1510
 
            f._add_text(key1, [key0], 'b\nc\n')
1511
 
        else:
1512
 
            f._add_text(key1, [], 'b\nc\n')
1513
 
        keys = f.keys()
1514
 
        self.assertTrue(key0 in keys)
1515
 
        self.assertTrue(key1 in keys)
1516
 
        records = []
1517
 
        for record in f.get_record_stream([key0, key1], 'unordered', True):
1518
 
            records.append((record.key, record.get_bytes_as('fulltext')))
1519
 
        records.sort()
1520
 
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
1521
 
 
1522
1441
    def test_annotate(self):
1523
1442
        files = self.get_versionedfiles()
1524
1443
        self.get_diamond_files(files)
1558
1477
        self.assertRaises(RevisionNotPresent,
1559
1478
            files.annotate, prefix + ('missing-key',))
1560
1479
 
1561
 
    def test_check_no_parameters(self):
1562
 
        files = self.get_versionedfiles()
1563
 
 
1564
 
    def test_check_progressbar_parameter(self):
1565
 
        """A progress bar can be supplied because check can be a generator."""
1566
 
        pb = ui.ui_factory.nested_progress_bar()
1567
 
        self.addCleanup(pb.finished)
1568
 
        files = self.get_versionedfiles()
1569
 
        files.check(progress_bar=pb)
1570
 
 
1571
 
    def test_check_with_keys_becomes_generator(self):
1572
 
        files = self.get_versionedfiles()
1573
 
        self.get_diamond_files(files)
1574
 
        keys = files.keys()
1575
 
        entries = files.check(keys=keys)
1576
 
        seen = set()
1577
 
        # Texts output should be fulltexts.
1578
 
        self.capture_stream(files, entries, seen.add,
1579
 
            files.get_parent_map(keys), require_fulltext=True)
1580
 
        # All texts should be output.
1581
 
        self.assertEqual(set(keys), seen)
1582
 
 
1583
 
    def test_clear_cache(self):
1584
 
        files = self.get_versionedfiles()
1585
 
        files.clear_cache()
1586
 
 
1587
1480
    def test_construct(self):
1588
1481
        """Each parameterised test can be constructed on a transport."""
1589
1482
        files = self.get_versionedfiles()
1590
1483
 
1591
 
    def get_diamond_files(self, files, trailing_eol=True, left_only=False,
1592
 
        nokeys=False):
 
1484
    def get_diamond_files(self, files, trailing_eol=True, left_only=False):
1593
1485
        return get_diamond_files(files, self.key_length,
1594
1486
            trailing_eol=trailing_eol, nograph=not self.graph,
1595
 
            left_only=left_only, nokeys=nokeys)
1596
 
 
1597
 
    def _add_content_nostoresha(self, add_lines):
1598
 
        """When nostore_sha is supplied using old content raises."""
1599
 
        vf = self.get_versionedfiles()
1600
 
        empty_text = ('a', [])
1601
 
        sample_text_nl = ('b', ["foo\n", "bar\n"])
1602
 
        sample_text_no_nl = ('c', ["foo\n", "bar"])
1603
 
        shas = []
1604
 
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
1605
 
            if add_lines:
1606
 
                sha, _, _ = vf.add_lines(self.get_simple_key(version), [],
1607
 
                                         lines)
1608
 
            else:
1609
 
                sha, _, _ = vf._add_text(self.get_simple_key(version), [],
1610
 
                                         ''.join(lines))
1611
 
            shas.append(sha)
1612
 
        # we now have a copy of all the lines in the vf.
1613
 
        for sha, (version, lines) in zip(
1614
 
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
1615
 
            new_key = self.get_simple_key(version + "2")
1616
 
            self.assertRaises(errors.ExistingContent,
1617
 
                vf.add_lines, new_key, [], lines,
1618
 
                nostore_sha=sha)
1619
 
            self.assertRaises(errors.ExistingContent,
1620
 
                vf._add_text, new_key, [], ''.join(lines),
1621
 
                nostore_sha=sha)
1622
 
            # and no new version should have been added.
1623
 
            record = vf.get_record_stream([new_key], 'unordered', True).next()
1624
 
            self.assertEqual('absent', record.storage_kind)
1625
 
 
1626
 
    def test_add_lines_nostoresha(self):
1627
 
        self._add_content_nostoresha(add_lines=True)
1628
 
 
1629
 
    def test__add_text_nostoresha(self):
1630
 
        self._add_content_nostoresha(add_lines=False)
 
1487
            left_only=left_only)
1631
1488
 
1632
1489
    def test_add_lines_return(self):
1633
1490
        files = self.get_versionedfiles()
1660
1517
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1661
1518
                results)
1662
1519
 
1663
 
    def test_add_lines_no_key_generates_chk_key(self):
1664
 
        files = self.get_versionedfiles()
1665
 
        # save code by using the stock data insertion helper.
1666
 
        adds = self.get_diamond_files(files, nokeys=True)
1667
 
        results = []
1668
 
        # We can only validate the first 2 elements returned from add_lines.
1669
 
        for add in adds:
1670
 
            self.assertEqual(3, len(add))
1671
 
            results.append(add[:2])
1672
 
        if self.key_length == 1:
1673
 
            self.assertEqual([
1674
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1675
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1676
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1677
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1678
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1679
 
                results)
1680
 
            # Check the added items got CHK keys.
1681
 
            self.assertEqual(set([
1682
 
                ('sha1:00e364d235126be43292ab09cb4686cf703ddc17',),
1683
 
                ('sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',),
1684
 
                ('sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',),
1685
 
                ('sha1:a8478686da38e370e32e42e8a0c220e33ee9132f',),
1686
 
                ('sha1:ed8bce375198ea62444dc71952b22cfc2b09226d',),
1687
 
                ]),
1688
 
                files.keys())
1689
 
        elif self.key_length == 2:
1690
 
            self.assertEqual([
1691
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1692
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1693
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1694
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1695
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1696
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1697
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1698
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1699
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
1700
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1701
 
                results)
1702
 
            # Check the added items got CHK keys.
1703
 
            self.assertEqual(set([
1704
 
                ('FileA', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1705
 
                ('FileA', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1706
 
                ('FileA', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1707
 
                ('FileA', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1708
 
                ('FileA', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1709
 
                ('FileB', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1710
 
                ('FileB', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1711
 
                ('FileB', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1712
 
                ('FileB', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1713
 
                ('FileB', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1714
 
                ]),
1715
 
                files.keys())
1716
 
 
1717
1520
    def test_empty_lines(self):
1718
1521
        """Empty files can be stored."""
1719
1522
        f = self.get_versionedfiles()
1741
1544
            f.get_record_stream([key_b], 'unordered', True
1742
1545
                ).next().get_bytes_as('fulltext'))
1743
1546
 
1744
 
    def test_get_known_graph_ancestry(self):
1745
 
        f = self.get_versionedfiles()
1746
 
        if not self.graph:
1747
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
1748
 
        key_a = self.get_simple_key('a')
1749
 
        key_b = self.get_simple_key('b')
1750
 
        key_c = self.get_simple_key('c')
1751
 
        # A
1752
 
        # |\
1753
 
        # | B
1754
 
        # |/
1755
 
        # C
1756
 
        f.add_lines(key_a, [], ['\n'])
1757
 
        f.add_lines(key_b, [key_a], ['\n'])
1758
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
1759
 
        kg = f.get_known_graph_ancestry([key_c])
1760
 
        self.assertIsInstance(kg, _mod_graph.KnownGraph)
1761
 
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1762
 
 
1763
 
    def test_known_graph_with_fallbacks(self):
1764
 
        f = self.get_versionedfiles('files')
1765
 
        if not self.graph:
1766
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
1767
 
        if getattr(f, 'add_fallback_versioned_files', None) is None:
1768
 
            raise TestNotApplicable("%s doesn't support fallbacks"
1769
 
                                    % (f.__class__.__name__,))
1770
 
        key_a = self.get_simple_key('a')
1771
 
        key_b = self.get_simple_key('b')
1772
 
        key_c = self.get_simple_key('c')
1773
 
        # A     only in fallback
1774
 
        # |\
1775
 
        # | B
1776
 
        # |/
1777
 
        # C
1778
 
        g = self.get_versionedfiles('fallback')
1779
 
        g.add_lines(key_a, [], ['\n'])
1780
 
        f.add_fallback_versioned_files(g)
1781
 
        f.add_lines(key_b, [key_a], ['\n'])
1782
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
1783
 
        kg = f.get_known_graph_ancestry([key_c])
1784
 
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1785
 
 
1786
1547
    def test_get_record_stream_empty(self):
1787
1548
        """An empty stream can be requested without error."""
1788
1549
        f = self.get_versionedfiles()
1793
1554
        """Assert that storage_kind is a valid storage_kind."""
1794
1555
        self.assertSubset([storage_kind],
1795
1556
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1796
 
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1797
 
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1798
 
             'knit-delta-gz',
1799
 
             'knit-delta-closure', 'knit-delta-closure-ref',
1800
 
             'groupcompress-block', 'groupcompress-block-ref'])
 
1557
             'knit-ft', 'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
 
1558
             'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'])
1801
1559
 
1802
 
    def capture_stream(self, f, entries, on_seen, parents,
1803
 
        require_fulltext=False):
 
1560
    def capture_stream(self, f, entries, on_seen, parents):
1804
1561
        """Capture a stream for testing."""
1805
1562
        for factory in entries:
1806
1563
            on_seen(factory.key)
1807
1564
            self.assertValidStorageKind(factory.storage_kind)
1808
 
            if factory.sha1 is not None:
1809
 
                self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1810
 
                    factory.sha1)
 
1565
            self.assertEqual(f.get_sha1s([factory.key])[0], factory.sha1)
1811
1566
            self.assertEqual(parents[factory.key], factory.parents)
1812
1567
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1813
1568
                str)
1814
 
            if require_fulltext:
1815
 
                factory.get_bytes_as('fulltext')
1816
1569
 
1817
1570
    def test_get_record_stream_interface(self):
1818
1571
        """each item in a stream has to provide a regular interface."""
1825
1578
        self.capture_stream(files, entries, seen.add, parent_map)
1826
1579
        self.assertEqual(set(keys), seen)
1827
1580
 
 
1581
    def get_simple_key(self, suffix):
 
1582
        """Return a key for the object under test."""
 
1583
        if self.key_length == 1:
 
1584
            return (suffix,)
 
1585
        else:
 
1586
            return ('FileA',) + (suffix,)
 
1587
 
1828
1588
    def get_keys_and_sort_order(self):
1829
1589
        """Get diamond test keys list, and their sort ordering."""
1830
1590
        if self.key_length == 1:
1845
1605
                }
1846
1606
        return keys, sort_order
1847
1607
 
1848
 
    def get_keys_and_groupcompress_sort_order(self):
1849
 
        """Get diamond test keys list, and their groupcompress sort ordering."""
1850
 
        if self.key_length == 1:
1851
 
            keys = [('merged',), ('left',), ('right',), ('base',)]
1852
 
            sort_order = {('merged',):0, ('left',):1, ('right',):1, ('base',):2}
1853
 
        else:
1854
 
            keys = [
1855
 
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1856
 
                ('FileA', 'base'),
1857
 
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1858
 
                ('FileB', 'base'),
1859
 
                ]
1860
 
            sort_order = {
1861
 
                ('FileA', 'merged'):0, ('FileA', 'left'):1, ('FileA', 'right'):1,
1862
 
                ('FileA', 'base'):2,
1863
 
                ('FileB', 'merged'):3, ('FileB', 'left'):4, ('FileB', 'right'):4,
1864
 
                ('FileB', 'base'):5,
1865
 
                }
1866
 
        return keys, sort_order
1867
 
 
1868
1608
    def test_get_record_stream_interface_ordered(self):
1869
1609
        """each item in a stream has to provide a regular interface."""
1870
1610
        files = self.get_versionedfiles()
1887
1627
        for factory in entries:
1888
1628
            seen.append(factory.key)
1889
1629
            self.assertValidStorageKind(factory.storage_kind)
1890
 
            self.assertSubset([factory.sha1],
1891
 
                [None, files.get_sha1s([factory.key])[factory.key]])
 
1630
            self.assertSubset([factory.sha1], [None, files.get_sha1s([factory.key])[0]])
1892
1631
            self.assertEqual(parent_map[factory.key], factory.parents)
1893
1632
            # self.assertEqual(files.get_text(factory.key),
1894
 
            ft_bytes = factory.get_bytes_as('fulltext')
1895
 
            self.assertIsInstance(ft_bytes, str)
1896
 
            chunked_bytes = factory.get_bytes_as('chunked')
1897
 
            self.assertEqualDiff(ft_bytes, ''.join(chunked_bytes))
1898
 
 
1899
 
        self.assertStreamOrder(sort_order, seen, keys)
1900
 
 
1901
 
    def test_get_record_stream_interface_groupcompress(self):
1902
 
        """each item in a stream has to provide a regular interface."""
1903
 
        files = self.get_versionedfiles()
1904
 
        self.get_diamond_files(files)
1905
 
        keys, sort_order = self.get_keys_and_groupcompress_sort_order()
1906
 
        parent_map = files.get_parent_map(keys)
1907
 
        entries = files.get_record_stream(keys, 'groupcompress', False)
1908
 
        seen = []
1909
 
        self.capture_stream(files, entries, seen.append, parent_map)
 
1633
            self.assertIsInstance(factory.get_bytes_as('fulltext'), str)
 
1634
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
 
1635
                str)
1910
1636
        self.assertStreamOrder(sort_order, seen, keys)
1911
1637
 
1912
1638
    def assertStreamOrder(self, sort_order, seen, keys):
1945
1671
        for factory in entries:
1946
1672
            seen.add(factory.key)
1947
1673
            self.assertValidStorageKind(factory.storage_kind)
1948
 
            if factory.sha1 is not None:
1949
 
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1950
 
                                 factory.sha1)
 
1674
            self.assertEqual(files.get_sha1s([factory.key])[0], factory.sha1)
1951
1675
            self.assertEqual(parent_map[factory.key], factory.parents)
1952
1676
            # currently no stream emits mpdiff
1953
1677
            self.assertRaises(errors.UnavailableRepresentation,
1975
1699
        entries = files.get_record_stream(keys, 'topological', False)
1976
1700
        self.assertAbsentRecord(files, keys, parent_map, entries)
1977
1701
 
1978
 
    def assertRecordHasContent(self, record, bytes):
1979
 
        """Assert that record has the bytes bytes."""
1980
 
        self.assertEqual(bytes, record.get_bytes_as('fulltext'))
1981
 
        self.assertEqual(bytes, ''.join(record.get_bytes_as('chunked')))
1982
 
 
1983
 
    def test_get_record_stream_native_formats_are_wire_ready_one_ft(self):
1984
 
        files = self.get_versionedfiles()
1985
 
        key = self.get_simple_key('foo')
1986
 
        files.add_lines(key, (), ['my text\n', 'content'])
1987
 
        stream = files.get_record_stream([key], 'unordered', False)
1988
 
        record = stream.next()
1989
 
        if record.storage_kind in ('chunked', 'fulltext'):
1990
 
            # chunked and fulltext representations are for direct use not wire
1991
 
            # serialisation: check they are able to be used directly. To send
1992
 
            # such records over the wire translation will be needed.
1993
 
            self.assertRecordHasContent(record, "my text\ncontent")
1994
 
        else:
1995
 
            bytes = [record.get_bytes_as(record.storage_kind)]
1996
 
            network_stream = versionedfile.NetworkRecordStream(bytes).read()
1997
 
            source_record = record
1998
 
            records = []
1999
 
            for record in network_stream:
2000
 
                records.append(record)
2001
 
                self.assertEqual(source_record.storage_kind,
2002
 
                    record.storage_kind)
2003
 
                self.assertEqual(source_record.parents, record.parents)
2004
 
                self.assertEqual(
2005
 
                    source_record.get_bytes_as(source_record.storage_kind),
2006
 
                    record.get_bytes_as(record.storage_kind))
2007
 
            self.assertEqual(1, len(records))
2008
 
 
2009
 
    def assertStreamMetaEqual(self, records, expected, stream):
2010
 
        """Assert that streams expected and stream have the same records.
2011
 
 
2012
 
        :param records: A list to collect the seen records.
2013
 
        :return: A generator of the records in stream.
2014
 
        """
2015
 
        # We make assertions during copying to catch things early for
2016
 
        # easier debugging.
2017
 
        for record, ref_record in izip(stream, expected):
2018
 
            records.append(record)
2019
 
            self.assertEqual(ref_record.key, record.key)
2020
 
            self.assertEqual(ref_record.storage_kind, record.storage_kind)
2021
 
            self.assertEqual(ref_record.parents, record.parents)
2022
 
            yield record
2023
 
 
2024
 
    def stream_to_bytes_or_skip_counter(self, skipped_records, full_texts,
2025
 
        stream):
2026
 
        """Convert a stream to a bytes iterator.
2027
 
 
2028
 
        :param skipped_records: A list with one element to increment when a
2029
 
            record is skipped.
2030
 
        :param full_texts: A dict from key->fulltext representation, for
2031
 
            checking chunked or fulltext stored records.
2032
 
        :param stream: A record_stream.
2033
 
        :return: An iterator over the bytes of each record.
2034
 
        """
2035
 
        for record in stream:
2036
 
            if record.storage_kind in ('chunked', 'fulltext'):
2037
 
                skipped_records[0] += 1
2038
 
                # check the content is correct for direct use.
2039
 
                self.assertRecordHasContent(record, full_texts[record.key])
2040
 
            else:
2041
 
                yield record.get_bytes_as(record.storage_kind)
2042
 
 
2043
 
    def test_get_record_stream_native_formats_are_wire_ready_ft_delta(self):
2044
 
        files = self.get_versionedfiles()
2045
 
        target_files = self.get_versionedfiles('target')
2046
 
        key = self.get_simple_key('ft')
2047
 
        key_delta = self.get_simple_key('delta')
2048
 
        files.add_lines(key, (), ['my text\n', 'content'])
2049
 
        if self.graph:
2050
 
            delta_parents = (key,)
2051
 
        else:
2052
 
            delta_parents = ()
2053
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
2054
 
        local = files.get_record_stream([key, key_delta], 'unordered', False)
2055
 
        ref = files.get_record_stream([key, key_delta], 'unordered', False)
2056
 
        skipped_records = [0]
2057
 
        full_texts = {
2058
 
            key: "my text\ncontent",
2059
 
            key_delta: "different\ncontent\n",
2060
 
            }
2061
 
        byte_stream = self.stream_to_bytes_or_skip_counter(
2062
 
            skipped_records, full_texts, local)
2063
 
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
2064
 
        records = []
2065
 
        # insert the stream from the network into a versioned files object so we can
2066
 
        # check the content was carried across correctly without doing delta
2067
 
        # inspection.
2068
 
        target_files.insert_record_stream(
2069
 
            self.assertStreamMetaEqual(records, ref, network_stream))
2070
 
        # No duplicates on the wire thank you!
2071
 
        self.assertEqual(2, len(records) + skipped_records[0])
2072
 
        if len(records):
2073
 
            # if any content was copied it all must have all been.
2074
 
            self.assertIdenticalVersionedFile(files, target_files)
2075
 
 
2076
 
    def test_get_record_stream_native_formats_are_wire_ready_delta(self):
2077
 
        # copy a delta over the wire
2078
 
        files = self.get_versionedfiles()
2079
 
        target_files = self.get_versionedfiles('target')
2080
 
        key = self.get_simple_key('ft')
2081
 
        key_delta = self.get_simple_key('delta')
2082
 
        files.add_lines(key, (), ['my text\n', 'content'])
2083
 
        if self.graph:
2084
 
            delta_parents = (key,)
2085
 
        else:
2086
 
            delta_parents = ()
2087
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
2088
 
        # Copy the basis text across so we can reconstruct the delta during
2089
 
        # insertion into target.
2090
 
        target_files.insert_record_stream(files.get_record_stream([key],
2091
 
            'unordered', False))
2092
 
        local = files.get_record_stream([key_delta], 'unordered', False)
2093
 
        ref = files.get_record_stream([key_delta], 'unordered', False)
2094
 
        skipped_records = [0]
2095
 
        full_texts = {
2096
 
            key_delta: "different\ncontent\n",
2097
 
            }
2098
 
        byte_stream = self.stream_to_bytes_or_skip_counter(
2099
 
            skipped_records, full_texts, local)
2100
 
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
2101
 
        records = []
2102
 
        # insert the stream from the network into a versioned files object so we can
2103
 
        # check the content was carried across correctly without doing delta
2104
 
        # inspection during check_stream.
2105
 
        target_files.insert_record_stream(
2106
 
            self.assertStreamMetaEqual(records, ref, network_stream))
2107
 
        # No duplicates on the wire thank you!
2108
 
        self.assertEqual(1, len(records) + skipped_records[0])
2109
 
        if len(records):
2110
 
            # if any content was copied it all must have all been
2111
 
            self.assertIdenticalVersionedFile(files, target_files)
2112
 
 
2113
 
    def test_get_record_stream_wire_ready_delta_closure_included(self):
2114
 
        # copy a delta over the wire with the ability to get its full text.
2115
 
        files = self.get_versionedfiles()
2116
 
        key = self.get_simple_key('ft')
2117
 
        key_delta = self.get_simple_key('delta')
2118
 
        files.add_lines(key, (), ['my text\n', 'content'])
2119
 
        if self.graph:
2120
 
            delta_parents = (key,)
2121
 
        else:
2122
 
            delta_parents = ()
2123
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
2124
 
        local = files.get_record_stream([key_delta], 'unordered', True)
2125
 
        ref = files.get_record_stream([key_delta], 'unordered', True)
2126
 
        skipped_records = [0]
2127
 
        full_texts = {
2128
 
            key_delta: "different\ncontent\n",
2129
 
            }
2130
 
        byte_stream = self.stream_to_bytes_or_skip_counter(
2131
 
            skipped_records, full_texts, local)
2132
 
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
2133
 
        records = []
2134
 
        # insert the stream from the network into a versioned files object so we can
2135
 
        # check the content was carried across correctly without doing delta
2136
 
        # inspection during check_stream.
2137
 
        for record in self.assertStreamMetaEqual(records, ref, network_stream):
2138
 
            # we have to be able to get the full text out:
2139
 
            self.assertRecordHasContent(record, full_texts[record.key])
2140
 
        # No duplicates on the wire thank you!
2141
 
        self.assertEqual(1, len(records) + skipped_records[0])
2142
 
 
2143
1702
    def assertAbsentRecord(self, files, keys, parents, entries):
2144
1703
        """Helper for test_get_record_stream_missing_records_are_absent."""
2145
1704
        seen = set()
2151
1710
                self.assertEqual(None, factory.parents)
2152
1711
            else:
2153
1712
                self.assertValidStorageKind(factory.storage_kind)
2154
 
                if factory.sha1 is not None:
2155
 
                    sha1 = files.get_sha1s([factory.key])[factory.key]
2156
 
                    self.assertEqual(sha1, factory.sha1)
 
1713
                self.assertEqual(files.get_sha1s([factory.key])[0], factory.sha1)
2157
1714
                self.assertEqual(parents[factory.key], factory.parents)
2158
1715
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
2159
1716
                    str)
2193
1750
        else:
2194
1751
            return None
2195
1752
 
2196
 
    def test_get_annotator(self):
2197
 
        files = self.get_versionedfiles()
2198
 
        self.get_diamond_files(files)
2199
 
        origin_key = self.get_simple_key('origin')
2200
 
        base_key = self.get_simple_key('base')
2201
 
        left_key = self.get_simple_key('left')
2202
 
        right_key = self.get_simple_key('right')
2203
 
        merged_key = self.get_simple_key('merged')
2204
 
        # annotator = files.get_annotator()
2205
 
        # introduced full text
2206
 
        origins, lines = files.get_annotator().annotate(origin_key)
2207
 
        self.assertEqual([(origin_key,)], origins)
2208
 
        self.assertEqual(['origin\n'], lines)
2209
 
        # a delta
2210
 
        origins, lines = files.get_annotator().annotate(base_key)
2211
 
        self.assertEqual([(base_key,)], origins)
2212
 
        # a merge
2213
 
        origins, lines = files.get_annotator().annotate(merged_key)
2214
 
        if self.graph:
2215
 
            self.assertEqual([
2216
 
                (base_key,),
2217
 
                (left_key,),
2218
 
                (right_key,),
2219
 
                (merged_key,),
2220
 
                ], origins)
2221
 
        else:
2222
 
            # Without a graph everything is new.
2223
 
            self.assertEqual([
2224
 
                (merged_key,),
2225
 
                (merged_key,),
2226
 
                (merged_key,),
2227
 
                (merged_key,),
2228
 
                ], origins)
2229
 
        self.assertRaises(RevisionNotPresent,
2230
 
            files.get_annotator().annotate, self.get_simple_key('missing-key'))
2231
 
 
2232
1753
    def test_get_parent_map(self):
2233
1754
        files = self.get_versionedfiles()
2234
1755
        if self.key_length == 1:
2277
1798
                ('FileA', 'base'), ('FileB', 'origin'), ('FileA', 'left'),
2278
1799
                ('FileA', 'merged'), ('FileB', 'right'),
2279
1800
                ]
2280
 
        self.assertEqual({
2281
 
            keys[0]: '51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',
2282
 
            keys[1]: '00e364d235126be43292ab09cb4686cf703ddc17',
2283
 
            keys[2]: 'a8478686da38e370e32e42e8a0c220e33ee9132f',
2284
 
            keys[3]: 'ed8bce375198ea62444dc71952b22cfc2b09226d',
2285
 
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
2286
 
            },
 
1801
        self.assertEqual([
 
1802
            '51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',
 
1803
            '00e364d235126be43292ab09cb4686cf703ddc17',
 
1804
            'a8478686da38e370e32e42e8a0c220e33ee9132f',
 
1805
            'ed8bce375198ea62444dc71952b22cfc2b09226d',
 
1806
            '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
 
1807
            ],
2287
1808
            files.get_sha1s(keys))
2288
 
 
 
1809
        
2289
1810
    def test_insert_record_stream_empty(self):
2290
1811
        """Inserting an empty record stream should work."""
2291
1812
        files = self.get_versionedfiles()
2437
1958
        else:
2438
1959
            self.assertIdenticalVersionedFile(source, files)
2439
1960
 
2440
 
    def test_insert_record_stream_long_parent_chain_out_of_order(self):
2441
 
        """An out of order stream can either error or work."""
2442
 
        if not self.graph:
2443
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
2444
 
        # Create a reasonably long chain of records based on each other, where
2445
 
        # most will be deltas.
2446
 
        source = self.get_versionedfiles('source')
2447
 
        parents = ()
2448
 
        keys = []
2449
 
        content = [('same same %d\n' % n) for n in range(500)]
2450
 
        for letter in 'abcdefghijklmnopqrstuvwxyz':
2451
 
            key = ('key-' + letter,)
2452
 
            if self.key_length == 2:
2453
 
                key = ('prefix',) + key
2454
 
            content.append('content for ' + letter + '\n')
2455
 
            source.add_lines(key, parents, content)
2456
 
            keys.append(key)
2457
 
            parents = (key,)
2458
 
        # Create a stream of these records, excluding the first record that the
2459
 
        # rest ultimately depend upon, and insert it into a new vf.
2460
 
        streams = []
2461
 
        for key in reversed(keys):
2462
 
            streams.append(source.get_record_stream([key], 'unordered', False))
2463
 
        deltas = chain(*streams[:-1])
2464
 
        files = self.get_versionedfiles()
2465
 
        try:
2466
 
            files.insert_record_stream(deltas)
2467
 
        except RevisionNotPresent:
2468
 
            # Must not have corrupted the file.
2469
 
            files.check()
2470
 
        else:
2471
 
            # Must only report either just the first key as a missing parent,
2472
 
            # no key as missing (for nodelta scenarios).
2473
 
            missing = set(files.get_missing_compression_parent_keys())
2474
 
            missing.discard(keys[0])
2475
 
            self.assertEqual(set(), missing)
2476
 
 
2477
 
    def get_knit_delta_source(self):
2478
 
        """Get a source that can produce a stream with knit delta records,
2479
 
        regardless of this test's scenario.
2480
 
        """
2481
 
        mapper = self.get_mapper()
2482
 
        source_transport = self.get_transport('source')
2483
 
        source_transport.mkdir('.')
2484
 
        source = make_file_factory(False, mapper)(source_transport)
2485
 
        get_diamond_files(source, self.key_length, trailing_eol=True,
2486
 
            nograph=False, left_only=False)
2487
 
        return source
2488
 
 
2489
1961
    def test_insert_record_stream_delta_missing_basis_no_corruption(self):
2490
 
        """Insertion where a needed basis is not included notifies the caller
2491
 
        of the missing basis.  In the meantime a record missing its basis is
2492
 
        not added.
2493
 
        """
2494
 
        source = self.get_knit_delta_source()
2495
 
        keys = [self.get_simple_key('origin'), self.get_simple_key('merged')]
2496
 
        entries = source.get_record_stream(keys, 'unordered', False)
2497
 
        files = self.get_versionedfiles()
2498
 
        if self.support_partial_insertion:
2499
 
            self.assertEqual([],
2500
 
                list(files.get_missing_compression_parent_keys()))
2501
 
            files.insert_record_stream(entries)
2502
 
            missing_bases = files.get_missing_compression_parent_keys()
2503
 
            self.assertEqual(set([self.get_simple_key('left')]),
2504
 
                set(missing_bases))
2505
 
            self.assertEqual(set(keys), set(files.get_parent_map(keys)))
2506
 
        else:
2507
 
            self.assertRaises(
2508
 
                errors.RevisionNotPresent, files.insert_record_stream, entries)
2509
 
            files.check()
2510
 
 
2511
 
    def test_insert_record_stream_delta_missing_basis_can_be_added_later(self):
2512
 
        """Insertion where a needed basis is not included notifies the caller
2513
 
        of the missing basis.  That basis can be added in a second
2514
 
        insert_record_stream call that does not need to repeat records present
2515
 
        in the previous stream.  The record(s) that required that basis are
2516
 
        fully inserted once their basis is no longer missing.
2517
 
        """
2518
 
        if not self.support_partial_insertion:
2519
 
            raise TestNotApplicable(
2520
 
                'versioned file scenario does not support partial insertion')
2521
 
        source = self.get_knit_delta_source()
2522
 
        entries = source.get_record_stream([self.get_simple_key('origin'),
2523
 
            self.get_simple_key('merged')], 'unordered', False)
2524
 
        files = self.get_versionedfiles()
2525
 
        files.insert_record_stream(entries)
2526
 
        missing_bases = files.get_missing_compression_parent_keys()
2527
 
        self.assertEqual(set([self.get_simple_key('left')]),
2528
 
            set(missing_bases))
2529
 
        # 'merged' is inserted (although a commit of a write group involving
2530
 
        # this versionedfiles would fail).
2531
 
        merged_key = self.get_simple_key('merged')
2532
 
        self.assertEqual(
2533
 
            [merged_key], files.get_parent_map([merged_key]).keys())
2534
 
        # Add the full delta closure of the missing records
2535
 
        missing_entries = source.get_record_stream(
2536
 
            missing_bases, 'unordered', True)
2537
 
        files.insert_record_stream(missing_entries)
2538
 
        # Now 'merged' is fully inserted (and a commit would succeed).
2539
 
        self.assertEqual([], list(files.get_missing_compression_parent_keys()))
2540
 
        self.assertEqual(
2541
 
            [merged_key], files.get_parent_map([merged_key]).keys())
 
1962
        """Insertion where a needed basis is not included aborts safely."""
 
1963
        # We use a knit always here to be sure we are getting a binary delta.
 
1964
        mapper = self.get_mapper()
 
1965
        source_transport = self.get_transport('source')
 
1966
        source_transport.mkdir('.')
 
1967
        source = make_file_factory(False, mapper)(source_transport)
 
1968
        self.get_diamond_files(source)
 
1969
        entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
 
1970
        files = self.get_versionedfiles()
 
1971
        self.assertRaises(RevisionNotPresent, files.insert_record_stream,
 
1972
            entries)
2542
1973
        files.check()
 
1974
        self.assertEqual({}, files.get_parent_map([]))
2543
1975
 
2544
1976
    def test_iter_lines_added_or_present_in_keys(self):
2545
1977
        # test that we get at least an equalset of the lines added by
2547
1979
        # the ordering here is to make a tree so that dumb searches have
2548
1980
        # more changes to muck up.
2549
1981
 
2550
 
        class InstrumentedProgress(progress.ProgressTask):
 
1982
        class InstrumentedProgress(progress.DummyProgress):
2551
1983
 
2552
1984
            def __init__(self):
2553
 
                progress.ProgressTask.__init__(self)
 
1985
 
 
1986
                progress.DummyProgress.__init__(self)
2554
1987
                self.updates = []
2555
1988
 
2556
1989
            def update(self, msg=None, current=None, total=None):
2587
2020
            return lines
2588
2021
        lines = iter_with_keys(
2589
2022
            [self.get_simple_key('child'), self.get_simple_key('otherchild')],
2590
 
            [('Walking content', 0, 2),
2591
 
             ('Walking content', 1, 2),
2592
 
             ('Walking content', 2, 2)])
 
2023
            [('Walking content.', 0, 2),
 
2024
             ('Walking content.', 1, 2),
 
2025
             ('Walking content.', 2, 2)])
2593
2026
        # we must see child and otherchild
2594
2027
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2595
2028
        self.assertTrue(
2596
2029
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2597
2030
        # we dont care if we got more than that.
2598
 
 
 
2031
        
2599
2032
        # test all lines
2600
2033
        lines = iter_with_keys(files.keys(),
2601
 
            [('Walking content', 0, 5),
2602
 
             ('Walking content', 1, 5),
2603
 
             ('Walking content', 2, 5),
2604
 
             ('Walking content', 3, 5),
2605
 
             ('Walking content', 4, 5),
2606
 
             ('Walking content', 5, 5)])
 
2034
            [('Walking content.', 0, 5),
 
2035
             ('Walking content.', 1, 5),
 
2036
             ('Walking content.', 2, 5),
 
2037
             ('Walking content.', 3, 5),
 
2038
             ('Walking content.', 4, 5),
 
2039
             ('Walking content.', 5, 5)])
2607
2040
        # all lines must be seen at least once
2608
2041
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2609
2042
        self.assertTrue(
2642
2075
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2643
2076
        # noeol preceeding its leftmost parent in the output:
2644
2077
        # this is done by making it a merge of two parents with no common
2645
 
        # anestry: noeolbase and noeol with the
 
2078
        # anestry: noeolbase and noeol with the 
2646
2079
        # later-inserted parent the leftmost.
2647
2080
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2648
2081
            self.get_parents([self.get_simple_key('noeolbase'),
2699
2132
            mpdiff = files.make_mpdiffs([key])[0]
2700
2133
            parents = files.get_parent_map([key])[key] or []
2701
2134
            target.add_mpdiffs(
2702
 
                [(key, parents, files.get_sha1s([key])[key], mpdiff)])
 
2135
                [(key, parents, files.get_sha1s([key])[0], mpdiff)])
2703
2136
            self.assertEqualDiff(
2704
2137
                files.get_record_stream([key], 'unordered',
2705
2138
                    True).next().get_bytes_as('fulltext'),
2718
2151
            key = ('foo', 'bar',)
2719
2152
        files.add_lines(key, (), [])
2720
2153
        self.assertEqual(set([key]), set(files.keys()))
2721
 
 
2722
 
 
2723
 
class VirtualVersionedFilesTests(TestCase):
2724
 
    """Basic tests for the VirtualVersionedFiles implementations."""
2725
 
 
2726
 
    def _get_parent_map(self, keys):
2727
 
        ret = {}
2728
 
        for k in keys:
2729
 
            if k in self._parent_map:
2730
 
                ret[k] = self._parent_map[k]
2731
 
        return ret
2732
 
 
2733
 
    def setUp(self):
2734
 
        TestCase.setUp(self)
2735
 
        self._lines = {}
2736
 
        self._parent_map = {}
2737
 
        self.texts = VirtualVersionedFiles(self._get_parent_map,
2738
 
                                           self._lines.get)
2739
 
 
2740
 
    def test_add_lines(self):
2741
 
        self.assertRaises(NotImplementedError,
2742
 
                self.texts.add_lines, "foo", [], [])
2743
 
 
2744
 
    def test_add_mpdiffs(self):
2745
 
        self.assertRaises(NotImplementedError,
2746
 
                self.texts.add_mpdiffs, [])
2747
 
 
2748
 
    def test_check_noerrors(self):
2749
 
        self.texts.check()
2750
 
 
2751
 
    def test_insert_record_stream(self):
2752
 
        self.assertRaises(NotImplementedError, self.texts.insert_record_stream,
2753
 
                          [])
2754
 
 
2755
 
    def test_get_sha1s_nonexistent(self):
2756
 
        self.assertEquals({}, self.texts.get_sha1s([("NONEXISTENT",)]))
2757
 
 
2758
 
    def test_get_sha1s(self):
2759
 
        self._lines["key"] = ["dataline1", "dataline2"]
2760
 
        self.assertEquals({("key",): osutils.sha_strings(self._lines["key"])},
2761
 
                           self.texts.get_sha1s([("key",)]))
2762
 
 
2763
 
    def test_get_parent_map(self):
2764
 
        self._parent_map = {"G": ("A", "B")}
2765
 
        self.assertEquals({("G",): (("A",),("B",))},
2766
 
                          self.texts.get_parent_map([("G",), ("L",)]))
2767
 
 
2768
 
    def test_get_record_stream(self):
2769
 
        self._lines["A"] = ["FOO", "BAR"]
2770
 
        it = self.texts.get_record_stream([("A",)], "unordered", True)
2771
 
        record = it.next()
2772
 
        self.assertEquals("chunked", record.storage_kind)
2773
 
        self.assertEquals("FOOBAR", record.get_bytes_as("fulltext"))
2774
 
        self.assertEquals(["FOO", "BAR"], record.get_bytes_as("chunked"))
2775
 
 
2776
 
    def test_get_record_stream_absent(self):
2777
 
        it = self.texts.get_record_stream([("A",)], "unordered", True)
2778
 
        record = it.next()
2779
 
        self.assertEquals("absent", record.storage_kind)
2780
 
 
2781
 
    def test_iter_lines_added_or_present_in_keys(self):
2782
 
        self._lines["A"] = ["FOO", "BAR"]
2783
 
        self._lines["B"] = ["HEY"]
2784
 
        self._lines["C"] = ["Alberta"]
2785
 
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2786
 
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2787
 
            sorted(list(it)))
2788
 
 
2789
 
 
2790
 
class TestOrderingVersionedFilesDecorator(TestCaseWithMemoryTransport):
2791
 
 
2792
 
    def get_ordering_vf(self, key_priority):
2793
 
        builder = self.make_branch_builder('test')
2794
 
        builder.start_series()
2795
 
        builder.build_snapshot('A', None, [
2796
 
            ('add', ('', 'TREE_ROOT', 'directory', None))])
2797
 
        builder.build_snapshot('B', ['A'], [])
2798
 
        builder.build_snapshot('C', ['B'], [])
2799
 
        builder.build_snapshot('D', ['C'], [])
2800
 
        builder.finish_series()
2801
 
        b = builder.get_branch()
2802
 
        b.lock_read()
2803
 
        self.addCleanup(b.unlock)
2804
 
        vf = b.repository.inventories
2805
 
        return versionedfile.OrderingVersionedFilesDecorator(vf, key_priority)
2806
 
 
2807
 
    def test_get_empty(self):
2808
 
        vf = self.get_ordering_vf({})
2809
 
        self.assertEqual([], vf.calls)
2810
 
 
2811
 
    def test_get_record_stream_topological(self):
2812
 
        vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
2813
 
        request_keys = [('B',), ('C',), ('D',), ('A',)]
2814
 
        keys = [r.key for r in vf.get_record_stream(request_keys,
2815
 
                                    'topological', False)]
2816
 
        # We should have gotten the keys in topological order
2817
 
        self.assertEqual([('A',), ('B',), ('C',), ('D',)], keys)
2818
 
        # And recorded that the request was made
2819
 
        self.assertEqual([('get_record_stream', request_keys, 'topological',
2820
 
                           False)], vf.calls)
2821
 
 
2822
 
    def test_get_record_stream_ordered(self):
2823
 
        vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
2824
 
        request_keys = [('B',), ('C',), ('D',), ('A',)]
2825
 
        keys = [r.key for r in vf.get_record_stream(request_keys,
2826
 
                                   'unordered', False)]
2827
 
        # They should be returned based on their priority
2828
 
        self.assertEqual([('D',), ('B',), ('A',), ('C',)], keys)
2829
 
        # And the request recorded
2830
 
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
2831
 
                           False)], vf.calls)
2832
 
 
2833
 
    def test_get_record_stream_implicit_order(self):
2834
 
        vf = self.get_ordering_vf({('B',): 2, ('D',): 1})
2835
 
        request_keys = [('B',), ('C',), ('D',), ('A',)]
2836
 
        keys = [r.key for r in vf.get_record_stream(request_keys,
2837
 
                                   'unordered', False)]
2838
 
        # A and C are not in the map, so they get sorted to the front. A comes
2839
 
        # before C alphabetically, so it comes back first
2840
 
        self.assertEqual([('A',), ('C',), ('D',), ('B',)], keys)
2841
 
        # And the request recorded
2842
 
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
2843
 
                           False)], vf.calls)