~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-01-12 03:53:21 UTC
  • mfrom: (4948 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4964.
  • Revision ID: andrew.bennetts@canonical.com-20100112035321-hofpz5p10224ryj3
Merge lp:bzr, resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2009 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
 
742
770
                self.assertEqual(expected, progress.updates)
743
771
            return lines
744
772
        lines = iter_with_versions(['child', 'otherchild'],
745
 
                                   [('Walking content.', 0, 2),
746
 
                                    ('Walking content.', 1, 2),
747
 
                                    ('Walking content.', 2, 2)])
 
773
                                   [('Walking content', 0, 2),
 
774
                                    ('Walking content', 1, 2),
 
775
                                    ('Walking content', 2, 2)])
748
776
        # we must see child and otherchild
749
777
        self.assertTrue(lines[('child\n', 'child')] > 0)
750
778
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
751
779
        # we dont care if we got more than that.
752
 
        
 
780
 
753
781
        # 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)])
 
782
        lines = iter_with_versions(None, [('Walking content', 0, 5),
 
783
                                          ('Walking content', 1, 5),
 
784
                                          ('Walking content', 2, 5),
 
785
                                          ('Walking content', 3, 5),
 
786
                                          ('Walking content', 4, 5),
 
787
                                          ('Walking content', 5, 5)])
760
788
        # all lines must be seen at least once
761
789
        self.assertTrue(lines[('base\n', 'base')] > 0)
762
790
        self.assertTrue(lines[('lancestor\n', 'lancestor')] > 0)
832
860
                          'base',
833
861
                          [],
834
862
                          [])
835
 
    
 
863
 
836
864
    def test_get_sha1s(self):
837
865
        # check the sha1 data is available
838
866
        vf = self.get_file()
848
876
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
849
877
            },
850
878
            vf.get_sha1s(['a', 'c', 'b']))
851
 
        
 
879
 
852
880
 
853
881
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
854
882
 
861
889
            get_scope=self.get_transaction)
862
890
        w.add_lines('v1', [], ['hello\n'])
863
891
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
864
 
        
 
892
 
865
893
        # We are going to invasively corrupt the text
866
894
        # Make sure the internals of weave are the same
867
895
        self.assertEqual([('{', 0)
871
899
                        , 'there\n'
872
900
                        , ('}', None)
873
901
                        ], w._weave)
874
 
        
 
902
 
875
903
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
876
904
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
877
905
                        ], w._sha1s)
878
906
        w.check()
879
 
        
 
907
 
880
908
        # Corrupted
881
909
        w._weave[4] = 'There\n'
882
910
        return w
886
914
        # Corrected
887
915
        w._weave[4] = 'there\n'
888
916
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
889
 
        
 
917
 
890
918
        #Invalid checksum, first digit changed
891
919
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
892
920
        return w
1001
1029
 
1002
1030
        def addcrlf(x):
1003
1031
            return x + '\n'
1004
 
        
 
1032
 
1005
1033
        w = self.get_file()
1006
1034
        w.add_lines('text0', [], map(addcrlf, base))
1007
1035
        w.add_lines('text1', ['text0'], map(addcrlf, a))
1023
1051
 
1024
1052
        mp = map(addcrlf, mp)
1025
1053
        self.assertEqual(mt.readlines(), mp)
1026
 
        
1027
 
        
 
1054
 
 
1055
 
1028
1056
    def testOneInsert(self):
1029
1057
        self.doMerge([],
1030
1058
                     ['aa'],
1048
1076
                     ['aaa', 'xxx', 'yyy', 'bbb'],
1049
1077
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1050
1078
 
1051
 
        # really it ought to reduce this to 
 
1079
        # really it ought to reduce this to
1052
1080
        # ['aaa', 'xxx', 'yyy', 'bbb']
1053
1081
 
1054
1082
 
1056
1084
        self.doMerge(['aaa'],
1057
1085
                     ['xxx'],
1058
1086
                     ['yyy', 'zzz'],
1059
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
1087
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
1060
1088
                      '>>>>>>> '])
1061
1089
 
1062
1090
    def testNonClashInsert1(self):
1063
1091
        self.doMerge(['aaa'],
1064
1092
                     ['xxx', 'aaa'],
1065
1093
                     ['yyy', 'zzz'],
1066
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
1094
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
1067
1095
                      '>>>>>>> '])
1068
1096
 
1069
1097
    def testNonClashInsert2(self):
1083
1111
        #######################################
1084
1112
        # skippd, not working yet
1085
1113
        return
1086
 
        
 
1114
 
