/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: Martin
  • Date: 2018-11-16 16:38:22 UTC
  • mto: This revision was merged to the branch mainline in revision 7172.
  • Revision ID: gzlist@googlemail.com-20181116163822-yg1h1cdng6w7w9kn
Make --profile-imports work on Python 3

Also tweak heading to line up correctly.

Show diffs side-by-side

added added

removed removed

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