/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/test_knit.py

  • Committer: Jelmer Vernooij
  • Date: 2019-02-03 01:42:11 UTC
  • mto: This revision was merged to the branch mainline in revision 7267.
  • Revision ID: jelmer@jelmer.uk-20190203014211-poj1fv922sejfsb4
Don't require that short git shas have an even number of characters.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""Tests for Knit data structure"""
18
18
 
19
 
from cStringIO import StringIO
20
 
import difflib
21
19
import gzip
 
20
from io import BytesIO
22
21
import sys
23
22
 
24
 
from bzrlib import (
 
23
from .. import (
25
24
    errors,
26
 
    generate_ids,
27
 
    knit,
28
25
    multiparent,
29
26
    osutils,
 
27
    tests,
 
28
    transport,
 
29
    )
 
30
from ..bzr import (
 
31
    knit,
30
32
    pack,
31
 
    tests,
32
 
    )
33
 
from bzrlib.errors import (
34
 
    RevisionAlreadyPresent,
35
 
    KnitHeaderError,
36
 
    RevisionNotPresent,
37
 
    NoSuchFile,
38
 
    )
39
 
from bzrlib.index import *
40
 
from bzrlib.knit import (
 
33
    )
 
34
from ..bzr.index import *
 
35
from ..bzr.knit import (
41
36
    AnnotatedKnitContent,
42
37
    KnitContent,
43
 
    KnitSequenceMatcher,
 
38
    KnitCorrupt,
 
39
    KnitDataStreamIncompatible,
 
40
    KnitDataStreamUnknown,
 
41
    KnitHeaderError,
 
42
    KnitIndexUnknownMethod,
44
43
    KnitVersionedFiles,
45
44
    PlainKnitContent,
46
45
    _VFContentMapGenerator,
47
 
    _DirectPackAccess,
48
46
    _KndxIndex,
49
47
    _KnitGraphIndex,
50
48
    _KnitKeyAccess,
51
49
    make_file_factory,
52
50
    )
53
 
from bzrlib.repofmt import pack_repo
54
 
from bzrlib.tests import (
55
 
    Feature,
56
 
    KnownFailure,
 
51
from ..patiencediff import PatienceSequenceMatcher
 
52
from ..bzr import (
 
53
    knitpack_repo,
 
54
    pack_repo,
 
55
    )
 
56
from . import (
57
57
    TestCase,
58
58
    TestCaseWithMemoryTransport,
59
59
    TestCaseWithTransport,
60
60
    TestNotApplicable,
61
61
    )
62
 
from bzrlib.transport import get_transport
63
 
from bzrlib.transport.memory import MemoryTransport
64
 
from bzrlib.tuned_gzip import GzipFile
65
 
from bzrlib.versionedfile import (
 
62
from ..bzr.versionedfile import (
66
63
    AbsentContentFactory,
67
64
    ConstantMapper,
68
65
    network_bytes_to_kind_and_offset,
69
66
    RecordingVersionedFilesDecorator,
70
67
    )
71
 
 
72
 
 
73
 
compiled_knit_feature = tests.ModuleAvailableFeature(
74
 
                            'bzrlib._knit_load_data_pyx')
 
68
from . import (
 
69
    features,
 
70
    )
 
71
 
 
72
 
 
73
compiled_knit_feature = features.ModuleAvailableFeature(
 
74
    'breezy.bzr._knit_load_data_pyx')
 
75
 
 
76
 
 
77
class ErrorTests(TestCase):
 
78
 
 
79
    def test_knit_data_stream_incompatible(self):
 
80
        error = KnitDataStreamIncompatible(
 
81
            'stream format', 'target format')
 
82
        self.assertEqual('Cannot insert knit data stream of format '
 
83
                         '"stream format" into knit of format '
 
84
                         '"target format".', str(error))
 
85
 
 
86
    def test_knit_data_stream_unknown(self):
 
87
        error = KnitDataStreamUnknown(
 
88
            'stream format')
 
89
        self.assertEqual('Cannot parse knit data stream of format '
 
90
                         '"stream format".', str(error))
 
91
 
 
92
    def test_knit_header_error(self):
 
93
        error = KnitHeaderError('line foo\n', 'path/to/file')
 
94
        self.assertEqual("Knit header error: 'line foo\\n' unexpected"
 
95
                         " for file \"path/to/file\".", str(error))
 
96
 
 
97
    def test_knit_index_unknown_method(self):
 
98
        error = KnitIndexUnknownMethod('http://host/foo.kndx',
 
99
                                       ['bad', 'no-eol'])
 
100
        self.assertEqual("Knit index http://host/foo.kndx does not have a"
 
101
                         " known method in options: ['bad', 'no-eol']",
 
102
                         str(error))
75
103
 
76
104
 
77
105
class KnitContentTestsMixin(object):
83
111
        content = self._make_content([])
84
112
        self.assertEqual(content.text(), [])
85
113
 
86
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
87
 
        self.assertEqual(content.text(), ["text1", "text2"])
 
114
        content = self._make_content(
 
115
            [(b"origin1", b"text1"), (b"origin2", b"text2")])
 
116
        self.assertEqual(content.text(), [b"text1", b"text2"])
88
117
 
89
118
    def test_copy(self):
90
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
 
119
        content = self._make_content(
 
120
            [(b"origin1", b"text1"), (b"origin2", b"text2")])
91
121
        copy = content.copy()
92
122
        self.assertIsInstance(copy, content.__class__)
93
123
        self.assertEqual(copy.annotate(), content.annotate())
96
126
        """Assert that the derived matching blocks match real output"""
97
127
        source_lines = source.splitlines(True)
98
128
        target_lines = target.splitlines(True)
 
129
 
99
130
        def nl(line):
100
131
            if noeol and not line.endswith('\n'):
101
132
                return line + '\n'
102
133
            else:
103
134
                return line
104
 
        source_content = self._make_content([(None, nl(l)) for l in source_lines])
105
 
        target_content = self._make_content([(None, nl(l)) for l in target_lines])
 
135
        source_content = self._make_content(
 
136
            [(None, nl(l)) for l in source_lines])
 
137
        target_content = self._make_content(
 
138
            [(None, nl(l)) for l in target_lines])
106
139
        line_delta = source_content.line_delta(target_content)
107
140
        delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
108
 
            source_lines, target_lines))
109
 
        matcher = KnitSequenceMatcher(None, source_lines, target_lines)
110
 
        matcher_blocks = list(list(matcher.get_matching_blocks()))
 
141
                                                              source_lines, target_lines))
 
142
        matcher = PatienceSequenceMatcher(None, source_lines, target_lines)
 
143
        matcher_blocks = list(matcher.get_matching_blocks())
111
144
        self.assertEqual(matcher_blocks, delta_blocks)
112
145
 
113
146
    def test_get_line_delta_blocks(self):
192
225
        content = self._make_content([])
193
226
        self.assertEqual(content.annotate(), [])
194
227
 
195
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
 
228
        content = self._make_content(
 
229
            [("origin1", "text1"), ("origin2", "text2")])
196
230
        self.assertEqual(content.annotate(),
197
 
            [("bogus", "text1"), ("bogus", "text2")])
 
231
                         [("bogus", "text1"), ("bogus", "text2")])
198
232
 
199
233
    def test_line_delta(self):
200
234
        content1 = self._make_content([("", "a"), ("", "b")])
201
235
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
202
236
        self.assertEqual(content1.line_delta(content2),
203
 
            [(1, 2, 2, ["a", "c"])])
 
237
                         [(1, 2, 2, ["a", "c"])])
204
238
 
205
239
    def test_line_delta_iter(self):
206
240
        content1 = self._make_content([("", "a"), ("", "b")])
207
241
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
208
242
        it = content1.line_delta_iter(content2)
209
 
        self.assertEqual(it.next(), (1, 2, 2, ["a", "c"]))
210
 
        self.assertRaises(StopIteration, it.next)
 
243
        self.assertEqual(next(it), (1, 2, 2, ["a", "c"]))
 
244
        self.assertRaises(StopIteration, next, it)
211
245
 
212
246
 
213
247
class TestAnnotatedKnitContent(TestCase, KnitContentTestsMixin):
219
253
        content = self._make_content([])
220
254
        self.assertEqual(content.annotate(), [])
221
255
 
222
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
 
256
        content = self._make_content(
 
257
            [(b"origin1", b"text1"), (b"origin2", b"text2")])
223
258
        self.assertEqual(content.annotate(),
224
 
            [("origin1", "text1"), ("origin2", "text2")])
 
259
                         [(b"origin1", b"text1"), (b"origin2", b"text2")])
225
260
 
226
261
    def test_line_delta(self):
227
262
        content1 = self._make_content([("", "a"), ("", "b")])
228
263
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
229
264
        self.assertEqual(content1.line_delta(content2),
230
 
            [(1, 2, 2, [("", "a"), ("", "c")])])
 
265
                         [(1, 2, 2, [("", "a"), ("", "c")])])
231
266
 
232
267
    def test_line_delta_iter(self):
233
268
        content1 = self._make_content([("", "a"), ("", "b")])
234
269
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
235
270
        it = content1.line_delta_iter(content2)
236
 
        self.assertEqual(it.next(), (1, 2, 2, [("", "a"), ("", "c")]))
237
 
        self.assertRaises(StopIteration, it.next)
 
271
        self.assertEqual(next(it), (1, 2, 2, [("", "a"), ("", "c")]))
 
272
        self.assertRaises(StopIteration, next, it)
238
273
 
239
274
 
240
275
class MockTransport(object):
247
282
 
248
283
    def get(self, filename):
249
284
        if self.file_lines is None:
250
 
            raise NoSuchFile(filename)
 
285
            raise errors.NoSuchFile(filename)
251
286
        else:
252
 
            return StringIO("\n".join(self.file_lines))
 
287
            return BytesIO(b"\n".join(self.file_lines))
253
288
 
254
289
    def readv(self, relpath, offsets):
255
290
        fp = self.get(relpath)
287
322
    def test_add_raw_records(self):
288
323
        """Add_raw_records adds records retrievable later."""
289
324
        access = self.get_access()
290
 
        memos = access.add_raw_records([('key', 10)], '1234567890')
291
 
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
 
325
        memos = access.add_raw_records([(b'key', 10)], b'1234567890')
 
326
        self.assertEqual([b'1234567890'], list(access.get_raw_records(memos)))
292
327
 
293
328
    def test_add_several_raw_records(self):
294
329
        """add_raw_records with many records and read some back."""
295
330
        access = self.get_access()
296
 
        memos = access.add_raw_records([('key', 10), ('key2', 2), ('key3', 5)],
297
 
            '12345678901234567')
298
 
        self.assertEqual(['1234567890', '12', '34567'],
299
 
            list(access.get_raw_records(memos)))
300
 
        self.assertEqual(['1234567890'],
301
 
            list(access.get_raw_records(memos[0:1])))
302
 
        self.assertEqual(['12'],
303
 
            list(access.get_raw_records(memos[1:2])))
304
 
        self.assertEqual(['34567'],
305
 
            list(access.get_raw_records(memos[2:3])))
306
 
        self.assertEqual(['1234567890', '34567'],
307
 
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
 
331
        memos = access.add_raw_records([(b'key', 10), (b'key2', 2), (b'key3', 5)],
 
332
                                       b'12345678901234567')
 
333
        self.assertEqual([b'1234567890', b'12', b'34567'],
 
334
                         list(access.get_raw_records(memos)))
 
335
        self.assertEqual([b'1234567890'],
 
336
                         list(access.get_raw_records(memos[0:1])))
 
337
        self.assertEqual([b'12'],
 
338
                         list(access.get_raw_records(memos[1:2])))
 
339
        self.assertEqual([b'34567'],
 
340
                         list(access.get_raw_records(memos[2:3])))
 
341
        self.assertEqual([b'1234567890', b'34567'],
 
342
                         list(access.get_raw_records(memos[0:1] + memos[2:3])))
308
343
 
309
344
 
310
345
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
329
364
 
330
365
    def _get_access(self, packname='packfile', index='FOO'):
331
366
        transport = self.get_transport()
 
367
 
332
368
        def write_data(bytes):
333
369
            transport.append_bytes(packname, bytes)
334
370
        writer = pack.ContainerWriter(write_data)
335
371
        writer.begin()
336
 
        access = _DirectPackAccess({})
 
372
        access = pack_repo._DirectPackAccess({})
337
373
        access.set_writer(writer, index, (transport, packname))
338
374
        return access, writer
339
375
 
341
377
        """Create a pack file with 2 records."""
342
378
        access, writer = self._get_access(packname='packname', index='foo')
343
379
        memos = []
344
 
        memos.extend(access.add_raw_records([('key1', 10)], '1234567890'))
345
 
        memos.extend(access.add_raw_records([('key2', 5)], '12345'))
 
380
        memos.extend(access.add_raw_records([(b'key1', 10)], b'1234567890'))
 
381
        memos.extend(access.add_raw_records([(b'key2', 5)], b'12345'))
346
382
        writer.end()
347
383
        return memos
348
384
 
 
385
    def test_pack_collection_pack_retries(self):
 
386
        """An explicit pack of a pack collection succeeds even when a
 
387
        concurrent pack happens.
 
388
        """
 
389
        builder = self.make_branch_builder('.')
 
390
        builder.start_series()
 
391
        builder.build_snapshot(None, [
 
392
            ('add', ('', b'root-id', 'directory', None)),
 
393
            ('add', ('file', b'file-id', 'file', b'content\nrev 1\n')),
 
394
            ], revision_id=b'rev-1')
 
395
        builder.build_snapshot([b'rev-1'], [
 
396
            ('modify', ('file', b'content\nrev 2\n')),
 
397
            ], revision_id=b'rev-2')
 
398
        builder.build_snapshot([b'rev-2'], [
 
399
            ('modify', ('file', b'content\nrev 3\n')),
 
400
            ], revision_id=b'rev-3')
 
401
        self.addCleanup(builder.finish_series)
 
402
        b = builder.get_branch()
 
403
        self.addCleanup(b.lock_write().unlock)
 
404
        repo = b.repository
 
405
        collection = repo._pack_collection
 
406
        # Concurrently repack the repo.
 
407
        reopened_repo = repo.controldir.open_repository()
 
408
        reopened_repo.pack()
 
409
        # Pack the new pack.
 
410
        collection.pack()
 
411
 
349
412
    def make_vf_for_retrying(self):
350
413
        """Create 3 packs and a reload function.
351
414
 
358
421
        """
359
422
        builder = self.make_branch_builder('.', format="1.9")
360
423
        builder.start_series()
361
 
        builder.build_snapshot('rev-1', None, [
362
 
            ('add', ('', 'root-id', 'directory', None)),
363
 
            ('add', ('file', 'file-id', 'file', 'content\nrev 1\n')),
364
 
            ])
365
 
        builder.build_snapshot('rev-2', ['rev-1'], [
366
 
            ('modify', ('file-id', 'content\nrev 2\n')),
367
 
            ])
368
 
        builder.build_snapshot('rev-3', ['rev-2'], [
369
 
            ('modify', ('file-id', 'content\nrev 3\n')),
370
 
            ])
 
424
        builder.build_snapshot(None, [
 
425
            ('add', ('', b'root-id', 'directory', None)),
 
426
            ('add', ('file', b'file-id', 'file', b'content\nrev 1\n')),
 
427
            ], revision_id=b'rev-1')
 
428
        builder.build_snapshot([b'rev-1'], [
 
429
            ('modify', ('file', b'content\nrev 2\n')),
 
430
            ], revision_id=b'rev-2')
 
431
        builder.build_snapshot([b'rev-2'], [
 
432
            ('modify', ('file', b'content\nrev 3\n')),
 
433
            ], revision_id=b'rev-3')
371
434
        builder.finish_series()
372
435
        b = builder.get_branch()
373
436
        b.lock_write()
378
441
        collection = repo._pack_collection
379
442
        collection.ensure_loaded()
380
443
        orig_packs = collection.packs
381
 
        packer = pack_repo.Packer(collection, orig_packs, '.testpack')
 
444
        packer = knitpack_repo.KnitPacker(collection, orig_packs, '.testpack')
382
445
        new_pack = packer.pack()
383
446
        # forget about the new pack
384
447
        collection.reset()
388
451
        new_index = new_pack.revision_index
389
452
        access_tuple = new_pack.access_tuple()
390
453
        reload_counter = [0, 0, 0]
 
454
 
391
455
        def reload():
392
456
            reload_counter[0] += 1
393
457
            if reload_counter[1] > 0:
410
474
 
411
475
    def make_reload_func(self, return_val=True):
412
476
        reload_called = [0]
 
477
 
413
478
        def reload():
414
479
            reload_called[0] += 1
415
480
            return return_val
420
485
        # populated
421
486
        try:
422
487
            raise _TestException('foobar')
423
 
        except _TestException, e:
 
488
        except _TestException as e:
424
489
            retry_exc = errors.RetryWithNewPacks(None, reload_occurred=False,
425
490
                                                 exc_info=sys.exc_info())
 
491
        # GZ 2010-08-10: Cycle with exc_info affects 3 tests
426
492
        return retry_exc
427
493
 
428
494
    def test_read_from_several_packs(self):
429
495
        access, writer = self._get_access()
430
496
        memos = []
431
 
        memos.extend(access.add_raw_records([('key', 10)], '1234567890'))
 
497
        memos.extend(access.add_raw_records([(b'key', 10)], b'1234567890'))
432
498
        writer.end()
433
499
        access, writer = self._get_access('pack2', 'FOOBAR')
434
 
        memos.extend(access.add_raw_records([('key', 5)], '12345'))
 
500
        memos.extend(access.add_raw_records([(b'key', 5)], b'12345'))
435
501
        writer.end()
436
502
        access, writer = self._get_access('pack3', 'BAZ')
437
 
        memos.extend(access.add_raw_records([('key', 5)], 'alpha'))
 
503
        memos.extend(access.add_raw_records([(b'key', 5)], b'alpha'))
438
504
        writer.end()
439
505
        transport = self.get_transport()
440
 
        access = _DirectPackAccess({"FOO":(transport, 'packfile'),
441
 
            "FOOBAR":(transport, 'pack2'),
442
 
            "BAZ":(transport, 'pack3')})
443
 
        self.assertEqual(['1234567890', '12345', 'alpha'],
444
 
            list(access.get_raw_records(memos)))
445
 
        self.assertEqual(['1234567890'],
446
 
            list(access.get_raw_records(memos[0:1])))
447
 
        self.assertEqual(['12345'],
448
 
            list(access.get_raw_records(memos[1:2])))
449
 
        self.assertEqual(['alpha'],
450
 
            list(access.get_raw_records(memos[2:3])))
451
 
        self.assertEqual(['1234567890', 'alpha'],
452
 
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
 
506
        access = pack_repo._DirectPackAccess({"FOO": (transport, 'packfile'),
 
507
                                              "FOOBAR": (transport, 'pack2'),
 
508
                                              "BAZ": (transport, 'pack3')})
 
509
        self.assertEqual([b'1234567890', b'12345', b'alpha'],
 
510
                         list(access.get_raw_records(memos)))
 
511
        self.assertEqual([b'1234567890'],
 
512
                         list(access.get_raw_records(memos[0:1])))
 
513
        self.assertEqual([b'12345'],
 
514
                         list(access.get_raw_records(memos[1:2])))
 
515
        self.assertEqual([b'alpha'],
 
516
                         list(access.get_raw_records(memos[2:3])))
 
517
        self.assertEqual([b'1234567890', b'alpha'],
 
518
                         list(access.get_raw_records(memos[0:1] + memos[2:3])))
453
519
 
454
520
    def test_set_writer(self):
455
521
        """The writer should be settable post construction."""
456
 
        access = _DirectPackAccess({})
 
522
        access = pack_repo._DirectPackAccess({})
457
523
        transport = self.get_transport()
458
524
        packname = 'packfile'
459
525
        index = 'foo'
 
526
 
460
527
        def write_data(bytes):
461
528
            transport.append_bytes(packname, bytes)
462
529
        writer = pack.ContainerWriter(write_data)
463
530
        writer.begin()
464
531
        access.set_writer(writer, index, (transport, packname))
465
 
        memos = access.add_raw_records([('key', 10)], '1234567890')
 
532
        memos = access.add_raw_records([(b'key', 10)], b'1234567890')
466
533
        writer.end()
467
 
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
 
534
        self.assertEqual([b'1234567890'], list(access.get_raw_records(memos)))
468
535
 
469
536
    def test_missing_index_raises_retry(self):
470
537
        memos = self.make_pack_file()
471
538
        transport = self.get_transport()
472
539
        reload_called, reload_func = self.make_reload_func()
473
540
        # Note that the index key has changed from 'foo' to 'bar'
474
 
        access = _DirectPackAccess({'bar':(transport, 'packname')},
475
 
                                   reload_func=reload_func)
 
541
        access = pack_repo._DirectPackAccess({'bar': (transport, 'packname')},
 
542
                                             reload_func=reload_func)
476
543
        e = self.assertListRaises(errors.RetryWithNewPacks,
477
544
                                  access.get_raw_records, memos)
478
545
        # Because a key was passed in which does not match our index list, we
486
553
        memos = self.make_pack_file()
487
554
        transport = self.get_transport()
488
555
        # Note that the index key has changed from 'foo' to 'bar'
489
 
        access = _DirectPackAccess({'bar':(transport, 'packname')})
 
556
        access = pack_repo._DirectPackAccess({'bar': (transport, 'packname')})
490
557
        e = self.assertListRaises(KeyError, access.get_raw_records, memos)
491
558
 
492
559
    def test_missing_file_raises_retry(self):
494
561
        transport = self.get_transport()
495
562
        reload_called, reload_func = self.make_reload_func()
496
563
        # Note that the 'filename' has been changed to 'different-packname'
497
 
        access = _DirectPackAccess({'foo':(transport, 'different-packname')},
498
 
                                   reload_func=reload_func)
 
564
        access = pack_repo._DirectPackAccess(
 
565
            {'foo': (transport, 'different-packname')},
 
566
            reload_func=reload_func)
499
567
        e = self.assertListRaises(errors.RetryWithNewPacks,
500
568
                                  access.get_raw_records, memos)
501
569
        # The file has gone missing, so we assume we need to reload
509
577
        memos = self.make_pack_file()
510
578
        transport = self.get_transport()
511
579
        # Note that the 'filename' has been changed to 'different-packname'
512
 
        access = _DirectPackAccess({'foo':(transport, 'different-packname')})
 
580
        access = pack_repo._DirectPackAccess(
 
581
            {'foo': (transport, 'different-packname')})
513
582
        e = self.assertListRaises(errors.NoSuchFile,
514
583
                                  access.get_raw_records, memos)
515
584
 
517
586
        memos = self.make_pack_file()
518
587
        transport = self.get_transport()
519
588
        failing_transport = MockReadvFailingTransport(
520
 
                                [transport.get_bytes('packname')])
 
589
            [transport.get_bytes('packname')])
521
590
        reload_called, reload_func = self.make_reload_func()
522
 
        access = _DirectPackAccess({'foo':(failing_transport, 'packname')},
523
 
                                   reload_func=reload_func)
 
591
        access = pack_repo._DirectPackAccess(
 
592
            {'foo': (failing_transport, 'packname')},
 
593
            reload_func=reload_func)
524
594
        # Asking for a single record will not trigger the Mock failure
525
 
        self.assertEqual(['1234567890'],
526
 
            list(access.get_raw_records(memos[:1])))
527
 
        self.assertEqual(['12345'],
528
 
            list(access.get_raw_records(memos[1:2])))
 
595
        self.assertEqual([b'1234567890'],
 
596
                         list(access.get_raw_records(memos[:1])))
 
597
        self.assertEqual([b'12345'],
 
598
                         list(access.get_raw_records(memos[1:2])))
529
599
        # A multiple offset readv() will fail mid-way through
530
600
        e = self.assertListRaises(errors.RetryWithNewPacks,
531
601
                                  access.get_raw_records, memos)
540
610
        memos = self.make_pack_file()
541
611
        transport = self.get_transport()
542
612
        failing_transport = MockReadvFailingTransport(
543
 
                                [transport.get_bytes('packname')])
 
613
            [transport.get_bytes('packname')])
