~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: 2006-08-17 07:52:09 UTC
  • mfrom: (1910.3.4 trivial)
  • Revision ID: pqm@pqm.ubuntu.com-20060817075209-e85a1f9e05ff8b87
(andrew) Trivial fixes to NotImplemented errors.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 by Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""Tests for Knit data structure"""
18
18
 
19
 
from cStringIO import StringIO
 
19
 
20
20
import difflib
21
 
import gzip
22
 
import sha
23
 
import sys
24
 
 
25
 
from bzrlib import (
26
 
    errors,
27
 
    generate_ids,
28
 
    knit,
29
 
    pack,
30
 
    )
31
 
from bzrlib.errors import (
32
 
    RevisionAlreadyPresent,
33
 
    KnitHeaderError,
34
 
    RevisionNotPresent,
35
 
    NoSuchFile,
36
 
    )
37
 
from bzrlib.index import *
 
21
 
 
22
 
 
23
from bzrlib.errors import KnitError, RevisionAlreadyPresent
38
24
from bzrlib.knit import (
39
 
    AnnotatedKnitContent,
40
 
    KnitContent,
41
 
    KnitGraphIndex,
42
25
    KnitVersionedFile,
43
26
    KnitPlainFactory,
44
27
    KnitAnnotateFactory,
45
 
    _KnitAccess,
46
 
    _KnitData,
47
 
    _KnitIndex,
48
 
    _PackAccess,
49
 
    PlainKnitContent,
50
 
    WeaveToKnit,
51
 
    KnitSequenceMatcher,
52
 
    )
 
28
    WeaveToKnit)
53
29
from bzrlib.osutils import split_lines
54
 
from bzrlib.tests import (
55
 
    Feature,
56
 
    TestCase,
57
 
    TestCaseWithMemoryTransport,
58
 
    TestCaseWithTransport,
59
 
    )
60
 
from bzrlib.transport import get_transport
 
30
from bzrlib.tests import TestCaseWithTransport
 
31
from bzrlib.transport import TransportLogger, get_transport
61
32
from bzrlib.transport.memory import MemoryTransport
62
 
from bzrlib.util import bencode
63
33
from bzrlib.weave import Weave
64
34
 
65
35
 
66
 
class _CompiledKnitFeature(Feature):
67
 
 
68
 
    def _probe(self):
69
 
        try:
70
 
            import bzrlib._knit_load_data_c
71
 
        except ImportError:
72
 
            return False
73
 
        return True
74
 
 
75
 
    def feature_name(self):
76
 
        return 'bzrlib._knit_load_data_c'
77
 
 
78
 
CompiledKnitFeature = _CompiledKnitFeature()
79
 
 
80
 
 
81
 
class KnitContentTestsMixin(object):
82
 
 
83
 
    def test_constructor(self):
84
 
        content = self._make_content([])
85
 
 
86
 
    def test_text(self):
87
 
        content = self._make_content([])
88
 
        self.assertEqual(content.text(), [])
89
 
 
90
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
91
 
        self.assertEqual(content.text(), ["text1", "text2"])
92
 
 
93
 
    def test_copy(self):
94
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
95
 
        copy = content.copy()
96
 
        self.assertIsInstance(copy, content.__class__)
97
 
        self.assertEqual(copy.annotate(), content.annotate())
98
 
 
99
 
    def assertDerivedBlocksEqual(self, source, target, noeol=False):
100
 
        """Assert that the derived matching blocks match real output"""
101
 
        source_lines = source.splitlines(True)
102
 
        target_lines = target.splitlines(True)
103
 
        def nl(line):
104
 
            if noeol and not line.endswith('\n'):
105
 
                return line + '\n'
106
 
            else:
107
 
                return line
108
 
        source_content = self._make_content([(None, nl(l)) for l in source_lines])
109
 
        target_content = self._make_content([(None, nl(l)) for l in target_lines])
110
 
        line_delta = source_content.line_delta(target_content)
111
 
        delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
112
 
            source_lines, target_lines))
113
 
        matcher = KnitSequenceMatcher(None, source_lines, target_lines)
114
 
        matcher_blocks = list(list(matcher.get_matching_blocks()))
115
 
        self.assertEqual(matcher_blocks, delta_blocks)
116
 
 
117
 
    def test_get_line_delta_blocks(self):
118
 
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'q\nc\n')
119
 
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1)
120
 
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1A)
121
 
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1B)
122
 
        self.assertDerivedBlocksEqual(TEXT_1B, TEXT_1A)
123
 
        self.assertDerivedBlocksEqual(TEXT_1A, TEXT_1B)
124
 
        self.assertDerivedBlocksEqual(TEXT_1A, '')
125
 
        self.assertDerivedBlocksEqual('', TEXT_1A)
126
 
        self.assertDerivedBlocksEqual('', '')
127
 
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd')
128
 
 
129
 
    def test_get_line_delta_blocks_noeol(self):
130
 
        """Handle historical knit deltas safely
131
 
 
132
 
        Some existing knit deltas don't consider the last line to differ
133
 
        when the only difference whether it has a final newline.
134
 
 
135
 
        New knit deltas appear to always consider the last line to differ
136
 
        in this case.
