~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_knit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-11-03 01:53:30 UTC
  • mfrom: (2955.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20071103015330-pt1tec7wyxwwcey8
Fix #158972 don't use timeout for HttpServer

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
    errors,
27
27
    generate_ids,
28
28
    knit,
29
 
    multiparent,
30
29
    pack,
31
30
    )
32
31
from bzrlib.errors import (
39
38
from bzrlib.knit import (
40
39
    AnnotatedKnitContent,
41
40
    KnitContent,
 
41
    KnitGraphIndex,
 
42
    KnitVersionedFile,
 
43
    KnitPlainFactory,
 
44
    KnitAnnotateFactory,
 
45
    _KnitAccess,
 
46
    _KnitData,
 
47
    _KnitIndex,
 
48
    _PackAccess,
 
49
    PlainKnitContent,
 
50
    WeaveToKnit,
42
51
    KnitSequenceMatcher,
43
 
    KnitVersionedFiles,
44
 
    PlainKnitContent,
45
 
    _DirectPackAccess,
46
 
    _KndxIndex,
47
 
    _KnitGraphIndex,
48
 
    _KnitKeyAccess,
49
 
    make_file_factory,
50
52
    )
51
53
from bzrlib.osutils import split_lines
52
 
from bzrlib.symbol_versioning import one_four
53
54
from bzrlib.tests import (
54
55
    Feature,
55
 
    KnownFailure,
56
56
    TestCase,
57
57
    TestCaseWithMemoryTransport,
58
58
    TestCaseWithTransport,
59
59
    )
60
60
from bzrlib.transport import get_transport
61
61
from bzrlib.transport.memory import MemoryTransport
62
 
from bzrlib.tuned_gzip import GzipFile
63
 
from bzrlib.versionedfile import (
64
 
    AbsentContentFactory,
65
 
    ConstantMapper,
66
 
    RecordingVersionedFilesDecorator,
67
 
    )
 
62
from bzrlib.util import bencode
 
63
from bzrlib.weave import Weave
68
64
 
69
65
 
70
66
class _CompiledKnitFeature(Feature):
145
141
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\n', noeol=True)
146
142
 
147
143
 
148
 
TEXT_1 = """\
149
 
Banana cup cakes:
150
 
 
151
 
- bananas
152
 
- eggs
153
 
- broken tea cups
154
 
"""
155
 
 
156
 
TEXT_1A = """\
157
 
Banana cup cake recipe
158
 
(serves 6)
159
 
 
160
 
- bananas
161
 
- eggs
162
 
- broken tea cups
163
 
- self-raising flour
164
 
"""
165
 
 
166
 
TEXT_1B = """\
167
 
Banana cup cake recipe
168
 
 
169
 
- bananas (do not use plantains!!!)
170
 
- broken tea cups
171
 
- flour
172
 
"""
173
 
 
174
 
delta_1_1a = """\
175
 
0,1,2
176
 
Banana cup cake recipe
177
 
(serves 6)
178
 
5,5,1
179
 
- self-raising flour
180
 
"""
181
 
 
182
 
TEXT_2 = """\
183
 
Boeuf bourguignon
184
 
 
185
 
- beef
186
 
- red wine
187
 
- small onions
188
 
- carrot
189
 
- mushrooms
190
 
"""
191
 
 
192
 
 
193
144
class TestPlainKnitContent(TestCase, KnitContentTestsMixin):
194
145
 
195
146
    def _make_content(self, lines):
204
155
        self.assertEqual(content.annotate(),
205
156
            [("bogus", "text1"), ("bogus", "text2")])
206
157
 
 
158
    def test_annotate_iter(self):
 
159
        content = self._make_content([])
 
160
        it = content.annotate_iter()
 
161
        self.assertRaises(StopIteration, it.next)
 
162
 
 
163
        content = self._make_content([("bogus", "text1"), ("bogus", "text2")])
 
164
        it = content.annotate_iter()
 
165
        self.assertEqual(it.next(), ("bogus", "text1"))
 
166
        self.assertEqual(it.next(), ("bogus", "text2"))
 
167
        self.assertRaises(StopIteration, it.next)
 
168
 
207
169
    def test_line_delta(self):
208
170
        content1 = self._make_content([("", "a"), ("", "b")])
209
171
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
231
193
        self.assertEqual(content.annotate(),
232
194
            [("origin1", "text1"), ("origin2", "text2")])
233
195
 
 
196
    def test_annotate_iter(self):
 
197
        content = self._make_content([])
 
198
        it = content.annotate_iter()
 
199
        self.assertRaises(StopIteration, it.next)
 
200
 
 
201
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
 
202
        it = content.annotate_iter()
 
203
        self.assertEqual(it.next(), ("origin1", "text1"))
 
204
        self.assertEqual(it.next(), ("origin2", "text2"))
 
205
        self.assertRaises(StopIteration, it.next)
 
206
 
234
207
    def test_line_delta(self):
235
208
        content1 = self._make_content([("", "a"), ("", "b")])
236
209
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
274
247
class KnitRecordAccessTestsMixin(object):
275
248
    """Tests for getting and putting knit records."""
276
249
 
 
250
    def assertAccessExists(self, access):
 
251
        """Ensure the data area for access has been initialised/exists."""
 
252
        raise NotImplementedError(self.assertAccessExists)
 
253
 
277
254
    def test_add_raw_records(self):
278
255
        """Add_raw_records adds records retrievable later."""
279
256
        access = self.get_access()
280
 
        memos = access.add_raw_records([('key', 10)], '1234567890')
 
257
        memos = access.add_raw_records([10], '1234567890')
281
258
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
282
259
 
283
260
    def test_add_several_raw_records(self):
284
261
        """add_raw_records with many records and read some back."""
285
262
        access = self.get_access()
286
 
        memos = access.add_raw_records([('key', 10), ('key2', 2), ('key3', 5)],
287
 
            '12345678901234567')
 
263
        memos = access.add_raw_records([10, 2, 5], '12345678901234567')
288
264
        self.assertEqual(['1234567890', '12', '34567'],
289
265
            list(access.get_raw_records(memos)))
290
266
        self.assertEqual(['1234567890'],
296
272
        self.assertEqual(['1234567890', '34567'],
297
273
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
298
274
 
 
275
    def test_create(self):
 
276
        """create() should make a file on disk."""
 
277
        access = self.get_access()
 
278
        access.create()
 
279
        self.assertAccessExists(access)
 
280
 
 
281
    def test_open_file(self):
 
282
        """open_file never errors."""
 
283
        access = self.get_access()
 
284
        access.open_file()
 
285
 
299
286
 
300
287
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
301
288
    """Tests for the .kndx implementation."""
302
289
 
 
290
    def assertAccessExists(self, access):
 
291
        self.assertNotEqual(None, access.open_file())
 
292
 
303
293
    def get_access(self):
304
294
        """Get a .knit style access instance."""
305
 
        mapper = ConstantMapper("foo")
306
 
        access = _KnitKeyAccess(self.get_transport(), mapper)
 
295
        access = _KnitAccess(self.get_transport(), "foo.knit", None, None,
 
296
            False, False)
307
297
        return access
308
298
    
309
299
 
310
300
class TestPackKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
311
301
    """Tests for the pack based access."""
312
302
 
 
303
    def assertAccessExists(self, access):
 
304
        # as pack based access has no backing unless an index maps data, this
 
305
        # is a no-op.
 
306
        pass
 
307
 
313
308
    def get_access(self):
314
309
        return self._get_access()[0]
315
310
 
319
314
            transport.append_bytes(packname, bytes)
320
315
        writer = pack.ContainerWriter(write_data)
321
316
        writer.begin()
322
 
        access = _DirectPackAccess({})
323
 
        access.set_writer(writer, index, (transport, packname))
 
317
        indices = {index:(transport, packname)}
 
318
        access = _PackAccess(indices, writer=(writer, index))
324
319
        return access, writer
325
320
 
326
321
    def test_read_from_several_packs(self):
327
322
        access, writer = self._get_access()
328
323
        memos = []
329
 
        memos.extend(access.add_raw_records([('key', 10)], '1234567890'))
 
324
        memos.extend(access.add_raw_records([10], '1234567890'))
330
325
        writer.end()
331
326
        access, writer = self._get_access('pack2', 'FOOBAR')
332
 
        memos.extend(access.add_raw_records([('key', 5)], '12345'))
 
327
        memos.extend(access.add_raw_records([5], '12345'))
333
328
        writer.end()
334
329
        access, writer = self._get_access('pack3', 'BAZ')
335
 
        memos.extend(access.add_raw_records([('key', 5)], 'alpha'))
 
330
        memos.extend(access.add_raw_records([5], 'alpha'))
336
331
        writer.end()
337
332
        transport = self.get_transport()
338
 
        access = _DirectPackAccess({"FOO":(transport, 'packfile'),
 
333
        access = _PackAccess({"FOO":(transport, 'packfile'),
339
334
            "FOOBAR":(transport, 'pack2'),
340
335
            "BAZ":(transport, 'pack3')})
341
336
        self.assertEqual(['1234567890', '12345', 'alpha'],
351
346
 
352
347
    def test_set_writer(self):
353
348
        """The writer should be settable post construction."""
354
 
        access = _DirectPackAccess({})
 
349
        access = _PackAccess({})
355
350
        transport = self.get_transport()
356
351
        packname = 'packfile'
357
352
        index = 'foo'
360
355
        writer = pack.ContainerWriter(write_data)
361
356
        writer.begin()
362
357
        access.set_writer(writer, index, (transport, packname))
363
 
        memos = access.add_raw_records([('key', 10)], '1234567890')
 
358
        memos = access.add_raw_records([10], '1234567890')
364
359
        writer.end()
365
360
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
366
361
 
382
377
                                        'end rev-id-1\n'
383
378
                                        % (sha1sum,))
384
379
        transport = MockTransport([gz_txt])
385
 
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
386
 
        knit = KnitVersionedFiles(None, access)
387
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
388
 
 
389
 
        contents = list(knit._read_records_iter(records))
390
 
        self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'],
391
 
            '4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
392
 
 
393
 
        raw_contents = list(knit._read_records_iter_raw(records))
394
 
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
 
380
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
381
        data = _KnitData(access=access)
 
382
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
383
 
 
384
        contents = data.read_records(records)
 
385
        self.assertEqual({'rev-id-1':(['foo\n', 'bar\n'], sha1sum)}, contents)
 
386
 
 
387
        raw_contents = list(data.read_records_iter_raw(records))
 
388
        self.assertEqual([('rev-id-1', gz_txt)], raw_contents)
395
389
 
396
390
    def test_not_enough_lines(self):
397
391
        sha1sum = sha.new('foo\n').hexdigest()
401
395
                                        'end rev-id-1\n'
402
396
                                        % (sha1sum,))
403
397
        transport = MockTransport([gz_txt])
404
 
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
405
 
        knit = KnitVersionedFiles(None, access)
406
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
407
 
        self.assertRaises(errors.KnitCorrupt, list,
408
 
            knit._read_records_iter(records))
 
398
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
399
        data = _KnitData(access=access)
 
400
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
401
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
409
402
 
410
403
        # read_records_iter_raw won't detect that sort of mismatch/corruption
411
 
        raw_contents = list(knit._read_records_iter_raw(records))
412
 
        self.assertEqual([(('rev-id-1',),  gz_txt, sha1sum)], raw_contents)
 
404
        raw_contents = list(data.read_records_iter_raw(records))
 
405
        self.assertEqual([('rev-id-1', gz_txt)], raw_contents)
413
406
 
414
407
    def test_too_many_lines(self):
415
408
        sha1sum = sha.new('foo\nbar\n').hexdigest()
420
413
                                        'end rev-id-1\n'
421
414
                                        % (sha1sum,))
422
415
        transport = MockTransport([gz_txt])
423
 
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
424
 
        knit = KnitVersionedFiles(None, access)
425
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
426
 
        self.assertRaises(errors.KnitCorrupt, list,
427
 
            knit._read_records_iter(records))
 
416
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
417
        data = _KnitData(access=access)
 
418
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
419
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
428
420
 
429
421
        # read_records_iter_raw won't detect that sort of mismatch/corruption
430
 
        raw_contents = list(knit._read_records_iter_raw(records))
431
 
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
 
422
        raw_contents = list(data.read_records_iter_raw(records))
 
423
        self.assertEqual([('rev-id-1', gz_txt)], raw_contents)
432
424
 
433
425
    def test_mismatched_version_id(self):
434
426
        sha1sum = sha.new('foo\nbar\n').hexdigest()
438
430
                                        'end rev-id-1\n'
439
431
                                        % (sha1sum,))
440
432
        transport = MockTransport([gz_txt])
441
 
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
442
 
        knit = KnitVersionedFiles(None, access)
 
433
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
434
        data = _KnitData(access=access)
443
435
        # We are asking for rev-id-2, but the data is rev-id-1
444
 
        records = [(('rev-id-2',), (('rev-id-2',), 0, len(gz_txt)))]
445
 
        self.assertRaises(errors.KnitCorrupt, list,
446
 
            knit._read_records_iter(records))
 
436
        records = [('rev-id-2', (None, 0, len(gz_txt)))]
 
437
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
447
438
 
448
 
        # read_records_iter_raw detects mismatches in the header
 
439
        # read_records_iter_raw will notice if we request the wrong version.
449
440
        self.assertRaises(errors.KnitCorrupt, list,
450
 
            knit._read_records_iter_raw(records))
 
441
                          data.read_records_iter_raw(records))
451
442
 
452
443
    def test_uncompressed_data(self):
453
444
        sha1sum = sha.new('foo\nbar\n').hexdigest()
457
448
               'end rev-id-1\n'
458
449
               % (sha1sum,))
459
450
        transport = MockTransport([txt])
460
 
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
461
 
        knit = KnitVersionedFiles(None, access)
462
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(txt)))]
 
451
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
452
        data = _KnitData(access=access)
 
453
        records = [('rev-id-1', (None, 0, len(txt)))]
463
454
 
464
455
        # We don't have valid gzip data ==> corrupt
465
 
        self.assertRaises(errors.KnitCorrupt, list,
466
 
            knit._read_records_iter(records))
 
456
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
467
457
 
468
458
        # read_records_iter_raw will notice the bad data
469
459
        self.assertRaises(errors.KnitCorrupt, list,
470
 
            knit._read_records_iter_raw(records))
 
460
                          data.read_records_iter_raw(records))
471
461
 
472
462
    def test_corrupted_data(self):
473
463
        sha1sum = sha.new('foo\nbar\n').hexdigest()
479
469
        # Change 2 bytes in the middle to \xff
480
470
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
481
471
        transport = MockTransport([gz_txt])
482
 
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
483
 
        knit = KnitVersionedFiles(None, access)
484
 
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
485
 
        self.assertRaises(errors.KnitCorrupt, list,
486
 
            knit._read_records_iter(records))
487
 
        # read_records_iter_raw will barf on bad gz data
488
 
        self.assertRaises(errors.KnitCorrupt, list,
489
 
            knit._read_records_iter_raw(records))
 
472
        access = _KnitAccess(transport, 'filename', None, None, False, False)
 
473
        data = _KnitData(access=access)
 
474
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
475
 
 
476
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
 
477
 
 
478
        # read_records_iter_raw will notice if we request the wrong version.
 
479
        self.assertRaises(errors.KnitCorrupt, list,
 
480
                          data.read_records_iter_raw(records))
490
481
 
491
482
 
492
483
class LowLevelKnitIndexTests(TestCase):
493
484
 
494
 
    def get_knit_index(self, transport, name, mode):
495
 
        mapper = ConstantMapper(name)
 
485
    def get_knit_index(self, *args, **kwargs):
496
486
        orig = knit._load_data
497
487
        def reset():
498
488
            knit._load_data = orig
499
489
        self.addCleanup(reset)
500
490
        from bzrlib._knit_load_data_py import _load_data_py
501
491
        knit._load_data = _load_data_py
502
 
        allow_writes = lambda: 'w' in mode
503
 
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
 
492
        return _KnitIndex(*args, **kwargs)
 
493
 
 
494
    def test_no_such_file(self):
 
495
        transport = MockTransport()
 
496
 
 
497
        self.assertRaises(NoSuchFile, self.get_knit_index,
 
498
                          transport, "filename", "r")
 
499
        self.assertRaises(NoSuchFile, self.get_knit_index,
 
500
                          transport, "filename", "w", create=False)
504
501
 
505
502
    def test_create_file(self):
506
503
        transport = MockTransport()
507
 
        index = self.get_knit_index(transport, "filename", "w")
508
 
        index.keys()
509
 
        call = transport.calls.pop(0)
510
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
511
 
        self.assertEqual('put_file_non_atomic', call[0])
512
 
        self.assertEqual('filename.kndx', call[1][0])
513
 
        # With no history, _KndxIndex writes a new index:
514
 
        self.assertEqual(_KndxIndex.HEADER,
515
 
            call[1][1].getvalue())
516
 
        self.assertEqual({'create_parent_dir': True}, call[2])
 
504
 
 
505
        index = self.get_knit_index(transport, "filename", "w",
 
506
            file_mode="wb", create=True)
 
507
        self.assertEqual(
 
508
                ("put_bytes_non_atomic",
 
509
                    ("filename", index.HEADER), {"mode": "wb"}),
 
510
                transport.calls.pop(0))
 
511
 
 
512
    def test_delay_create_file(self):
 
513
        transport = MockTransport()
 
514
 
 
515
        index = self.get_knit_index(transport, "filename", "w",
 
516
            create=True, file_mode="wb", create_parent_dir=True,
 
517
            delay_create=True, dir_mode=0777)
 
518
        self.assertEqual([], transport.calls)
 
519
 
 
520
        index.add_versions([])
 
521
        name, (filename, f), kwargs = transport.calls.pop(0)
 
522
        self.assertEqual("put_file_non_atomic", name)
 
