/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/per_versionedfile.py

  • Committer: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
# considered typical and check that it can be detected/corrected.
23
23
 
24
24
from gzip import GzipFile
 
25
from io import BytesIO
25
26
import itertools
26
27
 
27
28
from .. import (
45
46
    make_file_factory,
46
47
    make_pack_factory,
47
48
    )
48
 
from ..sixish import (
49
 
    BytesIO,
50
 
    zip,
51
 
    )
52
49
from . import (
53
50
    TestCase,
54
51
    TestCaseWithMemoryTransport,
59
56
from ..transport.memory import MemoryTransport
60
57
from ..bzr import versionedfile as versionedfile
61
58
from ..bzr.versionedfile import (
 
59
    ChunkedContentFactory,
62
60
    ConstantMapper,
63
61
    HashEscapedPrefixMapper,
64
62
    PrefixMapper,
82
80
    :param trailing_eol: If True end the last line with \n.
83
81
    """
84
82
    parents = {
85
 
        'origin': (),
86
 
        'base': (('origin',),),
87
 
        'left': (('base',),),
88
 
        'right': (('base',),),
89
 
        'merged': (('left',), ('right',)),
 
83
        b'origin': (),
 
84
        b'base': ((b'origin',),),
 
85
        b'left': ((b'base',),),
 
86
        b'right': ((b'base',),),
 
87
        b'merged': ((b'left',), (b'right',)),
90
88
        }
91
89
    # insert a diamond graph to exercise deltas and merges.
92
90
    if trailing_eol:
93
 
        last_char = '\n'
 
91
        last_char = b'\n'
94
92
    else:
95
 
        last_char = ''
96
 
    f.add_lines('origin', [], ['origin' + last_char])
97
 
    f.add_lines('base', ['origin'], ['base' + last_char])
98
 
    f.add_lines('left', ['base'], ['base\n', 'left' + last_char])
 
93
        last_char = b''
 
94
    f.add_lines(b'origin', [], [b'origin' + last_char])
 
95
    f.add_lines(b'base', [b'origin'], [b'base' + last_char])
 
96
    f.add_lines(b'left', [b'base'], [b'base\n', b'left' + last_char])
99
97
    if not left_only:
100
 
        f.add_lines('right', ['base'],
101
 
            ['base\n', 'right' + last_char])
102
 
        f.add_lines('merged', ['left', 'right'],
103
 
            ['base\n', 'left\n', 'right\n', 'merged' + last_char])
 
98
        f.add_lines(b'right', [b'base'],
 
99
                    [b'base\n', b'right' + last_char])
 
100
        f.add_lines(b'merged', [b'left', b'right'],
 
101
                    [b'base\n', b'left\n', b'right\n', b'merged' + last_char])
104
102
    return f, parents
105
103
 
106
104
 
107
105
def get_diamond_files(files, key_length, trailing_eol=True, left_only=False,
108
 
    nograph=False, nokeys=False):
 
106
                      nograph=False, nokeys=False):
109
107
    """Get a diamond graph to exercise deltas and merges.
110
108
 
111
109
    This creates a 5-node graph in files. If files supports 2-length keys two
127
125
    if key_length == 1:
128
126
        prefixes = [()]
129
127
    else:
130
 
        prefixes = [('FileA',), ('FileB',)]
 
128
        prefixes = [(b'FileA',), (b'FileB',)]
131
129
    # insert a diamond graph to exercise deltas and merges.
132
130
    if trailing_eol:
133
 
        last_char = '\n'
 
131
        last_char = b'\n'
134
132
    else:
135
 
        last_char = ''
 
133
        last_char = b''
136
134
    result = []
 
135
 
137
136
    def get_parents(suffix_list):
138
137
        if nograph:
139
138
            return ()
140
139
        else:
141
140
            result = [prefix + suffix for suffix in suffix_list]
142
141
            return result
 
142
 
143
143
    def get_key(suffix):
144
144
        if nokeys:
145
145
            return (None, )
148
148
    # we loop over each key because that spreads the inserts across prefixes,
149
149
    # which is how commit operates.
150
150
    for prefix in prefixes:
151
 
        result.append(files.add_lines(prefix + get_key('origin'), (),
152
 
            ['origin' + last_char]))
153
 
    for prefix in prefixes:
154
 
        result.append(files.add_lines(prefix + get_key('base'),
155
 
            get_parents([('origin',)]), ['base' + last_char]))
156
 
    for prefix in prefixes:
157
 
        result.append(files.add_lines(prefix + get_key('left'),
158
 
            get_parents([('base',)]),
159
 
            ['base\n', 'left' + last_char]))
 
151
        result.append(files.add_lines(prefix + get_key(b'origin'), (),
 
152
                                      [b'origin' + last_char]))
 
153
    for prefix in prefixes:
 
154
        result.append(files.add_lines(prefix + get_key(b'base'),
 
155
                                      get_parents([(b'origin',)]), [b'base' + last_char]))
 
156
    for prefix in prefixes:
 
157
        result.append(files.add_lines(prefix + get_key(b'left'),
 
158
                                      get_parents([(b'base',)]),
 
159
                                      [b'base\n', b'left' + last_char]))
160
160
    if not left_only:
161
161
        for prefix in prefixes:
162
 
            result.append(files.add_lines(prefix + get_key('right'),
163
 
                get_parents([('base',)]),
164
 
                ['base\n', 'right' + last_char]))
 
162
            result.append(files.add_lines(prefix + get_key(b'right'),
 
163
                                          get_parents([(b'base',)]),
 
164
                                          [b'base\n', b'right' + last_char]))
165
165
        for prefix in prefixes:
166
 
            result.append(files.add_lines(prefix + get_key('merged'),
167
 
                get_parents([('left',), ('right',)]),
168
 
                ['base\n', 'left\n', 'right\n', 'merged' + last_char]))
 
166
            result.append(files.add_lines(prefix + get_key(b'merged'),
 
167
                                          get_parents(
 
168
                                              [(b'left',), (b'right',)]),
 
169
                                          [b'base\n', b'left\n', b'right\n', b'merged' + last_char]))
169
170
    return result
170
171
 
171
172
 
184
185
 
185
186
    def test_add(self):
186
187
        f = self.get_file()
187
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
188
 
        f.add_lines('r1', ['r0'], ['b\n', 'c\n'])
 
188
        f.add_lines(b'r0', [], [b'a\n', b'b\n'])
 
189
        f.add_lines(b'r1', [b'r0'], [b'b\n', b'c\n'])
 
190
 
189
191
        def verify_file(f):
190
192
            versions = f.versions()
191
 
            self.assertTrue('r0' in versions)
192
 
            self.assertTrue('r1' in versions)
193
 
            self.assertEqual(f.get_lines('r0'), ['a\n', 'b\n'])
194
 
            self.assertEqual(f.get_text('r0'), 'a\nb\n')
195
 
            self.assertEqual(f.get_lines('r1'), ['b\n', 'c\n'])
 
193
            self.assertTrue(b'r0' in versions)
 
194
            self.assertTrue(b'r1' in versions)
 
195
            self.assertEqual(f.get_lines(b'r0'), [b'a\n', b'b\n'])
 
196
            self.assertEqual(f.get_text(b'r0'), b'a\nb\n')
 
197
            self.assertEqual(f.get_lines(b'r1'), [b'b\n', b'c\n'])
196
198
            self.assertEqual(2, len(f))
197
199
            self.assertEqual(2, f.num_versions())
198
200
 
199
201
            self.assertRaises(RevisionNotPresent,
200
 
                f.add_lines, 'r2', ['foo'], [])
 
202
                              f.add_lines, b'r2', [b'foo'], [])
201
203
            self.assertRaises(RevisionAlreadyPresent,
202
 
                f.add_lines, 'r1', [], [])
 
204
                              f.add_lines, b'r1', [], [])
203
205
        verify_file(f)
204
206
        # this checks that reopen with create=True does not break anything.
205
207
        f = self.reopen_file(create=True)
208
210
    def test_adds_with_parent_texts(self):
209
211
        f = self.get_file()
210
212
        parent_texts = {}
211
 
        _, _, parent_texts['r0'] = f.add_lines('r0', [], ['a\n', 'b\n'])
 
213
        _, _, parent_texts[b'r0'] = f.add_lines(b'r0', [], [b'a\n', b'b\n'])
212
214
        try:
213
 
            _, _, parent_texts['r1'] = f.add_lines_with_ghosts('r1',
214
 
                ['r0', 'ghost'], ['b\n', 'c\n'], parent_texts=parent_texts)
 
215
            _, _, parent_texts[b'r1'] = f.add_lines_with_ghosts(b'r1',
 
216
                                                                [b'r0', b'ghost'], [b'b\n', b'c\n'], parent_texts=parent_texts)
215
217
        except NotImplementedError:
216
218
            # if the format doesn't support ghosts, just add normally.
217
 
            _, _, parent_texts['r1'] = f.add_lines('r1',
218
 
                ['r0'], ['b\n', 'c\n'], parent_texts=parent_texts)
219
 
        f.add_lines('r2', ['r1'], ['c\n', 'd\n'], parent_texts=parent_texts)
220
 
        self.assertNotEqual(None, parent_texts['r0'])
221
 
        self.assertNotEqual(None, parent_texts['r1'])
 
219
            _, _, parent_texts[b'r1'] = f.add_lines(b'r1',
 
220
                                                    [b'r0'], [b'b\n', b'c\n'], parent_texts=parent_texts)
 
221
        f.add_lines(b'r2', [b'r1'], [b'c\n', b'd\n'],
 
222
                    parent_texts=parent_texts)
 
223
        self.assertNotEqual(None, parent_texts[b'r0'])
 
224
        self.assertNotEqual(None, parent_texts[b'r1'])
 
225
 
222
226
        def verify_file(f):
223
227
            versions = f.versions()
224
 
            self.assertTrue('r0' in versions)
225
 
            self.assertTrue('r1' in versions)
226
 
            self.assertTrue('r2' in versions)
227
 
            self.assertEqual(f.get_lines('r0'), ['a\n', 'b\n'])
228
 
            self.assertEqual(f.get_lines('r1'), ['b\n', 'c\n'])
229
 
            self.assertEqual(f.get_lines('r2'), ['c\n', 'd\n'])
 
228
            self.assertTrue(b'r0' in versions)
 
229
            self.assertTrue(b'r1' in versions)
 
230
            self.assertTrue(b'r2' in versions)
 
231
            self.assertEqual(f.get_lines(b'r0'), [b'a\n', b'b\n'])
 
232
            self.assertEqual(f.get_lines(b'r1'), [b'b\n', b'c\n'])
 
233
            self.assertEqual(f.get_lines(b'r2'), [b'c\n', b'd\n'])
230
234
            self.assertEqual(3, f.num_versions())
231
 
            origins = f.annotate('r1')
232
 
            self.assertEqual(origins[0][0], 'r0')
233
 
            self.assertEqual(origins[1][0], 'r1')
234
 
            origins = f.annotate('r2')
235
 
            self.assertEqual(origins[0][0], 'r1')
236
 
            self.assertEqual(origins[1][0], 'r2')
 
235
            origins = f.annotate(b'r1')
 
236
            self.assertEqual(origins[0][0], b'r0')
 
237
            self.assertEqual(origins[1][0], b'r1')
 
238
            origins = f.annotate(b'r2')
 
239
            self.assertEqual(origins[0][0], b'r1')
 
240
            self.assertEqual(origins[1][0], b'r2')
237
241
 
238
242
        verify_file(f)
239
243
        f = self.reopen_file()
244
248
        # versioned files version sequences of bytes only.
245
249
        vf = self.get_file()
246
250
        self.assertRaises(errors.BzrBadParameterUnicode,
247
 
            vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
 
251
                          vf.add_lines, b'a', [], [b'a\n', u'b\n', b'c\n'])
248
252
        self.assertRaises(
249
253
            (errors.BzrBadParameterUnicode, NotImplementedError),
250
 
            vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
 
254
            vf.add_lines_with_ghosts, b'a', [], [b'a\n', u'b\n', b'c\n'])
251
255
 
252
256
    def test_add_follows_left_matching_blocks(self):
