~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_versionedfile.py

  • Committer: Andrew Bennetts
  • Date: 2010-03-11 04:33:41 UTC
  • mfrom: (4797.33.4 2.1)
  • mto: This revision was merged to the branch mainline in revision 5082.
  • Revision ID: andrew.bennetts@canonical.com-20100311043341-rzdik83fnactjsxs
Merge lp:bzr/2.1, including fixes for #496813, #526211, #526353.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
 
24
from itertools import chain, izip
25
25
from StringIO import StringIO
26
26
 
27
 
import bzrlib
28
27
from bzrlib import (
29
28
    errors,
 
29
    graph as _mod_graph,
 
30
    groupcompress,
 
31
    knit as _mod_knit,
30
32
    osutils,
31
33
    progress,
 
34
    ui,
32
35
    )
33
36
from bzrlib.errors import (
34
37
                           RevisionNotPresent,
35
38
                           RevisionAlreadyPresent,
36
39
                           WeaveParentMismatch
37
40
                           )
38
 
from bzrlib import knit as _mod_knit
39
41
from bzrlib.knit import (
40
42
    cleanup_pack_knit,
41
43
    make_file_factory,
43
45
    KnitAnnotateFactory,
44
46
    KnitPlainFactory,
45
47
    )
46
 
from bzrlib.symbol_versioning import one_four, one_five
47
48
from bzrlib.tests import (
48
49
    TestCase,
49
50
    TestCaseWithMemoryTransport,
50
 
    TestScenarioApplier,
 
51
    TestNotApplicable,
51
52
    TestSkipped,
52
53
    condition_isinstance,
53
54
    split_suite_by_condition,
54
 
    iter_suite_tests,
 
55
    multiply_tests,
55
56
    )
56
57
from bzrlib.tests.http_utils import TestCaseWithWebserver
57
58
from bzrlib.trace import mutter
75
76
    """Parameterize VersionedFiles tests for different implementations."""
76
77
    to_adapt, result = split_suite_by_condition(
77
78
        standard_tests, condition_isinstance(TestVersionedFiles))
78
 
    len_one_adapter = TestScenarioApplier()
79
 
    len_two_adapter = TestScenarioApplier()
80
79
    # We want to be sure of behaviour for:
81
80
    # weaves prefix layout (weave texts)
82
81
    # individually named weaves (weave inventories)
87
86
    # individual graph knits in packs (inventories)
88
87
    # individual graph nocompression knits in packs (revisions)
89
88
    # plain text knits in packs (texts)