137
 
        """
138
 
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd\n', noeol=True)
139
 
        self.assertDerivedBlocksEqual('a\nb\nc\nd\n', 'a\nb\nc', noeol=True)
140
 
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'a\nb\nc', noeol=True)
141
 
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\n', noeol=True)
142
 
 
143
 
 
144
 
class TestPlainKnitContent(TestCase, KnitContentTestsMixin):
145
 
 
146
 
    def _make_content(self, lines):
147
 
        annotated_content = AnnotatedKnitContent(lines)
148
 
        return PlainKnitContent(annotated_content.text(), 'bogus')
149
 
 
150
 
    def test_annotate(self):
151
 
        content = self._make_content([])
152
 
        self.assertEqual(content.annotate(), [])
153
 
 
154
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
155
 
        self.assertEqual(content.annotate(),
156
 
            [("bogus", "text1"), ("bogus", "text2")])
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
 
 
169
 
    def test_line_delta(self):
170
 
        content1 = self._make_content([("", "a"), ("", "b")])
171
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
172
 
        self.assertEqual(content1.line_delta(content2),
173
 
            [(1, 2, 2, ["a", "c"])])
174
 
 
175
 
    def test_line_delta_iter(self):
176
 
        content1 = self._make_content([("", "a"), ("", "b")])
177
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
178
 
        it = content1.line_delta_iter(content2)
179
 
        self.assertEqual(it.next(), (1, 2, 2, ["a", "c"]))
180
 
        self.assertRaises(StopIteration, it.next)
181
 
 
182
 
 
183
 
class TestAnnotatedKnitContent(TestCase, KnitContentTestsMixin):
184
 
 
185
 
    def _make_content(self, lines):
186
 
        return AnnotatedKnitContent(lines)
187
 
 
188
 
    def test_annotate(self):
189
 
        content = self._make_content([])
190
 
        self.assertEqual(content.annotate(), [])
191
 
 
192
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
193
 
        self.assertEqual(content.annotate(),
194
 
            [("origin1", "text1"), ("origin2", "text2")])
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
 
 
207
 
    def test_line_delta(self):
208
 
        content1 = self._make_content([("", "a"), ("", "b")])
209
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
210
 
        self.assertEqual(content1.line_delta(content2),
211
 
            [(1, 2, 2, [("", "a"), ("", "c")])])
212
 
 
213
 
    def test_line_delta_iter(self):
214
 
        content1 = self._make_content([("", "a"), ("", "b")])
215
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
216
 
        it = content1.line_delta_iter(content2)
217
 
        self.assertEqual(it.next(), (1, 2, 2, [("", "a"), ("", "c")]))
218
 
        self.assertRaises(StopIteration, it.next)
219
 
 
220
 
 
221
 
class MockTransport(object):
222
 
 
223
 
    def __init__(self, file_lines=None):
224
 
        self.file_lines = file_lines
225
 
        self.calls = []
226
 
        # We have no base directory for the MockTransport
227
 
        self.base = ''
228
 
 
229
 
    def get(self, filename):
230
 
        if self.file_lines is None:
231
 
            raise NoSuchFile(filename)
232
 
        else:
233
 
            return StringIO("\n".join(self.file_lines))
234
 
 
235
 
    def readv(self, relpath, offsets):
236
 
        fp = self.get(relpath)
237
 
        for offset, size in offsets:
238
 
            fp.seek(offset)
239
 
            yield offset, fp.read(size)
240
 
 
241
 
    def __getattr__(self, name):
242
 
        def queue_call(*args, **kwargs):
243
 
            self.calls.append((name, args, kwargs))
244
 
        return queue_call
245
 
 
246
 
 
247
 
class KnitRecordAccessTestsMixin(object):
248
 
    """Tests for getting and putting knit records."""
249
 
 
250
 
    def assertAccessExists(self, access):
251
 
        """Ensure the data area for access has been initialised/exists."""
252
 
        raise NotImplementedError(self.assertAccessExists)
253
 
 
254
 
    def test_add_raw_records(self):
255
 
        """Add_raw_records adds records retrievable later."""
256
 
        access = self.get_access()
257
 
        memos = access.add_raw_records([10], '1234567890')
258
 
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
259
 
 
260
 
    def test_add_several_raw_records(self):
261
 
        """add_raw_records with many records and read some back."""
262
 
        access = self.get_access()
263
 
        memos = access.add_raw_records([10, 2, 5], '12345678901234567')
264
 
        self.assertEqual(['1234567890', '12', '34567'],
265
 
            list(access.get_raw_records(memos)))
266
 
        self.assertEqual(['1234567890'],
267
 
            list(access.get_raw_records(memos[0:1])))
268
 
        self.assertEqual(['12'],
269
 
            list(access.get_raw_records(memos[1:2])))
270
 
        self.assertEqual(['34567'],
271
 
            list(access.get_raw_records(memos[2:3])))
272
 
        self.assertEqual(['1234567890', '34567'],
273
 
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
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
 
 
286
 
 
287
 
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
288
 
    """Tests for the .kndx implementation."""
289
 
 
290
 
    def assertAccessExists(self, access):
291
 
        self.assertNotEqual(None, access.open_file())
292
 
 
293
 
    def get_access(self):
294
 
        """Get a .knit style access instance."""
295
 
        access = _KnitAccess(self.get_transport(), "foo.knit", None, None,
296
 
            False, False)
297
 
        return access
298
 
    
299
 
 
300
 
class TestPackKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
301
 
    """Tests for the pack based access."""
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
 
 
308
 
    def get_access(self):
309
 
        return self._get_access()[0]
310
 
 
311
 
    def _get_access(self, packname='packfile', index='FOO'):
312
 
        transport = self.get_transport()
313
 
        def write_data(bytes):
314
 
            transport.append_bytes(packname, bytes)
315
 
        writer = pack.ContainerWriter(write_data)
316
 
        writer.begin()
317
 
        indices = {index:(transport, packname)}
318
 
        access = _PackAccess(indices, writer=(writer, index))
319
 
        return access, writer
320
 
 
321
 
    def test_read_from_several_packs(self):
322
 
        access, writer = self._get_access()
323
 
        memos = []
324
 
        memos.extend(access.add_raw_records([10], '1234567890'))
325
 
        writer.end()
326
 
        access, writer = self._get_access('pack2', 'FOOBAR')
327
 
        memos.extend(access.add_raw_records([5], '12345'))
328
 
        writer.end()
329
 
        access, writer = self._get_access('pack3', 'BAZ')
330
 
        memos.extend(access.add_raw_records([5], 'alpha'))
331
 
        writer.end()
332
 
        transport = self.get_transport()
333
 
        access = _PackAccess({"FOO":(transport, 'packfile'),
334
 
            "FOOBAR":(transport, 'pack2'),
335
 
            "BAZ":(transport, 'pack3')})
336
 
        self.assertEqual(['1234567890', '12345', 'alpha'],
337
 
            list(access.get_raw_records(memos)))
338
 
        self.assertEqual(['1234567890'],
339
 
            list(access.get_raw_records(memos[0:1])))
340
 
        self.assertEqual(['12345'],
341
 
            list(access.get_raw_records(memos[1:2])))
342
 
        self.assertEqual(['alpha'],
343
 
            list(access.get_raw_records(memos[2:3])))
344
 
        self.assertEqual(['1234567890', 'alpha'],
345
 
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
346
 
 
347
 
    def test_set_writer(self):
348
 
        """The writer should be settable post construction."""
349
 
        access = _PackAccess({})
350
 
        transport = self.get_transport()
351
 
        packname = 'packfile'
352
 
        index = 'foo'
353
 
        def write_data(bytes):
354
 
            transport.append_bytes(packname, bytes)
355
 
        writer = pack.ContainerWriter(write_data)
356
 
        writer.begin()
357
 
        access.set_writer(writer, index, (transport, packname))
358
 
        memos = access.add_raw_records([10], '1234567890')
359
 
        writer.end()
360
 
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
361
 
 
362
 
 
363
 
class LowLevelKnitDataTests(TestCase):
364
 
 
365
 
    def create_gz_content(self, text):
366
 
        sio = StringIO()
367
 
        gz_file = gzip.GzipFile(mode='wb', fileobj=sio)
368
 
        gz_file.write(text)
369
 
        gz_file.close()
370
 
        return sio.getvalue()
371
 
 
372
 
    def test_valid_knit_data(self):
373
 
        sha1sum = sha.new('foo\nbar\n').hexdigest()
374
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
375
 
                                        'foo\n'
376
 
                                        'bar\n'
377
 
                                        'end rev-id-1\n'
378
 
                                        % (sha1sum,))
379
 
        transport = MockTransport([gz_txt])
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)
389
 
 
390
 
    def test_not_enough_lines(self):
391
 
        sha1sum = sha.new('foo\n').hexdigest()
392
 
        # record says 2 lines data says 1
393
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
394
 
                                        'foo\n'
395
 
                                        'end rev-id-1\n'
396
 
                                        % (sha1sum,))
397
 
        transport = MockTransport([gz_txt])
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)
402
 
 
403
 
        # read_records_iter_raw won't detect that sort of mismatch/corruption
404
 
        raw_contents = list(data.read_records_iter_raw(records))
405
 
        self.assertEqual([('rev-id-1', gz_txt)], raw_contents)
406
 
 
407
 
    def test_too_many_lines(self):
408
 
        sha1sum = sha.new('foo\nbar\n').hexdigest()
409
 
        # record says 1 lines data says 2
410
 
        gz_txt = self.create_gz_content('version rev-id-1 1 %s\n'
411
 
                                        'foo\n'
412
 
                                        'bar\n'
413
 
                                        'end rev-id-1\n'
414
 
                                        % (sha1sum,))
415
 
        transport = MockTransport([gz_txt])
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)
420
 
 
421
 
        # read_records_iter_raw won't detect that sort of mismatch/corruption
422
 
        raw_contents = list(data.read_records_iter_raw(records))
423
 
        self.assertEqual([('rev-id-1', gz_txt)], raw_contents)
424
 
 
425
 
    def test_mismatched_version_id(self):
426
 
        sha1sum = sha.new('foo\nbar\n').hexdigest()
427
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
428
 
                                        'foo\n'
429
 
                                        'bar\n'
430
 
                                        'end rev-id-1\n'
431
 
                                        % (sha1sum,))
432
 
        transport = MockTransport([gz_txt])
433
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
434
 
        data = _KnitData(access=access)
435
 
        # We are asking for rev-id-2, but the data is rev-id-1
436
 
        records = [('rev-id-2', (None, 0, len(gz_txt)))]
437
 
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
438
 
 
439
 
        # read_records_iter_raw will notice if we request the wrong version.
440
 
        self.assertRaises(errors.KnitCorrupt, list,
441
 
                          data.read_records_iter_raw(records))
442
 
 
443
 
    def test_uncompressed_data(self):
444
 
        sha1sum = sha.new('foo\nbar\n').hexdigest()
445
 
        txt = ('version rev-id-1 2 %s\n'
446
 
               'foo\n'
447
 
               'bar\n'
448
 
               'end rev-id-1\n'
449
 
               % (sha1sum,))
450
 
        transport = MockTransport([txt])
451
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
452
 
        data = _KnitData(access=access)
453
 
        records = [('rev-id-1', (None, 0, len(txt)))]
454
 
 
455
 
        # We don't have valid gzip data ==> corrupt
456
 
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
457
 
 
458
 
        # read_records_iter_raw will notice the bad data
459
 
        self.assertRaises(errors.KnitCorrupt, list,
460
 
                          data.read_records_iter_raw(records))
461
 
 
462
 
    def test_corrupted_data(self):
463
 
        sha1sum = sha.new('foo\nbar\n').hexdigest()
464
 
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
465
 
                                        'foo\n'
466
 
                                        'bar\n'
467
 
                                        'end rev-id-1\n'
468
 
                                        % (sha1sum,))
469
 
        # Change 2 bytes in the middle to \xff
470
 
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
471
 
        transport = MockTransport([gz_txt])
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))
481
 
 
482
 
 
483
 
class LowLevelKnitIndexTests(TestCase):
484
 
 
485
 
    def get_knit_index(self, *args, **kwargs):
486
 
        orig = knit._load_data
487
 
        def reset():
488
 
            knit._load_data = orig
489
 
        self.addCleanup(reset)
490
 
        from bzrlib._knit_load_data_py import _load_data_py
491
 
        knit._load_data = _load_data_py
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)
501
 
 
502
 
    def test_create_file(self):
503
 
        transport = MockTransport()
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))
532
 
 
533
 
    def test_read_utf8_version_id(self):
534
 
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
535
 
        utf8_revision_id = unicode_revision_id.encode('utf-8')
536
 
        transport = MockTransport([
537
 
            _KnitIndex.HEADER,
538
 
            '%s option 0 1 :' % (utf8_revision_id,)
539
 
            ])
540
 
        index = self.get_knit_index(transport, "filename", "r")
541
 
        # _KnitIndex is a private class, and deals in utf8 revision_ids, not
542
 
        # Unicode revision_ids.
543
 
        self.assertTrue(index.has_version(utf8_revision_id))
544
 
        self.assertFalse(index.has_version(unicode_revision_id))
545
 
 
546
 
    def test_read_utf8_parents(self):
547
 
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
548
 
        utf8_revision_id = unicode_revision_id.encode('utf-8')
549
 
        transport = MockTransport([
550
 
            _KnitIndex.HEADER,
551
 
            "version option 0 1 .%s :" % (utf8_revision_id,)
552
 
            ])
553
 
        index = self.get_knit_index(transport, "filename", "r")
554
 
        self.assertEqual([utf8_revision_id],
555
 
            index.get_parents_with_ghosts("version"))
556
 
 
557
 
    def test_read_ignore_corrupted_lines(self):
558
 
        transport = MockTransport([
559
 
            _KnitIndex.HEADER,
560
 
            "corrupted",
561
 
            "corrupted options 0 1 .b .c ",
562
 
            "version options 0 1 :"
563
 
            ])
564
 
        index = self.get_knit_index(transport, "filename", "r")
565
 
        self.assertEqual(1, index.num_versions())
566
 
        self.assertTrue(index.has_version("version"))
567
 
 
568
 
    def test_read_corrupted_header(self):
569
 
        transport = MockTransport(['not a bzr knit index header\n'])
570
 
        self.assertRaises(KnitHeaderError,
571
 
            self.get_knit_index, transport, "filename", "r")
572
 
 
573
 
    def test_read_duplicate_entries(self):
574
 
        transport = MockTransport([
575
 
            _KnitIndex.HEADER,
576
 
            "parent options 0 1 :",
577
 
            "version options1 0 1 0 :",
578
 
            "version options2 1 2 .other :",
579
 
            "version options3 3 4 0 .other :"
580
 
            ])
581
 
        index = self.get_knit_index(transport, "filename", "r")
582
 
        self.assertEqual(2, index.num_versions())
583
 
        # check that the index used is the first one written. (Specific
584
 
        # to KnitIndex style indices.
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"))
590
 
 
591
 
    def test_read_compressed_parents(self):
592
 
        transport = MockTransport([
593
 
            _KnitIndex.HEADER,
594
 
            "a option 0 1 :",
595
 
            "b option 0 1 0 :",
596
 
            "c option 0 1 1 0 :",
597
 
            ])
598
 
        index = self.get_knit_index(transport, "filename", "r")
599
 
        self.assertEqual(["a"], index.get_parents("b"))
600
 
        self.assertEqual(["b", "a"], index.get_parents("c"))
601
 
 
602
 
    def test_write_utf8_version_id(self):
603
 
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
604
 
        utf8_revision_id = unicode_revision_id.encode('utf-8')
605
 
        transport = MockTransport([
606
 
            _KnitIndex.HEADER
607
 
            ])
608
 
        index = self.get_knit_index(transport, "filename", "r")
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))
614
 
 
615
 
    def test_write_utf8_parents(self):
616
 
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
617
 
        utf8_revision_id = unicode_revision_id.encode('utf-8')
618
 
        transport = MockTransport([
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"))
792
 
 
793
 
    def test_add_versions(self):
794
 
        transport = MockTransport([
795
 
            _KnitIndex.HEADER
796
 
            ])
797
 
        index = self.get_knit_index(transport, "filename", "r")
798
 
 
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",
805
 
            "\na option 0 1 .b :"
806
 
            "\na opt 1 2 .c :"
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"))
818
 
 
819
 
    def test_add_versions_random_id_is_accepted(self):
820
 
        transport = MockTransport([
821
 
            _KnitIndex.HEADER
822
 
            ])
823
 
        index = self.get_knit_index(transport, "filename", "r")
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)
830
 
 
831
 
    def test_delay_create_and_add_versions(self):
832
 
        transport = MockTransport()
833
 
 
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)
837
 
        self.assertEqual([], transport.calls)
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 +
852
 
            "\na option 0 1 .b :"
853
 
            "\na opt 1 2 .c :"
854
 
            "\nb option 2 3 0 :",
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"))
866
 
 
867
 
    def test_get_position(self):
868
 
        transport = MockTransport([
869
 
            _KnitIndex.HEADER,
870
 
            "a option 0 1 :",
871
 
            "b option 1 2 :"
872
 
            ])
873
 
        index = self.get_knit_index(transport, "filename", "r")
874
 
 
875
 
        self.assertEqual((None, 0, 1), index.get_position("a"))
876
 
        self.assertEqual((None, 1, 2), index.get_position("b"))
877
 
 
878
 
    def test_get_method(self):
879
 
        transport = MockTransport([
880
 
            _KnitIndex.HEADER,
881
 
            "a fulltext,unknown 0 1 :",
882
 
            "b unknown,line-delta 1 2 :",
883
 
            "c bad 3 4 :"
884
 
            ])
885
 
        index = self.get_knit_index(transport, "filename", "r")
886
 
 
887
 
        self.assertEqual("fulltext", index.get_method("a"))
888
 
        self.assertEqual("line-delta", index.get_method("b"))
889
 
        self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, "c")
890
 
 
891
 
    def test_get_options(self):
892
 
        transport = MockTransport([
893
 
            _KnitIndex.HEADER,
894
 
            "a opt1 0 1 :",
895
 
            "b opt2,opt3 1 2 :"
896
 
            ])
897
 
        index = self.get_knit_index(transport, "filename", "r")
898
 
 
899
 
        self.assertEqual(["opt1"], index.get_options("a"))
900
 
        self.assertEqual(["opt2", "opt3"], index.get_options("b"))
901
 
 
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"])
945
 
 
946
 
    def test_impossible_parent(self):
947
 
        """Test we get KnitCorrupt if the parent couldn't possibly exist."""
