/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_knit.py

  • Committer: Jelmer Vernooij
  • Date: 2018-06-30 23:59:51 UTC
  • mto: This revision was merged to the branch mainline in revision 7014.
  • Revision ID: jelmer@jelmer.uk-20180630235951-7n0k1j4a8sc6xpra
Don't require ColorFeature for grep tests.

Testing this functionality doesn't actually require color support.

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):
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
699
759
class LowLevelKnitDataTests(TestCase):
700
760
 
701
761
    def create_gz_content(self, text):
702
 
        sio = StringIO()
 
762
        sio = BytesIO()
703
763
        gz_file = gzip.GzipFile(mode='wb', fileobj=sio)
704
764
        gz_file.write(text)
705
765
        gz_file.close()
707
767
 
708
768
    def make_multiple_records(self):
709
769
        """Create the content for multiple records."""
710
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
 
770
        sha1sum = osutils.sha_string(b'foo\nbar\n')
711
771
        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'
 
772
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
773
                                        b'foo\n'
 
774
                                        b'bar\n'
 
775
                                        b'end rev-id-1\n'
716
776
                                        % (sha1sum,))
717
777
        record_1 = (0, len(gz_txt), sha1sum)
718
778
        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'
 
779
        sha1sum = osutils.sha_string(b'baz\n')
 
780
        gz_txt = self.create_gz_content(b'version rev-id-2 1 %s\n'
 
781
                                        b'baz\n'
 
782
                                        b'end rev-id-2\n'
723
783
                                        % (sha1sum,))
724
784
        record_2 = (record_1[1], len(gz_txt), sha1sum)
725
785
        total_txt.append(gz_txt)
726
786
        return total_txt, record_1, record_2
727
787
 
728
788
    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'
 
789
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
790
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
791
                                        b'foo\n'
 
792
                                        b'bar\n'
 
793
                                        b'end rev-id-1\n'
734
794
                                        % (sha1sum,))
735
795
        transport = MockTransport([gz_txt])
736
796
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
737
797
        knit = KnitVersionedFiles(None, access)
738
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
 
798
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
739
799
 
740
800
        contents = list(knit._read_records_iter(records))
741
 
        self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'],
742
 
            '4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
 
801
        self.assertEqual([((b'rev-id-1',), [b'foo\n', b'bar\n'],
 
802
            b'4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
743
803
 
744
804
        raw_contents = list(knit._read_records_iter_raw(records))
745
 
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
 
805
        self.assertEqual([((b'rev-id-1',), gz_txt, sha1sum)], raw_contents)
746
806
 
747
807
    def test_multiple_records_valid(self):
748
808
        total_txt, record_1, record_2 = self.make_multiple_records()
749
 
        transport = MockTransport([''.join(total_txt)])
 
809
        transport = MockTransport([b''.join(total_txt)])
750
810
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
751
811
        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]))]
 
812
        records = [((b'rev-id-1',), ((b'rev-id-1',), record_1[0], record_1[1])),
 
813
                   ((b'rev-id-2',), ((b'rev-id-2',), record_2[0], record_2[1]))]
754
814
 
755
815
        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])],
 
816
        self.assertEqual([((b'rev-id-1',), [b'foo\n', b'bar\n'], record_1[2]),
 
817
                          ((b'rev-id-2',), [b'baz\n'], record_2[2])],
758
818
                         contents)
759
819
 
760
820
        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])],
 
821
        self.assertEqual([((b'rev-id-1',), total_txt[0], record_1[2]),
 
822
                          ((b'rev-id-2',), total_txt[1], record_2[2])],
763
823
                         raw_contents)
764
824
 
765
825
    def test_not_enough_lines(self):
766
 
        sha1sum = osutils.sha('foo\n').hexdigest()
 
826
        sha1sum = osutils.sha_string(b'foo\n')
767
827
        # 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'
 
828
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
829
                                        b'foo\n'
 
830
                                        b'end rev-id-1\n'
