~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_versionedfile.py

  • Committer: Martin von Gagern
  • Date: 2010-04-20 08:47:38 UTC
  • mfrom: (5167 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5195.
  • Revision ID: martin.vgagern@gmx.net-20100420084738-ygymnqmdllzrhpfn
merge trunk

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()
1558
1793
        """Assert that storage_kind is a valid storage_kind."""
1559
1794
        self.assertSubset([storage_kind],
1560
1795
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1561
 
             'knit-ft', 'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
1562
 
             'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'])
 
1796
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
 
1797
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
 
1798
             'knit-delta-gz',
 
1799
             'knit-delta-closure', 'knit-delta-closure-ref',
 
1800
             'groupcompress-block', 'groupcompress-block-ref'])
1563
1801
 
1564
 
    def capture_stream(self, f, entries, on_seen, parents):
 
1802
    def capture_stream(self, f, entries, on_seen, parents,
 
1803
        require_fulltext=False):
1565
1804
        """Capture a stream for testing."""
1566
1805
        for factory in entries:
1567
1806
            on_seen(factory.key)
1568
1807
            self.assertValidStorageKind(factory.storage_kind)
1569
 
            self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1570
 
                factory.sha1)
 
1808
            if factory.sha1 is not None:
 
1809
                self.assertEqual(f.get_sha1s([factory.key])[factory.key],
 
1810
                    factory.sha1)
1571
1811
            self.assertEqual(parents[factory.key], factory.parents)
1572
1812
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1573
1813
                str)
 
1814
            if require_fulltext:
 
1815
                factory.get_bytes_as('fulltext')
1574
1816
 
1575
1817
    def test_get_record_stream_interface(self):
1576
1818
        """each item in a stream has to provide a regular interface."""
1583
1825
        self.capture_stream(files, entries, seen.add, parent_map)
1584
1826
        self.assertEqual(set(keys), seen)
1585
1827
 
1586
 
    def get_simple_key(self, suffix):
1587
 
        """Return a key for the object under test."""
1588
 
        if self.key_length == 1:
1589
 
            return (suffix,)
1590
 
        else:
1591
 
            return ('FileA',) + (suffix,)
1592
 
 
1593
1828
    def get_keys_and_sort_order(self):
1594
1829
        """Get diamond test keys list, and their sort ordering."""
1595
1830
        if self.key_length == 1:
1610
1845
                }
1611
1846
        return keys, sort_order
1612
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
 
1613
1868
    def test_get_record_stream_interface_ordered(self):
1614
1869
        """each item in a stream has to provide a regular interface."""
1615
1870
        files = self.get_versionedfiles()
1636
1891
                [None, files.get_sha1s([factory.key])[factory.key]])
1637
1892
            self.assertEqual(parent_map[factory.key], factory.parents)
1638
1893
            # self.assertEqual(files.get_text(factory.key),
1639
 
            self.assertIsInstance(factory.get_bytes_as('fulltext'), str)
1640
 
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1641
 
                str)
 
1894
            ft_bytes = factory.get_bytes_as('fulltext')
 
1895
            self.assertIsInstance(ft_bytes, str)
 
1896
            chunked_bytes = factory.get_bytes_as('chunked')
 
1897
            self.assertEqualDiff(ft_bytes, ''.join(chunked_bytes))
 
1898
 
 
1899
        self.assertStreamOrder(sort_order, seen, keys)
 
1900
 
 
1901
    def test_get_record_stream_interface_groupcompress(self):
 
1902
        """each item in a stream has to provide a regular interface."""
 
1903
        files = self.get_versionedfiles()
 
1904
        self.get_diamond_files(files)
 
1905
        keys, sort_order = self.get_keys_and_groupcompress_sort_order()
 
1906
        parent_map = files.get_parent_map(keys)
 
1907
        entries = files.get_record_stream(keys, 'groupcompress', False)
 
1908
        seen = []
 
1909
        self.capture_stream(files, entries, seen.append, parent_map)
1642
1910
        self.assertStreamOrder(sort_order, seen, keys)
1643
1911
 
1644
1912
    def assertStreamOrder(self, sort_order, seen, keys):
1677
1945
        for factory in entries:
1678
1946
            seen.add(factory.key)
1679
1947
            self.assertValidStorageKind(factory.storage_kind)
1680
 
            self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1681
 
                factory.sha1)
 
1948
            if factory.sha1 is not None:
 
1949
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1950
                                 factory.sha1)
1682
1951
            self.assertEqual(parent_map[factory.key], factory.parents)
1683
1952
            # currently no stream emits mpdiff
1684
1953
            self.assertRaises(errors.UnavailableRepresentation,
1706
1975
        entries = files.get_record_stream(keys, 'topological', False)
1707
1976
        self.assertAbsentRecord(files, keys, parent_map, entries)
1708
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
 
1709
2143
    def assertAbsentRecord(self, files, keys, parents, entries):
1710
2144
        """Helper for test_get_record_stream_missing_records_are_absent."""
1711
2145
        seen = set()
1717
2151
                self.assertEqual(None, factory.parents)
1718
2152
            else:
1719
2153
                self.assertValidStorageKind(factory.storage_kind)
1720
 
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1721
 
                    factory.sha1)
 