523
        self.assertEqual(
 
524
            {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
 
525
            kwargs)
 
526
        self.assertEqual("filename", filename)
 
527
        self.assertEqual(index.HEADER, f.read())
 
528
 
 
529
        index.add_versions([])
 
530
        self.assertEqual(("append_bytes", ("filename", ""), {}),
 
531
            transport.calls.pop(0))
517
532
 
518
533
    def test_read_utf8_version_id(self):
519
534
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
520
535
        utf8_revision_id = unicode_revision_id.encode('utf-8')
521
536
        transport = MockTransport([
522
 
            _KndxIndex.HEADER,
 
537
            _KnitIndex.HEADER,
523
538
            '%s option 0 1 :' % (utf8_revision_id,)
524
539
            ])
525
540
        index = self.get_knit_index(transport, "filename", "r")
526
 
        # _KndxIndex is a private class, and deals in utf8 revision_ids, not
 
541
        # _KnitIndex is a private class, and deals in utf8 revision_ids, not
527
542
        # Unicode revision_ids.
528
 
        self.assertEqual({(utf8_revision_id,):()},
529
 
            index.get_parent_map(index.keys()))
530
 
        self.assertFalse((unicode_revision_id,) in index.keys())
 
543
        self.assertTrue(index.has_version(utf8_revision_id))
 
544
        self.assertFalse(index.has_version(unicode_revision_id))
531
545
 
532
546
    def test_read_utf8_parents(self):
533
547
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
534
548
        utf8_revision_id = unicode_revision_id.encode('utf-8')
535
549
        transport = MockTransport([
536
 
            _KndxIndex.HEADER,
 
550
            _KnitIndex.HEADER,
537
551
            "version option 0 1 .%s :" % (utf8_revision_id,)
538
552
            ])
539
553
        index = self.get_knit_index(transport, "filename", "r")
540
 
        self.assertEqual({("version",):((utf8_revision_id,),)},
541
 
            index.get_parent_map(index.keys()))
 
554
        self.assertEqual([utf8_revision_id],
 
555
            index.get_parents_with_ghosts("version"))
542
556
 
543
557
    def test_read_ignore_corrupted_lines(self):
544
558
        transport = MockTransport([
545
 
            _KndxIndex.HEADER,
 
559
            _KnitIndex.HEADER,
546
560
            "corrupted",
547
561
            "corrupted options 0 1 .b .c ",
548
562
            "version options 0 1 :"
549
563
            ])
550
564
        index = self.get_knit_index(transport, "filename", "r")
551
 
        self.assertEqual(1, len(index.keys()))
552
 
        self.assertEqual(set([("version",)]), index.keys())
 
565
        self.assertEqual(1, index.num_versions())
 
566
        self.assertTrue(index.has_version("version"))
553
567
 
554
568
    def test_read_corrupted_header(self):
555
569
        transport = MockTransport(['not a bzr knit index header\n'])
556
 
        index = self.get_knit_index(transport, "filename", "r")
557
 
        self.assertRaises(KnitHeaderError, index.keys)
 
570
        self.assertRaises(KnitHeaderError,
 
571
            self.get_knit_index, transport, "filename", "r")
558
572
 
559
573
    def test_read_duplicate_entries(self):
560
574
        transport = MockTransport([
561
 
            _KndxIndex.HEADER,
 
575
            _KnitIndex.HEADER,
562
576
            "parent options 0 1 :",
563
577
            "version options1 0 1 0 :",
564
578
            "version options2 1 2 .other :",
565
579
            "version options3 3 4 0 .other :"
566
580
            ])
567
581
        index = self.get_knit_index(transport, "filename", "r")
568
 
        self.assertEqual(2, len(index.keys()))
 
582
        self.assertEqual(2, index.num_versions())
569
583
        # check that the index used is the first one written. (Specific
570
584
        # to KnitIndex style indices.
571
 
        self.assertEqual("1", index._dictionary_compress([("version",)]))
572
 
        self.assertEqual((("version",), 3, 4), index.get_position(("version",)))
573
 
        self.assertEqual(["options3"], index.get_options(("version",)))
574
 
        self.assertEqual({("version",):(("parent",), ("other",))},
575
 
            index.get_parent_map([("version",)]))
 
585
        self.assertEqual("1", index._version_list_to_index(["version"]))
 
586
        self.assertEqual((None, 3, 4), index.get_position("version"))
 
587
        self.assertEqual(["options3"], index.get_options("version"))
 
588
        self.assertEqual(["parent", "other"],
 
589
            index.get_parents_with_ghosts("version"))
576
590
 
577
591
    def test_read_compressed_parents(self):
578
592
        transport = MockTransport([
579
 
            _KndxIndex.HEADER,
 
593
            _KnitIndex.HEADER,
580
594
            "a option 0 1 :",
581
595
            "b option 0 1 0 :",
582
596
            "c option 0 1 1 0 :",
583
597
            ])
584
598
        index = self.get_knit_index(transport, "filename", "r")
585
 
        self.assertEqual({("b",):(("a",),), ("c",):(("b",), ("a",))},
586
 
            index.get_parent_map([("b",), ("c",)]))
 
599
        self.assertEqual(["a"], index.get_parents("b"))
 
600
        self.assertEqual(["b", "a"], index.get_parents("c"))
587
601
 
588
602
    def test_write_utf8_version_id(self):
589
603
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
590
604
        utf8_revision_id = unicode_revision_id.encode('utf-8')
591
605
        transport = MockTransport([
592
 
            _KndxIndex.HEADER
 
606
            _KnitIndex.HEADER
593
607
            ])
594
608
        index = self.get_knit_index(transport, "filename", "r")
595
 
        index.add_records([
596
 
            ((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
597
 
        call = transport.calls.pop(0)
598
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
599
 
        self.assertEqual('put_file_non_atomic', call[0])
600
 
        self.assertEqual('filename.kndx', call[1][0])
601
 
        # With no history, _KndxIndex writes a new index:
602
 
        self.assertEqual(_KndxIndex.HEADER +
603
 
            "\n%s option 0 1  :" % (utf8_revision_id,),
604
 
            call[1][1].getvalue())
605
 
        self.assertEqual({'create_parent_dir': True}, call[2])
 
609
        index.add_version(utf8_revision_id, ["option"], (None, 0, 1), [])
 
610
        self.assertEqual(("append_bytes", ("filename",
 
611
            "\n%s option 0 1  :" % (utf8_revision_id,)),
 
612
            {}),
 
613
            transport.calls.pop(0))
606
614
 
607
615
    def test_write_utf8_parents(self):
608
616
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
609
617
        utf8_revision_id = unicode_revision_id.encode('utf-8')
610
618
        transport = MockTransport([
611
 
            _KndxIndex.HEADER
612
 
            ])
613
 
        index = self.get_knit_index(transport, "filename", "r")
614
 
        index.add_records([
615
 
            (("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
616
 
        call = transport.calls.pop(0)
617
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
618
 
        self.assertEqual('put_file_non_atomic', call[0])
619
 
        self.assertEqual('filename.kndx', call[1][0])
620
 
        # With no history, _KndxIndex writes a new index:
621
 
        self.assertEqual(_KndxIndex.HEADER +
622
 
            "\nversion option 0 1 .%s :" % (utf8_revision_id,),
623
 
            call[1][1].getvalue())
624
 
        self.assertEqual({'create_parent_dir': True}, call[2])
625
 
 
626
 
    def test_keys(self):
627
 
        transport = MockTransport([
628
 
            _KndxIndex.HEADER
629
 
            ])
630
 
        index = self.get_knit_index(transport, "filename", "r")
631
 
 
632
 
        self.assertEqual(set(), index.keys())
633
 
 
634
 
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
635
 
        self.assertEqual(set([("a",)]), index.keys())
636
 
 
637
 
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
638
 
        self.assertEqual(set([("a",)]), index.keys())
639
 
 
640
 
        index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
641
 
        self.assertEqual(set([("a",), ("b",)]), index.keys())
642
 
 
643
 
    def add_a_b(self, index, random_id=None):
644
 
        kwargs = {}
645
 
        if random_id is not None:
646
 
            kwargs["random_id"] = random_id
647
 
        index.add_records([
648
 
            (("a",), ["option"], (("a",), 0, 1), [("b",)]),
649
 
            (("a",), ["opt"], (("a",), 1, 2), [("c",)]),
650
 
            (("b",), ["option"], (("b",), 2, 3), [("a",)])
651
 
            ], **kwargs)
652
 
 
653
 
    def assertIndexIsAB(self, index):
654
 
        self.assertEqual({
655
 
            ('a',): (('c',),),
656
 
            ('b',): (('a',),),
657
 
            },
658
 
            index.get_parent_map(index.keys()))
659
 
        self.assertEqual((("a",), 1, 2), index.get_position(("a",)))
660
 
        self.assertEqual((("b",), 2, 3), index.get_position(("b",)))
661
 
        self.assertEqual(["opt"], index.get_options(("a",)))
 
619
            _KnitIndex.HEADER
 
620
            ])
 
621
        index = self.get_knit_index(transport, "filename", "r")
 
622
        index.add_version("version", ["option"], (None, 0, 1), [utf8_revision_id])
 
623
        self.assertEqual(("append_bytes", ("filename",
 
624
            "\nversion option 0 1 .%s :" % (utf8_revision_id,)),
 
625
            {}),
 
626
            transport.calls.pop(0))
 
627
 
 
628
    def test_get_graph(self):
 
629
        transport = MockTransport()
 
630
        index = self.get_knit_index(transport, "filename", "w", create=True)
 
631
        self.assertEqual([], index.get_graph())
 
632
 
 
633
        index.add_version("a", ["option"], (None, 0, 1), ["b"])
 
634
        self.assertEqual([("a", ["b"])], index.get_graph())
 
635
 
 
636
        index.add_version("c", ["option"], (None, 0, 1), ["d"])
 
637
        self.assertEqual([("a", ["b"]), ("c", ["d"])],
 
638
            sorted(index.get_graph()))
 
639
 
 
640
    def test_get_ancestry(self):
 
641
        transport = MockTransport([
 
642
            _KnitIndex.HEADER,
 
643
            "a option 0 1 :",
 
644
            "b option 0 1 0 .e :",
 
645
            "c option 0 1 1 0 :",
 
646
            "d option 0 1 2 .f :"
 
647
            ])
 
648
        index = self.get_knit_index(transport, "filename", "r")
 
649
 
 
650
        self.assertEqual([], index.get_ancestry([]))
 
651
        self.assertEqual(["a"], index.get_ancestry(["a"]))
 
652
        self.assertEqual(["a", "b"], index.get_ancestry(["b"]))
 
653
        self.assertEqual(["a", "b", "c"], index.get_ancestry(["c"]))
 
654
        self.assertEqual(["a", "b", "c", "d"], index.get_ancestry(["d"]))
 
655
        self.assertEqual(["a", "b"], index.get_ancestry(["a", "b"]))
 
656
        self.assertEqual(["a", "b", "c"], index.get_ancestry(["a", "c"]))
 
657
 
 
658
        self.assertRaises(RevisionNotPresent, index.get_ancestry, ["e"])
 
659
 
 
660
    def test_get_ancestry_with_ghosts(self):
 
661
        transport = MockTransport([
 
662
            _KnitIndex.HEADER,
 
663
            "a option 0 1 :",
 
664
            "b option 0 1 0 .e :",
 
665
            "c option 0 1 0 .f .g :",
 
666
            "d option 0 1 2 .h .j .k :"
 
667
            ])
 
668
        index = self.get_knit_index(transport, "filename", "r")
 
669
 
 
670
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
 
671
        self.assertEqual(["a"], index.get_ancestry_with_ghosts(["a"]))
 
672
        self.assertEqual(["a", "e", "b"],
 
673
            index.get_ancestry_with_ghosts(["b"]))
 
674
        self.assertEqual(["a", "g", "f", "c"],
 
675
            index.get_ancestry_with_ghosts(["c"]))
 
676
        self.assertEqual(["a", "g", "f", "c", "k", "j", "h", "d"],
 
677
            index.get_ancestry_with_ghosts(["d"]))
 
678
        self.assertEqual(["a", "e", "b"],
 
679
            index.get_ancestry_with_ghosts(["a", "b"]))
 
680
        self.assertEqual(["a", "g", "f", "c"],
 
681
            index.get_ancestry_with_ghosts(["a", "c"]))
 
682
        self.assertEqual(
 
683
            ["a", "g", "f", "c", "e", "b", "k", "j", "h", "d"],
 
684
            index.get_ancestry_with_ghosts(["b", "d"]))
 
685
 
 
686
        self.assertRaises(RevisionNotPresent,
 
687
            index.get_ancestry_with_ghosts, ["e"])
 
688
 
 
689
    def test_iter_parents(self):
 
690
        transport = MockTransport()
 
691
        index = self.get_knit_index(transport, "filename", "w", create=True)
 
692
        # no parents
 
693
        index.add_version('r0', ['option'], (None, 0, 1), [])
 
694
        # 1 parent
 
695
        index.add_version('r1', ['option'], (None, 0, 1), ['r0'])
 
696
        # 2 parents
 
697
        index.add_version('r2', ['option'], (None, 0, 1), ['r1', 'r0'])
 
698
        # XXX TODO a ghost
 
699
        # cases: each sample data individually:
 
700
        self.assertEqual(set([('r0', ())]),
 
701
            set(index.iter_parents(['r0'])))
 
702
        self.assertEqual(set([('r1', ('r0', ))]),
 
703
            set(index.iter_parents(['r1'])))
 
704
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
 
705
            set(index.iter_parents(['r2'])))
 
706
        # no nodes returned for a missing node
 
707
        self.assertEqual(set(),
 
708
            set(index.iter_parents(['missing'])))
 
709
        # 1 node returned with missing nodes skipped
 
710
        self.assertEqual(set([('r1', ('r0', ))]),
 
711
            set(index.iter_parents(['ghost1', 'r1', 'ghost'])))
 
712
        # 2 nodes returned
 
713
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
714
            set(index.iter_parents(['r0', 'r1'])))
 
715
        # 2 nodes returned, missing skipped
 
716
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
717
            set(index.iter_parents(['a', 'r0', 'b', 'r1', 'c'])))
 
718
 
 
719
    def test_num_versions(self):
 
720
        transport = MockTransport([
 
721
            _KnitIndex.HEADER
 
722
            ])
 
723
        index = self.get_knit_index(transport, "filename", "r")
 
724
 
 
725
        self.assertEqual(0, index.num_versions())
 
726
        self.assertEqual(0, len(index))
 
727
 
 
728
        index.add_version("a", ["option"], (None, 0, 1), [])
 
729
        self.assertEqual(1, index.num_versions())
 
730
        self.assertEqual(1, len(index))
 
731
 
 
732
        index.add_version("a", ["option2"], (None, 1, 2), [])
 
733
        self.assertEqual(1, index.num_versions())
 
734
        self.assertEqual(1, len(index))
 
735
 
 
736
        index.add_version("b", ["option"], (None, 0, 1), [])
 
737
        self.assertEqual(2, index.num_versions())
 
738
        self.assertEqual(2, len(index))
 
739
 
 
740
    def test_get_versions(self):
 
741
        transport = MockTransport([
 
742
            _KnitIndex.HEADER
 
743
            ])
 
744
        index = self.get_knit_index(transport, "filename", "r")
 
745
 
 
746
        self.assertEqual([], index.get_versions())
 
747
 
 
748
        index.add_version("a", ["option"], (None, 0, 1), [])
 
749
        self.assertEqual(["a"], index.get_versions())
 
750
 
 
751
        index.add_version("a", ["option"], (None, 0, 1), [])
 
752
        self.assertEqual(["a"], index.get_versions())
 
753
 
 
754
        index.add_version("b", ["option"], (None, 0, 1), [])
 
755
        self.assertEqual(["a", "b"], index.get_versions())
 
756
 
 
757
    def test_add_version(self):
 
758
        transport = MockTransport([
 
759
            _KnitIndex.HEADER
 
760
            ])
 
761
        index = self.get_knit_index(transport, "filename", "r")
 
762
 
 
763
        index.add_version("a", ["option"], (None, 0, 1), ["b"])
 
764
        self.assertEqual(("append_bytes",
 
765
            ("filename", "\na option 0 1 .b :"),
 
766
            {}), transport.calls.pop(0))
 
767
        self.assertTrue(index.has_version("a"))
 
768
        self.assertEqual(1, index.num_versions())
 
769
        self.assertEqual((None, 0, 1), index.get_position("a"))
 
770
        self.assertEqual(["option"], index.get_options("a"))
 
771
        self.assertEqual(["b"], index.get_parents_with_ghosts("a"))
 
772
 
 
773
        index.add_version("a", ["opt"], (None, 1, 2), ["c"])
 
774
        self.assertEqual(("append_bytes",
 
775
            ("filename", "\na opt 1 2 .c :"),
 
776
            {}), transport.calls.pop(0))
 
777
        self.assertTrue(index.has_version("a"))
 
778
        self.assertEqual(1, index.num_versions())
 
779
        self.assertEqual((None, 1, 2), index.get_position("a"))
 
780
        self.assertEqual(["opt"], index.get_options("a"))
 
781
        self.assertEqual(["c"], index.get_parents_with_ghosts("a"))
 
782
 
 
783
        index.add_version("b", ["option"], (None, 2, 3), ["a"])
 
784
        self.assertEqual(("append_bytes",
 
785
            ("filename", "\nb option 2 3 0 :"),
 
786
            {}), transport.calls.pop(0))
 
787
        self.assertTrue(index.has_version("b"))
 
788
        self.assertEqual(2, index.num_versions())
 
789
        self.assertEqual((None, 2, 3), index.get_position("b"))
 
790
        self.assertEqual(["option"], index.get_options("b"))
 
791
        self.assertEqual(["a"], index.get_parents_with_ghosts("b"))
662
792
 
663
793
    def test_add_versions(self):
664
794
        transport = MockTransport([
665
 
            _KndxIndex.HEADER
 
795
            _KnitIndex.HEADER
666
796
            ])
667
797
        index = self.get_knit_index(transport, "filename", "r")
668
798
 
669
 
        self.add_a_b(index)
670
 
        call = transport.calls.pop(0)
671
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
672
 
        self.assertEqual('put_file_non_atomic', call[0])
673
 
        self.assertEqual('filename.kndx', call[1][0])
674
 
        # With no history, _KndxIndex writes a new index:
675
 
        self.assertEqual(
676
 
            _KndxIndex.HEADER +
 
799
        index.add_versions([
 
800
            ("a", ["option"], (None, 0, 1), ["b"]),
 
801
            ("a", ["opt"], (None, 1, 2), ["c"]),
 
802
            ("b", ["option"], (None, 2, 3), ["a"])
 
803
            ])
 
804
        self.assertEqual(("append_bytes", ("filename",
677
805
            "\na option 0 1 .b :"
678
806
            "\na opt 1 2 .c :"
679
 
            "\nb option 2 3 0 :",
680
 
            call[1][1].getvalue())
681
 
        self.assertEqual({'create_parent_dir': True}, call[2])
682
 
        self.assertIndexIsAB(index)
 
807
            "\nb option 2 3 0 :"
 
808
            ), {}), transport.calls.pop(0))
 
809
        self.assertTrue(index.has_version("a"))
 
810
        self.assertTrue(index.has_version("b"))
 
811
        self.assertEqual(2, index.num_versions())
 
812
        self.assertEqual((None, 1, 2), index.get_position("a"))
 
813
        self.assertEqual((None, 2, 3), index.get_position("b"))
 
814
        self.assertEqual(["opt"], index.get_options("a"))
 
815
        self.assertEqual(["option"], index.get_options("b"))
 
816
        self.assertEqual(["c"], index.get_parents_with_ghosts("a"))
 
817
        self.assertEqual(["a"], index.get_parents_with_ghosts("b"))
683
818
 
684
819
    def test_add_versions_random_id_is_accepted(self):
685
820
        transport = MockTransport([
686
 
            _KndxIndex.HEADER
 
821
            _KnitIndex.HEADER
687
822
            ])
688
823
        index = self.get_knit_index(transport, "filename", "r")
689
 
        self.add_a_b(index, random_id=True)
 
824
 
 
825
        index.add_versions([
 
826
            ("a", ["option"], (None, 0, 1), ["b"]),
 
827
            ("a", ["opt"], (None, 1, 2), ["c"]),
 
828
            ("b", ["option"], (None, 2, 3), ["a"])
 
829
            ], random_id=True)
690
830
 
691
831
    def test_delay_create_and_add_versions(self):
692
832
        transport = MockTransport()
693
833
 
694
 
        index = self.get_knit_index(transport, "filename", "w")
695
 
        # dir_mode=0777)
 
834
        index = self.get_knit_index(transport, "filename", "w",
 
835
            create=True, file_mode="wb", create_parent_dir=True,
 
836
            delay_create=True, dir_mode=0777)
696
837
        self.assertEqual([], transport.calls)
697
 
        self.add_a_b(index)
698
 
        #self.assertEqual(
699
 
        #[    {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
700
 
        #    kwargs)
701
 
        # Two calls: one during which we load the existing index (and when its
702
 
        # missing create it), then a second where we write the contents out.
703
 
        self.assertEqual(2, len(transport.calls))
704
 
        call = transport.calls.pop(0)
705
 
        self.assertEqual('put_file_non_atomic', call[0])
706
 
        self.assertEqual('filename.kndx', call[1][0])
707
 
        # With no history, _KndxIndex writes a new index:
708
 
        self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
709
 
        self.assertEqual({'create_parent_dir': True}, call[2])
710
 
        call = transport.calls.pop(0)
711
 
        # call[1][1] is a StringIO - we can't test it by simple equality.
712
 
        self.assertEqual('put_file_non_atomic', call[0])
713
 
        self.assertEqual('filename.kndx', call[1][0])
714
 
        # With no history, _KndxIndex writes a new index:
715
 
        self.assertEqual(
716
 
            _KndxIndex.HEADER +
 
838
 
 
839
        index.add_versions([
 
840
            ("a", ["option"], (None, 0, 1), ["b"]),
 
841
            ("a", ["opt"], (None, 1, 2), ["c"]),
 
842
            ("b", ["option"], (None, 2, 3), ["a"])
 
843
            ])
 
844
        name, (filename, f), kwargs = transport.calls.pop(0)
 
845
        self.assertEqual("put_file_non_atomic", name)
 
846
        self.assertEqual(
 
847
            {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
 
848
            kwargs)
 
849
        self.assertEqual("filename", filename)
 
850
        self.assertEqual(
 
851
            index.HEADER +
717
852
            "\na option 0 1 .b :"
718
853
            "\na opt 1 2 .c :"
719
854
            "\nb option 2 3 0 :",
720
 
            call[1][1].getvalue())
721
 
        self.assertEqual({'create_parent_dir': True}, call[2])
 
855
            f.read())
 
856
 
 
857
    def test_has_version(self):
 
858
        transport = MockTransport([
 
859
            _KnitIndex.HEADER,
 
860
            "a option 0 1 :"
 
861
            ])
 
862
        index = self.get_knit_index(transport, "filename", "r")
 
863
 
 
864
        self.assertTrue(index.has_version("a"))
 
865
        self.assertFalse(index.has_version("b"))
722
866
 
723
867
    def test_get_position(self):
724
868
        transport = MockTransport([
725
 
            _KndxIndex.HEADER,
 
869
            _KnitIndex.HEADER,
726
870
            "a option 0 1 :",
727
871
            "b option 1 2 :"
728
872
            ])
729
873
        index = self.get_knit_index(transport, "filename", "r")
730
874
 
731
 
        self.assertEqual((("a",), 0, 1), index.get_position(("a",)))
732
 
        self.assertEqual((("b",), 1, 2), index.get_position(("b",)))
 
875
        self.assertEqual((None, 0, 1), index.get_position("a"))
 
876
        self.assertEqual((None, 1, 2), index.get_position("b"))
733
877
 
734
878
    def test_get_method(self):
735
879
        transport = MockTransport([
736
 
            _KndxIndex.HEADER,
 
880
            _KnitIndex.HEADER,
737
881
            "a fulltext,unknown 0 1 :",
738
882
            "b unknown,line-delta 1 2 :",
739
883
            "c bad 3 4 :"
746
890
 
747
891
    def test_get_options(self):
748
892
        transport = MockTransport([
749
 
            _KndxIndex.HEADER,
 
893
            _KnitIndex.HEADER,
750
894
            "a opt1 0 1 :",
751
895
            "b opt2,opt3 1 2 :"
752
896
            ])
755
899
        self.assertEqual(["opt1"], index.get_options("a"))
756
900
        self.assertEqual(["opt2", "opt3"], index.get_options("b"))
757
901
 
758
 
    def test_get_parent_map(self):
759
 
        transport = MockTransport([
760
 
            _KndxIndex.HEADER,
761
 
            "a option 0 1 :",
762
 
            "b option 1 2 0 .c :",
763
 
            "c option 1 2 1 0 .e :"
764
 
            ])
765
 
        index = self.get_knit_index(transport, "filename", "r")
766
 
 
767
 
        self.assertEqual({
768
 
            ("a",):(),
769
 
            ("b",):(("a",), ("c",)),
770
 
            ("c",):(("b",), ("a",), ("e",)),
771
 
            }, index.get_parent_map(index.keys()))
 
902
    def test_get_parents(self):
 
903
        transport = MockTransport([
 
904
            _KnitIndex.HEADER,
 
905
            "a option 0 1 :",
 
906
            "b option 1 2 0 .c :",
 
907
            "c option 1 2 1 0 .e :"
 
908
            ])
 
909
        index = self.get_knit_index(transport, "filename", "r")
 
910
 
 
911
        self.assertEqual([], index.get_parents("a"))
 
912
        self.assertEqual(["a", "c"], index.get_parents("b"))
 
913
        self.assertEqual(["b", "a"], index.get_parents("c"))
 
914
 
 
915
    def test_get_parents_with_ghosts(self):
 
916
        transport = MockTransport([
 
917
            _KnitIndex.HEADER,
 
918
            "a option 0 1 :",
 
919
            "b option 1 2 0 .c :",
 
920
            "c option 1 2 1 0 .e :"
 
921
            ])
 
922
        index = self.get_knit_index(transport, "filename", "r")
 
923
 
 
924
        self.assertEqual([], index.get_parents_with_ghosts("a"))
 
925
        self.assertEqual(["a", "c"], index.get_parents_with_ghosts("b"))
 
926
        self.assertEqual(["b", "a", "e"],
 
927
            index.get_parents_with_ghosts("c"))
 
928
 
 
929
    def test_check_versions_present(self):
 
930
        transport = MockTransport([
 
931
            _KnitIndex.HEADER,
 
932
            "a option 0 1 :",
 
933
            "b option 0 1 :"
 
934
            ])
 
935
        index = self.get_knit_index(transport, "filename", "r")
 
936
 
 
937
        check = index.check_versions_present
 
938
 
 
939
        check([])
 
940
        check(["a"])
 
941
        check(["b"])
 
942
        check(["a", "b"])
 
943
        self.assertRaises(RevisionNotPresent, check, ["c"])
 
944
        self.assertRaises(RevisionNotPresent, check, ["a", "b", "c"])
772
945
 
773
946
    def test_impossible_parent(self):
774
947
        """Test we get KnitCorrupt if the parent couldn't possibly exist."""
775
948
        transport = MockTransport([
776
 
            _KndxIndex.HEADER,
 
949
            _KnitIndex.HEADER,
777
950
            "a option 0 1 :",
778
951
            "b option 0 1 4 :"  # We don't have a 4th record
779
952
            ])
780
 
        index = self.get_knit_index(transport, 'filename', 'r')
781
953
        try:
782
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
 
954
            self.assertRaises(errors.KnitCorrupt,
 
955
                              self.get_knit_index, transport, 'filename', 'r')
783
956
        except TypeError, e:
784
957
            if (str(e) == ('exceptions must be strings, classes, or instances,'
785
958
                           ' not exceptions.IndexError')
792
965
 
793
966
    def test_corrupted_parent(self):
794
967
        transport = MockTransport([
795
 
            _KndxIndex.HEADER,
 
968
            _KnitIndex.HEADER,
796
969
            "a option 0 1 :",
797
970
            "b option 0 1 :",
798
971
            "c option 0 1 1v :", # Can't have a parent of '1v'
799
972
            ])
800
 
        index = self.get_knit_index(transport, 'filename', 'r')
801
973
        try:
802
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
 
974
            self.assertRaises(errors.KnitCorrupt,
 
975
                              self.get_knit_index, transport, 'filename', 'r')
803
976
        except TypeError, e:
804
977
            if (str(e) == ('exceptions must be strings, classes, or instances,'
805
978
                           ' not exceptions.ValueError')
812
985
 
813
986
    def test_corrupted_parent_in_list(self):
814
987
        transport = MockTransport([
815
 
            _KndxIndex.HEADER,
 
988
            _KnitIndex.HEADER,
816
989
            "a option 0 1 :",
817
990
            "b option 0 1 :",
818
991
            "c option 0 1 1 v :", # Can't have a parent of 'v'
819
992
            ])
820
 
        index = self.get_knit_index(transport, 'filename', 'r')
821
993
        try:
822
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
 
994
            self.assertRaises(errors.KnitCorrupt,
 
995
                              self.get_knit_index, transport, 'filename', 'r')
823
996
        except TypeError, e:
824
997
            if (str(e) == ('exceptions must be strings, classes, or instances,'
825
998
                           ' not exceptions.ValueError')
832
1005
 
833
1006
    def test_invalid_position(self):
834
1007
        transport = MockTransport([
835
 
            _KndxIndex.HEADER,
 
1008
            _KnitIndex.HEADER,
836
1009
            "a option 1v 1 :",
837
1010
            ])
838
 
        index = self.get_knit_index(transport, 'filename', 'r')
839
1011
        try:
840
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
 
1012
            self.assertRaises(errors.KnitCorrupt,
 
1013
                              self.get_knit_index, transport, 'filename', 'r')
841
1014
        except TypeError, e:
842
1015
            if (str(e) == ('exceptions must be strings, classes, or instances,'
843
1016
                           ' not exceptions.ValueError')
850
1023
 
851
1024
    def test_invalid_size(self):
852
1025
        transport = MockTransport([
853
 
            _KndxIndex.HEADER,
 
1026
            _KnitIndex.HEADER,
854
1027
            "a option 1 1v :",
855
1028
            ])
856
 
        index = self.get_knit_index(transport, 'filename', 'r')
857
1029
        try:
858
 
            self.assertRaises(errors.KnitCorrupt, index.keys)
 
1030
            self.assertRaises(errors.KnitCorrupt,
 
1031
                              self.get_knit_index, transport, 'filename', 'r')
859
1032
        except TypeError, e:
860
1033
            if (str(e) == ('exceptions must be strings, classes, or instances,'
861
1034
                           ' not exceptions.ValueError')
868
1041
 
869
1042
    def test_short_line(self):
870
1043
        transport = MockTransport([
871
 
            _KndxIndex.HEADER,
 
1044
            _KnitIndex.HEADER,
872
1045
            "a option 0 10  :",
873
1046
            "b option 10 10 0", # This line isn't terminated, ignored
874
1047
            ])
875
1048
        index = self.get_knit_index(transport, "filename", "r")
876
 
        self.assertEqual(set([('a',)]), index.keys())
 
1049
        self.assertEqual(['a'], index.get_versions())
877
1050
 
878
1051
    def test_skip_incomplete_record(self):
879
1052
        # A line with bogus data should just be skipped
880
1053
        transport = MockTransport([
881
 
            _KndxIndex.HEADER,
 
1054
            _KnitIndex.HEADER,
882
1055
            "a option 0 10  :",
883
1056
            "b option 10 10 0", # This line isn't terminated, ignored
884
1057
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
885
1058
            ])
886
1059
        index = self.get_knit_index(transport, "filename", "r")
887
 
        self.assertEqual(set([('a',), ('c',)]), index.keys())
 
1060
        self.assertEqual(['a', 'c'], index.get_versions())
888
1061
 
889
1062
    def test_trailing_characters(self):
890
1063
        # A line with bogus data should just be skipped
891
1064
        transport = MockTransport([
892
 
            _KndxIndex.HEADER,
 
1065
            _KnitIndex.HEADER,
893
1066
            "a option 0 10  :",
894
1067
            "b option 10 10 0 :a", # This line has extra trailing characters
895
1068
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
896
1069
            ])
897
1070
        index = self.get_knit_index(transport, "filename", "r")
898
 
        self.assertEqual(set([('a',), ('c',)]), index.keys())
 
1071
        self.assertEqual(['a', 'c'], index.get_versions())
899
1072
 
900
1073
 
901
1074
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
902
1075
 
903
1076
    _test_needs_features = [CompiledKnitFeature]
904
1077
 
905
 
    def get_knit_index(self, transport, name, mode):
906
 
        mapper = ConstantMapper(name)
 
1078
    def get_knit_index(self, *args, **kwargs):
907
1079
        orig = knit._load_data
908
1080
        def reset():
909
1081
            knit._load_data = orig
910
1082
        self.addCleanup(reset)
911
1083
        from bzrlib._knit_load_data_c import _load_data_c
912
1084
        knit._load_data = _load_data_c
913
 
        allow_writes = lambda: mode == 'w'
914
 
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
 
1085
        return _KnitIndex(*args, **kwargs)
 
1086
 
915
1087
 
916
1088
 
917
1089
class KnitTests(TestCaseWithTransport):
918
1090
    """Class containing knit test helper routines."""
919
1091
 
920
 
    def make_test_knit(self, annotate=False, name='test'):
921
 
        mapper = ConstantMapper(name)
922
 
        return make_file_factory(annotate, mapper)(self.get_transport())
 
1092
    def make_test_knit(self, annotate=False, delay_create=False, index=None,
 
1093
                       name='test'):
 
1094
        if not annotate:
 
1095
            factory = KnitPlainFactory()
 
1096
        else:
 
1097
            factory = None
 
1098
        return KnitVersionedFile(name, get_transport('.'), access_mode='w',
 
1099
                                 factory=factory, create=True,
 
1100
                                 delay_create=delay_create, index=index)
 
1101
 
 
1102
    def assertRecordContentEqual(self, knit, version_id, candidate_content):
 
1103
        """Assert that some raw record content matches the raw record content
 
1104
        for a particular version_id in the given knit.
 
1105
        """
 
1106
        index_memo = knit._index.get_position(version_id)
 
1107
        record = (version_id, index_memo)
 
1108
        [(_, expected_content)] = list(knit._data.read_records_iter_raw([record]))
 
1109
        self.assertEqual(expected_content, candidate_content)
 
1110
 
 
1111
 
 
1112
class BasicKnitTests(KnitTests):
 
1113
 
 
1114
    def add_stock_one_and_one_a(self, k):
 
1115
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
1116
        k.add_lines('text-1a', ['text-1'], split_lines(TEXT_1A))
 
1117
 
 
1118
    def test_knit_constructor(self):
 
1119
        """Construct empty k"""
 
1120
        self.make_test_knit()
 
1121
 
 
1122
    def test_make_explicit_index(self):
 
1123
        """We can supply an index to use."""
 
1124
        knit = KnitVersionedFile('test', get_transport('.'),
 
1125
            index='strangelove')
 
1126
        self.assertEqual(knit._index, 'strangelove')
 
1127
 
 
1128
    def test_knit_add(self):
 
1129
        """Store one text in knit and retrieve"""
 
1130
        k = self.make_test_knit()
 
1131
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
1132
        self.assertTrue(k.has_version('text-1'))
 
1133
        self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
 
1134
 
 
1135
    def test_knit_reload(self):
 
1136
        # test that the content in a reloaded knit is correct
 
1137
        k = self.make_test_knit()
 
1138
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
1139
        del k
 
1140
        k2 = KnitVersionedFile('test', get_transport('.'), access_mode='r', factory=KnitPlainFactory(), create=True)
 
1141
        self.assertTrue(k2.has_version('text-1'))
 
1142
        self.assertEqualDiff(''.join(k2.get_lines('text-1')), TEXT_1)
 
1143
 
 
1144
    def test_knit_several(self):
 
1145
        """Store several texts in a knit"""
 
1146
        k = self.make_test_knit()
 
1147
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
1148
        k.add_lines('text-2', [], split_lines(TEXT_2))
 
1149
        self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
 
1150
        self.assertEqualDiff(''.join(k.get_lines('text-2')), TEXT_2)
 
1151
        
 
1152
    def test_repeated_add(self):
 
1153
        """Knit traps attempt to replace existing version"""
 
1154
        k = self.make_test_knit()
 
1155
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
1156
        self.assertRaises(RevisionAlreadyPresent, 
 
1157
                k.add_lines,
 
1158
                'text-1', [], split_lines(TEXT_1))
 
1159
 
 
1160
    def test_empty(self):
 
1161
        k = self.make_test_knit(True)
 
1162
        k.add_lines('text-1', [], [])
 
1163
        self.assertEquals(k.get_lines('text-1'), [])
 
1164
 
 
1165
    def test_incomplete(self):
 
1166
        """Test if texts without a ending line-end can be inserted and
 
1167
        extracted."""
 
1168
        k = KnitVersionedFile('test', get_transport('.'), delta=False, create=True)
 
1169
        k.add_lines('text-1', [], ['a\n',    'b'  ])
 
1170
        k.add_lines('text-2', ['text-1'], ['a\rb\n', 'b\n'])
 
1171
        # reopening ensures maximum room for confusion
 
1172
        k = KnitVersionedFile('test', get_transport('.'), delta=False, create=True)
 
1173
        self.assertEquals(k.get_lines('text-1'), ['a\n',    'b'  ])
 
1174
        self.assertEquals(k.get_lines('text-2'), ['a\rb\n', 'b\n'])
 
1175
 
 
1176
    def test_delta(self):
 
1177
        """Expression of knit delta as lines"""
 
1178
        k = self.make_test_knit()
 
1179
        td = list(line_delta(TEXT_1.splitlines(True),
 
1180
                             TEXT_1A.splitlines(True)))
 
1181
        self.assertEqualDiff(''.join(td), delta_1_1a)
 
1182
        out = apply_line_delta(TEXT_1.splitlines(True), td)
 
1183
        self.assertEqualDiff(''.join(out), TEXT_1A)
 
1184
 
 
1185
    def test_add_with_parents(self):
 
1186
        """Store in knit with parents"""
 
1187
        k = self.make_test_knit()
 
1188
        self.add_stock_one_and_one_a(k)
 
1189
        self.assertEquals(k.get_parents('text-1'), [])
 
1190
        self.assertEquals(k.get_parents('text-1a'), ['text-1'])
 
1191
 
 
1192
    def test_ancestry(self):
 
1193
        """Store in knit with parents"""
 
1194
        k = self.make_test_knit()
 
1195
        self.add_stock_one_and_one_a(k)
 
1196
        self.assertEquals(set(k.get_ancestry(['text-1a'])), set(['text-1a', 'text-1']))
 
1197
 
 
1198
    def test_add_delta(self):
 
1199
        """Store in knit with parents"""
 
1200
        k = KnitVersionedFile('test', get_transport('.'), factory=KnitPlainFactory(),
 
1201
            delta=True, create=True)
 
1202
        self.add_stock_one_and_one_a(k)
 
1203
        k.clear_cache()
 
1204
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
 
1205
 
 
1206
    def test_add_delta_knit_graph_index(self):
 
1207
        """Does adding work with a KnitGraphIndex."""
 
1208
        index = InMemoryGraphIndex(2)
 
1209
        knit_index = KnitGraphIndex(index, add_callback=index.add_nodes,
 
1210
            deltas=True)
 
1211
        k = KnitVersionedFile('test', get_transport('.'),
 
1212
            delta=True, create=True, index=knit_index)
 
1213
        self.add_stock_one_and_one_a(k)
 
1214
        k.clear_cache()
 
1215
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
 
1216
        # check the index had the right data added.
 
1217
        self.assertEqual(set([
 
1218
            (index, ('text-1', ), ' 0 127', ((), ())),
 
1219
            (index, ('text-1a', ), ' 127 140', ((('text-1', ),), (('text-1', ),))),
 
1220
            ]), set(index.iter_all_entries()))
 
1221
        # we should not have a .kndx file
 
1222
        self.assertFalse(get_transport('.').has('test.kndx'))
 
1223
 
 
1224
    def test_annotate(self):
 
1225
        """Annotations"""
 
1226
        k = KnitVersionedFile('knit', get_transport('.'), factory=KnitAnnotateFactory(),
 
1227
            delta=True, create=True)
 
1228
        self.insert_and_test_small_annotate(k)
 
1229
 
 
1230
    def insert_and_test_small_annotate(self, k):
 
1231
        """test annotation with k works correctly."""
 
1232
        k.add_lines('text-1', [], ['a\n', 'b\n'])
 
1233
        k.add_lines('text-2', ['text-1'], ['a\n', 'c\n'])
 
1234
 
 
1235
        origins = k.annotate('text-2')
 
1236
        self.assertEquals(origins[0], ('text-1', 'a\n'))
 
1237
        self.assertEquals(origins[1], ('text-2', 'c\n'))
 
1238
 
 
1239
    def test_annotate_fulltext(self):
 
1240
        """Annotations"""
 
1241
        k = KnitVersionedFile('knit', get_transport('.'), factory=KnitAnnotateFactory(),
 
1242
            delta=False, create=True)
 
1243
        self.insert_and_test_small_annotate(k)
 
1244
 
 
1245
    def test_annotate_merge_1(self):
 
1246
        k = self.make_test_knit(True)
 
1247
        k.add_lines('text-a1', [], ['a\n', 'b\n'])
 
1248
        k.add_lines('text-a2', [], ['d\n', 'c\n'])
 
1249
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['d\n', 'b\n'])
 
1250
        origins = k.annotate('text-am')
 
1251
        self.assertEquals(origins[0], ('text-a2', 'd\n'))
 
1252
        self.assertEquals(origins[1], ('text-a1', 'b\n'))
 
1253
 
 
1254
    def test_annotate_merge_2(self):
 
1255
        k = self.make_test_knit(True)
 
1256
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
1257
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
1258
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['a\n', 'y\n', 'c\n'])
 
1259
        origins = k.annotate('text-am')
 
1260
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
1261
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
1262
        self.assertEquals(origins[2], ('text-a1', 'c\n'))
 
1263
 
 
1264
    def test_annotate_merge_9(self):
 
1265
        k = self.make_test_knit(True)
 
1266
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
1267
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
1268
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'c\n'])
 
1269
        origins = k.annotate('text-am')
 
1270
        self.assertEquals(origins[0], ('text-am', 'k\n'))
 
1271
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
1272
        self.assertEquals(origins[2], ('text-a1', 'c\n'))
 
1273
 
 
1274
    def test_annotate_merge_3(self):
 
1275
        k = self.make_test_knit(True)
 
1276
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
1277
        k.add_lines('text-a2', [] ,['x\n', 'y\n', 'z\n'])
 
1278
        k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'z\n'])
 
1279
        origins = k.annotate('text-am')
 
1280
        self.assertEquals(origins[0], ('text-am', 'k\n'))
 
1281
        self.assertEquals(origins[1], ('text-a2', 'y\n'))
 
1282
        self.assertEquals(origins[2], ('text-a2', 'z\n'))
 
1283
 
 
1284
    def test_annotate_merge_4(self):
 
1285
        k = self.make_test_knit(True)
 
1286
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
1287
        k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
 
1288
        k.add_lines('text-a3', ['text-a1'], ['a\n', 'b\n', 'p\n'])
 
1289
        k.add_lines('text-am', ['text-a2', 'text-a3'], ['a\n', 'b\n', 'z\n'])
 
1290
        origins = k.annotate('text-am')
 
1291
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
1292
        self.assertEquals(origins[1], ('text-a1', 'b\n'))
 
1293
        self.assertEquals(origins[2], ('text-a2', 'z\n'))
 
1294
 
 
1295
    def test_annotate_merge_5(self):
 
1296
        k = self.make_test_knit(True)
 
1297
        k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
 
1298
        k.add_lines('text-a2', [], ['d\n', 'e\n', 'f\n'])
 
1299
        k.add_lines('text-a3', [], ['x\n', 'y\n', 'z\n'])
 
1300
        k.add_lines('text-am',
 
1301
                    ['text-a1', 'text-a2', 'text-a3'],
 
1302
                    ['a\n', 'e\n', 'z\n'])
 
1303
        origins = k.annotate('text-am')
 
1304
        self.assertEquals(origins[0], ('text-a1', 'a\n'))
 
1305
        self.assertEquals(origins[1], ('text-a2', 'e\n'))
 
1306
        self.assertEquals(origins[2], ('text-a3', 'z\n'))
 
1307
 
 
1308
    def test_annotate_file_cherry_pick(self):
 
1309
        k = self.make_test_knit(True)
 
1310
        k.add_lines('text-1', [], ['a\n', 'b\n', 'c\n'])
 
1311
        k.add_lines('text-2', ['text-1'], ['d\n', 'e\n', 'f\n'])
 
1312
        k.add_lines('text-3', ['text-2', 'text-1'], ['a\n', 'b\n', 'c\n'])
 
1313
        origins = k.annotate('text-3')
 
1314
        self.assertEquals(origins[0], ('text-1', 'a\n'))
 
1315
        self.assertEquals(origins[1], ('text-1', 'b\n'))
 
1316
        self.assertEquals(origins[2], ('text-1', 'c\n'))
 
1317
 
 
1318
    def _test_join_with_factories(self, k1_factory, k2_factory):
 
1319
        k1 = KnitVersionedFile('test1', get_transport('.'), factory=k1_factory, create=True)
 
1320
        k1.add_lines('text-a', [], ['a1\n', 'a2\n', 'a3\n'])
 
1321
        k1.add_lines('text-b', ['text-a'], ['a1\n', 'b2\n', 'a3\n'])
 
1322
        k1.add_lines('text-c', [], ['c1\n', 'c2\n', 'c3\n'])
 
1323
        k1.add_lines('text-d', ['text-c'], ['c1\n', 'd2\n', 'd3\n'])
 
1324
        k1.add_lines('text-m', ['text-b', 'text-d'], ['a1\n', 'b2\n', 'd3\n'])
 
1325
        k2 = KnitVersionedFile('test2', get_transport('.'), factory=k2_factory, create=True)
 
1326
        count = k2.join(k1, version_ids=['text-m'])
 
1327
        self.assertEquals(count, 5)
 
1328
        self.assertTrue(k2.has_version('text-a'))
 
1329
        self.assertTrue(k2.has_version('text-c'))
 
1330
        origins = k2.annotate('text-m')
 
1331
        self.assertEquals(origins[0], ('text-a', 'a1\n'))
 
1332
        self.assertEquals(origins[1], ('text-b', 'b2\n'))
 
1333
        self.assertEquals(origins[2], ('text-d', 'd3\n'))
 
1334
 
 
1335
    def test_knit_join_plain_to_plain(self):
 
1336
        """Test joining a plain knit with a plain knit."""
 
1337
        self._test_join_with_factories(KnitPlainFactory(), KnitPlainFactory())
 
1338
 
 
1339
    def test_knit_join_anno_to_anno(self):
 
1340
        """Test joining an annotated knit with an annotated knit."""
 
1341
        self._test_join_with_factories(None, None)
 
1342
 
 
1343
    def test_knit_join_anno_to_plain(self):
 
1344
        """Test joining an annotated knit with a plain knit."""
 
1345
        self._test_join_with_factories(None, KnitPlainFactory())
 
1346
 
 
1347
    def test_knit_join_plain_to_anno(self):
 
1348
        """Test joining a plain knit with an annotated knit."""
 
1349
        self._test_join_with_factories(KnitPlainFactory(), None)
 
1350
 
 
1351
    def test_reannotate(self):
 
1352
        k1 = KnitVersionedFile('knit1', get_transport('.'),
 
1353
                               factory=KnitAnnotateFactory(), create=True)
 
1354
        # 0
 
1355
        k1.add_lines('text-a', [], ['a\n', 'b\n'])
 
1356
        # 1
 
1357
        k1.add_lines('text-b', ['text-a'], ['a\n', 'c\n'])
 
1358
 
 
1359
        k2 = KnitVersionedFile('test2', get_transport('.'),
 
1360
                               factory=KnitAnnotateFactory(), create=True)
 
1361
        k2.join(k1, version_ids=['text-b'])
 
1362
 
 
1363
        # 2
 
1364
        k1.add_lines('text-X', ['text-b'], ['a\n', 'b\n'])
 
1365
        # 2
 
1366
        k2.add_lines('text-c', ['text-b'], ['z\n', 'c\n'])
 
1367
        # 3
 
1368
        k2.add_lines('text-Y', ['text-b'], ['b\n', 'c\n'])
 
1369
 
 
1370
        # test-c will have index 3
 
1371
        k1.join(k2, version_ids=['text-c'])
 
1372
 
 
1373
        lines = k1.get_lines('text-c')
 
1374
        self.assertEquals(lines, ['z\n', 'c\n'])
 
1375
 
 
1376
        origins = k1.annotate('text-c')
 
1377
        self.assertEquals(origins[0], ('text-c', 'z\n'))
 
1378
        self.assertEquals(origins[1], ('text-b', 'c\n'))
 
1379
 
 
1380
    def test_get_line_delta_texts(self):
 
1381
        """Make sure we can call get_texts on text with reused line deltas"""
 
1382
        k1 = KnitVersionedFile('test1', get_transport('.'), 
 
1383
                               factory=KnitPlainFactory(), create=True)
 
1384
        for t in range(3):
 
1385
            if t == 0:
 
1386
                parents = []
 
1387
            else:
 
1388
                parents = ['%d' % (t-1)]
 
1389
            k1.add_lines('%d' % t, parents, ['hello\n'] * t)
 
1390
        k1.get_texts(('%d' % t) for t in range(3))
 
1391
        
 
1392
    def test_iter_lines_reads_in_order(self):
 
1393
        instrumented_t = get_transport('trace+memory:///')
 
1394
        k1 = KnitVersionedFile('id', instrumented_t, create=True, delta=True)
 
1395
        self.assertEqual([('get', 'id.kndx',)], instrumented_t._activity)
 
1396
        # add texts with no required ordering
 
1397
        k1.add_lines('base', [], ['text\n'])
 
1398
        k1.add_lines('base2', [], ['text2\n'])
 
1399
        k1.clear_cache()
 
1400
        # clear the logged activity, but preserve the list instance in case of
 
1401
        # clones pointing at it.
 
1402
        del instrumented_t._activity[:]
 
1403
        # request a last-first iteration
 
1404
        results = list(k1.iter_lines_added_or_present_in_versions(
 
1405
            ['base2', 'base']))
 
1406
        self.assertEqual(
 
1407
            [('readv', 'id.knit', [(0, 87), (87, 89)], False, None)],
 
1408
            instrumented_t._activity)
 
1409
        self.assertEqual(['text\n', 'text2\n'], results)
 
1410
 
 
1411
    def test_create_empty_annotated(self):
 
1412
        k1 = self.make_test_knit(True)
 
1413
        # 0
 
1414
        k1.add_lines('text-a', [], ['a\n', 'b\n'])
 
1415
        k2 = k1.create_empty('t', MemoryTransport())
 
1416
        self.assertTrue(isinstance(k2.factory, KnitAnnotateFactory))
 
1417
        self.assertEqual(k1.delta, k2.delta)
 
1418
        # the generic test checks for empty content and file class
 
1419
 
 
1420
    def test_knit_format(self):
 
1421
        # this tests that a new knit index file has the expected content
 
1422
        # and that is writes the data we expect as records are added.
 
1423
        knit = self.make_test_knit(True)
 
1424
        # Now knit files are not created until we first add data to them
 
1425
        self.assertFileEqual("# bzr knit index 8\n", 'test.kndx')
 
1426
        knit.add_lines_with_ghosts('revid', ['a_ghost'], ['a\n'])
 
1427
        self.assertFileEqual(
 
1428
            "# bzr knit index 8\n"
 
1429
            "\n"
 
1430
            "revid fulltext 0 84 .a_ghost :",
 
1431
            'test.kndx')
 
1432
        knit.add_lines_with_ghosts('revid2', ['revid'], ['a\n'])
 
1433
        self.assertFileEqual(
 
1434
            "# bzr knit index 8\n"
 
1435
            "\nrevid fulltext 0 84 .a_ghost :"
 
1436
            "\nrevid2 line-delta 84 82 0 :",
 
1437
            'test.kndx')
 
1438
        # we should be able to load this file again
 
1439
        knit = KnitVersionedFile('test', get_transport('.'), access_mode='r')
 
1440
        self.assertEqual(['revid', 'revid2'], knit.versions())
 
1441
        # write a short write to the file and ensure that its ignored
 
1442
        indexfile = file('test.kndx', 'ab')
 
1443
        indexfile.write('\nrevid3 line-delta 166 82 1 2 3 4 5 .phwoar:demo ')
 
1444
        indexfile.close()
 
1445
        # we should be able to load this file again
 
1446
        knit = KnitVersionedFile('test', get_transport('.'), access_mode='w')
 
1447
        self.assertEqual(['revid', 'revid2'], knit.versions())
 
1448
        # and add a revision with the same id the failed write had
 
1449
        knit.add_lines('revid3', ['revid2'], ['a\n'])
 
1450
        # and when reading it revid3 should now appear.
 
1451
        knit = KnitVersionedFile('test', get_transport('.'), access_mode='r')
 
1452
        self.assertEqual(['revid', 'revid2', 'revid3'], knit.versions())
 
1453
        self.assertEqual(['revid2'], knit.get_parents('revid3'))
 
1454
 
 
1455
    def test_delay_create(self):
 
1456
        """Test that passing delay_create=True creates files late"""
 
1457
        knit = self.make_test_knit(annotate=True, delay_create=True)
 
1458
        self.failIfExists('test.knit')
 
1459
        self.failIfExists('test.kndx')
 
1460
        knit.add_lines_with_ghosts('revid', ['a_ghost'], ['a\n'])
 
1461
        self.failUnlessExists('test.knit')
 
1462
        self.assertFileEqual(
 
1463
            "# bzr knit index 8\n"
 
1464
            "\n"
 
1465
            "revid fulltext 0 84 .a_ghost :",
 
1466
            'test.kndx')
 
1467
 
 
1468
    def test_create_parent_dir(self):
 
1469
        """create_parent_dir can create knits in nonexistant dirs"""
 
1470
        # Has no effect if we don't set 'delay_create'
 
1471
        trans = get_transport('.')
 
1472
        self.assertRaises(NoSuchFile, KnitVersionedFile, 'dir/test',
 
1473
                          trans, access_mode='w', factory=None,
 
1474
                          create=True, create_parent_dir=True)
 
1475
        # Nothing should have changed yet
 
1476
        knit = KnitVersionedFile('dir/test', trans, access_mode='w',
 
1477
                                 factory=None, create=True,
 
1478
                                 create_parent_dir=True,
 
1479
                                 delay_create=True)
 
1480
        self.failIfExists('dir/test.knit')
 
1481
        self.failIfExists('dir/test.kndx')
 
1482
        self.failIfExists('dir')
 
1483
        knit.add_lines('revid', [], ['a\n'])
 
1484
        self.failUnlessExists('dir')
 
1485
        self.failUnlessExists('dir/test.knit')
 
1486
        self.assertFileEqual(
 
1487
            "# bzr knit index 8\n"
 
1488
            "\n"
 
1489
            "revid fulltext 0 84  :",
 
1490
            'dir/test.kndx')
 
1491
 
 
1492
    def test_create_mode_700(self):
 
1493
        trans = get_transport('.')
 
1494
        if not trans._can_roundtrip_unix_modebits():
 
1495
            # Can't roundtrip, so no need to run this test
 
1496
            return
 
1497
        knit = KnitVersionedFile('dir/test', trans, access_mode='w',
 
1498
                                 factory=None, create=True,
 
1499
                                 create_parent_dir=True,
 
1500
                                 delay_create=True,
 
1501
                                 file_mode=0600,
 
1502
                                 dir_mode=0700)
 
1503
        knit.add_lines('revid', [], ['a\n'])
 
1504
        self.assertTransportMode(trans, 'dir', 0700)
 
1505
        self.assertTransportMode(trans, 'dir/test.knit', 0600)
 
1506
        self.assertTransportMode(trans, 'dir/test.kndx', 0600)
 
1507
 
 
1508
    def test_create_mode_770(self):
 
1509
        trans = get_transport('.')
 
1510
        if not trans._can_roundtrip_unix_modebits():
 
1511
            # Can't roundtrip, so no need to run this test
 
1512
            return
 
1513
        knit = KnitVersionedFile('dir/test', trans, access_mode='w',
 
1514
                                 factory=None, create=True,
 
1515
                                 create_parent_dir=True,
 
1516
                                 delay_create=True,
 
1517
                                 file_mode=0660,
 
1518
                                 dir_mode=0770)
 
1519
        knit.add_lines('revid', [], ['a\n'])
 
1520
        self.assertTransportMode(trans, 'dir', 0770)
 
1521
        self.assertTransportMode(trans, 'dir/test.knit', 0660)
 
1522
        self.assertTransportMode(trans, 'dir/test.kndx', 0660)
 
1523
 
 
1524
    def test_create_mode_777(self):
 
1525
        trans = get_transport('.')
 
1526
        if not trans._can_roundtrip_unix_modebits():
 
1527
            # Can't roundtrip, so no need to run this test
 
1528
            return
 
1529
        knit = KnitVersionedFile('dir/test', trans, access_mode='w',
 
1530
                                 factory=None, create=True,
 
1531
                                 create_parent_dir=True,
 
1532
                                 delay_create=True,
 
1533
                                 file_mode=0666,
 
1534
                                 dir_mode=0777)
 
1535
        knit.add_lines('revid', [], ['a\n'])
 
1536
        self.assertTransportMode(trans, 'dir', 0777)
 
1537
        self.assertTransportMode(trans, 'dir/test.knit', 0666)
 
1538
        self.assertTransportMode(trans, 'dir/test.kndx', 0666)
 
1539
 
 
1540
    def test_plan_merge(self):
 
1541
        my_knit = self.make_test_knit(annotate=True)
 
1542
        my_knit.add_lines('text1', [], split_lines(TEXT_1))
 
1543
        my_knit.add_lines('text1a', ['text1'], split_lines(TEXT_1A))
 
1544
        my_knit.add_lines('text1b', ['text1'], split_lines(TEXT_1B))
 
1545
        plan = list(my_knit.plan_merge('text1a', 'text1b'))
 
1546
        for plan_line, expected_line in zip(plan, AB_MERGE):
 
1547
            self.assertEqual(plan_line, expected_line)
 
1548
 
 
1549
    def test_get_stream_empty(self):
 
1550
        """Get a data stream for an empty knit file."""
 
1551
        k1 = self.make_test_knit()
 
1552
        format, data_list, reader_callable = k1.get_data_stream([])
 
1553
        self.assertEqual('knit-plain', format)
 
1554
        self.assertEqual([], data_list)
 
1555
        content = reader_callable(None)
 
1556
        self.assertEqual('', content)
 
1557
        self.assertIsInstance(content, str)
 
1558
 
 
1559
    def test_get_stream_one_version(self):
 
1560
        """Get a data stream for a single record out of a knit containing just
 
1561
        one record.
 
1562
        """
 
1563
        k1 = self.make_test_knit()
 
1564
        test_data = [
 
1565
            ('text-a', [], TEXT_1),
 
1566
            ]
 
1567
        expected_data_list = [
 
1568
            # version, options, length, parents
 
1569
            ('text-a', ['fulltext'], 122, []),
 
1570
           ]
 
1571
        for version_id, parents, lines in test_data:
 
1572
            k1.add_lines(version_id, parents, split_lines(lines))
 
1573
 
 
1574
        format, data_list, reader_callable = k1.get_data_stream(['text-a'])
 
1575
        self.assertEqual('knit-plain', format)
 
1576
        self.assertEqual(expected_data_list, data_list)
 
1577
        # There's only one record in the knit, so the content should be the
 
1578
        # entire knit data file's contents.
 
1579
        self.assertEqual(k1.transport.get_bytes(k1._data._access._filename),
 
1580
                         reader_callable(None))
 
1581
        
 
1582
    def test_get_stream_get_one_version_of_many(self):
 
1583
        """Get a data stream for just one version out of a knit containing many
 
1584
        versions.
 
1585
        """
 
1586
        k1 = self.make_test_knit()
 
1587
        # Insert the same data as test_knit_join, as they seem to cover a range
 
1588
        # of cases (no parents, one parent, multiple parents).
 
1589
        test_data = [
 
1590
            ('text-a', [], TEXT_1),
 
1591
            ('text-b', ['text-a'], TEXT_1),
 
1592
            ('text-c', [], TEXT_1),
 
1593
            ('text-d', ['text-c'], TEXT_1),
 
1594
            ('text-m', ['text-b', 'text-d'], TEXT_1),
 
1595
            ]
 
1596
        expected_data_list = [
 
1597
            # version, options, length, parents
 
1598
            ('text-m', ['line-delta'], 84, ['text-b', 'text-d']),
 
1599
            ]
 
1600
        for version_id, parents, lines in test_data:
 
1601
            k1.add_lines(version_id, parents, split_lines(lines))
 
1602
 
 
1603
        format, data_list, reader_callable = k1.get_data_stream(['text-m'])
 
1604
        self.assertEqual('knit-plain', format)
 
1605
        self.assertEqual(expected_data_list, data_list)
 
1606
        self.assertRecordContentEqual(k1, 'text-m', reader_callable(None))
 
1607
        
 
1608
    def test_get_stream_ghost_parent(self):
 
1609
        """Get a data stream for a version with a ghost parent."""
 
1610
        k1 = self.make_test_knit()
 
1611
        # Test data
 
1612
        k1.add_lines('text-a', [], split_lines(TEXT_1))
 
1613
        k1.add_lines_with_ghosts('text-b', ['text-a', 'text-ghost'],
 
1614
                                 split_lines(TEXT_1))
 
1615
        # Expected data
 
1616
        expected_data_list = [
 
1617
            # version, options, length, parents
 
1618
            ('text-b', ['line-delta'], 84, ['text-a', 'text-ghost']),
 
1619
            ]
 
1620
        
 
1621
        format, data_list, reader_callable = k1.get_data_stream(['text-b'])
 
1622
        self.assertEqual('knit-plain', format)
 
1623
        self.assertEqual(expected_data_list, data_list)
 
1624
        self.assertRecordContentEqual(k1, 'text-b', reader_callable(None))
 
1625
    
 
1626
    def test_get_stream_get_multiple_records(self):
 
1627
        """Get a stream for multiple records of a knit."""
 
1628
        k1 = self.make_test_knit()
 
1629
        # Insert the same data as test_knit_join, as they seem to cover a range
 
1630
        # of cases (no parents, one parent, multiple parents).
 
1631
        test_data = [
 
1632
            ('text-a', [], TEXT_1),
 
1633
            ('text-b', ['text-a'], TEXT_1),
 
1634
            ('text-c', [], TEXT_1),
 
1635
            ('text-d', ['text-c'], TEXT_1),
 
1636
            ('text-m', ['text-b', 'text-d'], TEXT_1),
 
1637
            ]
 
1638
        expected_data_list = [
 
1639
            # version, options, length, parents
 
1640
            ('text-b', ['line-delta'], 84, ['text-a']),
 
1641
            ('text-d', ['line-delta'], 84, ['text-c']),
 
1642
            ]
 
1643
        for version_id, parents, lines in test_data:
 
1644
            k1.add_lines(version_id, parents, split_lines(lines))
 
1645
 
 
1646
        # Note that even though we request the revision IDs in a particular
 
1647
        # order, the data stream may return them in any order it likes.  In this
 
1648
        # case, they'll be in the order they were inserted into the knit.
 
1649
        format, data_list, reader_callable = k1.get_data_stream(
 
1650
            ['text-d', 'text-b'])
 
1651
        self.assertEqual('knit-plain', format)
 
1652
        self.assertEqual(expected_data_list, data_list)
 
1653
        self.assertRecordContentEqual(k1, 'text-b', reader_callable(84))
 
1654
        self.assertRecordContentEqual(k1, 'text-d', reader_callable(84))
 
1655
        self.assertEqual('', reader_callable(None),
 
1656
                         "There should be no more bytes left to read.")
 
1657
 
 
1658
    def test_get_stream_all(self):
 
1659
        """Get a data stream for all the records in a knit.
 
1660
 
 
1661
        This exercises fulltext records, line-delta records, records with
 
1662
        various numbers of parents, and reading multiple records out of the
 
1663
        callable.  These cases ought to all be exercised individually by the
 
1664
        other test_get_stream_* tests; this test is basically just paranoia.
 
1665
        """
 
1666
        k1 = self.make_test_knit()
 
1667
        # Insert the same data as test_knit_join, as they seem to cover a range
 
1668
        # of cases (no parents, one parent, multiple parents).
 
1669
        test_data = [
 
1670
            ('text-a', [], TEXT_1),
 
1671
            ('text-b', ['text-a'], TEXT_1),
 
1672
            ('text-c', [], TEXT_1),
 
1673
            ('text-d', ['text-c'], TEXT_1),
 
1674
            ('text-m', ['text-b', 'text-d'], TEXT_1),
 
1675
           ]
 
1676
        expected_data_list = [
 
1677
            # version, options, length, parents
 
1678
            ('text-a', ['fulltext'], 122, []),
 
1679
            ('text-b', ['line-delta'], 84, ['text-a']),
 
1680
            ('text-c', ['fulltext'], 121, []),
 
1681
            ('text-d', ['line-delta'], 84, ['text-c']),
 
1682
            ('text-m', ['line-delta'], 84, ['text-b', 'text-d']),
 
1683
            ]
 
1684
        for version_id, parents, lines in test_data:
 
1685
            k1.add_lines(version_id, parents, split_lines(lines))
 
1686
 
 
1687
        format, data_list, reader_callable = k1.get_data_stream(
 
1688
            ['text-a', 'text-b', 'text-c', 'text-d', 'text-m'])
 
1689
        self.assertEqual('knit-plain', format)
 
1690
        self.assertEqual(expected_data_list, data_list)
 
1691
        for version_id, options, length, parents in expected_data_list:
 
1692
            bytes = reader_callable(length)
 
1693
            self.assertRecordContentEqual(k1, version_id, bytes)
 
1694
 
 
1695
    def assertKnitFilesEqual(self, knit1, knit2):
 
1696
        """Assert that the contents of the index and data files of two knits are
 
1697
        equal.
 
1698
        """
 
1699
        self.assertEqual(
 
1700
            knit1.transport.get_bytes(knit1._data._access._filename),
 
1701
            knit2.transport.get_bytes(knit2._data._access._filename))
 
1702
        self.assertEqual(
 
1703
            knit1.transport.get_bytes(knit1._index._filename),
 
1704
            knit2.transport.get_bytes(knit2._index._filename))
 
1705
 
 
1706
    def test_insert_data_stream_empty(self):
 
1707
        """Inserting a data stream with no records should not put any data into
 
1708
        the knit.
 
1709
        """
 
1710
        k1 = self.make_test_knit()
 
1711
        k1.insert_data_stream(
 
1712
            (k1.get_format_signature(), [], lambda ignored: ''))
 
1713
        self.assertEqual('', k1.transport.get_bytes(k1._data._access._filename),
 
1714
                         "The .knit should be completely empty.")
 
1715
        self.assertEqual(k1._index.HEADER,
 
1716
                         k1.transport.get_bytes(k1._index._filename),
 
1717
                         "The .kndx should have nothing apart from the header.")
 
1718
 
 
1719
    def test_insert_data_stream_one_record(self):
 
1720
        """Inserting a data stream with one record from a knit with one record
 
1721
        results in byte-identical files.
 
1722
        """
 
1723
        source = self.make_test_knit(name='source')
 
1724
        source.add_lines('text-a', [], split_lines(TEXT_1))
 
1725
        data_stream = source.get_data_stream(['text-a'])
 
1726
        
 
1727
        target = self.make_test_knit(name='target')
 
1728
        target.insert_data_stream(data_stream)
 
1729
        
 
1730
        self.assertKnitFilesEqual(source, target)
 
1731
 
 
1732
    def test_insert_data_stream_records_already_present(self):
 
1733
        """Insert a data stream where some records are alreday present in the
 
1734
        target, and some not.  Only the new records are inserted.
 
1735
        """
 
1736
        source = self.make_test_knit(name='source')
 
1737
        target = self.make_test_knit(name='target')
 
1738
        # Insert 'text-a' into both source and target
 
1739
        source.add_lines('text-a', [], split_lines(TEXT_1))
 
1740
        target.insert_data_stream(source.get_data_stream(['text-a']))
 
1741
        # Insert 'text-b' into just the source.
 
1742
        source.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
 
1743
        # Get a data stream of both text-a and text-b, and insert it.
 
1744
        data_stream = source.get_data_stream(['text-a', 'text-b'])
 
1745
        target.insert_data_stream(data_stream)
 
1746
        # The source and target will now be identical.  This means the text-a
 
1747
        # record was not added a second time.
 
1748
        self.assertKnitFilesEqual(source, target)
 
1749
 
 
1750
    def test_insert_data_stream_multiple_records(self):
 
1751
        """Inserting a data stream of all records from a knit with multiple
 
1752
        records results in byte-identical files.
 
1753
        """
 
1754
        source = self.make_test_knit(name='source')
 
1755
        source.add_lines('text-a', [], split_lines(TEXT_1))
 
1756
        source.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
 
1757
        source.add_lines('text-c', [], split_lines(TEXT_1))
 
1758
        data_stream = source.get_data_stream(['text-a', 'text-b', 'text-c'])
 
1759
        
 
1760
        target = self.make_test_knit(name='target')
 
1761
        target.insert_data_stream(data_stream)
 
1762
        
 
1763
        self.assertKnitFilesEqual(source, target)
 
1764
 
 
1765
    def test_insert_data_stream_ghost_parent(self):
 
1766
        """Insert a data stream with a record that has a ghost parent."""
 
1767
        # Make a knit with a record, text-a, that has a ghost parent.
 
1768
        source = self.make_test_knit(name='source')
 
1769
        source.add_lines_with_ghosts('text-a', ['text-ghost'],
 
1770
                                     split_lines(TEXT_1))
 
1771
        data_stream = source.get_data_stream(['text-a'])
 
1772
 
 
1773
        target = self.make_test_knit(name='target')
 
1774
        target.insert_data_stream(data_stream)
 
1775
 
 
1776
        self.assertKnitFilesEqual(source, target)
 
1777
 
 
1778
        # The target knit object is in a consistent state, i.e. the record we
 
1779
        # just added is immediately visible.
 
1780
        self.assertTrue(target.has_version('text-a'))
 
1781
        self.assertTrue(target.has_ghost('text-ghost'))
 
1782
        self.assertEqual(split_lines(TEXT_1), target.get_lines('text-a'))
 
1783
 
 
1784
    def test_insert_data_stream_inconsistent_version_lines(self):
 
1785
        """Inserting a data stream which has different content for a version_id
 
1786
        than already exists in the knit will raise KnitCorrupt.
 
1787
        """
 
1788
        source = self.make_test_knit(name='source')
 
1789
        target = self.make_test_knit(name='target')
 
1790
        # Insert a different 'text-a' into both source and target
 
1791
        source.add_lines('text-a', [], split_lines(TEXT_1))
 
1792
        target.add_lines('text-a', [], split_lines(TEXT_2))
 
1793
        # Insert a data stream with conflicting content into the target
 
1794
        data_stream = source.get_data_stream(['text-a'])
 
1795
        self.assertRaises(
 
1796
            errors.KnitCorrupt, target.insert_data_stream, data_stream)
 
1797
 
 
1798
    def test_insert_data_stream_inconsistent_version_parents(self):
 
1799
        """Inserting a data stream which has different parents for a version_id
 
1800
        than already exists in the knit will raise KnitCorrupt.
 
1801
        """
 
1802
        source = self.make_test_knit(name='source')
 
1803
        target = self.make_test_knit(name='target')
 
1804
        # Insert a different 'text-a' into both source and target.  They differ
 
1805
        # only by the parents list, the content is the same.
 
1806
        source.add_lines_with_ghosts('text-a', [], split_lines(TEXT_1))
 
1807
        target.add_lines_with_ghosts('text-a', ['a-ghost'], split_lines(TEXT_1))
 
1808
        # Insert a data stream with conflicting content into the target
 
1809
        data_stream = source.get_data_stream(['text-a'])
 
1810
        self.assertRaises(
 
1811
            errors.KnitCorrupt, target.insert_data_stream, data_stream)
 
1812
 
 
1813
    def test_insert_data_stream_incompatible_format(self):
 
1814
        """A data stream in a different format to the target knit cannot be
 
1815
        inserted.
 
1816
 
 
1817
        It will raise KnitDataStreamIncompatible.
 
1818
        """
 
1819
        data_stream = ('fake-format-signature', [], lambda _: '')
 
1820
        target = self.make_test_knit(name='target')
 
1821
        self.assertRaises(
 
1822
            errors.KnitDataStreamIncompatible,
 
1823
            target.insert_data_stream, data_stream)
 
1824
 
 
1825
    #  * test that a stream of "already present version, then new version"
 
1826
    #    inserts correctly.
 
1827
 
 
1828
TEXT_1 = """\
 
1829
Banana cup cakes:
 
1830
 
 
1831
- bananas
 
1832
- eggs
 
1833
- broken tea cups
 
1834
"""
 
1835
 
 
1836
TEXT_1A = """\
 
1837
Banana cup cake recipe
 
1838
(serves 6)
 
1839
 
 
1840
- bananas
 
1841
- eggs
 
1842
- broken tea cups
 
1843
- self-raising flour
 
1844
"""
 
1845
 
 
1846
TEXT_1B = """\
 
1847
Banana cup cake recipe
 
1848
 
 
1849
- bananas (do not use plantains!!!)
 
1850
- broken tea cups
 
1851
- flour
 
1852
"""
 
1853
 
 
1854
delta_1_1a = """\
 
1855
0,1,2
 
1856
Banana cup cake recipe
 
1857
(serves 6)
 
1858
5,5,1
 
1859
- self-raising flour
 
1860
"""
 
1861
 
 
1862
TEXT_2 = """\
 
1863
Boeuf bourguignon
 
1864
 
 
1865
- beef
 
1866
- red wine
 
1867
- small onions
 
1868
- carrot
 
1869
- mushrooms
 
1870
"""
 
1871
 
 
1872
AB_MERGE_TEXT="""unchanged|Banana cup cake recipe
 
1873
new-a|(serves 6)
 
1874
unchanged|
 
1875
killed-b|- bananas
 
1876
killed-b|- eggs
 
1877
new-b|- bananas (do not use plantains!!!)
 
1878
unchanged|- broken tea cups
 
1879
new-a|- self-raising flour
 
1880
new-b|- flour
 
1881
"""
 
1882
AB_MERGE=[tuple(l.split('|')) for l in AB_MERGE_TEXT.splitlines(True)]
 
1883
 
 
1884
 
 
1885
def line_delta(from_lines, to_lines):
 
1886
    """Generate line-based delta from one text to another"""
 
1887
    s = difflib.SequenceMatcher(None, from_lines, to_lines)
 
1888
    for op in s.get_opcodes():
 
1889
        if op[0] == 'equal':
 
1890
            continue
 
1891
        yield '%d,%d,%d\n' % (op[1], op[2], op[4]-op[3])
 
1892
        for i in range(op[3], op[4]):
 
1893
            yield to_lines[i]
 
1894
 
 
1895
 
 
1896
def apply_line_delta(basis_lines, delta_lines):
 
1897
    """Apply a line-based perfect diff
 
1898
    
 
1899
    basis_lines -- text to apply the patch to
 
1900
    delta_lines -- diff instructions and content
 
1901
    """
 
1902
    out = basis_lines[:]
 
1903
    i = 0
 
1904
    offset = 0
 
1905
    while i < len(delta_lines):
 
1906
        l = delta_lines[i]
 
1907
        a, b, c = map(long, l.split(','))
 
1908
        i = i + 1
 
1909
        out[offset+a:offset+b] = delta_lines[i:i+c]
 
1910
        i = i + c
 
1911
        offset = offset + (b - a) + c
 
1912
    return out
 
1913
 
 
1914
 
 
1915
class TestWeaveToKnit(KnitTests):
 
1916
 
 
1917
    def test_weave_to_knit_matches(self):
 
1918
        # check that the WeaveToKnit is_compatible function
 
1919
        # registers True for a Weave to a Knit.
 
1920
        w = Weave()
 
1921
        k = self.make_test_knit()
 
1922
        self.failUnless(WeaveToKnit.is_compatible(w, k))
 
1923
        self.failIf(WeaveToKnit.is_compatible(k, w))
 
1924
        self.failIf(WeaveToKnit.is_compatible(w, w))
 
1925
        self.failIf(WeaveToKnit.is_compatible(k, k))
 
1926
 
 
1927
 
 
1928
class TestKnitCaching(KnitTests):
 
1929
    
 
1930
    def create_knit(self):
 
1931
        k = self.make_test_knit(True)
 
1932
        k.add_lines('text-1', [], split_lines(TEXT_1))
 
1933
        k.add_lines('text-2', [], split_lines(TEXT_2))
 
1934
        return k
 
1935
 
 
1936
    def test_no_caching(self):
 
1937
        k = self.create_knit()
 
1938
        # Nothing should be cached without setting 'enable_cache'
 
1939
        self.assertEqual({}, k._data._cache)
 
1940
 
 
1941
    def test_cache_data_read_raw(self):
 
1942
        k = self.create_knit()
 
1943
 
 
1944
        # Now cache and read
 
1945
        k.enable_cache()
 
1946
 
 
1947
        def read_one_raw(version):
 
1948
            pos_map = k._get_components_positions([version])
 
1949
            method, index_memo, next = pos_map[version]
 
1950
            lst = list(k._data.read_records_iter_raw([(version, index_memo)]))
 
1951
            self.assertEqual(1, len(lst))
 
1952
            return lst[0]
 
1953
 
 
1954
        val = read_one_raw('text-1')
 
1955
        self.assertEqual({'text-1':val[1]}, k._data._cache)
 
1956
 
 
1957
        k.clear_cache()
 
1958
        # After clear, new reads are not cached
 
1959
        self.assertEqual({}, k._data._cache)
 
1960
 
 
1961
        val2 = read_one_raw('text-1')
 
1962
        self.assertEqual(val, val2)
 
1963
        self.assertEqual({}, k._data._cache)
 
1964
 
 
1965
    def test_cache_data_read(self):
 
1966
        k = self.create_knit()
 
1967
 
 
1968
        def read_one(version):
 
1969
            pos_map = k._get_components_positions([version])
 
1970
            method, index_memo, next = pos_map[version]
 
1971
            lst = list(k._data.read_records_iter([(version, index_memo)]))
 
1972
            self.assertEqual(1, len(lst))
 
1973
            return lst[0]
 
1974
 
 
1975
        # Now cache and read
 
1976
        k.enable_cache()
 
1977
 
 
1978
        val = read_one('text-2')
 
1979
        self.assertEqual(['text-2'], k._data._cache.keys())
 
1980
        self.assertEqual('text-2', val[0])
 
1981
        content, digest = k._data._parse_record('text-2',
 
1982
                                                k._data._cache['text-2'])
 
1983
        self.assertEqual(content, val[1])
 
1984
        self.assertEqual(digest, val[2])
 
1985
 
 
1986
        k.clear_cache()
 
1987
        self.assertEqual({}, k._data._cache)
 
1988
 
 
1989
        val2 = read_one('text-2')
 
1990
        self.assertEqual(val, val2)
 
1991
        self.assertEqual({}, k._data._cache)
 
1992
 
 
1993
    def test_cache_read(self):
 
1994
        k = self.create_knit()
 
1995
        k.enable_cache()
 
1996
 
 
1997
        text = k.get_text('text-1')
 
1998
        self.assertEqual(TEXT_1, text)
 
1999
        self.assertEqual(['text-1'], k._data._cache.keys())
 
2000
 
 
2001
        k.clear_cache()
 
2002
        self.assertEqual({}, k._data._cache)
 
2003
 
 
2004
        text = k.get_text('text-1')
 
2005
        self.assertEqual(TEXT_1, text)
 
2006
        self.assertEqual({}, k._data._cache)
923
2007
 
924
2008
 
925
2009
class TestKnitIndex(KnitTests):
928
2012
        """Adding versions to the index should update the lookup dict"""
929
2013
        knit = self.make_test_knit()
930
2014
        idx = knit._index
931
 
        idx.add_records([(('a-1',), ['fulltext'], (('a-1',), 0, 0), [])])
 
2015
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
932
2016
        self.check_file_contents('test.kndx',
933
2017
            '# bzr knit index 8\n'
934
2018
            '\n'
935
2019
            'a-1 fulltext 0 0  :'
936
2020
            )
937
 
        idx.add_records([
938
 
            (('a-2',), ['fulltext'], (('a-2',), 0, 0), [('a-1',)]),
939
 
            (('a-3',), ['fulltext'], (('a-3',), 0, 0), [('a-2',)]),
940
 
            ])
 
2021
        idx.add_versions([('a-2', ['fulltext'], (None, 0, 0), ['a-1']),
 
2022
                          ('a-3', ['fulltext'], (None, 0, 0), ['a-2']),
 
2023
                         ])
941
2024
        self.check_file_contents('test.kndx',
942
2025
            '# bzr knit index 8\n'
943
2026
            '\n'
945
2028
            'a-2 fulltext 0 0 0 :\n'
946
2029
            'a-3 fulltext 0 0 1 :'
947
2030
            )
948
 
        self.assertEqual(set([('a-3',), ('a-1',), ('a-2',)]), idx.keys())
949
 
        self.assertEqual({
950
 
            ('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False)),
951
 
            ('a-2',): ((('a-2',), 0, 0), None, (('a-1',),), ('fulltext', False)),
952
 
            ('a-3',): ((('a-3',), 0, 0), None, (('a-2',),), ('fulltext', False)),
953
 
            }, idx.get_build_details(idx.keys()))
954
 
        self.assertEqual({('a-1',):(),
955
 
            ('a-2',):(('a-1',),),
956
 
            ('a-3',):(('a-2',),),},
957
 
            idx.get_parent_map(idx.keys()))
 
2031
        self.assertEqual(['a-1', 'a-2', 'a-3'], idx._history)
 
2032
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, [], 0),
 
2033
                          'a-2':('a-2', ['fulltext'], 0, 0, ['a-1'], 1),
 
2034
                          'a-3':('a-3', ['fulltext'], 0, 0, ['a-2'], 2),
 
2035
                         }, idx._cache)
958
2036
 
959
2037
    def test_add_versions_fails_clean(self):
960
2038
        """If add_versions fails in the middle, it restores a pristine state.
970
2048
 
971
2049
        knit = self.make_test_knit()
972
2050
        idx = knit._index
973
 
        idx.add_records([(('a-1',), ['fulltext'], (('a-1',), 0, 0), [])])
 
2051
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
974
2052
 
975
2053
        class StopEarly(Exception):
976
2054
            pass
977
2055
 
978
2056
        def generate_failure():
979
2057
            """Add some entries and then raise an exception"""
980
 
            yield (('a-2',), ['fulltext'], (None, 0, 0), ('a-1',))
981
 
            yield (('a-3',), ['fulltext'], (None, 0, 0), ('a-2',))
 
2058
            yield ('a-2', ['fulltext'], (None, 0, 0), ['a-1'])
 
2059
            yield ('a-3', ['fulltext'], (None, 0, 0), ['a-2'])
982
2060
            raise StopEarly()
983
2061
 
984
2062
        # Assert the pre-condition
985
 
        def assertA1Only():
986
 
            self.assertEqual(set([('a-1',)]), set(idx.keys()))
987
 
            self.assertEqual(
988
 
                {('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False))},
989
 
                idx.get_build_details([('a-1',)]))
990
 
            self.assertEqual({('a-1',):()}, idx.get_parent_map(idx.keys()))
991
 
 
992
 
        assertA1Only()
993
 
        self.assertRaises(StopEarly, idx.add_records, generate_failure())
 
2063
        self.assertEqual(['a-1'], idx._history)
 
2064
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, [], 0)}, idx._cache)
 
2065
 
 
2066
        self.assertRaises(StopEarly, idx.add_versions, generate_failure())
 
2067
 
994
2068
        # And it shouldn't be modified
995
 
        assertA1Only()
 
2069
        self.assertEqual(['a-1'], idx._history)
 
2070
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, [], 0)}, idx._cache)
996
2071
 
997
2072
    def test_knit_index_ignores_empty_files(self):
998
2073
        # There was a race condition in older bzr, where a ^C at the right time
1007
2082
    def test_knit_index_checks_header(self):
1008
2083
        t = get_transport('.')
1009
2084
        t.put_bytes('test.kndx', '# not really a knit header\n\n')
1010
 
        k = self.make_test_knit()
1011
 
        self.assertRaises(KnitHeaderError, k.keys)
 
2085
 
 
2086
        self.assertRaises(KnitHeaderError, self.make_test_knit)
1012
2087
 
1013
2088
 
1014
2089
class TestGraphIndexKnit(KnitTests):
1053
2128
            add_callback = self.catch_add
1054
2129
        else:
1055
2130
            add_callback = None
1056
 
        return _KnitGraphIndex(combined_index, lambda:True, deltas=deltas,
 
2131
        return KnitGraphIndex(combined_index, deltas=deltas,
1057
2132
            add_callback=add_callback)
1058
2133
 
1059
 
    def test_keys(self):
1060
 
        index = self.two_graph_index()
1061
 
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1062
 
            set(index.keys()))
 
2134
    def test_get_graph(self):
 
2135
        index = self.two_graph_index()
 
2136
        self.assertEqual(set([
 
2137
            ('tip', ('parent', )),
 
2138
            ('tail', ()),
 
2139
            ('parent', ('tail', 'ghost')),
 
2140
            ('separate', ()),
 
2141
            ]), set(index.get_graph()))
 
2142
 
 
2143
    def test_get_ancestry(self):
 
2144
        # get_ancestry is defined as eliding ghosts, not erroring.
 
2145
        index = self.two_graph_index()
 
2146
        self.assertEqual([], index.get_ancestry([]))
 
2147
        self.assertEqual(['separate'], index.get_ancestry(['separate']))
 
2148
        self.assertEqual(['tail'], index.get_ancestry(['tail']))
 
2149
        self.assertEqual(['tail', 'parent'], index.get_ancestry(['parent']))
 
2150
        self.assertEqual(['tail', 'parent', 'tip'], index.get_ancestry(['tip']))
 
2151
        self.assertTrue(index.get_ancestry(['tip', 'separate']) in
 
2152
            (['tail', 'parent', 'tip', 'separate'],
 
2153
             ['separate', 'tail', 'parent', 'tip'],
 
2154
            ))
 
2155
        # and without topo_sort
 
2156
        self.assertEqual(set(['separate']),
 
2157
            set(index.get_ancestry(['separate'], topo_sorted=False)))
 
2158
        self.assertEqual(set(['tail']),
 
2159
            set(index.get_ancestry(['tail'], topo_sorted=False)))
 
2160
        self.assertEqual(set(['tail', 'parent']),
 
2161
            set(index.get_ancestry(['parent'], topo_sorted=False)))
 
2162
        self.assertEqual(set(['tail', 'parent', 'tip']),
 
2163
            set(index.get_ancestry(['tip'], topo_sorted=False)))
 
2164
        self.assertEqual(set(['separate', 'tail', 'parent', 'tip']),
 
2165
            set(index.get_ancestry(['tip', 'separate'])))
 
2166
        # asking for a ghost makes it go boom.
 
2167
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry, ['ghost'])
 
2168
 
 
2169
    def test_get_ancestry_with_ghosts(self):
 
2170
        index = self.two_graph_index()
 
2171
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
 
2172
        self.assertEqual(['separate'], index.get_ancestry_with_ghosts(['separate']))
 
2173
        self.assertEqual(['tail'], index.get_ancestry_with_ghosts(['tail']))
 
2174
        self.assertTrue(index.get_ancestry_with_ghosts(['parent']) in
 
2175
            (['tail', 'ghost', 'parent'],
 
2176
             ['ghost', 'tail', 'parent'],
 
2177
            ))
 
2178
        self.assertTrue(index.get_ancestry_with_ghosts(['tip']) in
 
2179
            (['tail', 'ghost', 'parent', 'tip'],
 
2180
             ['ghost', 'tail', 'parent', 'tip'],
 
2181
            ))
 
2182
        self.assertTrue(index.get_ancestry_with_ghosts(['tip', 'separate']) in
 
2183
            (['tail', 'ghost', 'parent', 'tip', 'separate'],
 
2184
             ['ghost', 'tail', 'parent', 'tip', 'separate'],
 
2185
             ['separate', 'tail', 'ghost', 'parent', 'tip'],
 
2186
             ['separate', 'ghost', 'tail', 'parent', 'tip'],
 
2187
            ))
 
2188
        # asking for a ghost makes it go boom.
 
2189
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry_with_ghosts, ['ghost'])
 
2190
 
 
2191
    def test_num_versions(self):
 
2192
        index = self.two_graph_index()
 
2193
        self.assertEqual(4, index.num_versions())
 
2194
 
 
2195
    def test_get_versions(self):
 
2196
        index = self.two_graph_index()
 
2197
        self.assertEqual(set(['tail', 'tip', 'parent', 'separate']),
 
2198
            set(index.get_versions()))
 
2199
 
 
2200
    def test_has_version(self):
 
2201
        index = self.two_graph_index()
 
2202
        self.assertTrue(index.has_version('tail'))
 
2203
        self.assertFalse(index.has_version('ghost'))
1063
2204
 
1064
2205
    def test_get_position(self):
1065
2206
        index = self.two_graph_index()
1066
 
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position(('tip',)))
1067
 
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position(('parent',)))
 
2207
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position('tip'))
 
2208
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position('parent'))
1068
2209
 
1069
2210
    def test_get_method_deltas(self):
1070
2211
        index = self.two_graph_index(deltas=True)
1071
 
        self.assertEqual('fulltext', index.get_method(('tip',)))
1072
 
        self.assertEqual('line-delta', index.get_method(('parent',)))
 
2212
        self.assertEqual('fulltext', index.get_method('tip'))
 
2213
        self.assertEqual('line-delta', index.get_method('parent'))
1073
2214
 
1074
2215
    def test_get_method_no_deltas(self):
1075
2216
        # check that the parent-history lookup is ignored with deltas=False.
1076
2217
        index = self.two_graph_index(deltas=False)
1077
 
        self.assertEqual('fulltext', index.get_method(('tip',)))
1078
 
        self.assertEqual('fulltext', index.get_method(('parent',)))
 
2218
        self.assertEqual('fulltext', index.get_method('tip'))
 
2219
        self.assertEqual('fulltext', index.get_method('parent'))
1079
2220
 
1080
2221
    def test_get_options_deltas(self):
1081
2222
        index = self.two_graph_index(deltas=True)
1082
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1083
 
        self.assertEqual(['line-delta'], index.get_options(('parent',)))
 
2223
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
 
2224
        self.assertEqual(['line-delta'], index.get_options('parent'))
1084
2225
 
1085
2226
    def test_get_options_no_deltas(self):
1086
2227
        # check that the parent-history lookup is ignored with deltas=False.
1087
2228
        index = self.two_graph_index(deltas=False)
1088
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1089
 
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
1090
 
 
1091
 
    def test_get_parent_map(self):
1092
 
        index = self.two_graph_index()
1093
 
        self.assertEqual({('parent',):(('tail',), ('ghost',))},
1094
 
            index.get_parent_map([('parent',), ('ghost',)]))
 
2229
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
 
2230
        self.assertEqual(['fulltext'], index.get_options('parent'))
 
2231
 
 
2232
    def test_get_parents(self):
 
2233
        # get_parents ignores ghosts
 
2234
        index = self.two_graph_index()
 
2235
        self.assertEqual(('tail', ), index.get_parents('parent'))
 
2236
        # and errors on ghosts.
 
2237
        self.assertRaises(errors.RevisionNotPresent,
 
2238
            index.get_parents, 'ghost')
 
2239
 
 
2240
    def test_get_parents_with_ghosts(self):
 
2241
        index = self.two_graph_index()
 
2242
        self.assertEqual(('tail', 'ghost'), index.get_parents_with_ghosts('parent'))
 
2243
        # and errors on ghosts.
 
2244
        self.assertRaises(errors.RevisionNotPresent,
 
2245
            index.get_parents_with_ghosts, 'ghost')
 
2246
 
 
2247
    def test_check_versions_present(self):
 
2248
        # ghosts should not be considered present
 
2249
        index = self.two_graph_index()
 
2250
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
2251
            ['ghost'])
 
2252
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
2253
            ['tail', 'ghost'])
 
2254
        index.check_versions_present(['tail', 'separate'])
1095
2255
 
1096
2256
    def catch_add(self, entries):
1097
2257
        self.caught_entries.append(entries)
1098
2258
 
1099
2259
    def test_add_no_callback_errors(self):
1100
2260
        index = self.two_graph_index()
1101
 
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1102
 
            [(('new',), 'fulltext,no-eol', (None, 50, 60), ['separate'])])
 
2261
        self.assertRaises(errors.ReadOnlyError, index.add_version,
 
2262
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
1103
2263
 
1104
2264
    def test_add_version_smoke(self):
1105
2265
        index = self.two_graph_index(catch_adds=True)
1106
 
        index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60),
1107
 
            [('separate',)])])
 
2266
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
1108
2267
        self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
1109
2268
            self.caught_entries)
1110
2269
 
1111
2270
    def test_add_version_delta_not_delta_index(self):
1112
2271
        index = self.two_graph_index(catch_adds=True)
1113
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1114
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
 
2272
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2273
            'new', 'no-eol,line-delta', (None, 0, 100), ['parent'])
1115
2274
        self.assertEqual([], self.caught_entries)
1116
2275
 
1117
2276
    def test_add_version_same_dup(self):
1118
2277
        index = self.two_graph_index(catch_adds=True)
1119
2278
        # options can be spelt two different ways
1120
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1121
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
1122
 
        # position/length are ignored (because each pack could have fulltext or
1123
 
        # delta, and be at a different position.
1124
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1125
 
            [('parent',)])])
1126
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1127
 
            [('parent',)])])
1128
 
        # but neither should have added data:
1129
 
        self.assertEqual([[], [], [], []], self.caught_entries)
 
2279
        index.add_version('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])
 
2280
        index.add_version('tip', 'no-eol,fulltext', (None, 0, 100), ['parent'])
 
2281
        # but neither should have added data.
 
2282
        self.assertEqual([[], []], self.caught_entries)
1130
2283
        
1131
2284
    def test_add_version_different_dup(self):
1132
2285
        index = self.two_graph_index(deltas=True, catch_adds=True)
1133
2286
        # change options
1134
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1135
 
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1136
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1137
 
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [('parent',)])])
1138
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1139
 
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
 
2287
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2288
            'tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])
 
2289
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2290
            'tip', 'line-delta,no-eol', (None, 0, 100), ['parent'])
 
2291
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2292
            'tip', 'fulltext', (None, 0, 100), ['parent'])
 
2293
        # position/length
 
2294
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2295
            'tip', 'fulltext,no-eol', (None, 50, 100), ['parent'])
 
2296
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2297
            'tip', 'fulltext,no-eol', (None, 0, 1000), ['parent'])
1140
2298
        # parents
1141
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1142
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
 
2299
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2300
            'tip', 'fulltext,no-eol', (None, 0, 100), [])
1143
2301
        self.assertEqual([], self.caught_entries)
1144
2302
        
1145
2303
    def test_add_versions_nodeltas(self):
1146
2304
        index = self.two_graph_index(catch_adds=True)
1147
 
        index.add_records([
1148
 
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1149
 
                (('new2',), 'fulltext', (None, 0, 6), [('new',)]),
 
2305
        index.add_versions([
 
2306
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
 
2307
                ('new2', 'fulltext', (None, 0, 6), ['new']),
1150
2308
                ])
1151
2309
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
1152
2310
            (('new2', ), ' 0 6', ((('new',),),))],
1155
2313
 
1156
2314
    def test_add_versions_deltas(self):
1157
2315
        index = self.two_graph_index(deltas=True, catch_adds=True)
1158
 
        index.add_records([
1159
 
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1160
 
                (('new2',), 'line-delta', (None, 0, 6), [('new',)]),
 
2316
        index.add_versions([
 
2317
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
 
2318
                ('new2', 'line-delta', (None, 0, 6), ['new']),
1161
2319
                ])
1162
2320
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
1163
2321
            (('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
1166
2324
 
1167
2325
    def test_add_versions_delta_not_delta_index(self):
1168
2326
        index = self.two_graph_index(catch_adds=True)
1169
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1170
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
 
2327
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2328
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
1171
2329
        self.assertEqual([], self.caught_entries)
1172
2330
 
1173
2331
    def test_add_versions_random_id_accepted(self):
1174
2332
        index = self.two_graph_index(catch_adds=True)
1175
 
        index.add_records([], random_id=True)
 
2333
        index.add_versions([], random_id=True)
1176
2334
 
1177
2335
    def test_add_versions_same_dup(self):
1178
2336
        index = self.two_graph_index(catch_adds=True)
1179
2337
        # options can be spelt two different ways
1180
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100),
1181
 
            [('parent',)])])
1182
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100),
1183
 
            [('parent',)])])
1184
 
        # position/length are ignored (because each pack could have fulltext or
1185
 
        # delta, and be at a different position.
1186
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1187
 
            [('parent',)])])
1188
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1189
 
            [('parent',)])])
 
2338
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
 
2339
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
1190
2340
        # but neither should have added data.
1191
 
        self.assertEqual([[], [], [], []], self.caught_entries)
 
2341
        self.assertEqual([[], []], self.caught_entries)
1192
2342
        
1193
2343
    def test_add_versions_different_dup(self):
1194
2344
        index = self.two_graph_index(deltas=True, catch_adds=True)
1195
2345
        # change options
1196
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1197
 
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1198
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1199
 
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [('parent',)])])
1200
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1201
 
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
 
2346
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2347
            [('tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
 
2348
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2349
            [('tip', 'line-delta,no-eol', (None, 0, 100), ['parent'])])
 
2350
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2351
            [('tip', 'fulltext', (None, 0, 100), ['parent'])])
 
2352
        # position/length
 
2353
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2354
            [('tip', 'fulltext,no-eol', (None, 50, 100), ['parent'])])
 
2355
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2356
            [('tip', 'fulltext,no-eol', (None, 0, 1000), ['parent'])])
1202
2357
        # parents
1203
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1204
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
 
2358
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2359
            [('tip', 'fulltext,no-eol', (None, 0, 100), [])])
1205
2360
        # change options in the second record
1206
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1207
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)]),
1208
 
             (('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
 
2361
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2362
            [('tip', 'fulltext,no-eol', (None, 0, 100), ['parent']),
 
2363
             ('tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
1209
2364
        self.assertEqual([], self.caught_entries)
1210
2365
 
 
2366
    def test_iter_parents(self):
 
2367
        index1 = self.make_g_index('1', 1, [
 
2368
        # no parents
 
2369
            (('r0', ), 'N0 100', ([], )),
 
2370
        # 1 parent
 
2371
            (('r1', ), '', ([('r0', )], ))])
 
2372
        index2 = self.make_g_index('2', 1, [
 
2373
        # 2 parents
 
2374
            (('r2', ), 'N0 100', ([('r1', ), ('r0', )], )),
 
2375
            ])
 
2376
        combined_index = CombinedGraphIndex([index1, index2])
 
2377
        index = KnitGraphIndex(combined_index)
 
2378
        # XXX TODO a ghost
 
2379
        # cases: each sample data individually:
 
2380
        self.assertEqual(set([('r0', ())]),
 
2381
            set(index.iter_parents(['r0'])))
 
2382
        self.assertEqual(set([('r1', ('r0', ))]),
 
2383
            set(index.iter_parents(['r1'])))
 
2384
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
 
2385
            set(index.iter_parents(['r2'])))
 
2386
        # no nodes returned for a missing node
 
2387
        self.assertEqual(set(),
 
2388
            set(index.iter_parents(['missing'])))
 
2389
        # 1 node returned with missing nodes skipped
 
2390
        self.assertEqual(set([('r1', ('r0', ))]),
 
2391
            set(index.iter_parents(['ghost1', 'r1', 'ghost'])))
 
2392
        # 2 nodes returned
 
2393
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
2394
            set(index.iter_parents(['r0', 'r1'])))
 
2395
        # 2 nodes returned, missing skipped
 
2396
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
 
2397
            set(index.iter_parents(['a', 'r0', 'b', 'r1', 'c'])))
 
2398
 
1211
2399
 
1212
2400
class TestNoParentsGraphIndexKnit(KnitTests):
1213
 
    """Tests for knits using _KnitGraphIndex with no parents."""
 
2401
    """Tests for knits using KnitGraphIndex with no parents."""
1214
2402
 
1215
2403
    def make_g_index(self, name, ref_lists=0, nodes=[]):
1216
2404
        builder = GraphIndexBuilder(ref_lists)
1223
2411
 
1224
2412
    def test_parents_deltas_incompatible(self):
1225
2413
        index = CombinedGraphIndex([])
1226
 
        self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1227
 
            index, deltas=True, parents=False)
 
2414
        self.assertRaises(errors.KnitError, KnitGraphIndex, index,
 
2415
            deltas=True, parents=False)
1228
2416
 
1229
2417
    def two_graph_index(self, catch_adds=False):
1230
2418
        """Build a two-graph index.
1246
2434
            add_callback = self.catch_add
1247
2435
        else:
1248
2436
            add_callback = None
1249
 
        return _KnitGraphIndex(combined_index, lambda:True, parents=False,
 
2437
        return KnitGraphIndex(combined_index, parents=False,
1250
2438
            add_callback=add_callback)
1251
2439
 
1252
 
    def test_keys(self):
1253
 
        index = self.two_graph_index()
1254
 
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1255
 
            set(index.keys()))
 
2440
    def test_get_graph(self):
 
2441
        index = self.two_graph_index()
 
2442
        self.assertEqual(set([
 
2443
            ('tip', ()),
 
2444
            ('tail', ()),
 
2445
            ('parent', ()),
 
2446
            ('separate', ()),
 
2447
            ]), set(index.get_graph()))
 
2448
 
 
2449
    def test_get_ancestry(self):
 
2450
        # with no parents, ancestry is always just the key.
 
2451
        index = self.two_graph_index()
 
2452
        self.assertEqual([], index.get_ancestry([]))
 
2453
        self.assertEqual(['separate'], index.get_ancestry(['separate']))
 
2454
        self.assertEqual(['tail'], index.get_ancestry(['tail']))
 
2455
        self.assertEqual(['parent'], index.get_ancestry(['parent']))
 
2456
        self.assertEqual(['tip'], index.get_ancestry(['tip']))
 
2457
        self.assertTrue(index.get_ancestry(['tip', 'separate']) in
 
2458
            (['tip', 'separate'],
 
2459
             ['separate', 'tip'],
 
2460
            ))
 
2461
        # asking for a ghost makes it go boom.
 
2462
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry, ['ghost'])
 
2463
 
 
2464
    def test_get_ancestry_with_ghosts(self):
 
2465
        index = self.two_graph_index()
 
2466
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
 
2467
        self.assertEqual(['separate'], index.get_ancestry_with_ghosts(['separate']))
 
2468
        self.assertEqual(['tail'], index.get_ancestry_with_ghosts(['tail']))
 
2469
        self.assertEqual(['parent'], index.get_ancestry_with_ghosts(['parent']))
 
2470
        self.assertEqual(['tip'], index.get_ancestry_with_ghosts(['tip']))
 
2471
        self.assertTrue(index.get_ancestry_with_ghosts(['tip', 'separate']) in
 
2472
            (['tip', 'separate'],
 
2473
             ['separate', 'tip'],
 
2474
            ))
 
2475
        # asking for a ghost makes it go boom.
 
2476
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry_with_ghosts, ['ghost'])
 
2477
 
 
2478
    def test_num_versions(self):
 
2479
        index = self.two_graph_index()
 
2480
        self.assertEqual(4, index.num_versions())
 
2481
 
 
2482
    def test_get_versions(self):
 
2483
        index = self.two_graph_index()
 
2484
        self.assertEqual(set(['tail', 'tip', 'parent', 'separate']),
 
2485
            set(index.get_versions()))
 
2486
 
 
2487
    def test_has_version(self):
 
2488
        index = self.two_graph_index()
 
2489
        self.assertTrue(index.has_version('tail'))
 
2490
        self.assertFalse(index.has_version('ghost'))
1256
2491
 
1257
2492
    def test_get_position(self):
1258
2493
        index = self.two_graph_index()
1259
 
        self.assertEqual((index._graph_index._indices[0], 0, 100),
1260
 
            index.get_position(('tip',)))
1261
 
        self.assertEqual((index._graph_index._indices[1], 100, 78),
1262
 
            index.get_position(('parent',)))
 
2494
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position('tip'))
 
2495
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position('parent'))
1263
2496
 
