~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_versionedfile.py

  • Committer: Vincent Ladeuil
  • Date: 2008-09-11 19:36:38 UTC
  • mfrom: (3703 +trunk)
  • mto: (3705.1.1 trunk2)
  • mto: This revision was merged to the branch mainline in revision 3708.
  • Revision ID: v.ladeuil+lp@free.fr-20080911193638-wtjyc1kcmacc6t1f
merge bzr.dev

Show diffs side-by-side

added added

removed removed

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