/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: 2020-02-05 01:40:59 UTC
  • mto: This revision was merged to the branch mainline in revision 7480.
  • Revision ID: jelmer@jelmer.uk-20200205014059-1jrhjaphw5vh9i7s
Fix Python 2.7 build.

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