1264
2497
    def test_get_method(self):
1265
2498
        index = self.two_graph_index()
1266
 
        self.assertEqual('fulltext', index.get_method(('tip',)))
1267
 
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
 
2499
        self.assertEqual('fulltext', index.get_method('tip'))
 
2500
        self.assertEqual(['fulltext'], index.get_options('parent'))
1268
2501
 
1269
2502
    def test_get_options(self):
1270
2503
        index = self.two_graph_index()
1271
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1272
 
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
1273
 
 
1274
 
    def test_get_parent_map(self):
1275
 
        index = self.two_graph_index()
1276
 
        self.assertEqual({('parent',):None},
1277
 
            index.get_parent_map([('parent',), ('ghost',)]))
 
2504
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
 
2505
        self.assertEqual(['fulltext'], index.get_options('parent'))
 
2506
 
 
2507
    def test_get_parents(self):
 
2508
        index = self.two_graph_index()
 
2509
        self.assertEqual((), index.get_parents('parent'))
 
2510
        # and errors on ghosts.
 
2511
        self.assertRaises(errors.RevisionNotPresent,
 
2512
            index.get_parents, 'ghost')
 
2513
 
 
2514
    def test_get_parents_with_ghosts(self):
 
2515
        index = self.two_graph_index()
 