253
257
        """If we change left_matching_blocks, delta changes
258
262
        vf = self.get_file()
259
263
        if isinstance(vf, WeaveFile):
260
264
            raise TestSkipped("WeaveFile ignores left_matching_blocks")
261
 
        vf.add_lines('1', [], ['a\n'])
262
 
        vf.add_lines('2', ['1'], ['a\n', 'a\n', 'a\n'],
 
265
        vf.add_lines(b'1', [], [b'a\n'])
 
266
        vf.add_lines(b'2', [b'1'], [b'a\n', b'a\n', b'a\n'],
263
267
                     left_matching_blocks=[(0, 0, 1), (1, 3, 0)])
264
 
        self.assertEqual(['a\n', 'a\n', 'a\n'], vf.get_lines('2'))
265
 
        vf.add_lines('3', ['1'], ['a\n', 'a\n', 'a\n'],
 
268
        self.assertEqual([b'a\n', b'a\n', b'a\n'], vf.get_lines(b'2'))
 
269
        vf.add_lines(b'3', [b'1'], [b'a\n', b'a\n', b'a\n'],
266
270
                     left_matching_blocks=[(0, 2, 1), (1, 3, 0)])
267
 
        self.assertEqual(['a\n', 'a\n', 'a\n'], vf.get_lines('3'))
 
271
        self.assertEqual([b'a\n', b'a\n', b'a\n'], vf.get_lines(b'3'))
268
272
 
269
273
    def test_inline_newline_throws(self):
270
274
        # \r characters are not permitted in lines being added
271
275
        vf = self.get_file()
272
276
        self.assertRaises(errors.BzrBadParameterContainsNewline,
273
 
            vf.add_lines, 'a', [], ['a\n\n'])
 
277
                          vf.add_lines, b'a', [], [b'a\n\n'])
274
278
        self.assertRaises(
275
279
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
276
 
            vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
 
280
            vf.add_lines_with_ghosts, b'a', [], [b'a\n\n'])
277
281
        # but inline CR's are allowed
278
 
        vf.add_lines('a', [], ['a\r\n'])
 
282
        vf.add_lines(b'a', [], [b'a\r\n'])
279
283
        try:
280
 
            vf.add_lines_with_ghosts('b', [], ['a\r\n'])
 
284
            vf.add_lines_with_ghosts(b'b', [], [b'a\r\n'])
281
285
        except NotImplementedError:
282
286
            pass
283
287
 
284
288
    def test_add_reserved(self):
285
289
        vf = self.get_file()
286
290
        self.assertRaises(errors.ReservedId,
287
 
            vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
 
291
                          vf.add_lines, b'a:', [], [b'a\n', b'b\n', b'c\n'])
288
292
 
289
293
    def test_add_lines_nostoresha(self):
290
294
        """When nostore_sha is supplied using old content raises."""
291
295
        vf = self.get_file()
292
 
        empty_text = ('a', [])
293
 
        sample_text_nl = ('b', ["foo\n", "bar\n"])
294
 
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
296
        empty_text = (b'a', [])
 
297
        sample_text_nl = (b'b', [b"foo\n", b"bar\n"])
 
298
        sample_text_no_nl = (b'c', [b"foo\n", b"bar"])
295
299
        shas = []
296
300
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
297
301
            sha, _, _ = vf.add_lines(version, [], lines)
298
302
            shas.append(sha)
299
303
        # we now have a copy of all the lines in the vf.
300
304
        for sha, (version, lines) in zip(
301
 
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
 
305
                shas, (empty_text, sample_text_nl, sample_text_no_nl)):
302
306
            self.assertRaises(errors.ExistingContent,
303
 
                vf.add_lines, version + "2", [], lines,
304
 
                nostore_sha=sha)
 
307
                              vf.add_lines, version + b"2", [], lines,
 
308
                              nostore_sha=sha)
305
309
            # and no new version should have been added.
306
310
            self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
307
 
                version + "2")
 
311
                              version + b"2")
308
312
 
309
313
    def test_add_lines_with_ghosts_nostoresha(self):
310
314
        """When nostore_sha is supplied using old content raises."""
311
315
        vf = self.get_file()
312
 
        empty_text = ('a', [])
313
 
        sample_text_nl = ('b', ["foo\n", "bar\n"])
314
 
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
316
        empty_text = (b'a', [])
 
317
        sample_text_nl = (b'b', [b"foo\n", b"bar\n"])
 
318
        sample_text_no_nl = (b'c', [b"foo\n", b"bar"])
315
319
        shas = []
316
320
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
317
321
            sha, _, _ = vf.add_lines(version, [], lines)
319
323
        # we now have a copy of all the lines in the vf.
320
324
        # is the test applicable to this vf implementation?
321
325
        try:
322
 
            vf.add_lines_with_ghosts('d', [], [])
 
326
            vf.add_lines_with_ghosts(b'd', [], [])
323
327
        except NotImplementedError:
324
328
            raise TestSkipped("add_lines_with_ghosts is optional")
325
329
        for sha, (version, lines) in zip(
326
 
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
 
330
                shas, (empty_text, sample_text_nl, sample_text_no_nl)):
327
331
            self.assertRaises(errors.ExistingContent,
328
 
                vf.add_lines_with_ghosts, version + "2", [], lines,
329
 
                nostore_sha=sha)
 
332
                              vf.add_lines_with_ghosts, version + b"2", [], lines,
 
333
                              nostore_sha=sha)
330
334
            # and no new version should have been added.
331
335
            self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
332
 
                version + "2")
 
336
                              version + b"2")
333
337
 
334
338
    def test_add_lines_return_value(self):
335
339
        # add_lines should return the sha1 and the text size.
336
340
        vf = self.get_file()
337
 
        empty_text = ('a', [])
338
 
        sample_text_nl = ('b', ["foo\n", "bar\n"])
339
 
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
341
        empty_text = (b'a', [])
 
342
        sample_text_nl = (b'b', [b"foo\n", b"bar\n"])
 
343
        sample_text_no_nl = (b'c', [b"foo\n", b"bar"])
340
344
        # check results for the three cases:
341
345
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
342
346
            # the first two elements are the same for all versioned files:
345
349
            result = vf.add_lines(version, [], lines)
346
350
            self.assertEqual(3, len(result))
347
351
            self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
348
 
                result[0:2])
 
352
                             result[0:2])
349
353
        # parents should not affect the result:
350
354
        lines = sample_text_nl[1]
351
355
        self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
352
 
            vf.add_lines('d', ['b', 'c'], lines)[0:2])
 
356
                         vf.add_lines(b'd', [b'b', b'c'], lines)[0:2])
353
357
 
354
358
    def test_get_reserved(self):
355
359
        vf = self.get_file()
356
 
        self.assertRaises(errors.ReservedId, vf.get_texts, ['b:'])
357
 
        self.assertRaises(errors.ReservedId, vf.get_lines, 'b:')
358
 
        self.assertRaises(errors.ReservedId, vf.get_text, 'b:')
 
360
        self.assertRaises(errors.ReservedId, vf.get_texts, [b'b:'])
 
361
        self.assertRaises(errors.ReservedId, vf.get_lines, b'b:')
 
362
        self.assertRaises(errors.ReservedId, vf.get_text, b'b:')
359
363
 
360
364
    def test_add_unchanged_last_line_noeol_snapshot(self):
361
365
        """Add a text with an unchanged last line with no eol should work."""
370
374
        for length in range(20):
371
375
            version_lines = {}
372
376
            vf = self.get_file('case-%d' % length)
373
 
            prefix = 'step-%d'
 
377
            prefix = b'step-%d'
374
378
            parents = []
375
379
            for step in range(length):
376
380
                version = prefix % step
377
 
                lines = (['prelude \n'] * step) + ['line']
 
381
                lines = ([b'prelude \n'] * step) + [b'line']
378
382
                vf.add_lines(version, parents, lines)
379
383
                version_lines[version] = lines
380
384
                parents = [version]
381
 
            vf.add_lines('no-eol', parents, ['line'])
 
385
            vf.add_lines(b'no-eol', parents, [b'line'])
382
386
            vf.get_texts(version_lines.keys())
383
 
            self.assertEqualDiff('line', vf.get_text('no-eol'))
 
387
            self.assertEqualDiff(b'line', vf.get_text(b'no-eol'))
384
388
 
385
389
    def test_get_texts_eol_variation(self):
386
390
        # similar to the failure in <http://bugs.launchpad.net/234748>
387
391
        vf = self.get_file()
388
 
        sample_text_nl = ["line\n"]
389
 
        sample_text_no_nl = ["line"]
 
392
        sample_text_nl = [b"line\n"]
 
393
        sample_text_no_nl = [b"line"]
390
394
        versions = []
391
395
        version_lines = {}
392
396
        parents = []
393
397
        for i in range(4):
394
 
            version = 'v%d' % i
 
398
            version = b'v%d' % i
395
399
            if i % 2:
396
400
                lines = sample_text_nl
397
401
            else:
403
407
            # (which is what this test tests) will generate a correct line
404
408
            # delta (which is to say, an empty delta).
405
409
            vf.add_lines(version, parents, lines,
406
 
                left_matching_blocks=[(0, 0, 1)])
 
410
                         left_matching_blocks=[(0, 0, 1)])
407
411
            parents = [version]
408
412
            versions.append(version)
409
413
            version_lines[version] = lines
421
425
        # Test adding this in two situations:
422
426
        # On top of a new insertion
423
427
        vf = self.get_file('fulltext')
424
 
        vf.add_lines('noeol', [], ['line'])
425
 
        vf.add_lines('noeol2', ['noeol'], ['newline\n', 'line'],
426
 
            left_matching_blocks=[(0, 1, 1)])
427
 
        self.assertEqualDiff('newline\nline', vf.get_text('noeol2'))
 
428
        vf.add_lines(b'noeol', [], [b'line'])
 
429
        vf.add_lines(b'noeol2', [b'noeol'], [b'newline\n', b'line'],
 
430
                     left_matching_blocks=[(0, 1, 1)])
 
431
        self.assertEqualDiff(b'newline\nline', vf.get_text(b'noeol2'))
428
432
        # On top of a delta
429
433
        vf = self.get_file('delta')
430
 
        vf.add_lines('base', [], ['line'])
431
 
        vf.add_lines('noeol', ['base'], ['prelude\n', 'line'])
432
 
        vf.add_lines('noeol2', ['noeol'], ['newline\n', 'line'],
433
 
            left_matching_blocks=[(1, 1, 1)])
434
 
        self.assertEqualDiff('newline\nline', vf.get_text('noeol2'))
 
434
        vf.add_lines(b'base', [], [b'line'])
 
435
        vf.add_lines(b'noeol', [b'base'], [b'prelude\n', b'line'])
 
436
        vf.add_lines(b'noeol2', [b'noeol'], [b'newline\n', b'line'],
 
437
                     left_matching_blocks=[(1, 1, 1)])
 
438
        self.assertEqualDiff(b'newline\nline', vf.get_text(b'noeol2'))
435
439
 
436
440
    def test_make_mpdiffs(self):
437
441
        from breezy import multiparent
448
452
    def test_make_mpdiffs_with_ghosts(self):
449
453
        vf = self.get_file('foo')
450
454
        try:
451
 
            vf.add_lines_with_ghosts('text', ['ghost'], ['line\n'])
 
455
            vf.add_lines_with_ghosts(b'text', [b'ghost'], [b'line\n'])
452
456
        except NotImplementedError:
453
457
            # old Weave formats do not allow ghosts
454
458
            return
455
 
        self.assertRaises(errors.RevisionNotPresent, vf.make_mpdiffs, ['ghost'])
 
459
        self.assertRaises(errors.RevisionNotPresent,
 
460
                          vf.make_mpdiffs, [b'ghost'])
456
461
 
457
462
    def _setup_for_deltas(self, f):
458
463
        self.assertFalse(f.has_version('base'))
459
464
        # add texts that should trip the knit maximum delta chain threshold
460
465
        # as well as doing parallel chains of data in knits.
461
466
        # this is done by two chains of 25 insertions
462
 
        f.add_lines('base', [], ['line\n'])
463
 
        f.add_lines('noeol', ['base'], ['line'])
 
467
        f.add_lines(b'base', [], [b'line\n'])
 
468
        f.add_lines(b'noeol', [b'base'], [b'line'])
464
469
        # detailed eol tests:
465
470
        # shared last line with parent no-eol
466
 
        f.add_lines('noeolsecond', ['noeol'], ['line\n', 'line'])
 
471
        f.add_lines(b'noeolsecond', [b'noeol'], [b'line\n', b'line'])
467
472
        # differing last line with parent, both no-eol
468
 
        f.add_lines('noeolnotshared', ['noeolsecond'], ['line\n', 'phone'])
 
473
        f.add_lines(b'noeolnotshared', [b'noeolsecond'], [b'line\n', b'phone'])
469
474
        # add eol following a noneol parent, change content
470
 
        f.add_lines('eol', ['noeol'], ['phone\n'])
 
475
        f.add_lines(b'eol', [b'noeol'], [b'phone\n'])
471
476
        # add eol following a noneol parent, no change content
472
 
        f.add_lines('eolline', ['noeol'], ['line\n'])
 
477
        f.add_lines(b'eolline', [b'noeol'], [b'line\n'])
473
478
        # noeol with no parents:
474
 
        f.add_lines('noeolbase', [], ['line'])
 
479
        f.add_lines(b'noeolbase', [], [b'line'])
475
480
        # noeol preceeding its leftmost parent in the output:
476
481
        # this is done by making it a merge of two parents with no common
477
482
        # anestry: noeolbase and noeol with the
478
483
        # later-inserted parent the leftmost.
479
 
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
 
484
        f.add_lines(b'eolbeforefirstparent', [
 
485
                    b'noeolbase', b'noeol'], [b'line'])
480
486
        # two identical eol texts
481
 
        f.add_lines('noeoldup', ['noeol'], ['line'])
482
 
        next_parent = 'base'
483
 
        text_name = 'chain1-'
484
 
        text = ['line\n']
485
 
        sha1s = {0: 'da6d3141cb4a5e6f464bf6e0518042ddc7bfd079',
486
 
                 1: '45e21ea146a81ea44a821737acdb4f9791c8abe7',
487
 
                 2: 'e1f11570edf3e2a070052366c582837a4fe4e9fa',
488
 
                 3: '26b4b8626da827088c514b8f9bbe4ebf181edda1',
489
 
                 4: 'e28a5510be25ba84d31121cff00956f9970ae6f6',
490
 
                 5: 'd63ec0ce22e11dcf65a931b69255d3ac747a318d',
491
 
                 6: '2c2888d288cb5e1d98009d822fedfe6019c6a4ea',
492
 
                 7: '95c14da9cafbf828e3e74a6f016d87926ba234ab',
493
 
                 8: '779e9a0b28f9f832528d4b21e17e168c67697272',
494
 
                 9: '1f8ff4e5c6ff78ac106fcfe6b1e8cb8740ff9a8f',
495
 
                 10: '131a2ae712cf51ed62f143e3fbac3d4206c25a05',
496
 
                 11: 'c5a9d6f520d2515e1ec401a8f8a67e6c3c89f199',
497
 
                 12: '31a2286267f24d8bedaa43355f8ad7129509ea85',
498
 
                 13: 'dc2a7fe80e8ec5cae920973973a8ee28b2da5e0a',
499
 
                 14: '2c4b1736566b8ca6051e668de68650686a3922f2',
500
 
                 15: '5912e4ecd9b0c07be4d013e7e2bdcf9323276cde',
501
 
                 16: 'b0d2e18d3559a00580f6b49804c23fea500feab3',
502
 
                 17: '8e1d43ad72f7562d7cb8f57ee584e20eb1a69fc7',
503
 
                 18: '5cf64a3459ae28efa60239e44b20312d25b253f3',
504
 
                 19: '1ebed371807ba5935958ad0884595126e8c4e823',
505
 
                 20: '2aa62a8b06fb3b3b892a3292a068ade69d5ee0d3',
506
 
                 21: '01edc447978004f6e4e962b417a4ae1955b6fe5d',
507
 
                 22: 'd8d8dc49c4bf0bab401e0298bb5ad827768618bb',
508
 
                 23: 'c21f62b1c482862983a8ffb2b0c64b3451876e3f',
509
 
                 24: 'c0593fe795e00dff6b3c0fe857a074364d5f04fc',
510
 
                 25: 'dd1a1cf2ba9cc225c3aff729953e6364bf1d1855',
 
487
        f.add_lines(b'noeoldup', [b'noeol'], [b'line'])
 
488
        next_parent = b'base'
 
489
        text_name = b'chain1-'
 
490
        text = [b'line\n']
 
491
        sha1s = {0: b'da6d3141cb4a5e6f464bf6e0518042ddc7bfd079',
 
492
                 1: b'45e21ea146a81ea44a821737acdb4f9791c8abe7',
 
493
                 2: b'e1f11570edf3e2a070052366c582837a4fe4e9fa',
 
494
                 3: b'26b4b8626da827088c514b8f9bbe4ebf181edda1',
 
495
                 4: b'e28a5510be25ba84d31121cff00956f9970ae6f6',
 
496
                 5: b'd63ec0ce22e11dcf65a931b69255d3ac747a318d',
 
497
                 6: b'2c2888d288cb5e1d98009d822fedfe6019c6a4ea',
 
498
                 7: b'95c14da9cafbf828e3e74a6f016d87926ba234ab',
 
499
                 8: b'779e9a0b28f9f832528d4b21e17e168c67697272',
 
500
                 9: b'1f8ff4e5c6ff78ac106fcfe6b1e8cb8740ff9a8f',
 
501
                 10: b'131a2ae712cf51ed62f143e3fbac3d4206c25a05',
 
502
                 11: b'c5a9d6f520d2515e1ec401a8f8a67e6c3c89f199',
 
503
                 12: b'31a2286267f24d8bedaa43355f8ad7129509ea85',
 
504
                 13: b'dc2a7fe80e8ec5cae920973973a8ee28b2da5e0a',
 
505
                 14: b'2c4b1736566b8ca6051e668de68650686a3922f2',
 
506
                 15: b'5912e4ecd9b0c07be4d013e7e2bdcf9323276cde',
 
507
                 16: b'b0d2e18d3559a00580f6b49804c23fea500feab3',
 
508
                 17: b'8e1d43ad72f7562d7cb8f57ee584e20eb1a69fc7',
 
509
                 18: b'5cf64a3459ae28efa60239e44b20312d25b253f3',
 
510
                 19: b'1ebed371807ba5935958ad0884595126e8c4e823',
 
511
                 20: b'2aa62a8b06fb3b3b892a3292a068ade69d5ee0d3',
 
512
                 21: b'01edc447978004f6e4e962b417a4ae1955b6fe5d',
 
513
                 22: b'd8d8dc49c4bf0bab401e0298bb5ad827768618bb',
 
514
                 23: b'c21f62b1c482862983a8ffb2b0c64b3451876e3f',
 
515
                 24: b'c0593fe795e00dff6b3c0fe857a074364d5f04fc',
 
516
                 25: b'dd1a1cf2ba9cc225c3aff729953e6364bf1d1855',
511
517
                 }
512
518
        for depth in range(26):
513
 
            new_version = text_name + '%s' % depth
514
 
            text = text + ['line\n']
 
519
            new_version = text_name + b'%d' % depth
 
520
            text = text + [b'line\n']
515
521
            f.add_lines(new_version, [next_parent], text)
516
522
            next_parent = new_version
517
 
        next_parent = 'base'
518
 
        text_name = 'chain2-'
519
 
        text = ['line\n']
 
523
        next_parent = b'base'
 
524
        text_name = b'chain2-'
 
525
        text = [b'line\n']
520
526
        for depth in range(26):
521
 
            new_version = text_name + '%s' % depth
522
 
            text = text + ['line\n']
 
527
            new_version = text_name + b'%d' % depth
 
528
            text = text + [b'line\n']
523
529
            f.add_lines(new_version, [next_parent], text)
524
530
            next_parent = new_version
525
531
        return sha1s
527
533
    def test_ancestry(self):
528
534
        f = self.get_file()
529
535
        self.assertEqual([], f.get_ancestry([]))
530
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
531
 
        f.add_lines('r1', ['r0'], ['b\n', 'c\n'])
532
 
        f.add_lines('r2', ['r0'], ['b\n', 'c\n'])
533
 
        f.add_lines('r3', ['r2'], ['b\n', 'c\n'])
534
 
        f.add_lines('rM', ['r1', 'r2'], ['b\n', 'c\n'])
 
536
        f.add_lines(b'r0', [], [b'a\n', b'b\n'])
 
537
        f.add_lines(b'r1', [b'r0'], [b'b\n', b'c\n'])
 
538
        f.add_lines(b'r2', [b'r0'], [b'b\n', b'c\n'])
 
539
        f.add_lines(b'r3', [b'r2'], [b'b\n', b'c\n'])
 
540
        f.add_lines(b'rM', [b'r1', b'r2'], [b'b\n', b'c\n'])
535
541
        self.assertEqual([], f.get_ancestry([]))
536
 
        versions = f.get_ancestry(['rM'])
 
542
        versions = f.get_ancestry([b'rM'])
537
543
        # there are some possibilities:
538
544
        # r0 r1 r2 rM r3
539
545
        # r0 r1 r2 r3 rM
540
546
        # etc
541
547
        # so we check indexes
542
 
        r0 = versions.index('r0')
543
 
        r1 = versions.index('r1')
544
 
        r2 = versions.index('r2')
545
 
        self.assertFalse('r3' in versions)
546
 
        rM = versions.index('rM')
 
548
        r0 = versions.index(b'r0')
 
549
        r1 = versions.index(b'r1')
 
550
        r2 = versions.index(b'r2')
 
551
        self.assertFalse(b'r3' in versions)
 
552
        rM = versions.index(b'rM')
547
553
        self.assertTrue(r0 < r1)
548
554
        self.assertTrue(r0 < r2)
549
555
        self.assertTrue(r1 < rM)
550
556
        self.assertTrue(r2 < rM)
551
557
 
552
558
        self.assertRaises(RevisionNotPresent,
553
 
            f.get_ancestry, ['rM', 'rX'])
 
559
                          f.get_ancestry, [b'rM', b'rX'])
554
560
 
555
 
        self.assertEqual(set(f.get_ancestry('rM')),
556
 
            set(f.get_ancestry('rM', topo_sorted=False)))
 
561
        self.assertEqual(set(f.get_ancestry(b'rM')),
 
562
                         set(f.get_ancestry(b'rM', topo_sorted=False)))
557
563
 
558
564
    def test_mutate_after_finish(self):
559
565
        self._transaction = 'before'
560
566
        f = self.get_file()
561
567
        self._transaction = 'after'
562
 
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
563
 
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
 
568
        self.assertRaises(errors.OutSideTransaction, f.add_lines, b'', [], [])
 
569
        self.assertRaises(errors.OutSideTransaction,
 
570
                          f.add_lines_with_ghosts, b'', [], [])
564
571
 
565
572
    def test_copy_to(self):
566
573
        f = self.get_file()
567
 
        f.add_lines('0', [], ['a\n'])
 
574
        f.add_lines(b'0', [], [b'a\n'])
568
575
        t = MemoryTransport()
569
576
        f.copy_to('foo', t)
570
577
        for suffix in self.get_factory().get_suffixes():
577
584
 
578
585
    def test_get_parent_map(self):
579
586
        f = self.get_file()
580
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
581
 
        self.assertEqual(
582
 
            {'r0':()}, f.get_parent_map(['r0']))
583
 
        f.add_lines('r1', ['r0'], ['a\n', 'b\n'])
584
 
        self.assertEqual(
585
 
            {'r1':('r0',)}, f.get_parent_map(['r1']))
586
 
        self.assertEqual(
587
 
            {'r0':(),
588
 
             'r1':('r0',)},
589
 
            f.get_parent_map(['r0', 'r1']))
590
 
        f.add_lines('r2', [], ['a\n', 'b\n'])
591
 
        f.add_lines('r3', [], ['a\n', 'b\n'])
592
 
        f.add_lines('m', ['r0', 'r1', 'r2', 'r3'], ['a\n', 'b\n'])
593
 
        self.assertEqual(
594
 
            {'m':('r0', 'r1', 'r2', 'r3')}, f.get_parent_map(['m']))
595
 
        self.assertEqual({}, f.get_parent_map('y'))
596
 
        self.assertEqual(
597
 
            {'r0':(),
598
 
             'r1':('r0',)},
599
 
            f.get_parent_map(['r0', 'y', 'r1']))
 
587
        f.add_lines(b'r0', [], [b'a\n', b'b\n'])
 
588
        self.assertEqual(
 
589
            {b'r0': ()}, f.get_parent_map([b'r0']))
 
590
        f.add_lines(b'r1', [b'r0'], [b'a\n', b'b\n'])
 
591
        self.assertEqual(
 
592
            {b'r1': (b'r0',)}, f.get_parent_map([b'r1']))
 
593
        self.assertEqual(
 
594
            {b'r0': (),
 
595
             b'r1': (b'r0',)},
 
596
            f.get_parent_map([b'r0', b'r1']))
 
597
        f.add_lines(b'r2', [], [b'a\n', b'b\n'])
 
598
        f.add_lines(b'r3', [], [b'a\n', b'b\n'])
 
599
        f.add_lines(b'm', [b'r0', b'r1', b'r2', b'r3'], [b'a\n', b'b\n'])
 
600
        self.assertEqual(
 
601
            {b'm': (b'r0', b'r1', b'r2', b'r3')}, f.get_parent_map([b'm']))
 
602
        self.assertEqual({}, f.get_parent_map(b'y'))
 
603
        self.assertEqual(
 
604
            {b'r0': (),
 
605
             b'r1': (b'r0',)},
 
606
            f.get_parent_map([b'r0', b'y', b'r1']))
600
607
 
601
608
    def test_annotate(self):
602
609
        f = self.get_file()
603
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
604
 
        f.add_lines('r1', ['r0'], ['c\n', 'b\n'])
605
 
        origins = f.annotate('r1')
606
 
        self.assertEqual(origins[0][0], 'r1')
607
 
        self.assertEqual(origins[1][0], 'r0')
 
610
        f.add_lines(b'r0', [], [b'a\n', b'b\n'])
 
611
        f.add_lines(b'r1', [b'r0'], [b'c\n', b'b\n'])
 
612
        origins = f.annotate(b'r1')
 
613
        self.assertEqual(origins[0][0], b'r1')
 
614
        self.assertEqual(origins[1][0], b'r0')
608
615
 
609
616
        self.assertRaises(RevisionNotPresent,
610
 
            f.annotate, 'foo')
 
617
                          f.annotate, b'foo')
611
618
 
612
619
    def test_detection(self):
613
620
        # Test weaves detect corruption.
618
625
 
619
626
        w = self.get_file_corrupted_text()
620
627
 
621
 
        self.assertEqual('hello\n', w.get_text('v1'))
622
 
        self.assertRaises(WeaveInvalidChecksum, w.get_text, 'v2')
623
 
        self.assertRaises(WeaveInvalidChecksum, w.get_lines, 'v2')
 
628
        self.assertEqual(b'hello\n', w.get_text(b'v1'))
 
629
        self.assertRaises(WeaveInvalidChecksum, w.get_text, b'v2')
 
630
        self.assertRaises(WeaveInvalidChecksum, w.get_lines, b'v2')
624
631
        self.assertRaises(WeaveInvalidChecksum, w.check)
625
632
 
626
633
        w = self.get_file_corrupted_checksum()
627
634
 
628
 
        self.assertEqual('hello\n', w.get_text('v1'))
629
 
        self.assertRaises(WeaveInvalidChecksum, w.get_text, 'v2')
630
 
        self.assertRaises(WeaveInvalidChecksum, w.get_lines, 'v2')
 
635
        self.assertEqual(b'hello\n', w.get_text(b'v1'))
 
636
        self.assertRaises(WeaveInvalidChecksum, w.get_text, b'v2')
 
637
        self.assertRaises(WeaveInvalidChecksum, w.get_lines, b'v2')
631
638
        self.assertRaises(WeaveInvalidChecksum, w.check)
632
639
 
633
640
    def get_file_corrupted_text(self):
655
662
 
656
663
        vf = self.get_file()
657
664
        # add a base to get included
658
 
        vf.add_lines('base', [], ['base\n'])
 
665
        vf.add_lines(b'base', [], [b'base\n'])
659
666
        # add a ancestor to be included on one side
660
 
        vf.add_lines('lancestor', [], ['lancestor\n'])
 
667
        vf.add_lines(b'lancestor', [], [b'lancestor\n'])
661
668
        # add a ancestor to be included on the other side
662
 
        vf.add_lines('rancestor', ['base'], ['rancestor\n'])
 
669
        vf.add_lines(b'rancestor', [b'base'], [b'rancestor\n'])
663
670
        # add a child of rancestor with no eofile-nl
664
 
        vf.add_lines('child', ['rancestor'], ['base\n', 'child\n'])
 
671
        vf.add_lines(b'child', [b'rancestor'], [b'base\n', b'child\n'])
665
672
        # add a child of lancestor and base to join the two roots
666
 
        vf.add_lines('otherchild',
667
 
                     ['lancestor', 'base'],
668
 
                     ['base\n', 'lancestor\n', 'otherchild\n'])
 
673
        vf.add_lines(b'otherchild',
 
674
                     [b'lancestor', b'base'],
 
675
                     [b'base\n', b'lancestor\n', b'otherchild\n'])
 
676
 
669
677
        def iter_with_versions(versions, expected):
670
678
            # now we need to see what lines are returned, and how often.
671
679
            lines = {}
672
680
            progress = InstrumentedProgress()
673
681
            # iterate over the lines
674
682
            for line in vf.iter_lines_added_or_present_in_versions(versions,
675
 
                pb=progress):
 
683
                                                                   pb=progress):
676
684
                lines.setdefault(line, 0)
677
685
                lines[line] += 1
678
 
            if []!= progress.updates:
 
686
            if [] != progress.updates:
679
687
                self.assertEqual(expected, progress.updates)
680
688
            return lines
681
 
        lines = iter_with_versions(['child', 'otherchild'],
 
689
        lines = iter_with_versions([b'child', b'otherchild'],
682
690
                                   [('Walking content', 0, 2),
683
691
                                    ('Walking content', 1, 2),
684
692
                                    ('Walking content', 2, 2)])
685
693
        # we must see child and otherchild
686
 
        self.assertTrue(lines[('child\n', 'child')] > 0)
687
 
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
 
694
        self.assertTrue(lines[(b'child\n', b'child')] > 0)
 
695
        self.assertTrue(lines[(b'otherchild\n', b'otherchild')] > 0)
688
696
        # we dont care if we got more than that.
689
697
 
690
698
        # test all lines
695
703
                                          ('Walking content', 4, 5),
696
704
                                          ('Walking content', 5, 5)])
697
705
        # all lines must be seen at least once
698
 
        self.assertTrue(lines[('base\n', 'base')] > 0)
699
 
        self.assertTrue(lines[('lancestor\n', 'lancestor')] > 0)
700
 
        self.assertTrue(lines[('rancestor\n', 'rancestor')] > 0)
701
 
        self.assertTrue(lines[('child\n', 'child')] > 0)
702
 
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
 
706
        self.assertTrue(lines[(b'base\n', b'base')] > 0)
 
707
        self.assertTrue(lines[(b'lancestor\n', b'lancestor')] > 0)
 
708
        self.assertTrue(lines[(b'rancestor\n', b'rancestor')] > 0)
 
709
        self.assertTrue(lines[(b'child\n', b'child')] > 0)
 
710
        self.assertTrue(lines[(b'otherchild\n', b'otherchild')] > 0)
703
711
 
704
712
    def test_add_lines_with_ghosts(self):
705
713
        # some versioned file formats allow lines to be added with parent
712
720
        parent_id_unicode = u'b\xbfse'
713
721
        parent_id_utf8 = parent_id_unicode.encode('utf8')
714
722
        try:
715
 
            vf.add_lines_with_ghosts('notbxbfse', [parent_id_utf8], [])
 
723
            vf.add_lines_with_ghosts(b'notbxbfse', [parent_id_utf8], [])
716
724
        except NotImplementedError:
717
725
            # check the other ghost apis are also not implemented
718
 
            self.assertRaises(NotImplementedError, vf.get_ancestry_with_ghosts, ['foo'])
719
 
            self.assertRaises(NotImplementedError, vf.get_parents_with_ghosts, 'foo')
 
726
            self.assertRaises(NotImplementedError,
 
727
                              vf.get_ancestry_with_ghosts, [b'foo'])
 
728
            self.assertRaises(NotImplementedError,
 
729
                              vf.get_parents_with_ghosts, b'foo')
720
730
            return
721
731
        vf = self.reopen_file()
722
732
        # test key graph related apis: getncestry, _graph, get_parents
723
733
        # has_version
724
734
        # - these are ghost unaware and must not be reflect ghosts
725
 
        self.assertEqual(['notbxbfse'], vf.get_ancestry('notbxbfse'))
 
735
        self.assertEqual([b'notbxbfse'], vf.get_ancestry(b'notbxbfse'))
726
736
        self.assertFalse(vf.has_version(parent_id_utf8))
727
737
        # we have _with_ghost apis to give us ghost information.
728
 
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry_with_ghosts(['notbxbfse']))
729
 
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
 
738
        self.assertEqual([parent_id_utf8, b'notbxbfse'],
 
739
                         vf.get_ancestry_with_ghosts([b'notbxbfse']))
 
740
        self.assertEqual([parent_id_utf8],
 
741
                         vf.get_parents_with_ghosts(b'notbxbfse'))
730
742
        # if we add something that is a ghost of another, it should correct the
731
743
        # results of the prior apis
732
744
        vf.add_lines(parent_id_utf8, [], [])
733
 
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry(['notbxbfse']))
734
 
        self.assertEqual({'notbxbfse':(parent_id_utf8,)},
735
 
            vf.get_parent_map(['notbxbfse']))
 
745
        self.assertEqual([parent_id_utf8, b'notbxbfse'],
 
746
                         vf.get_ancestry([b'notbxbfse']))
 
747
        self.assertEqual({b'notbxbfse': (parent_id_utf8,)},
 
748
                         vf.get_parent_map([b'notbxbfse']))
736
749
        self.assertTrue(vf.has_version(parent_id_utf8))
737
750
        # we have _with_ghost apis to give us ghost information.
738
 
        self.assertEqual([parent_id_utf8, 'notbxbfse'],
739
 
            vf.get_ancestry_with_ghosts(['notbxbfse']))
740
 
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
 
751
        self.assertEqual([parent_id_utf8, b'notbxbfse'],
 
752
                         vf.get_ancestry_with_ghosts([b'notbxbfse']))
 
753
        self.assertEqual([parent_id_utf8],
 
754
                         vf.get_parents_with_ghosts(b'notbxbfse'))
741
755
 
742
756
    def test_add_lines_with_ghosts_after_normal_revs(self):
743
757
        # some versioned file formats allow lines to be added with parent
747
761
        vf = self.get_file()
748
762
        # probe for ghost support
749
763
        try:
750
 
            vf.add_lines_with_ghosts('base', [], ['line\n', 'line_b\n'])
 
764
            vf.add_lines_with_ghosts(b'base', [], [b'line\n', b'line_b\n'])
751
765
        except NotImplementedError:
752
766
            return
753
 
        vf.add_lines_with_ghosts('references_ghost',
754
 
                                 ['base', 'a_ghost'],
755
 
                                 ['line\n', 'line_b\n', 'line_c\n'])
756
 
        origins = vf.annotate('references_ghost')
757
 
        self.assertEqual(('base', 'line\n'), origins[0])
758
 
        self.assertEqual(('base', 'line_b\n'), origins[1])
759
 
        self.assertEqual(('references_ghost', 'line_c\n'), origins[2])
 
767
        vf.add_lines_with_ghosts(b'references_ghost',
 
768
                                 [b'base', b'a_ghost'],
 
769
                                 [b'line\n', b'line_b\n', b'line_c\n'])
 
770
        origins = vf.annotate(b'references_ghost')
 
771
        self.assertEqual((b'base', b'line\n'), origins[0])
 
772
        self.assertEqual((b'base', b'line_b\n'), origins[1])
 
773
        self.assertEqual((b'references_ghost', b'line_c\n'), origins[2])
760
774
 
761
775
    def test_readonly_mode(self):
762
776
        t = self.get_transport()
763
777
        factory = self.get_factory()
764
778
        vf = factory('id', t, 0o777, create=True, access_mode='w')
765
779
        vf = factory('id', t, access_mode='r')
766
 
        self.assertRaises(errors.ReadOnlyError, vf.add_lines, 'base', [], [])
 
780
        self.assertRaises(errors.ReadOnlyError, vf.add_lines, b'base', [], [])
767
781
        self.assertRaises(errors.ReadOnlyError,
768
782
                          vf.add_lines_with_ghosts,
769
 
                          'base',
 
783
                          b'base',
770
784
                          [],
771
785
                          [])
772
786
 
774
788
        # check the sha1 data is available
775
789
        vf = self.get_file()
776
790
        # a simple file
777
 
        vf.add_lines('a', [], ['a\n'])
 
791
        vf.add_lines(b'a', [], [b'a\n'])
778
792
        # the same file, different metadata
779
 
        vf.add_lines('b', ['a'], ['a\n'])
 
793
        vf.add_lines(b'b', [b'a'], [b'a\n'])
780
794
        # a file differing only in last newline.
781
 
        vf.add_lines('c', [], ['a'])
 
795
        vf.add_lines(b'c', [], [b'a'])
782
796
        self.assertEqual({
783
 
            'a': '3f786850e387550fdab836ed7e6dc881de23001b',
784
 
            'c': '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
785
 
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
 
797
            b'a': b'3f786850e387550fdab836ed7e6dc881de23001b',
 
798
            b'c': b'86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
 
799
            b'b': b'3f786850e387550fdab836ed7e6dc881de23001b',
786
800
            },
787
 
            vf.get_sha1s(['a', 'c', 'b']))
 
801
            vf.get_sha1s([b'a', b'c', b'b']))
788
802
 
789
803
 
790
804
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
798
812
        w = WeaveFile('foo', self.get_transport(),
799
813
                      create=True,
800
814
                      get_scope=self.get_transaction)
801
 
        w.add_lines('v1', [], ['hello\n'])
802
 
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
 
815
        w.add_lines(b'v1', [], [b'hello\n'])
 
816
        w.add_lines(b'v2', [b'v1'], [b'hello\n', b'there\n'])
803
817
 
804
818
        # We are going to invasively corrupt the text
805
819
        # Make sure the internals of weave are the same
806
 
        self.assertEqual([('{', 0)
807
 
                        , 'hello\n'
808
 
                        , ('}', None)
809
 
                        , ('{', 1)
810
 
                        , 'there\n'
811
 
                        , ('}', None)
812
 
                        ], w._weave)
 
820
        self.assertEqual([(b'{', 0), b'hello\n', (b'}', None), (b'{', 1), b'there\n', (b'}', None)
 
821
                          ], w._weave)
813
822
 
814
 
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
815
 
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
816
 
                        ], w._sha1s)
 
823
        self.assertEqual([b'f572d396fae9206628714fb2ce00f72e94f2258f', b'90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
 
824
                          ], w._sha1s)
817
825
        w.check()
818
826
 
819
827
        # Corrupted
820
 
        w._weave[4] = 'There\n'
 
828
        w._weave[4] = b'There\n'
821
829
        return w
822
830
 
823
831
    def get_file_corrupted_checksum(self):
824
832
        w = self.get_file_corrupted_text()
825
833
        # Corrected
826
 
        w._weave[4] = 'there\n'
827
 
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
 
834
        w._weave[4] = b'there\n'
 
835
        self.assertEqual(b'hello\nthere\n', w.get_text(b'v2'))
828
836
 
829
 
        #Invalid checksum, first digit changed
830
 
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
 
837
        # Invalid checksum, first digit changed
 
838
        w._sha1s[1] = b'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
831
839
        return w
832
840
 
833
841
    def reopen_file(self, name='foo', create=False):
858
866
        self.plan_merge_vf.fallback_versionedfiles.extend([self.vf1, self.vf2])
859
867
 
860
868
    def test_add_lines(self):
861
 
        self.plan_merge_vf.add_lines(('root', 'a:'), [], [])
862
 
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines,
863
 
            ('root', 'a'), [], [])
864
 
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines,
865
 
            ('root', 'a:'), None, [])
866
 
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines,
867
 
            ('root', 'a:'), [], None)
 
869
        self.plan_merge_vf.add_lines((b'root', b'a:'), [], [])
 
870
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines,
 
871
                          (b'root', b'a'), [], [])
 
872
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines,
 
873
                          (b'root', b'a:'), None, [])
 
874
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines,
 
875
                          (b'root', b'a:'), [], None)
868
876
 
869
877
    def setup_abcde(self):
870
 
        self.vf1.add_lines(('root', 'A'), [], ['a'])
871
 
        self.vf1.add_lines(('root', 'B'), [('root', 'A')], ['b'])
872
 
        self.vf2.add_lines(('root', 'C'), [], ['c'])
873
 
        self.vf2.add_lines(('root', 'D'), [('root', 'C')], ['d'])
874
 
        self.plan_merge_vf.add_lines(('root', 'E:'),
875
 
            [('root', 'B'), ('root', 'D')], ['e'])
 
878
        self.vf1.add_lines((b'root', b'A'), [], [b'a'])
 
879
        self.vf1.add_lines((b'root', b'B'), [(b'root', b'A')], [b'b'])
 
880
        self.vf2.add_lines((b'root', b'C'), [], [b'c'])
 
881
        self.vf2.add_lines((b'root', b'D'), [(b'root', b'C')], [b'd'])
 
882
        self.plan_merge_vf.add_lines((b'root', b'E:'),
 
883
                                     [(b'root', b'B'), (b'root', b'D')], [b'e'])
876
884
 
877
885
    def test_get_parents(self):
878
886
        self.setup_abcde()
879
 
        self.assertEqual({('root', 'B'):(('root', 'A'),)},
880
 
            self.plan_merge_vf.get_parent_map([('root', 'B')]))
881
 
        self.assertEqual({('root', 'D'):(('root', 'C'),)},
882
 
            self.plan_merge_vf.get_parent_map([('root', 'D')]))
883
 
        self.assertEqual({('root', 'E:'):(('root', 'B'), ('root', 'D'))},
884
 
            self.plan_merge_vf.get_parent_map([('root', 'E:')]))
 
887
        self.assertEqual({(b'root', b'B'): ((b'root', b'A'),)},
 
888
                         self.plan_merge_vf.get_parent_map([(b'root', b'B')]))
 
889
        self.assertEqual({(b'root', b'D'): ((b'root', b'C'),)},
 
890
                         self.plan_merge_vf.get_parent_map([(b'root', b'D')]))
 
891
        self.assertEqual({(b'root', b'E:'): ((b'root', b'B'), (b'root', b'D'))},
 
892
                         self.plan_merge_vf.get_parent_map([(b'root', b'E:')]))
885
893
        self.assertEqual({},
886
 
            self.plan_merge_vf.get_parent_map([('root', 'F')]))
 
894
                         self.plan_merge_vf.get_parent_map([(b'root', b'F')]))
887
895
        self.assertEqual({
888
 
                ('root', 'B'): (('root', 'A'),),
889
 
                ('root', 'D'): (('root', 'C'),),
890
 
                ('root', 'E:'): (('root', 'B'), ('root', 'D')),
891
 
                },
 
896
            (b'root', b'B'): ((b'root', b'A'),),
 
897
            (b'root', b'D'): ((b'root', b'C'),),
 
898
            (b'root', b'E:'): ((b'root', b'B'), (b'root', b'D')),
 
899
            },
892
900
            self.plan_merge_vf.get_parent_map(
893
 
                [('root', 'B'), ('root', 'D'), ('root', 'E:'), ('root', 'F')]))
 
901
                [(b'root', b'B'), (b'root', b'D'), (b'root', b'E:'), (b'root', b'F')]))
894
902
 
895
903
    def test_get_record_stream(self):
896
904
        self.setup_abcde()
 
905
 
897
906
        def get_record(suffix):
898
907
            return next(self.plan_merge_vf.get_record_stream(
899
 
                [('root', suffix)], 'unordered', True))
900
 
        self.assertEqual('a', get_record('A').get_bytes_as('fulltext'))
901
 
        self.assertEqual('c', get_record('C').get_bytes_as('fulltext'))
902
 
        self.assertEqual('e', get_record('E:').get_bytes_as('fulltext'))
 
908
                [(b'root', suffix)], 'unordered', True))
 
909
        self.assertEqual(b'a', get_record(b'A').get_bytes_as('fulltext'))
 
910
        self.assertEqual(b'a', b''.join(get_record(b'A').iter_bytes_as('chunked')))
 
911
        self.assertEqual(b'c', get_record(b'C').get_bytes_as('fulltext'))
 
912
        self.assertEqual(b'e', get_record(b'E:').get_bytes_as('fulltext'))
903
913
        self.assertEqual('absent', get_record('F').storage_kind)
904
914
 
905
915
 
913
923
        vf = self.get_file()
914
924
        # try an empty file access
915
925
        readonly_vf = self.get_factory()('foo',
916
 
            transport.get_transport_from_url(self.get_readonly_url('.')))
 
926
                                         transport.get_transport_from_url(self.get_readonly_url('.')))
917
927
        self.assertEqual([], readonly_vf.versions())
918
928
 
919
929
    def test_readonly_http_works_with_feeling(self):
920
930
        # we should be able to read from http with a versioned file.
921
931
        vf = self.get_file()
922
932
        # now with feeling.
923
 
        vf.add_lines('1', [], ['a\n'])
924
 
        vf.add_lines('2', ['1'], ['b\n', 'a\n'])
 
933
        vf.add_lines(b'1', [], [b'a\n'])
 
934
        vf.add_lines(b'2', [b'1'], [b'b\n', b'a\n'])
925
935
        readonly_vf = self.get_factory()('foo',
926
 
            transport.get_transport_from_url(self.get_readonly_url('.')))
927
 
        self.assertEqual(['1', '2'], vf.versions())
928
 
        self.assertEqual(['1', '2'], readonly_vf.versions())
 
936
                                         transport.get_transport_from_url(self.get_readonly_url('.')))
 
937
        self.assertEqual([b'1', b'2'], vf.versions())
 
938
        self.assertEqual([b'1', b'2'], readonly_vf.versions())
929
939
        for version in readonly_vf.versions():
930
940
            readonly_vf.get_lines(version)
931
941
 
947
957
        from textwrap import dedent
948
958
 
949
959
        def addcrlf(x):
950
 
            return x + '\n'
 
960
            return x + b'\n'
951
961
 
952
962
        w = self.get_file()
953
 
        w.add_lines('text0', [], list(map(addcrlf, base)))
954
 
        w.add_lines('text1', ['text0'], list(map(addcrlf, a)))
955
 
        w.add_lines('text2', ['text0'], list(map(addcrlf, b)))
 
963
        w.add_lines(b'text0', [], list(map(addcrlf, base)))
 
964
        w.add_lines(b'text1', [b'text0'], list(map(addcrlf, a)))
 
965
        w.add_lines(b'text2', [b'text0'], list(map(addcrlf, b)))
956
966
 
957
967
        self.log_contents(w)
958
968
 
959
969
        self.log('merge plan:')
960
 
        p = list(w.plan_merge('text1', 'text2'))
 
970
        p = list(w.plan_merge(b'text1', b'text2'))
961
971
        for state, line in p:
962
972
            if line:
963
973
                self.log('%12s | %s' % (state, line[:-1]))
971
981
        mp = list(map(addcrlf, mp))
972
982
        self.assertEqual(mt.readlines(), mp)
973
983
 
974
 
 
975
984
    def testOneInsert(self):
976
985
        self.doMerge([],
977
 
                     ['aa'],
 
986
                     [b'aa'],
978
987
                     [],
979
 
                     ['aa'])
 
988
                     [b'aa'])
980
989
 
981
990
    def testSeparateInserts(self):
982
 
        self.doMerge(['aaa', 'bbb', 'ccc'],
983
 
                     ['aaa', 'xxx', 'bbb', 'ccc'],
984
 
                     ['aaa', 'bbb', 'yyy', 'ccc'],
985
 
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
991
        self.doMerge([b'aaa', b'bbb', b'ccc'],
 
992
                     [b'aaa', b'xxx', b'bbb', b'ccc'],
 
993
                     [b'aaa', b'bbb', b'yyy', b'ccc'],
 
994
                     [b'aaa', b'xxx', b'bbb', b'yyy', b'ccc'])
986
995
 
987
996
    def testSameInsert(self):
988
 
        self.doMerge(['aaa', 'bbb', 'ccc'],
989
 
                     ['aaa', 'xxx', 'bbb', 'ccc'],
990
 
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
991
 
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
992
 
    overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
 
997
        self.doMerge([b'aaa', b'bbb', b'ccc'],
 
998
                     [b'aaa', b'xxx', b'bbb', b'ccc'],
 
999
                     [b'aaa', b'xxx', b'bbb', b'yyy', b'ccc'],
 
1000
                     [b'aaa', b'xxx', b'bbb', b'yyy', b'ccc'])
 
1001
    overlappedInsertExpected = [b'aaa', b'xxx', b'yyy', b'bbb']
 
1002
 
993
1003
    def testOverlappedInsert(self):
994
 
        self.doMerge(['aaa', 'bbb'],
995
 
                     ['aaa', 'xxx', 'yyy', 'bbb'],
996
 
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
 
1004
        self.doMerge([b'aaa', b'bbb'],
 
1005
                     [b'aaa', b'xxx', b'yyy', b'bbb'],
 
1006
                     [b'aaa', b'xxx', b'bbb'], self.overlappedInsertExpected)
997
1007
 
998
1008
        # really it ought to reduce this to
999
 
        # ['aaa', 'xxx', 'yyy', 'bbb']
1000
 
 
 
1009
        # [b'aaa', b'xxx', b'yyy', b'bbb']
1001
1010
 
1002
1011
    def testClashReplace(self):
1003
 
        self.doMerge(['aaa'],
1004
 
                     ['xxx'],
1005
 
                     ['yyy', 'zzz'],
1006
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
1007
 
                      '>>>>>>> '])
 
1012
        self.doMerge([b'aaa'],
 
1013
                     [b'xxx'],
 
1014
                     [b'yyy', b'zzz'],
 
1015
                     [b'<<<<<<< ', b'xxx', b'=======', b'yyy', b'zzz',
 
1016
                      b'>>>>>>> '])
1008
1017
 
1009
1018
    def testNonClashInsert1(self):
1010
 
        self.doMerge(['aaa'],
1011
 
                     ['xxx', 'aaa'],
1012
 
                     ['yyy', 'zzz'],
1013
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
1014
 
                      '>>>>>>> '])
 
1019
        self.doMerge([b'aaa'],
 
1020
                     [b'xxx', b'aaa'],
 
1021
                     [b'yyy', b'zzz'],
 
1022
                     [b'<<<<<<< ', b'xxx', b'aaa', b'=======', b'yyy', b'zzz',
 
1023
                      b'>>>>>>> '])
1015
1024
 
1016
1025
    def testNonClashInsert2(self):
1017
 
        self.doMerge(['aaa'],
1018
 
                     ['aaa'],
1019
 
                     ['yyy', 'zzz'],
1020
 
                     ['yyy', 'zzz'])
1021
 
 
 
1026
        self.doMerge([b'aaa'],
 
1027
                     [b'aaa'],
 
1028
                     [b'yyy', b'zzz'],
 
1029
                     [b'yyy', b'zzz'])
1022
1030
 
1023
1031
    def testDeleteAndModify(self):
1024
1032
        """Clashing delete and modification.