1087
1115
        self.doMerge(['aaa', 'bbb', 'ccc'],
1088
1116
                     ['aaa', 'ddd', 'ccc'],
1089
1117
                     ['aaa', 'ccc'],
1126
1154
            """
1127
1155
        result = """\
1128
1156
            line 1
 
1157
<<<<<<<\x20
 
1158
            line 2
 
1159
=======
 
1160
>>>>>>>\x20
1129
1161
            """
1130
1162
        self._test_merge_from_strings(base, a, b, result)
1131
1163
 
1132
1164
    def test_deletion_overlap(self):
1133
1165
        """Delete overlapping regions with no other conflict.
1134
1166
 
1135
 
        Arguably it'd be better to treat these as agreement, rather than 
 
1167
        Arguably it'd be better to treat these as agreement, rather than
1136
1168
        conflict, but for now conflict is safer.
1137
1169
        """
1138
1170
        base = """\
1154
1186
            """
1155
1187
        result = """\
1156
1188
            start context
1157
 
<<<<<<< 
 
1189
<<<<<<<\x20
1158
1190
            int a() {}
1159
1191
=======
1160
1192
            int c() {}
1161
 
>>>>>>> 
 
1193
>>>>>>>\x20
1162
1194
            end context
1163
1195
            """
1164
1196
        self._test_merge_from_strings(base, a, b, result)
1190
1222
 
1191
1223
    def test_sync_on_deletion(self):
1192
1224
        """Specific case of merge where we can synchronize incorrectly.
1193
 
        
 
1225
 
1194
1226
        A previous version of the weave merge concluded that the two versions
1195
1227
        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 
 
1228
        Line 1 was then considered in isolation, and thought to be deleted on
1197
1229
        both sides.
1198
1230
 
1199
1231
        It's better to consider the whole thing as a disagreement region.
1218
1250
            """
1219
1251
        result = """\
1220
1252
            start context
1221
 
<<<<<<< 
 
1253
<<<<<<<\x20
1222
1254
            base line 1
1223
1255
            a's replacement line 2
1224
1256
=======
1225
1257
            b replaces
1226
1258
            both lines
1227
 
>>>>>>> 
 
1259
>>>>>>>\x20
1228
1260
            end context
1229
1261
            """
1230
1262
        self._test_merge_from_strings(base, a, b, result)
1241
1273
        write_weave(w, tmpf)
1242
1274
        self.log(tmpf.getvalue())
1243
1275
 
1244
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
 
1276
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1245
1277
                                'xxx', '>>>>>>> ', 'bbb']
1246
1278
 
1247
1279
 
1279
1311
        # origin is a fulltext
1280
1312
        entries = f.get_record_stream([('origin',)], 'unordered', False)
1281
1313
        base = entries.next()
1282
 
        ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
 
1314
        ft_data = ft_adapter.get_bytes(base)
1283
1315
        # merged is both a delta and multiple parents.
1284
1316
        entries = f.get_record_stream([('merged',)], 'unordered', False)
1285
1317
        merged = entries.next()
1286
 
        delta_data = delta_adapter.get_bytes(merged,
1287
 
            merged.get_bytes_as(merged.storage_kind))
 
1318
        delta_data = delta_adapter.get_bytes(merged)
1288
1319
        return ft_data, delta_data
1289
1320
 
1290
1321
    def test_deannotation_noeol(self):
1357
1388
 
1358
1389
    def test_unannotated_to_fulltext(self):
1359
1390
        """Test adapting unannotated knits to full texts.
1360
 
        
 
1391
 
1361
1392
        This is used for -> weaves, and for -> annotated knits.
1362
1393
        """
1363
1394
        # we need a full text, and a delta
1376
1407
 
1377
1408
    def test_unannotated_to_fulltext_no_eol(self):
1378
1409
        """Test adapting unannotated knits to full texts.
1379
 
        
 
1410
 
1380
1411
        This is used for -> weaves, and for -> annotated knits.
1381
1412
        """
1382
1413
        # we need a full text, and a delta
1442
1473
            self.addCleanup(lambda:self.cleanup(files))
1443
1474
        return files
1444
1475
 
 
1476
    def get_simple_key(self, suffix):
 
1477
        """Return a key for the object under test."""
 
1478
        if self.key_length == 1:
 
1479
            return (suffix,)
 
1480
        else:
 
1481
            return ('FileA',) + (suffix,)
 
1482
 
 
1483
    def test_add_lines(self):
 
1484
        f = self.get_versionedfiles()
 
1485
        key0 = self.get_simple_key('r0')
 
1486
        key1 = self.get_simple_key('r1')
 
1487
        key2 = self.get_simple_key('r2')
 
1488
        keyf = self.get_simple_key('foo')
 
1489
        f.add_lines(key0, [], ['a\n', 'b\n'])
 
1490
        if self.graph:
 
1491
            f.add_lines(key1, [key0], ['b\n', 'c\n'])
 
1492
        else:
 
1493
            f.add_lines(key1, [], ['b\n', 'c\n'])
 
1494
        keys = f.keys()
 
1495
        self.assertTrue(key0 in keys)
 
1496
        self.assertTrue(key1 in keys)
 
1497
        records = []
 
1498
        for record in f.get_record_stream([key0, key1], 'unordered', True):
 
1499
            records.append((record.key, record.get_bytes_as('fulltext')))
 
1500
        records.sort()
 
1501
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
 
1502
 
 
1503
    def test__add_text(self):
 
1504
        f = self.get_versionedfiles()
 
1505
        key0 = self.get_simple_key('r0')
 
1506
        key1 = self.get_simple_key('r1')
 
1507
        key2 = self.get_simple_key('r2')
 
1508
        keyf = self.get_simple_key('foo')
 
1509
        f._add_text(key0, [], 'a\nb\n')
 
1510
        if self.graph:
 
1511
            f._add_text(key1, [key0], 'b\nc\n')
 
1512
        else:
 
1513
            f._add_text(key1, [], 'b\nc\n')
 
1514
        keys = f.keys()
 
1515
        self.assertTrue(key0 in keys)
 
1516
        self.assertTrue(key1 in keys)
 
1517
        records = []
 
1518
        for record in f.get_record_stream([key0, key1], 'unordered', True):
 
1519
            records.append((record.key, record.get_bytes_as('fulltext')))
 
1520
        records.sort()
 
1521
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
 
1522
 
1445
1523
    def test_annotate(self):
1446
1524
        files = self.get_versionedfiles()
1447
1525
        self.get_diamond_files(files)
1481
1559
        self.assertRaises(RevisionNotPresent,
1482
1560
            files.annotate, prefix + ('missing-key',))
1483
1561
 
 
1562
    def test_check_no_parameters(self):
 
1563
        files = self.get_versionedfiles()
 
1564
 
 
1565
    def test_check_progressbar_parameter(self):
 
1566
        """A progress bar can be supplied because check can be a generator."""
 
1567
        pb = ui.ui_factory.nested_progress_bar()
 
1568
        self.addCleanup(pb.finished)
 
1569
        files = self.get_versionedfiles()
 
1570
        files.check(progress_bar=pb)
 
1571
 
 
1572
    def test_check_with_keys_becomes_generator(self):
 
1573
        files = self.get_versionedfiles()
 
1574
        self.get_diamond_files(files)
 
1575
        keys = files.keys()
 
1576
        entries = files.check(keys=keys)
 
1577
        seen = set()
 
1578
        # Texts output should be fulltexts.
 
1579
        self.capture_stream(files, entries, seen.add,
 
1580
            files.get_parent_map(keys), require_fulltext=True)
 
1581
        # All texts should be output.
 
1582
        self.assertEqual(set(keys), seen)
 
1583
 
 
1584
    def test_clear_cache(self):
 
1585
        files = self.get_versionedfiles()
 
1586
        files.clear_cache()
 
1587
 
1484
1588
    def test_construct(self):
1485
1589
        """Each parameterised test can be constructed on a transport."""
1486
1590
        files = self.get_versionedfiles()
1487
1591
 
1488
 
    def get_diamond_files(self, files, trailing_eol=True, left_only=False):
 
1592
    def get_diamond_files(self, files, trailing_eol=True, left_only=False,
 
1593
        nokeys=False):
1489
1594
        return get_diamond_files(files, self.key_length,
1490
1595
            trailing_eol=trailing_eol, nograph=not self.graph,
1491
 
            left_only=left_only)
 
1596
            left_only=left_only, nokeys=nokeys)
 
1597
 
 
1598
    def _add_content_nostoresha(self, add_lines):
 
1599
        """When nostore_sha is supplied using old content raises."""
 
1600
        vf = self.get_versionedfiles()
 
1601
        empty_text = ('a', [])
 
1602
        sample_text_nl = ('b', ["foo\n", "bar\n"])
 
1603
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
1604
        shas = []
 
1605
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
 
1606
            if add_lines:
 
1607
                sha, _, _ = vf.add_lines(self.get_simple_key(version), [],
 
1608
                                         lines)
 
1609
            else:
 
1610
                sha, _, _ = vf._add_text(self.get_simple_key(version), [],
 
1611
                                         ''.join(lines))
 
1612
            shas.append(sha)
 
1613
        # we now have a copy of all the lines in the vf.
 
1614
        for sha, (version, lines) in zip(
 
1615
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
 
1616
            new_key = self.get_simple_key(version + "2")
 
1617
            self.assertRaises(errors.ExistingContent,
 
1618
                vf.add_lines, new_key, [], lines,
 
1619
                nostore_sha=sha)
 
1620
            self.assertRaises(errors.ExistingContent,
 
1621
                vf._add_text, new_key, [], ''.join(lines),
 
1622
                nostore_sha=sha)
 
1623
            # and no new version should have been added.
 
1624
            record = vf.get_record_stream([new_key], 'unordered', True).next()
 
1625
            self.assertEqual('absent', record.storage_kind)
 
1626
 
 
1627
    def test_add_lines_nostoresha(self):
 
1628
        self._add_content_nostoresha(add_lines=True)
 
1629
 
 
1630
    def test__add_text_nostoresha(self):
 
1631
        self._add_content_nostoresha(add_lines=False)
1492
1632
 
1493
1633
    def test_add_lines_return(self):
1494
1634
        files = self.get_versionedfiles()
1521
1661
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1522
1662
                results)
1523
1663
 
 
1664
    def test_add_lines_no_key_generates_chk_key(self):
 
1665
        files = self.get_versionedfiles()
 
1666
        # save code by using the stock data insertion helper.
 
1667
        adds = self.get_diamond_files(files, nokeys=True)
 
1668
        results = []
 
1669
        # We can only validate the first 2 elements returned from add_lines.
 
1670
        for add in adds:
 
1671
            self.assertEqual(3, len(add))
 
1672
            results.append(add[:2])
 
1673
        if self.key_length == 1:
 
1674
            self.assertEqual([
 
1675
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1676
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1677
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1678
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1679
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
 
1680
                results)
 
1681
            # Check the added items got CHK keys.
 
1682
            self.assertEqual(set([
 
1683
                ('sha1:00e364d235126be43292ab09cb4686cf703ddc17',),
 
1684
                ('sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',),
 
1685
                ('sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',),
 
1686
                ('sha1:a8478686da38e370e32e42e8a0c220e33ee9132f',),
 
1687
                ('sha1:ed8bce375198ea62444dc71952b22cfc2b09226d',),
 
1688
                ]),
 
1689
                files.keys())
 
1690
        elif self.key_length == 2:
 
1691
            self.assertEqual([
 
1692
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1693
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1694
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1695
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1696
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1697
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1698
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1699
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1700
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
 
1701
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
 
1702
                results)
 
1703
            # Check the added items got CHK keys.
 
1704
            self.assertEqual(set([
 
1705
                ('FileA', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
 
1706
                ('FileA', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
 
1707
                ('FileA', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
 
1708
                ('FileA', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
 
1709
                ('FileA', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
 
1710
                ('FileB', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
 
1711
                ('FileB', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
 
1712
                ('FileB', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
 
1713
                ('FileB', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
 
1714
                ('FileB', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
 
1715
                ]),
 
1716
                files.keys())
 
1717
 
1524
1718
    def test_empty_lines(self):
1525
1719
        """Empty files can be stored."""
1526
1720
        f = self.get_versionedfiles()
1548
1742
            f.get_record_stream([key_b], 'unordered', True
1549
1743
                ).next().get_bytes_as('fulltext'))
1550
1744
 
 
1745
    def test_get_known_graph_ancestry(self):
 
1746
        f = self.get_versionedfiles()
 
1747
        if not self.graph:
 
1748
            raise TestNotApplicable('ancestry info only relevant with graph.')
 
1749
        key_a = self.get_simple_key('a')
 
1750
        key_b = self.get_simple_key('b')
 
1751
        key_c = self.get_simple_key('c')
 
1752
        # A
 
1753
        # |\
 
1754
        # | B
 
1755
        # |/
 
1756
        # C
 
1757
        f.add_lines(key_a, [], ['\n'])
 
1758
        f.add_lines(key_b, [key_a], ['\n'])
 
1759
        f.add_lines(key_c, [key_a, key_b], ['\n'])
 
1760
        kg = f.get_known_graph_ancestry([key_c])
 
1761
        self.assertIsInstance(kg, _mod_graph.KnownGraph)
 
1762
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
 
1763
 
 
1764
    def test_known_graph_with_fallbacks(self):
 
1765
        f = self.get_versionedfiles('files')
 
1766
        if not self.graph:
 
1767
            raise TestNotApplicable('ancestry info only relevant with graph.')
 
1768
        if getattr(f, 'add_fallback_versioned_files', None) is None:
 
1769
            raise TestNotApplicable("%s doesn't support fallbacks"
 
1770
                                    % (f.__class__.__name__,))
 
1771
        key_a = self.get_simple_key('a')
 
1772
        key_b = self.get_simple_key('b')
 
1773
        key_c = self.get_simple_key('c')
 
1774
        # A     only in fallback
 
1775
        # |\
 
1776
        # | B
 
1777
        # |/
 
1778
        # C
 
1779
        g = self.get_versionedfiles('fallback')
 
1780
        g.add_lines(key_a, [], ['\n'])
 
1781
        f.add_fallback_versioned_files(g)
 
1782
        f.add_lines(key_b, [key_a], ['\n'])
 
1783
        f.add_lines(key_c, [key_a, key_b], ['\n'])
 
1784
        kg = f.get_known_graph_ancestry([key_c])
 
1785
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
 
1786
 
1551
1787
    def test_get_record_stream_empty(self):
1552
1788
        """An empty stream can be requested without error."""
1553
1789
        f = self.get_versionedfiles()
1560
1796
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1561
1797
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1562
1798
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1563
 
             'knit-delta-gz'])
 
1799
             'knit-delta-gz',
 
1800
             'knit-delta-closure', 'knit-delta-closure-ref',
 
1801
             'groupcompress-block', 'groupcompress-block-ref'])
1564
1802
 
1565
 
    def capture_stream(self, f, entries, on_seen, parents):
 
1803
    def capture_stream(self, f, entries, on_seen, parents,
 
1804
        require_fulltext=False):
1566
1805
        """Capture a stream for testing."""
1567
1806
        for factory in entries:
1568
1807
            on_seen(factory.key)
1569
1808
            self.assertValidStorageKind(factory.storage_kind)
1570
 
            self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1571
 
                factory.sha1)
 
1809
            if factory.sha1 is not None:
 
1810
                self.assertEqual(f.get_sha1s([factory.key])[factory.key],
 
1811
                    factory.sha1)
1572
1812
            self.assertEqual(parents[factory.key], factory.parents)
1573
1813
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1574
1814
                str)
 
1815
            if require_fulltext:
 
1816
                factory.get_bytes_as('fulltext')
1575
1817
 
1576
1818
    def test_get_record_stream_interface(self):
1577
1819
        """each item in a stream has to provide a regular interface."""
1584
1826
        self.capture_stream(files, entries, seen.add, parent_map)
1585
1827
        self.assertEqual(set(keys), seen)
1586
1828
 
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
1829
    def get_keys_and_sort_order(self):
1595
1830
        """Get diamond test keys list, and their sort ordering."""
1596
1831
        if self.key_length == 1:
1611
1846
                }
1612
1847
        return keys, sort_order
1613
1848
 
 
1849
    def get_keys_and_groupcompress_sort_order(self):
 
1850
        """Get diamond test keys list, and their groupcompress sort ordering."""
 
1851
        if self.key_length == 1:
 
1852
            keys = [('merged',), ('left',), ('right',), ('base',)]
 
1853
            sort_order = {('merged',):0, ('left',):1, ('right',):1, ('base',):2}
 
1854
        else:
 
1855
            keys = [
 
1856
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
 
1857
                ('FileA', 'base'),
 
1858
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
 
1859
                ('FileB', 'base'),
 
1860
                ]
 
1861
            sort_order = {
 
1862
                ('FileA', 'merged'):0, ('FileA', 'left'):1, ('FileA', 'right'):1,
 
1863
                ('FileA', 'base'):2,
 
1864
                ('FileB', 'merged'):3, ('FileB', 'left'):4, ('FileB', 'right'):4,
 
1865
                ('FileB', 'base'):5,
 
1866
                }
 
1867
        return keys, sort_order
 
1868
 
1614
1869
    def test_get_record_stream_interface_ordered(self):
1615
1870
        """each item in a stream has to provide a regular interface."""
1616
1871
        files = self.get_versionedfiles()
1644
1899
 
1645
1900
        self.assertStreamOrder(sort_order, seen, keys)
1646
1901
 
 
1902
    def test_get_record_stream_interface_groupcompress(self):
 
1903
        """each item in a stream has to provide a regular interface."""
 
1904
        files = self.get_versionedfiles()
 
1905
        self.get_diamond_files(files)
 
1906
        keys, sort_order = self.get_keys_and_groupcompress_sort_order()
 
1907
        parent_map = files.get_parent_map(keys)
 
1908
        entries = files.get_record_stream(keys, 'groupcompress', False)
 
1909
        seen = []
 
1910
        self.capture_stream(files, entries, seen.append, parent_map)
 
1911
        self.assertStreamOrder(sort_order, seen, keys)
 
1912
 
1647
1913
    def assertStreamOrder(self, sort_order, seen, keys):
1648
1914
        self.assertEqual(len(set(seen)), len(keys))
1649
1915
        if self.key_length == 1:
1680
1946
        for factory in entries:
1681
1947
            seen.add(factory.key)
1682
1948
            self.assertValidStorageKind(factory.storage_kind)
1683
 
            self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1684
 
                factory.sha1)
 
1949
            if factory.sha1 is not None:
 
1950
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1951
                                 factory.sha1)
1685
1952
            self.assertEqual(parent_map[factory.key], factory.parents)
1686
1953
            # currently no stream emits mpdiff
1687
1954
            self.assertRaises(errors.UnavailableRepresentation,
1709
1976
        entries = files.get_record_stream(keys, 'topological', False)
1710
1977
        self.assertAbsentRecord(files, keys, parent_map, entries)
1711
1978
 
 
1979
    def assertRecordHasContent(self, record, bytes):
 
1980
        """Assert that record has the bytes bytes."""
 
1981
        self.assertEqual(bytes, record.get_bytes_as('fulltext'))
 
1982
        self.assertEqual(bytes, ''.join(record.get_bytes_as('chunked')))
 
1983
 
 
1984
    def test_get_record_stream_native_formats_are_wire_ready_one_ft(self):
 
1985
        files = self.get_versionedfiles()
 
1986
        key = self.get_simple_key('foo')
 
1987
        files.add_lines(key, (), ['my text\n', 'content'])
 
1988
        stream = files.get_record_stream([key], 'unordered', False)
 
1989
        record = stream.next()
 
1990
        if record.storage_kind in ('chunked', 'fulltext'):
 
1991
            # chunked and fulltext representations are for direct use not wire
 
1992
            # serialisation: check they are able to be used directly. To send
 
1993
            # such records over the wire translation will be needed.
 
1994
            self.assertRecordHasContent(record, "my text\ncontent")
 
1995
        else:
 
1996
            bytes = [record.get_bytes_as(record.storage_kind)]
 
1997
            network_stream = versionedfile.NetworkRecordStream(bytes).read()
 
1998
            source_record = record
 
1999
            records = []
 
2000
            for record in network_stream:
 
2001
                records.append(record)
 
2002
                self.assertEqual(source_record.storage_kind,
 
2003
                    record.storage_kind)
 
2004
                self.assertEqual(source_record.parents, record.parents)
 
2005
                self.assertEqual(
 
2006
                    source_record.get_bytes_as(source_record.storage_kind),
 
2007
                    record.get_bytes_as(record.storage_kind))
 
2008
            self.assertEqual(1, len(records))
 
2009
 
 
2010
    def assertStreamMetaEqual(self, records, expected, stream):
 
2011
        """Assert that streams expected and stream have the same records.
 
2012
 
 
2013
        :param records: A list to collect the seen records.
 
2014
        :return: A generator of the records in stream.
 
2015
        """
 
2016
        # We make assertions during copying to catch things early for
 
2017
        # easier debugging.
 
2018
        for record, ref_record in izip(stream, expected):
 
2019
            records.append(record)
 
2020
            self.assertEqual(ref_record.key, record.key)
 
2021
            self.assertEqual(ref_record.storage_kind, record.storage_kind)
 
2022
            self.assertEqual(ref_record.parents, record.parents)
 
2023
            yield record
 
2024
 
 
2025
    def stream_to_bytes_or_skip_counter(self, skipped_records, full_texts,
 
2026
        stream):
 
2027
        """Convert a stream to a bytes iterator.
 
2028
 
 
2029
        :param skipped_records: A list with one element to increment when a
 
2030
            record is skipped.
 
2031
        :param full_texts: A dict from key->fulltext representation, for
 
2032
            checking chunked or fulltext stored records.
 
2033
        :param stream: A record_stream.
 
2034
        :return: An iterator over the bytes of each record.
 
2035
        """
 
2036
        for record in stream:
 
2037
            if record.storage_kind in ('chunked', 'fulltext'):
 
2038
                skipped_records[0] += 1
 
2039
                # check the content is correct for direct use.
 
2040
                self.assertRecordHasContent(record, full_texts[record.key])
 
2041
            else:
 
2042
                yield record.get_bytes_as(record.storage_kind)
 
2043
 
 
2044
    def test_get_record_stream_native_formats_are_wire_ready_ft_delta(self):
 
2045
        files = self.get_versionedfiles()
 
2046
        target_files = self.get_versionedfiles('target')
 
2047
        key = self.get_simple_key('ft')
 
2048
        key_delta = self.get_simple_key('delta')
 
2049
        files.add_lines(key, (), ['my text\n', 'content'])
 
2050
        if self.graph:
 
2051
            delta_parents = (key,)
 
2052
        else:
 
2053
            delta_parents = ()
 
2054
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
2055
        local = files.get_record_stream([key, key_delta], 'unordered', False)
 
2056
        ref = files.get_record_stream([key, key_delta], 'unordered', False)
 
2057
        skipped_records = [0]
 
2058
        full_texts = {
 
2059
            key: "my text\ncontent",
 
2060
            key_delta: "different\ncontent\n",
 
2061
            }
 
2062
        byte_stream = self.stream_to_bytes_or_skip_counter(
 
2063
            skipped_records, full_texts, local)
 
2064
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
 
2065
        records = []
 
2066
        # insert the stream from the network into a versioned files object so we can
 
2067
        # check the content was carried across correctly without doing delta
 
2068
        # inspection.
 
2069
        target_files.insert_record_stream(
 
2070
            self.assertStreamMetaEqual(records, ref, network_stream))
 
2071
        # No duplicates on the wire thank you!
 
2072
        self.assertEqual(2, len(records) + skipped_records[0])
 
2073
        if len(records):
 
2074
            # if any content was copied it all must have all been.
 
2075
            self.assertIdenticalVersionedFile(files, target_files)
 
2076
 
 
2077
    def test_get_record_stream_native_formats_are_wire_ready_delta(self):
 
2078
        # copy a delta over the wire
 
2079
        files = self.get_versionedfiles()
 
2080
        target_files = self.get_versionedfiles('target')
 
2081
        key = self.get_simple_key('ft')
 
2082
        key_delta = self.get_simple_key('delta')
 
2083
        files.add_lines(key, (), ['my text\n', 'content'])
 
2084
        if self.graph:
 
2085
            delta_parents = (key,)
 
2086
        else:
 
2087
            delta_parents = ()
 
2088
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
2089
        # Copy the basis text across so we can reconstruct the delta during
 
2090
        # insertion into target.
 
2091
        target_files.insert_record_stream(files.get_record_stream([key],
 
2092
            'unordered', False))
 
2093
        local = files.get_record_stream([key_delta], 'unordered', False)
 
2094
        ref = files.get_record_stream([key_delta], 'unordered', False)
 
2095
        skipped_records = [0]
 
2096
        full_texts = {
 
2097
            key_delta: "different\ncontent\n",
 
2098
            }
 
2099
        byte_stream = self.stream_to_bytes_or_skip_counter(
 
2100
            skipped_records, full_texts, local)
 
2101
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
 
2102
        records = []
 
2103
        # insert the stream from the network into a versioned files object so we can
 
2104
        # check the content was carried across correctly without doing delta
 
2105
        # inspection during check_stream.
 
2106
        target_files.insert_record_stream(
 
2107
            self.assertStreamMetaEqual(records, ref, network_stream))
 
2108
        # No duplicates on the wire thank you!
 
2109
        self.assertEqual(1, len(records) + skipped_records[0])
 
2110
        if len(records):
 
2111
            # if any content was copied it all must have all been
 
2112
            self.assertIdenticalVersionedFile(files, target_files)
 
2113
 
 
2114
    def test_get_record_stream_wire_ready_delta_closure_included(self):
 
2115
        # copy a delta over the wire with the ability to get its full text.
 
2116
        files = self.get_versionedfiles()
 
2117
        key = self.get_simple_key('ft')
 
2118
        key_delta = self.get_simple_key('delta')
 
2119
        files.add_lines(key, (), ['my text\n', 'content'])
 
2120
        if self.graph:
 
2121
            delta_parents = (key,)
 
2122
        else:
 
2123
            delta_parents = ()
 
2124
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
2125
        local = files.get_record_stream([key_delta], 'unordered', True)
 
2126
        ref = files.get_record_stream([key_delta], 'unordered', True)
 
2127
        skipped_records = [0]
 
2128
        full_texts = {
 
2129
            key_delta: "different\ncontent\n",
 
2130
            }
 
2131
        byte_stream = self.stream_to_bytes_or_skip_counter(
 
2132
            skipped_records, full_texts, local)
 
2133
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
 
2134
        records = []
 
2135
        # insert the stream from the network into a versioned files object so we can
 
2136
        # check the content was carried across correctly without doing delta
 
2137
        # inspection during check_stream.
 
2138
        for record in self.assertStreamMetaEqual(records, ref, network_stream):
 
2139
            # we have to be able to get the full text out:
 
2140
            self.assertRecordHasContent(record, full_texts[record.key])
 
2141
        # No duplicates on the wire thank you!
 
2142
        self.assertEqual(1, len(records) + skipped_records[0])
 
2143
 
1712
2144
    def assertAbsentRecord(self, files, keys, parents, entries):
1713
2145
        """Helper for test_get_record_stream_missing_records_are_absent."""
1714
2146
        seen = set()
1720
2152
                self.assertEqual(None, factory.parents)
1721
2153
            else:
1722
2154
                self.assertValidStorageKind(factory.storage_kind)
1723
 
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1724
 
                    factory.sha1)
 
2155
                if factory.sha1 is not None:
 
2156
                    sha1 = files.get_sha1s([factory.key])[factory.key]
 
2157
                    self.assertEqual(sha1, factory.sha1)
1725
2158
                self.assertEqual(parents[factory.key], factory.parents)
1726
2159
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1727
2160
                    str)
1761
2194
        else:
1762
2195
            return None
1763
2196
 
 
2197
    def test_get_annotator(self):
 
2198
        files = self.get_versionedfiles()
 
2199
        self.get_diamond_files(files)
 
2200
        origin_key = self.get_simple_key('origin')
 
2201
        base_key = self.get_simple_key('base')
 
2202
        left_key = self.get_simple_key('left')
 
2203
        right_key = self.get_simple_key('right')
 
2204
        merged_key = self.get_simple_key('merged')
 
2205
        # annotator = files.get_annotator()
 
2206
        # introduced full text
 
2207
        origins, lines = files.get_annotator().annotate(origin_key)
 
2208
        self.assertEqual([(origin_key,)], origins)
 
2209
        self.assertEqual(['origin\n'], lines)
 
2210
        # a delta
 
2211
        origins, lines = files.get_annotator().annotate(base_key)
 
2212
        self.assertEqual([(base_key,)], origins)
 
2213
        # a merge
 
2214
        origins, lines = files.get_annotator().annotate(merged_key)
 
2215
        if self.graph:
 
2216
            self.assertEqual([
 
2217
                (base_key,),
 
2218
                (left_key,),
 
2219
                (right_key,),
 
2220
                (merged_key,),
 
2221
                ], origins)
 
2222
        else:
 
2223
            # Without a graph everything is new.
 
2224
            self.assertEqual([
 
2225
                (merged_key,),
 
2226
                (merged_key,),
 
2227
                (merged_key,),
 
2228
                (merged_key,),
 
2229
                ], origins)
 
2230
        self.assertRaises(RevisionNotPresent,
 
2231
            files.get_annotator().annotate, self.get_simple_key('missing-key'))
 
2232
 
1764
2233
    def test_get_parent_map(self):
1765
2234
        files = self.get_versionedfiles()
1766
2235
        if self.key_length == 1:
1817
2286
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
1818
2287
            },
1819
2288
            files.get_sha1s(keys))
1820
 
        
 
2289
 
1821
2290
    def test_insert_record_stream_empty(self):
1822
2291
        """Inserting an empty record stream should work."""
1823
2292
        files = self.get_versionedfiles()
1969
2438
        else:
1970
2439
            self.assertIdenticalVersionedFile(source, files)
1971
2440
 
 
2441
    def test_insert_record_stream_long_parent_chain_out_of_order(self):
 
2442
        """An out of order stream can either error or work."""
 
2443
        if not self.graph:
 
2444
            raise TestNotApplicable('ancestry info only relevant with graph.')
 
2445
        # Create a reasonably long chain of records based on each other, where
 
2446
        # most will be deltas.
 
2447
        source = self.get_versionedfiles('source')
 
2448
        parents = ()
 
2449
        keys = []
 
2450
        content = [('same same %d\n' % n) for n in range(500)]
 
2451
        for letter in 'abcdefghijklmnopqrstuvwxyz':
 
2452
            key = ('key-' + letter,)
 
2453
            if self.key_length == 2:
 
2454
                key = ('prefix',) + key
 
2455
            content.append('content for ' + letter + '\n')
 
2456
            source.add_lines(key, parents, content)
 
2457
            keys.append(key)
 
2458
            parents = (key,)
 
2459
        # Create a stream of these records, excluding the first record that the
 
2460
        # rest ultimately depend upon, and insert it into a new vf.
 
2461
        streams = []
 
2462
        for key in reversed(keys):
 
2463
            streams.append(source.get_record_stream([key], 'unordered', False))
 
2464
        deltas = chain(*streams[:-1])
 
2465
        files = self.get_versionedfiles()
 
2466
        try:
 
2467
            files.insert_record_stream(deltas)
 
2468
        except RevisionNotPresent:
 
2469
            # Must not have corrupted the file.
 
2470
            files.check()
 
2471
        else:
 
2472
            # Must only report either just the first key as a missing parent,
 
2473
            # no key as missing (for nodelta scenarios).
 
2474
            missing = set(files.get_missing_compression_parent_keys())
 
2475
            missing.discard(keys[0])
 
2476
            self.assertEqual(set(), missing)
 
2477
 
 
2478
    def get_knit_delta_source(self):
 
2479
        """Get a source that can produce a stream with knit delta records,
 
2480
        regardless of this test's scenario.
 
2481
        """
 
2482
        mapper = self.get_mapper()
 
2483
        source_transport = self.get_transport('source')
 
2484
        source_transport.mkdir('.')
 
2485
        source = make_file_factory(False, mapper)(source_transport)
 
2486
        get_diamond_files(source, self.key_length, trailing_eol=True,
 
2487
            nograph=False, left_only=False)
 
2488
        return source
 
2489
 
1972
2490
    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)
 
2491
        """Insertion where a needed basis is not included notifies the caller
 
2492
        of the missing basis.  In the meantime a record missing its basis is
 
2493
        not added.
 
2494
        """
 
2495
        source = self.get_knit_delta_source()
 
2496
        keys = [self.get_simple_key('origin'), self.get_simple_key('merged')]
 
2497
        entries = source.get_record_stream(keys, 'unordered', False)
 
2498
        files = self.get_versionedfiles()
 
2499
        if self.support_partial_insertion:
 
2500
            self.assertEqual([],
 
2501
                list(files.get_missing_compression_parent_keys()))
 
2502
            files.insert_record_stream(entries)
 
2503
            missing_bases = files.get_missing_compression_parent_keys()
 
2504
            self.assertEqual(set([self.get_simple_key('left')]),
 
2505
                set(missing_bases))
 
2506
            self.assertEqual(set(keys), set(files.get_parent_map(keys)))
 
2507
        else:
 
2508
            self.assertRaises(
 
2509
                errors.RevisionNotPresent, files.insert_record_stream, entries)
 
2510
            files.check()
 
2511
 
 
2512
    def test_insert_record_stream_delta_missing_basis_can_be_added_later(self):
 
2513
        """Insertion where a needed basis is not included notifies the caller
 
2514
        of the missing basis.  That basis can be added in a second
 
2515
        insert_record_stream call that does not need to repeat records present
 
2516
        in the previous stream.  The record(s) that required that basis are
 
2517
        fully inserted once their basis is no longer missing.
 
2518
        """
 
2519
        if not self.support_partial_insertion:
 
2520
            raise TestNotApplicable(
 
2521
                'versioned file scenario does not support partial insertion')
 
2522
        source = self.get_knit_delta_source()
 
2523
        entries = source.get_record_stream([self.get_simple_key('origin'),
 
2524
            self.get_simple_key('merged')], 'unordered', False)
 
2525
        files = self.get_versionedfiles()
 
2526
        files.insert_record_stream(entries)
 
2527
        missing_bases = files.get_missing_compression_parent_keys()
 
2528
        self.assertEqual(set([self.get_simple_key('left')]),
 
2529
            set(missing_bases))
 
2530
        # 'merged' is inserted (although a commit of a write group involving
 
2531
        # this versionedfiles would fail).
 
2532
        merged_key = self.get_simple_key('merged')
 
2533
        self.assertEqual(
 
2534
            [merged_key], files.get_parent_map([merged_key]).keys())
 
2535
        # Add the full delta closure of the missing records
 
2536
        missing_entries = source.get_record_stream(
 
2537
            missing_bases, 'unordered', True)
 
2538
        files.insert_record_stream(missing_entries)
 
2539
        # Now 'merged' is fully inserted (and a commit would succeed).
 
2540
        self.assertEqual([], list(files.get_missing_compression_parent_keys()))
 
2541
        self.assertEqual(
 
2542
            [merged_key], files.get_parent_map([merged_key]).keys())
1984
2543
        files.check()
1985
 
        self.assertEqual({}, files.get_parent_map([]))
1986
2544
 
1987
2545
    def test_iter_lines_added_or_present_in_keys(self):
1988
2546
        # test that we get at least an equalset of the lines added by
2031
2589
            return lines
2032
2590
        lines = iter_with_keys(
2033
2591
            [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)])
 
2592
            [('Walking content', 0, 2),
 
2593
             ('Walking content', 1, 2),
 
2594
             ('Walking content', 2, 2)])
2037
2595
        # we must see child and otherchild
2038
2596
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2039
2597
        self.assertTrue(
2040
2598
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2041
2599
        # we dont care if we got more than that.
2042
 
        
 
2600
 
2043
2601
        # test all lines
2044
2602
        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)])
 
2603
            [('Walking content', 0, 5),
 
2604
             ('Walking content', 1, 5),
 
2605
             ('Walking content', 2, 5),
 
2606
             ('Walking content', 3, 5),
 
2607
             ('Walking content', 4, 5),
 
2608
             ('Walking content', 5, 5)])
2051
2609
        # all lines must be seen at least once
2052
2610
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2053
2611
        self.assertTrue(
2086
2644
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2087
2645
        # noeol preceeding its leftmost parent in the output:
2088
2646
        # this is done by making it a merge of two parents with no common
2089
 
        # anestry: noeolbase and noeol with the 
 
2647
        # anestry: noeolbase and noeol with the
2090
2648
        # later-inserted parent the leftmost.
2091
2649
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2092
2650
            self.get_parents([self.get_simple_key('noeolbase'),
2178
2736
        TestCase.setUp(self)
2179
2737
        self._lines = {}
2180
2738
        self._parent_map = {}
2181
 
        self.texts = VirtualVersionedFiles(self._get_parent_map, 
 
2739
        self.texts = VirtualVersionedFiles(self._get_parent_map,
2182
2740
                                           self._lines.get)
2183
2741
 
2184
2742
    def test_add_lines(self):
2185
 
        self.assertRaises(NotImplementedError, 
 
2743
        self.assertRaises(NotImplementedError,
2186
2744
                self.texts.add_lines, "foo", [], [])
2187
2745
 
2188
2746
    def test_add_mpdiffs(self):
2189
 
        self.assertRaises(NotImplementedError, 
 
2747
        self.assertRaises(NotImplementedError,
2190
2748
                self.texts.add_mpdiffs, [])
2191
2749
 
2192
 
    def test_check(self):
2193
 
        self.assertTrue(self.texts.check())
 
2750
    def test_check_noerrors(self):
 
2751
        self.texts.check()
2194
2752
 
2195
2753
    def test_insert_record_stream(self):
2196
2754
        self.assertRaises(NotImplementedError, self.texts.insert_record_stream,
2206
2764
 
2207
2765
    def test_get_parent_map(self):
2208
2766
        self._parent_map = {"G": ("A", "B")}
2209
 
        self.assertEquals({("G",): (("A",),("B",))}, 
 
2767
        self.assertEquals({("G",): (("A",),("B",))},
2210
2768
                          self.texts.get_parent_map([("G",), ("L",)]))
2211
2769
 
2212
2770
    def test_get_record_stream(self):
2227
2785
        self._lines["B"] = ["HEY"]
2228
2786
        self._lines["C"] = ["Alberta"]
2229
2787
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2230
 
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]), 
 
2788
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2231
2789
            sorted(list(it)))
2232
2790
 
2233
2791