2516
        self.assertEqual((), index.get_parents_with_ghosts('parent'))
 
2517
        # and errors on ghosts.
 
2518
        self.assertRaises(errors.RevisionNotPresent,
 
2519
            index.get_parents_with_ghosts, 'ghost')
 
2520
 
 
2521
    def test_check_versions_present(self):
 
2522
        index = self.two_graph_index()
 
2523
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
2524
            ['missing'])
 
2525
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
 
2526
            ['tail', 'missing'])
 
2527
        index.check_versions_present(['tail', 'separate'])
1278
2528
 
1279
2529
    def catch_add(self, entries):
1280
2530
        self.caught_entries.append(entries)
1281
2531
 
1282
2532
    def test_add_no_callback_errors(self):
1283
2533
        index = self.two_graph_index()
1284
 
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1285
 
            [(('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)])])
 
2534
        self.assertRaises(errors.ReadOnlyError, index.add_version,
 
2535
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
1286
2536
 
1287
2537
    def test_add_version_smoke(self):
1288
2538
        index = self.two_graph_index(catch_adds=True)
1289
 
        index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60), [])])
 
2539
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), [])
1290
2540
        self.assertEqual([[(('new', ), 'N50 60')]],
1291
2541
            self.caught_entries)
1292
2542
 
1293
2543
    def test_add_version_delta_not_delta_index(self):