948
 
        transport = MockTransport([
949
 
            _KnitIndex.HEADER,
950
 
            "a option 0 1 :",
951
 
            "b option 0 1 4 :"  # We don't have a 4th record
952
 
            ])
953
 
        try:
954
 
            self.assertRaises(errors.KnitCorrupt,
955
 
                              self.get_knit_index, transport, 'filename', 'r')
956
 
        except TypeError, e:
957
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
958
 
                           ' not exceptions.IndexError')
959
 
                and sys.version_info[0:2] >= (2,5)):
960
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
961
 
                                  ' raising new style exceptions with python'
962
 
                                  ' >=2.5')
963
 
            else:
964
 
                raise
965
 
 
966
 
    def test_corrupted_parent(self):
967
 
        transport = MockTransport([
968
 
            _KnitIndex.HEADER,
969
 
            "a option 0 1 :",
970
 
            "b option 0 1 :",
971
 
            "c option 0 1 1v :", # Can't have a parent of '1v'
972
 
            ])
973
 
        try:
974
 
            self.assertRaises(errors.KnitCorrupt,
975
 
                              self.get_knit_index, transport, 'filename', 'r')
976
 
        except TypeError, e:
977
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
978
 
                           ' not exceptions.ValueError')
979
 
                and sys.version_info[0:2] >= (2,5)):