1031
1039
        # skippd, not working yet
1032
1040
        return
1033
1041
 
1034
 
        self.doMerge(['aaa', 'bbb', 'ccc'],
1035
 
                     ['aaa', 'ddd', 'ccc'],
1036
 
                     ['aaa', 'ccc'],
1037
 
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
 
1042
        self.doMerge([b'aaa', b'bbb', b'ccc'],
 
1043
                     [b'aaa', b'ddd', b'ccc'],
 
1044
                     [b'aaa', b'ccc'],
 
1045
                     [b'<<<<<<<< ', b'aaa', b'=======', b'>>>>>>> ', b'ccc'])
1038
1046
 
1039
1047
    def _test_merge_from_strings(self, base, a, b, expected):
1040
1048
        w = self.get_file()
1041
 
        w.add_lines('text0', [], base.splitlines(True))
1042
 
        w.add_lines('text1', ['text0'], a.splitlines(True))
1043
 
        w.add_lines('text2', ['text0'], b.splitlines(True))
 
1049
        w.add_lines(b'text0', [], base.splitlines(True))
 
1050
        w.add_lines(b'text1', [b'text0'], a.splitlines(True))
 
1051
        w.add_lines(b'text2', [b'text0'], b.splitlines(True))
1044
1052
        self.log('merge plan:')
1045
 
        p = list(w.plan_merge('text1', 'text2'))
 
1053
        p = list(w.plan_merge(b'text1', b'text2'))
1046
1054
        for state, line in p:
1047
1055
            if line:
1048
1056
                self.log('%12s | %s' % (state, line[:-1]))
1049
1057
        self.log('merge result:')
1050
 
        result_text = ''.join(w.weave_merge(p))
 
1058
        result_text = b''.join(w.weave_merge(p))
1051
1059
        self.log(result_text)
1052
1060
        self.assertEqualDiff(result_text, expected)
1053
1061
 
1054
1062
    def test_weave_merge_conflicts(self):
1055
1063
        # does weave merge properly handle plans that end with unchanged?
1056
 
        result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
1057
 
        self.assertEqual(result, 'hello\n')
 
1064
        result = b''.join(self.get_file().weave_merge([('new-a', b'hello\n')]))
 
1065
        self.assertEqual(result, b'hello\n')
1058
1066
 
1059
1067
    def test_deletion_extended(self):
1060
1068
        """One side deletes, the other deletes more.
1061
1069
        """
1062
 
        base = """\
 
1070
        base = b"""\
1063
1071
            line 1
1064
1072
            line 2
1065
1073
            line 3
1066
1074
            """
1067
 
        a = """\
 
1075
        a = b"""\
1068
1076
            line 1
1069
1077
            line 2
1070
1078
            """
1071
 
        b = """\
 
1079
        b = b"""\
1072
1080
            line 1
1073
1081
            """
1074
 
        result = """\
 
1082
        result = b"""\
1075
1083
            line 1
1076
1084
<<<<<<<\x20
1077
1085
            line 2
1086
1094
        Arguably it'd be better to treat these as agreement, rather than
1087
1095
        conflict, but for now conflict is safer.
1088
1096
        """
1089
 
        base = """\
 
1097
        base = b"""\
1090
1098
            start context
1091
1099
            int a() {}
1092
1100
            int b() {}
1093
1101
            int c() {}
1094
1102
            end context
1095
1103
            """
1096
 
        a = """\
 
1104
        a = b"""\
1097
1105
            start context
1098
1106
            int a() {}
1099
1107
            end context
1100
1108
            """
1101
 
        b = """\
 
1109
        b = b"""\
1102
1110
            start context
1103
1111
            int c() {}
1104
1112
            end context
1105
1113
            """
1106
 
        result = """\
 
1114
        result = b"""\
1107
1115
            start context
1108
1116
<<<<<<<\x20
1109
1117
            int a() {}
1116
1124
 
1117
1125
    def test_agreement_deletion(self):
1118
1126
        """Agree to delete some lines, without conflicts."""
1119
 
        base = """\
 
1127
        base = b"""\
1120
1128
            start context
1121
1129
            base line 1
1122
1130
            base line 2
1123
1131
            end context
1124
1132
            """
1125
 
        a = """\
1126
 
            start context
1127
 
            base line 1
1128
 
            end context
1129
 
            """
1130
 
        b = """\
1131
 
            start context
1132
 
            base line 1
1133
 
            end context
1134
 
            """
1135
 
        result = """\
 
1133
        a = b"""\
 
1134
            start context
 
1135
            base line 1
 
1136
            end context
 
1137
            """
 
1138
        b = b"""\
 
1139
            start context
 
1140
            base line 1
 
1141
            end context
 
1142
            """
 
1143
        result = b"""\
1136
1144
            start context
1137
1145
            base line 1
1138
1146
            end context
1149
1157
 
1150
1158
        It's better to consider the whole thing as a disagreement region.
1151
1159
        """
1152
 
        base = """\
 
1160
        base = b"""\
1153
1161
            start context
1154
1162
            base line 1
1155
1163
            base line 2
1156
1164
            end context
1157
1165
            """
1158
 
        a = """\
 
1166
        a = b"""\
1159
1167
            start context
1160
1168
            base line 1
1161
1169
            a's replacement line 2
1162
1170
            end context
1163
1171
            """
1164
 
        b = """\
 
1172
        b = b"""\
1165
1173
            start context
1166
1174
            b replaces
1167
1175
            both lines
1168
1176
            end context
1169
1177
            """
1170
 
        result = """\
 
1178
        result = b"""\
1171
1179
            start context
1172
1180
<<<<<<<\x20
1173
1181
            base line 1
1193
1201
        write_weave(w, tmpf)
1194
1202
        self.log(tmpf.getvalue())
1195
1203
 
1196
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1197
 
                                'xxx', '>>>>>>> ', 'bbb']
 