1294
2544
        index = self.two_graph_index(catch_adds=True)
1295
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1296
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [])])
 
2545
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2546
            'new', 'no-eol,line-delta', (None, 0, 100), [])
1297
2547
        self.assertEqual([], self.caught_entries)
1298
2548
 
1299
2549
    def test_add_version_same_dup(self):
1300
2550
        index = self.two_graph_index(catch_adds=True)
1301
2551
        # options can be spelt two different ways
1302
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1303
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
1304
 
        # position/length are ignored (because each pack could have fulltext or
1305
 
        # delta, and be at a different position.
1306
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1307
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
 
2552
        index.add_version('tip', 'fulltext,no-eol', (None, 0, 100), [])
 
2553
        index.add_version('tip', 'no-eol,fulltext', (None, 0, 100), [])
1308
2554
        # but neither should have added data.
1309
 
        self.assertEqual([[], [], [], []], self.caught_entries)
 
2555
        self.assertEqual([[], []], self.caught_entries)
1310
2556
        
1311
2557
    def test_add_version_different_dup(self):
1312
2558
        index = self.two_graph_index(catch_adds=True)
1313
2559
        # change options
1314
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1315
 
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
1316
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1317
 
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
1318
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1319
 
            [(('tip',), 'fulltext', (None, 0, 100), [])])
 