980
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
981
 
                                  ' raising new style exceptions with python'
982
 
                                  ' >=2.5')
983
 
            else:
984
 
                raise
985
 
 
986
 
    def test_corrupted_parent_in_list(self):
987
 
        transport = MockTransport([
988
 
            _KnitIndex.HEADER,
989
 
            "a option 0 1 :",
990
 
            "b option 0 1 :",
991
 
            "c option 0 1 1 v :", # Can't have a parent of 'v'
992
 
            ])
993
 
        try:
994
 
            self.assertRaises(errors.KnitCorrupt,
995
 
                              self.get_knit_index, transport, 'filename', 'r')
996
 
        except TypeError, e:
997
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
998
 
                           ' not exceptions.ValueError')
999
 
                and sys.version_info[0:2] >= (2,5)):
1000
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1001
 
                                  ' raising new style exceptions with python'
1002
 
                                  ' >=2.5')
1003
 
            else:
1004
 
                raise
1005
 
 
1006
 
    def test_invalid_position(self):
1007
 
        transport = MockTransport([
1008
 
            _KnitIndex.HEADER,
1009
 
            "a option 1v 1 :",
1010
 
            ])
1011
 
        try:
1012
 
            self.assertRaises(errors.KnitCorrupt,
1013
 
                              self.get_knit_index, transport, 'filename', 'r')