1204
    overlappedInsertExpected = [b'aaa', b'<<<<<<< ', b'xxx', b'yyy', b'=======',
 
1205
                                b'xxx', b'>>>>>>> ', b'bbb']
1198
1206
 
1199
1207
 
1200
1208
class TestContentFactoryAdaption(TestCaseWithMemoryTransport):
1205
1213
        # Each is source_kind, requested_kind, adapter class
1206
1214
        scenarios = [
1207
1215
            ('knit-delta-gz', 'fulltext', _mod_knit.DeltaPlainToFullText),
 
1216
            ('knit-delta-gz', 'lines', _mod_knit.DeltaPlainToFullText),
 
1217
            ('knit-delta-gz', 'chunked', _mod_knit.DeltaPlainToFullText),
1208
1218
            ('knit-ft-gz', 'fulltext', _mod_knit.FTPlainToFullText),
 
1219
            ('knit-ft-gz', 'lines', _mod_knit.FTPlainToFullText),
 
1220
            ('knit-ft-gz', 'chunked', _mod_knit.FTPlainToFullText),
1209
1221
            ('knit-annotated-delta-gz', 'knit-delta-gz',
1210
1222
                _mod_knit.DeltaAnnotatedToUnannotated),
1211
1223
            ('knit-annotated-delta-gz', 'fulltext',
1214
1226
                _mod_knit.FTAnnotatedToUnannotated),
1215
1227
            ('knit-annotated-ft-gz', 'fulltext',
1216
1228
                _mod_knit.FTAnnotatedToFullText),
 
1229
            ('knit-annotated-ft-gz', 'lines',
 
1230
                _mod_knit.FTAnnotatedToFullText),
 
1231
            ('knit-annotated-ft-gz', 'chunked',
 
1232
                _mod_knit.FTAnnotatedToFullText),
1217
1233
            ]
1218
1234
        for source, requested, klass in scenarios:
1219
1235
            adapter_factory = versionedfile.adapter_registry.get(
1226
1242
        transport = self.get_transport()
1227
1243
        return make_file_factory(annotated, mapper)(transport)
1228
1244
 
1229
 
    def helpGetBytes(self, f, ft_adapter, delta_adapter):
 
1245
    def helpGetBytes(self, f, ft_name, ft_adapter, delta_name, delta_adapter):
1230
1246
        """Grab the interested adapted texts for tests."""
1231
1247
        # origin is a fulltext
1232
 
        entries = f.get_record_stream([('origin',)], 'unordered', False)
 
1248
        entries = f.get_record_stream([(b'origin',)], 'unordered', False)
1233
1249
        base = next(entries)
1234
 
        ft_data = ft_adapter.get_bytes(base)
 
1250
        ft_data = ft_adapter.get_bytes(base, ft_name)
1235
1251
        # merged is both a delta and multiple parents.
1236
 
        entries = f.get_record_stream([('merged',)], 'unordered', False)
 
1252
        entries = f.get_record_stream([(b'merged',)], 'unordered', False)
1237
1253
        merged = next(entries)
1238
 
        delta_data = delta_adapter.get_bytes(merged)
 
1254
        delta_data = delta_adapter.get_bytes(merged, delta_name)
1239
1255
        return ft_data, delta_data
1240
1256
 
1241
1257
    def test_deannotation_noeol(self):
1243
1259
        # we need a full text, and a delta
1244
1260
        f = self.get_knit()
1245
1261
        get_diamond_files(f, 1, trailing_eol=False)
1246
 
        ft_data, delta_data = self.helpGetBytes(f,
1247
 
            _mod_knit.FTAnnotatedToUnannotated(None),
1248
 
            _mod_knit.DeltaAnnotatedToUnannotated(None))
 
1262
        ft_data, delta_data = self.helpGetBytes(
 
1263
            f, 'knit-ft-gz', _mod_knit.FTAnnotatedToUnannotated(None),
 
1264
            'knit-delta-gz', _mod_knit.DeltaAnnotatedToUnannotated(None))
1249
1265
        self.assertEqual(
1250
 
            'version origin 1 b284f94827db1fa2970d9e2014f080413b547a7e\n'
1251
 
            'origin\n'
1252
 
            'end origin\n',
 
1266
            b'version origin 1 b284f94827db1fa2970d9e2014f080413b547a7e\n'
 
1267
            b'origin\n'
 
1268
            b'end origin\n',
1253
1269
            GzipFile(mode='rb', fileobj=BytesIO(ft_data)).read())
1254
1270
        self.assertEqual(
1255
 
            'version merged 4 32c2e79763b3f90e8ccde37f9710b6629c25a796\n'
1256
 
            '1,2,3\nleft\nright\nmerged\nend merged\n',
 
1271
            b'version merged 4 32c2e79763b3f90e8ccde37f9710b6629c25a796\n'
 
1272
            b'1,2,3\nleft\nright\nmerged\nend merged\n',
1257
1273
            GzipFile(mode='rb', fileobj=BytesIO(delta_data)).read())
1258
1274
 
1259
1275
    def test_deannotation(self):
1261
1277
        # we need a full text, and a delta
1262
1278
        f = self.get_knit()
1263
1279
        get_diamond_files(f, 1)
1264
 
        ft_data, delta_data = self.helpGetBytes(f,
1265
 
            _mod_knit.FTAnnotatedToUnannotated(None),
1266
 
            _mod_knit.DeltaAnnotatedToUnannotated(None))
 
1280
        ft_data, delta_data = self.helpGetBytes(
 
1281
            f, 'knit-ft-gz', _mod_knit.FTAnnotatedToUnannotated(None),
 
1282
            'knit-delta-gz', _mod_knit.DeltaAnnotatedToUnannotated(None))
1267
1283
        self.assertEqual(
1268
 
            'version origin 1 00e364d235126be43292ab09cb4686cf703ddc17\n'
1269
 
            'origin\n'
1270
 
            'end origin\n',
 
1284
            b'version origin 1 00e364d235126be43292ab09cb4686cf703ddc17\n'
 
1285
            b'origin\n'
 
1286
            b'end origin\n',
1271
1287
            GzipFile(mode='rb', fileobj=BytesIO(ft_data)).read())
1272
1288
        self.assertEqual(
1273
 
            'version merged 3 ed8bce375198ea62444dc71952b22cfc2b09226d\n'
1274
 
            '2,2,2\nright\nmerged\nend merged\n',
 
1289
            b'version merged 3 ed8bce375198ea62444dc71952b22cfc2b09226d\n'
 
1290
            b'2,2,2\nright\nmerged\nend merged\n',
1275
1291
            GzipFile(mode='rb', fileobj=BytesIO(delta_data)).read())
1276
1292
 
1277
1293
    def test_annotated_to_fulltext_no_eol(self):
1282
1298
        # Reconstructing a full text requires a backing versioned file, and it
1283
1299
        # must have the base lines requested from it.
1284
1300
        logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1285
 
        ft_data, delta_data = self.helpGetBytes(f,
1286
 
            _mod_knit.FTAnnotatedToFullText(None),
1287
 
            _mod_knit.DeltaAnnotatedToFullText(logged_vf))
1288
 
        self.assertEqual('origin', ft_data)
1289
 
        self.assertEqual('base\nleft\nright\nmerged', delta_data)
1290
 
        self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1291
 
            True)], logged_vf.calls)
 
1301
        ft_data, delta_data = self.helpGetBytes(
 
1302
            f, 'fulltext', _mod_knit.FTAnnotatedToFullText(None),
 
1303
            'fulltext', _mod_knit.DeltaAnnotatedToFullText(logged_vf))
 
1304
        self.assertEqual(b'origin', ft_data)
 
1305
        self.assertEqual(b'base\nleft\nright\nmerged', delta_data)
 
1306
        self.assertEqual([('get_record_stream', [(b'left',)], 'unordered',
 
1307
                           True)], logged_vf.calls)
1292
1308
 
1293
1309
    def test_annotated_to_fulltext(self):
1294
1310
        """Test adapting annotated knits to full texts (for -> weaves)."""
1298
1314
        # Reconstructing a full text requires a backing versioned file, and it
1299
1315
        # must have the base lines requested from it.
1300
1316
        logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1301
 
        ft_data, delta_data = self.helpGetBytes(f,
1302
 
            _mod_knit.FTAnnotatedToFullText(None),
1303
 
            _mod_knit.DeltaAnnotatedToFullText(logged_vf))
1304
 
        self.assertEqual('origin\n', ft_data)
1305
 
        self.assertEqual('base\nleft\nright\nmerged\n', delta_data)
1306
 
        self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1307
 
            True)], logged_vf.calls)
 
1317
        ft_data, delta_data = self.helpGetBytes(
 
1318
            f, 'fulltext', _mod_knit.FTAnnotatedToFullText(None),
 
1319
            'fulltext', _mod_knit.DeltaAnnotatedToFullText(logged_vf))
 
1320
        self.assertEqual(b'origin\n', ft_data)
 
1321
        self.assertEqual(b'base\nleft\nright\nmerged\n', delta_data)
 
1322
        self.assertEqual([('get_record_stream', [(b'left',)], 'unordered',
 
1323
                           True)], logged_vf.calls)
1308
1324
 
1309
1325
    def test_unannotated_to_fulltext(self):
1310
1326
        """Test adapting unannotated knits to full texts.
1317
1333
        # Reconstructing a full text requires a backing versioned file, and it
1318
1334
        # must have the base lines requested from it.
1319
1335
        logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1320
 
        ft_data, delta_data = self.helpGetBytes(f,
1321
 
            _mod_knit.FTPlainToFullText(None),
1322
 
            _mod_knit.DeltaPlainToFullText(logged_vf))
1323
 
        self.assertEqual('origin\n', ft_data)
1324
 
        self.assertEqual('base\nleft\nright\nmerged\n', delta_data)
1325
 
        self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1326
 
            True)], logged_vf.calls)
 
1336
        ft_data, delta_data = self.helpGetBytes(
 
1337
            f, 'fulltext', _mod_knit.FTPlainToFullText(None),
 
1338
            'fulltext', _mod_knit.DeltaPlainToFullText(logged_vf))
 
1339
        self.assertEqual(b'origin\n', ft_data)
 
1340
        self.assertEqual(b'base\nleft\nright\nmerged\n', delta_data)
 
1341
        self.assertEqual([('get_record_stream', [(b'left',)], 'unordered',
 
1342
                           True)], logged_vf.calls)
1327
1343
 
1328
1344
    def test_unannotated_to_fulltext_no_eol(self):
1329
1345
        """Test adapting unannotated knits to full texts.
1336
1352
        # Reconstructing a full text requires a backing versioned file, and it
1337
1353
        # must have the base lines requested from it.
1338
1354
        logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1339
 
        ft_data, delta_data = self.helpGetBytes(f,
1340
 
            _mod_knit.FTPlainToFullText(None),
1341
 
            _mod_knit.DeltaPlainToFullText(logged_vf))
1342
 
        self.assertEqual('origin', ft_data)
1343
 
        self.assertEqual('base\nleft\nright\nmerged', delta_data)
1344
 
        self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1345
 
            True)], logged_vf.calls)
 
1355
        ft_data, delta_data = self.helpGetBytes(
 
1356
            f, 'fulltext', _mod_knit.FTPlainToFullText(None),
 
1357
            'fulltext', _mod_knit.DeltaPlainToFullText(logged_vf))
 
1358
        self.assertEqual(b'origin', ft_data)
 
1359
        self.assertEqual(b'base\nleft\nright\nmerged', delta_data)
 
1360
        self.assertEqual([('get_record_stream', [(b'left',)], 'unordered',
 
1361
                           True)], logged_vf.calls)
1346
1362
 
1347
1363
 
1348
1364
class TestKeyMapper(TestCaseWithMemoryTransport):
1350
1366
 
1351
1367
    def test_identity_mapper(self):
1352
1368
        mapper = versionedfile.ConstantMapper("inventory")
1353
 
        self.assertEqual("inventory", mapper.map(('foo@ar',)))
1354
 
        self.assertEqual("inventory", mapper.map(('quux',)))
 
1369
        self.assertEqual("inventory", mapper.map((b'foo@ar',)))
 
1370
        self.assertEqual("inventory", mapper.map((b'quux',)))
1355
1371
 
1356
1372
    def test_prefix_mapper(self):
1357
1373
        #format5: plain
1358
1374
        mapper = versionedfile.PrefixMapper()
1359
 
        self.assertEqual("file-id", mapper.map(("file-id", "revision-id")))
1360
 
        self.assertEqual("new-id", mapper.map(("new-id", "revision-id")))
1361
 
        self.assertEqual(('file-id',), mapper.unmap("file-id"))