2560
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2561
            'tip', 'no-eol,line-delta', (None, 0, 100), [])
 
2562
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2563
            'tip', 'line-delta,no-eol', (None, 0, 100), [])
 
2564
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2565
            'tip', 'fulltext', (None, 0, 100), [])
 
2566
        # position/length
 
2567
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2568
            'tip', 'fulltext,no-eol', (None, 50, 100), [])
 
2569
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2570
            'tip', 'fulltext,no-eol', (None, 0, 1000), [])
1320
2571
        # parents
1321
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1322
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
 
2572
        self.assertRaises(errors.KnitCorrupt, index.add_version,
 
2573
            'tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])
1323
2574
        self.assertEqual([], self.caught_entries)
1324
2575
        
1325
2576
    def test_add_versions(self):
1326
2577
        index = self.two_graph_index(catch_adds=True)
1327
 
        index.add_records([
1328
 
                (('new',), 'fulltext,no-eol', (None, 50, 60), []),
1329
 
                (('new2',), 'fulltext', (None, 0, 6), []),
 
2578
        index.add_versions([
 
2579
                ('new', 'fulltext,no-eol', (None, 50, 60), []),
 
2580
                ('new2', 'fulltext', (None, 0, 6), []),
1330
2581
                ])
1331
2582
        self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
1332
2583
            sorted(self.caught_entries[0]))
1334
2585
 
1335
2586
    def test_add_versions_delta_not_delta_index(self):
1336
2587
        index = self.two_graph_index(catch_adds=True)
1337
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1338
 
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
 
2588
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2589
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
1339
2590
        self.assertEqual([], self.caught_entries)
1340
2591
 
1341
2592
    def test_add_versions_parents_not_parents_index(self):
1342
2593
        index = self.two_graph_index(catch_adds=True)
1343
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1344
 
            [(('new',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
 
2594
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2595
            [('new', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
1345
2596
        self.assertEqual([], self.caught_entries)
1346
2597
 
1347
2598
    def test_add_versions_random_id_accepted(self):
1348
2599
        index = self.two_graph_index(catch_adds=True)
1349
 
        index.add_records([], random_id=True)
 
2600
        index.add_versions([], random_id=True)
1350
2601
 
1351
2602
    def test_add_versions_same_dup(self):
1352
2603
        index = self.two_graph_index(catch_adds=True)
1353
2604
        # options can be spelt two different ways
1354
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1355
 
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
1356
 
        # position/length are ignored (because each pack could have fulltext or
1357
 
        # delta, and be at a different position.
1358
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1359
 
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
 
2605
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), [])])
 
2606
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), [])])
1360
2607
        # but neither should have added data.