2154
                if factory.sha1 is not None:
 
2155
                    sha1 = files.get_sha1s([factory.key])[factory.key]
 
2156
                    self.assertEqual(sha1, factory.sha1)
1722
2157
                self.assertEqual(parents[factory.key], factory.parents)
1723
2158
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1724
2159
                    str)
1758
2193
        else:
1759
2194
            return None
1760
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
 
1761
2232
    def test_get_parent_map(self):
1762
2233
        files = self.get_versionedfiles()
1763
2234
        if self.key_length == 1:
1814
2285
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
1815
2286
            },
1816
2287
            files.get_sha1s(keys))
1817
 
        
 
2288
 
1818
2289
    def test_insert_record_stream_empty(self):
1819
2290
        """Inserting an empty record stream should work."""
1820
2291
        files = self.get_versionedfiles()
1966
2437
        else:
1967
2438
            self.assertIdenticalVersionedFile(source, files)
1968
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
 
1969
2489
    def test_insert_record_stream_delta_missing_basis_no_corruption(self):
1970
 
        """Insertion where a needed basis is not included aborts safely."""
1971
 
        # We use a knit always here to be sure we are getting a binary delta.
1972
 
        mapper = self.get_mapper()
1973
 
        source_transport = self.get_transport('source')
1974
 
        source_transport.mkdir('.')
1975
 
        source = make_file_factory(False, mapper)(source_transport)
1976
 
        self.get_diamond_files(source)
1977
 
        entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
1978
 
        files = self.get_versionedfiles()
1979
 
        self.assertRaises(RevisionNotPresent, files.insert_record_stream,
1980
 
            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())
1981
2542
        files.check()
1982
 
        self.assertEqual({}, files.get_parent_map([]))
1983
2543
 
1984
2544
    def test_iter_lines_added_or_present_in_keys(self):
1985
2545
        # test that we get at least an equalset of the lines added by
1987
2547
        # the ordering here is to make a tree so that dumb searches have
1988
2548
        # more changes to muck up.
1989
2549
 
1990
 
        class InstrumentedProgress(progress.DummyProgress):
 
2550
        class InstrumentedProgress(progress.ProgressTask):
1991
2551
 
1992
2552
            def __init__(self):
1993
 
 
1994
 
                progress.DummyProgress.__init__(self)
 
2553
                progress.ProgressTask.__init__(self)
1995
2554
                self.updates = []
1996
2555
 
1997
2556
            def update(self, msg=None, current=None, total=None):
2028
2587
            return lines
2029
2588
        lines = iter_with_keys(
2030
2589
            [self.get_simple_key('child'), self.get_simple_key('otherchild')],
2031
 
            [('Walking content.', 0, 2),
2032
 
             ('Walking content.', 1, 2),
2033
 
             ('Walking content.', 2, 2)])
 
2590
            [('Walking content', 0, 2),
 
2591
             ('Walking content', 1, 2),
 
2592
             ('Walking content', 2, 2)])
2034
2593
        # we must see child and otherchild
