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

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

Show diffs side-by-side

added added

removed removed

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