544
614
        reload_called, reload_func = self.make_reload_func()
545
 
        access = _DirectPackAccess({'foo':(failing_transport, 'packname')})
 
615
        access = pack_repo._DirectPackAccess(
 
616
            {'foo': (failing_transport, 'packname')})
546
617
        # Asking for a single record will not trigger the Mock failure
547
 
        self.assertEqual(['1234567890'],
548
 
            list(access.get_raw_records(memos[:1])))
549
 
        self.assertEqual(['12345'],
550
 
            list(access.get_raw_records(memos[1:2])))
 
618
        self.assertEqual([b'1234567890'],
 
619
                         list(access.get_raw_records(memos[:1])))
 
620
        self.assertEqual([b'12345'],
 
621
                         list(access.get_raw_records(memos[1:2])))
551
622
        # A multiple offset readv() will fail mid-way through
552
623
        e = self.assertListRaises(errors.NoSuchFile,
553
624
                                  access.get_raw_records, memos)
554
625
 
555
626
    def test_reload_or_raise_no_reload(self):
556
 
        access = _DirectPackAccess({}, reload_func=None)
 
627
        access = pack_repo._DirectPackAccess({}, reload_func=None)
557
628
        retry_exc = self.make_retry_exception()
558
629
        # Without a reload_func, we will just re-raise the original exception
559
630
        self.assertRaises(_TestException, access.reload_or_raise, retry_exc)
560
631
 
561
632
    def test_reload_or_raise_reload_changed(self):
562
633
        reload_called, reload_func = self.make_reload_func(return_val=True)
563
 
        access = _DirectPackAccess({}, reload_func=reload_func)
 
634
        access = pack_repo._DirectPackAccess({}, reload_func=reload_func)
564
635
        retry_exc = self.make_retry_exception()
565
636
        access.reload_or_raise(retry_exc)
566
637
        self.assertEqual([1], reload_called)
567
 
        retry_exc.reload_occurred=True
 
638
        retry_exc.reload_occurred = True
568
639
        access.reload_or_raise(retry_exc)
569
640
        self.assertEqual([2], reload_called)
570
641
 
571
642
    def test_reload_or_raise_reload_no_change(self):
572
643
        reload_called, reload_func = self.make_reload_func(return_val=False)
573
 
        access = _DirectPackAccess({}, reload_func=reload_func)
 
644
        access = pack_repo._DirectPackAccess({}, reload_func=reload_func)
574
645
        retry_exc = self.make_retry_exception()
575
646
        # If reload_occurred is False, then we consider it an error to have
576
647
        # reload_func() return False (no changes).
577
648
        self.assertRaises(_TestException, access.reload_or_raise, retry_exc)
578
649
        self.assertEqual([1], reload_called)
579
 
        retry_exc.reload_occurred=True
 
650
        retry_exc.reload_occurred = True
580
651
        # If reload_occurred is True, then we assume nothing changed because
581
652
        # it had changed earlier, but didn't change again
582
653
        access.reload_or_raise(retry_exc)
586
657
        vf, reload_counter = self.make_vf_for_retrying()
587
658
        # It is a little bit bogus to annotate the Revision VF, but it works,
588
659
        # as we have ancestry stored there
589
 
        key = ('rev-3',)
 
660
        key = (b'rev-3',)
590
661
        reload_lines = vf.annotate(key)
591
662
        self.assertEqual([1, 1, 0], reload_counter)
592
663
        plain_lines = vf.annotate(key)
593
 
        self.assertEqual([1, 1, 0], reload_counter) # No extra reloading
 
664
        self.assertEqual([1, 1, 0], reload_counter)  # No extra reloading
594
665
        if reload_lines != plain_lines:
595
666
            self.fail('Annotation was not identical with reloading.')
596
667
        # Now delete the packs-in-use, which should trigger another reload, but
597
668
        # this time we just raise an exception because we can't recover
598
 
        for trans, name in vf._access._indices.itervalues():
 
669
        for trans, name in vf._access._indices.values():
599
670
            trans.delete(name)
600
671
        self.assertRaises(errors.NoSuchFile, vf.annotate, key)
601
672
        self.assertEqual([2, 1, 1], reload_counter)
602
673
 
603
674
    def test__get_record_map_retries(self):
604
675
        vf, reload_counter = self.make_vf_for_retrying()
605
 
        keys = [('rev-1',), ('rev-2',), ('rev-3',)]
 
676
        keys = [(b'rev-1',), (b'rev-2',), (b'rev-3',)]
606
677
        records = vf._get_record_map(keys)
607
678
        self.assertEqual(keys, sorted(records.keys()))
608
679
        self.assertEqual([1, 1, 0], reload_counter)
609
680
        # Now delete the packs-in-use, which should trigger another reload, but
610
681
        # this time we just raise an exception because we can't recover
611
 
        for trans, name in vf._access._indices.itervalues():
 
682
        for trans, name in vf._access._indices.values():
612
683
            trans.delete(name)
613
684
        self.assertRaises(errors.NoSuchFile, vf._get_record_map, keys)
614
685
        self.assertEqual([2, 1, 1], reload_counter)
615
686
 
616
687
    def test_get_record_stream_retries(self):
617
688
        vf, reload_counter = self.make_vf_for_retrying()
618
 
        keys = [('rev-1',), ('rev-2',), ('rev-3',)]
 
689
        keys = [(b'rev-1',), (b'rev-2',), (b'rev-3',)]
619
690
        record_stream = vf.get_record_stream(keys, 'topological', False)
620
 
        record = record_stream.next()
621
 
        self.assertEqual(('rev-1',), record.key)
 
691
        record = next(record_stream)
 
692
        self.assertEqual((b'rev-1',), record.key)
622
693
        self.assertEqual([0, 0, 0], reload_counter)
623
 
        record = record_stream.next()
624
 
        self.assertEqual(('rev-2',), record.key)
 
694
        record = next(record_stream)
 
695
        self.assertEqual((b'rev-2',), record.key)
625
696
        self.assertEqual([1, 1, 0], reload_counter)
626
 
        record = record_stream.next()
627
 
        self.assertEqual(('rev-3',), record.key)
 
697
        record = next(record_stream)
 
698
        self.assertEqual((b'rev-3',), record.key)
628
699
        self.assertEqual([1, 1, 0], reload_counter)
629
700
        # Now delete all pack files, and see that we raise the right error
630
 
        for trans, name in vf._access._indices.itervalues():
 
701
        for trans, name in vf._access._indices.values():
631
702
            trans.delete(name)
632
703
        self.assertListRaises(errors.NoSuchFile,
633
 
            vf.get_record_stream, keys, 'topological', False)
 
704
                              vf.get_record_stream, keys, 'topological', False)
634
705
 
635
706
    def test_iter_lines_added_or_present_in_keys_retries(self):
636
707
        vf, reload_counter = self.make_vf_for_retrying()
637
 
        keys = [('rev-1',), ('rev-2',), ('rev-3',)]
 
708
        keys = [(b'rev-1',), (b'rev-2',), (b'rev-3',)]
638
709
        # Unfortunately, iter_lines_added_or_present_in_keys iterates the
639
710
        # result in random order (determined by the iteration order from a
640
711
        # set()), so we don't have any solid way to trigger whether data is
647
718
        self.assertEqual([1, 1, 0], reload_counter)
648
719
        # Now do it again, to make sure the result is equivalent
649
720
        plain_lines = sorted(vf.iter_lines_added_or_present_in_keys(keys))
650
 
        self.assertEqual([1, 1, 0], reload_counter) # No extra reloading
 
721
        self.assertEqual([1, 1, 0], reload_counter)  # No extra reloading
651
722
        self.assertEqual(plain_lines, reload_lines)
652
723
        self.assertEqual(21, len(plain_lines))
653
724
        # Now delete all pack files, and see that we raise the right error
654
 
        for trans, name in vf._access._indices.itervalues():
 
725
        for trans, name in vf._access._indices.values():
655
726
            trans.delete(name)
656
727
        self.assertListRaises(errors.NoSuchFile,
657
 
            vf.iter_lines_added_or_present_in_keys, keys)
 
728
                              vf.iter_lines_added_or_present_in_keys, keys)
658
729
        self.assertEqual([2, 1, 1], reload_counter)
659
730
 
660
731
    def test_get_record_stream_yields_disk_sorted_order(self):
665
736
        self.addCleanup(repo.unlock)
666
737
        repo.start_write_group()
667
738
        vf = repo.texts
668
 
        vf.add_lines(('f-id', 'rev-5'), [('f-id', 'rev-4')], ['lines\n'])
669
 
        vf.add_lines(('f-id', 'rev-1'), [], ['lines\n'])
670
 
        vf.add_lines(('f-id', 'rev-2'), [('f-id', 'rev-1')], ['lines\n'])
 
739
        vf.add_lines((b'f-id', b'rev-5'), [(b'f-id', b'rev-4')], [b'lines\n'])
 
740
        vf.add_lines((b'f-id', b'rev-1'), [], [b'lines\n'])
 
741
        vf.add_lines((b'f-id', b'rev-2'), [(b'f-id', b'rev-1')], [b'lines\n'])
671
742
        repo.commit_write_group()
672
743
        # We inserted them as rev-5, rev-1, rev-2, we should get them back in
673
744
        # the same order
674
 
        stream = vf.get_record_stream([('f-id', 'rev-1'), ('f-id', 'rev-5'),
675
 
                                       ('f-id', 'rev-2')], 'unordered', False)
 
745
        stream = vf.get_record_stream([(b'f-id', b'rev-1'), (b'f-id', b'rev-5'),
 
746
                                       (b'f-id', b'rev-2')], 'unordered', False)
676
747
        keys = [r.key for r in stream]
677
 
        self.assertEqual([('f-id', 'rev-5'), ('f-id', 'rev-1'),
678
 
                          ('f-id', 'rev-2')], keys)
 
748
        self.assertEqual([(b'f-id', b'rev-5'), (b'f-id', b'rev-1'),
 
749
                          (b'f-id', b'rev-2')], keys)
679
750
        repo.start_write_group()
680
 
        vf.add_lines(('f-id', 'rev-4'), [('f-id', 'rev-3')], ['lines\n'])
681
 
        vf.add_lines(('f-id', 'rev-3'), [('f-id', 'rev-2')], ['lines\n'])
682
 
        vf.add_lines(('f-id', 'rev-6'), [('f-id', 'rev-5')], ['lines\n'])
 
751
        vf.add_lines((b'f-id', b'rev-4'), [(b'f-id', b'rev-3')], [b'lines\n'])
 
752
        vf.add_lines((b'f-id', b'rev-3'), [(b'f-id', b'rev-2')], [b'lines\n'])
 
753
        vf.add_lines((b'f-id', b'rev-6'), [(b'f-id', b'rev-5')], [b'lines\n'])
683
754
        repo.commit_write_group()
684
755
        # Request in random order, to make sure the output order isn't based on
685
756
        # the request
686
 
        request_keys = set(('f-id', 'rev-%d' % i) for i in range(1, 7))
 
757
        request_keys = set((b'f-id', b'rev-%d' % i) for i in range(1, 7))
687
758
        stream = vf.get_record_stream(request_keys, 'unordered', False)
688
759
        keys = [r.key for r in stream]
689
760
        # We want to get the keys back in disk order, but it doesn't matter
690
761
        # which pack we read from first. So this can come back in 2 orders
691
 
        alt1 = [('f-id', 'rev-%d' % i) for i in [4, 3, 6, 5, 1, 2]]
692
 
        alt2 = [('f-id', 'rev-%d' % i) for i in [5, 1, 2, 4, 3, 6]]
 
762
        alt1 = [(b'f-id', b'rev-%d' % i) for i in [4, 3, 6, 5, 1, 2]]
 
763
        alt2 = [(b'f-id', b'rev-%d' % i) for i in [5, 1, 2, 4, 3, 6]]
693
764
        if keys != alt1 and keys != alt2:
694
765
            self.fail('Returned key order did not match either expected order.'
695
766
                      ' expected %s or %s, not %s'
699
770
class LowLevelKnitDataTests(TestCase):
700
771
 
701
772
    def create_gz_content(self, text):
702
 
        sio = StringIO()
703
 
        gz_file = gzip.GzipFile(mode='wb', fileobj=sio)
704
 
        gz_file.write(text)
705
 
        gz_file.close()
 
773
        sio = BytesIO()
 
774
        with gzip.GzipFile(mode='wb', fileobj=sio) as gz_file:
 
775
            gz_file.write(text)
706
776
        return sio.getvalue()
707
777
 
708
778
    def make_multiple_records(self):
709
779
        """Create the content for multiple records."""
710
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
 
780
        sha1sum = osutils.sha_string(b'foo\nbar\n')
711
781
        total_txt = []
712
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
713
 
                                        'foo\n'
714
 
                                        'bar\n'
715
 
                                        'end rev-id-1\n'
 
782
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
783
                                        b'foo\n'
 
784
                                        b'bar\n'
 
785
                                        b'end rev-id-1\n'
716
786
                                        % (sha1sum,))
717
787
        record_1 = (0, len(gz_txt), sha1sum)
718
788
        total_txt.append(gz_txt)
719
 
        sha1sum = osutils.sha('baz\n').hexdigest()
720
 
        gz_txt = self.create_gz_content('version rev-id-2 1 %s\n'
721
 
                                        'baz\n'
722
 
                                        'end rev-id-2\n'
 
789
        sha1sum = osutils.sha_string(b'baz\n')
 
790
        gz_txt = self.create_gz_content(b'version rev-id-2 1 %s\n'
 
791
                                        b'baz\n'
 
792
                                        b'end rev-id-2\n'
723
793
                                        % (sha1sum,))
724
794
        record_2 = (record_1[1], len(gz_txt), sha1sum)
725
795
        total_txt.append(gz_txt)
726
796
        return total_txt, record_1, record_2
727
797
 
728
798
    def test_valid_knit_data(self):
729
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
730
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
731
 
                                        'foo\n'
732
 
                                        'bar\n'
733
 
                                        'end rev-id-1\n'
 
799
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
800
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
801
                                        b'foo\n'
 
802
                                        b'bar\n'
 
803
                                        b'end rev-id-1\n'
734
804
                                        % (sha1sum,))
735
805
        transport = MockTransport([gz_txt])
736
806
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
737
807
        knit = KnitVersionedFiles(None, access)
738
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
 
808
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
739
809
 
740
810
        contents = list(knit._read_records_iter(records))
741
 
        self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'],
742
 
            '4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
 