771
831
                                        % (sha1sum,))
772
832
        transport = MockTransport([gz_txt])
773
833
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
774
834
        knit = KnitVersionedFiles(None, access)
775
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
776
 
        self.assertRaises(errors.KnitCorrupt, list,
 
835
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
 
836
        self.assertRaises(KnitCorrupt, list,
777
837
            knit._read_records_iter(records))
778
838
 
779
839
        # read_records_iter_raw won't detect that sort of mismatch/corruption
780
840
        raw_contents = list(knit._read_records_iter_raw(records))
781
 
        self.assertEqual([(('rev-id-1',),  gz_txt, sha1sum)], raw_contents)
 
841
        self.assertEqual([((b'rev-id-1',),  gz_txt, sha1sum)], raw_contents)
782
842
 
783
843
    def test_too_many_lines(self):
784
 
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
 
844
        sha1sum = osutils.sha_string(b'foo\nbar\n')
785
845
        # 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'
 
846
        gz_txt = self.create_gz_content(b'version rev-id-1 1 %s\n'
 
847
                                        b'foo\n'
 
848
                                        b'bar\n'
 
849
                                        b'end rev-id-1\n'
790
850
                                        % (sha1sum,))
791
851
        transport = MockTransport([gz_txt])
792
852
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
793
853
        knit = KnitVersionedFiles(None, access)
794
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
795
 
        self.assertRaises(errors.KnitCorrupt, list,
 
854
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
 
855
        self.assertRaises(KnitCorrupt, list,
796
856
            knit._read_records_iter(records))
797
857
 
798
858
        # read_records_iter_raw won't detect that sort of mismatch/corruption
799
859
        raw_contents = list(knit._read_records_iter_raw(records))
800
 
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
 
860
        self.assertEqual([((b'rev-id-1',), gz_txt, sha1sum)], raw_contents)
801
861
 
802
862
    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'
 
863
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
864
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
865
                                        b'foo\n'
 
866
                                        b'bar\n'
 
867
                                        b'end rev-id-1\n'
808
868
                                        % (sha1sum,))
809
869
        transport = MockTransport([gz_txt])
810
870
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
811
871
        knit = KnitVersionedFiles(None, access)
812
872
        # 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,
 
873
        records = [((b'rev-id-2',), ((b'rev-id-2',), 0, len(gz_txt)))]
 
874
        self.assertRaises(KnitCorrupt, list,
815
875
            knit._read_records_iter(records))
816
876
 
817
877
        # read_records_iter_raw detects mismatches in the header
818
 
        self.assertRaises(errors.KnitCorrupt, list,
 
878
        self.assertRaises(KnitCorrupt, list,
819
879
            knit._read_records_iter_raw(records))
820
880
 
821
881
    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'
 
882
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
883
        txt = (b'version rev-id-1 2 %s\n'
 
884
               b'foo\n'
 
885
               b'bar\n'
 
886
               b'end rev-id-1\n'
827
887
               % (sha1sum,))
828
888
        transport = MockTransport([txt])
829
889
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
830
890
        knit = KnitVersionedFiles(None, access)
831
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(txt)))]
 
891
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(txt)))]
832
892
 
833
893
        # We don't have valid gzip data ==> corrupt
