/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: Canonical.com Patch Queue Manager
  • Date: 2008-07-17 07:33:12 UTC
  • mfrom: (3530.3.3 btree-graphindex)
  • Revision ID: pqm@pqm.ubuntu.com-20080717073312-reglpowwyo671081
(robertc) Intern GraphIndex strings and handle frozenset inputs to
        make_mpdiffs in the case of errors. (Robert Collins, Andrew Bennetts)

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),
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()
875
846
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
876
847
            },
877
848
            vf.get_sha1s(['a', 'c', 'b']))
878
 
 
 
849
        
879
850
 
880
851
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
881
852
 
888
859
            get_scope=self.get_transaction)
889
860
        w.add_lines('v1', [], ['hello\n'])
890
861
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
891
 
 
 
862
        
892
863
        # We are going to invasively corrupt the text
893
864
        # Make sure the internals of weave are the same
894
865
        self.assertEqual([('{', 0)
898
869
                        , 'there\n'
899
870
                        , ('}', None)
900
871
                        ], w._weave)
901
 
 
 
872
        
902
873
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
903
874
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
904
875
                        ], w._sha1s)
905
876
        w.check()
906
 
 
 
877
        
907
878
        # Corrupted
908
879
        w._weave[4] = 'There\n'
909
880
        return w
913
884
        # Corrected
914
885
        w._weave[4] = 'there\n'
915
886
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
916
 
 
 
887
        
917
888
        #Invalid checksum, first digit changed
918
889
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
919
890
        return w
1028
999
 
1029
1000
        def addcrlf(x):
1030
1001
            return x + '\n'
1031
 
 
 
1002
        
1032
1003
        w = self.get_file()
1033
1004
        w.add_lines('text0', [], map(addcrlf, base))
1034
1005
        w.add_lines('text1', ['text0'], map(addcrlf, a))
1050
1021
 
1051
1022
        mp = map(addcrlf, mp)
1052
1023
        self.assertEqual(mt.readlines(), mp)
1053
 
 
1054
 
 
 
1024
        
 
1025
        
1055
1026
    def testOneInsert(self):
1056
1027
        self.doMerge([],
1057
1028
                     ['aa'],
1075
1046
                     ['aaa', 'xxx', 'yyy', 'bbb'],
1076
1047
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1077
1048
 
1078
 
        # really it ought to reduce this to
 
1049
        # really it ought to reduce this to 
1079
1050
        # ['aaa', 'xxx', 'yyy', 'bbb']
1080
1051
 
1081
1052
 
1083
1054
        self.doMerge(['aaa'],
1084
1055
                     ['xxx'],
1085
1056
                     ['yyy', 'zzz'],
1086
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
 
1057
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
1087
1058
                      '>>>>>>> '])
1088
1059
 
1089
1060
    def testNonClashInsert1(self):
1090
1061
        self.doMerge(['aaa'],
1091
1062
                     ['xxx', 'aaa'],
1092
1063
                     ['yyy', 'zzz'],
1093
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
 
1064
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
1094
1065
                      '>>>>>>> '])
1095
1066
 
1096
1067
    def testNonClashInsert2(self):
1110
1081
        #######################################
1111
1082
        # skippd, not working yet
1112
1083
        return
1113
 
 
 
1084
        