811
        self.assertEqual([((b'rev-id-1',), [b'foo\n', b'bar\n'],
 
812
                           b'4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
743
813
 
744
814
        raw_contents = list(knit._read_records_iter_raw(records))
745
 
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
 
815
        self.assertEqual([((b'rev-id-1',), gz_txt, sha1sum)], raw_contents)
746
816
 
747
817
    def test_multiple_records_valid(self):
748
818
        total_txt, record_1, record_2 = self.make_multiple_records()
749
 
        transport = MockTransport([''.join(total_txt)])
 
819
        transport = MockTransport([b''.join(total_txt)])
750
820
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
751
821
        knit = KnitVersionedFiles(None, access)
752
 
        records = [(('rev-id-1',), (('rev-id-1',), record_1[0], record_1[1])),
753
 
                   (('rev-id-2',), (('rev-id-2',), record_2[0], record_2[1]))]
 
822
        records = [((b'rev-id-1',), ((b'rev-id-1',), record_1[0], record_1[1])),
 
823
                   ((b'rev-id-2',), ((b'rev-id-2',), record_2[0], record_2[1]))]
754
824
 
755
825
        contents = list(knit._read_records_iter(records))
756
 
        self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'], record_1[2]),
757
 
                          (('rev-id-2',), ['baz\n'], record_2[2])],
 
826
        self.assertEqual([((b'rev-id-1',), [b'foo\n', b'bar\n'], record_1[2]),
 
827
                          ((b'rev-id-2',), [b'baz\n'], record_2[2])],
758
828
                         contents)
759
829
 
760
830
        raw_contents = list(knit._read_records_iter_raw(records))
761
 
        self.assertEqual([(('rev-id-1',), total_txt[0], record_1[2]),
762
 
                          (('rev-id-2',), total_txt[1], record_2[2])],
 
831
        self.assertEqual([((b'rev-id-1',), total_txt[0], record_1[2]),
 
832
                          ((b'rev-id-2',), total_txt[1], record_2[2])],
763
833
                         raw_contents)
764
834
 
765
835
    def test_not_enough_lines(self):
766
 
        sha1sum = osutils.sha('foo\n').hexdigest()
 
836
        sha1sum = osutils.sha_string(b'foo\n')
767
837
        # record says 2 lines data says 1
768
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
769
 
                                        'foo\n'
770
 
                                        'end rev-id-1\n'
 
838
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
839
                                        b'foo\n'
 
840
                                        b'end rev-id-1\n'
771
841
                                        % (sha1sum,))
772
842
        transport = MockTransport([gz_txt])
773
843
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
774
844
        knit = KnitVersionedFiles(None, access)
775
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
776
 
        self.assertRaises(errors.KnitCorrupt, list,
777
 
            knit._read_records_iter(records))
 
845
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
 
846
        self.assertRaises(KnitCorrupt, list,
 
847
                          knit._read_records_iter(records))
778
848
 
779
849
        # read_records_iter_raw won't detect that sort of mismatch/corruption
780
850
        raw_contents = list(knit._read_records_iter_raw(records))
781
 
        self.assertEqual([(('rev-id-1',),  gz_txt, sha1sum)], raw_contents)
 
851
        self.assertEqual([((b'rev-id-1',), gz_txt, sha1sum)], raw_contents)
782
852
 
783
853
    def test_too_many_lines(self):
784
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
 
854
        sha1sum = osutils.sha_string(b'foo\nbar\n')
785
855
        # record says 1 lines data says 2
786
 
        gz_txt = self.create_gz_content('version rev-id-1 1 %s\n'
787
 
                                        'foo\n'
788
 
                                        'bar\n'
789
 
                                        'end rev-id-1\n'
 
856
        gz_txt = self.create_gz_content(b'version rev-id-1 1 %s\n'
 
857
                                        b'foo\n'
 
858
                                        b'bar\n'
 
859
                                        b'end rev-id-1\n'
790
860
                                        % (sha1sum,))
791
861
        transport = MockTransport([gz_txt])
792
862
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
793
863
        knit = KnitVersionedFiles(None, access)
794
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
795
 
        self.assertRaises(errors.KnitCorrupt, list,
796
 
            knit._read_records_iter(records))
 
864
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
 
865
        self.assertRaises(KnitCorrupt, list,
 
866
                          knit._read_records_iter(records))
797
867
 
798
868
        # read_records_iter_raw won't detect that sort of mismatch/corruption
799
869
        raw_contents = list(knit._read_records_iter_raw(records))
800
 
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
 
870
        self.assertEqual([((b'rev-id-1',), gz_txt, sha1sum)], raw_contents)
801
871
 
802
872
    def test_mismatched_version_id(self):
803
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
804
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
805
 
                                        'foo\n'
806
 
                                        'bar\n'
807
 
                                        'end rev-id-1\n'
 
873
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
874
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
875
                                        b'foo\n'
 
876
                                        b'bar\n'
 
877
                                        b'end rev-id-1\n'
808
878
                                        % (sha1sum,))
809
879
        transport = MockTransport([gz_txt])
810
880
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
811
881
        knit = KnitVersionedFiles(None, access)
812
882
        # We are asking for rev-id-2, but the data is rev-id-1
813
 
        records = [(('rev-id-2',), (('rev-id-2',), 0, len(gz_txt)))]
814
 
        self.assertRaises(errors.KnitCorrupt, list,
815
 
            knit._read_records_iter(records))
 
883
        records = [((b'rev-id-2',), ((b'rev-id-2',), 0, len(gz_txt)))]
 
884
        self.assertRaises(KnitCorrupt, list,
 
885
                          knit._read_records_iter(records))
816
886
 
817
887
        # read_records_iter_raw detects mismatches in the header
818
 
        self.assertRaises(errors.KnitCorrupt, list,
819
 
            knit._read_records_iter_raw(records))
 
888
        self.assertRaises(KnitCorrupt, list,
 
889
                          knit._read_records_iter_raw(records))
820
890
 
821
891
    def test_uncompressed_data(self):
822
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
823
 
        txt = ('version rev-id-1 2 %s\n'
824
 
               'foo\n'
825
 
               'bar\n'
826
 
               'end rev-id-1\n'
 
892
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
893
        txt = (b'version rev-id-1 2 %s\n'
 
894
               b'foo\n'
 
895
               b'bar\n'
 
896
               b'end rev-id-1\n'
827
897
               % (sha1sum,))
828
898
        transport = MockTransport([txt])
829
899
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
830
900
        knit = KnitVersionedFiles(None, access)
831
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(txt)))]
 
901
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(txt)))]
832
902
 
833
903
        # We don't have valid gzip data ==> corrupt
834
 
        self.assertRaises(errors.KnitCorrupt, list,
835
 
            knit._read_records_iter(records))
 
904
        self.assertRaises(KnitCorrupt, list,
 
905
                          knit._read_records_iter(records))
836
906
 
837
907
        # read_records_iter_raw will notice the bad data
838
 
        self.assertRaises(errors.KnitCorrupt, list,
839
 
            knit._read_records_iter_raw(records))
 
908
        self.assertRaises(KnitCorrupt, list,
 
909
                          knit._read_records_iter_raw(records))
840
910
 
841
911
    def test_corrupted_data(self):
842
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
843
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
844
 
                                        'foo\n'
845
 
                                        'bar\n'
846
 
                                        'end rev-id-1\n'
 
912
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
913
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
914
                                        b'foo\n'
 
915
                                        b'bar\n'
 
916
                                        b'end rev-id-1\n'
847
917
                                        % (sha1sum,))
848
918
        # Change 2 bytes in the middle to \xff
849
 
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
 
919
        gz_txt = gz_txt[:10] + b'\xff\xff' + gz_txt[12:]
850
920
        transport = MockTransport([gz_txt])
851
921
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
852
922
        knit = KnitVersionedFiles(None, access)
853
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
854
 
        self.assertRaises(errors.KnitCorrupt, list,
855
 
            knit._read_records_iter(records))
 
923
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
 
924
        self.assertRaises(KnitCorrupt, list,
 
925
                          knit._read_records_iter(records))
856
926
        # read_records_iter_raw will barf on bad gz data
857
 
        self.assertRaises(errors.KnitCorrupt, list,
858
 
            knit._read_records_iter_raw(records))
 
927
        self.assertRaises(KnitCorrupt, list,
 
928
                          knit._read_records_iter_raw(records))
859
929
 
860
930
 
861
931
class LowLevelKnitIndexTests(TestCase):
862
932
 
 
933
    @property
 
934
    def _load_data(self):
 
935
        from ..bzr._knit_load_data_py import _load_data_py
 
936
        return _load_data_py
 
937
 
863
938
    def get_knit_index(self, transport, name, mode):
864
939
        mapper = ConstantMapper(name)
865
 
        from bzrlib._knit_load_data_py import _load_data_py
866
 
        self.overrideAttr(knit, '_load_data', _load_data_py)
867
 
        allow_writes = lambda: 'w' in mode
868
 
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
 
940
        self.overrideAttr(knit, '_load_data', self._load_data)
 
941
 
 
942
        def allow_writes():
 
943
            return 'w' in mode
 
944
        return _KndxIndex(transport, mapper, lambda: None, allow_writes, lambda: True)
869
945
 
870
946
    def test_create_file(self):
871
947
        transport = MockTransport()
872
948
        index = self.get_knit_index(transport, "filename", "w")
873
949
        index.keys()
874
950
        call = transport.calls.pop(0)
875
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
951
        # call[1][1] is a BytesIO - we can't test it by simple equality.
876
952
        self.assertEqual('put_file_non_atomic', call[0])
877
953
        self.assertEqual('filename.kndx', call[1][0])
878
954
        # With no history, _KndxIndex writes a new index:
879
955
        self.assertEqual(_KndxIndex.HEADER,
880
 
            call[1][1].getvalue())
 
956
                         call[1][1].getvalue())
881
957
        self.assertEqual({'create_parent_dir': True}, call[2])
882
958
 
883
959
    def test_read_utf8_version_id(self):
885
961
        utf8_revision_id = unicode_revision_id.encode('utf-8')
886
962
        transport = MockTransport([
887
963
            _KndxIndex.HEADER,
888
 
            '%s option 0 1 :' % (utf8_revision_id,)
 
964
            b'%s option 0 1 :' % (utf8_revision_id,)
889
965
            ])
890
966
        index = self.get_knit_index(transport, "filename", "r")
891
967
        # _KndxIndex is a private class, and deals in utf8 revision_ids, not
892
968
        # Unicode revision_ids.
893
 
        self.assertEqual({(utf8_revision_id,):()},
894
 
            index.get_parent_map(index.keys()))
 
969
        self.assertEqual({(utf8_revision_id,): ()},
 
970
                         index.get_parent_map(index.keys()))
895
971
        self.assertFalse((unicode_revision_id,) in index.keys())
896
972
 
897
973
    def test_read_utf8_parents(self):
899
975
        utf8_revision_id = unicode_revision_id.encode('utf-8')
900
976
        transport = MockTransport([
901
977
            _KndxIndex.HEADER,
902
 
            "version option 0 1 .%s :" % (utf8_revision_id,)
 
978
            b"version option 0 1 .%s :" % (utf8_revision_id,)
903
979
            ])
904
980
        index = self.get_knit_index(transport, "filename", "r")
905
 
        self.assertEqual({("version",):((utf8_revision_id,),)},
906
 
            index.get_parent_map(index.keys()))
 
981
        self.assertEqual({(b"version",): ((utf8_revision_id,),)},
 
982
                         index.get_parent_map(index.keys()))
907
983
 
908
984
    def test_read_ignore_corrupted_lines(self):
909
985
        transport = MockTransport([
910
986
            _KndxIndex.HEADER,
911
 
            "corrupted",
912
 
            "corrupted options 0 1 .b .c ",
913
 
            "version options 0 1 :"
 
987
            b"corrupted",
 
988
            b"corrupted options 0 1 .b .c ",
 
989
            b"version options 0 1 :"
914
990
            ])
915
991
        index = self.get_knit_index(transport, "filename", "r")
916
992
        self.assertEqual(1, len(index.keys()))
917
 
        self.assertEqual(set([("version",)]), index.keys())
 
993
        self.assertEqual({(b"version",)}, index.keys())
918
994
 
919
995
    def test_read_corrupted_header(self):
920
 
        transport = MockTransport(['not a bzr knit index header\n'])
 
996
        transport = MockTransport([b'not a bzr knit index header\n'])
921
997
        index = self.get_knit_index(transport, "filename", "r")
922
998
        self.assertRaises(KnitHeaderError, index.keys)
923
999
 
924
1000
    def test_read_duplicate_entries(self):
925
1001
        transport = MockTransport([
926
1002
            _KndxIndex.HEADER,
927
 
            "parent options 0 1 :",
928
 
            "version options1 0 1 0 :",
929
 
            "version options2 1 2 .other :",
930
 
            "version options3 3 4 0 .other :"
 
1003
            b"parent options 0 1 :",
 
1004
            b"version options1 0 1 0 :",
 
1005
            b"version options2 1 2 .other :",
 
1006
            b"version options3 3 4 0 .other :"
931
1007
            ])
932
1008
        index = self.get_knit_index(transport, "filename", "r")
933
1009
        self.assertEqual(2, len(index.keys()))
934
1010
        # check that the index used is the first one written. (Specific
935
1011
        # to KnitIndex style indices.
936
 
        self.assertEqual("1", index._dictionary_compress([("version",)]))
937
 
        self.assertEqual((("version",), 3, 4), index.get_position(("version",)))
938
 
        self.assertEqual(["options3"], index.get_options(("version",)))
939
 
        self.assertEqual({("version",):(("parent",), ("other",))},
940
 
            index.get_parent_map([("version",)]))
 
1012
        self.assertEqual(b"1", index._dictionary_compress([(b"version",)]))
 
1013
        self.assertEqual(((b"version",), 3, 4),
 
1014
                         index.get_position((b"version",)))
 
1015
        self.assertEqual([b"options3"], index.get_options((b"version",)))
 
1016
        self.assertEqual({(b"version",): ((b"parent",), (b"other",))},
 
1017
                         index.get_parent_map([(b"version",)]))
941
1018
 
942
1019
    def test_read_compressed_parents(self):
943
1020
        transport = MockTransport([
944
1021
            _KndxIndex.HEADER,
945
 
            "a option 0 1 :",
946
 
            "b option 0 1 0 :",
947
 
            "c option 0 1 1 0 :",
 
1022
            b"a option 0 1 :",
 
1023
            b"b option 0 1 0 :",
 
1024
            b"c option 0 1 1 0 :",
948
1025
            ])
949
1026
        index = self.get_knit_index(transport, "filename", "r")
950
 
        self.assertEqual({("b",):(("a",),), ("c",):(("b",), ("a",))},
951
 
            index.get_parent_map([("b",), ("c",)]))
 
1027
        self.assertEqual({(b"b",): ((b"a",),), (b"c",): ((b"b",), (b"a",))},
 
1028
                         index.get_parent_map([(b"b",), (b"c",)]))
952
1029
 
953
1030
    def test_write_utf8_version_id(self):
954
1031
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
958
1035
            ])
959
1036
        index = self.get_knit_index(transport, "filename", "r")
960
1037
        index.add_records([
961
 
            ((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
 
1038
            ((utf8_revision_id,), [b"option"], ((utf8_revision_id,), 0, 1), [])])
962
1039
        call = transport.calls.pop(0)
963
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1040
        # call[1][1] is a BytesIO - we can't test it by simple equality.
964
1041
        self.assertEqual('put_file_non_atomic', call[0])
965
1042
        self.assertEqual('filename.kndx', call[1][0])
966
1043
        # With no history, _KndxIndex writes a new index:
967
1044
        self.assertEqual(_KndxIndex.HEADER +
968
 
            "\n%s option 0 1  :" % (utf8_revision_id,),
969
 
            call[1][1].getvalue())
 
1045
                         b"\n%s option 0 1  :" % (utf8_revision_id,),
 
1046
                         call[1][1].getvalue())
970
1047
        self.assertEqual({'create_parent_dir': True}, call[2])
971
1048
 
972
1049
    def test_write_utf8_parents(self):
977
1054
            ])
978
1055
        index = self.get_knit_index(transport, "filename", "r")