2035
2594
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2036
2595
        self.assertTrue(
2037
2596
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2038
2597
        # we dont care if we got more than that.
2039
 
        
 
2598
 
2040
2599
        # test all lines
2041
2600
        lines = iter_with_keys(files.keys(),
2042
 
            [('Walking content.', 0, 5),
2043
 
             ('Walking content.', 1, 5),
2044
 
             ('Walking content.', 2, 5),
2045
 
             ('Walking content.', 3, 5),
2046
 
             ('Walking content.', 4, 5),
2047
 
             ('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)])
2048
2607
        # all lines must be seen at least once
2049
2608
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2050
2609
        self.assertTrue(
2083
2642
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2084
2643
        # noeol preceeding its leftmost parent in the output:
2085
2644
        # this is done by making it a merge of two parents with no common
2086
 
        # anestry: noeolbase and noeol with the 
 
2645
        # anestry: noeolbase and noeol with the
2087
2646
        # later-inserted parent the leftmost.
2088
2647
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2089
2648
            self.get_parents([self.get_simple_key('noeolbase'),
2175
2734
        TestCase.setUp(self)
2176
2735
        self._lines = {}
2177
2736
        self._parent_map = {}
2178
 
        self.texts = VirtualVersionedFiles(self._get_parent_map, 
 
2737
        self.texts = VirtualVersionedFiles(self._get_parent_map,
2179
2738
                                           self._lines.get)
2180
2739
 
2181
2740
    def test_add_lines(self):
2182
 
        self.assertRaises(NotImplementedError, 
 
2741
        self.assertRaises(NotImplementedError,
2183
2742
                self.texts.add_lines, "foo", [], [])
2184
2743
 
2185
2744
    def test_add_mpdiffs(self):
2186
 
        self.assertRaises(NotImplementedError, 
 
2745
        self.assertRaises(NotImplementedError,
2187
2746
                self.texts.add_mpdiffs, [])
2188
2747
 
2189
 
    def test_check(self):
2190
 
        self.assertTrue(self.texts.check())
 
2748
    def test_check_noerrors(self):
 
2749
        self.texts.check()
2191
2750
 
2192
2751
    def test_insert_record_stream(self):
2193
2752
        self.assertRaises(NotImplementedError, self.texts.insert_record_stream,
2203
2762
 
2204
2763
    def test_get_parent_map(self):
2205
2764
        self._parent_map = {"G": ("A", "B")}
2206
 
        self.assertEquals({("G",): (("A",),("B",))}, 
 
2765
        self.assertEquals({("G",): (("A",),("B",))},
2207
2766
                          self.texts.get_parent_map([("G",), ("L",)]))
2208
2767
 
2209
2768
    def test_get_record_stream(self):
2210
2769
        self._lines["A"] = ["FOO", "BAR"]
2211
2770
        it = self.texts.get_record_stream([("A",)], "unordered", True)
2212
2771
        record = it.next()
2213
 
        self.assertEquals("fulltext", record.storage_kind)
 
2772
        self.assertEquals("chunked", record.storage_kind)
2214
2773
        self.assertEquals("FOOBAR", record.get_bytes_as("fulltext"))
 
2774
        self.assertEquals(["FOO", "BAR"], record.get_bytes_as("chunked"))
2215
2775
 
2216
2776
    def test_get_record_stream_absent(self):
2217
2777
        it = self.texts.get_record_stream([("A",)], "unordered", True)
2218
2778
        record = it.next()
2219
2779
        self.assertEquals("absent", record.storage_kind)
2220
2780
 
 
2781
    def test_iter_lines_added_or_present_in_keys(self):
 
2782
        self._lines["A"] = ["FOO", "BAR"]
 
2783
        self._lines["B"] = ["HEY"]
 
2784
        self._lines["C"] = ["Alberta"]
 
2785
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
 
2786
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
 
2787
            sorted(list(it)))
 
2788
 
 
2789
 
 
2790
class TestOrderingVersionedFilesDecorator(TestCaseWithMemoryTransport):
 
2791
 
 
2792
    def get_ordering_vf(self, key_priority):
 
2793
        builder = self.make_branch_builder('test')
 
2794
        builder.start_series()
 
2795
        builder.build_snapshot('A', None, [
 
2796
            ('add', ('', 'TREE_ROOT', 'directory', None))])
 
2797
        builder.build_snapshot('B', ['A'], [])
 
2798
        builder.build_snapshot('C', ['B'], [])
 
2799
        builder.build_snapshot('D', ['C'], [])
 
2800
        builder.finish_series()
 
2801
        b = builder.get_branch()
 
2802
        b.lock_read()
 
2803
        self.addCleanup(b.unlock)
 
2804
        vf = b.repository.inventories
 
2805
        return versionedfile.OrderingVersionedFilesDecorator(vf, key_priority)
 
2806
 
 
2807
    def test_get_empty(self):
 
2808
        vf = self.get_ordering_vf({})
 
2809
        self.assertEqual([], vf.calls)
 
2810
 
 
2811
    def test_get_record_stream_topological(self):
 
2812
        vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
 
2813
        request_keys = [('B',), ('C',), ('D',), ('A',)]
 
2814
        keys = [r.key for r in vf.get_record_stream(request_keys,
 
2815
                                    'topological', False)]
 
2816
        # We should have gotten the keys in topological order
 
2817
        self.assertEqual([('A',), ('B',), ('C',), ('D',)], keys)
 
2818
        # And recorded that the request was made
 
2819
        self.assertEqual([('get_record_stream', request_keys, 'topological',
 
2820
                           False)], vf.calls)
 
2821
 
 
2822
    def test_get_record_stream_ordered(self):
 
2823
        vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
 
2824
        request_keys = [('B',), ('C',), ('D',), ('A',)]
 
2825
        keys = [r.key for r in vf.get_record_stream(request_keys,
 
2826
                                   'unordered', False)]
 
2827
        # They should be returned based on their priority
 
2828
        self.assertEqual([('D',), ('B',), ('A',), ('C',)], keys)
 
2829
        # And the request recorded
 
2830
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
 
2831
                           False)], vf.calls)
 
2832
 
 
2833
    def test_get_record_stream_implicit_order(self):
 
2834
        vf = self.get_ordering_vf({('B',): 2, ('D',): 1})
 
2835
        request_keys = [('B',), ('C',), ('D',), ('A',)]
 
2836
        keys = [r.key for r in vf.get_record_stream(request_keys,
 
2837
                                   'unordered', False)]
 
2838
        # A and C are not in the map, so they get sorted to the front. A comes
 
2839
        # before C alphabetically, so it comes back first
 
2840
        self.assertEqual([('A',), ('C',), ('D',), ('B',)], keys)
 
2841
        # And the request recorded
 
2842
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
 
2843
                           False)], vf.calls)