1014
 
        except TypeError, e:
1015
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1016
 
                           ' not exceptions.ValueError')
1017
 
                and sys.version_info[0:2] >= (2,5)):
1018
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1019
 
                                  ' raising new style exceptions with python'
1020
 
                                  ' >=2.5')
1021
 
            else:
1022
 
                raise
1023
 
 
1024
 
    def test_invalid_size(self):
1025
 
        transport = MockTransport([
1026
 
            _KnitIndex.HEADER,
1027
 
            "a option 1 1v :",
1028
 
            ])
1029
 
        try:
1030
 
            self.assertRaises(errors.KnitCorrupt,
1031
 
                              self.get_knit_index, transport, 'filename', 'r')
1032
 
        except TypeError, e:
1033
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1034
 
                           ' not exceptions.ValueError')
1035
 
                and sys.version_info[0:2] >= (2,5)):
1036
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1037
 
                                  ' raising new style exceptions with python'
1038
 
                                  ' >=2.5')
1039
 
            else:
1040
 
                raise
1041
 
 
1042
 
    def test_short_line(self):
1043
 
        transport = MockTransport([
1044
 
            _KnitIndex.HEADER,
1045
 
            "a option 0 10  :",
1046
 
            "b option 10 10 0", # This line isn't terminated, ignored
1047
 
            ])
1048
 
        index = self.get_knit_index(transport, "filename", "r")
1049
 
        self.assertEqual(['a'], index.get_versions())
1050
 
 
1051
 
    def test_skip_incomplete_record(self):
1052
 
        # A line with bogus data should just be skipped
1053
 
        transport = MockTransport([
1054
 
            _KnitIndex.HEADER,
1055
 
            "a option 0 10  :",
1056
 
            "b option 10 10 0", # This line isn't terminated, ignored
1057
 
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
1058
 
            ])
1059
 
        index = self.get_knit_index(transport, "filename", "r")
1060
 
        self.assertEqual(['a', 'c'], index.get_versions())
1061
 
 
1062
 
    def test_trailing_characters(self):
1063
 
        # A line with bogus data should just be skipped
1064
 
        transport = MockTransport([
1065
 
            _KnitIndex.HEADER,
1066
 
            "a option 0 10  :",
1067
 
            "b option 10 10 0 :a", # This line has extra trailing characters
1068
 
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
1069
 
            ])
1070
 
        index = self.get_knit_index(transport, "filename", "r")
1071
 
        self.assertEqual(['a', 'c'], index.get_versions())
1072
 
 
1073
 
 
1074
 
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1075
 
 
1076
 
    _test_needs_features = [CompiledKnitFeature]
1077
 
 
1078
 
    def get_knit_index(self, *args, **kwargs):
1079
 
        orig = knit._load_data
1080
 
        def reset():
1081
 
            knit._load_data = orig
1082
 
        self.addCleanup(reset)
1083
 
        from bzrlib._knit_load_data_c import _load_data_c
1084
 
        knit._load_data = _load_data_c
1085
 
        return _KnitIndex(*args, **kwargs)
1086
 
 
1087
 
 
1088
 
 
1089
36
class KnitTests(TestCaseWithTransport):
1090
37
    """Class containing knit test helper routines."""
1091
38
 
1092
 
    def make_test_knit(self, annotate=False, delay_create=False, index=None,
1093
 
                       name='test'):
 
39
    def make_test_knit(self, annotate=False):
1094
40
        if not annotate:
1095
41
            factory = KnitPlainFactory()
1096
42
        else:
1097
43
            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)
 
44
        return KnitVersionedFile('test', get_transport('.'), access_mode='w', factory=factory, create=True)
1110
45
 
1111
46
 
1112
47
class BasicKnitTests(KnitTests):
1119
54
        """Construct empty k"""
1120
55
        self.make_test_knit()
1121
56
 
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
57
    def test_knit_add(self):
1129
58
        """Store one text in knit and retrieve"""
1130
59
        k = self.make_test_knit()
1203
132
        k.clear_cache()
1204
133
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
1205
134
 
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
135
    def test_annotate(self):
1225
136
        """Annotations"""
1226
137
        k = KnitVersionedFile('knit', get_transport('.'), factory=KnitAnnotateFactory(),
1315
226
        self.assertEquals(origins[1], ('text-1', 'b\n'))
1316
227
        self.assertEquals(origins[2], ('text-1', 'c\n'))
1317
228
 
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)
 
229
    def test_knit_join(self):
 
230
        """Store in knit with parents"""
 
231
        k1 = KnitVersionedFile('test1', get_transport('.'), factory=KnitPlainFactory(), create=True)
 
232
        k1.add_lines('text-a', [], split_lines(TEXT_1))
 
233
        k1.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
 
234
 
 
235
        k1.add_lines('text-c', [], split_lines(TEXT_1))
 
236
        k1.add_lines('text-d', ['text-c'], split_lines(TEXT_1))
 
237
 
 
238
        k1.add_lines('text-m', ['text-b', 'text-d'], split_lines(TEXT_1))
 
239
 
 
240
        k2 = KnitVersionedFile('test2', get_transport('.'), factory=KnitPlainFactory(), create=True)
1326
241
        count = k2.join(k1, version_ids=['text-m'])
1327
242
        self.assertEquals(count, 5)
1328
243
        self.assertTrue(k2.has_version('text-a'))
1329
244
        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
245
 
1351
246
    def test_reannotate(self):