979
1056
        index.add_records([
980
 
            (("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
 
1057
            ((b"version",), [b"option"], ((b"version",), 0, 1), [(utf8_revision_id,)])])
981
1058
        call = transport.calls.pop(0)
982
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1059
        # call[1][1] is a BytesIO - we can't test it by simple equality.
983
1060
        self.assertEqual('put_file_non_atomic', call[0])
984
1061
        self.assertEqual('filename.kndx', call[1][0])
985
1062
        # With no history, _KndxIndex writes a new index:
986
1063
        self.assertEqual(_KndxIndex.HEADER +
987
 
            "\nversion option 0 1 .%s :" % (utf8_revision_id,),
988
 
            call[1][1].getvalue())
 
1064
                         b"\nversion option 0 1 .%s :" % (utf8_revision_id,),
 
1065
                         call[1][1].getvalue())
989
1066
        self.assertEqual({'create_parent_dir': True}, call[2])
990
1067
 
991
1068
    def test_keys(self):
996
1073
 
997
1074
        self.assertEqual(set(), index.keys())
998
1075
 
999
 
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1000
 
        self.assertEqual(set([("a",)]), index.keys())
1001
 
 
1002
 
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1003
 
        self.assertEqual(set([("a",)]), index.keys())
1004
 
 
1005
 
        index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
1006
 
        self.assertEqual(set([("a",), ("b",)]), index.keys())
 
1076
        index.add_records([((b"a",), [b"option"], ((b"a",), 0, 1), [])])
 
1077
        self.assertEqual({(b"a",)}, index.keys())
 
1078
 
 
1079
        index.add_records([((b"a",), [b"option"], ((b"a",), 0, 1), [])])
 
1080
        self.assertEqual({(b"a",)}, index.keys())
 
1081
 
 
1082
        index.add_records([((b"b",), [b"option"], ((b"b",), 0, 1), [])])
 
1083
        self.assertEqual({(b"a",), (b"b",)}, index.keys())
1007
1084
 
1008
1085
    def add_a_b(self, index, random_id=None):
1009
1086
        kwargs = {}
1010
1087
        if random_id is not None:
1011
1088
            kwargs["random_id"] = random_id
1012
1089
        index.add_records([
1013
 
            (("a",), ["option"], (("a",), 0, 1), [("b",)]),
1014
 
            (("a",), ["opt"], (("a",), 1, 2), [("c",)]),
1015
 
            (("b",), ["option"], (("b",), 2, 3), [("a",)])
 
1090
            ((b"a",), [b"option"], ((b"a",), 0, 1), [(b"b",)]),
 
1091
            ((b"a",), [b"opt"], ((b"a",), 1, 2), [(b"c",)]),
 
1092
            ((b"b",), [b"option"], ((b"b",), 2, 3), [(b"a",)])
1016
1093
            ], **kwargs)
1017
1094
 
1018
1095
    def assertIndexIsAB(self, index):
1019
1096
        self.assertEqual({
1020
 
            ('a',): (('c',),),
1021
 
            ('b',): (('a',),),
 
1097
            (b'a',): ((b'c',),),
 
1098
            (b'b',): ((b'a',),),
1022
1099
            },
1023
1100
            index.get_parent_map(index.keys()))
1024
 
        self.assertEqual((("a",), 1, 2), index.get_position(("a",)))
1025
 
        self.assertEqual((("b",), 2, 3), index.get_position(("b",)))
1026
 
        self.assertEqual(["opt"], index.get_options(("a",)))
 
1101
        self.assertEqual(((b"a",), 1, 2), index.get_position((b"a",)))
 
1102
        self.assertEqual(((b"b",), 2, 3), index.get_position((b"b",)))
 
1103
        self.assertEqual([b"opt"], index.get_options((b"a",)))
1027
1104
 
1028
1105
    def test_add_versions(self):
1029
1106
        transport = MockTransport([
1033
1110
 
1034
1111
        self.add_a_b(index)
1035
1112
        call = transport.calls.pop(0)
1036
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1113
        # call[1][1] is a BytesIO - we can't test it by simple equality.
1037
1114
        self.assertEqual('put_file_non_atomic', call[0])
1038
1115
        self.assertEqual('filename.kndx', call[1][0])
1039
1116
        # With no history, _KndxIndex writes a new index:
1040
1117
        self.assertEqual(
1041
1118
            _KndxIndex.HEADER +
1042
 
            "\na option 0 1 .b :"
1043
 
            "\na opt 1 2 .c :"
1044
 
            "\nb option 2 3 0 :",
 
1119
            b"\na option 0 1 .b :"
 
1120
            b"\na opt 1 2 .c :"
 
1121
            b"\nb option 2 3 0 :",
1045
1122
            call[1][1].getvalue())
1046
1123
        self.assertEqual({'create_parent_dir': True}, call[2])
1047
1124
        self.assertIndexIsAB(index)
1060
1137
        # dir_mode=0777)
1061
1138
        self.assertEqual([], transport.calls)
1062
1139
        self.add_a_b(index)
1063
 
        #self.assertEqual(
1064
 
        #[    {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
 
1140
        # self.assertEqual(
 
1141
        # [    {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
1065
1142
        #    kwargs)
1066
1143
        # Two calls: one during which we load the existing index (and when its
1067
1144
        # missing create it), then a second where we write the contents out.
1073
1150
        self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
1074
1151
        self.assertEqual({'create_parent_dir': True}, call[2])
1075
1152
        call = transport.calls.pop(0)
1076
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1153
        # call[1][1] is a BytesIO - we can't test it by simple equality.
1077
1154
        self.assertEqual('put_file_non_atomic', call[0])
1078
1155
        self.assertEqual('filename.kndx', call[1][0])
1079
1156
        # With no history, _KndxIndex writes a new index:
1080
1157
        self.assertEqual(
1081
1158
            _KndxIndex.HEADER +
1082
 
            "\na option 0 1 .b :"
1083
 
            "\na opt 1 2 .c :"
1084
 
            "\nb option 2 3 0 :",
 
1159
            b"\na option 0 1 .b :"
 
1160
            b"\na opt 1 2 .c :"
 
1161
            b"\nb option 2 3 0 :",
1085
1162
            call[1][1].getvalue())
1086
1163
        self.assertEqual({'create_parent_dir': True}, call[2])
1087
1164
 
1091
1168
 
1092
1169
    def test__get_total_build_size(self):
1093
1170
        positions = {
1094
 
            ('a',): (('fulltext', False), (('a',), 0, 100), None),
1095
 
            ('b',): (('line-delta', False), (('b',), 100, 21), ('a',)),
1096
 
            ('c',): (('line-delta', False), (('c',), 121, 35), ('b',)),
1097
 
            ('d',): (('line-delta', False), (('d',), 156, 12), ('b',)),
 
1171
            (b'a',): (('fulltext', False), ((b'a',), 0, 100), None),
 
1172
            (b'b',): (('line-delta', False), ((b'b',), 100, 21), (b'a',)),
 
1173
            (b'c',): (('line-delta', False), ((b'c',), 121, 35), (b'b',)),
 
1174
            (b'd',): (('line-delta', False), ((b'd',), 156, 12), (b'b',)),
1098
1175
            }
1099
 
        self.assertTotalBuildSize(100, [('a',)], positions)
1100
 
        self.assertTotalBuildSize(121, [('b',)], positions)
 
1176
        self.assertTotalBuildSize(100, [(b'a',)], positions)
 
1177
        self.assertTotalBuildSize(121, [(b'b',)], positions)
1101
1178
        # c needs both a & b
1102
 
        self.assertTotalBuildSize(156, [('c',)], positions)
 
1179
        self.assertTotalBuildSize(156, [(b'c',)], positions)
1103
1180
        # we shouldn't count 'b' twice
1104
 
        self.assertTotalBuildSize(156, [('b',), ('c',)], positions)
1105
 
        self.assertTotalBuildSize(133, [('d',)], positions)
1106
 
        self.assertTotalBuildSize(168, [('c',), ('d',)], positions)
 
1181
        self.assertTotalBuildSize(156, [(b'b',), (b'c',)], positions)
 
1182
        self.assertTotalBuildSize(133, [(b'd',)], positions)
 
1183
        self.assertTotalBuildSize(168, [(b'c',), (b'd',)], positions)
1107
1184
 
1108
1185
    def test_get_position(self):
1109
1186
        transport = MockTransport([
1110
1187
            _KndxIndex.HEADER,
1111
 
            "a option 0 1 :",
1112
 
            "b option 1 2 :"
 
1188
            b"a option 0 1 :",
 
1189
            b"b option 1 2 :"
1113
1190
            ])
1114
1191
        index = self.get_knit_index(transport, "filename", "r")
1115
1192
 
1116
 
        self.assertEqual((("a",), 0, 1), index.get_position(("a",)))
1117
 
        self.assertEqual((("b",), 1, 2), index.get_position(("b",)))
 
1193
        self.assertEqual(((b"a",), 0, 1), index.get_position((b"a",)))
 
1194
        self.assertEqual(((b"b",), 1, 2), index.get_position((b"b",)))
1118
1195
 
1119
1196
    def test_get_method(self):
1120
1197
        transport = MockTransport([
1121
1198
            _KndxIndex.HEADER,
1122
 
            "a fulltext,unknown 0 1 :",
1123
 
            "b unknown,line-delta 1 2 :",
1124
 
            "c bad 3 4 :"
 
1199
            b"a fulltext,unknown 0 1 :",
 
1200
            b"b unknown,line-delta 1 2 :",
 
1201
            b"c bad 3 4 :"
1125
1202
            ])
1126
1203
        index = self.get_knit_index(transport, "filename", "r")
1127
1204
 
1128
 
        self.assertEqual("fulltext", index.get_method("a"))
1129
 
        self.assertEqual("line-delta", index.get_method("b"))
1130
 
        self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, "c")
 
1205
        self.assertEqual("fulltext", index.get_method(b"a"))
 
1206
        self.assertEqual("line-delta", index.get_method(b"b"))
 
1207
        self.assertRaises(knit.KnitIndexUnknownMethod, index.get_method, b"c")
1131
1208
 
1132
1209
    def test_get_options(self):
1133
1210
        transport = MockTransport([
1134
1211
            _KndxIndex.HEADER,
1135
 
            "a opt1 0 1 :",
1136
 
            "b opt2,opt3 1 2 :"
 
1212
            b"a opt1 0 1 :",
 
1213
            b"b opt2,opt3 1 2 :"
1137
1214
            ])
1138
1215
        index = self.get_knit_index(transport, "filename", "r")
1139
1216
 
1140
 
        self.assertEqual(["opt1"], index.get_options("a"))
1141
 
        self.assertEqual(["opt2", "opt3"], index.get_options("b"))
 
1217
        self.assertEqual([b"opt1"], index.get_options(b"a"))
 
1218
        self.assertEqual([b"opt2", b"opt3"], index.get_options(b"b"))
1142
1219
 
1143
1220
    def test_get_parent_map(self):
1144
1221
        transport = MockTransport([
1145
1222
            _KndxIndex.HEADER,
1146
 
            "a option 0 1 :",
1147
 
            "b option 1 2 0 .c :",
1148
 
            "c option 1 2 1 0 .e :"
 
1223
            b"a option 0 1 :",
 
1224
            b"b option 1 2 0 .c :",
 
1225
            b"c option 1 2 1 0 .e :"
1149
1226
            ])
1150
1227
        index = self.get_knit_index(transport, "filename", "r")
1151
1228
 
1152
1229
        self.assertEqual({
1153
 
            ("a",):(),
1154
 
            ("b",):(("a",), ("c",)),
1155
 
            ("c",):(("b",), ("a",), ("e",)),
 
1230
            (b"a",): (),
 
1231
            (b"b",): ((b"a",), (b"c",)),
 
1232
            (b"c",): ((b"b",), (b"a",), (b"e",)),
1156
1233
            }, index.get_parent_map(index.keys()))
1157
1234
 
1158
1235
    def test_impossible_parent(self):
1159
1236
        """Test we get KnitCorrupt if the parent couldn't possibly exist."""
1160
1237
        transport = MockTransport([
1161
1238
            _KndxIndex.HEADER,
1162
 
            "a option 0 1 :",
1163
 
            "b option 0 1 4 :"  # We don't have a 4th record
 
1239
            b"a option 0 1 :",
 
1240
            b"b option 0 1 4 :"  # We don't have a 4th record
1164
1241
            ])
1165
1242
        index = self.get_knit_index(transport, 'filename', 'r')
1166
 
        try:
1167
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
1168
 
        except TypeError, e:
1169
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1170
 
                           ' not exceptions.IndexError')
1171
 
                and sys.version_info[0:2] >= (2,5)):
1172
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1173
 
                                  ' raising new style exceptions with python'
1174
 
                                  ' >=2.5')
1175
 
            else:
1176
 
                raise
 
1243
        self.assertRaises(KnitCorrupt, index.keys)
1177
1244
 
1178
1245
    def test_corrupted_parent(self):
1179
1246
        transport = MockTransport([
1180
1247
            _KndxIndex.HEADER,
1181
 
            "a option 0 1 :",
1182
 
            "b option 0 1 :",
1183
 
            "c option 0 1 1v :", # Can't have a parent of '1v'
 
1248
            b"a option 0 1 :",
 
1249
            b"b option 0 1 :",
 
1250
            b"c option 0 1 1v :",  # Can't have a parent of '1v'
1184
1251
            ])
1185
1252
        index = self.get_knit_index(transport, 'filename', 'r')
1186
 
        try:
1187
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
1188
 
        except TypeError, e:
1189
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1190
 
                           ' not exceptions.ValueError')
1191
 
                and sys.version_info[0:2] >= (2,5)):
1192
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1193
 
                                  ' raising new style exceptions with python'
1194
 
                                  ' >=2.5')
1195
 
            else:
1196
 
                raise
 
1253
        self.assertRaises(KnitCorrupt, index.keys)
1197
1254
 
1198
1255
    def test_corrupted_parent_in_list(self):
1199
1256
        transport = MockTransport([
1200
1257
            _KndxIndex.HEADER,
1201
 
            "a option 0 1 :",
1202
 
            "b option 0 1 :",
1203
 
            "c option 0 1 1 v :", # Can't have a parent of 'v'
 
1258
            b"a option 0 1 :",
 
1259
            b"b option 0 1 :",
 
1260
            b"c option 0 1 1 v :",  # Can't have a parent of 'v'
1204
1261
            ])
1205
1262
        index = self.get_knit_index(transport, 'filename', 'r')
1206
 
        try:
1207
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
1208
 
        except TypeError, e:
1209
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1210
 
                           ' not exceptions.ValueError')
1211
 
                and sys.version_info[0:2] >= (2,5)):
1212
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1213
 
                                  ' raising new style exceptions with python'
1214
 
                                  ' >=2.5')
1215
 
            else:
1216
 
                raise
 
1263
        self.assertRaises(KnitCorrupt, index.keys)
1217
1264
 
1218
1265
    def test_invalid_position(self):
1219
1266
        transport = MockTransport([
1220
1267
            _KndxIndex.HEADER,
1221
 
            "a option 1v 1 :",
 
1268
            b"a option 1v 1 :",
1222
1269
            ])
1223
1270
        index = self.get_knit_index(transport, 'filename', 'r')
1224
 
        try:
1225
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
1226
 
        except TypeError, e:
1227
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1228
 
                           ' not exceptions.ValueError')
1229
 
                and sys.version_info[0:2] >= (2,5)):
1230
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1231
 
                                  ' raising new style exceptions with python'
1232
 
                                  ' >=2.5')
1233
 
            else:
1234
 
                raise
 
1271
        self.assertRaises(KnitCorrupt, index.keys)
1235
1272
 
1236
1273
    def test_invalid_size(self):
1237
1274
        transport = MockTransport([
1238
1275
            _KndxIndex.HEADER,
1239
 
            "a option 1 1v :",
 
1276
            b"a option 1 1v :",
1240
1277
            ])
1241
1278
        index = self.get_knit_index(transport, 'filename', 'r')
1242
 
        try:
1243
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
1244
 
        except TypeError, e:
1245
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1246
 
                           ' not exceptions.ValueError')
1247
 
                and sys.version_info[0:2] >= (2,5)):
1248
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1249
 
                                  ' raising new style exceptions with python'
1250
 
                                  ' >=2.5')
1251
 
            else:
1252
 
                raise
 
1279
        self.assertRaises(KnitCorrupt, index.keys)
1253
1280
 
1254
1281
    def test_scan_unvalidated_index_not_implemented(self):
1255
1282
        transport = MockTransport()
1263
1290
    def test_short_line(self):
1264
1291
        transport = MockTransport([
1265
1292
            _KndxIndex.HEADER,
1266
 
            "a option 0 10  :",
1267
 
            "b option 10 10 0", # This line isn't terminated, ignored
 
1293
            b"a option 0 10  :",
 
1294
            b"b option 10 10 0",  # This line isn't terminated, ignored
1268
1295
            ])
1269
1296
        index = self.get_knit_index(transport, "filename", "r")
1270
 
        self.assertEqual(set([('a',)]), index.keys())
 
1297
        self.assertEqual({(b'a',)}, index.keys())
1271
1298
 
1272
1299
    def test_skip_incomplete_record(self):
1273
1300
        # A line with bogus data should just be skipped
1274
1301
        transport = MockTransport([
1275
1302
            _KndxIndex.HEADER,
1276
 
            "a option 0 10  :",
1277
 
            "b option 10 10 0", # This line isn't terminated, ignored
1278
 
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
 
1303
            b"a option 0 10  :",
 
1304
            b"b option 10 10 0",  # This line isn't terminated, ignored
 
1305
            b"c option 20 10 0 :",  # Properly terminated, and starts with '\n'
1279
1306
            ])
1280
1307
        index = self.get_knit_index(transport, "filename", "r")
1281
 
        self.assertEqual(set([('a',), ('c',)]), index.keys())
 
1308
        self.assertEqual({(b'a',), (b'c',)}, index.keys())
1282
1309
 
1283
1310
    def test_trailing_characters(self):
1284
1311
        # A line with bogus data should just be skipped
1285
1312
        transport = MockTransport([
1286
1313
            _KndxIndex.HEADER,
1287
 
            "a option 0 10  :",
1288
 
            "b option 10 10 0 :a", # This line has extra trailing characters
1289
 
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
 
1314
            b"a option 0 10  :",
 
1315
            b"b option 10 10 0 :a",  # This line has extra trailing characters
 
1316
            b"c option 20 10 0 :",  # Properly terminated, and starts with '\n'
1290
1317
            ])
1291
1318
        index = self.get_knit_index(transport, "filename", "r")
1292
 
        self.assertEqual(set([('a',), ('c',)]), index.keys())
 
1319
        self.assertEqual({(b'a',), (b'c',)}, index.keys())
1293
1320
 
1294
1321
 
1295
1322
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1296
1323
 
1297
1324
    _test_needs_features = [compiled_knit_feature]
1298
1325
 
1299
 
    def get_knit_index(self, transport, name, mode):
1300
 
        mapper = ConstantMapper(name)
1301
 
        from bzrlib._knit_load_data_pyx import _load_data_c
1302
 
        self.overrideAttr(knit, '_load_data', _load_data_c)
1303
 
        allow_writes = lambda: mode == 'w'
1304
 
        return _KndxIndex(transport, mapper, lambda:None,
1305
 
                          allow_writes, lambda:True)
 
1326
    @property
 
1327
    def _load_data(self):
 
1328
        from ..bzr._knit_load_data_pyx import _load_data_c
 
1329
        return _load_data_c
1306
1330
 
1307
1331
 
1308
1332
class Test_KnitAnnotator(TestCaseWithMemoryTransport):
1314
1338
 
1315
1339
    def test__expand_fulltext(self):
1316
1340
        ann = self.make_annotator()
1317
 
        rev_key = ('rev-id',)
 
1341
        rev_key = (b'rev-id',)
1318
1342
        ann._num_compression_children[rev_key] = 1
1319
 
        res = ann._expand_record(rev_key, (('parent-id',),), None,
1320
 
                           ['line1\n', 'line2\n'], ('fulltext', True))
 
1343
        res = ann._expand_record(rev_key, ((b'parent-id',),), None,
 
1344
                                 [b'line1\n', b'line2\n'], ('fulltext', True))
1321
1345
        # The content object and text lines should be cached appropriately
1322
 
        self.assertEqual(['line1\n', 'line2'], res)
 
1346
        self.assertEqual([b'line1\n', b'line2'], res)
1323
1347
        content_obj = ann._content_objects[rev_key]
1324
 
        self.assertEqual(['line1\n', 'line2\n'], content_obj._lines)
 
1348
        self.assertEqual([b'line1\n', b'line2\n'], content_obj._lines)
1325
1349
        self.assertEqual(res, content_obj.text())
1326
1350
        self.assertEqual(res, ann._text_cache[rev_key])
1327
1351
 
1329
1353
        # Parent isn't available yet, so we return nothing, but queue up this
1330
1354
        # node for later processing
1331
1355
        ann = self.make_annotator()
1332
 
        rev_key = ('rev-id',)
1333
 
        parent_key = ('parent-id',)
1334
 
        record = ['0,1,1\n', 'new-line\n']
 
1356
        rev_key = (b'rev-id',)
 
1357
        parent_key = (b'parent-id',)
 
1358
        record = [b'0,1,1\n', b'new-line\n']
1335
1359
        details = ('line-delta', False)
1336
1360
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
1337
1361
                                 record, details)
1343
1367
 
1344
1368
    def test__expand_record_tracks_num_children(self):
1345
1369
        ann = self.make_annotator()
1346
 
        rev_key = ('rev-id',)
1347
 
        rev2_key = ('rev2-id',)
1348
 
        parent_key = ('parent-id',)
1349
 
        record = ['0,1,1\n', 'new-line\n']
 
1370
        rev_key = (b'rev-id',)
 
1371
        rev2_key = (b'rev2-id',)
 
1372
        parent_key = (b'parent-id',)
 
1373
        record = [b'0,1,1\n', b'new-line\n']
1350
1374
        details = ('line-delta', False)
1351
1375
        ann._num_compression_children[parent_key] = 2
1352
 
        ann._expand_record(parent_key, (), None, ['line1\n', 'line2\n'],
 
1376
        ann._expand_record(parent_key, (), None, [b'line1\n', b'line2\n'],
1353
1377
                           ('fulltext', False))
1354
1378
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
1355
1379
                                 record, details)
1366
1390
 
1367
1391
    def test__expand_delta_records_blocks(self):
1368
1392
        ann = self.make_annotator()
1369
 
        rev_key = ('rev-id',)
1370
 
        parent_key = ('parent-id',)
1371
 
        record = ['0,1,1\n', 'new-line\n']
 
1393
        rev_key = (b'rev-id',)
 
1394
        parent_key = (b'parent-id',)
 
1395
        record = [b'0,1,1\n', b'new-line\n']
1372
1396
        details = ('line-delta', True)
1373
1397
        ann._num_compression_children[parent_key] = 2
1374
1398
        ann._expand_record(parent_key, (), None,
1375
 
                           ['line1\n', 'line2\n', 'line3\n'],
 
1399
                           [b'line1\n', b'line2\n', b'line3\n'],
1376
1400
                           ('fulltext', False))
1377
1401
        ann._expand_record(rev_key, (parent_key,), parent_key, record, details)
1378
1402
        self.assertEqual({(rev_key, parent_key): [(1, 1, 1), (3, 3, 0)]},
1379
1403
                         ann._matching_blocks)
1380
 
        rev2_key = ('rev2-id',)
1381
 
        record = ['0,1,1\n', 'new-line\n']
 
1404
        rev2_key = (b'rev2-id',)
 
1405
        record = [b'0,1,1\n', b'new-line\n']
1382
1406
        details = ('line-delta', False)
1383
 
        ann._expand_record(rev2_key, (parent_key,), parent_key, record, details)
 
1407
        ann._expand_record(rev2_key, (parent_key,),
 
1408
                           parent_key, record, details)
1384
1409
        self.assertEqual([(1, 1, 2), (3, 3, 0)],
1385
1410
                         ann._matching_blocks[(rev2_key, parent_key)])
1386
1411
 
1387
1412
    def test__get_parent_ann_uses_matching_blocks(self):
1388
1413
        ann = self.make_annotator()
1389
 
        rev_key = ('rev-id',)
1390
 
        parent_key = ('parent-id',)
1391
 
        parent_ann = [(parent_key,)]*3
 
1414
        rev_key = (b'rev-id',)
 
1415
        parent_key = (b'parent-id',)
 
1416
        parent_ann = [(parent_key,)] * 3
1392
1417
        block_key = (rev_key, parent_key)
1393
1418
        ann._annotations_cache[parent_key] = parent_ann
1394
1419
        ann._matching_blocks[block_key] = [(0, 1, 1), (3, 3, 0)]
1395
1420
        # We should not try to access any parent_lines content, because we know
1396
1421
        # we already have the matching blocks
1397
1422
        par_ann, blocks = ann._get_parent_annotations_and_matches(rev_key,
1398
 
                                        ['1\n', '2\n', '3\n'], parent_key)
 
1423
                                                                  [b'1\n', b'2\n', b'3\n'], parent_key)
1399
1424
        self.assertEqual(parent_ann, par_ann)
1400
1425
        self.assertEqual([(0, 1, 1), (3, 3, 0)], blocks)
1401
1426
        self.assertEqual({}, ann._matching_blocks)
1402
1427
 
1403
1428
    def test__process_pending(self):
1404
1429
        ann = self.make_annotator()
1405
 
        rev_key = ('rev-id',)
1406
 
        p1_key = ('p1-id',)
1407
 
        p2_key = ('p2-id',)
1408
 
        record = ['0,1,1\n', 'new-line\n']
 
1430
        rev_key = (b'rev-id',)
 
1431
        p1_key = (b'p1-id',)
 
1432
        p2_key = (b'p2-id',)
 
1433
        record = [b'0,1,1\n', b'new-line\n']
1409
1434
        details = ('line-delta', False)
1410
 
        p1_record = ['line1\n', 'line2\n']
 
1435
        p1_record = [b'line1\n', b'line2\n']
1411
1436
        ann._num_compression_children[p1_key] = 1