1361
 
        self.assertEqual([[], [], [], []], self.caught_entries)
 
2608
        self.assertEqual([[], []], self.caught_entries)
1362
2609
        
1363
2610
    def test_add_versions_different_dup(self):
1364
2611
        index = self.two_graph_index(catch_adds=True)
1365
2612
        # change options
1366
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1367
 
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
1368
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1369
 
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
1370
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1371
 
            [(('tip',), 'fulltext', (None, 0, 100), [])])
 
2613
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2614
            [('tip', 'no-eol,line-delta', (None, 0, 100), [])])
 
2615
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2616
            [('tip', 'line-delta,no-eol', (None, 0, 100), [])])
 
2617
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2618
            [('tip', 'fulltext', (None, 0, 100), [])])
 
2619
        # position/length
 
2620
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2621
            [('tip', 'fulltext,no-eol', (None, 50, 100), [])])
 
2622
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2623
            [('tip', 'fulltext,no-eol', (None, 0, 1000), [])])
1372
2624
        # parents
1373
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1374
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
 
2625
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2626
            [('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
1375
2627
        # change options in the second record
1376
 
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1377
 
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), []),
1378
 
             (('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
 
2628
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
 
2629
            [('tip', 'fulltext,no-eol', (None, 0, 100), []),
 
2630
             ('tip', 'no-eol,line-delta', (None, 0, 100), [])])
1379
2631
        self.assertEqual([], self.caught_entries)
1380
2632
 
1381
 
 
1382
 
class TestStacking(KnitTests):
1383
 
 
1384
 
    def get_basis_and_test_knit(self):
1385
 
        basis = self.make_test_knit(name='basis')
1386
 
        basis = RecordingVersionedFilesDecorator(basis)
1387
 
        test = self.make_test_knit(name='test')
1388
 
        test.add_fallback_versioned_files(basis)
1389
 
        return basis, test
1390
 
 
1391
 
    def test_add_fallback_versioned_files(self):
1392
 
        basis = self.make_test_knit(name='basis')
1393
 
        test = self.make_test_knit(name='test')
1394
 
        # It must not error; other tests test that the fallback is referred to
1395
 
        # when accessing data.
1396
 
        test.add_fallback_versioned_files(basis)
1397
 
 
1398
 
    def test_add_lines(self):
1399
 
        # lines added to the test are not added to the basis
1400
 
        basis, test = self.get_basis_and_test_knit()
1401
 
        key = ('foo',)
1402
 
        key_basis = ('bar',)
1403
 
        key_cross_border = ('quux',)
1404
 
        key_delta = ('zaphod',)
1405
 
        test.add_lines(key, (), ['foo\n'])
1406
 
        self.assertEqual({}, basis.get_parent_map([key]))
1407
 
        # lines added to the test that reference across the stack do a
1408
 
        # fulltext.
1409
 
        basis.add_lines(key_basis, (), ['foo\n'])
1410
 
        basis.calls = []
1411
 
        test.add_lines(key_cross_border, (key_basis,), ['foo\n'])
1412
 
        self.assertEqual('fulltext', test._index.get_method(key_cross_border))
1413
 
        self.assertEqual([("get_parent_map", set([key_basis]))], basis.calls)
1414
 
        # Subsequent adds do delta.
1415
 
        basis.calls = []
1416
 
        test.add_lines(key_delta, (key_cross_border,), ['foo\n'])
1417
 
        self.assertEqual('line-delta', test._index.get_method(key_delta))
1418
 
        self.assertEqual([], basis.calls)
1419
 
 
1420
 
    def test_annotate(self):
1421
 
        # annotations from the test knit are answered without asking the basis
1422
 
        basis, test = self.get_basis_and_test_knit()
1423
 
        key = ('foo',)
1424
 
        key_basis = ('bar',)
1425
 
        key_missing = ('missing',)
1426
 
        test.add_lines(key, (), ['foo\n'])
1427
 
        details = test.annotate(key)
1428
 
        self.assertEqual([(key, 'foo\n')], details)
1429
 
        self.assertEqual([], basis.calls)
1430
 
        # But texts that are not in the test knit are looked for in the basis
1431
 
        # directly.
1432
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1433
 
        basis.calls = []
1434
 
        details = test.annotate(key_basis)
1435
 
        self.assertEqual([(key_basis, 'foo\n'), (key_basis, 'bar\n')], details)
1436
 
        # Not optimised to date:
1437
 
        # self.assertEqual([("annotate", key_basis)], basis.calls)
1438
 
        self.assertEqual([('get_parent_map', set([key_basis])),
1439
 
            ('get_parent_map', set([key_basis])),
1440
 
            ('get_parent_map', set([key_basis])),
1441
 
            ('get_record_stream', [key_basis], 'unordered', True)],
1442
 
            basis.calls)
1443
 
 
1444
 
    def test_check(self):
1445
 
        # At the moment checking a stacked knit does implicitly check the
1446
 
        # fallback files.  
1447
 
        basis, test = self.get_basis_and_test_knit()
1448
 
        test.check()
1449
 
 
1450
 
    def test_get_parent_map(self):
1451
 
        # parents in the test knit are answered without asking the basis
1452
 
        basis, test = self.get_basis_and_test_knit()
1453
 
        key = ('foo',)
1454
 
        key_basis = ('bar',)
1455
 
        key_missing = ('missing',)
1456
 
        test.add_lines(key, (), [])
1457
 
        parent_map = test.get_parent_map([key])
1458
 
        self.assertEqual({key: ()}, parent_map)
1459
 
        self.assertEqual([], basis.calls)
1460
 
        # But parents that are not in the test knit are looked for in the basis
1461
 
        basis.add_lines(key_basis, (), [])
1462
 
        basis.calls = []
1463
 
        parent_map = test.get_parent_map([key, key_basis, key_missing])
1464
 
        self.assertEqual({key: (),
1465
 
            key_basis: ()}, parent_map)
1466
 
        self.assertEqual([("get_parent_map", set([key_basis, key_missing]))],
1467
 
            basis.calls)
1468
 
 
1469
 
    def test_get_record_stream_unordered_fulltexts(self):
1470
 
        # records from the test knit are answered without asking the basis:
1471
 
        basis, test = self.get_basis_and_test_knit()
1472
 
        key = ('foo',)
1473
 
        key_basis = ('bar',)
1474
 
        key_missing = ('missing',)
1475
 
        test.add_lines(key, (), ['foo\n'])
1476
 
        records = list(test.get_record_stream([key], 'unordered', True))
1477
 
        self.assertEqual(1, len(records))
1478
 
        self.assertEqual([], basis.calls)
1479
 
        # Missing (from test knit) objects are retrieved from the basis:
1480
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1481
 
        basis.calls = []
1482
 
        records = list(test.get_record_stream([key_basis, key_missing],
1483
 
            'unordered', True))
1484
 
        self.assertEqual(2, len(records))
1485
 
        calls = list(basis.calls)
1486
 
        for record in records:
1487
 
            self.assertSubset([record.key], (key_basis, key_missing))
1488
 
            if record.key == key_missing:
1489
 
                self.assertIsInstance(record, AbsentContentFactory)
1490
 
            else:
1491
 
                reference = list(basis.get_record_stream([key_basis],
1492
 
                    'unordered', True))[0]
1493
 
                self.assertEqual(reference.key, record.key)
1494
 
                self.assertEqual(reference.sha1, record.sha1)
1495
 
                self.assertEqual(reference.storage_kind, record.storage_kind)
1496
 
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
1497
 
                    record.get_bytes_as(record.storage_kind))
