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

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

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