/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: Robert Collins
  • Date: 2010-05-05 00:05:29 UTC
  • mto: This revision was merged to the branch mainline in revision 5206.
  • Revision ID: robertc@robertcollins.net-20100505000529-ltmllyms5watqj5u
Make 'pydoc bzrlib.tests.build_tree_shape' useful.

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