1362
 
        self.assertEqual(('new-id',), mapper.unmap("new-id"))
 
1375
        self.assertEqual("file-id", mapper.map((b"file-id", b"revision-id")))
 
1376
        self.assertEqual("new-id", mapper.map((b"new-id", b"revision-id")))
 
1377
        self.assertEqual((b'file-id',), mapper.unmap("file-id"))
 
1378
        self.assertEqual((b'new-id',), mapper.unmap("new-id"))
1363
1379
 
1364
1380
    def test_hash_prefix_mapper(self):
1365
1381
        #format6: hash + plain
1366
1382
        mapper = versionedfile.HashPrefixMapper()
1367
 
        self.assertEqual("9b/file-id", mapper.map(("file-id", "revision-id")))
1368
 
        self.assertEqual("45/new-id", mapper.map(("new-id", "revision-id")))
1369
 
        self.assertEqual(('file-id',), mapper.unmap("9b/file-id"))
1370
 
        self.assertEqual(('new-id',), mapper.unmap("45/new-id"))
 
1383
        self.assertEqual(
 
1384
            "9b/file-id", mapper.map((b"file-id", b"revision-id")))
 
1385
        self.assertEqual("45/new-id", mapper.map((b"new-id", b"revision-id")))
 
1386
        self.assertEqual((b'file-id',), mapper.unmap("9b/file-id"))
 
1387
        self.assertEqual((b'new-id',), mapper.unmap("45/new-id"))
1371
1388
 
1372
1389
    def test_hash_escaped_mapper(self):
1373
1390
        #knit1: hash + escaped
1374
1391
        mapper = versionedfile.HashEscapedPrefixMapper()
1375
 
        self.assertEqual("88/%2520", mapper.map((" ", "revision-id")))
1376
 
        self.assertEqual("ed/fil%2545-%2549d", mapper.map(("filE-Id",
1377
 
            "revision-id")))
1378
 
        self.assertEqual("88/ne%2557-%2549d", mapper.map(("neW-Id",
1379
 
            "revision-id")))
1380
 
        self.assertEqual(('filE-Id',), mapper.unmap("ed/fil%2545-%2549d"))
1381
 
        self.assertEqual(('neW-Id',), mapper.unmap("88/ne%2557-%2549d"))
 
1392
        self.assertEqual("88/%2520", mapper.map((b" ", b"revision-id")))
 
1393
        self.assertEqual("ed/fil%2545-%2549d", mapper.map((b"filE-Id",
 
1394
                                                           b"revision-id")))
 
1395
        self.assertEqual("88/ne%2557-%2549d", mapper.map((b"neW-Id",
 
1396
                                                          b"revision-id")))
 
1397
        self.assertEqual((b'filE-Id',), mapper.unmap("ed/fil%2545-%2549d"))
 
1398
        self.assertEqual((b'neW-Id',), mapper.unmap("88/ne%2557-%2549d"))
1382
1399
 
1383
1400
 
1384
1401
class TestVersionedFiles(TestCaseWithMemoryTransport):
1398
1415
        ('weave-named', {
1399
1416
            'cleanup': None,
1400
1417
            'factory': make_versioned_files_factory(WeaveFile,
1401
 
                ConstantMapper('inventory')),
 
1418
                                                    ConstantMapper('inventory')),
1402
1419
            'graph': True,
1403
1420
            'key_length': 1,
1404
1421
            'support_partial_insertion': False,
1443
1460
        ('weave-prefix', {
1444
1461
            'cleanup': None,
1445
1462
            'factory': make_versioned_files_factory(WeaveFile,
1446
 
                PrefixMapper()),
 
1463
                                                    PrefixMapper()),
1447
1464
            'graph': True,
1448
1465
            'key_length': 2,
1449
1466
            'support_partial_insertion': False,
1487
1504
        if self.key_length == 1:
1488
1505
            return (suffix,)
1489
1506
        else:
1490
 
            return ('FileA',) + (suffix,)
 
1507
            return (b'FileA',) + (suffix,)
1491
1508
 
1492
1509
    def test_add_fallback_implies_without_fallbacks(self):
1493
1510
        f = self.get_versionedfiles('files')
1495
1512
            raise TestNotApplicable("%s doesn't support fallbacks"
1496
1513
                                    % (f.__class__.__name__,))
1497
1514
        g = self.get_versionedfiles('fallback')
1498
 
        key_a = self.get_simple_key('a')
1499
 
        g.add_lines(key_a, [], ['\n'])
 
1515
        key_a = self.get_simple_key(b'a')
 
1516
        g.add_lines(key_a, [], [b'\n'])
1500
1517
        f.add_fallback_versioned_files(g)
1501
1518
        self.assertTrue(key_a in f.get_parent_map([key_a]))
1502
 
        self.assertFalse(key_a in f.without_fallbacks().get_parent_map([key_a]))
 
1519
        self.assertFalse(
 
1520
            key_a in f.without_fallbacks().get_parent_map([key_a]))
1503
1521
 
1504
1522
    def test_add_lines(self):
1505
1523
        f = self.get_versionedfiles()
1506
 
        key0 = self.get_simple_key('r0')
1507
 
        key1 = self.get_simple_key('r1')
1508
 
        key2 = self.get_simple_key('r2')
1509
 
        keyf = self.get_simple_key('foo')
1510
 
        f.add_lines(key0, [], ['a\n', 'b\n'])
 
1524
        key0 = self.get_simple_key(b'r0')
 
1525
        key1 = self.get_simple_key(b'r1')
 
1526
        key2 = self.get_simple_key(b'r2')
 
1527
        keyf = self.get_simple_key(b'foo')
 
1528
        f.add_lines(key0, [], [b'a\n', b'b\n'])
1511
1529
        if self.graph:
1512
 
            f.add_lines(key1, [key0], ['b\n', 'c\n'])
 
1530
            f.add_lines(key1, [key0], [b'b\n', b'c\n'])
1513
1531
        else:
1514
 
            f.add_lines(key1, [], ['b\n', 'c\n'])
 
1532
            f.add_lines(key1, [], [b'b\n', b'c\n'])
1515
1533
        keys = f.keys()
1516
1534
        self.assertTrue(key0 in keys)
1517
1535
        self.assertTrue(key1 in keys)
1519
1537
        for record in f.get_record_stream([key0, key1], 'unordered', True):
1520
1538
            records.append((record.key, record.get_bytes_as('fulltext')))
1521
1539
        records.sort()
1522
 
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
 
1540
        self.assertEqual([(key0, b'a\nb\n'), (key1, b'b\nc\n')], records)
 
1541
 
 
1542
    def test_add_chunks(self):
 
1543
        f = self.get_versionedfiles()
 
1544
        key0 = self.get_simple_key(b'r0')
 
1545
        key1 = self.get_simple_key(b'r1')
 
1546
        key2 = self.get_simple_key(b'r2')
 
1547
        keyf = self.get_simple_key(b'foo')
 
1548
        def add_chunks(key, parents, chunks):
 
1549
            factory = ChunkedContentFactory(
 
1550
                key, parents, osutils.sha_strings(chunks), chunks)
 
1551
            return f.add_content(factory)
 
1552
 
 
1553
        add_chunks(key0, [], [b'a', b'\nb\n'])
 
1554
        if self.graph:
 
1555
            add_chunks(key1, [key0], [b'b', b'\n', b'c\n'])
 
1556
        else:
 
1557
            add_chunks(key1, [], [b'b\n', b'c\n'])
 
1558
        keys = f.keys()
 
1559
        self.assertIn(key0, keys)
 
1560
        self.assertIn(key1, keys)
 
1561
        records = []
 
1562
        for record in f.get_record_stream([key0, key1], 'unordered', True):
 
1563
            records.append((record.key, record.get_bytes_as('fulltext')))
 
1564
        records.sort()
 
1565
        self.assertEqual([(key0, b'a\nb\n'), (key1, b'b\nc\n')], records)
1523
1566
 
1524
1567
    def test_annotate(self):
1525
1568
        files = self.get_versionedfiles()
1527
1570
        if self.key_length == 1:
1528
1571
            prefix = ()
1529
1572
        else:
1530
 
            prefix = ('FileA',)
 
1573
            prefix = (b'FileA',)
1531
1574
        # introduced full text
1532
 
        origins = files.annotate(prefix + ('origin',))
 
1575
        origins = files.annotate(prefix + (b'origin',))
1533
1576
        self.assertEqual([
1534
 
            (prefix + ('origin',), 'origin\n')],
 
1577
            (prefix + (b'origin',), b'origin\n')],
1535
1578
            origins)
1536
1579
        # a delta
1537
 
        origins = files.annotate(prefix + ('base',))
 
1580
        origins = files.annotate(prefix + (b'base',))
1538
1581
        self.assertEqual([
1539
 
            (prefix + ('base',), 'base\n')],
 
1582
            (prefix + (b'base',), b'base\n')],
1540
1583
            origins)
1541
1584
        # a merge
1542
 
        origins = files.annotate(prefix + ('merged',))
 
1585
        origins = files.annotate(prefix + (b'merged',))
1543
1586
        if self.graph:
1544
1587
            self.assertEqual([
1545
 
                (prefix + ('base',), 'base\n'),
1546
 
                (prefix + ('left',), 'left\n'),
1547
 
                (prefix + ('right',), 'right\n'),
1548
 
                (prefix + ('merged',), 'merged\n')
 
1588
                (prefix + (b'base',), b'base\n'),
 
1589
                (prefix + (b'left',), b'left\n'),
 
1590
                (prefix + (b'right',), b'right\n'),
 
1591
                (prefix + (b'merged',), b'merged\n')
1549
1592
                ],
1550
1593
                origins)
1551
1594
        else:
1552
1595
            # Without a graph everything is new.
1553
1596
            self.assertEqual([
1554
 
                (prefix + ('merged',), 'base\n'),
1555
 
                (prefix + ('merged',), 'left\n'),
1556
 
                (prefix + ('merged',), 'right\n'),
1557
 
                (prefix + ('merged',), 'merged\n')
 
1597
                (prefix + (b'merged',), b'base\n'),
 
1598
                (prefix + (b'merged',), b'left\n'),
 
1599
                (prefix + (b'merged',), b'right\n'),
 
1600
                (prefix + (b'merged',), b'merged\n')
1558
1601
                ],
1559
1602
                origins)
1560
1603
        self.assertRaises(RevisionNotPresent,
1561
 
            files.annotate, prefix + ('missing-key',))
 
1604
                          files.annotate, prefix + ('missing-key',))
1562
1605
 
1563
1606
    def test_check_no_parameters(self):
1564
1607
        files = self.get_versionedfiles()
1578
1621
        seen = set()
1579
1622
        # Texts output should be fulltexts.
1580
1623
        self.capture_stream(files, entries, seen.add,
1581
 
            files.get_parent_map(keys), require_fulltext=True)
 
1624
                            files.get_parent_map(keys), require_fulltext=True)
1582
1625
        # All texts should be output.
1583
1626
        self.assertEqual(set(keys), seen)
1584
1627
 
1591
1634
        files = self.get_versionedfiles()
1592
1635
 
1593
1636
    def get_diamond_files(self, files, trailing_eol=True, left_only=False,
1594
 
        nokeys=False):
 
1637
                          nokeys=False):
1595
1638
        return get_diamond_files(files, self.key_length,
1596
 
            trailing_eol=trailing_eol, nograph=not self.graph,
1597
 
            left_only=left_only, nokeys=nokeys)
 
1639
                                 trailing_eol=trailing_eol, nograph=not self.graph,
 
1640
                                 left_only=left_only, nokeys=nokeys)
1598
1641
 
1599
1642
    def _add_content_nostoresha(self, add_lines):
1600
1643
        """When nostore_sha is supplied using old content raises."""
1601
1644
        vf = self.get_versionedfiles()
1602
 
        empty_text = ('a', [])
1603
 
        sample_text_nl = ('b', ["foo\n", "bar\n"])
1604
 
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
1645
        empty_text = (b'a', [])
 
1646
        sample_text_nl = (b'b', [b"foo\n", b"bar\n"])
 
1647
        sample_text_no_nl = (b'c', [b"foo\n", b"bar"])
1605
1648
        shas = []
1606
1649
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
1607
1650
            if add_lines:
1613
1656
            shas.append(sha)
1614
1657
        # we now have a copy of all the lines in the vf.
1615
1658
        for sha, (version, lines) in zip(
1616
 
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
1617
 
            new_key = self.get_simple_key(version + "2")
1618
 
            self.assertRaises(errors.ExistingContent,
1619
 
                vf.add_lines, new_key, [], lines,
1620
 
                nostore_sha=sha)
1621
 
            self.assertRaises(errors.ExistingContent,
1622
 
                vf.add_lines, new_key, [], lines,
1623
 
                nostore_sha=sha)
 
1659
                shas, (empty_text, sample_text_nl, sample_text_no_nl)):
 
1660
            new_key = self.get_simple_key(version + b"2")
 
1661
            self.assertRaises(errors.ExistingContent,
 
1662
                              vf.add_lines, new_key, [], lines,
 
1663
                              nostore_sha=sha)
 
1664
            self.assertRaises(errors.ExistingContent,
 
1665
                              vf.add_lines, new_key, [], lines,
 
1666
                              nostore_sha=sha)
1624
1667
            # and no new version should have been added.
1625
1668
            record = next(vf.get_record_stream([new_key], 'unordered', True))
1626
1669
            self.assertEqual('absent', record.storage_kind)
1639
1682
            results.append(add[:2])
1640
1683
        if self.key_length == 1:
1641
1684
            self.assertEqual([
1642
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1643
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1644
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1645
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1646
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
 
1685
                (b'00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1686
                (b'51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1687
                (b'a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1688
                (b'9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1689
                (b'ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1647
1690
                results)
1648
1691
        elif self.key_length == 2:
1649
1692
            self.assertEqual([
1650
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1651
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1652
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1653
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1654
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1655
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1656
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1657
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1658
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
1659
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
 
1693
                (b'00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1694
                (b'00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1695
                (b'51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1696
                (b'51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1697
                (b'a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1698
                (b'a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1699
                (b'9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1700
                (b'9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1701
                (b'ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
 
1702
                (b'ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1660
1703
                results)
1661
1704
 
1662
1705
    def test_add_lines_no_key_generates_chk_key(self):
1670
1713
            results.append(add[:2])
1671
1714
        if self.key_length == 1:
1672
1715
            self.assertEqual([
1673
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1674
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1675
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1676
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1677
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
 
1716
                (b'00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1717
                (b'51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1718
                (b'a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1719
                (b'9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1720
                (b'ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1678
1721
                results)
1679
1722
            # Check the added items got CHK keys.
1680
1723
            self.assertEqual({
1681
 
                ('sha1:00e364d235126be43292ab09cb4686cf703ddc17',),
1682
 
                ('sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',),
1683
 
                ('sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',),
1684
 
                ('sha1:a8478686da38e370e32e42e8a0c220e33ee9132f',),
1685
 
                ('sha1:ed8bce375198ea62444dc71952b22cfc2b09226d',),
 
1724
                (b'sha1:00e364d235126be43292ab09cb4686cf703ddc17',),
 
1725
                (b'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',),
 
1726
                (b'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',),
 
1727
                (b'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f',),
 
1728
                (b'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d',),
1686
1729
                },
1687
1730
                files.keys())
1688
1731
        elif self.key_length == 2:
1689
1732
            self.assertEqual([
1690
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1691
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1692
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1693
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1694
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1695
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1696
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1697
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1698
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
1699
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
 
1733
                (b'00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1734
                (b'00e364d235126be43292ab09cb4686cf703ddc17', 7),
 
1735
                (b'51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1736
                (b'51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
 
1737
                (b'a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1738
                (b'a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
 
1739
                (b'9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1740
                (b'9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
 
1741
                (b'ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
 
1742
                (b'ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1700
1743
                results)
1701
1744
            # Check the added items got CHK keys.
1702
1745
            self.assertEqual({
1703
 
                ('FileA', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1704
 
                ('FileA', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1705
 
                ('FileA', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1706
 
                ('FileA', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1707
 
                ('FileA', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1708
 
                ('FileB', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1709
 
                ('FileB', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1710
 
                ('FileB', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1711
 
                ('FileB', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1712
 
                ('FileB', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
 
1746
                (b'FileA', b'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
 
1747
                (b'FileA', b'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
 
1748
                (b'FileA', b'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
 
1749
                (b'FileA', b'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
 
1750
                (b'FileA', b'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
 
1751
                (b'FileB', b'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
 
1752
                (b'FileB', b'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
 
1753
                (b'FileB', b'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
 
1754
                (b'FileB', b'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
 
1755
                (b'FileB', b'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1713
1756
                },
1714
1757
                files.keys())
1715
1758
 
1716
1759
    def test_empty_lines(self):
1717
1760
        """Empty files can be stored."""
1718
1761
        f = self.get_versionedfiles()
1719
 
        key_a = self.get_simple_key('a')
 
1762
        key_a = self.get_simple_key(b'a')
1720
1763
        f.add_lines(key_a, [], [])
1721
 
        self.assertEqual('',
1722
 
            f.get_record_stream([key_a], 'unordered', True
1723
 
                ).next().get_bytes_as('fulltext'))
1724
 
        key_b = self.get_simple_key('b')
 
1764
        self.assertEqual(b'',
 
1765
                         next(f.get_record_stream([key_a], 'unordered', True
 
1766
                                                  )).get_bytes_as('fulltext'))
 
1767
        key_b = self.get_simple_key(b'b')
1725
1768
        f.add_lines(key_b, self.get_parents([key_a]), [])
1726
 
        self.assertEqual('',
1727
 
            f.get_record_stream([key_b], 'unordered', True
1728
 
                ).next().get_bytes_as('fulltext'))
 
1769
        self.assertEqual(b'',
 
1770
                         next(f.get_record_stream([key_b], 'unordered', True
 
1771
                                                  )).get_bytes_as('fulltext'))
1729
1772
 
1730
1773
    def test_newline_only(self):
1731
1774
        f = self.get_versionedfiles()
1732
 
        key_a = self.get_simple_key('a')
1733
 
        f.add_lines(key_a, [], ['\n'])
1734
 
        self.assertEqual('\n',
1735
 
            f.get_record_stream([key_a], 'unordered', True
1736
 
                ).next().get_bytes_as('fulltext'))
1737
 
        key_b = self.get_simple_key('b')
1738
 
        f.add_lines(key_b, self.get_parents([key_a]), ['\n'])
1739
 
        self.assertEqual('\n',
1740
 
            f.get_record_stream([key_b], 'unordered', True
1741
 
                ).next().get_bytes_as('fulltext'))
 
1775
        key_a = self.get_simple_key(b'a')
 
1776
        f.add_lines(key_a, [], [b'\n'])
 
1777
        self.assertEqual(b'\n',
 
1778
                         next(f.get_record_stream([key_a], 'unordered', True
 
1779
                                                  )).get_bytes_as('fulltext'))
 
1780
        key_b = self.get_simple_key(b'b')
 
1781
        f.add_lines(key_b, self.get_parents([key_a]), [b'\n'])
 
1782
        self.assertEqual(b'\n',
 
1783
                         next(f.get_record_stream([key_b], 'unordered', True
 
1784
                                                  )).get_bytes_as('fulltext'))
1742
1785
 
1743
1786
    def test_get_known_graph_ancestry(self):
1744
1787
        f = self.get_versionedfiles()
1745
1788
        if not self.graph:
1746
1789
            raise TestNotApplicable('ancestry info only relevant with graph.')
1747
 
        key_a = self.get_simple_key('a')
1748
 
        key_b = self.get_simple_key('b')
1749
 
        key_c = self.get_simple_key('c')
 
1790
        key_a = self.get_simple_key(b'a')
 
1791
        key_b = self.get_simple_key(b'b')
 
1792
        key_c = self.get_simple_key(b'c')
1750
1793
        # A
1751
1794
        # |\
1752
1795
        # | B
1753
1796
        # |/
1754
1797
        # C
1755
 
        f.add_lines(key_a, [], ['\n'])
1756
 
        f.add_lines(key_b, [key_a], ['\n'])
1757
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
 
1798
        f.add_lines(key_a, [], [b'\n'])
 
1799
        f.add_lines(key_b, [key_a], [b'\n'])
 
1800
        f.add_lines(key_c, [key_a, key_b], [b'\n'])
1758
1801
        kg = f.get_known_graph_ancestry([key_c])
1759
1802
        self.assertIsInstance(kg, _mod_graph.KnownGraph)
1760
1803
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1766
1809
        if getattr(f, 'add_fallback_versioned_files', None) is None:
1767
1810
            raise TestNotApplicable("%s doesn't support fallbacks"
1768
1811
                                    % (f.__class__.__name__,))
1769
 
        key_a = self.get_simple_key('a')
1770
 
        key_b = self.get_simple_key('b')
1771
 
        key_c = self.get_simple_key('c')
 
1812
        key_a = self.get_simple_key(b'a')
 
1813
        key_b = self.get_simple_key(b'b')
 
1814
        key_c = self.get_simple_key(b'c')
1772
1815
        # A     only in fallback
1773
1816
        # |\
1774
1817
        # | B
1775
1818
        # |/
1776
1819
        # C
1777
1820
        g = self.get_versionedfiles('fallback')
1778
 
        g.add_lines(key_a, [], ['\n'])
 
1821
        g.add_lines(key_a, [], [b'\n'])
1779
1822
        f.add_fallback_versioned_files(g)
1780
 
        f.add_lines(key_b, [key_a], ['\n'])
1781
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
 
1823
        f.add_lines(key_b, [key_a], [b'\n'])
 
1824
        f.add_lines(key_c, [key_a, key_b], [b'\n'])
1782
1825
        kg = f.get_known_graph_ancestry([key_c])
1783
1826
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1784
1827
 
1791
1834
    def assertValidStorageKind(self, storage_kind):
1792
1835
        """Assert that storage_kind is a valid storage_kind."""
1793
1836
        self.assertSubset([storage_kind],
1794
 
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1795
 
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1796
 
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1797
 
             'knit-delta-gz',
1798
 
             'knit-delta-closure', 'knit-delta-closure-ref',
1799
 
             'groupcompress-block', 'groupcompress-block-ref'])
 
1837
                          ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
 
1838
                           'knit-ft', 'knit-delta', 'chunked', 'fulltext',
 
1839
                           'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
 
1840
                           'knit-delta-gz',
 
1841
                           'knit-delta-closure', 'knit-delta-closure-ref',
 
1842
                           'groupcompress-block', 'groupcompress-block-ref'])
1800
1843
 
1801
1844
    def capture_stream(self, f, entries, on_seen, parents,
1802
 
        require_fulltext=False):
 
1845
                       require_fulltext=False):
1803
1846
        """Capture a stream for testing."""
1804
1847
        for factory in entries:
1805
1848
            on_seen(factory.key)
1806
1849
            self.assertValidStorageKind(factory.storage_kind)
1807
1850
            if factory.sha1 is not None:
1808
1851
                self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1809
 
                    factory.sha1)
 
1852
                                 factory.sha1)
1810
1853
            self.assertEqual(parents[factory.key], factory.parents)
1811
1854
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1812
 
                str)
 
1855
                                  bytes)
1813
1856
            if require_fulltext:
1814
1857
                factory.get_bytes_as('fulltext')
1815
1858
 
1827
1870
    def get_keys_and_sort_order(self):
1828
1871
        """Get diamond test keys list, and their sort ordering."""
1829
1872
        if self.key_length == 1:
1830
 
            keys = [('merged',), ('left',), ('right',), ('base',)]
1831
 
            sort_order = {('merged',):2, ('left',):1, ('right',):1, ('base',):0}
 
1873
            keys = [(b'merged',), (b'left',), (b'right',), (b'base',)]
 
1874
            sort_order = {(b'merged',): 2, (b'left',): 1,
 
1875
                          (b'right',): 1, (b'base',): 0}
1832
1876
        else:
1833
1877
            keys = [
1834
 
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1835
 
                ('FileA', 'base'),
1836
 
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1837
 
                ('FileB', 'base'),
 
1878
                (b'FileA', b'merged'), (b'FileA', b'left'), (b'FileA', b'right'),
 
1879
                (b'FileA', b'base'),
 
1880
                (b'FileB', b'merged'), (b'FileB', b'left'), (b'FileB', b'right'),
 
1881
                (b'FileB', b'base'),
1838
1882
                ]
1839
1883
            sort_order = {
1840
 
                ('FileA', 'merged'): 2, ('FileA', 'left'): 1, ('FileA', 'right'): 1,
1841
 
                ('FileA', 'base'): 0,
1842
 
                ('FileB', 'merged'): 2, ('FileB', 'left'): 1, ('FileB', 'right'): 1,
1843
 
                ('FileB', 'base'): 0,
 
1884
                (b'FileA', b'merged'): 2, (b'FileA', b'left'): 1, (b'FileA', b'right'): 1,
 
1885
                (b'FileA', b'base'): 0,
 
1886
                (b'FileB', b'merged'): 2, (b'FileB', b'left'): 1, (b'FileB', b'right'): 1,
 
1887
                (b'FileB', b'base'): 0,
1844
1888
                }
1845
1889
        return keys, sort_order
1846
1890
 
1847
1891
    def get_keys_and_groupcompress_sort_order(self):
1848
1892
        """Get diamond test keys list, and their groupcompress sort ordering."""
1849
1893
        if self.key_length == 1:
1850
 
            keys = [('merged',), ('left',), ('right',), ('base',)]
1851
 
            sort_order = {('merged',):0, ('left',):1, ('right',):1, ('base',):2}
 
1894
            keys = [(b'merged',), (b'left',), (b'right',), (b'base',)]
 
1895
            sort_order = {(b'merged',): 0, (b'left',): 1,
 
1896
                          (b'right',): 1, (b'base',): 2}
1852
1897
        else:
1853
1898
            keys = [
1854
 
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1855
 
                ('FileA', 'base'),
1856
 
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1857
 
                ('FileB', 'base'),
 
1899
                (b'FileA', b'merged'), (b'FileA', b'left'), (b'FileA', b'right'),
 
1900
                (b'FileA', b'base'),
 
1901
                (b'FileB', b'merged'), (b'FileB', b'left'), (b'FileB', b'right'),
 
1902
                (b'FileB', b'base'),
1858
1903
                ]
1859
1904
            sort_order = {
1860
 
                ('FileA', 'merged'): 0, ('FileA', 'left'): 1, ('FileA', 'right'): 1,
1861
 
                ('FileA', 'base'): 2,
1862
 
                ('FileB', 'merged'): 3, ('FileB', 'left'): 4, ('FileB', 'right'): 4,
1863
 
                ('FileB', 'base'): 5,
 
1905
                (b'FileA', b'merged'): 0, (b'FileA', b'left'): 1, (b'FileA', b'right'): 1,
 
1906
                (b'FileA', b'base'): 2,
 
1907
                (b'FileB', b'merged'): 3, (b'FileB', b'left'): 4, (b'FileB', b'right'): 4,
 
1908
                (b'FileB', b'base'): 5,
1864
1909
                }
1865
1910
        return keys, sort_order
1866
1911
 
1887
1932
            seen.append(factory.key)
1888
1933
            self.assertValidStorageKind(factory.storage_kind)
1889
1934
            self.assertSubset([factory.sha1],
1890
 
                [None, files.get_sha1s([factory.key])[factory.key]])
 
1935
                              [None, files.get_sha1s([factory.key])[factory.key]])
1891
1936
            self.assertEqual(parent_map[factory.key], factory.parents)
1892
1937
            # self.assertEqual(files.get_text(factory.key),
1893
1938
            ft_bytes = factory.get_bytes_as('fulltext')
1894
 
            self.assertIsInstance(ft_bytes, str)
 
1939
            self.assertIsInstance(ft_bytes, bytes)
1895
1940
            chunked_bytes = factory.get_bytes_as('chunked')
1896
 
            self.assertEqualDiff(ft_bytes, ''.join(chunked_bytes))
 
1941
            self.assertEqualDiff(ft_bytes, b''.join(chunked_bytes))
 
1942
            chunked_bytes = factory.iter_bytes_as('chunked')
 
1943
            self.assertEqualDiff(ft_bytes, b''.join(chunked_bytes))
1897
1944
 
1898
1945
        self.assertStreamOrder(sort_order, seen, keys)
1899
1946
 
1911
1958
    def assertStreamOrder(self, sort_order, seen, keys):
1912
1959
        self.assertEqual(len(set(seen)), len(keys))
1913
1960
        if self.key_length == 1:
1914
 
            lows = {():0}
 
1961
            lows = {(): 0}
1915
1962
        else:
1916
 
            lows = {('FileA',):0, ('FileB',):0}
 
1963
            lows = {(b'FileA',): 0, (b'FileB',): 0}
1917
1964
        if not self.graph:
1918
1965
            self.assertEqual(set(keys), set(seen))
1919
1966
        else:
1920
1967
            for key in seen:
1921
1968
                sort_pos = sort_order[key]
1922
1969
                self.assertTrue(sort_pos >= lows[key[:-1]],
1923
 
                    "Out of order in sorted stream: %r, %r" % (key, seen))
 
1970
                                "Out of order in sorted stream: %r, %r" % (key, seen))
1924
1971
                lows[key[:-1]] = sort_pos
1925
1972
 
1926
1973
    def test_get_record_stream_unknown_storage_kind_raises(self):
1928
1975
        files = self.get_versionedfiles()
1929
1976
        self.get_diamond_files(files)
1930
1977
        if self.key_length == 1:
1931
 
            keys = [('merged',), ('left',), ('right',), ('base',)]
 
1978
            keys = [(b'merged',), (b'left',), (b'right',), (b'base',)]
1932
1979
        else:
1933
1980
            keys = [
1934
 
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1935
 
                ('FileA', 'base'),
1936
 
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1937
 
                ('FileB', 'base'),
 
1981
                (b'FileA', b'merged'), (b'FileA', b'left'), (b'FileA', b'right'),
 
1982
                (b'FileA', b'base'),
 
1983
                (b'FileB', b'merged'), (b'FileB', b'left'), (b'FileB', b'right'),
 
1984
                (b'FileB', b'base'),
1938
1985
                ]
1939
1986
        parent_map = files.get_parent_map(keys)
1940
1987
        entries = files.get_record_stream(keys, 'unordered', False)
1950
1997
            self.assertEqual(parent_map[factory.key], factory.parents)
1951
1998
            # currently no stream emits mpdiff
1952
1999
            self.assertRaises(errors.UnavailableRepresentation,
1953
 
                factory.get_bytes_as, 'mpdiff')
 
2000
                              factory.get_bytes_as, 'mpdiff')
1954
2001
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1955
 
                str)
 
2002
                                  bytes)
1956
2003
        self.assertEqual(set(keys), seen)
1957
2004
 
1958
2005
    def test_get_record_stream_missing_records_are_absent(self):
1959
2006
        files = self.get_versionedfiles()
1960
2007
        self.get_diamond_files(files)
1961
2008
        if self.key_length == 1:
1962
 
            keys = [('merged',), ('left',), ('right',), ('absent',), ('base',)]
 
2009
            keys = [(b'merged',), (b'left',), (b'right',),
 
2010
                    (b'absent',), (b'base',)]
1963
2011
        else:
1964
2012
            keys = [
1965
 
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1966
 
                ('FileA', 'absent'), ('FileA', 'base'),
1967
 
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1968
 
                ('FileB', 'absent'), ('FileB', 'base'),
1969
 
                ('absent', 'absent'),
 
2013
                (b'FileA', b'merged'), (b'FileA', b'left'), (b'FileA', b'right'),
 
2014
                (b'FileA', b'absent'), (b'FileA', b'base'),
 
2015
                (b'FileB', b'merged'), (b'FileB', b'left'), (b'FileB', b'right'),
 
2016
                (b'FileB', b'absent'), (b'FileB', b'base'),
 
2017
                (b'absent', b'absent'),
1970
2018
                ]
1971
2019
        parent_map = files.get_parent_map(keys)
1972
2020
        entries = files.get_record_stream(keys, 'unordered', False)
1977
2025
    def assertRecordHasContent(self, record, bytes):
1978
2026
        """Assert that record has the bytes bytes."""
1979
2027
        self.assertEqual(bytes, record.get_bytes_as('fulltext'))
1980
 
        self.assertEqual(bytes, ''.join(record.get_bytes_as('chunked')))
 
2028
        self.assertEqual(bytes, b''.join(record.get_bytes_as('chunked')))
1981
2029
 
1982
2030
    def test_get_record_stream_native_formats_are_wire_ready_one_ft(self):
1983
2031
        files = self.get_versionedfiles()
1984
 
        key = self.get_simple_key('foo')
1985
 
        files.add_lines(key, (), ['my text\n', 'content'])
 
2032
        key = self.get_simple_key(b'foo')
 
2033
        files.add_lines(key, (), [b'my text\n', b'content'])
1986
2034
        stream = files.get_record_stream([key], 'unordered', False)
1987
2035
        record = next(stream)
1988
2036
        if record.storage_kind in ('chunked', 'fulltext'):
1989
2037
            # chunked and fulltext representations are for direct use not wire
1990
2038
            # serialisation: check they are able to be used directly. To send
1991
2039
            # such records over the wire translation will be needed.
1992
 
            self.assertRecordHasContent(record, "my text\ncontent")
 
2040
            self.assertRecordHasContent(record, b"my text\ncontent")
1993
2041
        else:
1994
2042
            bytes = [record.get_bytes_as(record.storage_kind)]
1995
2043
            network_stream = versionedfile.NetworkRecordStream(bytes).read()
1998
2046
            for record in network_stream:
1999
2047
                records.append(record)
2000
2048
                self.assertEqual(source_record.storage_kind,
2001
 
                    record.storage_kind)
 
2049
                                 record.storage_kind)
2002
2050
                self.assertEqual(source_record.parents, record.parents)
2003
2051
                self.assertEqual(
2004
2052
                    source_record.get_bytes_as(source_record.storage_kind),
2021
2069
            yield record
2022
2070
 
2023
2071
    def stream_to_bytes_or_skip_counter(self, skipped_records, full_texts,
2024
 
        stream):
 
2072
                                        stream):
2025
2073
        """Convert a stream to a bytes iterator.
2026
2074
 
2027
2075
        :param skipped_records: A list with one element to increment when a
2042
2090
    def test_get_record_stream_native_formats_are_wire_ready_ft_delta(self):
2043
2091
        files = self.get_versionedfiles()
2044
2092
        target_files = self.get_versionedfiles('target')
2045
 
        key = self.get_simple_key('ft')
2046
 
        key_delta = self.get_simple_key('delta')
2047
 
        files.add_lines(key, (), ['my text\n', 'content'])
 
2093
        key = self.get_simple_key(b'ft')
 
2094
        key_delta = self.get_simple_key(b'delta')
 
2095
        files.add_lines(key, (), [b'my text\n', b'content'])
2048
2096
        if self.graph:
2049
2097
            delta_parents = (key,)
2050
2098
        else:
2051
2099
            delta_parents = ()
2052
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
2100
        files.add_lines(key_delta, delta_parents, [
 
2101
                        b'different\n', b'content\n'])
2053
2102
        local = files.get_record_stream([key, key_delta], 'unordered', False)
2054
2103
        ref = files.get_record_stream([key, key_delta], 'unordered', False)
2055
2104
        skipped_records = [0]
2056
2105
        full_texts = {
2057
 
            key: "my text\ncontent",
2058
 
            key_delta: "different\ncontent\n",
 
2106
            key: b"my text\ncontent",
 
2107
            key_delta: b"different\ncontent\n",
2059
2108
            }
2060
2109
        byte_stream = self.stream_to_bytes_or_skip_counter(
2061
2110
            skipped_records, full_texts, local)
2076
2125
        # copy a delta over the wire
2077
2126
        files = self.get_versionedfiles()
2078
2127
        target_files = self.get_versionedfiles('target')
2079
 
        key = self.get_simple_key('ft')
2080
 
        key_delta = self.get_simple_key('delta')
2081
 
        files.add_lines(key, (), ['my text\n', 'content'])
 
2128
        key = self.get_simple_key(b'ft')
 
2129
        key_delta = self.get_simple_key(b'delta')
 
2130
        files.add_lines(key, (), [b'my text\n', b'content'])
2082
2131
        if self.graph:
2083
2132
            delta_parents = (key,)
2084
2133
        else:
2085
2134
            delta_parents = ()
2086
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
2135
        files.add_lines(key_delta, delta_parents, [
 
2136
                        b'different\n', b'content\n'])
2087
2137
        # Copy the basis text across so we can reconstruct the delta during
2088
2138
        # insertion into target.
2089
2139
        target_files.insert_record_stream(files.get_record_stream([key],
2090
 
            'unordered', False))
 
2140
                                                                  'unordered', False))
2091
2141
        local = files.get_record_stream([key_delta], 'unordered', False)
2092
2142
        ref = files.get_record_stream([key_delta], 'unordered', False)
2093
2143
        skipped_records = [0]
2094
2144
        full_texts = {
2095
 
            key_delta: "different\ncontent\n",
 
2145
            key_delta: b"different\ncontent\n",
2096
2146
            }
2097
2147
        byte_stream = self.stream_to_bytes_or_skip_counter(
2098
2148
            skipped_records, full_texts, local)
2112
2162
    def test_get_record_stream_wire_ready_delta_closure_included(self):
2113
2163
        # copy a delta over the wire with the ability to get its full text.
2114
2164
        files = self.get_versionedfiles()
2115
 
        key = self.get_simple_key('ft')
2116
 
        key_delta = self.get_simple_key('delta')
2117
 
        files.add_lines(key, (), ['my text\n', 'content'])
 
2165
        key = self.get_simple_key(b'ft')
 
2166
        key_delta = self.get_simple_key(b'delta')
 
2167
        files.add_lines(key, (), [b'my text\n', b'content'])
2118
2168
        if self.graph:
2119
2169
            delta_parents = (key,)
2120
2170
        else:
2121
2171
            delta_parents = ()
2122
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
2172
        files.add_lines(key_delta, delta_parents, [
 
2173
                        b'different\n', b'content\n'])
2123
2174
        local = files.get_record_stream([key_delta], 'unordered', True)
2124
2175
        ref = files.get_record_stream([key_delta], 'unordered', True)
2125
2176
        skipped_records = [0]
2126
2177
        full_texts = {
2127
 
            key_delta: "different\ncontent\n",
 
2178
            key_delta: b"different\ncontent\n",
2128
2179
            }
2129
2180
        byte_stream = self.stream_to_bytes_or_skip_counter(
2130
2181
            skipped_records, full_texts, local)
2144
2195
        seen = set()
2145
2196
        for factory in entries:
2146
2197
            seen.add(factory.key)
2147
 
            if factory.key[-1] == 'absent':
 
2198
            if factory.key[-1] == b'absent':
2148
2199
                self.assertEqual('absent', factory.storage_kind)
2149
2200
                self.assertEqual(None, factory.sha1)
2150
2201
                self.assertEqual(None, factory.parents)
2155
2206
                    self.assertEqual(sha1, factory.sha1)
2156
2207
                self.assertEqual(parents[factory.key], factory.parents)
2157
2208
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
2158
 
                    str)
 
2209
                                      bytes)
2159
2210
        self.assertEqual(set(keys), seen)
2160
2211
 
2161
2212
    def test_filter_absent_records(self):
2169
2220
        # absent keys is still delivered).
2170
2221
        present_keys = list(keys)
2171
2222
        if self.key_length == 1:
2172
 
            keys.insert(2, ('extra',))
 
2223
            keys.insert(2, (b'extra',))
2173
2224
        else:
2174
 
            keys.insert(2, ('extra', 'extra'))
 
2225
            keys.insert(2, (b'extra', b'extra'))
2175
2226
        entries = files.get_record_stream(keys, 'unordered', False)
2176
2227
        seen = set()
2177
2228
        self.capture_stream(files, versionedfile.filter_absent(entries), seen.add,
2178
 
            parent_map)
 
2229
                            parent_map)
2179
2230
        self.assertEqual(set(present_keys), seen)
2180
2231
 
2181
2232
    def get_mapper(self):
2195
2246
    def test_get_annotator(self):
2196
2247
        files = self.get_versionedfiles()
2197
2248
        self.get_diamond_files(files)
2198
 
        origin_key = self.get_simple_key('origin')
2199
 
        base_key = self.get_simple_key('base')
2200
 
        left_key = self.get_simple_key('left')
2201
 
        right_key = self.get_simple_key('right')
2202
 
        merged_key = self.get_simple_key('merged')
 
2249
        origin_key = self.get_simple_key(b'origin')
 
2250
        base_key = self.get_simple_key(b'base')
 
2251
        left_key = self.get_simple_key(b'left')
 
2252
        right_key = self.get_simple_key(b'right')
 
2253
        merged_key = self.get_simple_key(b'merged')
2203
2254
        # annotator = files.get_annotator()
2204
2255
        # introduced full text
2205
2256
        origins, lines = files.get_annotator().annotate(origin_key)
2206
2257
        self.assertEqual([(origin_key,)], origins)
2207
 
        self.assertEqual(['origin\n'], lines)
 
2258
        self.assertEqual([b'origin\n'], lines)
2208
2259
        # a delta
2209
2260
        origins, lines = files.get_annotator().annotate(base_key)
2210
2261
        self.assertEqual([(base_key,)], origins)
2226
2277
                (merged_key,),
2227
2278
                ], origins)
2228
2279
        self.assertRaises(RevisionNotPresent,
2229
 
            files.get_annotator().annotate, self.get_simple_key('missing-key'))
 
2280
                          files.get_annotator().annotate, self.get_simple_key(b'missing-key'))
2230
2281
 
2231
2282
    def test_get_parent_map(self):
2232
2283
        files = self.get_versionedfiles()
2233
2284
        if self.key_length == 1:
2234
2285
            parent_details = [
2235
 
                (('r0',), self.get_parents(())),
2236
 
                (('r1',), self.get_parents((('r0',),))),
2237
 
                (('r2',), self.get_parents(())),
2238
 
                (('r3',), self.get_parents(())),
2239
 
                (('m',), self.get_parents((('r0',), ('r1',), ('r2',), ('r3',)))),
 
2286
                ((b'r0',), self.get_parents(())),
 
2287
                ((b'r1',), self.get_parents(((b'r0',),))),
 
2288
                ((b'r2',), self.get_parents(())),
 
2289
                ((b'r3',), self.get_parents(())),
 
2290
                ((b'm',), self.get_parents(((b'r0',), (b'r1',), (b'r2',), (b'r3',)))),
2240
2291
                ]
2241
2292
        else:
2242
2293
            parent_details = [
2243
 
                (('FileA', 'r0'), self.get_parents(())),
2244
 
                (('FileA', 'r1'), self.get_parents((('FileA', 'r0'),))),
2245
 
                (('FileA', 'r2'), self.get_parents(())),
2246
 
                (('FileA', 'r3'), self.get_parents(())),
2247
 
                (('FileA', 'm'), self.get_parents((('FileA', 'r0'),
2248
 
                    ('FileA', 'r1'), ('FileA', 'r2'), ('FileA', 'r3')))),
 
2294
                ((b'FileA', b'r0'), self.get_parents(())),
 
2295
                ((b'FileA', b'r1'), self.get_parents(((b'FileA', b'r0'),))),
 
2296
                ((b'FileA', b'r2'), self.get_parents(())),
 
2297
                ((b'FileA', b'r3'), self.get_parents(())),
 
2298
                ((b'FileA', b'm'), self.get_parents(((b'FileA', b'r0'),
 
2299
                                                     (b'FileA', b'r1'), (b'FileA', b'r2'), (b'FileA', b'r3')))),
2249
2300
                ]
2250
2301
        for key, parents in parent_details:
2251
2302
            files.add_lines(key, parents, [])
2252
2303
            # immediately after adding it should be queryable.
2253
 
            self.assertEqual({key:parents}, files.get_parent_map([key]))
 
2304
            self.assertEqual({key: parents}, files.get_parent_map([key]))
2254
2305
        # We can ask for an empty set
2255
2306
        self.assertEqual({}, files.get_parent_map([]))
2256
2307
        # We can ask for many keys
2257
2308
        all_parents = dict(parent_details)
2258
2309
        self.assertEqual(all_parents, files.get_parent_map(all_parents.keys()))
2259
2310
        # Absent keys are just not included in the result.
2260
 
        keys = all_parents.keys()
 
2311
        keys = list(all_parents.keys())
2261
2312
        if self.key_length == 1:
2262
 
            keys.insert(1, ('missing',))
 
2313
            keys.insert(1, (b'missing',))
2263
2314
        else:
2264
 
            keys.insert(1, ('missing', 'missing'))
 
2315
            keys.insert(1, (b'missing', b'missing'))
2265
2316
        # Absent keys are just ignored
2266
2317
        self.assertEqual(all_parents, files.get_parent_map(keys))
2267
2318
 
2269
2320
        files = self.get_versionedfiles()
2270
2321
        self.get_diamond_files(files)
2271
2322
        if self.key_length == 1:
2272
 
            keys = [('base',), ('origin',), ('left',), ('merged',), ('right',)]
 
2323
            keys = [(b'base',), (b'origin',), (b'left',),
 
2324
                    (b'merged',), (b'right',)]
2273
2325
        else:
2274
2326
            # ask for shas from different prefixes.
2275
2327
            keys = [
2276
 
                ('FileA', 'base'), ('FileB', 'origin'), ('FileA', 'left'),
2277
 
                ('FileA', 'merged'), ('FileB', 'right'),
 
2328
                (b'FileA', b'base'), (b'FileB', b'origin'), (b'FileA', b'left'),
 
2329
                (b'FileA', b'merged'), (b'FileB', b'right'),
2278
2330
                ]
2279
2331
        self.assertEqual({
2280
 
            keys[0]: '51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',
2281
 
            keys[1]: '00e364d235126be43292ab09cb4686cf703ddc17',
2282
 
            keys[2]: 'a8478686da38e370e32e42e8a0c220e33ee9132f',
2283
 
            keys[3]: 'ed8bce375198ea62444dc71952b22cfc2b09226d',
2284
 
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
 
2332
            keys[0]: b'51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',
 
2333
            keys[1]: b'00e364d235126be43292ab09cb4686cf703ddc17',
 
2334
            keys[2]: b'a8478686da38e370e32e42e8a0c220e33ee9132f',
 
2335
            keys[3]: b'ed8bce375198ea62444dc71952b22cfc2b09226d',
 
2336
            keys[4]: b'9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
2285
2337
            },
2286
2338
            files.get_sha1s(keys))
2287
2339
 
2295
2347
        self.assertEqual(set(actual.keys()), set(expected.keys()))
2296
2348
        actual_parents = actual.get_parent_map(actual.keys())
2297
2349
        if self.graph:
2298
 
            self.assertEqual(actual_parents, expected.get_parent_map(expected.keys()))
 
2350
            self.assertEqual(
 
2351
                actual_parents, expected.get_parent_map(expected.keys()))
2299
2352
        else:
2300
2353
            for key, parents in actual_parents.items():
2301
2354
                self.assertEqual(None, parents)
2302
2355
        for key in actual.keys():
2303
 
            actual_text = actual.get_record_stream(
2304
 
                [key], 'unordered', True).next().get_bytes_as('fulltext')
2305
 
            expected_text = expected.get_record_stream(
2306
 
                [key], 'unordered', True).next().get_bytes_as('fulltext')
 
2356
            actual_text = next(actual.get_record_stream(
 
2357
                [key], 'unordered', True)).get_bytes_as('fulltext')
 
2358
            expected_text = next(expected.get_record_stream(
 
2359
                [key], 'unordered', True)).get_bytes_as('fulltext')
2307
2360
            self.assertEqual(actual_text, expected_text)
2308
2361
 
2309
2362
    def test_insert_record_stream_fulltexts(self):
2317
2370
            source_transport)
2318
2371
        self.get_diamond_files(source, trailing_eol=False)
2319
2372
        stream = source.get_record_stream(source.keys(), 'topological',
2320
 
            False)
 
2373
                                          False)
2321
2374
        files.insert_record_stream(stream)
2322
2375
        self.assertIdenticalVersionedFile(source, files)
2323
2376
 
2332
2385
            source_transport)
2333
2386
        self.get_diamond_files(source, trailing_eol=False)
2334
2387
        stream = source.get_record_stream(source.keys(), 'topological',
2335
 
            False)
 
2388
                                          False)
2336
2389
        files.insert_record_stream(stream)
2337
2390
        self.assertIdenticalVersionedFile(source, files)
2338
2391
 
2345
2398
        source = make_file_factory(True, mapper)(source_transport)
2346
2399
        self.get_diamond_files(source)
2347
2400
        stream = source.get_record_stream(source.keys(), 'topological',
2348
 
            False)
 
2401
                                          False)
2349
2402
        files.insert_record_stream(stream)
2350
2403
        self.assertIdenticalVersionedFile(source, files)
2351
2404
 
2358
2411
        source = make_file_factory(True, mapper)(source_transport)
2359
2412
        self.get_diamond_files(source, trailing_eol=False)
2360
2413
        stream = source.get_record_stream(source.keys(), 'topological',
2361
 
            False)
 
2414
                                          False)
2362
2415
        files.insert_record_stream(stream)
2363
2416
        self.assertIdenticalVersionedFile(source, files)
2364
2417
 
2371
2424
        source = make_file_factory(False, mapper)(source_transport)
2372
2425
        self.get_diamond_files(source)
2373
2426
        stream = source.get_record_stream(source.keys(), 'topological',
2374
 
            False)
 
2427
                                          False)
2375
2428
        files.insert_record_stream(stream)
2376
2429
        self.assertIdenticalVersionedFile(source, files)
2377
2430
 
2384
2437
        source = make_file_factory(False, mapper)(source_transport)
2385
2438
        self.get_diamond_files(source, trailing_eol=False)
2386
2439
        stream = source.get_record_stream(source.keys(), 'topological',
2387
 
            False)
 
2440
                                          False)
2388
2441
        files.insert_record_stream(stream)
2389
2442
        self.assertIdenticalVersionedFile(source, files)
2390
2443
 
2396
2449
        # insert some keys into f.
2397
2450
        self.get_diamond_files(files, left_only=True)
2398
2451
        stream = source.get_record_stream(source.keys(), 'topological',
2399
 
            False)
 
2452
                                          False)
2400
2453
        files.insert_record_stream(stream)
2401
2454
        self.assertIdenticalVersionedFile(source, files)
2402
2455
 
2404
2457
        """Inserting a stream with absent keys should raise an error."""
2405
2458
        files = self.get_versionedfiles()
2406
2459
        source = self.get_versionedfiles('source')
2407
 
        stream = source.get_record_stream([('missing',) * self.key_length],
2408
 
            'topological', False)
 
2460
        stream = source.get_record_stream([(b'missing',) * self.key_length],
 
2461
                                          'topological', False)
2409
2462
        self.assertRaises(errors.RevisionNotPresent, files.insert_record_stream,
2410
 
            stream)
 
2463
                          stream)
2411
2464
 
2412
2465
    def test_insert_record_stream_out_of_order(self):
2413
2466
        """An out of order stream can either error or work."""
2415
2468
        source = self.get_versionedfiles('source')
2416
2469
        self.get_diamond_files(source)
2417
2470
        if self.key_length == 1:
2418
 
            origin_keys = [('origin',)]
2419
 
            end_keys = [('merged',), ('left',)]
2420
 
            start_keys = [('right',), ('base',)]
 
2471
            origin_keys = [(b'origin',)]
 
2472
            end_keys = [(b'merged',), (b'left',)]
 
2473
            start_keys = [(b'right',), (b'base',)]
2421
2474
        else:
2422
 
            origin_keys = [('FileA', 'origin'), ('FileB', 'origin')]
2423
 
            end_keys = [('FileA', 'merged',), ('FileA', 'left',),
2424
 
                ('FileB', 'merged',), ('FileB', 'left',)]
2425
 
            start_keys = [('FileA', 'right',), ('FileA', 'base',),
2426
 
                ('FileB', 'right',), ('FileB', 'base',)]
2427
 
        origin_entries = source.get_record_stream(origin_keys, 'unordered', False)
 
2475
            origin_keys = [(b'FileA', b'origin'), (b'FileB', b'origin')]
 
2476
            end_keys = [(b'FileA', b'merged',), (b'FileA', b'left',),
 
2477
                        (b'FileB', b'merged',), (b'FileB', b'left',)]
 
2478
            start_keys = [(b'FileA', b'right',), (b'FileA', b'base',),
 
2479
                          (b'FileB', b'right',), (b'FileB', b'base',)]
 
2480
        origin_entries = source.get_record_stream(
 
2481
            origin_keys, 'unordered', False)
2428
2482
        end_entries = source.get_record_stream(end_keys, 'topological', False)
2429
 
        start_entries = source.get_record_stream(start_keys, 'topological', False)
 
2483
        start_entries = source.get_record_stream(
 
2484
            start_keys, 'topological', False)
2430
2485
        entries = itertools.chain(origin_entries, end_entries, start_entries)
2431
2486
        try:
2432
2487
            files.insert_record_stream(entries)
2445
2500
        source = self.get_versionedfiles('source')
2446
2501
        parents = ()
2447
2502
        keys = []
2448
 
        content = [('same same %d\n' % n) for n in range(500)]
2449
 
        for letter in 'abcdefghijklmnopqrstuvwxyz':
2450
 
            key = ('key-' + letter,)
 
2503
        content = [(b'same same %d\n' % n) for n in range(500)]
 
2504
        letters = b'abcdefghijklmnopqrstuvwxyz'
 
2505
        for i in range(len(letters)):
 
2506
            letter = letters[i:i + 1]
 
2507
            key = (b'key-' + letter,)
2451
2508
            if self.key_length == 2:
2452
 
                key = ('prefix',) + key
2453
 
            content.append('content for ' + letter + '\n')
 
2509
                key = (b'prefix',) + key
 
2510
            content.append(b'content for ' + letter + b'\n')
2454
2511
            source.add_lines(key, parents, content)
2455
2512
            keys.append(key)
2456
2513
            parents = (key,)
2482
2539
        source_transport.mkdir('.')
2483
2540
        source = make_file_factory(False, mapper)(source_transport)
2484
2541
        get_diamond_files(source, self.key_length, trailing_eol=True,
2485
 
            nograph=False, left_only=False)
 
2542
                          nograph=False, left_only=False)
2486
2543
        return source
2487
2544
 
2488
2545
    def test_insert_record_stream_delta_missing_basis_no_corruption(self):
2491
2548
        not added.
2492
2549
        """
2493
2550
        source = self.get_knit_delta_source()
2494
 
        keys = [self.get_simple_key('origin'), self.get_simple_key('merged')]
 
2551
        keys = [self.get_simple_key(b'origin'), self.get_simple_key(b'merged')]
2495
2552
        entries = source.get_record_stream(keys, 'unordered', False)
2496
2553
        files = self.get_versionedfiles()
2497
2554
        if self.support_partial_insertion:
2498
2555
            self.assertEqual([],
2499
 
                list(files.get_missing_compression_parent_keys()))
 
2556
                             list(files.get_missing_compression_parent_keys()))
2500
2557
            files.insert_record_stream(entries)
2501
2558
            missing_bases = files.get_missing_compression_parent_keys()
2502
 
            self.assertEqual({self.get_simple_key('left')},
2503
 
                set(missing_bases))
 
2559
            self.assertEqual({self.get_simple_key(b'left')},
 
2560
                             set(missing_bases))
2504
2561
            self.assertEqual(set(keys), set(files.get_parent_map(keys)))
2505
2562
        else:
2506
2563
            self.assertRaises(
2518
2575
            raise TestNotApplicable(
2519
2576
                'versioned file scenario does not support partial insertion')
2520
2577
        source = self.get_knit_delta_source()
2521
 
        entries = source.get_record_stream([self.get_simple_key('origin'),
2522
 
            self.get_simple_key('merged')], 'unordered', False)
 
2578
        entries = source.get_record_stream([self.get_simple_key(b'origin'),
 
2579
                                            self.get_simple_key(b'merged')], 'unordered', False)
2523
2580
        files = self.get_versionedfiles()
2524
2581
        files.insert_record_stream(entries)
2525
2582
        missing_bases = files.get_missing_compression_parent_keys()
2526
 
        self.assertEqual({self.get_simple_key('left')},
2527
 
            set(missing_bases))
 
2583
        self.assertEqual({self.get_simple_key(b'left')},
 
2584
                         set(missing_bases))
2528
2585
        # 'merged' is inserted (although a commit of a write group involving
2529
2586
        # this versionedfiles would fail).
2530
 
        merged_key = self.get_simple_key('merged')
 
2587
        merged_key = self.get_simple_key(b'merged')
2531
2588
        self.assertEqual(
2532
 
            [merged_key], files.get_parent_map([merged_key]).keys())
 
2589
            [merged_key], list(files.get_parent_map([merged_key]).keys()))
2533
2590
        # Add the full delta closure of the missing records
2534
2591
        missing_entries = source.get_record_stream(
2535
2592
            missing_bases, 'unordered', True)
2537
2594
        # Now 'merged' is fully inserted (and a commit would succeed).
2538
2595
        self.assertEqual([], list(files.get_missing_compression_parent_keys()))
2539
2596
        self.assertEqual(
2540
 
            [merged_key], files.get_parent_map([merged_key]).keys())
 
2597
            [merged_key], list(files.get_parent_map([merged_key]).keys()))
2541
2598
        files.check()
2542
2599
 
2543
2600
    def test_iter_lines_added_or_present_in_keys(self):
2557
2614
 
2558
2615
        files = self.get_versionedfiles()
2559
2616
        # add a base to get included
2560
 
        files.add_lines(self.get_simple_key('base'), (), ['base\n'])
 
2617
        files.add_lines(self.get_simple_key(b'base'), (), [b'base\n'])
2561
2618
        # add a ancestor to be included on one side
2562
 
        files.add_lines(self.get_simple_key('lancestor'), (), ['lancestor\n'])
 
2619
        files.add_lines(self.get_simple_key(
 
2620
            b'lancestor'), (), [b'lancestor\n'])
2563
2621
        # add a ancestor to be included on the other side
2564
 
        files.add_lines(self.get_simple_key('rancestor'),
2565
 
            self.get_parents([self.get_simple_key('base')]), ['rancestor\n'])
 
2622
        files.add_lines(self.get_simple_key(b'rancestor'),
 
2623
                        self.get_parents([self.get_simple_key(b'base')]), [b'rancestor\n'])
2566
2624
        # add a child of rancestor with no eofile-nl
2567
 
        files.add_lines(self.get_simple_key('child'),
2568
 
            self.get_parents([self.get_simple_key('rancestor')]),
2569
 
            ['base\n', 'child\n'])
 
2625
        files.add_lines(self.get_simple_key(b'child'),
 
2626
                        self.get_parents([self.get_simple_key(b'rancestor')]),
 
2627
                        [b'base\n', b'child\n'])
2570
2628
        # add a child of lancestor and base to join the two roots
2571
 
        files.add_lines(self.get_simple_key('otherchild'),
2572
 
            self.get_parents([self.get_simple_key('lancestor'),
2573
 
                self.get_simple_key('base')]),
2574
 
            ['base\n', 'lancestor\n', 'otherchild\n'])
 
2629
        files.add_lines(self.get_simple_key(b'otherchild'),
 
2630
                        self.get_parents([self.get_simple_key(b'lancestor'),
 
2631
                                          self.get_simple_key(b'base')]),
 
2632
                        [b'base\n', b'lancestor\n', b'otherchild\n'])
 
2633
 
2575
2634
        def iter_with_keys(keys, expected):
2576
2635
            # now we need to see what lines are returned, and how often.
2577
2636
            lines = {}
2578
2637
            progress = InstrumentedProgress()
2579
2638
            # iterate over the lines
2580
2639
            for line in files.iter_lines_added_or_present_in_keys(keys,
2581
 
                pb=progress):
 
2640
                                                                  pb=progress):
2582
2641
                lines.setdefault(line, 0)
2583
2642
                lines[line] += 1
2584
 
            if []!= progress.updates:
 
2643
            if [] != progress.updates:
2585
2644
                self.assertEqual(expected, progress.updates)
2586
2645
            return lines
2587
2646
        lines = iter_with_keys(
2588
 
            [self.get_simple_key('child'), self.get_simple_key('otherchild')],
 
2647
            [self.get_simple_key(b'child'),
 
2648
             self.get_simple_key(b'otherchild')],
2589
2649
            [('Walking content', 0, 2),
2590
2650
             ('Walking content', 1, 2),
2591
2651
             ('Walking content', 2, 2)])
2592
2652
        # we must see child and otherchild
2593
 
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
 
2653
        self.assertTrue(lines[(b'child\n', self.get_simple_key(b'child'))] > 0)
2594
2654
        self.assertTrue(
2595
 
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
 
2655
            lines[(b'otherchild\n', self.get_simple_key(b'otherchild'))] > 0)
2596
2656
        # we dont care if we got more than that.
2597
2657
 
2598
2658
        # test all lines
2599
2659
        lines = iter_with_keys(files.keys(),
2600
 
            [('Walking content', 0, 5),
2601
 
             ('Walking content', 1, 5),
2602
 
             ('Walking content', 2, 5),
2603
 
             ('Walking content', 3, 5),
2604
 
             ('Walking content', 4, 5),
2605
 
             ('Walking content', 5, 5)])
 
2660
                               [('Walking content', 0, 5),
 
2661
                                ('Walking content', 1, 5),
 
2662
                                ('Walking content', 2, 5),
 
2663
                                ('Walking content', 3, 5),
 
2664
                                ('Walking content', 4, 5),
 
2665
                                ('Walking content', 5, 5)])
2606
2666
        # all lines must be seen at least once
2607
 
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2608
 
        self.assertTrue(
2609
 
            lines[('lancestor\n', self.get_simple_key('lancestor'))] > 0)
2610
 
        self.assertTrue(
2611
 
            lines[('rancestor\n', self.get_simple_key('rancestor'))] > 0)
2612
 
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2613
 
        self.assertTrue(
2614
 
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
 
2667
        self.assertTrue(lines[(b'base\n', self.get_simple_key(b'base'))] > 0)
 
2668
        self.assertTrue(
 
2669
            lines[(b'lancestor\n', self.get_simple_key(b'lancestor'))] > 0)
 
2670
        self.assertTrue(
 
2671
            lines[(b'rancestor\n', self.get_simple_key(b'rancestor'))] > 0)
 
2672
        self.assertTrue(lines[(b'child\n', self.get_simple_key(b'child'))] > 0)
 
2673
        self.assertTrue(
 
2674
            lines[(b'otherchild\n', self.get_simple_key(b'otherchild'))] > 0)
2615
2675
 
2616
2676
    def test_make_mpdiffs(self):
2617
2677
        from breezy import multiparent
2619
2679
        # add texts that should trip the knit maximum delta chain threshold
2620
2680
        # as well as doing parallel chains of data in knits.
2621
2681
        # this is done by two chains of 25 insertions
2622
 
        files.add_lines(self.get_simple_key('base'), [], ['line\n'])
2623
 
        files.add_lines(self.get_simple_key('noeol'),
2624
 
            self.get_parents([self.get_simple_key('base')]), ['line'])
 
2682
        files.add_lines(self.get_simple_key(b'base'), [], [b'line\n'])
 
2683
        files.add_lines(self.get_simple_key(b'noeol'),
 
2684
                        self.get_parents([self.get_simple_key(b'base')]), [b'line'])
2625
2685
        # detailed eol tests:
2626
2686
        # shared last line with parent no-eol
2627
 
        files.add_lines(self.get_simple_key('noeolsecond'),
2628
 
            self.get_parents([self.get_simple_key('noeol')]),
2629
 
                ['line\n', 'line'])
 
2687
        files.add_lines(self.get_simple_key(b'noeolsecond'),
 
2688
                        self.get_parents([self.get_simple_key(b'noeol')]),
 
2689
                        [b'line\n', b'line'])
2630
2690
        # differing last line with parent, both no-eol
2631
 
        files.add_lines(self.get_simple_key('noeolnotshared'),
2632
 
            self.get_parents([self.get_simple_key('noeolsecond')]),
2633
 
                ['line\n', 'phone'])
 
2691
        files.add_lines(self.get_simple_key(b'noeolnotshared'),
 
2692
                        self.get_parents(
 
2693
                            [self.get_simple_key(b'noeolsecond')]),
 
2694
                        [b'line\n', b'phone'])
2634
2695
        # add eol following a noneol parent, change content
2635
 
        files.add_lines(self.get_simple_key('eol'),
2636
 
            self.get_parents([self.get_simple_key('noeol')]), ['phone\n'])
 
2696
        files.add_lines(self.get_simple_key(b'eol'),
 
2697
                        self.get_parents([self.get_simple_key(b'noeol')]), [b'phone\n'])
2637
2698
        # add eol following a noneol parent, no change content
2638
 
        files.add_lines(self.get_simple_key('eolline'),
2639
 
            self.get_parents([self.get_simple_key('noeol')]), ['line\n'])
 
2699
        files.add_lines(self.get_simple_key(b'eolline'),
 
2700
                        self.get_parents([self.get_simple_key(b'noeol')]), [b'line\n'])
2640
2701
        # noeol with no parents:
2641
 
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
 
2702
        files.add_lines(self.get_simple_key(b'noeolbase'), [], [b'line'])
2642
2703
        # noeol preceeding its leftmost parent in the output:
2643
2704
        # this is done by making it a merge of two parents with no common
2644
2705
        # anestry: noeolbase and noeol with the
2645
2706
        # later-inserted parent the leftmost.
2646
 
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2647
 
            self.get_parents([self.get_simple_key('noeolbase'),
2648
 
                self.get_simple_key('noeol')]),
2649
 
            ['line'])
 
2707
        files.add_lines(self.get_simple_key(b'eolbeforefirstparent'),
 
2708
                        self.get_parents([self.get_simple_key(b'noeolbase'),
 
2709
                                          self.get_simple_key(b'noeol')]),
 
2710
                        [b'line'])
2650
2711
        # two identical eol texts
2651
 
        files.add_lines(self.get_simple_key('noeoldup'),
2652
 
            self.get_parents([self.get_simple_key('noeol')]), ['line'])
2653
 
        next_parent = self.get_simple_key('base')
2654
 
        text_name = 'chain1-'
2655
 
        text = ['line\n']
2656
 
        sha1s = {0: 'da6d3141cb4a5e6f464bf6e0518042ddc7bfd079',
2657
 
                 1: '45e21ea146a81ea44a821737acdb4f9791c8abe7',
2658
 
                 2: 'e1f11570edf3e2a070052366c582837a4fe4e9fa',
2659
 
                 3: '26b4b8626da827088c514b8f9bbe4ebf181edda1',
2660
 
                 4: 'e28a5510be25ba84d31121cff00956f9970ae6f6',
2661
 
                 5: 'd63ec0ce22e11dcf65a931b69255d3ac747a318d',
2662
 
                 6: '2c2888d288cb5e1d98009d822fedfe6019c6a4ea',
2663
 
                 7: '95c14da9cafbf828e3e74a6f016d87926ba234ab',
2664
 
                 8: '779e9a0b28f9f832528d4b21e17e168c67697272',
2665
 
                 9: '1f8ff4e5c6ff78ac106fcfe6b1e8cb8740ff9a8f',
2666
 
                 10: '131a2ae712cf51ed62f143e3fbac3d4206c25a05',
2667
 
                 11: 'c5a9d6f520d2515e1ec401a8f8a67e6c3c89f199',
2668
 
                 12: '31a2286267f24d8bedaa43355f8ad7129509ea85',
2669
 
                 13: 'dc2a7fe80e8ec5cae920973973a8ee28b2da5e0a',
2670
 
                 14: '2c4b1736566b8ca6051e668de68650686a3922f2',
2671
 
                 15: '5912e4ecd9b0c07be4d013e7e2bdcf9323276cde',
2672
 
                 16: 'b0d2e18d3559a00580f6b49804c23fea500feab3',
2673
 
                 17: '8e1d43ad72f7562d7cb8f57ee584e20eb1a69fc7',
2674
 
                 18: '5cf64a3459ae28efa60239e44b20312d25b253f3',
2675
 
                 19: '1ebed371807ba5935958ad0884595126e8c4e823',
2676
 
                 20: '2aa62a8b06fb3b3b892a3292a068ade69d5ee0d3',
2677
 
                 21: '01edc447978004f6e4e962b417a4ae1955b6fe5d',
2678
 
                 22: 'd8d8dc49c4bf0bab401e0298bb5ad827768618bb',
2679
 
                 23: 'c21f62b1c482862983a8ffb2b0c64b3451876e3f',
2680
 
                 24: 'c0593fe795e00dff6b3c0fe857a074364d5f04fc',
2681
 
                 25: 'dd1a1cf2ba9cc225c3aff729953e6364bf1d1855',
 
2712
        files.add_lines(self.get_simple_key(b'noeoldup'),
 
2713
                        self.get_parents([self.get_simple_key(b'noeol')]), [b'line'])
 
2714
        next_parent = self.get_simple_key(b'base')
 
2715
        text_name = b'chain1-'
 
2716
        text = [b'line\n']
 
2717
        sha1s = {0: b'da6d3141cb4a5e6f464bf6e0518042ddc7bfd079',
 
2718
                 1: b'45e21ea146a81ea44a821737acdb4f9791c8abe7',
 
2719
                 2: b'e1f11570edf3e2a070052366c582837a4fe4e9fa',
 
2720
                 3: b'26b4b8626da827088c514b8f9bbe4ebf181edda1',
 
2721
                 4: b'e28a5510be25ba84d31121cff00956f9970ae6f6',
 
2722
                 5: b'd63ec0ce22e11dcf65a931b69255d3ac747a318d',
 
2723
                 6: b'2c2888d288cb5e1d98009d822fedfe6019c6a4ea',
 
2724
                 7: b'95c14da9cafbf828e3e74a6f016d87926ba234ab',
 
2725
                 8: b'779e9a0b28f9f832528d4b21e17e168c67697272',
 
2726
                 9: b'1f8ff4e5c6ff78ac106fcfe6b1e8cb8740ff9a8f',
 
2727
                 10: b'131a2ae712cf51ed62f143e3fbac3d4206c25a05',
 
2728
                 11: b'c5a9d6f520d2515e1ec401a8f8a67e6c3c89f199',
 
2729
                 12: b'31a2286267f24d8bedaa43355f8ad7129509ea85',
 
2730
                 13: b'dc2a7fe80e8ec5cae920973973a8ee28b2da5e0a',
 
2731
                 14: b'2c4b1736566b8ca6051e668de68650686a3922f2',
 
2732
                 15: b'5912e4ecd9b0c07be4d013e7e2bdcf9323276cde',
 
2733
                 16: b'b0d2e18d3559a00580f6b49804c23fea500feab3',
 
2734
                 17: b'8e1d43ad72f7562d7cb8f57ee584e20eb1a69fc7',
 
2735
                 18: b'5cf64a3459ae28efa60239e44b20312d25b253f3',
 
2736
                 19: b'1ebed371807ba5935958ad0884595126e8c4e823',
 
2737
                 20: b'2aa62a8b06fb3b3b892a3292a068ade69d5ee0d3',
 
2738
                 21: b'01edc447978004f6e4e962b417a4ae1955b6fe5d',
 
2739
                 22: b'd8d8dc49c4bf0bab401e0298bb5ad827768618bb',
 
2740
                 23: b'c21f62b1c482862983a8ffb2b0c64b3451876e3f',
 
2741
                 24: b'c0593fe795e00dff6b3c0fe857a074364d5f04fc',
 
2742
                 25: b'dd1a1cf2ba9cc225c3aff729953e6364bf1d1855',
2682
2743
                 }
2683
2744
        for depth in range(26):
2684
 
            new_version = self.get_simple_key(text_name + '%s' % depth)
2685
 
            text = text + ['line\n']
 
2745
            new_version = self.get_simple_key(text_name + b'%d' % depth)
 
2746
            text = text + [b'line\n']
2686
2747
            files.add_lines(new_version, self.get_parents([next_parent]), text)
2687
2748
            next_parent = new_version
2688
 
        next_parent = self.get_simple_key('base')
2689
 
        text_name = 'chain2-'
2690
 
        text = ['line\n']
 
2749
        next_parent = self.get_simple_key(b'base')
 
2750
        text_name = b'chain2-'
 
2751
        text = [b'line\n']
2691
2752
        for depth in range(26):
2692
 
            new_version = self.get_simple_key(text_name + '%s' % depth)
2693
 
            text = text + ['line\n']
 
2753
            new_version = self.get_simple_key(text_name + b'%d' % depth)
 
2754
            text = text + [b'line\n']
2694
2755
            files.add_lines(new_version, self.get_parents([next_parent]), text)
2695
2756
            next_parent = new_version
2696
2757
        target = self.get_versionedfiles('target')
2700
2761
            target.add_mpdiffs(
2701
2762
                [(key, parents, files.get_sha1s([key])[key], mpdiff)])
2702
2763
            self.assertEqualDiff(
2703
 
                files.get_record_stream([key], 'unordered',
2704
 
                    True).next().get_bytes_as('fulltext'),
2705
 
                target.get_record_stream([key], 'unordered',
2706
 
                    True).next().get_bytes_as('fulltext')
 
2764
                next(files.get_record_stream([key], 'unordered',
 
2765
                                             True)).get_bytes_as('fulltext'),
 
2766
                next(target.get_record_stream([key], 'unordered',
 
2767
                                              True)).get_bytes_as('fulltext')
2707
2768
                )
2708
2769
 
2709
2770
    def test_keys(self):
2712
2773
        files = self.get_versionedfiles()
2713
2774
        self.assertEqual(set(), set(files.keys()))
2714
2775
        if self.key_length == 1:
2715
 
            key = ('foo',)
 
2776
            key = (b'foo',)
2716
2777
        else:
2717
 
            key = ('foo', 'bar',)
 
2778
            key = (b'foo', b'bar',)
2718
2779
        files.add_lines(key, (), [])
2719
2780
        self.assertEqual({key}, set(files.keys()))
2720
2781
 
2738
2799
 
2739
2800
    def test_add_lines(self):
2740
2801
        self.assertRaises(NotImplementedError,
2741
 
                self.texts.add_lines, "foo", [], [])
 
2802
                          self.texts.add_lines, b"foo", [], [])
2742
2803
 
2743
2804
    def test_add_mpdiffs(self):
2744
2805
        self.assertRaises(NotImplementedError,
2745
 
                self.texts.add_mpdiffs, [])
 
2806
                          self.texts.add_mpdiffs, [])
2746
2807
 
2747
2808
    def test_check_noerrors(self):
2748
2809
        self.texts.check()
2752
2813
                          [])
2753
2814
 
2754
2815
    def test_get_sha1s_nonexistent(self):
2755
 
        self.assertEqual({}, self.texts.get_sha1s([("NONEXISTENT",)]))
 
2816
        self.assertEqual({}, self.texts.get_sha1s([(b"NONEXISTENT",)]))
2756
2817
 
2757
2818
    def test_get_sha1s(self):
2758
 
        self._lines["key"] = ["dataline1", "dataline2"]
2759
 
        self.assertEqual({("key",): osutils.sha_strings(self._lines["key"])},
2760
 
                           self.texts.get_sha1s([("key",)]))
 
2819
        self._lines[b"key"] = [b"dataline1", b"dataline2"]
 
2820
        self.assertEqual({(b"key",): osutils.sha_strings(self._lines[b"key"])},
 
2821
                         self.texts.get_sha1s([(b"key",)]))
2761
2822
 
2762
2823
    def test_get_parent_map(self):
2763
 
        self._parent_map = {"G": ("A", "B")}
2764
 
        self.assertEqual({("G",): (("A",), ("B",))},
2765
 
                          self.texts.get_parent_map([("G",), ("L",)]))
 
2824
        self._parent_map = {b"G": (b"A", b"B")}
 
2825
        self.assertEqual({(b"G",): ((b"A",), (b"B",))},
 
2826
                         self.texts.get_parent_map([(b"G",), (b"L",)]))
2766
2827
 
2767
2828
    def test_get_record_stream(self):
2768
 
        self._lines["A"] = ["FOO", "BAR"]
2769
 
        it = self.texts.get_record_stream([("A",)], "unordered", True)
 
2829
        self._lines[b"A"] = [b"FOO", b"BAR"]
 
2830
        it = self.texts.get_record_stream([(b"A",)], "unordered", True)
2770
2831
        record = next(it)
2771
2832
        self.assertEqual("chunked", record.storage_kind)
2772
 
        self.assertEqual("FOOBAR", record.get_bytes_as("fulltext"))
2773
 
        self.assertEqual(["FOO", "BAR"], record.get_bytes_as("chunked"))
 
2833
        self.assertEqual(b"FOOBAR", record.get_bytes_as("fulltext"))
 
2834
        self.assertEqual([b"FOO", b"BAR"], record.get_bytes_as("chunked"))
2774
2835
 
2775
2836
    def test_get_record_stream_absent(self):
2776
 
        it = self.texts.get_record_stream([("A",)], "unordered", True)
 
2837
        it = self.texts.get_record_stream([(b"A",)], "unordered", True)
2777
2838
        record = next(it)
2778
2839
        self.assertEqual("absent", record.storage_kind)
2779
2840
 
2780
2841
    def test_iter_lines_added_or_present_in_keys(self):
2781
 
        self._lines["A"] = ["FOO", "BAR"]
2782
 
        self._lines["B"] = ["HEY"]
2783
 
        self._lines["C"] = ["Alberta"]
2784
 
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2785
 
        self.assertEqual(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2786
 
            sorted(list(it)))
 
2842
        self._lines[b"A"] = [b"FOO", b"BAR"]
 
2843
        self._lines[b"B"] = [b"HEY"]
 
2844
        self._lines[b"C"] = [b"Alberta"]
 
2845
        it = self.texts.iter_lines_added_or_present_in_keys([(b"A",), (b"B",)])
 
2846
        self.assertEqual(sorted([(b"FOO", b"A"), (b"BAR", b"A"), (b"HEY", b"B")]),
 
2847
                         sorted(list(it)))
2787
2848
 
2788
2849
 
2789
2850
class TestOrderingVersionedFilesDecorator(TestCaseWithMemoryTransport):
2792
2853
        builder = self.make_branch_builder('test')
2793
2854
        builder.start_series()
2794
2855
        builder.build_snapshot(None, [
2795
 
            ('add', ('', 'TREE_ROOT', 'directory', None))],
2796
 
            revision_id='A')
2797
 
        builder.build_snapshot(['A'], [], revision_id='B')
2798
 
        builder.build_snapshot(['B'], [], revision_id='C')
2799
 
        builder.build_snapshot(['C'], [], revision_id='D')
 
2856
            ('add', ('', b'TREE_ROOT', 'directory', None))],
 
2857
            revision_id=b'A')
 
2858
        builder.build_snapshot([b'A'], [], revision_id=b'B')
 
2859
        builder.build_snapshot([b'B'], [], revision_id=b'C')
 
2860
        builder.build_snapshot([b'C'], [], revision_id=b'D')
2800
2861
        builder.finish_series()
2801
2862
        b = builder.get_branch()
2802
2863
        b.lock_read()
2809
2870
        self.assertEqual([], vf.calls)
2810
2871
 
2811
2872
    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',)]
 
2873
        vf = self.get_ordering_vf(
 
2874
            {(b'A',): 3, (b'B',): 2, (b'C',): 4, (b'D',): 1})
 
2875
        request_keys = [(b'B',), (b'C',), (b'D',), (b'A',)]
2814
2876
        keys = [r.key for r in vf.get_record_stream(request_keys,
2815
 
                                    'topological', False)]
 
2877
                                                    'topological', False)]
2816
2878
        # We should have gotten the keys in topological order
2817
 
        self.assertEqual([('A',), ('B',), ('C',), ('D',)], keys)
 
2879
        self.assertEqual([(b'A',), (b'B',), (b'C',), (b'D',)], keys)
2818
2880
        # And recorded that the request was made
2819
2881
        self.assertEqual([('get_record_stream', request_keys, 'topological',
2820
2882
                           False)], vf.calls)
2821
2883
 
2822
2884
    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',)]
 
2885
        vf = self.get_ordering_vf(
 
2886
            {(b'A',): 3, (b'B',): 2, (b'C',): 4, (b'D',): 1})
 
2887
        request_keys = [(b'B',), (b'C',), (b'D',), (b'A',)]
2825
2888
        keys = [r.key for r in vf.get_record_stream(request_keys,
2826
 
                                   'unordered', False)]
 
2889
                                                    'unordered', False)]
2827
2890
        # They should be returned based on their priority
2828
 
        self.assertEqual([('D',), ('B',), ('A',), ('C',)], keys)
 
2891
        self.assertEqual([(b'D',), (b'B',), (b'A',), (b'C',)], keys)
2829
2892
        # And the request recorded
2830
2893
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
2831
2894
                           False)], vf.calls)
2832
2895
 
2833
2896
    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',)]
 
2897
        vf = self.get_ordering_vf({(b'B',): 2, (b'D',): 1})
 
2898
        request_keys = [(b'B',), (b'C',), (b'D',), (b'A',)]
2836
2899
        keys = [r.key for r in vf.get_record_stream(request_keys,
2837
 
                                   'unordered', False)]
 
2900
                                                    'unordered', False)]
2838
2901
        # A and C are not in the map, so they get sorted to the front. A comes
2839
2902
        # before C alphabetically, so it comes back first
2840
 
        self.assertEqual([('A',), ('C',), ('D',), ('B',)], keys)
 
2903
        self.assertEqual([(b'A',), (b'C',), (b'D',), (b'B',)], keys)
2841
2904
        # And the request recorded
2842
2905
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
2843
2906
                           False)], vf.calls)