1412
 
        res = ann._expand_record(rev_key, (p1_key,p2_key), p1_key,
 
1437
        res = ann._expand_record(rev_key, (p1_key, p2_key), p1_key,
1413
1438
                                 record, details)
1414
1439
        self.assertEqual(None, res)
1415
1440
        # self.assertTrue(p1_key in ann._pending_deltas)
1418
1443
        res = ann._expand_record(p1_key, (), None, p1_record,
1419
1444
                                 ('fulltext', False))
1420
1445
        self.assertEqual(p1_record, res)
1421
 
        ann._annotations_cache[p1_key] = [(p1_key,)]*2
 
1446
        ann._annotations_cache[p1_key] = [(p1_key,)] * 2
1422
1447
        res = ann._process_pending(p1_key)
1423
1448
        self.assertEqual([], res)
1424
1449
        self.assertFalse(p1_key in ann._pending_deltas)
1435
1460
 
1436
1461
    def test_record_delta_removes_basis(self):
1437
1462
        ann = self.make_annotator()
1438
 
        ann._expand_record(('parent-id',), (), None,
1439
 
                           ['line1\n', 'line2\n'], ('fulltext', False))
1440
 
        ann._num_compression_children['parent-id'] = 2
 
1463
        ann._expand_record((b'parent-id',), (), None,
 
1464
                           [b'line1\n', b'line2\n'], ('fulltext', False))
 
1465
        ann._num_compression_children[b'parent-id'] = 2
1441
1466
 
1442
1467
    def test_annotate_special_text(self):
1443
1468
        ann = self.make_annotator()
1444
1469
        vf = ann._vf
1445
 
        rev1_key = ('rev-1',)
1446
 
        rev2_key = ('rev-2',)
1447
 
        rev3_key = ('rev-3',)
1448
 
        spec_key = ('special:',)
1449
 
        vf.add_lines(rev1_key, [], ['initial content\n'])
1450
 
        vf.add_lines(rev2_key, [rev1_key], ['initial content\n',
1451
 
                                            'common content\n',
1452
 
                                            'content in 2\n'])
1453
 
        vf.add_lines(rev3_key, [rev1_key], ['initial content\n',
1454
 
                                            'common content\n',
1455
 
                                            'content in 3\n'])
1456
 
        spec_text = ('initial content\n'
1457
 
                     'common content\n'
1458
 
                     'content in 2\n'
1459
 
                     'content in 3\n')
 
1470
        rev1_key = (b'rev-1',)
 
1471
        rev2_key = (b'rev-2',)
 
1472
        rev3_key = (b'rev-3',)
 
1473
        spec_key = (b'special:',)
 
1474
        vf.add_lines(rev1_key, [], [b'initial content\n'])
 
1475
        vf.add_lines(rev2_key, [rev1_key], [b'initial content\n',
 
1476
                                            b'common content\n',
 
1477
                                            b'content in 2\n'])
 
1478
        vf.add_lines(rev3_key, [rev1_key], [b'initial content\n',
 
1479
                                            b'common content\n',
 
1480
                                            b'content in 3\n'])
 
1481
        spec_text = (b'initial content\n'
 
1482
                     b'common content\n'
 
1483
                     b'content in 2\n'
 
1484
                     b'content in 3\n')
1460
1485
        ann.add_special_text(spec_key, [rev2_key, rev3_key], spec_text)
1461
1486
        anns, lines = ann.annotate(spec_key)
1462
1487
        self.assertEqual([(rev1_key,),
1463
1488
                          (rev2_key, rev3_key),
1464
1489
                          (rev2_key,),
1465
1490
                          (rev3_key,),
1466
 
                         ], anns)
1467
 
        self.assertEqualDiff(spec_text, ''.join(lines))
 
1491
                          ], anns)
 
1492
        self.assertEqualDiff(spec_text, b''.join(lines))
1468
1493
 
1469
1494
 
1470
1495
class KnitTests(TestCaseWithTransport):
1486
1511
            raise TestNotApplicable(
1487
1512
                "cannot get delta-caused sha failures without deltas.")
1488
1513
        # create a basis
1489
 
        basis = ('basis',)
1490
 
        broken = ('broken',)
1491
 
        source.add_lines(basis, (), ['foo\n'])
1492
 
        source.add_lines(broken, (basis,), ['foo\n', 'bar\n'])
 
1514
        basis = (b'basis',)
 
1515
        broken = (b'broken',)
 
1516
        source.add_lines(basis, (), [b'foo\n'])
 
1517
        source.add_lines(broken, (basis,), [b'foo\n', b'bar\n'])
1493
1518
        # Seed target with a bad basis text
1494
 
        target.add_lines(basis, (), ['gam\n'])
 
1519
        target.add_lines(basis, (), [b'gam\n'])
1495
1520
        target.insert_record_stream(
1496
1521
            source.get_record_stream([broken], 'unordered', False))
1497
 
        err = self.assertRaises(errors.KnitCorrupt,
1498
 
            target.get_record_stream([broken], 'unordered', True
1499
 
            ).next().get_bytes_as, 'chunked')
1500
 
        self.assertEqual(['gam\n', 'bar\n'], err.content)
 
1522
        err = self.assertRaises(KnitCorrupt,
 
1523
                                next(target.get_record_stream([broken], 'unordered', True
 
1524
                                                              )).get_bytes_as, 'chunked')
 
1525
        self.assertEqual([b'gam\n', b'bar\n'], err.content)
1501
1526
        # Test for formatting with live data
1502
1527
        self.assertStartsWith(str(err), "Knit ")
1503
1528
 
1508
1533
        """Adding versions to the index should update the lookup dict"""
1509
1534
        knit = self.make_test_knit()
1510
1535
        idx = knit._index
1511
 
        idx.add_records([(('a-1',), ['fulltext'], (('a-1',), 0, 0), [])])
 
1536
        idx.add_records([((b'a-1',), [b'fulltext'], ((b'a-1',), 0, 0), [])])
1512
1537
        self.check_file_contents('test.kndx',
1513
 
            '# bzr knit index 8\n'
1514
 
            '\n'
1515
 
            'a-1 fulltext 0 0  :'
1516
 
            )
 
1538
                                 b'# bzr knit index 8\n'
 
1539
                                 b'\n'
 
1540
                                 b'a-1 fulltext 0 0  :'
 
1541
                                 )
1517
1542
        idx.add_records([
1518
 
            (('a-2',), ['fulltext'], (('a-2',), 0, 0), [('a-1',)]),
1519
 
            (('a-3',), ['fulltext'], (('a-3',), 0, 0), [('a-2',)]),
 
1543
            ((b'a-2',), [b'fulltext'], ((b'a-2',), 0, 0), [(b'a-1',)]),
 
1544
            ((b'a-3',), [b'fulltext'], ((b'a-3',), 0, 0), [(b'a-2',)]),
1520
1545
            ])
1521
1546
        self.check_file_contents('test.kndx',
1522
 
            '# bzr knit index 8\n'
1523
 
            '\n'
1524
 
            'a-1 fulltext 0 0  :\n'
1525
 
            'a-2 fulltext 0 0 0 :\n'
1526
 
            'a-3 fulltext 0 0 1 :'
1527
 
            )
1528
 
        self.assertEqual(set([('a-3',), ('a-1',), ('a-2',)]), idx.keys())
 
1547
                                 b'# bzr knit index 8\n'
 
1548
                                 b'\n'
 
1549
                                 b'a-1 fulltext 0 0  :\n'
 
1550
                                 b'a-2 fulltext 0 0 0 :\n'
 
1551
                                 b'a-3 fulltext 0 0 1 :'
 
1552
                                 )
 
1553
        self.assertEqual({(b'a-3',), (b'a-1',), (b'a-2',)}, idx.keys())