1352
247
        k1 = KnitVersionedFile('knit1', get_transport('.'),
1390
285
        k1.get_texts(('%d' % t) for t in range(3))
1391
286
        
1392
287
    def test_iter_lines_reads_in_order(self):
1393
 
        instrumented_t = get_transport('trace+memory:///')
 
288
        t = MemoryTransport()
 
289
        instrumented_t = TransportLogger(t)
1394
290
        k1 = KnitVersionedFile('id', instrumented_t, create=True, delta=True)
1395
 
        self.assertEqual([('get', 'id.kndx',)], instrumented_t._activity)
 
291
        self.assertEqual([('id.kndx',)], instrumented_t._calls)
1396
292
        # add texts with no required ordering
1397
293
        k1.add_lines('base', [], ['text\n'])
1398
294
        k1.add_lines('base2', [], ['text2\n'])
1399
295
        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[:]
 
296
        instrumented_t._calls = []
1403
297
        # 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)
 
298
        results = list(k1.iter_lines_added_or_present_in_versions(['base2', 'base']))
 
299
        self.assertEqual([('id.knit', [(0, 87), (87, 89)])], instrumented_t._calls)
1409
300
        self.assertEqual(['text\n', 'text2\n'], results)
1410
301
 
1411
302
    def test_create_empty_annotated(self):
1421
312
        # this tests that a new knit index file has the expected content
1422
313
        # and that is writes the data we expect as records are added.
1423
314
        knit = self.make_test_knit(True)
1424
 
        # Now knit files are not created until we first add data to them
1425
315
        self.assertFileEqual("# bzr knit index 8\n", 'test.kndx')
1426
316
        knit.add_lines_with_ghosts('revid', ['a_ghost'], ['a\n'])
1427
317
        self.assertFileEqual(
1439
329
        knit = KnitVersionedFile('test', get_transport('.'), access_mode='r')
1440
330
        self.assertEqual(['revid', 'revid2'], knit.versions())
1441
331
        # write a short write to the file and ensure that its ignored
1442
 
        indexfile = file('test.kndx', 'ab')
 
332
        indexfile = file('test.kndx', 'at')
1443
333
        indexfile.write('\nrevid3 line-delta 166 82 1 2 3 4 5 .phwoar:demo ')
1444
334
        indexfile.close()
1445
335
        # we should be able to load this file again
1452
342
        self.assertEqual(['revid', 'revid2', 'revid3'], knit.versions())
1453
343
        self.assertEqual(['revid2'], knit.get_parents('revid3'))
1454
344
 
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
345
    def test_plan_merge(self):
1541
346
        my_knit = self.make_test_knit(annotate=True)
1542
347
        my_knit.add_lines('text1', [], split_lines(TEXT_1))
1546
351
        for plan_line, expected_line in zip(plan, AB_MERGE):
1547
352
            self.assertEqual(plan_line, expected_line)
1548
353
 
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
354
 
1828
355
TEXT_1 = """\
1829
356
Banana cup cakes:
1927
454
 
1928
455
class TestKnitCaching(KnitTests):
1929
456
    
1930
 
    def create_knit(self):
 
457
    def create_knit(self, cache_add=False):
1931
458
        k = self.make_test_knit(True)
 
459
        if cache_add:
 
460
            k.enable_cache()
 
461
 
1932
462
        k.add_lines('text-1', [], split_lines(TEXT_1))
1933
463
        k.add_lines('text-2', [], split_lines(TEXT_2))
1934
464
        return k
1938
468
        # Nothing should be cached without setting 'enable_cache'
1939
469
        self.assertEqual({}, k._data._cache)
1940
470
 
 
471
    def test_cache_add_and_clear(self):
 
472
        k = self.create_knit(True)
 
473
 
 
474
        self.assertEqual(['text-1', 'text-2'], sorted(k._data._cache.keys()))
 
475
 
 
476
        k.clear_cache()
 
477
        self.assertEqual({}, k._data._cache)
 
478
 
1941
479
    def test_cache_data_read_raw(self):
1942
480
        k = self.create_knit()
1943
481
 
1946
484
 
1947
485
        def read_one_raw(version):
1948
486
            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)]))
 
487
            method, pos, size, next = pos_map[version]
 
488
            lst = list(k._data.read_records_iter_raw([(version, pos, size)]))
1951
489
            self.assertEqual(1, len(lst))
1952
490
            return lst[0]
1953
491
 
1967
505
 
1968
506
        def read_one(version):
1969
507
            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)]))
 
508
            method, pos, size, next = pos_map[version]
 
509
            lst = list(k._data.read_records_iter([(version, pos, size)]))
1972
510
            self.assertEqual(1, len(lst))
1973
511
            return lst[0]
1974
512
 
2004
542
        text = k.get_text('text-1')
2005
543
        self.assertEqual(TEXT_1, text)
2006
544
        self.assertEqual({}, k._data._cache)
2007
 
 
2008
 
 
2009
 
class TestKnitIndex(KnitTests):
2010
 
 
2011
 
    def test_add_versions_dictionary_compresses(self):
2012
 
        """Adding versions to the index should update the lookup dict"""
2013
 
        knit = self.make_test_knit()
2014
 
        idx = knit._index
2015
 
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
2016
 
        self.check_file_contents('test.kndx',
2017
 
            '# bzr knit index 8\n'
2018
 
            '\n'
2019
 
            'a-1 fulltext 0 0  :'
2020
 
            )
2021
 
        idx.add_versions([('a-2', ['fulltext'], (None, 0, 0), ['a-1']),
2022
 
                          ('a-3', ['fulltext'], (None, 0, 0), ['a-2']),
2023
 
                         ])
2024
 
        self.check_file_contents('test.kndx',
2025
 
            '# bzr knit index 8\n'
2026
 
            '\n'
2027
 
            'a-1 fulltext 0 0  :\n'
2028
 
            'a-2 fulltext 0 0 0 :\n'
2029
 
            'a-3 fulltext 0 0 1 :'
2030
 
            )
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)
2036
 
 
2037
 
    def test_add_versions_fails_clean(self):
2038
 
        """If add_versions fails in the middle, it restores a pristine state.
2039
 
 
2040
 
        Any modifications that are made to the index are reset if all versions
2041
 
        cannot be added.
2042
 
        """
2043
 
        # This cheats a little bit by passing in a generator which will
2044
 
        # raise an exception before the processing finishes
2045
 
        # Other possibilities would be to have an version with the wrong number
2046
 
        # of entries, or to make the backing transport unable to write any
2047
 
        # files.
2048
 
 
2049
 
        knit = self.make_test_knit()
2050
 
        idx = knit._index
2051
 
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
2052
 
 
2053
 
        class StopEarly(Exception):
2054
 
            pass
2055
 
 
2056
 
        def generate_failure():
2057
 
            """Add some entries and then raise an exception"""
2058
 
            yield ('a-2', ['fulltext'], (None, 0, 0), ['a-1'])
2059
 
            yield ('a-3', ['fulltext'], (None, 0, 0), ['a-2'])
2060
 
            raise StopEarly()
2061
 
 
2062
 
        # Assert the pre-condition
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
 
 
2068
 
        # And it shouldn't be modified
2069
 
        self.assertEqual(['a-1'], idx._history)
2070
 
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, [], 0)}, idx._cache)
2071
 
 
2072
 
    def test_knit_index_ignores_empty_files(self):
2073
 
        # There was a race condition in older bzr, where a ^C at the right time
2074
 
        # could leave an empty .kndx file, which bzr would later claim was a
2075
 
        # corrupted file since the header was not present. In reality, the file
2076
 
        # just wasn't created, so it should be ignored.
2077
 
        t = get_transport('.')
2078
 
        t.put_bytes('test.kndx', '')
2079
 
 
2080
 
        knit = self.make_test_knit()
2081
 
 
2082
 
    def test_knit_index_checks_header(self):
2083
 
        t = get_transport('.')
2084
 
        t.put_bytes('test.kndx', '# not really a knit header\n\n')
2085
 
 
2086
 
        self.assertRaises(KnitHeaderError, self.make_test_knit)
2087
 
 
2088
 
 
2089
 
class TestGraphIndexKnit(KnitTests):
2090
 
    """Tests for knits using a GraphIndex rather than a KnitIndex."""
2091
 
 
2092
 
    def make_g_index(self, name, ref_lists=0, nodes=[]):
2093
 
        builder = GraphIndexBuilder(ref_lists)
2094
 
        for node, references, value in nodes:
2095
 
            builder.add_node(node, references, value)
2096
 
        stream = builder.finish()
2097
 
        trans = self.get_transport()
2098
 
        size = trans.put_file(name, stream)
2099
 
        return GraphIndex(trans, name, size)
2100
 
 
2101
 
    def two_graph_index(self, deltas=False, catch_adds=False):
2102
 
        """Build a two-graph index.
2103
 
 
2104
 
        :param deltas: If true, use underlying indices with two node-ref
2105
 
            lists and 'parent' set to a delta-compressed against tail.
2106
 
        """
2107
 
        # build a complex graph across several indices.
2108
 
        if deltas:
2109
 
            # delta compression inn the index
2110
 
            index1 = self.make_g_index('1', 2, [
2111
 
                (('tip', ), 'N0 100', ([('parent', )], [], )),
2112
 
                (('tail', ), '', ([], []))])
2113
 
            index2 = self.make_g_index('2', 2, [
2114
 
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], [('tail', )])),
2115
 
                (('separate', ), '', ([], []))])
2116
 
        else:
2117
 
            # just blob location and graph in the index.
2118
 
            index1 = self.make_g_index('1', 1, [
2119
 
                (('tip', ), 'N0 100', ([('parent', )], )),
2120
 
                (('tail', ), '', ([], ))])
2121
 
            index2 = self.make_g_index('2', 1, [
2122
 
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], )),
2123
 
                (('separate', ), '', ([], ))])
2124
 
        combined_index = CombinedGraphIndex([index1, index2])
2125
 
        if catch_adds:
2126
 
            self.combined_index = combined_index
2127
 
            self.caught_entries = []
2128
 
            add_callback = self.catch_add
2129
 
        else:
2130
 
            add_callback = None
2131
 
        return KnitGraphIndex(combined_index, deltas=deltas,
2132
 
            add_callback=add_callback)
2133
 
 
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'))
2204
 
 
2205
 
    def test_get_position(self):
2206
 
        index = self.two_graph_index()
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'))
2209
 
 
2210
 
    def test_get_method_deltas(self):
2211
 
        index = self.two_graph_index(deltas=True)
2212
 
        self.assertEqual('fulltext', index.get_method('tip'))
2213
 
        self.assertEqual('line-delta', index.get_method('parent'))
2214
 
 
2215
 
    def test_get_method_no_deltas(self):
2216
 
        # check that the parent-history lookup is ignored with deltas=False.
2217
 
        index = self.two_graph_index(deltas=False)
2218
 
        self.assertEqual('fulltext', index.get_method('tip'))
2219
 
        self.assertEqual('fulltext', index.get_method('parent'))
2220
 
 
2221
 
    def test_get_options_deltas(self):
2222
 
        index = self.two_graph_index(deltas=True)
2223
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
2224
 
        self.assertEqual(['line-delta'], index.get_options('parent'))
2225
 
 
2226
 
    def test_get_options_no_deltas(self):
2227
 
        # check that the parent-history lookup is ignored with deltas=False.
2228
 
        index = self.two_graph_index(deltas=False)
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'])
2255
 
 
2256
 
    def catch_add(self, entries):
2257
 
        self.caught_entries.append(entries)
2258
 
 
2259
 
    def test_add_no_callback_errors(self):
2260
 
        index = self.two_graph_index()
2261
 
        self.assertRaises(errors.ReadOnlyError, index.add_version,
2262
 
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
2263
 
 
2264
 
    def test_add_version_smoke(self):
2265
 
        index = self.two_graph_index(catch_adds=True)
2266
 
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
2267
 
        self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
2268
 
            self.caught_entries)
2269
 
 
2270
 
    def test_add_version_delta_not_delta_index(self):
2271
 
        index = self.two_graph_index(catch_adds=True)
2272
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2273
 
            'new', 'no-eol,line-delta', (None, 0, 100), ['parent'])
2274
 
        self.assertEqual([], self.caught_entries)
2275
 
 
2276
 
    def test_add_version_same_dup(self):
2277
 
        index = self.two_graph_index(catch_adds=True)
2278
 
        # options can be spelt two different ways
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)
2283
 
        
2284
 
    def test_add_version_different_dup(self):
2285
 
        index = self.two_graph_index(deltas=True, catch_adds=True)
2286
 
        # change options
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'])
2298
 
        # parents
2299
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2300
 
            'tip', 'fulltext,no-eol', (None, 0, 100), [])
2301
 
        self.assertEqual([], self.caught_entries)
2302
 
        
2303
 
    def test_add_versions_nodeltas(self):
2304
 
        index = self.two_graph_index(catch_adds=True)
2305
 
        index.add_versions([
2306
 
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
2307
 
                ('new2', 'fulltext', (None, 0, 6), ['new']),
2308
 
                ])
2309
 
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
2310
 
            (('new2', ), ' 0 6', ((('new',),),))],
2311
 
            sorted(self.caught_entries[0]))
2312
 
        self.assertEqual(1, len(self.caught_entries))
2313
 
 
2314
 
    def test_add_versions_deltas(self):
2315
 
        index = self.two_graph_index(deltas=True, catch_adds=True)
2316
 
        index.add_versions([
2317
 
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
2318
 
                ('new2', 'line-delta', (None, 0, 6), ['new']),
2319
 
                ])
2320
 
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
2321
 
            (('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
2322
 
            sorted(self.caught_entries[0]))
2323
 
        self.assertEqual(1, len(self.caught_entries))
2324
 
 
2325
 
    def test_add_versions_delta_not_delta_index(self):
2326
 
        index = self.two_graph_index(catch_adds=True)
2327
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2328
 
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
2329
 
        self.assertEqual([], self.caught_entries)
2330
 
 
2331
 
    def test_add_versions_random_id_accepted(self):
2332
 
        index = self.two_graph_index(catch_adds=True)
2333
 
        index.add_versions([], random_id=True)
2334
 
 
2335
 
    def test_add_versions_same_dup(self):
2336
 
        index = self.two_graph_index(catch_adds=True)
2337
 
        # options can be spelt two different ways
2338
 
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
2339
 
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
2340
 
        # but neither should have added data.
2341
 
        self.assertEqual([[], []], self.caught_entries)
2342
 
        
2343
 
    def test_add_versions_different_dup(self):
2344
 
        index = self.two_graph_index(deltas=True, catch_adds=True)
2345
 
        # change options
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'])])
2357
 
        # parents
2358
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2359
 
            [('tip', 'fulltext,no-eol', (None, 0, 100), [])])
2360
 
        # change options in the second record
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'])])
2364
 
        self.assertEqual([], self.caught_entries)
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
 
 
2399
 
 
2400
 
class TestNoParentsGraphIndexKnit(KnitTests):
2401
 
    """Tests for knits using KnitGraphIndex with no parents."""
2402
 
 
2403
 
    def make_g_index(self, name, ref_lists=0, nodes=[]):
2404
 
        builder = GraphIndexBuilder(ref_lists)
2405
 
        for node, references in nodes:
2406
 
            builder.add_node(node, references)
2407
 
        stream = builder.finish()
2408
 
        trans = self.get_transport()
2409
 
        size = trans.put_file(name, stream)
2410
 
        return GraphIndex(trans, name, size)
2411
 
 
2412
 
    def test_parents_deltas_incompatible(self):
2413
 
        index = CombinedGraphIndex([])
2414
 
        self.assertRaises(errors.KnitError, KnitGraphIndex, index,
2415
 
            deltas=True, parents=False)
2416
 
 
2417
 
    def two_graph_index(self, catch_adds=False):
2418
 
        """Build a two-graph index.
2419
 
 
2420
 
        :param deltas: If true, use underlying indices with two node-ref
2421
 
            lists and 'parent' set to a delta-compressed against tail.
2422
 
        """
2423
 
        # put several versions in the index.
2424
 
        index1 = self.make_g_index('1', 0, [
2425
 
            (('tip', ), 'N0 100'),
2426
 
            (('tail', ), '')])
2427
 
        index2 = self.make_g_index('2', 0, [
2428
 
            (('parent', ), ' 100 78'),
2429
 
            (('separate', ), '')])
2430
 
        combined_index = CombinedGraphIndex([index1, index2])
2431
 
        if catch_adds:
2432
 
            self.combined_index = combined_index
2433
 
            self.caught_entries = []
2434
 
            add_callback = self.catch_add
2435
 
        else:
2436
 
            add_callback = None
2437
 
        return KnitGraphIndex(combined_index, parents=False,
2438
 
            add_callback=add_callback)
2439
 
 
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'))
2491
 
 
2492
 
    def test_get_position(self):
2493
 
        index = self.two_graph_index()
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'))
2496
 
 
2497
 
    def test_get_method(self):
2498
 
        index = self.two_graph_index()
2499
 
        self.assertEqual('fulltext', index.get_method('tip'))
2500
 
        self.assertEqual(['fulltext'], index.get_options('parent'))
2501
 
 
2502
 
    def test_get_options(self):
2503
 
        index = self.two_graph_index()
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'])
2528
 
 
2529
 
    def catch_add(self, entries):
2530
 
        self.caught_entries.append(entries)
2531
 
 
2532
 
    def test_add_no_callback_errors(self):
2533
 
        index = self.two_graph_index()
2534
 
        self.assertRaises(errors.ReadOnlyError, index.add_version,
2535
 
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
2536
 
 
2537
 
    def test_add_version_smoke(self):
2538
 
        index = self.two_graph_index(catch_adds=True)
2539
 
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), [])
2540
 
        self.assertEqual([[(('new', ), 'N50 60')]],
2541
 
            self.caught_entries)
2542
 
 
2543
 
    def test_add_version_delta_not_delta_index(self):
2544
 
        index = self.two_graph_index(catch_adds=True)
2545
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2546
 
            'new', 'no-eol,line-delta', (None, 0, 100), [])
2547
 
        self.assertEqual([], self.caught_entries)
2548
 
 
2549
 
    def test_add_version_same_dup(self):
2550
 
        index = self.two_graph_index(catch_adds=True)
2551
 
        # options can be spelt two different ways
2552
 
        index.add_version('tip', 'fulltext,no-eol', (None, 0, 100), [])
2553
 
        index.add_version('tip', 'no-eol,fulltext', (None, 0, 100), [])
2554
 
        # but neither should have added data.
2555
 
        self.assertEqual([[], []], self.caught_entries)
2556
 
        
2557
 
    def test_add_version_different_dup(self):
2558
 
        index = self.two_graph_index(catch_adds=True)
2559
 
        # change options
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), [])
2571
 
        # parents
2572
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2573
 
            'tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])
2574
 
        self.assertEqual([], self.caught_entries)
2575
 
        
2576
 
    def test_add_versions(self):
2577
 
        index = self.two_graph_index(catch_adds=True)
2578
 
        index.add_versions([
2579
 
                ('new', 'fulltext,no-eol', (None, 50, 60), []),
2580
 
                ('new2', 'fulltext', (None, 0, 6), []),
2581
 
                ])
2582
 
        self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
2583
 
            sorted(self.caught_entries[0]))
2584
 
        self.assertEqual(1, len(self.caught_entries))
2585
 
 
2586
 
    def test_add_versions_delta_not_delta_index(self):
2587
 
        index = self.two_graph_index(catch_adds=True)
2588
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2589
 
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
2590
 
        self.assertEqual([], self.caught_entries)
2591
 
 
2592
 
    def test_add_versions_parents_not_parents_index(self):
2593
 
        index = self.two_graph_index(catch_adds=True)
2594
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2595
 
            [('new', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
2596
 
        self.assertEqual([], self.caught_entries)
2597
 
 
2598
 
    def test_add_versions_random_id_accepted(self):
2599
 
        index = self.two_graph_index(catch_adds=True)
2600
 
        index.add_versions([], random_id=True)
2601
 
 
2602
 
    def test_add_versions_same_dup(self):
2603
 
        index = self.two_graph_index(catch_adds=True)
2604
 
        # options can be spelt two different ways
2605
 
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), [])])
2606
 
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), [])])
2607
 
        # but neither should have added data.
2608
 
        self.assertEqual([[], []], self.caught_entries)
2609
 
        
2610
 
    def test_add_versions_different_dup(self):
2611
 
        index = self.two_graph_index(catch_adds=True)
2612
 
        # change options
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), [])])
2624
 
        # parents
2625
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2626
 
            [('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
2627
 
        # change options in the second record
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), [])])
2631
 
        self.assertEqual([], self.caught_entries)
2632
 
 
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([])))