1114
1085
        self.doMerge(['aaa', 'bbb', 'ccc'],
1115
1086
                     ['aaa', 'ddd', 'ccc'],
1116
1087
                     ['aaa', 'ccc'],
1153
1124
            """
1154
1125
        result = """\
1155
1126
            line 1
1156
 
<<<<<<<\x20
1157
 
            line 2
1158
 
=======
1159
 
>>>>>>>\x20
1160
1127
            """
1161
1128
        self._test_merge_from_strings(base, a, b, result)
1162
1129
 
1163
1130
    def test_deletion_overlap(self):
1164
1131
        """Delete overlapping regions with no other conflict.
1165
1132
 
1166
 
        Arguably it'd be better to treat these as agreement, rather than
 
1133
        Arguably it'd be better to treat these as agreement, rather than 
1167
1134
        conflict, but for now conflict is safer.
1168
1135
        """
1169
1136
        base = """\
1185
1152
            """
1186
1153
        result = """\
1187
1154
            start context
1188
 
<<<<<<<\x20
 
1155
<<<<<<< 
1189
1156
            int a() {}
1190
1157
=======
1191
1158
            int c() {}
1192
 
>>>>>>>\x20
 
1159
>>>>>>> 
1193
1160
            end context
1194
1161
            """
1195
1162
        self._test_merge_from_strings(base, a, b, result)
1221
1188
 
1222
1189
    def test_sync_on_deletion(self):
1223
1190
        """Specific case of merge where we can synchronize incorrectly.
1224
 
 
 
1191
        
1225
1192
        A previous version of the weave merge concluded that the two versions
1226
1193
        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
 
1194
        Line 1 was then considered in isolation, and thought to be deleted on 
1228
1195
        both sides.
1229
1196
 
1230
1197
        It's better to consider the whole thing as a disagreement region.
1249
1216
            """
1250
1217
        result = """\
1251
1218
            start context
1252
 
<<<<<<<\x20
 
1219
<<<<<<< 
1253
1220
            base line 1
1254
1221
            a's replacement line 2
1255
1222
=======
1256
1223
            b replaces
1257
1224
            both lines
1258
 
>>>>>>>\x20
 
1225
>>>>>>> 
1259
1226
            end context
1260
1227
            """
1261
1228
        self._test_merge_from_strings(base, a, b, result)
1272
1239
        write_weave(w, tmpf)
1273
1240
        self.log(tmpf.getvalue())
1274
1241
 
1275
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
 
1242
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1276
1243
                                'xxx', '>>>>>>> ', 'bbb']
1277
1244
 
1278
1245
 
1310
1277
        # origin is a fulltext
1311
1278
        entries = f.get_record_stream([('origin',)], 'unordered', False)
1312
1279
        base = entries.next()
1313
 
        ft_data = ft_adapter.get_bytes(base)
 
1280
        ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
1314
1281
        # merged is both a delta and multiple parents.
1315
1282
        entries = f.get_record_stream([('merged',)], 'unordered', False)
1316
1283
        merged = entries.next()
1317
 
        delta_data = delta_adapter.get_bytes(merged)
 
1284
        delta_data = delta_adapter.get_bytes(merged,
 
1285
            merged.get_bytes_as(merged.storage_kind))
1318
1286
        return ft_data, delta_data
1319
1287
 
1320
1288
    def test_deannotation_noeol(self):
1387
1355
 
1388
1356
    def test_unannotated_to_fulltext(self):
1389
1357
        """Test adapting unannotated knits to full texts.
1390
 
 
 
1358
        
1391
1359
        This is used for -> weaves, and for -> annotated knits.
1392
1360
        """
1393
1361
        # we need a full text, and a delta
1406
1374
 
1407
1375
    def test_unannotated_to_fulltext_no_eol(self):
1408
1376
        """Test adapting unannotated knits to full texts.
1409
 
 
 
1377
        
1410
1378
        This is used for -> weaves, and for -> annotated knits.
1411
1379
        """
1412
1380
        # we need a full text, and a delta
1469
1437
            transport.mkdir('.')
1470
1438
        files = self.factory(transport)
1471
1439
        if self.cleanup is not None:
1472
 
            self.addCleanup(self.cleanup, files)
 
1440
            self.addCleanup(lambda:self.cleanup(files))
1473
1441
        return files
1474
1442
 
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
1443
    def test_annotate(self):
1523
1444
        files = self.get_versionedfiles()
1524
1445
        self.get_diamond_files(files)
1558
1479
        self.assertRaises(RevisionNotPresent,
1559
1480
            files.annotate, prefix + ('missing-key',))
1560
1481
 
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
1482
    def test_construct(self):
1588
1483
        """Each parameterised test can be constructed on a transport."""
1589
1484
        files = self.get_versionedfiles()
1590
1485
 
1591
 
    def get_diamond_files(self, files, trailing_eol=True, left_only=False,
1592
 
        nokeys=False):
 
1486
    def get_diamond_files(self, files, trailing_eol=True, left_only=False):
1593
1487
        return get_diamond_files(files, self.key_length,
1594
1488
            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)
 
1489
            left_only=left_only)
1631
1490
 
1632
1491
    def test_add_lines_return(self):
1633
1492
        files = self.get_versionedfiles()
1660
1519
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1661
1520
                results)
1662
1521
 
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
1522
    def test_empty_lines(self):
1718
1523
        """Empty files can be stored."""
1719
1524
        f = self.get_versionedfiles()
1741
1546
            f.get_record_stream([key_b], 'unordered', True
1742
1547
                ).next().get_bytes_as('fulltext'))
1743
1548
 
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
1549
    def test_get_record_stream_empty(self):
1787
1550
        """An empty stream can be requested without error."""
1788
1551
        f = self.get_versionedfiles()
1793
1556
        """Assert that storage_kind is a valid storage_kind."""
1794
1557
        self.assertSubset([storage_kind],
1795
1558
            ['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'])
 
1559
             'knit-ft', 'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
 
1560
             'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'])
1801
1561
 
1802
 
    def capture_stream(self, f, entries, on_seen, parents,
1803
 
        require_fulltext=False):
 
1562
    def capture_stream(self, f, entries, on_seen, parents):
1804
1563
        """Capture a stream for testing."""
1805
1564
        for factory in entries:
1806
1565
            on_seen(factory.key)
1807
1566
            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)
 
1567
            self.assertEqual(f.get_sha1s([factory.key])[factory.key],
 
1568
                factory.sha1)
1811
1569
            self.assertEqual(parents[factory.key], factory.parents)
1812
1570
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1813
1571
                str)
1814
 
            if require_fulltext:
1815
 
                factory.get_bytes_as('fulltext')
1816
1572
 
1817
1573
    def test_get_record_stream_interface(self):
1818
1574
        """each item in a stream has to provide a regular interface."""
1825
1581
        self.capture_stream(files, entries, seen.add, parent_map)
1826
1582
        self.assertEqual(set(keys), seen)
1827
1583
 
 
1584
    def get_simple_key(self, suffix):
 
1585
        """Return a key for the object under test."""
 
1586
        if self.key_length == 1:
 
1587
            return (suffix,)
 
1588
        else:
 
1589
            return ('FileA',) + (suffix,)
 
1590
 
1828
1591
    def get_keys_and_sort_order(self):
1829
1592
        """Get diamond test keys list, and their sort ordering."""
1830
1593
        if self.key_length == 1:
1845
1608
                }
1846
1609
        return keys, sort_order
1847
1610
 
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
1611
    def test_get_record_stream_interface_ordered(self):
1869
1612
        """each item in a stream has to provide a regular interface."""
1870
1613
        files = self.get_versionedfiles()
1891
1634
                [None, files.get_sha1s([factory.key])[factory.key]])
1892
1635
            self.assertEqual(parent_map[factory.key], factory.parents)
1893
1636
            # 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)
 
1637
            self.assertIsInstance(factory.get_bytes_as('fulltext'), str)
 
1638
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
 
1639
                str)
1910
1640
        self.assertStreamOrder(sort_order, seen, keys)
1911
1641
 
1912
1642
    def assertStreamOrder(self, sort_order, seen, keys):
1945
1675
        for factory in entries:
1946
1676
            seen.add(factory.key)
1947
1677
            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)
 
1678
            self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1679
                factory.sha1)
1951
1680
            self.assertEqual(parent_map[factory.key], factory.parents)
1952
1681
            # currently no stream emits mpdiff
1953
1682
            self.assertRaises(errors.UnavailableRepresentation,
1975
1704
        entries = files.get_record_stream(keys, 'topological', False)
1976
1705
        self.assertAbsentRecord(files, keys, parent_map, entries)
1977
1706
 
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
1707
    def assertAbsentRecord(self, files, keys, parents, entries):
2144
1708
        """Helper for test_get_record_stream_missing_records_are_absent."""
2145
1709
        seen = set()
2151
1715
                self.assertEqual(None, factory.parents)
2152
1716
            else:
2153
1717
                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)
 
1718
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1719
                    factory.sha1)
2157
1720
                self.assertEqual(parents[factory.key], factory.parents)
2158
1721
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
2159
1722
                    str)
2193
1756
        else:
2194
1757
            return None
2195
1758
 
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
1759
    def test_get_parent_map(self):
2233
1760
        files = self.get_versionedfiles()
2234
1761
        if self.key_length == 1:
2285
1812
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
2286
1813
            },
2287
1814
            files.get_sha1s(keys))
2288
 
 
 
1815
        
2289
1816
    def test_insert_record_stream_empty(self):
2290
1817
        """Inserting an empty record stream should work."""
2291
1818
        files = self.get_versionedfiles()
2437
1964
        else:
2438
1965
            self.assertIdenticalVersionedFile(source, files)
2439
1966
 
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
1967
    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())
 
1968
        """Insertion where a needed basis is not included aborts safely."""
 
1969
        # We use a knit always here to be sure we are getting a binary delta.
 
1970
        mapper = self.get_mapper()
 
1971
        source_transport = self.get_transport('source')
 
1972
        source_transport.mkdir('.')
 
1973
        source = make_file_factory(False, mapper)(source_transport)
 
1974
        self.get_diamond_files(source)
 
1975
        entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
 
1976
        files = self.get_versionedfiles()
 
1977
        self.assertRaises(RevisionNotPresent, files.insert_record_stream,
 
1978
            entries)
2542
1979
        files.check()
 
1980
        self.assertEqual({}, files.get_parent_map([]))
2543
1981
 
2544
1982
    def test_iter_lines_added_or_present_in_keys(self):
2545
1983
        # test that we get at least an equalset of the lines added by
2547
1985
        # the ordering here is to make a tree so that dumb searches have
2548
1986
        # more changes to muck up.
2549
1987
 
2550
 
        class InstrumentedProgress(progress.ProgressTask):
 
1988
        class InstrumentedProgress(progress.DummyProgress):
2551
1989
 
2552
1990
            def __init__(self):
2553
 
                progress.ProgressTask.__init__(self)
 
1991
 
 
1992
                progress.DummyProgress.__init__(self)
2554
1993
                self.updates = []
2555
1994
 
2556
1995
            def update(self, msg=None, current=None, total=None):
2587
2026
            return lines
2588
2027
        lines = iter_with_keys(
2589
2028
            [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)])
 
2029
            [('Walking content.', 0, 2),
 
2030
             ('Walking content.', 1, 2),
 
2031
             ('Walking content.', 2, 2)])
2593
2032
        # we must see child and otherchild
2594
2033
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2595
2034
        self.assertTrue(
2596
2035
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2597
2036
        # we dont care if we got more than that.
2598
 
 
 
2037
        
2599
2038
        # test all lines
2600
2039
        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)])
 
2040
            [('Walking content.', 0, 5),
 
2041
             ('Walking content.', 1, 5),
 
2042
             ('Walking content.', 2, 5),
 
2043
             ('Walking content.', 3, 5),
 
2044
             ('Walking content.', 4, 5),
 
2045
             ('Walking content.', 5, 5)])
2607
2046
        # all lines must be seen at least once
2608
2047
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2609
2048
        self.assertTrue(
2642
2081
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2643
2082
        # noeol preceeding its leftmost parent in the output:
2644
2083
        # this is done by making it a merge of two parents with no common
2645
 
        # anestry: noeolbase and noeol with the
 
2084
        # anestry: noeolbase and noeol with the 
2646
2085
        # later-inserted parent the leftmost.
2647
2086
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2648
2087
            self.get_parents([self.get_simple_key('noeolbase'),
2718
2157
            key = ('foo', 'bar',)
2719
2158
        files.add_lines(key, (), [])
2720
2159
        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)