1498
 
                self.assertEqual(reference.get_bytes_as('fulltext'),
1499
 
                    record.get_bytes_as('fulltext'))
1500
 
        # It's not strictly minimal, but it seems reasonable for now for it to
1501
 
        # ask which fallbacks have which parents.
1502
 
        self.assertEqual([
1503
 
            ("get_parent_map", set([key_basis, key_missing])),
1504
 
            ("get_record_stream", [key_basis], 'unordered', True)],
1505
 
            calls)
1506
 
 
1507
 
    def test_get_record_stream_ordered_fulltexts(self):
1508
 
        # ordering is preserved down into the fallback store.
1509
 
        basis, test = self.get_basis_and_test_knit()
1510
 
        key = ('foo',)
1511
 
        key_basis = ('bar',)
1512
 
        key_basis_2 = ('quux',)
1513
 
        key_missing = ('missing',)
1514
 
        test.add_lines(key, (key_basis,), ['foo\n'])
1515
 
        # Missing (from test knit) objects are retrieved from the basis:
1516
 
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
1517
 
        basis.add_lines(key_basis_2, (), ['quux\n'])
1518
 
        basis.calls = []
1519
 
        # ask for in non-topological order
1520
 
        records = list(test.get_record_stream(
1521
 
            [key, key_basis, key_missing, key_basis_2], 'topological', True))
1522
 
        self.assertEqual(4, len(records))
1523
 
        results = []
1524
 
        for record in records:
1525
 
            self.assertSubset([record.key],
1526
 
                (key_basis, key_missing, key_basis_2, key))
1527
 
            if record.key == key_missing:
1528
 
                self.assertIsInstance(record, AbsentContentFactory)
1529
 
            else:
1530
 
                results.append((record.key, record.sha1, record.storage_kind,
1531
 
                    record.get_bytes_as('fulltext')))
1532
 
        calls = list(basis.calls)
1533
 
        order = [record[0] for record in results]
1534
 
        self.assertEqual([key_basis_2, key_basis, key], order)
1535
 
        for result in results:
1536
 
            if result[0] == key:
1537
 
                source = test
1538
 
            else:
1539
 
                source = basis
1540
 
            record = source.get_record_stream([result[0]], 'unordered',
1541
 
                True).next()
1542
 
            self.assertEqual(record.key, result[0])
1543
 
            self.assertEqual(record.sha1, result[1])
1544
 
            self.assertEqual(record.storage_kind, result[2])
1545
 
            self.assertEqual(record.get_bytes_as('fulltext'), result[3])
1546
 
        # It's not strictly minimal, but it seems reasonable for now for it to
1547
 
        # ask which fallbacks have which parents.
1548
 
        self.assertEqual([
1549
 
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
1550
 
            # unordered is asked for by the underlying worker as it still
1551
 
            # buffers everything while answering - which is a problem!
1552
 
            ("get_record_stream", [key_basis_2, key_basis], 'unordered', True)],
1553
 
            calls)
1554
 
 
1555
 
    def test_get_record_stream_unordered_deltas(self):
1556
 
        # records from the test knit are answered without asking the basis:
1557
 
        basis, test = self.get_basis_and_test_knit()
1558
 
        key = ('foo',)
1559
 
        key_basis = ('bar',)
1560
 
        key_missing = ('missing',)
1561
 
        test.add_lines(key, (), ['foo\n'])
1562
 
        records = list(test.get_record_stream([key], 'unordered', False))
1563
 
        self.assertEqual(1, len(records))
1564
 
        self.assertEqual([], basis.calls)
1565
 
        # Missing (from test knit) objects are retrieved from the basis:
1566
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1567
 
        basis.calls = []
1568
 
        records = list(test.get_record_stream([key_basis, key_missing],
1569
 
            'unordered', False))
1570
 
        self.assertEqual(2, len(records))
1571
 
        calls = list(basis.calls)
1572
 
        for record in records:
1573
 
            self.assertSubset([record.key], (key_basis, key_missing))
1574
 
            if record.key == key_missing:
1575
 
                self.assertIsInstance(record, AbsentContentFactory)
1576
 
            else:
1577
 
                reference = list(basis.get_record_stream([key_basis],
1578
 
                    'unordered', False))[0]
1579
 
                self.assertEqual(reference.key, record.key)
1580
 
                self.assertEqual(reference.sha1, record.sha1)
1581
 
                self.assertEqual(reference.storage_kind, record.storage_kind)
1582
 
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
1583
 
                    record.get_bytes_as(record.storage_kind))
1584
 
        # It's not strictly minimal, but it seems reasonable for now for it to
1585
 
        # ask which fallbacks have which parents.
1586
 
        self.assertEqual([
1587
 
            ("get_parent_map", set([key_basis, key_missing])),
1588
 
            ("get_record_stream", [key_basis], 'unordered', False)],
1589
 
            calls)
1590
 
 
1591
 
    def test_get_record_stream_ordered_deltas(self):
1592
 
        # ordering is preserved down into the fallback store.
1593
 
        basis, test = self.get_basis_and_test_knit()
1594
 
        key = ('foo',)
1595
 
        key_basis = ('bar',)
1596
 
        key_basis_2 = ('quux',)
1597
 
        key_missing = ('missing',)
1598
 
        test.add_lines(key, (key_basis,), ['foo\n'])
1599
 
        # Missing (from test knit) objects are retrieved from the basis:
1600
 
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
1601
 
        basis.add_lines(key_basis_2, (), ['quux\n'])
1602
 
        basis.calls = []
1603
 
        # ask for in non-topological order
1604
 
        records = list(test.get_record_stream(
1605
 
            [key, key_basis, key_missing, key_basis_2], 'topological', False))
1606
 
        self.assertEqual(4, len(records))
1607
 
        results = []
1608
 
        for record in records:
1609
 
            self.assertSubset([record.key],
1610
 
                (key_basis, key_missing, key_basis_2, key))
1611
 
            if record.key == key_missing:
1612
 
                self.assertIsInstance(record, AbsentContentFactory)
1613
 
            else:
1614
 
                results.append((record.key, record.sha1, record.storage_kind,
1615
 
                    record.get_bytes_as(record.storage_kind)))
1616
 
        calls = list(basis.calls)
1617
 
        order = [record[0] for record in results]
1618
 
        self.assertEqual([key_basis_2, key_basis, key], order)
1619
 
        for result in results:
1620
 
            if result[0] == key:
1621
 
                source = test
1622
 
            else:
1623
 
                source = basis
1624
 
            record = source.get_record_stream([result[0]], 'unordered',
1625
 
                False).next()
1626
 
            self.assertEqual(record.key, result[0])
1627
 
            self.assertEqual(record.sha1, result[1])
1628
 
            self.assertEqual(record.storage_kind, result[2])
1629
 
            self.assertEqual(record.get_bytes_as(record.storage_kind), result[3])
1630
 
        # It's not strictly minimal, but it seems reasonable for now for it to
1631
 
        # ask which fallbacks have which parents.
1632
 
        self.assertEqual([
1633
 
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
1634
 
            ("get_record_stream", [key_basis_2, key_basis], 'topological', False)],
1635
 
            calls)
1636
 
 
1637
 
    def test_get_sha1s(self):
1638
 
        # sha1's in the test knit are answered without asking the basis
1639
 
        basis, test = self.get_basis_and_test_knit()
1640
 
        key = ('foo',)
1641
 
        key_basis = ('bar',)
1642
 
        key_missing = ('missing',)
1643
 
        test.add_lines(key, (), ['foo\n'])
1644
 
        key_sha1sum = sha.new('foo\n').hexdigest()
1645
 
        sha1s = test.get_sha1s([key])
1646
 
        self.assertEqual({key: key_sha1sum}, sha1s)
1647
 
        self.assertEqual([], basis.calls)
1648
 
        # But texts that are not in the test knit are looked for in the basis
1649
 
        # directly (rather than via text reconstruction) so that remote servers
1650
 
        # etc don't have to answer with full content.
1651
 
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1652
 
        basis_sha1sum = sha.new('foo\nbar\n').hexdigest()
1653
 
        basis.calls = []
1654
 
        sha1s = test.get_sha1s([key, key_missing, key_basis])
1655
 
        self.assertEqual({key: key_sha1sum,
1656
 
            key_basis: basis_sha1sum}, sha1s)
1657
 
        self.assertEqual([("get_sha1s", set([key_basis, key_missing]))],
1658
 
            basis.calls)
1659
 
 
1660
 
    def test_insert_record_stream(self):
1661
 
        # records are inserted as normal; insert_record_stream builds on
1662
 
        # add_lines, so a smoke test should be all that's needed:
1663
 
        key = ('foo',)
1664
 
        key_basis = ('bar',)
1665
 
        key_delta = ('zaphod',)
1666
 
        basis, test = self.get_basis_and_test_knit()
1667
 
        source = self.make_test_knit(name='source')
1668
 
        basis.add_lines(key_basis, (), ['foo\n'])
1669
 
        basis.calls = []
1670
 
        source.add_lines(key_basis, (), ['foo\n'])
1671
 
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
1672
 
        stream = source.get_record_stream([key_delta], 'unordered', False)
1673
 
        test.insert_record_stream(stream)
1674
 
        self.assertEqual([("get_parent_map", set([key_basis]))],
1675
 
            basis.calls)
1676
 
        self.assertEqual({key_delta:(key_basis,)},
1677
 
            test.get_parent_map([key_delta]))
1678
 
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
1679
 
            'unordered', True).next().get_bytes_as('fulltext'))
1680
 
 
1681
 
    def test_iter_lines_added_or_present_in_keys(self):
1682
 
        # Lines from the basis are returned, and lines for a given key are only
1683
 
        # returned once. 
1684
 
        key1 = ('foo1',)
1685
 
        key2 = ('foo2',)
1686
 
        # all sources are asked for keys:
1687
 
        basis, test = self.get_basis_and_test_knit()
1688
 
        basis.add_lines(key1, (), ["foo"])
1689
 
        basis.calls = []
1690
 
        lines = list(test.iter_lines_added_or_present_in_keys([key1]))
1691
 
        self.assertEqual([("foo\n", key1)], lines)
1692
 
        self.assertEqual([("iter_lines_added_or_present_in_keys", set([key1]))],
1693
 
            basis.calls)
1694
 
        # keys in both are not duplicated:
1695
 
        test.add_lines(key2, (), ["bar\n"])
1696
 
        basis.add_lines(key2, (), ["bar\n"])
1697
 
        basis.calls = []
1698
 
        lines = list(test.iter_lines_added_or_present_in_keys([key2]))
1699
 
        self.assertEqual([("bar\n", key2)], lines)
1700
 
        self.assertEqual([], basis.calls)
1701
 
 
1702
 
    def test_keys(self):
1703
 
        key1 = ('foo1',)
1704
 
        key2 = ('foo2',)
1705
 
        # all sources are asked for keys:
1706
 
        basis, test = self.get_basis_and_test_knit()
1707
 
        keys = test.keys()
1708
 
        self.assertEqual(set(), set(keys))
1709
 
        self.assertEqual([("keys",)], basis.calls)
1710
 
        # keys from a basis are returned:
1711
 
        basis.add_lines(key1, (), [])
1712
 
        basis.calls = []
1713
 
        keys = test.keys()
1714
 
        self.assertEqual(set([key1]), set(keys))
1715
 
        self.assertEqual([("keys",)], basis.calls)
1716
 
        # keys in both are not duplicated:
1717
 
        test.add_lines(key2, (), [])
1718
 
        basis.add_lines(key2, (), [])
1719
 
        basis.calls = []
1720
 
        keys = test.keys()
1721
 
        self.assertEqual(2, len(keys))
1722
 
        self.assertEqual(set([key1, key2]), set(keys))
1723
 
        self.assertEqual([("keys",)], basis.calls)
1724
 
 
1725
 
    def test_add_mpdiffs(self):
1726
 
        # records are inserted as normal; add_mpdiff builds on
1727
 
        # add_lines, so a smoke test should be all that's needed:
1728
 
        key = ('foo',)
1729
 
        key_basis = ('bar',)
1730
 
        key_delta = ('zaphod',)
1731
 
        basis, test = self.get_basis_and_test_knit()
1732
 
        source = self.make_test_knit(name='source')
1733
 
        basis.add_lines(key_basis, (), ['foo\n'])
1734
 
        basis.calls = []
1735
 
        source.add_lines(key_basis, (), ['foo\n'])
1736
 
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
1737
 
        diffs = source.make_mpdiffs([key_delta])
1738
 
        test.add_mpdiffs([(key_delta, (key_basis,),
1739
 
            source.get_sha1s([key_delta])[key_delta], diffs[0])])
1740
 
        self.assertEqual([("get_parent_map", set([key_basis])),
1741
 
            ('get_record_stream', [key_basis], 'unordered', True),
1742
 
            ('get_parent_map', set([key_basis]))],
1743
 
            basis.calls)
1744
 
        self.assertEqual({key_delta:(key_basis,)},
1745
 
            test.get_parent_map([key_delta]))
1746
 
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
1747
 
            'unordered', True).next().get_bytes_as('fulltext'))
1748
 
 
1749
 
    def test_make_mpdiffs(self):
1750
 
        # Generating an mpdiff across a stacking boundary should detect parent
1751
 
        # texts regions.
1752
 
        key = ('foo',)
1753
 
        key_left = ('bar',)
1754
 
        key_right = ('zaphod',)
1755
 
        basis, test = self.get_basis_and_test_knit()
1756
 
        basis.add_lines(key_left, (), ['bar\n'])
1757
 
        basis.add_lines(key_right, (), ['zaphod\n'])
1758
 
        basis.calls = []
1759
 
        test.add_lines(key, (key_left, key_right),
1760
 
            ['bar\n', 'foo\n', 'zaphod\n'])
1761
 
        diffs = test.make_mpdiffs([key])
1762
 
        self.assertEqual([
1763
 
            multiparent.MultiParent([multiparent.ParentText(0, 0, 0, 1),
1764
 
                multiparent.NewText(['foo\n']),
1765
 
                multiparent.ParentText(1, 0, 2, 1)])],
1766
 
            diffs)
1767
 
        self.assertEqual(4, len(basis.calls))
1768
 
        self.assertEqual([
1769
 
            ("get_parent_map", set([key_left, key_right])),
1770
 
            ("get_parent_map", set([key_left, key_right])),
1771
 
            ("get_parent_map", set([key_left, key_right])),
1772
 
            ],
1773
 
            basis.calls[:3])
1774
 
        last_call = basis.calls[3]
1775
 
        self.assertEqual('get_record_stream', last_call[0])
1776
 
        self.assertEqual(set([key_left, key_right]), set(last_call[1]))
1777
 
        self.assertEqual('unordered', last_call[2])
1778
 
        self.assertEqual(True, last_call[3])
 
2633
    def test_iter_parents(self):
 
2634
        index = self.two_graph_index()
 
2635
        self.assertEqual(set([
 
2636
            ('tip', ()), ('tail', ()), ('parent', ()), ('separate', ())
 
2637
            ]),
 
2638
            set(index.iter_parents(['tip', 'tail', 'ghost', 'parent', 'separate'])))
 
2639
        self.assertEqual(set([('tip', ())]),
 
2640
            set(index.iter_parents(['tip'])))
 
2641
        self.assertEqual(set(),
 
2642
            set(index.iter_parents([])))