90
 
    len_one_adapter.scenarios = [
 
89
    len_one_scenarios = [
91
90
        ('weave-named', {
92
91
            'cleanup':None,
93
92
            'factory':make_versioned_files_factory(WeaveFile,
94
93
                ConstantMapper('inventory')),
95
94
            'graph':True,
96
95
            'key_length':1,
 
96
            'support_partial_insertion': False,
97
97
            }),
98
98
        ('named-knit', {
99
99
            'cleanup':None,
100
100
            'factory':make_file_factory(False, ConstantMapper('revisions')),
101
101
            'graph':True,
102
102
            'key_length':1,
 
103
            'support_partial_insertion': False,
103
104
            }),
104
 
        ('named-nograph-knit-pack', {
 
105
        ('named-nograph-nodelta-knit-pack', {
105
106
            'cleanup':cleanup_pack_knit,
106
107
            'factory':make_pack_factory(False, False, 1),
107
108
            'graph':False,
108
109
            'key_length':1,
 
110
            'support_partial_insertion': False,
109
111
            }),
110
112
        ('named-graph-knit-pack', {
111
113
            'cleanup':cleanup_pack_knit,
112
114
            'factory':make_pack_factory(True, True, 1),
113
115
            'graph':True,
114
116
            'key_length':1,
 
117
            'support_partial_insertion': True,
115
118
            }),
116
119
        ('named-graph-nodelta-knit-pack', {
117
120
            'cleanup':cleanup_pack_knit,
118
121
            'factory':make_pack_factory(True, False, 1),
119
122
            'graph':True,
120
123
            '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,
121
132
            }),
122
133
        ]
123
 
    len_two_adapter.scenarios = [
 
134
    len_two_scenarios = [
124
135
        ('weave-prefix', {
125
136
            'cleanup':None,
126
137
            'factory':make_versioned_files_factory(WeaveFile,
127
138
                PrefixMapper()),
128
139
            'graph':True,
129
140
            'key_length':2,
 
141
            'support_partial_insertion': False,
130
142
            }),
131
143
        ('annotated-knit-escape', {
132
144
            'cleanup':None,
133
145
            'factory':make_file_factory(True, HashEscapedPrefixMapper()),
134
146
            'graph':True,
135
147
            'key_length':2,
 
148
            'support_partial_insertion': False,
136
149
            }),
137
150
        ('plain-knit-pack', {
138
151
            'cleanup':cleanup_pack_knit,
139
152
            'factory':make_pack_factory(True, True, 2),
140
153
            'graph':True,
141
154
            '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,
142
163
            }),
143
164
        ]
144
 
    for test in iter_suite_tests(to_adapt):
145
 
        result.addTests(len_one_adapter.adapt(test))
146
 
        result.addTests(len_two_adapter.adapt(test))
147
 
    return result
 
165
    scenarios = len_one_scenarios + len_two_scenarios
 
166
    return multiply_tests(to_adapt, scenarios, result)
148
167
 
149
168
 
150
169
def get_diamond_vf(f, trailing_eol=True, left_only=False):
151
170
    """Get a diamond graph to exercise deltas and merges.
152
 
    
 
171
 
153
172
    :param trailing_eol: If True end the last line with \n.
154
173
    """
155
174
    parents = {
176
195
 
177
196
 
178
197
def get_diamond_files(files, key_length, trailing_eol=True, left_only=False,
179
 
    nograph=False):
 
198
    nograph=False, nokeys=False):
180
199
    """Get a diamond graph to exercise deltas and merges.
181
200
 
182
201
    This creates a 5-node graph in files. If files supports 2-length keys two
183
202
    graphs are made to exercise the support for multiple ids.
184
 
    
 
203
 
185
204
    :param trailing_eol: If True end the last line with \n.
186
205
    :param key_length: The length of keys in files. Currently supports length 1
187
206
        and 2 keys.
189
208
    :param nograph: If True, do not provide parents to the add_lines calls;
190
209
        this is useful for tests that need inserted data but have graphless
191
210
        stores.
 
211
    :param nokeys: If True, pass None is as the key for all insertions.
 
212
        Currently implies nograph.
192
213
    :return: The results of the add_lines calls.
193
214
    """
 
215
    if nokeys:
 
216
        nograph = True
194
217
    if key_length == 1:
195
218
        prefixes = [()]
196
219
    else:
207
230
        else:
208
231
            result = [prefix + suffix for suffix in suffix_list]
209
232
            return result
 
233
    def get_key(suffix):
 
234
        if nokeys:
 
235
            return (None, )
 
236
        else:
 
237
            return (suffix,)
210
238
    # we loop over each key because that spreads the inserts across prefixes,
211
239
    # which is how commit operates.
212
240
    for prefix in prefixes:
213
 
        result.append(files.add_lines(prefix + ('origin',), (),
 
241
        result.append(files.add_lines(prefix + get_key('origin'), (),
214
242
            ['origin' + last_char]))
215
243
    for prefix in prefixes:
216
 
        result.append(files.add_lines(prefix + ('base',),
 
244
        result.append(files.add_lines(prefix + get_key('base'),
217
245
            get_parents([('origin',)]), ['base' + last_char]))
218
246
    for prefix in prefixes:
219
 
        result.append(files.add_lines(prefix + ('left',),
 
247
        result.append(files.add_lines(prefix + get_key('left'),
220
248
            get_parents([('base',)]),
221
249
            ['base\n', 'left' + last_char]))
222
250
    if not left_only:
223
251
        for prefix in prefixes:
224
 
            result.append(files.add_lines(prefix + ('right',),
 
252
            result.append(files.add_lines(prefix + get_key('right'),
225
253
                get_parents([('base',)]),
226
254
                ['base\n', 'right' + last_char]))
227
255
        for prefix in prefixes:
228
 
            result.append(files.add_lines(prefix + ('merged',),
 
256
            result.append(files.add_lines(prefix + get_key('merged'),
229
257
                get_parents([('left',), ('right',)]),
230
258
                ['base\n', 'left\n', 'right\n', 'merged' + last_char]))
231
259
    return result
257
285
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
258
286
            self.assertEqual(2, len(f))
259
287
            self.assertEqual(2, f.num_versions())
260
 
    
 
288
 
261
289
            self.assertRaises(RevisionNotPresent,
262
290
                f.add_lines, 'r2', ['foo'], [])
263
291
            self.assertRaises(RevisionAlreadyPresent,
302
330
        verify_file(f)
303
331
 
304
332
    def test_add_unicode_content(self):
305
 
        # unicode content is not permitted in versioned files. 
 
333
        # unicode content is not permitted in versioned files.
306
334
        # versioned files version sequences of bytes only.
307
335
        vf = self.get_file()
308
336
        self.assertRaises(errors.BzrBadParameterUnicode,
331
359
    def test_inline_newline_throws(self):
332
360
        # \r characters are not permitted in lines being added
333
361
        vf = self.get_file()
334
 
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
 
362
        self.assertRaises(errors.BzrBadParameterContainsNewline,
335
363
            vf.add_lines, 'a', [], ['a\n\n'])
336
364
        self.assertRaises(
337
365
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
536
564
        f.add_lines('noeolbase', [], ['line'])
537
565
        # noeol preceeding its leftmost parent in the output:
538
566
        # this is done by making it a merge of two parents with no common
539
 
        # anestry: noeolbase and noeol with the 
 
567
        # anestry: noeolbase and noeol with the
540
568
        # later-inserted parent the leftmost.
541
569
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
542
570
        # two identical eol texts
623
651
        self._transaction = 'after'
624
652
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
625
653
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
626
 
        
 
654
 
627
655
    def test_copy_to(self):
628
656
        f = self.get_file()
629
657
        f.add_lines('0', [], ['a\n'])
702
730
 
703
731
    def test_iter_lines_added_or_present_in_versions(self):
704
732
        # test that we get at least an equalset of the lines added by
705
 
        # versions in the weave 
 
733
        # versions in the weave
706
734
        # the ordering here is to make a tree so that dumb searches have
707
735
        # more changes to muck up.
708
736
 
709
 
        class InstrumentedProgress(progress.DummyProgress):
 
737
        class InstrumentedProgress(progress.ProgressTask):
710
738
 
711
739
            def __init__(self):
712
 
 
713
 
                progress.DummyProgress.__init__(self)
 
740
                progress.ProgressTask.__init__(self)
714
741
                self.updates = []
715
742
 
716
743
            def update(self, msg=None, current=None, total=None):
742
769
                self.assertEqual(expected, progress.updates)
743
770
            return lines
744
771
        lines = iter_with_versions(['child', 'otherchild'],
745
 
                                   [('Walking content.', 0, 2),
746
 
                                    ('Walking content.', 1, 2),
747
 
                                    ('Walking content.', 2, 2)])
 
772
                                   [('Walking content', 0, 2),
 
773
                                    ('Walking content', 1, 2),
 
774
                                    ('Walking content', 2, 2)])
748
775
        # we must see child and otherchild
749
776
        self.assertTrue(lines[('child\n', 'child')] > 0)
750
777
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
751
778
        # we dont care if we got more than that.
752
 
        
 
779
 
753
780
        # test all lines
754
 
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
755
 
                                          ('Walking content.', 1, 5),
756
 
                                          ('Walking content.', 2, 5),
757
 
                                          ('Walking content.', 3, 5),
758
 
                                          ('Walking content.', 4, 5),
759
 
                                          ('Walking content.', 5, 5)])
 
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)])
760
787
        # all lines must be seen at least once
761
788
        self.assertTrue(lines[('base\n', 'base')] > 0)
762
789
        self.assertTrue(lines[('lancestor\n', 'lancestor')] > 0)
832
859
                          'base',
833
860
                          [],
834
861
                          [])
835
 
    
 
862
 
836
863
    def test_get_sha1s(self):
837
864
        # check the sha1 data is available
838
865
        vf = self.get_file()
848
875
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
849
876
            },
850
877
            vf.get_sha1s(['a', 'c', 'b']))
851
 
        
 
878
 
852
879
 
853
880
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
854
881
 
861
888
            get_scope=self.get_transaction)
862
889
        w.add_lines('v1', [], ['hello\n'])
863
890
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
864
 
        
 
891
 
865
892
        # We are going to invasively corrupt the text
866
893
        # Make sure the internals of weave are the same
867
894
        self.assertEqual([('{', 0)
871
898
                        , 'there\n'
872
899
                        , ('}', None)
873
900
                        ], w._weave)
874
 
        
 
901
 
875
902
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
876
903
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
877
904
                        ], w._sha1s)
878
905
        w.check()
879
 
        
 
906
 
880
907
        # Corrupted
881
908
        w._weave[4] = 'There\n'
882
909
        return w
886
913
        # Corrected
887
914
        w._weave[4] = 'there\n'
888
915
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
889
 
        
 
916
 
890
917
        #Invalid checksum, first digit changed
891
918
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
892
919
        return w
1001
1028
 
1002
1029
        def addcrlf(x):
1003
1030
            return x + '\n'
1004
 
        
 
1031
 
1005
1032
        w = self.get_file()
1006
1033
        w.add_lines('text0', [], map(addcrlf, base))
1007
1034
        w.add_lines('text1', ['text0'], map(addcrlf, a))
1023
1050
 
1024
1051
        mp = map(addcrlf, mp)
1025
1052
        self.assertEqual(mt.readlines(), mp)
1026
 
        
1027
 
        
 
1053
 
 
1054
 
1028
1055
    def testOneInsert(self):
1029
1056
        self.doMerge([],
1030
1057
                     ['aa'],
1048
1075
                     ['aaa', 'xxx', 'yyy', 'bbb'],
1049
1076
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1050
1077
 
1051
 
        # really it ought to reduce this to 
 
1078
        # really it ought to reduce this to
1052
1079
        # ['aaa', 'xxx', 'yyy', 'bbb']
1053
1080
 
1054
1081
 
1056
1083
        self.doMerge(['aaa'],
1057
1084
                     ['xxx'],
1058
1085
                     ['yyy', 'zzz'],
1059
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
1086
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
1060
1087
                      '>>>>>>> '])
1061
1088
 
1062
1089
    def testNonClashInsert1(self):
1063
1090
        self.doMerge(['aaa'],
1064
1091
                     ['xxx', 'aaa'],
1065
1092
                     ['yyy', 'zzz'],
1066
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
1093
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
1067
1094
                      '>>>>>>> '])
1068
1095
 
1069
1096
    def testNonClashInsert2(self):
1083
1110
        #######################################
1084
1111
        # skippd, not working yet
1085
1112
        return
1086
 
        
 
1113
 
1087
1114
        self.doMerge(['aaa', 'bbb', 'ccc'],
1088
1115
                     ['aaa', 'ddd', 'ccc'],
1089
1116
                     ['aaa', 'ccc'],
1126
1153
            """
1127
1154
        result = """\
1128
1155
            line 1
 
1156
<<<<<<<\x20
 
1157
            line 2
 
1158
=======
 
1159
>>>>>>>\x20
1129
1160
            """
1130
1161
        self._test_merge_from_strings(base, a, b, result)
1131
1162
 
1132
1163
    def test_deletion_overlap(self):
1133
1164
        """Delete overlapping regions with no other conflict.
1134
1165
 
1135
 
        Arguably it'd be better to treat these as agreement, rather than 
 
1166
        Arguably it'd be better to treat these as agreement, rather than
1136
1167
        conflict, but for now conflict is safer.
1137
1168
        """
1138
1169
        base = """\
1154
1185
            """
1155
1186
        result = """\
1156
1187
            start context
1157
 
<<<<<<< 
 
1188
<<<<<<<\x20
1158
1189
            int a() {}
1159
1190
=======
1160
1191
            int c() {}
1161
 
>>>>>>> 
 
1192
>>>>>>>\x20
1162
1193
            end context
1163
1194
            """
1164
1195
        self._test_merge_from_strings(base, a, b, result)
1190
1221
 
1191
1222
    def test_sync_on_deletion(self):
1192
1223
        """Specific case of merge where we can synchronize incorrectly.
1193
 
        
 
1224
 
1194
1225
        A previous version of the weave merge concluded that the two versions
1195
1226
        agreed on deleting line 2, and this could be a synchronization point.
1196
 
        Line 1 was then considered in isolation, and thought to be deleted on 
 
1227
        Line 1 was then considered in isolation, and thought to be deleted on
1197
1228
        both sides.
1198
1229
 
1199
1230
        It's better to consider the whole thing as a disagreement region.
1218
1249
            """
1219
1250
        result = """\
1220
1251
            start context
1221
 
<<<<<<< 
 
1252
<<<<<<<\x20
1222
1253
            base line 1
1223
1254
            a's replacement line 2
1224
1255
=======
1225
1256
            b replaces
1226
1257
            both lines
1227
 
>>>>>>> 
 
1258
>>>>>>>\x20
1228
1259
            end context
1229
1260
            """
1230
1261
        self._test_merge_from_strings(base, a, b, result)
1241
1272
        write_weave(w, tmpf)
1242
1273
        self.log(tmpf.getvalue())
1243
1274
 
1244
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
 
1275
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1245
1276
                                'xxx', '>>>>>>> ', 'bbb']
1246
1277
 
1247
1278
 
1279
1310
        # origin is a fulltext
1280
1311
        entries = f.get_record_stream([('origin',)], 'unordered', False)
1281
1312
        base = entries.next()
1282
 
        ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
 
1313
        ft_data = ft_adapter.get_bytes(base)
1283
1314
        # merged is both a delta and multiple parents.
1284
1315
        entries = f.get_record_stream([('merged',)], 'unordered', False)
1285
1316
        merged = entries.next()
1286
 
        delta_data = delta_adapter.get_bytes(merged,
1287
 
            merged.get_bytes_as(merged.storage_kind))
 
1317
        delta_data = delta_adapter.get_bytes(merged)
1288
1318
        return ft_data, delta_data
1289
1319
 
1290
1320
    def test_deannotation_noeol(self):
1357
1387
 
1358
1388
    def test_unannotated_to_fulltext(self):
1359
1389
        """Test adapting unannotated knits to full texts.
1360
 
        
 
1390
 
1361
1391
        This is used for -> weaves, and for -> annotated knits.
1362
1392
        """
1363
1393
        # we need a full text, and a delta
1376
1406
 
1377
1407
    def test_unannotated_to_fulltext_no_eol(self):
1378
1408
        """Test adapting unannotated knits to full texts.
1379
 
        
 
1409
 
1380
1410
        This is used for -> weaves, and for -> annotated knits.
1381
1411
        """
1382
1412
        # we need a full text, and a delta
1439
1469
            transport.mkdir('.')
1440
1470
        files = self.factory(transport)
1441
1471
        if self.cleanup is not None:
1442
 
            self.addCleanup(lambda:self.cleanup(files))
 
1472
            self.addCleanup(self.cleanup, files)
1443
1473
        return files
1444
1474
 
 
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
 
1445
1522
    def test_annotate(self):
1446
1523
        files = self.get_versionedfiles()
1447
1524
        self.get_diamond_files(files)
1481
1558
        self.assertRaises(RevisionNotPresent,
1482
1559
            files.annotate, prefix + ('missing-key',))
1483
1560
 
 
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
 
1484
1587
    def test_construct(self):
1485
1588
        """Each parameterised test can be constructed on a transport."""
1486
1589
        files = self.get_versionedfiles()
1487
1590
 
1488
 
    def get_diamond_files(self, files, trailing_eol=True, left_only=False):
 
1591
    def get_diamond_files(self, files, trailing_eol=True, left_only=False,
 
1592
        nokeys=False):
1489
1593
        return get_diamond_files(files, self.key_length,
1490
1594
            trailing_eol=trailing_eol, nograph=not self.graph,
1491
 
            left_only=left_only)
 
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)
1492
1631
 
1493
1632
    def test_add_lines_return(self):
1494
1633
        files = self.get_versionedfiles()
1521
1660
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1522
1661
                results)
1523
1662
 
 
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
 
1524
1717
    def test_empty_lines(self):
1525
1718
        """Empty files can be stored."""
1526
1719
        f = self.get_versionedfiles()
1548
1741
            f.get_record_stream([key_b], 'unordered', True
1549
1742
                ).next().get_bytes_as('fulltext'))
1550
1743
 
 
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
 
1551
1786
    def test_get_record_stream_empty(self):
1552
1787
        """An empty stream can be requested without error."""
1553
1788
        f = self.get_versionedfiles()
1560
1795
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1561
1796
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1562
1797
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1563
 
             'knit-delta-gz'])
 
1798
             'knit-delta-gz',
 
1799
             'knit-delta-closure', 'knit-delta-closure-ref',
 
1800
             'groupcompress-block', 'groupcompress-block-ref'])
1564
1801
 
1565
 
    def capture_stream(self, f, entries, on_seen, parents):
 
1802
    def capture_stream(self, f, entries, on_seen, parents,
 
1803
        require_fulltext=False):
1566
1804
        """Capture a stream for testing."""
1567
1805
        for factory in entries:
1568
1806
            on_seen(factory.key)
1569
1807
            self.assertValidStorageKind(factory.storage_kind)
1570
 
            self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1571
 
                factory.sha1)
 
1808
            if factory.sha1 is not None:
 
1809
                self.assertEqual(f.get_sha1s([factory.key])[factory.key],
 
1810
                    factory.sha1)
1572
1811
            self.assertEqual(parents[factory.key], factory.parents)
1573
1812
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1574
1813
                str)
 
1814
            if require_fulltext:
 
1815
                factory.get_bytes_as('fulltext')
1575
1816
 
1576
1817
    def test_get_record_stream_interface(self):
1577
1818
        """each item in a stream has to provide a regular interface."""
1584
1825
        self.capture_stream(files, entries, seen.add, parent_map)
1585
1826
        self.assertEqual(set(keys), seen)
1586
1827
 
1587
 
    def get_simple_key(self, suffix):
1588
 
        """Return a key for the object under test."""
1589
 
        if self.key_length == 1:
1590
 
            return (suffix,)
1591
 
        else:
1592
 
            return ('FileA',) + (suffix,)
1593
 
 
1594
1828
    def get_keys_and_sort_order(self):
1595
1829
        """Get diamond test keys list, and their sort ordering."""
1596
1830
        if self.key_length == 1:
1611
1845
                }
1612
1846
        return keys, sort_order
1613
1847
 
 
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
 
1614
1868
    def test_get_record_stream_interface_ordered(self):
1615
1869
        """each item in a stream has to provide a regular interface."""
1616
1870
        files = self.get_versionedfiles()
1644
1898
 
1645
1899
        self.assertStreamOrder(sort_order, seen, keys)
1646
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)
 
1910
        self.assertStreamOrder(sort_order, seen, keys)
 
1911
 
1647
1912
    def assertStreamOrder(self, sort_order, seen, keys):
1648
1913
        self.assertEqual(len(set(seen)), len(keys))
1649
1914
        if self.key_length == 1:
1680
1945
        for factory in entries:
1681
1946
            seen.add(factory.key)
1682
1947
            self.assertValidStorageKind(factory.storage_kind)
1683
 
            self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1684
 
                factory.sha1)
 
1948
            if factory.sha1 is not None:
 
1949
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1950
                                 factory.sha1)
1685
1951
            self.assertEqual(parent_map[factory.key], factory.parents)
1686
1952
            # currently no stream emits mpdiff
1687
1953
            self.assertRaises(errors.UnavailableRepresentation,
1709
1975
        entries = files.get_record_stream(keys, 'topological', False)
1710
1976
        self.assertAbsentRecord(files, keys, parent_map, entries)
1711
1977
 
 
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
 
1712
2143
    def assertAbsentRecord(self, files, keys, parents, entries):
1713
2144
        """Helper for test_get_record_stream_missing_records_are_absent."""
1714
2145
        seen = set()
1720
2151
                self.assertEqual(None, factory.parents)
1721
2152
            else:
1722
2153
                self.assertValidStorageKind(factory.storage_kind)
1723
 
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1724
 
                    factory.sha1)
 
2154
                if factory.sha1 is not None:
 
2155
                    sha1 = files.get_sha1s([factory.key])[factory.key]
 
2156
                    self.assertEqual(sha1, factory.sha1)
1725
2157
                self.assertEqual(parents[factory.key], factory.parents)
1726
2158
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1727
2159
                    str)
1761
2193
        else:
1762
2194
            return None
1763
2195
 
 
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
 
1764
2232
    def test_get_parent_map(self):
1765
2233
        files = self.get_versionedfiles()
1766
2234
        if self.key_length == 1:
1817
2285
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
1818
2286
            },
1819
2287
            files.get_sha1s(keys))
1820
 
        
 
2288
 
1821
2289
    def test_insert_record_stream_empty(self):
1822
2290
        """Inserting an empty record stream should work."""
1823
2291
        files = self.get_versionedfiles()
1969
2437
        else:
1970
2438
            self.assertIdenticalVersionedFile(source, files)
1971
2439
 
 
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
 
1972
2489
    def test_insert_record_stream_delta_missing_basis_no_corruption(self):
1973
 
        """Insertion where a needed basis is not included aborts safely."""
1974
 
        # We use a knit always here to be sure we are getting a binary delta.
1975
 
        mapper = self.get_mapper()
1976
 
        source_transport = self.get_transport('source')
1977
 
        source_transport.mkdir('.')
1978
 
        source = make_file_factory(False, mapper)(source_transport)
1979
 
        self.get_diamond_files(source)
1980
 
        entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
1981
 
        files = self.get_versionedfiles()
1982
 
        self.assertRaises(RevisionNotPresent, files.insert_record_stream,
1983
 
            entries)
 
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())
1984
2542
        files.check()
1985
 
        self.assertEqual({}, files.get_parent_map([]))
1986
2543
 
1987
2544
    def test_iter_lines_added_or_present_in_keys(self):
1988
2545
        # test that we get at least an equalset of the lines added by
1990
2547
        # the ordering here is to make a tree so that dumb searches have
1991
2548
        # more changes to muck up.
1992
2549
 
1993
 
        class InstrumentedProgress(progress.DummyProgress):
 
2550
        class InstrumentedProgress(progress.ProgressTask):
1994
2551
 
1995
2552
            def __init__(self):
1996
 
 
1997
 
                progress.DummyProgress.__init__(self)
 
2553
                progress.ProgressTask.__init__(self)
1998
2554
                self.updates = []
1999
2555
 
2000
2556
            def update(self, msg=None, current=None, total=None):
2031
2587
            return lines
2032
2588
        lines = iter_with_keys(
2033
2589
            [self.get_simple_key('child'), self.get_simple_key('otherchild')],
2034
 
            [('Walking content.', 0, 2),
2035
 
             ('Walking content.', 1, 2),
2036
 
             ('Walking content.', 2, 2)])
 
2590
            [('Walking content', 0, 2),
 
2591
             ('Walking content', 1, 2),
 
2592
             ('Walking content', 2, 2)])
2037
2593
        # we must see child and otherchild
2038
2594
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2039
2595
        self.assertTrue(
2040
2596
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2041
2597
        # we dont care if we got more than that.
2042
 
        
 
2598
 
2043
2599
        # test all lines
2044
2600
        lines = iter_with_keys(files.keys(),
2045
 
            [('Walking content.', 0, 5),
2046
 
             ('Walking content.', 1, 5),
2047
 
             ('Walking content.', 2, 5),
2048
 
             ('Walking content.', 3, 5),
2049
 
             ('Walking content.', 4, 5),
2050
 
             ('Walking content.', 5, 5)])
 
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)])
2051
2607
        # all lines must be seen at least once
2052
2608
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2053
2609
        self.assertTrue(
2086
2642
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2087
2643
        # noeol preceeding its leftmost parent in the output:
2088
2644
        # this is done by making it a merge of two parents with no common
2089
 
        # anestry: noeolbase and noeol with the 
 
2645
        # anestry: noeolbase and noeol with the
2090
2646
        # later-inserted parent the leftmost.
2091
2647
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2092
2648
            self.get_parents([self.get_simple_key('noeolbase'),
2178
2734
        TestCase.setUp(self)
2179
2735
        self._lines = {}
2180
2736
        self._parent_map = {}
2181
 
        self.texts = VirtualVersionedFiles(self._get_parent_map, 
 
2737
        self.texts = VirtualVersionedFiles(self._get_parent_map,
2182
2738
                                           self._lines.get)
2183
2739
 
2184
2740
    def test_add_lines(self):
2185
 
        self.assertRaises(NotImplementedError, 
 
2741
        self.assertRaises(NotImplementedError,
2186
2742
                self.texts.add_lines, "foo", [], [])
2187
2743
 
2188
2744
    def test_add_mpdiffs(self):
2189
 
        self.assertRaises(NotImplementedError, 
 
2745
        self.assertRaises(NotImplementedError,
2190
2746
                self.texts.add_mpdiffs, [])
2191
2747
 
2192
 
    def test_check(self):
2193
 
        self.assertTrue(self.texts.check())
 
2748
    def test_check_noerrors(self):
 
2749
        self.texts.check()
2194
2750
 
2195
2751
    def test_insert_record_stream(self):
2196
2752
        self.assertRaises(NotImplementedError, self.texts.insert_record_stream,
2206
2762
 
2207
2763
    def test_get_parent_map(self):
2208
2764
        self._parent_map = {"G": ("A", "B")}
2209
 
        self.assertEquals({("G",): (("A",),("B",))}, 
 
2765
        self.assertEquals({("G",): (("A",),("B",))},
2210
2766
                          self.texts.get_parent_map([("G",), ("L",)]))
2211
2767
 
2212
2768
    def test_get_record_stream(self):
2227
2783
        self._lines["B"] = ["HEY"]
2228
2784
        self._lines["C"] = ["Alberta"]
2229
2785
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2230
 
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]), 
 
2786
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2231
2787
            sorted(list(it)))
2232
2788
 
2233
2789