1529
1554
        self.assertEqual({
1530
 
            ('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False)),
1531
 
            ('a-2',): ((('a-2',), 0, 0), None, (('a-1',),), ('fulltext', False)),
1532
 
            ('a-3',): ((('a-3',), 0, 0), None, (('a-2',),), ('fulltext', False)),
 
1555
            (b'a-1',): (((b'a-1',), 0, 0), None, (), ('fulltext', False)),
 
1556
            (b'a-2',): (((b'a-2',), 0, 0), None, ((b'a-1',),), ('fulltext', False)),
 
1557
            (b'a-3',): (((b'a-3',), 0, 0), None, ((b'a-2',),), ('fulltext', False)),
1533
1558
            }, idx.get_build_details(idx.keys()))
1534
 
        self.assertEqual({('a-1',):(),
1535
 
            ('a-2',):(('a-1',),),
1536
 
            ('a-3',):(('a-2',),),},
1537
 
            idx.get_parent_map(idx.keys()))
 
1559
        self.assertEqual({(b'a-1',): (),
 
1560
                          (b'a-2',): ((b'a-1',),),
 
1561
                          (b'a-3',): ((b'a-2',),), },
 
1562
                         idx.get_parent_map(idx.keys()))
1538
1563
 
1539
1564
    def test_add_versions_fails_clean(self):
1540
1565
        """If add_versions fails in the middle, it restores a pristine state.
1550
1575
 
1551
1576
        knit = self.make_test_knit()
1552
1577
        idx = knit._index
1553
 
        idx.add_records([(('a-1',), ['fulltext'], (('a-1',), 0, 0), [])])
 
1578
        idx.add_records([((b'a-1',), [b'fulltext'], ((b'a-1',), 0, 0), [])])
1554
1579
 
1555
1580
        class StopEarly(Exception):
1556
1581
            pass
1557
1582
 
1558
1583
        def generate_failure():
1559
1584
            """Add some entries and then raise an exception"""
1560
 
            yield (('a-2',), ['fulltext'], (None, 0, 0), ('a-1',))
1561
 
            yield (('a-3',), ['fulltext'], (None, 0, 0), ('a-2',))
 
1585
            yield ((b'a-2',), [b'fulltext'], (None, 0, 0), (b'a-1',))
 
1586
            yield ((b'a-3',), [b'fulltext'], (None, 0, 0), (b'a-2',))
1562
1587
            raise StopEarly()
1563
1588
 
1564
1589
        # Assert the pre-condition
1565
1590
        def assertA1Only():
1566
 
            self.assertEqual(set([('a-1',)]), set(idx.keys()))
 
1591
            self.assertEqual({(b'a-1',)}, set(idx.keys()))
1567
1592
            self.assertEqual(
1568
 
                {('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False))},
1569
 
                idx.get_build_details([('a-1',)]))
1570
 
            self.assertEqual({('a-1',):()}, idx.get_parent_map(idx.keys()))
 
1593
                {(b'a-1',): (((b'a-1',), 0, 0), None, (), ('fulltext', False))},
 
1594
                idx.get_build_details([(b'a-1',)]))
 
1595
            self.assertEqual({(b'a-1',): ()}, idx.get_parent_map(idx.keys()))
1571
1596
 
1572
1597
        assertA1Only()
1573
1598
        self.assertRaises(StopEarly, idx.add_records, generate_failure())
1579
1604
        # could leave an empty .kndx file, which bzr would later claim was a
1580
1605
        # corrupted file since the header was not present. In reality, the file
1581
1606
        # just wasn't created, so it should be ignored.
1582
 
        t = get_transport('.')
1583
 
        t.put_bytes('test.kndx', '')
 
1607
        t = transport.get_transport_from_path('.')
 
1608
        t.put_bytes('test.kndx', b'')
1584
1609
 
1585
1610
        knit = self.make_test_knit()
1586
1611
 
1587
1612
    def test_knit_index_checks_header(self):
1588
 
        t = get_transport('.')
1589
 
        t.put_bytes('test.kndx', '# not really a knit header\n\n')
 
1613
        t = transport.get_transport_from_path('.')
 
1614
        t.put_bytes('test.kndx', b'# not really a knit header\n\n')
1590
1615
        k = self.make_test_knit()
1591
1616
        self.assertRaises(KnitHeaderError, k.keys)
1592
1617
 
1613
1638
        if deltas:
1614
1639
            # delta compression inn the index
1615
1640
            index1 = self.make_g_index('1', 2, [
1616
 
                (('tip', ), 'N0 100', ([('parent', )], [], )),
1617
 
                (('tail', ), '', ([], []))])
 
1641
                ((b'tip', ), b'N0 100', ([(b'parent', )], [], )),
 
1642
                ((b'tail', ), b'', ([], []))])
1618
1643
            index2 = self.make_g_index('2', 2, [
1619
 
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], [('tail', )])),
1620
 
                (('separate', ), '', ([], []))])
 
1644
                ((b'parent', ), b' 100 78',
 
1645
                 ([(b'tail', ), (b'ghost', )], [(b'tail', )])),
 
1646
                ((b'separate', ), b'', ([], []))])
1621
1647
        else:
1622
1648
            # just blob location and graph in the index.
1623
1649
            index1 = self.make_g_index('1', 1, [
1624
 
                (('tip', ), 'N0 100', ([('parent', )], )),
1625
 
                (('tail', ), '', ([], ))])
 
1650
                ((b'tip', ), b'N0 100', ([(b'parent', )], )),
 
1651
                ((b'tail', ), b'', ([], ))])
1626
1652
            index2 = self.make_g_index('2', 1, [
1627
 
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], )),
1628
 
                (('separate', ), '', ([], ))])
 
1653
                ((b'parent', ), b' 100 78', ([(b'tail', ), (b'ghost', )], )),
 
1654
                ((b'separate', ), b'', ([], ))])
1629
1655
        combined_index = CombinedGraphIndex([index1, index2])
1630
1656
        if catch_adds:
1631
1657
            self.combined_index = combined_index
1633
1659
            add_callback = self.catch_add
1634
1660
        else:
1635
1661
            add_callback = None
1636
 
        return _KnitGraphIndex(combined_index, lambda:True, deltas=deltas,
1637
 
            add_callback=add_callback)
 
1662
        return _KnitGraphIndex(combined_index, lambda: True, deltas=deltas,
 
1663
                               add_callback=add_callback)
1638
1664
 
1639
1665
    def test_keys(self):
1640
1666
        index = self.two_graph_index()
1641
 
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1642
 
            set(index.keys()))
 
1667
        self.assertEqual({(b'tail',), (b'tip',), (b'parent',), (b'separate',)},
 
1668
                         set(index.keys()))
1643
1669
 
1644
1670
    def test_get_position(self):
1645
1671
        index = self.two_graph_index()
1646
 
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position(('tip',)))
1647
 
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position(('parent',)))
 
1672
        self.assertEqual(
 
1673
            (index._graph_index._indices[0], 0, 100), index.get_position((b'tip',)))
 
1674
        self.assertEqual(
 
1675
            (index._graph_index._indices[1], 100, 78), index.get_position((b'parent',)))
1648
1676
 
1649
1677
    def test_get_method_deltas(self):
1650
1678
        index = self.two_graph_index(deltas=True)
1651
 
        self.assertEqual('fulltext', index.get_method(('tip',)))
1652
 
        self.assertEqual('line-delta', index.get_method(('parent',)))
 
1679
        self.assertEqual('fulltext', index.get_method((b'tip',)))
 
1680
        self.assertEqual('line-delta', index.get_method((b'parent',)))
1653
1681
 
1654
1682
    def test_get_method_no_deltas(self):
1655
1683
        # check that the parent-history lookup is ignored with deltas=False.
1656
1684
        index = self.two_graph_index(deltas=False)
1657
 
        self.assertEqual('fulltext', index.get_method(('tip',)))
1658
 
        self.assertEqual('fulltext', index.get_method(('parent',)))
 
1685
        self.assertEqual('fulltext', index.get_method((b'tip',)))
 
1686
        self.assertEqual('fulltext', index.get_method((b'parent',)))
1659
1687
 
1660
1688
    def test_get_options_deltas(self):
1661
1689
        index = self.two_graph_index(deltas=True)
1662
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1663
 
        self.assertEqual(['line-delta'], index.get_options(('parent',)))
 
1690
        self.assertEqual([b'fulltext', b'no-eol'],
 
1691
                         index.get_options((b'tip',)))
 
1692
        self.assertEqual([b'line-delta'], index.get_options((b'parent',)))
1664
1693
 
1665
1694
    def test_get_options_no_deltas(self):
1666
1695
        # check that the parent-history lookup is ignored with deltas=False.
1667
1696
        index = self.two_graph_index(deltas=False)
1668
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1669
 
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
 
1697
        self.assertEqual([b'fulltext', b'no-eol'],
 
1698
                         index.get_options((b'tip',)))
 
1699
        self.assertEqual([b'fulltext'], index.get_options((b'parent',)))
1670
1700
 
1671
1701
    def test_get_parent_map(self):
1672
1702
        index = self.two_graph_index()
1673
 
        self.assertEqual({('parent',):(('tail',), ('ghost',))},
1674
 
            index.get_parent_map([('parent',), ('ghost',)]))
 
1703
        self.assertEqual({(b'parent',): ((b'tail',), (b'ghost',))},
 
1704
                         index.get_parent_map([(b'parent',), (b'ghost',)]))
1675
1705
 
1676
1706
    def catch_add(self, entries):
1677
1707
        self.caught_entries.append(entries)
1679
1709
    def test_add_no_callback_errors(self):
1680
1710
        index = self.two_graph_index()
1681
1711
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1682
 
            [(('new',), 'fulltext,no-eol', (None, 50, 60), ['separate'])])
 
1712
                          [((b'new',), b'fulltext,no-eol', (None, 50, 60), [b'separate'])])
1683
1713
 
1684
1714
    def test_add_version_smoke(self):
1685
1715
        index = self.two_graph_index(catch_adds=True)
1686
 
        index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60),
1687
 
            [('separate',)])])
1688
 
        self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
1689
 
            self.caught_entries)
 
1716
        index.add_records([((b'new',), b'fulltext,no-eol', (None, 50, 60),
 
1717
                            [(b'separate',)])])
 
1718
        self.assertEqual([[((b'new', ), b'N50 60', (((b'separate',),),))]],
 
1719
                         self.caught_entries)
1690
1720
 
1691
1721
    def test_add_version_delta_not_delta_index(self):
1692
1722
        index = self.two_graph_index(catch_adds=True)
1693
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1694
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
 
1723
        self.assertRaises(KnitCorrupt, index.add_records,
 
1724
                          [((b'new',), b'no-eol,line-delta', (None, 0, 100), [(b'parent',)])])
1695
1725
        self.assertEqual([], self.caught_entries)
1696
1726
 
1697
1727
    def test_add_version_same_dup(self):
1698
1728
        index = self.two_graph_index(catch_adds=True)
1699
1729
        # options can be spelt two different ways
1700
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1701
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
 
1730
        index.add_records(
 
1731
            [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [(b'parent',)])])
 
1732
        index.add_records(
 
1733
            [((b'tip',), b'no-eol,fulltext', (None, 0, 100), [(b'parent',)])])
1702
1734
        # position/length are ignored (because each pack could have fulltext or
1703
1735
        # delta, and be at a different position.
1704
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1705
 
            [('parent',)])])
1706
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1707
 
            [('parent',)])])
 
1736
        index.add_records([((b'tip',), b'fulltext,no-eol', (None, 50, 100),
 
1737
                            [(b'parent',)])])
 
1738
        index.add_records([((b'tip',), b'fulltext,no-eol', (None, 0, 1000),
 
1739
                            [(b'parent',)])])
1708
1740
        # but neither should have added data:
1709
1741
        self.assertEqual([[], [], [], []], self.caught_entries)
1710
1742
 
1711
1743
    def test_add_version_different_dup(self):
1712
1744
        index = self.two_graph_index(deltas=True, catch_adds=True)
1713
1745
        # change options
1714
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1715
 
            [(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1716
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1717
 
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
 
1746
        self.assertRaises(KnitCorrupt, index.add_records,
 
1747
                          [((b'tip',), b'line-delta', (None, 0, 100), [(b'parent',)])])
 
1748
        self.assertRaises(KnitCorrupt, index.add_records,
 
1749
                          [((b'tip',), b'fulltext', (None, 0, 100), [(b'parent',)])])
1718
1750
        # parents
1719
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1720
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
 
1751
        self.assertRaises(KnitCorrupt, index.add_records,
 
1752
                          [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [])])
1721
1753
        self.assertEqual([], self.caught_entries)
1722
1754
 
1723
1755
    def test_add_versions_nodeltas(self):
1724
1756
        index = self.two_graph_index(catch_adds=True)
1725
1757
        index.add_records([
1726
 
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1727
 
                (('new2',), 'fulltext', (None, 0, 6), [('new',)]),
1728
 
                ])
1729
 
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
1730
 
            (('new2', ), ' 0 6', ((('new',),),))],
1731
 
            sorted(self.caught_entries[0]))
 
1758
            ((b'new',), b'fulltext,no-eol', (None, 50, 60), [(b'separate',)]),
 
1759
            ((b'new2',), b'fulltext', (None, 0, 6), [(b'new',)]),
 
1760
            ])
 
1761
        self.assertEqual([((b'new', ), b'N50 60', (((b'separate',),),)),
 
1762
                          ((b'new2', ), b' 0 6', (((b'new',),),))],
 
1763
                         sorted(self.caught_entries[0]))
1732
1764
        self.assertEqual(1, len(self.caught_entries))
1733
1765
 
1734
1766
    def test_add_versions_deltas(self):
1735
1767
        index = self.two_graph_index(deltas=True, catch_adds=True)
1736
1768
        index.add_records([
1737
 
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1738
 
                (('new2',), 'line-delta', (None, 0, 6), [('new',)]),
1739
 
                ])
1740
 
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
1741
 
            (('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
1742
 
            sorted(self.caught_entries[0]))
 
1769
            ((b'new',), b'fulltext,no-eol', (None, 50, 60), [(b'separate',)]),
 
1770
            ((b'new2',), b'line-delta', (None, 0, 6), [(b'new',)]),
 
1771
            ])
 
1772
        self.assertEqual([((b'new', ), b'N50 60', (((b'separate',),), ())),
 
1773
                          ((b'new2', ), b' 0 6', (((b'new',),), ((b'new',),), ))],
 
1774
                         sorted(self.caught_entries[0]))
1743
1775
        self.assertEqual(1, len(self.caught_entries))
1744
1776
 
1745
1777
    def test_add_versions_delta_not_delta_index(self):
1746
1778
        index = self.two_graph_index(catch_adds=True)
1747
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1748
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
 
1779
        self.assertRaises(KnitCorrupt, index.add_records,
 
1780
                          [((b'new',), b'no-eol,line-delta', (None, 0, 100), [(b'parent',)])])
1749
1781
        self.assertEqual([], self.caught_entries)
1750
1782
 
1751
1783
    def test_add_versions_random_id_accepted(self):
1755
1787
    def test_add_versions_same_dup(self):
1756
1788
        index = self.two_graph_index(catch_adds=True)
1757
1789
        # options can be spelt two different ways
1758
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100),
1759
 
            [('parent',)])])
1760
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100),
1761
 
            [('parent',)])])
 
1790
        index.add_records([((b'tip',), b'fulltext,no-eol', (None, 0, 100),
 
1791
                            [(b'parent',)])])
 
1792
        index.add_records([((b'tip',), b'no-eol,fulltext', (None, 0, 100),
 
1793
                            [(b'parent',)])])
1762
1794
        # position/length are ignored (because each pack could have fulltext or
1763
1795
        # delta, and be at a different position.
1764
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1765
 
            [('parent',)])])
1766
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1767
 
            [('parent',)])])
 
1796
        index.add_records([((b'tip',), b'fulltext,no-eol', (None, 50, 100),
 
1797
                            [(b'parent',)])])
 
1798
        index.add_records([((b'tip',), b'fulltext,no-eol', (None, 0, 1000),
 
1799
                            [(b'parent',)])])
1768
1800
        # but neither should have added data.
1769
1801
        self.assertEqual([[], [], [], []], self.caught_entries)
1770
1802
 
1771
1803
    def test_add_versions_different_dup(self):
1772
1804
        index = self.two_graph_index(deltas=True, catch_adds=True)
1773
1805
        # change options
1774
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1775
 
            [(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1776
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1777
 
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
 
1806
        self.assertRaises(KnitCorrupt, index.add_records,
 
1807
                          [((b'tip',), b'line-delta', (None, 0, 100), [(b'parent',)])])
 
1808
        self.assertRaises(KnitCorrupt, index.add_records,
 
1809
                          [((b'tip',), b'fulltext', (None, 0, 100), [(b'parent',)])])
1778
1810
        # parents
1779
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1780
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
 
1811
        self.assertRaises(KnitCorrupt, index.add_records,
 
1812
                          [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [])])
1781
1813
        # change options in the second record
1782
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1783
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)]),
1784
 
             (('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
 
1814
        self.assertRaises(KnitCorrupt, index.add_records,
 
1815
                          [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [(b'parent',)]),
 
1816
                           ((b'tip',), b'line-delta', (None, 0, 100), [(b'parent',)])])
1785
1817
        self.assertEqual([], self.caught_entries)
1786
1818
 
1787
1819
    def make_g_index_missing_compression_parent(self):
1788
1820
        graph_index = self.make_g_index('missing_comp', 2,
1789
 
            [(('tip', ), ' 100 78',
1790
 
              ([('missing-parent', ), ('ghost', )], [('missing-parent', )]))])
 
1821
                                        [((b'tip', ), b' 100 78',
 
1822
                                          ([(b'missing-parent', ), (b'ghost', )], [(b'missing-parent', )]))])
1791
1823
        return graph_index
1792
1824
 
1793
1825
    def make_g_index_missing_parent(self):
1794
1826
        graph_index = self.make_g_index('missing_parent', 2,
1795
 
            [(('parent', ), ' 100 78', ([], [])),
1796
 
             (('tip', ), ' 100 78',
1797
 
              ([('parent', ), ('missing-parent', )], [('parent', )])),
1798
 
              ])
 
1827
                                        [((b'parent', ), b' 100 78', ([], [])),
 
1828
                                         ((b'tip', ), b' 100 78',
 
1829
                                            ([(b'parent', ), (b'missing-parent', )], [(b'parent', )])),
 
1830
                                         ])
1799
1831
        return graph_index
1800
1832
 
1801
1833
    def make_g_index_no_external_refs(self):
1802
1834
        graph_index = self.make_g_index('no_external_refs', 2,
1803
 
            [(('rev', ), ' 100 78',
1804
 
              ([('parent', ), ('ghost', )], []))])
 
1835
                                        [((b'rev', ), b' 100 78',
 
1836
                                          ([(b'parent', ), (b'ghost', )], []))])
1805
1837
        return graph_index
1806
1838
 
1807
1839
    def test_add_good_unvalidated_index(self):
1820
1852
        # examined, otherwise 'ghost' would also be reported as a missing
1821
1853
        # parent.
1822
1854
        self.assertEqual(
1823
 
            frozenset([('missing-parent',)]),
 
1855
            frozenset([(b'missing-parent',)]),
1824
1856
            index.get_missing_compression_parents())
1825
1857
 
1826
1858
    def test_add_missing_noncompression_parent_unvalidated_index(self):
1827
1859
        unvalidated = self.make_g_index_missing_parent()
1828
1860
        combined = CombinedGraphIndex([unvalidated])
1829
1861
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
1830
 
            track_external_parent_refs=True)
 
1862
                                track_external_parent_refs=True)
1831
1863
        index.scan_unvalidated_index(unvalidated)
1832
1864
        self.assertEqual(
1833
 
            frozenset([('missing-parent',)]), index.get_missing_parents())
 
1865
            frozenset([(b'missing-parent',)]), index.get_missing_parents())
1834
1866
 
1835
1867
    def test_track_external_parent_refs(self):
1836
1868
        g_index = self.make_g_index('empty', 2, [])
1837
1869
        combined = CombinedGraphIndex([g_index])
1838
1870
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
1839
 
            add_callback=self.catch_add, track_external_parent_refs=True)
 
1871
                                add_callback=self.catch_add, track_external_parent_refs=True)
1840
1872
        self.caught_entries = []
1841
1873
        index.add_records([
1842
 
            (('new-key',), 'fulltext,no-eol', (None, 50, 60),
1843
 
             [('parent-1',), ('parent-2',)])])
 
1874
            ((b'new-key',), b'fulltext,no-eol', (None, 50, 60),
 
1875
             [(b'parent-1',), (b'parent-2',)])])
1844
1876
        self.assertEqual(
1845
 
            frozenset([('parent-1',), ('parent-2',)]),
 
1877
            frozenset([(b'parent-1',), (b'parent-2',)]),
1846
1878
            index.get_missing_parents())
1847
1879
 
1848
1880
    def test_add_unvalidated_index_with_present_external_references(self):
1856
1888
        self.assertEqual(frozenset(), index.get_missing_compression_parents())
1857
1889
 
1858
1890
    def make_new_missing_parent_g_index(self, name):
1859
 
        missing_parent = name + '-missing-parent'
 
1891
        missing_parent = name.encode('ascii') + b'-missing-parent'
1860
1892
        graph_index = self.make_g_index(name, 2,
1861
 
            [((name + 'tip', ), ' 100 78',
1862
 
              ([(missing_parent, ), ('ghost', )], [(missing_parent, )]))])
 
1893
                                        [((name.encode('ascii') + b'tip', ), b' 100 78',
 
1894
                                          ([(missing_parent, ), (b'ghost', )], [(missing_parent, )]))])
1863
1895
        return graph_index
1864
1896
 
1865
1897
    def test_add_mulitiple_unvalidated_indices_with_missing_parents(self):
1870
1902
        index.scan_unvalidated_index(g_index_1)
1871
1903
        index.scan_unvalidated_index(g_index_2)
1872
1904
        self.assertEqual(
1873
 
            frozenset([('one-missing-parent',), ('two-missing-parent',)]),
 
1905
            frozenset([(b'one-missing-parent',), (b'two-missing-parent',)]),
1874
1906
            index.get_missing_compression_parents())
1875
1907
 
1876
1908
    def test_add_mulitiple_unvalidated_indices_with_mutual_dependencies(self):
1877
1909
        graph_index_a = self.make_g_index('one', 2,
1878
 
            [(('parent-one', ), ' 100 78', ([('non-compression-parent',)], [])),
1879
 
             (('child-of-two', ), ' 100 78',
1880
 
              ([('parent-two',)], [('parent-two',)]))])
 
1910
                                          [((b'parent-one', ), b' 100 78', ([(b'non-compression-parent',)], [])),
 
1911
                                           ((b'child-of-two', ), b' 100 78',
 
1912
                                              ([(b'parent-two',)], [(b'parent-two',)]))])
1881
1913
        graph_index_b = self.make_g_index('two', 2,
1882
 
            [(('parent-two', ), ' 100 78', ([('non-compression-parent',)], [])),
1883
 
             (('child-of-one', ), ' 100 78',
1884
 
              ([('parent-one',)], [('parent-one',)]))])
 
1914
                                          [((b'parent-two', ), b' 100 78', ([(b'non-compression-parent',)], [])),
 
1915
                                           ((b'child-of-one', ), b' 100 78',
 
1916
                                              ([(b'parent-one',)], [(b'parent-one',)]))])
1885
1917
        combined = CombinedGraphIndex([graph_index_a, graph_index_b])
1886
1918
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
1887
1919
        index.scan_unvalidated_index(graph_index_a)
1908
1940
        index = _KnitGraphIndex(combined, lambda: True, parents=False)
1909
1941
        index.scan_unvalidated_index(unvalidated)
1910
1942
        self.assertEqual(frozenset(),
1911
 
            index.get_missing_compression_parents())
 
1943
                         index.get_missing_compression_parents())
1912
1944
 
1913
1945
    def test_parents_deltas_incompatible(self):
1914
1946
        index = CombinedGraphIndex([])
1915
 
        self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1916
 
            index, deltas=True, parents=False)
 
1947
        self.assertRaises(knit.KnitError, _KnitGraphIndex, lambda: True,
 
1948
                          index, deltas=True, parents=False)
1917
1949
 
1918
1950
    def two_graph_index(self, catch_adds=False):
1919
1951
        """Build a two-graph index.
1923
1955
        """
1924
1956
        # put several versions in the index.
1925
1957
        index1 = self.make_g_index('1', 0, [
1926
 
            (('tip', ), 'N0 100'),
1927
 
            (('tail', ), '')])
 
1958
            ((b'tip', ), b'N0 100'),
 
1959
            ((b'tail', ), b'')])
1928
1960
        index2 = self.make_g_index('2', 0, [
1929
 
            (('parent', ), ' 100 78'),
1930
 
            (('separate', ), '')])
 
1961
            ((b'parent', ), b' 100 78'),
 
1962
            ((b'separate', ), b'')])
1931
1963
        combined_index = CombinedGraphIndex([index1, index2])
1932
1964
        if catch_adds:
1933
1965
            self.combined_index = combined_index
1935
1967
            add_callback = self.catch_add
1936
1968
        else:
1937
1969
            add_callback = None
1938
 
        return _KnitGraphIndex(combined_index, lambda:True, parents=False,
1939
 
            add_callback=add_callback)
 
1970
        return _KnitGraphIndex(combined_index, lambda: True, parents=False,
 
1971
                               add_callback=add_callback)
1940
1972
 
1941
1973
    def test_keys(self):
1942
1974
        index = self.two_graph_index()
1943
 
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1944
 
            set(index.keys()))
 
1975
        self.assertEqual({(b'tail',), (b'tip',), (b'parent',), (b'separate',)},
 
1976
                         set(index.keys()))
1945
1977
 
1946
1978
    def test_get_position(self):
1947
1979
        index = self.two_graph_index()
1948
1980
        self.assertEqual((index._graph_index._indices[0], 0, 100),
1949
 
            index.get_position(('tip',)))
 
1981
                         index.get_position((b'tip',)))
1950
1982
        self.assertEqual((index._graph_index._indices[1], 100, 78),
1951
 
            index.get_position(('parent',)))
 
1983
                         index.get_position((b'parent',)))
1952
1984
 
1953
1985
    def test_get_method(self):
1954
1986
        index = self.two_graph_index()
1955
 
        self.assertEqual('fulltext', index.get_method(('tip',)))
1956
 
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
 
1987
        self.assertEqual('fulltext', index.get_method((b'tip',)))
 
1988
        self.assertEqual([b'fulltext'], index.get_options((b'parent',)))
1957
1989
 
1958
1990
    def test_get_options(self):
1959
1991
        index = self.two_graph_index()
1960
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1961
 
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
 
1992
        self.assertEqual([b'fulltext', b'no-eol'],
 
1993
                         index.get_options((b'tip',)))
 
1994
        self.assertEqual([b'fulltext'], index.get_options((b'parent',)))
1962
1995
 
1963
1996
    def test_get_parent_map(self):
1964
1997
        index = self.two_graph_index()
1965
 
        self.assertEqual({('parent',):None},
1966
 
            index.get_parent_map([('parent',), ('ghost',)]))
 
1998
        self.assertEqual({(b'parent',): None},
 
1999
                         index.get_parent_map([(b'parent',), (b'ghost',)]))
1967
2000
 
1968
2001
    def catch_add(self, entries):
1969
2002
        self.caught_entries.append(entries)
1971
2004
    def test_add_no_callback_errors(self):
1972
2005
        index = self.two_graph_index()
1973
2006
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1974
 
            [(('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)])])
 
2007
                          [((b'new',), b'fulltext,no-eol', (None, 50, 60), [(b'separate',)])])
1975
2008
 
1976
2009
    def test_add_version_smoke(self):
1977
2010
        index = self.two_graph_index(catch_adds=True)
1978
 
        index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60), [])])
1979
 
        self.assertEqual([[(('new', ), 'N50 60')]],
1980
 
            self.caught_entries)
 
2011
        index.add_records(
 
2012
            [((b'new',), b'fulltext,no-eol', (None, 50, 60), [])])
 
2013
        self.assertEqual([[((b'new', ), b'N50 60')]],
 
2014
                         self.caught_entries)
1981
2015
 
1982
2016
    def test_add_version_delta_not_delta_index(self):
1983
2017
        index = self.two_graph_index(catch_adds=True)
1984
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1985
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [])])
 
2018
        self.assertRaises(KnitCorrupt, index.add_records,
 
2019
                          [((b'new',), b'no-eol,line-delta', (None, 0, 100), [])])
1986
2020
        self.assertEqual([], self.caught_entries)
1987
2021
 
1988
2022
    def test_add_version_same_dup(self):
1989
2023
        index = self.two_graph_index(catch_adds=True)
1990
2024
        # options can be spelt two different ways
1991
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1992
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
 
2025
        index.add_records(
 
2026
            [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [])])
 
2027
        index.add_records(
 
2028
            [((b'tip',), b'no-eol,fulltext', (None, 0, 100), [])])
1993
2029
        # position/length are ignored (because each pack could have fulltext or
1994
2030
        # delta, and be at a different position.
1995
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1996
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
 
2031
        index.add_records(
 
2032
            [((b'tip',), b'fulltext,no-eol', (None, 50, 100), [])])
 
2033
        index.add_records(
 
2034
            [((b'tip',), b'fulltext,no-eol', (None, 0, 1000), [])])
1997
2035
        # but neither should have added data.
1998
2036
        self.assertEqual([[], [], [], []], self.caught_entries)
1999
2037
 
2000
2038
    def test_add_version_different_dup(self):
2001
2039
        index = self.two_graph_index(catch_adds=True)
2002
2040
        # change options
2003
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2004
 
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2005
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2006
 
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
2007
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2008
 
            [(('tip',), 'fulltext', (None, 0, 100), [])])
 
2041
        self.assertRaises(KnitCorrupt, index.add_records,
 
2042
                          [((b'tip',), b'no-eol,line-delta', (None, 0, 100), [])])
 
2043
        self.assertRaises(KnitCorrupt, index.add_records,
 
2044
                          [((b'tip',), b'line-delta,no-eol', (None, 0, 100), [])])
 
2045
        self.assertRaises(KnitCorrupt, index.add_records,
 
2046
                          [((b'tip',), b'fulltext', (None, 0, 100), [])])
2009
2047
        # parents
2010
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2011
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
 
2048
        self.assertRaises(KnitCorrupt, index.add_records,
 
2049
                          [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [(b'parent',)])])
2012
2050
        self.assertEqual([], self.caught_entries)
2013
2051
 
2014
2052
    def test_add_versions(self):
2015
2053
        index = self.two_graph_index(catch_adds=True)
2016
2054
        index.add_records([
2017
 
                (('new',), 'fulltext,no-eol', (None, 50, 60), []),
2018
 
                (('new2',), 'fulltext', (None, 0, 6), []),
2019
 
                ])
2020
 
        self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
2021
 
            sorted(self.caught_entries[0]))
 
2055
            ((b'new',), b'fulltext,no-eol', (None, 50, 60), []),
 
2056
            ((b'new2',), b'fulltext', (None, 0, 6), []),
 
2057
            ])
 
2058
        self.assertEqual([((b'new', ), b'N50 60'), ((b'new2', ), b' 0 6')],
 
2059
                         sorted(self.caught_entries[0]))
2022
2060
        self.assertEqual(1, len(self.caught_entries))
2023
2061
 
2024
2062
    def test_add_versions_delta_not_delta_index(self):
2025
2063
        index = self.two_graph_index(catch_adds=True)
2026
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2027
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
 
2064
        self.assertRaises(KnitCorrupt, index.add_records,
 
2065
                          [((b'new',), b'no-eol,line-delta', (None, 0, 100), [(b'parent',)])])
2028
2066
        self.assertEqual([], self.caught_entries)
2029
2067
 
2030
2068
    def test_add_versions_parents_not_parents_index(self):
2031
2069
        index = self.two_graph_index(catch_adds=True)
2032
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2033
 
            [(('new',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
 
2070
        self.assertRaises(KnitCorrupt, index.add_records,
 
2071
                          [((b'new',), b'no-eol,fulltext', (None, 0, 100), [(b'parent',)])])
2034
2072
        self.assertEqual([], self.caught_entries)
2035
2073
 
2036
2074
    def test_add_versions_random_id_accepted(self):
2040
2078
    def test_add_versions_same_dup(self):
2041
2079
        index = self.two_graph_index(catch_adds=True)
2042
2080
        # options can be spelt two different ways
2043
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
2044
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
 
2081
        index.add_records(
 
2082
            [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [])])
 
2083
        index.add_records(
 
2084
            [((b'tip',), b'no-eol,fulltext', (None, 0, 100), [])])
2045
2085
        # position/length are ignored (because each pack could have fulltext or
2046
2086
        # delta, and be at a different position.
2047
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
2048
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
 
2087
        index.add_records(
 
2088
            [((b'tip',), b'fulltext,no-eol', (None, 50, 100), [])])
 
2089
        index.add_records(
 
2090
            [((b'tip',), b'fulltext,no-eol', (None, 0, 1000), [])])
2049
2091
        # but neither should have added data.
2050
2092
        self.assertEqual([[], [], [], []], self.caught_entries)
2051
2093
 
2052
2094
    def test_add_versions_different_dup(self):
2053
2095
        index = self.two_graph_index(catch_adds=True)
2054
2096
        # change options
2055
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2056
 
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2057
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2058
 
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
2059
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2060
 
            [(('tip',), 'fulltext', (None, 0, 100), [])])
 
2097
        self.assertRaises(KnitCorrupt, index.add_records,
 
2098
                          [((b'tip',), b'no-eol,line-delta', (None, 0, 100), [])])
 
2099
        self.assertRaises(KnitCorrupt, index.add_records,
 
2100
                          [((b'tip',), b'line-delta,no-eol', (None, 0, 100), [])])
 
2101
        self.assertRaises(KnitCorrupt, index.add_records,
 
2102
                          [((b'tip',), b'fulltext', (None, 0, 100), [])])
2061
2103
        # parents
2062
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2063
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
 
2104
        self.assertRaises(KnitCorrupt, index.add_records,
 
2105
                          [((b'tip',), b'fulltext,no-eol', (None, 0, 100), [(b'parent',)])])
2064
2106
        # change options in the second record
2065
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2066
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), []),
2067
 
             (('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
 
2107
        self.assertRaises(KnitCorrupt, index.add_records,
 
2108
                          [((b'tip',), b'fulltext,no-eol', (None, 0, 100), []),
 
2109
                           ((b'tip',), b'no-eol,line-delta', (None, 0, 100), [])])
2068
2110
        self.assertEqual([], self.caught_entries)
2069
2111
 
2070
2112
 
2076
2118
        if _min_buffer_size is None:
2077
2119
            _min_buffer_size = knit._STREAM_MIN_BUFFER_SIZE
2078
2120
        self.assertEqual(exp_groups, kvf._group_keys_for_io(keys,
2079
 
                                        non_local_keys, positions,
2080
 
                                        _min_buffer_size=_min_buffer_size))
 
2121
                                                            non_local_keys, positions,
 
2122
                                                            _min_buffer_size=_min_buffer_size))
2081
2123
 
2082
2124
    def assertSplitByPrefix(self, expected_map, expected_prefix_order,
2083
2125
                            keys):
2088
2130
    def test__group_keys_for_io(self):
2089
2131
        ft_detail = ('fulltext', False)
2090
2132
        ld_detail = ('line-delta', False)
2091
 
        f_a = ('f', 'a')
2092
 
        f_b = ('f', 'b')
2093
 
        f_c = ('f', 'c')
2094
 
        g_a = ('g', 'a')
2095
 
        g_b = ('g', 'b')
2096
 
        g_c = ('g', 'c')
 
2133
        f_a = (b'f', b'a')
 
2134
        f_b = (b'f', b'b')
 
2135
        f_c = (b'f', b'c')
 
2136
        g_a = (b'g', b'a')
 
2137
        g_b = (b'g', b'b')
 
2138
        g_c = (b'g', b'c')
2097
2139
        positions = {
2098
2140
            f_a: (ft_detail, (f_a, 0, 100), None),
2099
2141
            f_b: (ld_detail, (f_b, 100, 21), f_a),
2104
2146
            }
2105
2147
        self.assertGroupKeysForIo([([f_a], set())],
2106
2148
                                  [f_a], [], positions)
2107
 
        self.assertGroupKeysForIo([([f_a], set([f_a]))],
 
2149
        self.assertGroupKeysForIo([([f_a], {f_a})],
2108
2150
                                  [f_a], [f_a], positions)
2109
2151
        self.assertGroupKeysForIo([([f_a, f_b], set([]))],
2110
2152
                                  [f_a, f_b], [], positions)
2111
 
        self.assertGroupKeysForIo([([f_a, f_b], set([f_b]))],
 
2153
        self.assertGroupKeysForIo([([f_a, f_b], {f_b})],
2112
2154
                                  [f_a, f_b], [f_b], positions)
2113
2155
        self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
2114
2156
                                  [f_a, g_a, f_b, g_b], [], positions)
2126
2168
                                  _min_buffer_size=125)
2127
2169
 
2128
2170
    def test__split_by_prefix(self):
2129
 
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2130
 
                                  'g': [('g', 'b'), ('g', 'a')],
2131
 
                                 }, ['f', 'g'],
2132
 
                                 [('f', 'a'), ('g', 'b'),
2133
 
                                  ('g', 'a'), ('f', 'b')])
2134
 
 
2135
 
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2136
 
                                  'g': [('g', 'b'), ('g', 'a')],
2137
 
                                 }, ['f', 'g'],
2138
 
                                 [('f', 'a'), ('f', 'b'),
2139
 
                                  ('g', 'b'), ('g', 'a')])
2140
 
 
2141
 
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2142
 
                                  'g': [('g', 'b'), ('g', 'a')],
2143
 
                                 }, ['f', 'g'],
2144
 
                                 [('f', 'a'), ('f', 'b'),
2145
 
                                  ('g', 'b'), ('g', 'a')])
2146
 
 
2147
 
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2148
 
                                  'g': [('g', 'b'), ('g', 'a')],
2149
 
                                  '': [('a',), ('b',)]
2150
 
                                 }, ['f', 'g', ''],
2151
 
                                 [('f', 'a'), ('g', 'b'),
2152
 
                                  ('a',), ('b',),
2153
 
                                  ('g', 'a'), ('f', 'b')])
 
2171
        self.assertSplitByPrefix({b'f': [(b'f', b'a'), (b'f', b'b')],
 
2172
                                  b'g': [(b'g', b'b'), (b'g', b'a')],
 
2173
                                  }, [b'f', b'g'],
 
2174
                                 [(b'f', b'a'), (b'g', b'b'),
 
2175
                                  (b'g', b'a'), (b'f', b'b')])
 
2176
 
 
2177
        self.assertSplitByPrefix({b'f': [(b'f', b'a'), (b'f', b'b')],
 
2178
                                  b'g': [(b'g', b'b'), (b'g', b'a')],
 
2179
                                  }, [b'f', b'g'],
 
2180
                                 [(b'f', b'a'), (b'f', b'b'),
 
2181
                                  (b'g', b'b'), (b'g', b'a')])
 
2182
 
 
2183
        self.assertSplitByPrefix({b'f': [(b'f', b'a'), (b'f', b'b')],
 
2184
                                  b'g': [(b'g', b'b'), (b'g', b'a')],
 
2185
                                  }, [b'f', b'g'],
 
2186
                                 [(b'f', b'a'), (b'f', b'b'),
 
2187
                                  (b'g', b'b'), (b'g', b'a')])
 
2188
 
 
2189
        self.assertSplitByPrefix({b'f': [(b'f', b'a'), (b'f', b'b')],
 
2190
                                  b'g': [(b'g', b'b'), (b'g', b'a')],
 
2191
                                  b'': [(b'a',), (b'b',)]
 
2192
                                  }, [b'f', b'g', b''],
 
2193
                                 [(b'f', b'a'), (b'g', b'b'),
 
2194
                                  (b'a',), (b'b',),
 
2195
                                  (b'g', b'a'), (b'f', b'b')])
2154
2196
 
2155
2197
 
2156
2198
class TestStacking(KnitTests):
2172
2214
    def test_add_lines(self):
2173
2215
        # lines added to the test are not added to the basis
2174
2216
        basis, test = self.get_basis_and_test_knit()
2175
 
        key = ('foo',)
2176
 
        key_basis = ('bar',)
2177
 
        key_cross_border = ('quux',)
2178
 
        key_delta = ('zaphod',)
2179
 
        test.add_lines(key, (), ['foo\n'])
 
2217
        key = (b'foo',)
 
2218
        key_basis = (b'bar',)
 
2219
        key_cross_border = (b'quux',)
 
2220
        key_delta = (b'zaphod',)
 
2221
        test.add_lines(key, (), [b'foo\n'])
2180
2222
        self.assertEqual({}, basis.get_parent_map([key]))
2181
2223
        # lines added to the test that reference across the stack do a
2182
2224
        # fulltext.
2183
 
        basis.add_lines(key_basis, (), ['foo\n'])
 
2225
        basis.add_lines(key_basis, (), [b'foo\n'])
2184
2226
        basis.calls = []
2185
 
        test.add_lines(key_cross_border, (key_basis,), ['foo\n'])
 
2227
        test.add_lines(key_cross_border, (key_basis,), [b'foo\n'])
2186
2228
        self.assertEqual('fulltext', test._index.get_method(key_cross_border))
2187
2229
        # we don't even need to look at the basis to see that this should be
2188
2230
        # stored as a fulltext
2189
2231
        self.assertEqual([], basis.calls)
2190
2232
        # Subsequent adds do delta.
2191
2233
        basis.calls = []
2192
 
        test.add_lines(key_delta, (key_cross_border,), ['foo\n'])
 
2234
        test.add_lines(key_delta, (key_cross_border,), [b'foo\n'])
2193
2235
        self.assertEqual('line-delta', test._index.get_method(key_delta))
2194
2236
        self.assertEqual([], basis.calls)
2195
2237
 
2196
2238
    def test_annotate(self):
2197
2239
        # annotations from the test knit are answered without asking the basis
2198
2240
        basis, test = self.get_basis_and_test_knit()
2199
 
        key = ('foo',)
2200
 
        key_basis = ('bar',)
2201
 
        key_missing = ('missing',)
2202
 
        test.add_lines(key, (), ['foo\n'])
 
2241
        key = (b'foo',)
 
2242
        key_basis = (b'bar',)
 
2243
        test.add_lines(key, (), [b'foo\n'])
2203
2244
        details = test.annotate(key)
2204
 
        self.assertEqual([(key, 'foo\n')], details)
 
2245
        self.assertEqual([(key, b'foo\n')], details)
2205
2246
        self.assertEqual([], basis.calls)
2206
2247
        # But texts that are not in the test knit are looked for in the basis
2207
2248
        # directly.
2208
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
 
2249
        basis.add_lines(key_basis, (), [b'foo\n', b'bar\n'])
2209
2250
        basis.calls = []
2210
2251
        details = test.annotate(key_basis)
2211
 
        self.assertEqual([(key_basis, 'foo\n'), (key_basis, 'bar\n')], details)
 
2252
        self.assertEqual(
 
2253
            [(key_basis, b'foo\n'), (key_basis, b'bar\n')], details)
2212
2254
        # Not optimised to date:
2213
2255
        # self.assertEqual([("annotate", key_basis)], basis.calls)
2214
 
        self.assertEqual([('get_parent_map', set([key_basis])),
2215
 
            ('get_parent_map', set([key_basis])),
2216
 
            ('get_record_stream', [key_basis], 'topological', True)],
2217
 
            basis.calls)
 
2256
        self.assertEqual([('get_parent_map', {key_basis}),
 
2257
                          ('get_parent_map', {key_basis}),
 
2258
                          ('get_record_stream', [key_basis], 'topological', True)],
 
2259
                         basis.calls)
2218
2260
 
2219
2261
    def test_check(self):
2220
2262
        # At the moment checking a stacked knit does implicitly check the
2225
2267
    def test_get_parent_map(self):
2226
2268
        # parents in the test knit are answered without asking the basis
2227
2269
        basis, test = self.get_basis_and_test_knit()
2228
 
        key = ('foo',)
2229
 
        key_basis = ('bar',)
2230
 
        key_missing = ('missing',)
 
2270
        key = (b'foo',)
 
2271
        key_basis = (b'bar',)
 
2272
        key_missing = (b'missing',)
2231
2273
        test.add_lines(key, (), [])
2232
2274
        parent_map = test.get_parent_map([key])
2233
2275
        self.assertEqual({key: ()}, parent_map)
2237
2279
        basis.calls = []
2238
2280
        parent_map = test.get_parent_map([key, key_basis, key_missing])
2239
2281
        self.assertEqual({key: (),
2240
 
            key_basis: ()}, parent_map)
2241
 
        self.assertEqual([("get_parent_map", set([key_basis, key_missing]))],
2242
 
            basis.calls)
 
2282
                          key_basis: ()}, parent_map)
 
2283
        self.assertEqual([("get_parent_map", {key_basis, key_missing})],
 
2284
                         basis.calls)
2243
2285
 
2244
2286
    def test_get_record_stream_unordered_fulltexts(self):
2245
2287
        # records from the test knit are answered without asking the basis:
2246
2288
        basis, test = self.get_basis_and_test_knit()
2247
 
        key = ('foo',)
2248
 
        key_basis = ('bar',)
2249
 
        key_missing = ('missing',)
2250
 
        test.add_lines(key, (), ['foo\n'])
 
2289
        key = (b'foo',)
 
2290
        key_basis = (b'bar',)
 
2291
        key_missing = (b'missing',)
 
2292
        test.add_lines(key, (), [b'foo\n'])
2251
2293
        records = list(test.get_record_stream([key], 'unordered', True))
2252
2294
        self.assertEqual(1, len(records))
2253
2295
        self.assertEqual([], basis.calls)
2254
2296
        # Missing (from test knit) objects are retrieved from the basis:
2255
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
 
2297
        basis.add_lines(key_basis, (), [b'foo\n', b'bar\n'])
2256
2298
        basis.calls = []
2257
2299
        records = list(test.get_record_stream([key_basis, key_missing],
2258
 
            'unordered', True))
 
2300
                                              'unordered', True))
2259
2301
        self.assertEqual(2, len(records))
2260
2302
        calls = list(basis.calls)
2261
2303
        for record in records:
2264
2306
                self.assertIsInstance(record, AbsentContentFactory)
2265
2307
            else:
2266
2308
                reference = list(basis.get_record_stream([key_basis],
2267
 
                    'unordered', True))[0]
 
2309
                                                         'unordered', True))[0]
2268
2310
                self.assertEqual(reference.key, record.key)
2269
2311
                self.assertEqual(reference.sha1, record.sha1)
2270
2312
                self.assertEqual(reference.storage_kind, record.storage_kind)
2271
2313
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
2272
 
                    record.get_bytes_as(record.storage_kind))
 
2314
                                 record.get_bytes_as(record.storage_kind))
2273
2315
                self.assertEqual(reference.get_bytes_as('fulltext'),
2274
 
                    record.get_bytes_as('fulltext'))
 
2316
                                 record.get_bytes_as('fulltext'))
2275
2317
        # It's not strictly minimal, but it seems reasonable for now for it to
2276
2318
        # ask which fallbacks have which parents.
2277
2319
        self.assertEqual([
2278
 
            ("get_parent_map", set([key_basis, key_missing])),
 
2320
            ("get_parent_map", {key_basis, key_missing}),
2279
2321
            ("get_record_stream", [key_basis], 'unordered', True)],
2280
2322
            calls)
2281
2323
 
2282
2324
    def test_get_record_stream_ordered_fulltexts(self):
2283
2325
        # ordering is preserved down into the fallback store.
2284
2326
        basis, test = self.get_basis_and_test_knit()
2285
 
        key = ('foo',)
2286
 
        key_basis = ('bar',)
2287
 
        key_basis_2 = ('quux',)
2288
 
        key_missing = ('missing',)
2289
 
        test.add_lines(key, (key_basis,), ['foo\n'])
 
2327
        key = (b'foo',)
 
2328
        key_basis = (b'bar',)
 
2329
        key_basis_2 = (b'quux',)
 
2330
        key_missing = (b'missing',)
 
2331
        test.add_lines(key, (key_basis,), [b'foo\n'])
2290
2332
        # Missing (from test knit) objects are retrieved from the basis:
2291
 
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
2292
 
        basis.add_lines(key_basis_2, (), ['quux\n'])
 
2333
        basis.add_lines(key_basis, (key_basis_2,), [b'foo\n', b'bar\n'])
 
2334
        basis.add_lines(key_basis_2, (), [b'quux\n'])
2293
2335
        basis.calls = []
2294
2336
        # ask for in non-topological order
2295
2337
        records = list(test.get_record_stream(
2298
2340
        results = []
2299
2341
        for record in records:
2300
2342
            self.assertSubset([record.key],
2301
 
                (key_basis, key_missing, key_basis_2, key))
 
2343
                              (key_basis, key_missing, key_basis_2, key))
2302
2344
            if record.key == key_missing:
2303
2345
                self.assertIsInstance(record, AbsentContentFactory)
2304
2346
            else:
2305
2347
                results.append((record.key, record.sha1, record.storage_kind,
2306
 
                    record.get_bytes_as('fulltext')))
 
2348
                                record.get_bytes_as('fulltext')))
2307
2349
        calls = list(basis.calls)
2308
2350
        order = [record[0] for record in results]
2309
2351
        self.assertEqual([key_basis_2, key_basis, key], order)
2312
2354
                source = test
2313
2355
            else:
2314
2356
                source = basis
2315
 
            record = source.get_record_stream([result[0]], 'unordered',
2316
 
                True).next()
 
2357
            record = next(source.get_record_stream([result[0]], 'unordered',
 
2358
                                                   True))
2317
2359
            self.assertEqual(record.key, result[0])
2318
2360
            self.assertEqual(record.sha1, result[1])
2319
2361
            # We used to check that the storage kind matched, but actually it
2323
2365
            self.assertEqual(record.get_bytes_as('fulltext'), result[3])
2324
2366
        # It's not strictly minimal, but it seems reasonable for now for it to
2325
2367
        # ask which fallbacks have which parents.
2326
 
        self.assertEqual([
2327
 
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
2328
 
            # topological is requested from the fallback, because that is what
2329
 
            # was requested at the top level.
2330
 
            ("get_record_stream", [key_basis_2, key_basis], 'topological', True)],
2331
 
            calls)
 
2368
        self.assertEqual(2, len(calls))
 
2369
        self.assertEqual(
 
2370
            ("get_parent_map", {key_basis, key_basis_2, key_missing}),
 
2371
            calls[0])
 
2372
        # topological is requested from the fallback, because that is what
 
2373
        # was requested at the top level.
 
2374
        self.assertIn(
 
2375
            calls[1], [
 
2376
                ("get_record_stream", [key_basis_2,
 
2377
                                       key_basis], 'topological', True),
 
2378
                ("get_record_stream", [key_basis, key_basis_2], 'topological', True)])
2332
2379
 
2333
2380
    def test_get_record_stream_unordered_deltas(self):
2334
2381
        # records from the test knit are answered without asking the basis:
2335
2382
        basis, test = self.get_basis_and_test_knit()
2336
 
        key = ('foo',)
2337
 
        key_basis = ('bar',)
2338
 
        key_missing = ('missing',)
2339
 
        test.add_lines(key, (), ['foo\n'])
 
2383
        key = (b'foo',)
 
2384
        key_basis = (b'bar',)
 
2385
        key_missing = (b'missing',)
 
2386
        test.add_lines(key, (), [b'foo\n'])
2340
2387
        records = list(test.get_record_stream([key], 'unordered', False))
2341
2388
        self.assertEqual(1, len(records))
2342
2389
        self.assertEqual([], basis.calls)
2343
2390
        # Missing (from test knit) objects are retrieved from the basis:
2344
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
 
2391
        basis.add_lines(key_basis, (), [b'foo\n', b'bar\n'])
2345
2392
        basis.calls = []
2346
2393
        records = list(test.get_record_stream([key_basis, key_missing],
2347
 
            'unordered', False))
 
2394
                                              'unordered', False))
2348
2395
        self.assertEqual(2, len(records))
2349
2396
        calls = list(basis.calls)
2350
2397
        for record in records:
2353
2400
                self.assertIsInstance(record, AbsentContentFactory)
2354
2401
            else:
2355
2402
                reference = list(basis.get_record_stream([key_basis],
2356
 
                    'unordered', False))[0]
 
2403
                                                         'unordered', False))[0]
2357
2404
                self.assertEqual(reference.key, record.key)
2358
2405
                self.assertEqual(reference.sha1, record.sha1)
2359
2406
                self.assertEqual(reference.storage_kind, record.storage_kind)
2360
2407
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
2361
 
                    record.get_bytes_as(record.storage_kind))
 
2408
                                 record.get_bytes_as(record.storage_kind))
2362
2409
        # It's not strictly minimal, but it seems reasonable for now for it to
2363
2410
        # ask which fallbacks have which parents.
2364
2411
        self.assertEqual([
2365
 
            ("get_parent_map", set([key_basis, key_missing])),
 
2412
            ("get_parent_map", {key_basis, key_missing}),
2366
2413
            ("get_record_stream", [key_basis], 'unordered', False)],
2367
2414
            calls)
2368
2415
 
2369
2416
    def test_get_record_stream_ordered_deltas(self):
2370
2417
        # ordering is preserved down into the fallback store.
2371
2418
        basis, test = self.get_basis_and_test_knit()
2372
 
        key = ('foo',)
2373
 
        key_basis = ('bar',)
2374
 
        key_basis_2 = ('quux',)
2375
 
        key_missing = ('missing',)
2376
 
        test.add_lines(key, (key_basis,), ['foo\n'])
 
2419
        key = (b'foo',)
 
2420
        key_basis = (b'bar',)
 
2421
        key_basis_2 = (b'quux',)
 
2422
        key_missing = (b'missing',)
 
2423
        test.add_lines(key, (key_basis,), [b'foo\n'])
2377
2424
        # Missing (from test knit) objects are retrieved from the basis:
2378
 
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
2379
 
        basis.add_lines(key_basis_2, (), ['quux\n'])
 
2425
        basis.add_lines(key_basis, (key_basis_2,), [b'foo\n', b'bar\n'])
 
2426
        basis.add_lines(key_basis_2, (), [b'quux\n'])
2380
2427
        basis.calls = []
2381
2428
        # ask for in non-topological order
2382
2429
        records = list(test.get_record_stream(
2385
2432
        results = []
2386
2433
        for record in records:
2387
2434
            self.assertSubset([record.key],
2388
 
                (key_basis, key_missing, key_basis_2, key))
 
2435
                              (key_basis, key_missing, key_basis_2, key))
2389
2436
            if record.key == key_missing:
2390
2437
                self.assertIsInstance(record, AbsentContentFactory)
2391
2438
            else:
2392
2439
                results.append((record.key, record.sha1, record.storage_kind,
2393
 
                    record.get_bytes_as(record.storage_kind)))
 
2440
                                record.get_bytes_as(record.storage_kind)))
2394
2441
        calls = list(basis.calls)
2395
2442
        order = [record[0] for record in results]
2396
2443
        self.assertEqual([key_basis_2, key_basis, key], order)
2399
2446
                source = test
2400
2447
            else:
2401
2448
                source = basis
2402
 
            record = source.get_record_stream([result[0]], 'unordered',
2403
 
                False).next()
 
2449
            record = next(source.get_record_stream([result[0]], 'unordered',
 
2450
                                                   False))
2404
2451
            self.assertEqual(record.key, result[0])
2405
2452
            self.assertEqual(record.sha1, result[1])
2406
2453
            self.assertEqual(record.storage_kind, result[2])
2407
 
            self.assertEqual(record.get_bytes_as(record.storage_kind), result[3])
 
2454
            self.assertEqual(record.get_bytes_as(
 
2455
                record.storage_kind), result[3])
2408
2456
        # It's not strictly minimal, but it seems reasonable for now for it to
2409
2457
        # ask which fallbacks have which parents.
2410
2458
        self.assertEqual([
2411
 
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
 
2459
            ("get_parent_map", {key_basis, key_basis_2, key_missing}),
2412
2460
            ("get_record_stream", [key_basis_2, key_basis], 'topological', False)],
2413
2461
            calls)
2414
2462
 
2415
2463
    def test_get_sha1s(self):
2416
2464
        # sha1's in the test knit are answered without asking the basis
2417
2465
        basis, test = self.get_basis_and_test_knit()
2418
 
        key = ('foo',)
2419
 
        key_basis = ('bar',)
2420
 
        key_missing = ('missing',)
2421
 
        test.add_lines(key, (), ['foo\n'])
2422
 
        key_sha1sum = osutils.sha('foo\n').hexdigest()
 
2466
        key = (b'foo',)
 
2467
        key_basis = (b'bar',)
 
2468
        key_missing = (b'missing',)
 
2469
        test.add_lines(key, (), [b'foo\n'])
 
2470
        key_sha1sum = osutils.sha_string(b'foo\n')
2423
2471
        sha1s = test.get_sha1s([key])
2424
2472
        self.assertEqual({key: key_sha1sum}, sha1s)
2425
2473
        self.assertEqual([], basis.calls)
2426
2474
        # But texts that are not in the test knit are looked for in the basis
2427
2475
        # directly (rather than via text reconstruction) so that remote servers
2428
2476
        # etc don't have to answer with full content.
2429
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
2430
 
        basis_sha1sum = osutils.sha('foo\nbar\n').hexdigest()
 
2477
        basis.add_lines(key_basis, (), [b'foo\n', b'bar\n'])
 
2478
        basis_sha1sum = osutils.sha_string(b'foo\nbar\n')
2431
2479
        basis.calls = []
2432
2480
        sha1s = test.get_sha1s([key, key_missing, key_basis])
2433
2481
        self.assertEqual({key: key_sha1sum,
2434
 
            key_basis: basis_sha1sum}, sha1s)
2435
 
        self.assertEqual([("get_sha1s", set([key_basis, key_missing]))],
2436
 
            basis.calls)
 
2482
                          key_basis: basis_sha1sum}, sha1s)
 
2483
        self.assertEqual([("get_sha1s", {key_basis, key_missing})],
 
2484
                         basis.calls)
2437
2485
 
2438
2486
    def test_insert_record_stream(self):
2439
2487
        # records are inserted as normal; insert_record_stream builds on
2440
2488
        # add_lines, so a smoke test should be all that's needed:
2441
 
        key = ('foo',)
2442
 
        key_basis = ('bar',)
2443
 
        key_delta = ('zaphod',)
 
2489
        key_basis = (b'bar',)
 
2490
        key_delta = (b'zaphod',)
2444
2491
        basis, test = self.get_basis_and_test_knit()
2445
2492
        source = self.make_test_knit(name='source')
2446
 
        basis.add_lines(key_basis, (), ['foo\n'])
 
2493
        basis.add_lines(key_basis, (), [b'foo\n'])
2447
2494
        basis.calls = []
2448
 
        source.add_lines(key_basis, (), ['foo\n'])
2449
 
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
 
2495
        source.add_lines(key_basis, (), [b'foo\n'])
 
2496
        source.add_lines(key_delta, (key_basis,), [b'bar\n'])
2450
2497
        stream = source.get_record_stream([key_delta], 'unordered', False)
2451
2498
        test.insert_record_stream(stream)
2452
2499
        # XXX: this does somewhat too many calls in making sure of whether it
2453
2500
        # has to recreate the full text.
2454
 
        self.assertEqual([("get_parent_map", set([key_basis])),
2455
 
             ('get_parent_map', set([key_basis])),
2456
 
             ('get_record_stream', [key_basis], 'unordered', True)],
2457
 
            basis.calls)
2458
 
        self.assertEqual({key_delta:(key_basis,)},
2459
 
            test.get_parent_map([key_delta]))
2460
 
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
2461
 
            'unordered', True).next().get_bytes_as('fulltext'))
 
2501
        self.assertEqual([("get_parent_map", {key_basis}),
 
2502
                          ('get_parent_map', {key_basis}),
 
2503
                          ('get_record_stream', [key_basis], 'unordered', True)],
 
2504
                         basis.calls)
 
2505
        self.assertEqual({key_delta: (key_basis,)},
 
2506
                         test.get_parent_map([key_delta]))
 
2507
        self.assertEqual(b'bar\n', next(test.get_record_stream([key_delta],
 
2508
                                                               'unordered', True)).get_bytes_as('fulltext'))
2462
2509
 
2463
2510
    def test_iter_lines_added_or_present_in_keys(self):
2464
2511
        # Lines from the basis are returned, and lines for a given key are only
2465
2512
        # returned once.
2466
 
        key1 = ('foo1',)
2467
 
        key2 = ('foo2',)
 
2513
        key1 = (b'foo1',)
 
2514
        key2 = (b'foo2',)
2468
2515
        # all sources are asked for keys:
2469
2516
        basis, test = self.get_basis_and_test_knit()
2470
 
        basis.add_lines(key1, (), ["foo"])
 
2517
        basis.add_lines(key1, (), [b"foo"])
2471
2518
        basis.calls = []
2472
2519
        lines = list(test.iter_lines_added_or_present_in_keys([key1]))
2473
 
        self.assertEqual([("foo\n", key1)], lines)
2474
 
        self.assertEqual([("iter_lines_added_or_present_in_keys", set([key1]))],
2475
 
            basis.calls)
 
2520
        self.assertEqual([(b"foo\n", key1)], lines)
 
2521
        self.assertEqual([("iter_lines_added_or_present_in_keys", {key1})],
 
2522
                         basis.calls)
2476
2523
        # keys in both are not duplicated:
2477
 
        test.add_lines(key2, (), ["bar\n"])
2478
 
        basis.add_lines(key2, (), ["bar\n"])
 
2524
        test.add_lines(key2, (), [b"bar\n"])
 
2525
        basis.add_lines(key2, (), [b"bar\n"])
2479
2526
        basis.calls = []
2480
2527
        lines = list(test.iter_lines_added_or_present_in_keys([key2]))
2481
 
        self.assertEqual([("bar\n", key2)], lines)
 
2528
        self.assertEqual([(b"bar\n", key2)], lines)
2482
2529
        self.assertEqual([], basis.calls)
2483
2530
 
2484
2531
    def test_keys(self):
2485
 
        key1 = ('foo1',)
2486
 
        key2 = ('foo2',)
 
2532
        key1 = (b'foo1',)
 
2533
        key2 = (b'foo2',)
2487
2534
        # all sources are asked for keys:
2488
2535
        basis, test = self.get_basis_and_test_knit()
2489
2536
        keys = test.keys()
2493
2540
        basis.add_lines(key1, (), [])
2494
2541
        basis.calls = []
2495
2542
        keys = test.keys()
2496
 
        self.assertEqual(set([key1]), set(keys))
 
2543
        self.assertEqual({key1}, set(keys))
2497
2544
        self.assertEqual([("keys",)], basis.calls)
2498
2545
        # keys in both are not duplicated:
2499
2546
        test.add_lines(key2, (), [])
2501
2548
        basis.calls = []
2502
2549
        keys = test.keys()
2503
2550
        self.assertEqual(2, len(keys))
2504
 
        self.assertEqual(set([key1, key2]), set(keys))
 
2551
        self.assertEqual({key1, key2}, set(keys))
2505
2552
        self.assertEqual([("keys",)], basis.calls)
2506
2553
 
2507
2554
    def test_add_mpdiffs(self):
2508
2555
        # records are inserted as normal; add_mpdiff builds on
2509
2556
        # add_lines, so a smoke test should be all that's needed:
2510
 
        key = ('foo',)
2511
 
        key_basis = ('bar',)
2512
 
        key_delta = ('zaphod',)
 
2557
        key_basis = (b'bar',)
 
2558
        key_delta = (b'zaphod',)
2513
2559
        basis, test = self.get_basis_and_test_knit()
2514
2560
        source = self.make_test_knit(name='source')
2515
 
        basis.add_lines(key_basis, (), ['foo\n'])
 
2561
        basis.add_lines(key_basis, (), [b'foo\n'])
2516
2562
        basis.calls = []
2517
 
        source.add_lines(key_basis, (), ['foo\n'])
2518
 
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
 
2563
        source.add_lines(key_basis, (), [b'foo\n'])
 
2564
        source.add_lines(key_delta, (key_basis,), [b'bar\n'])
2519
2565
        diffs = source.make_mpdiffs([key_delta])
2520
2566
        test.add_mpdiffs([(key_delta, (key_basis,),
2521
 
            source.get_sha1s([key_delta])[key_delta], diffs[0])])
2522
 
        self.assertEqual([("get_parent_map", set([key_basis])),
2523
 
            ('get_record_stream', [key_basis], 'unordered', True),],
2524
 
            basis.calls)
2525
 
        self.assertEqual({key_delta:(key_basis,)},
2526
 
            test.get_parent_map([key_delta]))
2527
 
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
2528
 
            'unordered', True).next().get_bytes_as('fulltext'))
 
2567
                           source.get_sha1s([key_delta])[key_delta], diffs[0])])
 
2568
        self.assertEqual([("get_parent_map", {key_basis}),
 
2569
                          ('get_record_stream', [key_basis], 'unordered', True), ],
 
2570
                         basis.calls)
 
2571
        self.assertEqual({key_delta: (key_basis,)},
 
2572
                         test.get_parent_map([key_delta]))
 
2573
        self.assertEqual(b'bar\n', next(test.get_record_stream([key_delta],
 
2574
                                                               'unordered', True)).get_bytes_as('fulltext'))
2529
2575
 
2530
2576
    def test_make_mpdiffs(self):
2531
2577
        # Generating an mpdiff across a stacking boundary should detect parent
2532
2578
        # texts regions.
2533
 
        key = ('foo',)
2534
 
        key_left = ('bar',)
2535
 
        key_right = ('zaphod',)
 
2579
        key = (b'foo',)
 
2580
        key_left = (b'bar',)
 
2581
        key_right = (b'zaphod',)
2536
2582
        basis, test = self.get_basis_and_test_knit()
2537
 
        basis.add_lines(key_left, (), ['bar\n'])
2538
 
        basis.add_lines(key_right, (), ['zaphod\n'])
 
2583
        basis.add_lines(key_left, (), [b'bar\n'])
 
2584
        basis.add_lines(key_right, (), [b'zaphod\n'])
2539
2585
        basis.calls = []
2540
2586
        test.add_lines(key, (key_left, key_right),
2541
 
            ['bar\n', 'foo\n', 'zaphod\n'])
 
2587
                       [b'bar\n', b'foo\n', b'zaphod\n'])
2542
2588
        diffs = test.make_mpdiffs([key])
2543
2589
        self.assertEqual([
2544
2590
            multiparent.MultiParent([multiparent.ParentText(0, 0, 0, 1),
2545
 
                multiparent.NewText(['foo\n']),
2546
 
                multiparent.ParentText(1, 0, 2, 1)])],
2547
 
            diffs)
 
2591
                                     multiparent.NewText([b'foo\n']),
 
2592
                                     multiparent.ParentText(1, 0, 2, 1)])],
 
2593
                         diffs)
2548
2594
        self.assertEqual(3, len(basis.calls))
2549
2595
        self.assertEqual([
2550
 
            ("get_parent_map", set([key_left, key_right])),
2551
 
            ("get_parent_map", set([key_left, key_right])),
 
2596
            ("get_parent_map", {key_left, key_right}),
 
2597
            ("get_parent_map", {key_left, key_right}),
2552
2598
            ],
2553
2599
            basis.calls[:-1])
2554
2600
        last_call = basis.calls[-1]
2555
2601
        self.assertEqual('get_record_stream', last_call[0])
2556
 
        self.assertEqual(set([key_left, key_right]), set(last_call[1]))
 
2602
        self.assertEqual({key_left, key_right}, set(last_call[1]))
2557
2603
        self.assertEqual('topological', last_call[2])
2558
2604
        self.assertEqual(True, last_call[3])
2559
2605
 
2564
2610
    def test_include_delta_closure_generates_a_knit_delta_closure(self):
2565
2611
        vf = self.make_test_knit(name='test')
2566
2612
        # put in three texts, giving ft, delta, delta
2567
 
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
2568
 
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
2569
 
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
 
2613
        vf.add_lines((b'base',), (), [b'base\n', b'content\n'])
 
2614
        vf.add_lines((b'd1',), ((b'base',),), [b'd1\n'])
 
2615
        vf.add_lines((b'd2',), ((b'd1',),), [b'd2\n'])
2570
2616
        # But heuristics could interfere, so check what happened:
2571
2617
        self.assertEqual(['knit-ft-gz', 'knit-delta-gz', 'knit-delta-gz'],
2572
 
            [record.storage_kind for record in
2573
 
             vf.get_record_stream([('base',), ('d1',), ('d2',)],
2574
 
                'topological', False)])
 
2618
                         [record.storage_kind for record in
 
2619
                          vf.get_record_stream([(b'base',), (b'd1',), (b'd2',)],
 
2620
                                               'topological', False)])
2575
2621
        # generate a stream of just the deltas include_delta_closure=True,
2576
2622
        # serialise to the network, and check that we get a delta closure on the wire.
2577
 
        stream = vf.get_record_stream([('d1',), ('d2',)], 'topological', True)
 
2623
        stream = vf.get_record_stream(
 
2624
            [(b'd1',), (b'd2',)], 'topological', True)
2578
2625
        netb = [record.get_bytes_as(record.storage_kind) for record in stream]
2579
2626
        # The first bytes should be a memo from _ContentMapGenerator, and the
2580
2627
        # second bytes should be empty (because its a API proxy not something
2581
2628
        # for wire serialisation.
2582
 
        self.assertEqual('', netb[1])
 
2629
        self.assertEqual(b'', netb[1])
2583
2630
        bytes = netb[0]
2584
2631
        kind, line_end = network_bytes_to_kind_and_offset(bytes)
2585
2632
        self.assertEqual('knit-delta-closure', kind)
2591
2638
    def test_get_record_stream_gives_records(self):
2592
2639
        vf = self.make_test_knit(name='test')
2593
2640
        # put in three texts, giving ft, delta, delta
2594
 
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
2595
 
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
2596
 
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
2597
 
        keys = [('d1',), ('d2',)]
 
2641
        vf.add_lines((b'base',), (), [b'base\n', b'content\n'])
 
2642
        vf.add_lines((b'd1',), ((b'base',),), [b'd1\n'])
 
2643
        vf.add_lines((b'd2',), ((b'd1',),), [b'd2\n'])
 
2644
        keys = [(b'd1',), (b'd2',)]
2598
2645
        generator = _VFContentMapGenerator(vf, keys,
2599
 
            global_map=vf.get_parent_map(keys))
 
2646
                                           global_map=vf.get_parent_map(keys))
2600
2647
        for record in generator.get_record_stream():
2601
 
            if record.key == ('d1',):
2602
 
                self.assertEqual('d1\n', record.get_bytes_as('fulltext'))
 
2648
            if record.key == (b'd1',):
 
2649
                self.assertEqual(b'd1\n', record.get_bytes_as('fulltext'))
2603
2650
            else:
2604
 
                self.assertEqual('d2\n', record.get_bytes_as('fulltext'))
 
2651
                self.assertEqual(b'd2\n', record.get_bytes_as('fulltext'))
2605
2652
 
2606
2653
    def test_get_record_stream_kinds_are_raw(self):
2607
2654
        vf = self.make_test_knit(name='test')
2608
2655
        # put in three texts, giving ft, delta, delta
2609
 
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
2610
 
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
2611
 
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
2612
 
        keys = [('base',), ('d1',), ('d2',)]
 
2656
        vf.add_lines((b'base',), (), [b'base\n', b'content\n'])
 
2657
        vf.add_lines((b'd1',), ((b'base',),), [b'd1\n'])
 
2658
        vf.add_lines((b'd2',), ((b'd1',),), [b'd2\n'])
 
2659
        keys = [(b'base',), (b'd1',), (b'd2',)]
2613
2660
        generator = _VFContentMapGenerator(vf, keys,
2614
 
            global_map=vf.get_parent_map(keys))
2615
 
        kinds = {('base',): 'knit-delta-closure',
2616
 
            ('d1',): 'knit-delta-closure-ref',
2617
 
            ('d2',): 'knit-delta-closure-ref',
2618
 
            }
 
2661
                                           global_map=vf.get_parent_map(keys))
 
2662
        kinds = {(b'base',): 'knit-delta-closure',
 
2663
                 (b'd1',): 'knit-delta-closure-ref',
 
2664
                 (b'd2',): 'knit-delta-closure-ref',
 
2665
                 }
2619
2666
        for record in generator.get_record_stream():
2620
2667
            self.assertEqual(kinds[record.key], record.storage_kind)