/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-02-18 21:42:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6859.
  • Revision ID: jelmer@jelmer.uk-20180218214257-jpevutp1wa30tz3v
Update TODO to reference Breezy, not Bazaar.

Show diffs side-by-side

added added

removed removed

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