834
 
        self.assertRaises(errors.KnitCorrupt, list,
 
894
        self.assertRaises(KnitCorrupt, list,
835
895
            knit._read_records_iter(records))
836
896
 
837
897
        # read_records_iter_raw will notice the bad data
838
 
        self.assertRaises(errors.KnitCorrupt, list,
 
898
        self.assertRaises(KnitCorrupt, list,
839
899
            knit._read_records_iter_raw(records))
840
900
 
841
901
    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'
 
902
        sha1sum = osutils.sha_string(b'foo\nbar\n')
 
903
        gz_txt = self.create_gz_content(b'version rev-id-1 2 %s\n'
 
904
                                        b'foo\n'
 
905
                                        b'bar\n'
 
906
                                        b'end rev-id-1\n'
847
907
                                        % (sha1sum,))
848
908
        # Change 2 bytes in the middle to \xff
849
 
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
 
909
        gz_txt = gz_txt[:10] + b'\xff\xff' + gz_txt[12:]
850
910
        transport = MockTransport([gz_txt])
851
911
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
852
912
        knit = KnitVersionedFiles(None, access)
853
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
854
 
        self.assertRaises(errors.KnitCorrupt, list,
 
913
        records = [((b'rev-id-1',), ((b'rev-id-1',), 0, len(gz_txt)))]
 
914
        self.assertRaises(KnitCorrupt, list,
855
915
            knit._read_records_iter(records))
856
916
        # read_records_iter_raw will barf on bad gz data
857
 
        self.assertRaises(errors.KnitCorrupt, list,
 
917
        self.assertRaises(KnitCorrupt, list,
858
918
            knit._read_records_iter_raw(records))
859
919
 
860
920
 
862
922
 
863
923
    def get_knit_index(self, transport, name, mode):
864
924
        mapper = ConstantMapper(name)
865
 
        from bzrlib._knit_load_data_py import _load_data_py
 
925
        from ..bzr._knit_load_data_py import _load_data_py
866
926
        self.overrideAttr(knit, '_load_data', _load_data_py)
867
927
        allow_writes = lambda: 'w' in mode
868
928
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
872
932
        index = self.get_knit_index(transport, "filename", "w")
873
933
        index.keys()
874
934
        call = transport.calls.pop(0)
875
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
935
        # call[1][1] is a BytesIO - we can't test it by simple equality.
876
936
        self.assertEqual('put_file_non_atomic', call[0])
877
937
        self.assertEqual('filename.kndx', call[1][0])
878
938
        # With no history, _KndxIndex writes a new index:
899
959
        utf8_revision_id = unicode_revision_id.encode('utf-8')
900
960
        transport = MockTransport([
901
961
            _KndxIndex.HEADER,
902
 
            "version option 0 1 .%s :" % (utf8_revision_id,)
 
962
            b"version option 0 1 .%s :" % (utf8_revision_id,)
903
963
            ])
904
964
        index = self.get_knit_index(transport, "filename", "r")
905
965
        self.assertEqual({("version",):((utf8_revision_id,),)},
908
968
    def test_read_ignore_corrupted_lines(self):
909
969
        transport = MockTransport([
910
970
            _KndxIndex.HEADER,
911
 
            "corrupted",
912
 
            "corrupted options 0 1 .b .c ",
913
 
            "version options 0 1 :"
 
971
            b"corrupted",
 
972
            b"corrupted options 0 1 .b .c ",
 
973
            b"version options 0 1 :"
914
974
            ])
915
975
        index = self.get_knit_index(transport, "filename", "r")
916
976
        self.assertEqual(1, len(index.keys()))
917
 
        self.assertEqual(set([("version",)]), index.keys())
 
977
        self.assertEqual({("version",)}, index.keys())
918
978
 
919
979
    def test_read_corrupted_header(self):
920
 
        transport = MockTransport(['not a bzr knit index header\n'])
 
980
        transport = MockTransport([b'not a bzr knit index header\n'])
921
981
        index = self.get_knit_index(transport, "filename", "r")
922
982
        self.assertRaises(KnitHeaderError, index.keys)
923
983
 
924
984
    def test_read_duplicate_entries(self):
925
985
        transport = MockTransport([
926
986
            _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 :"
 
987
            b"parent options 0 1 :",
 
988
            b"version options1 0 1 0 :",
 
989
            b"version options2 1 2 .other :",
 
990
            b"version options3 3 4 0 .other :"
931
991
            ])
932
992
        index = self.get_knit_index(transport, "filename", "r")
933
993
        self.assertEqual(2, len(index.keys()))
942
1002
    def test_read_compressed_parents(self):
943
1003
        transport = MockTransport([
944
1004
            _KndxIndex.HEADER,
945
 
            "a option 0 1 :",
946
 
            "b option 0 1 0 :",
947
 
            "c option 0 1 1 0 :",
 
1005
            b"a option 0 1 :",
 
1006
            b"b option 0 1 0 :",
 
1007
            b"c option 0 1 1 0 :",
948
1008
            ])
949
1009
        index = self.get_knit_index(transport, "filename", "r")
950
1010
        self.assertEqual({("b",):(("a",),), ("c",):(("b",), ("a",))},
960
1020
        index.add_records([
961
1021
            ((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
962
1022
        call = transport.calls.pop(0)
963
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1023
        # call[1][1] is a BytesIO - we can't test it by simple equality.
964
1024
        self.assertEqual('put_file_non_atomic', call[0])
965
1025
        self.assertEqual('filename.kndx', call[1][0])
966
1026
        # With no history, _KndxIndex writes a new index:
979
1039
        index.add_records([
980
1040
            (("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
981
1041
        call = transport.calls.pop(0)
982
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1042
        # call[1][1] is a BytesIO - we can't test it by simple equality.
983
1043
        self.assertEqual('put_file_non_atomic', call[0])
984
1044
        self.assertEqual('filename.kndx', call[1][0])
985
1045
        # With no history, _KndxIndex writes a new index:
997
1057
        self.assertEqual(set(), index.keys())
998
1058
 
999
1059
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1000
 
        self.assertEqual(set([("a",)]), index.keys())
 
1060
        self.assertEqual({("a",)}, index.keys())
1001
1061
 
1002
1062
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1003
 
        self.assertEqual(set([("a",)]), index.keys())
 
1063
        self.assertEqual({("a",)}, index.keys())
1004
1064
 
1005
1065
        index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
1006
 
        self.assertEqual(set([("a",), ("b",)]), index.keys())
 
1066
        self.assertEqual({("a",), ("b",)}, index.keys())
1007
1067
 
1008
1068
    def add_a_b(self, index, random_id=None):
1009
1069
        kwargs = {}
1033
1093
 
1034
1094
        self.add_a_b(index)
1035
1095
        call = transport.calls.pop(0)
1036
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1096
        # call[1][1] is a BytesIO - we can't test it by simple equality.
1037
1097
        self.assertEqual('put_file_non_atomic', call[0])
1038
1098
        self.assertEqual('filename.kndx', call[1][0])
1039
1099
        # With no history, _KndxIndex writes a new index:
1040
1100
        self.assertEqual(
1041
1101
            _KndxIndex.HEADER +
1042
 
            "\na option 0 1 .b :"
1043
 
            "\na opt 1 2 .c :"
1044
 
            "\nb option 2 3 0 :",
 
1102
            b"\na option 0 1 .b :"
 
1103
            b"\na opt 1 2 .c :"
 
1104
            b"\nb option 2 3 0 :",
1045
1105
            call[1][1].getvalue())
1046
1106
        self.assertEqual({'create_parent_dir': True}, call[2])
1047
1107
        self.assertIndexIsAB(index)
1073
1133
        self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
1074
1134
        self.assertEqual({'create_parent_dir': True}, call[2])
1075
1135
        call = transport.calls.pop(0)
1076
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
 
1136
        # call[1][1] is a BytesIO - we can't test it by simple equality.
1077
1137
        self.assertEqual('put_file_non_atomic', call[0])
1078
1138
        self.assertEqual('filename.kndx', call[1][0])
1079
1139
        # With no history, _KndxIndex writes a new index:
1080
1140
        self.assertEqual(
1081
1141
            _KndxIndex.HEADER +
1082
 
            "\na option 0 1 .b :"
1083
 
            "\na opt 1 2 .c :"
1084
 
            "\nb option 2 3 0 :",
 
1142
            b"\na option 0 1 .b :"
 
1143
            b"\na opt 1 2 .c :"
 
1144
            b"\nb option 2 3 0 :",
1085
1145
            call[1][1].getvalue())
1086
1146
        self.assertEqual({'create_parent_dir': True}, call[2])
1087
1147
 
1108
1168
    def test_get_position(self):
1109
1169
        transport = MockTransport([
1110
1170
            _KndxIndex.HEADER,
1111
 
            "a option 0 1 :",
1112
 
            "b option 1 2 :"
 
1171
            b"a option 0 1 :",
 
1172
            b"b option 1 2 :"
1113
1173
            ])
1114
1174
        index = self.get_knit_index(transport, "filename", "r")
1115
1175
 
1119
1179
    def test_get_method(self):
1120
1180
        transport = MockTransport([
1121
1181
            _KndxIndex.HEADER,
1122
 
            "a fulltext,unknown 0 1 :",
1123
 
            "b unknown,line-delta 1 2 :",
1124
 
            "c bad 3 4 :"
 
1182
            b"a fulltext,unknown 0 1 :",
 
1183
            b"b unknown,line-delta 1 2 :",
 
1184
            b"c bad 3 4 :"
1125
1185
            ])
1126
1186
        index = self.get_knit_index(transport, "filename", "r")
1127
1187
 
1128
1188
        self.assertEqual("fulltext", index.get_method("a"))
1129
1189
        self.assertEqual("line-delta", index.get_method("b"))
1130
 
        self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, "c")
 
1190
        self.assertRaises(knit.KnitIndexUnknownMethod, index.get_method, "c")
1131
1191
 
1132
1192
    def test_get_options(self):
1133
1193
        transport = MockTransport([
1134
1194
            _KndxIndex.HEADER,
1135
 
            "a opt1 0 1 :",
1136
 
            "b opt2,opt3 1 2 :"
 
1195
            b"a opt1 0 1 :",
 
1196
            b"b opt2,opt3 1 2 :"
1137
1197
            ])
1138
1198
        index = self.get_knit_index(transport, "filename", "r")
1139
1199
 
1143
1203
    def test_get_parent_map(self):
1144
1204
        transport = MockTransport([
1145
1205
            _KndxIndex.HEADER,
1146
 
            "a option 0 1 :",
1147
 
            "b option 1 2 0 .c :",
1148
 
            "c option 1 2 1 0 .e :"
 
1206
            b"a option 0 1 :",
 
1207
            b"b option 1 2 0 .c :",
 
1208
            b"c option 1 2 1 0 .e :"
1149
1209
            ])
1150
1210
        index = self.get_knit_index(transport, "filename", "r")
1151
1211
 
1152
1212
        self.assertEqual({
1153
 
            ("a",):(),
1154
 
            ("b",):(("a",), ("c",)),
1155
 
            ("c",):(("b",), ("a",), ("e",)),
 
1213
            (b"a",): (),
 
1214
            (b"b",): ((b"a",), (b"c",)),
 
1215
            (b"c",): ((b"b",), (b"a",), (b"e",)),
1156
1216
            }, index.get_parent_map(index.keys()))
1157
1217
 
1158
1218
    def test_impossible_parent(self):
1159
1219
        """Test we get KnitCorrupt if the parent couldn't possibly exist."""
1160
1220
        transport = MockTransport([
1161
1221
            _KndxIndex.HEADER,
1162
 
            "a option 0 1 :",
1163
 
            "b option 0 1 4 :"  # We don't have a 4th record
 
1222
            b"a option 0 1 :",
 
1223
            b"b option 0 1 4 :"  # We don't have a 4th record
1164
1224
            ])
1165
1225
        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
 
1226
        self.assertRaises(KnitCorrupt, index.keys)
1177
1227
 
1178
1228
    def test_corrupted_parent(self):
1179
1229
        transport = MockTransport([
1180
1230
            _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'
 
1231
            b"a option 0 1 :",
 
1232
            b"b option 0 1 :",
 
1233
            b"c option 0 1 1v :", # Can't have a parent of '1v'
1184
1234
            ])
1185
1235
        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
 
1236
        self.assertRaises(KnitCorrupt, index.keys)
1197
1237
 
1198
1238
    def test_corrupted_parent_in_list(self):
1199
1239
        transport = MockTransport([
1200
1240
            _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'
 
1241
            b"a option 0 1 :",
 
1242
            b"b option 0 1 :",
 
1243
            b"c option 0 1 1 v :", # Can't have a parent of 'v'
1204
1244
            ])
1205
1245
        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
 
1246
        self.assertRaises(KnitCorrupt, index.keys)
1217
1247
 
1218
1248
    def test_invalid_position(self):
1219
1249
        transport = MockTransport([
1220
1250
            _KndxIndex.HEADER,
1221
 
            "a option 1v 1 :",
 
1251
            b"a option 1v 1 :",
1222
1252
            ])
1223
1253
        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
 
1254
        self.assertRaises(KnitCorrupt, index.keys)
1235
1255
 
1236
1256
    def test_invalid_size(self):
1237
1257
        transport = MockTransport([
1238
1258
            _KndxIndex.HEADER,
1239
 
            "a option 1 1v :",
 
1259
            b"a option 1 1v :",
1240
1260
            ])
1241
1261
        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
 
1262
        self.assertRaises(KnitCorrupt, index.keys)
1253
1263
 
1254
1264
    def test_scan_unvalidated_index_not_implemented(self):
1255
1265
        transport = MockTransport()
1263
1273
    def test_short_line(self):
1264
1274
        transport = MockTransport([
1265
1275
            _KndxIndex.HEADER,
1266
 
            "a option 0 10  :",
1267
 
            "b option 10 10 0", # This line isn't terminated, ignored
 
1276
            b"a option 0 10  :",
 
1277
            b"b option 10 10 0", # This line isn't terminated, ignored
1268
1278
            ])
1269
1279
        index = self.get_knit_index(transport, "filename", "r")
1270
 
        self.assertEqual(set([('a',)]), index.keys())
 
1280
        self.assertEqual({(b'a',)}, index.keys())
1271
1281
 
1272
1282
    def test_skip_incomplete_record(self):
1273
1283
        # A line with bogus data should just be skipped
1274
1284
        transport = MockTransport([
1275
1285
            _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'
 
1286
            b"a option 0 10  :",
 
1287
            b"b option 10 10 0", # This line isn't terminated, ignored
 
1288
            b"c option 20 10 0 :", # Properly terminated, and starts with '\n'
1279
1289
            ])
1280
1290
        index = self.get_knit_index(transport, "filename", "r")
1281
 
        self.assertEqual(set([('a',), ('c',)]), index.keys())
 
1291
        self.assertEqual({(b'a',), (b'c',)}, index.keys())
1282
1292
 
1283
1293
    def test_trailing_characters(self):
1284
1294
        # A line with bogus data should just be skipped
1285
1295
        transport = MockTransport([
1286
1296
            _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'
 
1297
            b"a option 0 10  :",
 
1298
            b"b option 10 10 0 :a", # This line has extra trailing characters
 
1299
            b"c option 20 10 0 :", # Properly terminated, and starts with '\n'
1290
1300
            ])
1291
1301
        index = self.get_knit_index(transport, "filename", "r")
1292
 
        self.assertEqual(set([('a',), ('c',)]), index.keys())
 
1302
        self.assertEqual({(b'a',), (b'c',)}, index.keys())
1293
1303
 
1294
1304
 
1295
1305
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1298
1308
 
1299
1309
    def get_knit_index(self, transport, name, mode):
1300
1310
        mapper = ConstantMapper(name)
1301
 
        from bzrlib._knit_load_data_pyx import _load_data_c
 
1311
        from ..bzr._knit_load_data_pyx import _load_data_c
1302
1312
        self.overrideAttr(knit, '_load_data', _load_data_c)
1303
1313
        allow_writes = lambda: mode == 'w'
1304
1314
        return _KndxIndex(transport, mapper, lambda:None,
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'], (b'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'],
1353
 
                           ('fulltext', False))
 
1362
        ann._expand_record(parent_key, (), None, [b'line1\n', b'line2\n'],
 
1363
                           (b'fulltext', False))
1354
1364
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
1355
1365
                                 record, details)
1356
1366
        self.assertEqual({parent_key: 1}, ann._num_compression_children)
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'],
1376
 
                           ('fulltext', False))
 
1385
                           [b'line1\n', b'line2\n', b'line3\n'],
 
1386
                           (b'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)
1416
1426
        self.assertEqual({}, ann._pending_annotation)
1417
1427
        # Now insert p1, and we should be able to expand the delta
1418
1428
        res = ann._expand_record(p1_key, (), None, p1_record,
1419
 
                                 ('fulltext', False))
 
1429
                                 (b'fulltext', False))
1420
1430
        self.assertEqual(p1_record, res)
1421
1431
        ann._annotations_cache[p1_key] = [(p1_key,)]*2
1422
1432
        res = ann._process_pending(p1_key)
1426
1436
        self.assertEqual({p2_key: [(rev_key, (p1_key, p2_key))]},
1427
1437
                         ann._pending_annotation)
1428
1438
        # Now fill in parent 2, and pending annotation should be satisfied
1429
 
        res = ann._expand_record(p2_key, (), None, [], ('fulltext', False))
 
1439
        res = ann._expand_record(p2_key, (), None, [], (b'fulltext', False))
1430
1440
        ann._annotations_cache[p2_key] = []
1431
1441
        res = ann._process_pending(p2_key)
1432
1442
        self.assertEqual([rev_key], res)
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'], (b'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()
1494
1504
        target.add_lines(basis, (), ['gam\n'])
1495
1505
        target.insert_record_stream(
1496
1506
            source.get_record_stream([broken], 'unordered', False))
1497
 
        err = self.assertRaises(errors.KnitCorrupt,
 
1507
        err = self.assertRaises(KnitCorrupt,
1498
1508
            target.get_record_stream([broken], 'unordered', True
1499
1509
            ).next().get_bytes_as, 'chunked')
1500
1510
        self.assertEqual(['gam\n', 'bar\n'], err.content)
1510
1520
        idx = knit._index
1511
1521
        idx.add_records([(('a-1',), ['fulltext'], (('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
1528
            (('a-2',), ['fulltext'], (('a-2',), 0, 0), [('a-1',)]),
1519
1529
            (('a-3',), ['fulltext'], (('a-3',), 0, 0), [('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({('a-3',), ('a-1',), ('a-2',)}, idx.keys())
1529
1539
        self.assertEqual({
1530
1540
            ('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False)),
1531
1541
            ('a-2',): ((('a-2',), 0, 0), None, (('a-1',),), ('fulltext', False)),
1532
1542
            ('a-3',): ((('a-3',), 0, 0), None, (('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({('a-1',): (),
 
1545
            ('a-2',): (('a-1',),),
 
1546
            ('a-3',): (('a-2',),),},
1537
1547
            idx.get_parent_map(idx.keys()))
1538
1548
 
1539
1549
    def test_add_versions_fails_clean(self):
1563
1573
 
1564
1574
        # Assert the pre-condition
1565
1575
        def assertA1Only():
1566
 
            self.assertEqual(set([('a-1',)]), set(idx.keys()))
 
1576
            self.assertEqual({('a-1',)}, set(idx.keys()))
1567
1577
            self.assertEqual(
1568
1578
                {('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False))},
1569
1579
                idx.get_build_details([('a-1',)]))
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('.')
 
1592
        t = transport.get_transport_from_path('.')
1583
1593
        t.put_bytes('test.kndx', '')
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
 
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)
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,
 
1703
        self.assertRaises(KnitCorrupt, index.add_records,
1694
1704
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1695
1705
        self.assertEqual([], self.caught_entries)
1696
1706
 
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,
 
1724
        self.assertRaises(KnitCorrupt, index.add_records,
1715
1725
            [(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1716
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
1726
        self.assertRaises(KnitCorrupt, index.add_records,
1717
1727
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
1718
1728
        # parents
1719
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
1729
        self.assertRaises(KnitCorrupt, index.add_records,
1720
1730
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1721
1731
        self.assertEqual([], self.caught_entries)
1722
1732
 
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,
 
1757
        self.assertRaises(KnitCorrupt, index.add_records,
1748
1758
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1749
1759
        self.assertEqual([], self.caught_entries)
1750
1760
 
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,
 
1784
        self.assertRaises(KnitCorrupt, index.add_records,
1775
1785
            [(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1776
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
1786
        self.assertRaises(KnitCorrupt, index.add_records,
1777
1787
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
1778
1788
        # parents
1779
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
1789
        self.assertRaises(KnitCorrupt, index.add_records,
1780
1790
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1781
1791
        # change options in the second record
1782
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
1792
        self.assertRaises(KnitCorrupt, index.add_records,
1783
1793
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)]),
1784
1794
             (('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1785
1795
        self.assertEqual([], self.caught_entries)
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 + b'tip', ), ' 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', ), 'N0 100'),
 
1937
            ((b'tail', ), '')])
1928
1938
        index2 = self.make_g_index('2', 0, [
1929
 
            (('parent', ), ' 100 78'),
1930
 
            (('separate', ), '')])
 
1939
            ((b'parent', ), ' 100 78'),
 
1940
            ((b'separate', ), '')])
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):
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,
 
1994
        self.assertRaises(KnitCorrupt, index.add_records,
1985
1995
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [])])
1986
1996
        self.assertEqual([], self.caught_entries)
1987
1997
 
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,
 
2013
        self.assertRaises(KnitCorrupt, index.add_records,
2004
2014
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2005
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
2015
        self.assertRaises(KnitCorrupt, index.add_records,
2006
2016
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
2007
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
2017
        self.assertRaises(KnitCorrupt, index.add_records,
2008
2018
            [(('tip',), 'fulltext', (None, 0, 100), [])])
2009
2019
        # parents
2010
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
 
2020
        self.assertRaises(KnitCorrupt, index.add_records,
2011
2021
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
2012
2022
        self.assertEqual([], self.caught_entries)
2013
2023
 
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',), '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',), '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',), 'fulltext,no-eol', (None, 0, 100), [])])
 
2054
        index.add_records([((b'tip',), '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',), 'fulltext,no-eol', (None, 50, 100), [])])
 
2058
        index.add_records([((b'tip',), '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',), 'no-eol,line-delta', (None, 0, 100), [])])
 
2067
        self.assertRaises(KnitCorrupt, index.add_records,
 
2068
            [((b'tip',), 'line-delta,no-eol', (None, 0, 100), [])])
 
2069
        self.assertRaises(KnitCorrupt, index.add_records,
 
2070
            [((b'tip',), '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',), 'fulltext,no-eol', (None, 0, 100), [('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',), 'fulltext,no-eol', (None, 0, 100), []),
 
2077
             ((b'tip',), '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
 
2419
2433
        key_basis = ('bar',)
2420
2434
        key_missing = ('missing',)
2421
2435
        test.add_lines(key, (), ['foo\n'])
2422
 
        key_sha1sum = osutils.sha('foo\n').hexdigest()
 
2436
        key_sha1sum = osutils.sha_string('foo\n')
2423
2437
        sha1s = test.get_sha1s([key])
2424
2438
        self.assertEqual({key: key_sha1sum}, sha1s)
2425
2439
        self.assertEqual([], basis.calls)
2427
2441
        # directly (rather than via text reconstruction) so that remote servers
2428
2442
        # etc don't have to answer with full content.
2429
2443
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
2430
 
        basis_sha1sum = osutils.sha('foo\nbar\n').hexdigest()
 
2444
        basis_sha1sum = osutils.sha_string('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):
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,)},
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,)},
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