~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_knit.py

  • Committer: John Arbash Meinel
  • Date: 2007-05-04 18:59:36 UTC
  • mto: This revision was merged to the branch mainline in revision 2643.
  • Revision ID: john@arbash-meinel.com-20070504185936-1mjdoqmtz74xe5mg
A C implementation of _fields_to_entry_0_parents drops the time from 400ms to 330ms for a 21k-entry tree

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 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
20
20
import difflib
21
21
import gzip
22
22
import sha
23
 
import sys
24
23
 
25
24
from bzrlib import (
26
25
    errors,
27
 
    generate_ids,
28
 
    knit,
29
 
    pack,
30
26
    )
31
27
from bzrlib.errors import (
32
28
    RevisionAlreadyPresent,
34
30
    RevisionNotPresent,
35
31
    NoSuchFile,
36
32
    )
37
 
from bzrlib.index import *
38
33
from bzrlib.knit import (
39
 
    AnnotatedKnitContent,
40
34
    KnitContent,
41
 
    KnitGraphIndex,
42
35
    KnitVersionedFile,
43
36
    KnitPlainFactory,
44
37
    KnitAnnotateFactory,
45
 
    _KnitAccess,
46
38
    _KnitData,
47
39
    _KnitIndex,
48
 
    _PackAccess,
49
 
    PlainKnitContent,
50
 
    _StreamAccess,
51
 
    _StreamIndex,
52
40
    WeaveToKnit,
53
 
    KnitSequenceMatcher,
54
41
    )
55
42
from bzrlib.osutils import split_lines
56
 
from bzrlib.tests import (
57
 
    Feature,
58
 
    TestCase,
59
 
    TestCaseWithMemoryTransport,
60
 
    TestCaseWithTransport,
61
 
    )
62
 
from bzrlib.transport import get_transport
 
43
from bzrlib.tests import TestCase, TestCaseWithTransport
 
44
from bzrlib.transport import TransportLogger, get_transport
63
45
from bzrlib.transport.memory import MemoryTransport
64
 
from bzrlib.tuned_gzip import GzipFile
65
 
from bzrlib.util import bencode
66
46
from bzrlib.weave import Weave
67
47
 
68
48
 
69
 
class _CompiledKnitFeature(Feature):
70
 
 
71
 
    def _probe(self):
72
 
        try:
73
 
            import bzrlib._knit_load_data_c
74
 
        except ImportError:
75
 
            return False
76
 
        return True
77
 
 
78
 
    def feature_name(self):
79
 
        return 'bzrlib._knit_load_data_c'
80
 
 
81
 
CompiledKnitFeature = _CompiledKnitFeature()
82
 
 
83
 
 
84
 
class KnitContentTestsMixin(object):
 
49
class KnitContentTests(TestCase):
85
50
 
86
51
    def test_constructor(self):
87
 
        content = self._make_content([])
 
52
        content = KnitContent([])
88
53
 
89
54
    def test_text(self):
90
 
        content = self._make_content([])
 
55
        content = KnitContent([])
91
56
        self.assertEqual(content.text(), [])
92
57
 
93
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
 
58
        content = KnitContent([("origin1", "text1"), ("origin2", "text2")])
94
59
        self.assertEqual(content.text(), ["text1", "text2"])
95
60
 
 
61
    def test_annotate(self):
 
62
        content = KnitContent([])
 
63
        self.assertEqual(content.annotate(), [])
 
64
 
 
65
        content = KnitContent([("origin1", "text1"), ("origin2", "text2")])
 
66
        self.assertEqual(content.annotate(),
 
67
            [("origin1", "text1"), ("origin2", "text2")])
 
68
 
 
69
    def test_annotate_iter(self):
 
70
        content = KnitContent([])
 
71
        it = content.annotate_iter()
 
72
        self.assertRaises(StopIteration, it.next)
 
73
 
 
74
        content = KnitContent([("origin1", "text1"), ("origin2", "text2")])
 
75
        it = content.annotate_iter()
 
76
        self.assertEqual(it.next(), ("origin1", "text1"))
 
77
        self.assertEqual(it.next(), ("origin2", "text2"))
 
78
        self.assertRaises(StopIteration, it.next)
 
79
 
96
80
    def test_copy(self):
97
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
 
81
        content = KnitContent([("origin1", "text1"), ("origin2", "text2")])
98
82
        copy = content.copy()
99
 
        self.assertIsInstance(copy, content.__class__)
100
 
        self.assertEqual(copy.annotate(), content.annotate())
101
 
 
102
 
    def assertDerivedBlocksEqual(self, source, target, noeol=False):
103
 
        """Assert that the derived matching blocks match real output"""
104
 
        source_lines = source.splitlines(True)
105
 
        target_lines = target.splitlines(True)
106
 
        def nl(line):
107
 
            if noeol and not line.endswith('\n'):
108
 
                return line + '\n'
109
 
            else:
110
 
                return line
111
 
        source_content = self._make_content([(None, nl(l)) for l in source_lines])
112
 
        target_content = self._make_content([(None, nl(l)) for l in target_lines])
113
 
        line_delta = source_content.line_delta(target_content)
114
 
        delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
115
 
            source_lines, target_lines))
116
 
        matcher = KnitSequenceMatcher(None, source_lines, target_lines)
117
 
        matcher_blocks = list(list(matcher.get_matching_blocks()))
118
 
        self.assertEqual(matcher_blocks, delta_blocks)
119
 
 
120
 
    def test_get_line_delta_blocks(self):
121
 
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'q\nc\n')
122
 
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1)
123
 
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1A)
124
 
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1B)
125
 
        self.assertDerivedBlocksEqual(TEXT_1B, TEXT_1A)
126
 
        self.assertDerivedBlocksEqual(TEXT_1A, TEXT_1B)
127
 
        self.assertDerivedBlocksEqual(TEXT_1A, '')
128
 
        self.assertDerivedBlocksEqual('', TEXT_1A)
129
 
        self.assertDerivedBlocksEqual('', '')
130
 
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd')
131
 
 
132
 
    def test_get_line_delta_blocks_noeol(self):
133
 
        """Handle historical knit deltas safely
134
 
 
135
 
        Some existing knit deltas don't consider the last line to differ
136
 
        when the only difference whether it has a final newline.
137
 
 
138
 
        New knit deltas appear to always consider the last line to differ
139
 
        in this case.
140
 
        """
141
 
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd\n', noeol=True)
142
 
        self.assertDerivedBlocksEqual('a\nb\nc\nd\n', 'a\nb\nc', noeol=True)
143
 
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'a\nb\nc', noeol=True)
144
 
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\n', noeol=True)
145
 
 
146
 
 
147
 
class TestPlainKnitContent(TestCase, KnitContentTestsMixin):
148
 
 
149
 
    def _make_content(self, lines):
150
 
        annotated_content = AnnotatedKnitContent(lines)
151
 
        return PlainKnitContent(annotated_content.text(), 'bogus')
152
 
 
153
 
    def test_annotate(self):
154
 
        content = self._make_content([])
155
 
        self.assertEqual(content.annotate(), [])
156
 
 
157
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
158
 
        self.assertEqual(content.annotate(),
159
 
            [("bogus", "text1"), ("bogus", "text2")])
160
 
 
161
 
    def test_annotate_iter(self):
162
 
        content = self._make_content([])
163
 
        it = content.annotate_iter()
164
 
        self.assertRaises(StopIteration, it.next)
165
 
 
166
 
        content = self._make_content([("bogus", "text1"), ("bogus", "text2")])
167
 
        it = content.annotate_iter()
168
 
        self.assertEqual(it.next(), ("bogus", "text1"))
169
 
        self.assertEqual(it.next(), ("bogus", "text2"))
170
 
        self.assertRaises(StopIteration, it.next)
171
 
 
172
 
    def test_line_delta(self):
173
 
        content1 = self._make_content([("", "a"), ("", "b")])
174
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
175
 
        self.assertEqual(content1.line_delta(content2),
176
 
            [(1, 2, 2, ["a", "c"])])
177
 
 
178
 
    def test_line_delta_iter(self):
179
 
        content1 = self._make_content([("", "a"), ("", "b")])
180
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
181
 
        it = content1.line_delta_iter(content2)
182
 
        self.assertEqual(it.next(), (1, 2, 2, ["a", "c"]))
183
 
        self.assertRaises(StopIteration, it.next)
184
 
 
185
 
 
186
 
class TestAnnotatedKnitContent(TestCase, KnitContentTestsMixin):
187
 
 
188
 
    def _make_content(self, lines):
189
 
        return AnnotatedKnitContent(lines)
190
 
 
191
 
    def test_annotate(self):
192
 
        content = self._make_content([])
193
 
        self.assertEqual(content.annotate(), [])
194
 
 
195
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
196
 
        self.assertEqual(content.annotate(),
 
83
        self.assertIsInstance(copy, KnitContent)
 
84
        self.assertEqual(copy.annotate(),
197
85
            [("origin1", "text1"), ("origin2", "text2")])
198
86
 
199
 
    def test_annotate_iter(self):
200
 
        content = self._make_content([])
201
 
        it = content.annotate_iter()
202
 
        self.assertRaises(StopIteration, it.next)
203
 
 
204
 
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
205
 
        it = content.annotate_iter()
206
 
        self.assertEqual(it.next(), ("origin1", "text1"))
207
 
        self.assertEqual(it.next(), ("origin2", "text2"))
208
 
        self.assertRaises(StopIteration, it.next)
209
 
 
210
87
    def test_line_delta(self):
211
 
        content1 = self._make_content([("", "a"), ("", "b")])
212
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
 
88
        content1 = KnitContent([("", "a"), ("", "b")])
 
89
        content2 = KnitContent([("", "a"), ("", "a"), ("", "c")])
213
90
        self.assertEqual(content1.line_delta(content2),
214
91
            [(1, 2, 2, [("", "a"), ("", "c")])])
215
92
 
216
93
    def test_line_delta_iter(self):
217
 
        content1 = self._make_content([("", "a"), ("", "b")])
218
 
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
 
94
        content1 = KnitContent([("", "a"), ("", "b")])
 
95
        content2 = KnitContent([("", "a"), ("", "a"), ("", "c")])
219
96
        it = content1.line_delta_iter(content2)
220
97
        self.assertEqual(it.next(), (1, 2, 2, [("", "a"), ("", "c")]))
221
98
        self.assertRaises(StopIteration, it.next)
247
124
        return queue_call
248
125
 
249
126
 
250
 
class KnitRecordAccessTestsMixin(object):
251
 
    """Tests for getting and putting knit records."""
252
 
 
253
 
    def assertAccessExists(self, access):
254
 
        """Ensure the data area for access has been initialised/exists."""
255
 
        raise NotImplementedError(self.assertAccessExists)
256
 
 
257
 
    def test_add_raw_records(self):
258
 
        """Add_raw_records adds records retrievable later."""
259
 
        access = self.get_access()
260
 
        memos = access.add_raw_records([10], '1234567890')
261
 
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
262
 
 
263
 
    def test_add_several_raw_records(self):
264
 
        """add_raw_records with many records and read some back."""
265
 
        access = self.get_access()
266
 
        memos = access.add_raw_records([10, 2, 5], '12345678901234567')
267
 
        self.assertEqual(['1234567890', '12', '34567'],
268
 
            list(access.get_raw_records(memos)))
269
 
        self.assertEqual(['1234567890'],
270
 
            list(access.get_raw_records(memos[0:1])))
271
 
        self.assertEqual(['12'],
272
 
            list(access.get_raw_records(memos[1:2])))
273
 
        self.assertEqual(['34567'],
274
 
            list(access.get_raw_records(memos[2:3])))
275
 
        self.assertEqual(['1234567890', '34567'],
276
 
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
277
 
 
278
 
    def test_create(self):
279
 
        """create() should make a file on disk."""
280
 
        access = self.get_access()
281
 
        access.create()
282
 
        self.assertAccessExists(access)
283
 
 
284
 
    def test_open_file(self):
285
 
        """open_file never errors."""
286
 
        access = self.get_access()
287
 
        access.open_file()
288
 
 
289
 
 
290
 
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
291
 
    """Tests for the .kndx implementation."""
292
 
 
293
 
    def assertAccessExists(self, access):
294
 
        self.assertNotEqual(None, access.open_file())
295
 
 
296
 
    def get_access(self):
297
 
        """Get a .knit style access instance."""
298
 
        access = _KnitAccess(self.get_transport(), "foo.knit", None, None,
299
 
            False, False)
300
 
        return access
301
 
    
302
 
 
303
 
class TestPackKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
304
 
    """Tests for the pack based access."""
305
 
 
306
 
    def assertAccessExists(self, access):
307
 
        # as pack based access has no backing unless an index maps data, this
308
 
        # is a no-op.
309
 
        pass
310
 
 
311
 
    def get_access(self):
312
 
        return self._get_access()[0]
313
 
 
314
 
    def _get_access(self, packname='packfile', index='FOO'):
315
 
        transport = self.get_transport()
316
 
        def write_data(bytes):
317
 
            transport.append_bytes(packname, bytes)
318
 
        writer = pack.ContainerWriter(write_data)
319
 
        writer.begin()
320
 
        indices = {index:(transport, packname)}
321
 
        access = _PackAccess(indices, writer=(writer, index))
322
 
        return access, writer
323
 
 
324
 
    def test_read_from_several_packs(self):
325
 
        access, writer = self._get_access()
326
 
        memos = []
327
 
        memos.extend(access.add_raw_records([10], '1234567890'))
328
 
        writer.end()
329
 
        access, writer = self._get_access('pack2', 'FOOBAR')
330
 
        memos.extend(access.add_raw_records([5], '12345'))
331
 
        writer.end()
332
 
        access, writer = self._get_access('pack3', 'BAZ')
333
 
        memos.extend(access.add_raw_records([5], 'alpha'))
334
 
        writer.end()
335
 
        transport = self.get_transport()
336
 
        access = _PackAccess({"FOO":(transport, 'packfile'),
337
 
            "FOOBAR":(transport, 'pack2'),
338
 
            "BAZ":(transport, 'pack3')})
339
 
        self.assertEqual(['1234567890', '12345', 'alpha'],
340
 
            list(access.get_raw_records(memos)))
341
 
        self.assertEqual(['1234567890'],
342
 
            list(access.get_raw_records(memos[0:1])))
343
 
        self.assertEqual(['12345'],
344
 
            list(access.get_raw_records(memos[1:2])))
345
 
        self.assertEqual(['alpha'],
346
 
            list(access.get_raw_records(memos[2:3])))
347
 
        self.assertEqual(['1234567890', 'alpha'],
348
 
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
349
 
 
350
 
    def test_set_writer(self):
351
 
        """The writer should be settable post construction."""
352
 
        access = _PackAccess({})
353
 
        transport = self.get_transport()
354
 
        packname = 'packfile'
355
 
        index = 'foo'
356
 
        def write_data(bytes):
357
 
            transport.append_bytes(packname, bytes)
358
 
        writer = pack.ContainerWriter(write_data)
359
 
        writer.begin()
360
 
        access.set_writer(writer, index, (transport, packname))
361
 
        memos = access.add_raw_records([10], '1234567890')
362
 
        writer.end()
363
 
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
364
 
 
365
 
 
366
127
class LowLevelKnitDataTests(TestCase):
367
128
 
368
129
    def create_gz_content(self, text):
380
141
                                        'end rev-id-1\n'
381
142
                                        % (sha1sum,))
382
143
        transport = MockTransport([gz_txt])
383
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
384
 
        data = _KnitData(access=access)
385
 
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
144
        data = _KnitData(transport, 'filename', mode='r')
 
145
        records = [('rev-id-1', 0, len(gz_txt))]
386
146
 
387
147
        contents = data.read_records(records)
388
148
        self.assertEqual({'rev-id-1':(['foo\n', 'bar\n'], sha1sum)}, contents)
398
158
                                        'end rev-id-1\n'
399
159
                                        % (sha1sum,))
400
160
        transport = MockTransport([gz_txt])
401
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
402
 
        data = _KnitData(access=access)
403
 
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
161
        data = _KnitData(transport, 'filename', mode='r')
 
162
        records = [('rev-id-1', 0, len(gz_txt))]
404
163
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
405
164
 
406
165
        # read_records_iter_raw won't detect that sort of mismatch/corruption
416
175
                                        'end rev-id-1\n'
417
176
                                        % (sha1sum,))
418
177
        transport = MockTransport([gz_txt])
419
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
420
 
        data = _KnitData(access=access)
421
 
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
178
        data = _KnitData(transport, 'filename', mode='r')
 
179
        records = [('rev-id-1', 0, len(gz_txt))]
422
180
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
423
181
 
424
182
        # read_records_iter_raw won't detect that sort of mismatch/corruption
433
191
                                        'end rev-id-1\n'
434
192
                                        % (sha1sum,))
435
193
        transport = MockTransport([gz_txt])
436
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
437
 
        data = _KnitData(access=access)
 
194
        data = _KnitData(transport, 'filename', mode='r')
438
195
        # We are asking for rev-id-2, but the data is rev-id-1
439
 
        records = [('rev-id-2', (None, 0, len(gz_txt)))]
 
196
        records = [('rev-id-2', 0, len(gz_txt))]
440
197
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
441
198
 
442
199
        # read_records_iter_raw will notice if we request the wrong version.
451
208
               'end rev-id-1\n'
452
209
               % (sha1sum,))
453
210
        transport = MockTransport([txt])
454
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
455
 
        data = _KnitData(access=access)
456
 
        records = [('rev-id-1', (None, 0, len(txt)))]
 
211
        data = _KnitData(transport, 'filename', mode='r')
 
212
        records = [('rev-id-1', 0, len(txt))]
457
213
 
458
214
        # We don't have valid gzip data ==> corrupt
459
215
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
472
228
        # Change 2 bytes in the middle to \xff
473
229
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
474
230
        transport = MockTransport([gz_txt])
475
 
        access = _KnitAccess(transport, 'filename', None, None, False, False)
476
 
        data = _KnitData(access=access)
477
 
        records = [('rev-id-1', (None, 0, len(gz_txt)))]
 
231
        data = _KnitData(transport, 'filename', mode='r')
 
232
        records = [('rev-id-1', 0, len(gz_txt))]
478
233
 
479
234
        self.assertRaises(errors.KnitCorrupt, data.read_records, records)
480
235
 
485
240
 
486
241
class LowLevelKnitIndexTests(TestCase):
487
242
 
488
 
    def get_knit_index(self, *args, **kwargs):
489
 
        orig = knit._load_data
490
 
        def reset():
491
 
            knit._load_data = orig
492
 
        self.addCleanup(reset)
493
 
        from bzrlib._knit_load_data_py import _load_data_py
494
 
        knit._load_data = _load_data_py
495
 
        return _KnitIndex(*args, **kwargs)
496
 
 
497
243
    def test_no_such_file(self):
498
244
        transport = MockTransport()
499
245
 
500
 
        self.assertRaises(NoSuchFile, self.get_knit_index,
501
 
                          transport, "filename", "r")
502
 
        self.assertRaises(NoSuchFile, self.get_knit_index,
503
 
                          transport, "filename", "w", create=False)
 
246
        self.assertRaises(NoSuchFile, _KnitIndex, transport, "filename", "r")
 
247
        self.assertRaises(NoSuchFile, _KnitIndex, transport,
 
248
            "filename", "w", create=False)
504
249
 
505
250
    def test_create_file(self):
506
251
        transport = MockTransport()
507
252
 
508
 
        index = self.get_knit_index(transport, "filename", "w",
 
253
        index = _KnitIndex(transport, "filename", "w",
509
254
            file_mode="wb", create=True)
510
255
        self.assertEqual(
511
256
                ("put_bytes_non_atomic",
515
260
    def test_delay_create_file(self):
516
261
        transport = MockTransport()
517
262
 
518
 
        index = self.get_knit_index(transport, "filename", "w",
 
263
        index = _KnitIndex(transport, "filename", "w",
519
264
            create=True, file_mode="wb", create_parent_dir=True,
520
265
            delay_create=True, dir_mode=0777)
521
266
        self.assertEqual([], transport.calls)
540
285
            _KnitIndex.HEADER,
541
286
            '%s option 0 1 :' % (utf8_revision_id,)
542
287
            ])
543
 
        index = self.get_knit_index(transport, "filename", "r")
 
288
        index = _KnitIndex(transport, "filename", "r")
544
289
        # _KnitIndex is a private class, and deals in utf8 revision_ids, not
545
290
        # Unicode revision_ids.
546
291
        self.assertTrue(index.has_version(utf8_revision_id))
553
298
            _KnitIndex.HEADER,
554
299
            "version option 0 1 .%s :" % (utf8_revision_id,)
555
300
            ])
556
 
        index = self.get_knit_index(transport, "filename", "r")
557
 
        self.assertEqual((utf8_revision_id,),
 
301
        index = _KnitIndex(transport, "filename", "r")
 
302
        self.assertEqual([utf8_revision_id],
558
303
            index.get_parents_with_ghosts("version"))
559
304
 
560
305
    def test_read_ignore_corrupted_lines(self):
564
309
            "corrupted options 0 1 .b .c ",
565
310
            "version options 0 1 :"
566
311
            ])
567
 
        index = self.get_knit_index(transport, "filename", "r")
 
312
        index = _KnitIndex(transport, "filename", "r")
568
313
        self.assertEqual(1, index.num_versions())
569
314
        self.assertTrue(index.has_version("version"))
570
315
 
571
316
    def test_read_corrupted_header(self):
572
317
        transport = MockTransport(['not a bzr knit index header\n'])
573
318
        self.assertRaises(KnitHeaderError,
574
 
            self.get_knit_index, transport, "filename", "r")
 
319
            _KnitIndex, transport, "filename", "r")
575
320
 
576
321
    def test_read_duplicate_entries(self):
577
322
        transport = MockTransport([
581
326
            "version options2 1 2 .other :",
582
327
            "version options3 3 4 0 .other :"
583
328
            ])
584
 
        index = self.get_knit_index(transport, "filename", "r")
 
329
        index = _KnitIndex(transport, "filename", "r")
585
330
        self.assertEqual(2, index.num_versions())
586
 
        # check that the index used is the first one written. (Specific
587
 
        # to KnitIndex style indices.
588
 
        self.assertEqual("1", index._version_list_to_index(["version"]))
589
 
        self.assertEqual((None, 3, 4), index.get_position("version"))
 
331
        self.assertEqual(1, index.lookup("version"))
 
332
        self.assertEqual((3, 4), index.get_position("version"))
590
333
        self.assertEqual(["options3"], index.get_options("version"))
591
 
        self.assertEqual(("parent", "other"),
 
334
        self.assertEqual(["parent", "other"],
592
335
            index.get_parents_with_ghosts("version"))
593
336
 
594
337
    def test_read_compressed_parents(self):
598
341
            "b option 0 1 0 :",
599
342
            "c option 0 1 1 0 :",
600
343
            ])
601
 
        index = self.get_knit_index(transport, "filename", "r")
602
 
        self.assertEqual({"b":("a",), "c":("b", "a")},
603
 
            index.get_parent_map(["b", "c"]))
 
344
        index = _KnitIndex(transport, "filename", "r")
 
345
        self.assertEqual(["a"], index.get_parents("b"))
 
346
        self.assertEqual(["b", "a"], index.get_parents("c"))
604
347
 
605
348
    def test_write_utf8_version_id(self):
606
349
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
608
351
        transport = MockTransport([
609
352
            _KnitIndex.HEADER
610
353
            ])
611
 
        index = self.get_knit_index(transport, "filename", "r")
612
 
        index.add_version(utf8_revision_id, ["option"], (None, 0, 1), [])
 
354
        index = _KnitIndex(transport, "filename", "r")
 
355
        index.add_version(utf8_revision_id, ["option"], 0, 1, [])
613
356
        self.assertEqual(("append_bytes", ("filename",
614
357
            "\n%s option 0 1  :" % (utf8_revision_id,)),
615
358
            {}),
621
364
        transport = MockTransport([
622
365
            _KnitIndex.HEADER
623
366
            ])
624
 
        index = self.get_knit_index(transport, "filename", "r")
625
 
        index.add_version("version", ["option"], (None, 0, 1), [utf8_revision_id])
 
367
        index = _KnitIndex(transport, "filename", "r")
 
368
        index.add_version("version", ["option"], 0, 1, [utf8_revision_id])
626
369
        self.assertEqual(("append_bytes", ("filename",
627
370
            "\nversion option 0 1 .%s :" % (utf8_revision_id,)),
628
371
            {}),
629
372
            transport.calls.pop(0))
630
373
 
 
374
    def test_get_graph(self):
 
375
        transport = MockTransport()
 
376
        index = _KnitIndex(transport, "filename", "w", create=True)
 
377
        self.assertEqual([], index.get_graph())
 
378
 
 
379
        index.add_version("a", ["option"], 0, 1, ["b"])
 
380
        self.assertEqual([("a", ["b"])], index.get_graph())
 
381
 
 
382
        index.add_version("c", ["option"], 0, 1, ["d"])
 
383
        self.assertEqual([("a", ["b"]), ("c", ["d"])],
 
384
            sorted(index.get_graph()))
 
385
 
631
386
    def test_get_ancestry(self):
632
387
        transport = MockTransport([
633
388
            _KnitIndex.HEADER,
636
391
            "c option 0 1 1 0 :",
637
392
            "d option 0 1 2 .f :"
638
393
            ])
639
 
        index = self.get_knit_index(transport, "filename", "r")
 
394
        index = _KnitIndex(transport, "filename", "r")
640
395
 
641
396
        self.assertEqual([], index.get_ancestry([]))
642
397
        self.assertEqual(["a"], index.get_ancestry(["a"]))
656
411
            "c option 0 1 0 .f .g :",
657
412
            "d option 0 1 2 .h .j .k :"
658
413
            ])
659
 
        index = self.get_knit_index(transport, "filename", "r")
 
414
        index = _KnitIndex(transport, "filename", "r")
660
415
 
661
416
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
662
417
        self.assertEqual(["a"], index.get_ancestry_with_ghosts(["a"]))
677
432
        self.assertRaises(RevisionNotPresent,
678
433
            index.get_ancestry_with_ghosts, ["e"])
679
434
 
680
 
    def test_iter_parents(self):
681
 
        transport = MockTransport()
682
 
        index = self.get_knit_index(transport, "filename", "w", create=True)
683
 
        # no parents
684
 
        index.add_version('r0', ['option'], (None, 0, 1), [])
685
 
        # 1 parent
686
 
        index.add_version('r1', ['option'], (None, 0, 1), ['r0'])
687
 
        # 2 parents
688
 
        index.add_version('r2', ['option'], (None, 0, 1), ['r1', 'r0'])
689
 
        # XXX TODO a ghost
690
 
        # cases: each sample data individually:
691
 
        self.assertEqual(set([('r0', ())]),
692
 
            set(index.iter_parents(['r0'])))
693
 
        self.assertEqual(set([('r1', ('r0', ))]),
694
 
            set(index.iter_parents(['r1'])))
695
 
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
696
 
            set(index.iter_parents(['r2'])))
697
 
        # no nodes returned for a missing node
698
 
        self.assertEqual(set(),
699
 
            set(index.iter_parents(['missing'])))
700
 
        # 1 node returned with missing nodes skipped
701
 
        self.assertEqual(set([('r1', ('r0', ))]),
702
 
            set(index.iter_parents(['ghost1', 'r1', 'ghost'])))
703
 
        # 2 nodes returned
704
 
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
705
 
            set(index.iter_parents(['r0', 'r1'])))
706
 
        # 2 nodes returned, missing skipped
707
 
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
708
 
            set(index.iter_parents(['a', 'r0', 'b', 'r1', 'c'])))
709
 
 
710
435
    def test_num_versions(self):
711
436
        transport = MockTransport([
712
437
            _KnitIndex.HEADER
713
438
            ])
714
 
        index = self.get_knit_index(transport, "filename", "r")
 
439
        index = _KnitIndex(transport, "filename", "r")
715
440
 
716
441
        self.assertEqual(0, index.num_versions())
717
442
        self.assertEqual(0, len(index))
718
443
 
719
 
        index.add_version("a", ["option"], (None, 0, 1), [])
720
 
        self.assertEqual(1, index.num_versions())
721
 
        self.assertEqual(1, len(index))
722
 
 
723
 
        index.add_version("a", ["option2"], (None, 1, 2), [])
724
 
        self.assertEqual(1, index.num_versions())
725
 
        self.assertEqual(1, len(index))
726
 
 
727
 
        index.add_version("b", ["option"], (None, 0, 1), [])
 
444
        index.add_version("a", ["option"], 0, 1, [])
 
445
        self.assertEqual(1, index.num_versions())
 
446
        self.assertEqual(1, len(index))
 
447
 
 
448
        index.add_version("a", ["option2"], 1, 2, [])
 
449
        self.assertEqual(1, index.num_versions())
 
450
        self.assertEqual(1, len(index))
 
451
 
 
452
        index.add_version("b", ["option"], 0, 1, [])
728
453
        self.assertEqual(2, index.num_versions())
729
454
        self.assertEqual(2, len(index))
730
455
 
732
457
        transport = MockTransport([
733
458
            _KnitIndex.HEADER
734
459
            ])
735
 
        index = self.get_knit_index(transport, "filename", "r")
 
460
        index = _KnitIndex(transport, "filename", "r")
736
461
 
737
462
        self.assertEqual([], index.get_versions())
738
463
 
739
 
        index.add_version("a", ["option"], (None, 0, 1), [])
740
 
        self.assertEqual(["a"], index.get_versions())
741
 
 
742
 
        index.add_version("a", ["option"], (None, 0, 1), [])
743
 
        self.assertEqual(["a"], index.get_versions())
744
 
 
745
 
        index.add_version("b", ["option"], (None, 0, 1), [])
 
464
        index.add_version("a", ["option"], 0, 1, [])
 
465
        self.assertEqual(["a"], index.get_versions())
 
466
 
 
467
        index.add_version("a", ["option"], 0, 1, [])
 
468
        self.assertEqual(["a"], index.get_versions())
 
469
 
 
470
        index.add_version("b", ["option"], 0, 1, [])
746
471
        self.assertEqual(["a", "b"], index.get_versions())
747
472
 
 
473
    def test_idx_to_name(self):
 
474
        transport = MockTransport([
 
475
            _KnitIndex.HEADER,
 
476
            "a option 0 1 :",
 
477
            "b option 0 1 :"
 
478
            ])
 
479
        index = _KnitIndex(transport, "filename", "r")
 
480
 
 
481
        self.assertEqual("a", index.idx_to_name(0))
 
482
        self.assertEqual("b", index.idx_to_name(1))
 
483
        self.assertEqual("b", index.idx_to_name(-1))
 
484
        self.assertEqual("a", index.idx_to_name(-2))
 
485
 
 
486
    def test_lookup(self):
 
487
        transport = MockTransport([
 
488
            _KnitIndex.HEADER,
 
489
            "a option 0 1 :",
 
490
            "b option 0 1 :"
 
491
            ])
 
492
        index = _KnitIndex(transport, "filename", "r")
 
493
 
 
494
        self.assertEqual(0, index.lookup("a"))
 
495
        self.assertEqual(1, index.lookup("b"))
 
496
 
748
497
    def test_add_version(self):
749
498
        transport = MockTransport([
750
499
            _KnitIndex.HEADER
751
500
            ])
752
 
        index = self.get_knit_index(transport, "filename", "r")
 
501
        index = _KnitIndex(transport, "filename", "r")
753
502
 
754
 
        index.add_version("a", ["option"], (None, 0, 1), ["b"])
 
503
        index.add_version("a", ["option"], 0, 1, ["b"])
755
504
        self.assertEqual(("append_bytes",
756
505
            ("filename", "\na option 0 1 .b :"),
757
506
            {}), transport.calls.pop(0))
758
507
        self.assertTrue(index.has_version("a"))
759
508
        self.assertEqual(1, index.num_versions())
760
 
        self.assertEqual((None, 0, 1), index.get_position("a"))
 
509
        self.assertEqual((0, 1), index.get_position("a"))
761
510
        self.assertEqual(["option"], index.get_options("a"))
762
 
        self.assertEqual(("b",), index.get_parents_with_ghosts("a"))
 
511
        self.assertEqual(["b"], index.get_parents_with_ghosts("a"))
763
512
 
764
 
        index.add_version("a", ["opt"], (None, 1, 2), ["c"])
 
513
        index.add_version("a", ["opt"], 1, 2, ["c"])
765
514
        self.assertEqual(("append_bytes",
766
515
            ("filename", "\na opt 1 2 .c :"),
767
516
            {}), transport.calls.pop(0))
768
517
        self.assertTrue(index.has_version("a"))
769
518
        self.assertEqual(1, index.num_versions())
770
 
        self.assertEqual((None, 1, 2), index.get_position("a"))
 
519
        self.assertEqual((1, 2), index.get_position("a"))
771
520
        self.assertEqual(["opt"], index.get_options("a"))
772
 
        self.assertEqual(("c",), index.get_parents_with_ghosts("a"))
 
521
        self.assertEqual(["c"], index.get_parents_with_ghosts("a"))
773
522
 
774
 
        index.add_version("b", ["option"], (None, 2, 3), ["a"])
 
523
        index.add_version("b", ["option"], 2, 3, ["a"])
775
524
        self.assertEqual(("append_bytes",
776
525
            ("filename", "\nb option 2 3 0 :"),
777
526
            {}), transport.calls.pop(0))
778
527
        self.assertTrue(index.has_version("b"))
779
528
        self.assertEqual(2, index.num_versions())
780
 
        self.assertEqual((None, 2, 3), index.get_position("b"))
 
529
        self.assertEqual((2, 3), index.get_position("b"))
781
530
        self.assertEqual(["option"], index.get_options("b"))
782
 
        self.assertEqual(("a",), index.get_parents_with_ghosts("b"))
 
531
        self.assertEqual(["a"], index.get_parents_with_ghosts("b"))
783
532
 
784
533
    def test_add_versions(self):
785
534
        transport = MockTransport([
786
535
            _KnitIndex.HEADER
787
536
            ])
788
 
        index = self.get_knit_index(transport, "filename", "r")
 
537
        index = _KnitIndex(transport, "filename", "r")
789
538
 
790
539
        index.add_versions([
791
 
            ("a", ["option"], (None, 0, 1), ["b"]),
792
 
            ("a", ["opt"], (None, 1, 2), ["c"]),
793
 
            ("b", ["option"], (None, 2, 3), ["a"])
 
540
            ("a", ["option"], 0, 1, ["b"]),
 
541
            ("a", ["opt"], 1, 2, ["c"]),
 
542
            ("b", ["option"], 2, 3, ["a"])
794
543
            ])
795
544
        self.assertEqual(("append_bytes", ("filename",
796
545
            "\na option 0 1 .b :"
800
549
        self.assertTrue(index.has_version("a"))
801
550
        self.assertTrue(index.has_version("b"))
802
551
        self.assertEqual(2, index.num_versions())
803
 
        self.assertEqual((None, 1, 2), index.get_position("a"))
804
 
        self.assertEqual((None, 2, 3), index.get_position("b"))
 
552
        self.assertEqual((1, 2), index.get_position("a"))
 
553
        self.assertEqual((2, 3), index.get_position("b"))
805
554
        self.assertEqual(["opt"], index.get_options("a"))
806
555
        self.assertEqual(["option"], index.get_options("b"))
807
 
        self.assertEqual(("c",), index.get_parents_with_ghosts("a"))
808
 
        self.assertEqual(("a",), index.get_parents_with_ghosts("b"))
809
 
 
810
 
    def test_add_versions_random_id_is_accepted(self):
811
 
        transport = MockTransport([
812
 
            _KnitIndex.HEADER
813
 
            ])
814
 
        index = self.get_knit_index(transport, "filename", "r")
815
 
 
816
 
        index.add_versions([
817
 
            ("a", ["option"], (None, 0, 1), ["b"]),
818
 
            ("a", ["opt"], (None, 1, 2), ["c"]),
819
 
            ("b", ["option"], (None, 2, 3), ["a"])
820
 
            ], random_id=True)
 
556
        self.assertEqual(["c"], index.get_parents_with_ghosts("a"))
 
557
        self.assertEqual(["a"], index.get_parents_with_ghosts("b"))
821
558
 
822
559
    def test_delay_create_and_add_versions(self):
823
560
        transport = MockTransport()
824
561
 
825
 
        index = self.get_knit_index(transport, "filename", "w",
 
562
        index = _KnitIndex(transport, "filename", "w",
826
563
            create=True, file_mode="wb", create_parent_dir=True,
827
564
            delay_create=True, dir_mode=0777)
828
565
        self.assertEqual([], transport.calls)
829
566
 
830
567
        index.add_versions([
831
 
            ("a", ["option"], (None, 0, 1), ["b"]),
832
 
            ("a", ["opt"], (None, 1, 2), ["c"]),
833
 
            ("b", ["option"], (None, 2, 3), ["a"])
 
568
            ("a", ["option"], 0, 1, ["b"]),
 
569
            ("a", ["opt"], 1, 2, ["c"]),
 
570
            ("b", ["option"], 2, 3, ["a"])
834
571
            ])
835
572
        name, (filename, f), kwargs = transport.calls.pop(0)
836
573
        self.assertEqual("put_file_non_atomic", name)
850
587
            _KnitIndex.HEADER,
851
588
            "a option 0 1 :"
852
589
            ])
853
 
        index = self.get_knit_index(transport, "filename", "r")
 
590
        index = _KnitIndex(transport, "filename", "r")
854
591
 
855
592
        self.assertTrue(index.has_version("a"))
856
593
        self.assertFalse(index.has_version("b"))
861
598
            "a option 0 1 :",
862
599
            "b option 1 2 :"
863
600
            ])
864
 
        index = self.get_knit_index(transport, "filename", "r")
 
601
        index = _KnitIndex(transport, "filename", "r")
865
602
 
866
 
        self.assertEqual((None, 0, 1), index.get_position("a"))
867
 
        self.assertEqual((None, 1, 2), index.get_position("b"))
 
603
        self.assertEqual((0, 1), index.get_position("a"))
 
604
        self.assertEqual((1, 2), index.get_position("b"))
868
605
 
869
606
    def test_get_method(self):
870
607
        transport = MockTransport([
873
610
            "b unknown,line-delta 1 2 :",
874
611
            "c bad 3 4 :"
875
612
            ])
876
 
        index = self.get_knit_index(transport, "filename", "r")
 
613
        index = _KnitIndex(transport, "filename", "r")
877
614
 
878
615
        self.assertEqual("fulltext", index.get_method("a"))
879
616
        self.assertEqual("line-delta", index.get_method("b"))
885
622
            "a opt1 0 1 :",
886
623
            "b opt2,opt3 1 2 :"
887
624
            ])
888
 
        index = self.get_knit_index(transport, "filename", "r")
 
625
        index = _KnitIndex(transport, "filename", "r")
889
626
 
890
627
        self.assertEqual(["opt1"], index.get_options("a"))
891
628
        self.assertEqual(["opt2", "opt3"], index.get_options("b"))
892
629
 
893
 
    def test_get_parent_map(self):
 
630
    def test_get_parents(self):
894
631
        transport = MockTransport([
895
632
            _KnitIndex.HEADER,
896
633
            "a option 0 1 :",
897
634
            "b option 1 2 0 .c :",
898
635
            "c option 1 2 1 0 .e :"
899
636
            ])
900
 
        index = self.get_knit_index(transport, "filename", "r")
 
637
        index = _KnitIndex(transport, "filename", "r")
901
638
 
902
 
        self.assertEqual({
903
 
            "a":(),
904
 
            "b":("a", "c"),
905
 
            "c":("b", "a", "e"),
906
 
            }, index.get_parent_map(["a", "b", "c"]))
 
639
        self.assertEqual([], index.get_parents("a"))
 
640
        self.assertEqual(["a", "c"], index.get_parents("b"))
 
641
        self.assertEqual(["b", "a"], index.get_parents("c"))
907
642
 
908
643
    def test_get_parents_with_ghosts(self):
909
644
        transport = MockTransport([
912
647
            "b option 1 2 0 .c :",
913
648
            "c option 1 2 1 0 .e :"
914
649
            ])
915
 
        index = self.get_knit_index(transport, "filename", "r")
 
650
        index = _KnitIndex(transport, "filename", "r")
916
651
 
917
 
        self.assertEqual((), index.get_parents_with_ghosts("a"))
918
 
        self.assertEqual(("a", "c"), index.get_parents_with_ghosts("b"))
919
 
        self.assertEqual(("b", "a", "e"),
 
652
        self.assertEqual([], index.get_parents_with_ghosts("a"))
 
653
        self.assertEqual(["a", "c"], index.get_parents_with_ghosts("b"))
 
654
        self.assertEqual(["b", "a", "e"],
920
655
            index.get_parents_with_ghosts("c"))
921
656
 
922
657
    def test_check_versions_present(self):
925
660
            "a option 0 1 :",
926
661
            "b option 0 1 :"
927
662
            ])
928
 
        index = self.get_knit_index(transport, "filename", "r")
 
663
        index = _KnitIndex(transport, "filename", "r")
929
664
 
930
665
        check = index.check_versions_present
931
666
 
936
671
        self.assertRaises(RevisionNotPresent, check, ["c"])
937
672
        self.assertRaises(RevisionNotPresent, check, ["a", "b", "c"])
938
673
 
939
 
    def test_impossible_parent(self):
940
 
        """Test we get KnitCorrupt if the parent couldn't possibly exist."""
941
 
        transport = MockTransport([
942
 
            _KnitIndex.HEADER,
943
 
            "a option 0 1 :",
944
 
            "b option 0 1 4 :"  # We don't have a 4th record
945
 
            ])
946
 
        try:
947
 
            self.assertRaises(errors.KnitCorrupt,
948
 
                              self.get_knit_index, transport, 'filename', 'r')
949
 
        except TypeError, e:
950
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
951
 
                           ' not exceptions.IndexError')
952
 
                and sys.version_info[0:2] >= (2,5)):
953
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
954
 
                                  ' raising new style exceptions with python'
955
 
                                  ' >=2.5')
956
 
            else:
957
 
                raise
958
 
 
959
 
    def test_corrupted_parent(self):
960
 
        transport = MockTransport([
961
 
            _KnitIndex.HEADER,
962
 
            "a option 0 1 :",
963
 
            "b option 0 1 :",
964
 
            "c option 0 1 1v :", # Can't have a parent of '1v'
965
 
            ])
966
 
        try:
967
 
            self.assertRaises(errors.KnitCorrupt,
968
 
                              self.get_knit_index, transport, 'filename', 'r')
969
 
        except TypeError, e:
970
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
971
 
                           ' not exceptions.ValueError')
972
 
                and sys.version_info[0:2] >= (2,5)):
973
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
974
 
                                  ' raising new style exceptions with python'
975
 
                                  ' >=2.5')
976
 
            else:
977
 
                raise
978
 
 
979
 
    def test_corrupted_parent_in_list(self):
980
 
        transport = MockTransport([
981
 
            _KnitIndex.HEADER,
982
 
            "a option 0 1 :",
983
 
            "b option 0 1 :",
984
 
            "c option 0 1 1 v :", # Can't have a parent of 'v'
985
 
            ])
986
 
        try:
987
 
            self.assertRaises(errors.KnitCorrupt,
988
 
                              self.get_knit_index, transport, 'filename', 'r')
989
 
        except TypeError, e:
990
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
991
 
                           ' not exceptions.ValueError')
992
 
                and sys.version_info[0:2] >= (2,5)):
993
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
994
 
                                  ' raising new style exceptions with python'
995
 
                                  ' >=2.5')
996
 
            else:
997
 
                raise
998
 
 
999
 
    def test_invalid_position(self):
1000
 
        transport = MockTransport([
1001
 
            _KnitIndex.HEADER,
1002
 
            "a option 1v 1 :",
1003
 
            ])
1004
 
        try:
1005
 
            self.assertRaises(errors.KnitCorrupt,
1006
 
                              self.get_knit_index, transport, 'filename', 'r')
1007
 
        except TypeError, e:
1008
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1009
 
                           ' not exceptions.ValueError')
1010
 
                and sys.version_info[0:2] >= (2,5)):
1011
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1012
 
                                  ' raising new style exceptions with python'
1013
 
                                  ' >=2.5')
1014
 
            else:
1015
 
                raise
1016
 
 
1017
 
    def test_invalid_size(self):
1018
 
        transport = MockTransport([
1019
 
            _KnitIndex.HEADER,
1020
 
            "a option 1 1v :",
1021
 
            ])
1022
 
        try:
1023
 
            self.assertRaises(errors.KnitCorrupt,
1024
 
                              self.get_knit_index, transport, 'filename', 'r')
1025
 
        except TypeError, e:
1026
 
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1027
 
                           ' not exceptions.ValueError')
1028
 
                and sys.version_info[0:2] >= (2,5)):
1029
 
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1030
 
                                  ' raising new style exceptions with python'
1031
 
                                  ' >=2.5')
1032
 
            else:
1033
 
                raise
1034
 
 
1035
 
    def test_short_line(self):
1036
 
        transport = MockTransport([
1037
 
            _KnitIndex.HEADER,
1038
 
            "a option 0 10  :",
1039
 
            "b option 10 10 0", # This line isn't terminated, ignored
1040
 
            ])
1041
 
        index = self.get_knit_index(transport, "filename", "r")
1042
 
        self.assertEqual(['a'], index.get_versions())
1043
 
 
1044
 
    def test_skip_incomplete_record(self):
1045
 
        # A line with bogus data should just be skipped
1046
 
        transport = MockTransport([
1047
 
            _KnitIndex.HEADER,
1048
 
            "a option 0 10  :",
1049
 
            "b option 10 10 0", # This line isn't terminated, ignored
1050
 
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
1051
 
            ])
1052
 
        index = self.get_knit_index(transport, "filename", "r")
1053
 
        self.assertEqual(['a', 'c'], index.get_versions())
1054
 
 
1055
 
    def test_trailing_characters(self):
1056
 
        # A line with bogus data should just be skipped
1057
 
        transport = MockTransport([
1058
 
            _KnitIndex.HEADER,
1059
 
            "a option 0 10  :",
1060
 
            "b option 10 10 0 :a", # This line has extra trailing characters
1061
 
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
1062
 
            ])
1063
 
        index = self.get_knit_index(transport, "filename", "r")
1064
 
        self.assertEqual(['a', 'c'], index.get_versions())
1065
 
 
1066
 
 
1067
 
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1068
 
 
1069
 
    _test_needs_features = [CompiledKnitFeature]
1070
 
 
1071
 
    def get_knit_index(self, *args, **kwargs):
1072
 
        orig = knit._load_data
1073
 
        def reset():
1074
 
            knit._load_data = orig
1075
 
        self.addCleanup(reset)
1076
 
        from bzrlib._knit_load_data_c import _load_data_c
1077
 
        knit._load_data = _load_data_c
1078
 
        return _KnitIndex(*args, **kwargs)
1079
 
 
1080
 
 
1081
674
 
1082
675
class KnitTests(TestCaseWithTransport):
1083
676
    """Class containing knit test helper routines."""
1084
677
 
1085
 
    def make_test_knit(self, annotate=False, delay_create=False, index=None,
1086
 
                       name='test'):
 
678
    def make_test_knit(self, annotate=False, delay_create=False):
1087
679
        if not annotate:
1088
680
            factory = KnitPlainFactory()
1089
681
        else:
1090
682
            factory = None
1091
 
        return KnitVersionedFile(name, get_transport('.'), access_mode='w',
 
683
        return KnitVersionedFile('test', get_transport('.'), access_mode='w',
1092
684
                                 factory=factory, create=True,
1093
 
                                 delay_create=delay_create, index=index)
1094
 
 
1095
 
    def assertRecordContentEqual(self, knit, version_id, candidate_content):
1096
 
        """Assert that some raw record content matches the raw record content
1097
 
        for a particular version_id in the given knit.
1098
 
        """
1099
 
        index_memo = knit._index.get_position(version_id)
1100
 
        record = (version_id, index_memo)
1101
 
        [(_, expected_content)] = list(knit._data.read_records_iter_raw([record]))
1102
 
        self.assertEqual(expected_content, candidate_content)
 
685
                                 delay_create=delay_create)
1103
686
 
1104
687
 
1105
688
class BasicKnitTests(KnitTests):
1112
695
        """Construct empty k"""
1113
696
        self.make_test_knit()
1114
697
 
1115
 
    def test_make_explicit_index(self):
1116
 
        """We can supply an index to use."""
1117
 
        knit = KnitVersionedFile('test', get_transport('.'),
1118
 
            index='strangelove')
1119
 
        self.assertEqual(knit._index, 'strangelove')
1120
 
 
1121
698
    def test_knit_add(self):
1122
699
        """Store one text in knit and retrieve"""
1123
700
        k = self.make_test_knit()
1125
702
        self.assertTrue(k.has_version('text-1'))
1126
703
        self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
1127
704
 
1128
 
    def test_newline_empty_lines(self):
1129
 
        # ensure that ["\n"] round trips ok.
1130
 
        knit = self.make_test_knit()
1131
 
        knit.add_lines('a', [], ["\n"])
1132
 
        knit.add_lines_with_ghosts('b', [], ["\n"])
1133
 
        self.assertEqual(["\n"], knit.get_lines('a'))
1134
 
        self.assertEqual(["\n"], knit.get_lines('b'))
1135
 
        self.assertEqual(['fulltext'], knit._index.get_options('a'))
1136
 
        self.assertEqual(['fulltext'], knit._index.get_options('b'))
1137
 
        knit.add_lines('c', ['a'], ["\n"])
1138
 
        knit.add_lines_with_ghosts('d', ['b'], ["\n"])
1139
 
        self.assertEqual(["\n"], knit.get_lines('c'))
1140
 
        self.assertEqual(["\n"], knit.get_lines('d'))
1141
 
        self.assertEqual(['line-delta'], knit._index.get_options('c'))
1142
 
        self.assertEqual(['line-delta'], knit._index.get_options('d'))
1143
 
 
1144
 
    def test_empty_lines(self):
1145
 
        # bizarrely, [] is not listed as having no-eol. 
1146
 
        knit = self.make_test_knit()
1147
 
        knit.add_lines('a', [], [])
1148
 
        knit.add_lines_with_ghosts('b', [], [])
1149
 
        self.assertEqual([], knit.get_lines('a'))
1150
 
        self.assertEqual([], knit.get_lines('b'))
1151
 
        self.assertEqual(['fulltext'], knit._index.get_options('a'))
1152
 
        self.assertEqual(['fulltext'], knit._index.get_options('b'))
1153
 
        knit.add_lines('c', ['a'], [])
1154
 
        knit.add_lines_with_ghosts('d', ['b'], [])
1155
 
        self.assertEqual([], knit.get_lines('c'))
1156
 
        self.assertEqual([], knit.get_lines('d'))
1157
 
        self.assertEqual(['line-delta'], knit._index.get_options('c'))
1158
 
        self.assertEqual(['line-delta'], knit._index.get_options('d'))
1159
 
 
1160
705
    def test_knit_reload(self):
1161
706
        # test that the content in a reloaded knit is correct
1162
707
        k = self.make_test_knit()
1211
756
        """Store in knit with parents"""
1212
757
        k = self.make_test_knit()
1213
758
        self.add_stock_one_and_one_a(k)
1214
 
        self.assertEqual({'text-1':(), 'text-1a':('text-1',)},
1215
 
            k.get_parent_map(['text-1', 'text-1a']))
 
759
        self.assertEquals(k.get_parents('text-1'), [])
 
760
        self.assertEquals(k.get_parents('text-1a'), ['text-1'])
1216
761
 
1217
762
    def test_ancestry(self):
1218
763
        """Store in knit with parents"""
1228
773
        k.clear_cache()
1229
774
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
1230
775
 
1231
 
    def test_add_delta_knit_graph_index(self):
1232
 
        """Does adding work with a KnitGraphIndex."""
1233
 
        index = InMemoryGraphIndex(2)
1234
 
        knit_index = KnitGraphIndex(index, add_callback=index.add_nodes,
1235
 
            deltas=True)
1236
 
        k = KnitVersionedFile('test', get_transport('.'),
1237
 
            delta=True, create=True, index=knit_index)
1238
 
        self.add_stock_one_and_one_a(k)
1239
 
        k.clear_cache()
1240
 
        self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
1241
 
        # check the index had the right data added.
1242
 
        self.assertEqual(set([
1243
 
            (index, ('text-1', ), ' 0 127', ((), ())),
1244
 
            (index, ('text-1a', ), ' 127 140', ((('text-1', ),), (('text-1', ),))),
1245
 
            ]), set(index.iter_all_entries()))
1246
 
        # we should not have a .kndx file
1247
 
        self.assertFalse(get_transport('.').has('test.kndx'))
1248
 
 
1249
776
    def test_annotate(self):
1250
777
        """Annotations"""
1251
778
        k = KnitVersionedFile('knit', get_transport('.'), factory=KnitAnnotateFactory(),
1340
867
        self.assertEquals(origins[1], ('text-1', 'b\n'))
1341
868
        self.assertEquals(origins[2], ('text-1', 'c\n'))
1342
869
 
1343
 
    def _test_join_with_factories(self, k1_factory, k2_factory):
1344
 
        k1 = KnitVersionedFile('test1', get_transport('.'), factory=k1_factory, create=True)
1345
 
        k1.add_lines('text-a', [], ['a1\n', 'a2\n', 'a3\n'])
1346
 
        k1.add_lines('text-b', ['text-a'], ['a1\n', 'b2\n', 'a3\n'])
1347
 
        k1.add_lines('text-c', [], ['c1\n', 'c2\n', 'c3\n'])
1348
 
        k1.add_lines('text-d', ['text-c'], ['c1\n', 'd2\n', 'd3\n'])
1349
 
        k1.add_lines('text-m', ['text-b', 'text-d'], ['a1\n', 'b2\n', 'd3\n'])
1350
 
        k2 = KnitVersionedFile('test2', get_transport('.'), factory=k2_factory, create=True)
 
870
    def test_knit_join(self):
 
871
        """Store in knit with parents"""
 
872
        k1 = KnitVersionedFile('test1', get_transport('.'), factory=KnitPlainFactory(), create=True)
 
873
        k1.add_lines('text-a', [], split_lines(TEXT_1))
 
874
        k1.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
 
875
 
 
876
        k1.add_lines('text-c', [], split_lines(TEXT_1))
 
877
        k1.add_lines('text-d', ['text-c'], split_lines(TEXT_1))
 
878
 
 
879
        k1.add_lines('text-m', ['text-b', 'text-d'], split_lines(TEXT_1))
 
880
 
 
881
        k2 = KnitVersionedFile('test2', get_transport('.'), factory=KnitPlainFactory(), create=True)
1351
882
        count = k2.join(k1, version_ids=['text-m'])
1352
883
        self.assertEquals(count, 5)
1353
884
        self.assertTrue(k2.has_version('text-a'))
1354
885
        self.assertTrue(k2.has_version('text-c'))
1355
 
        origins = k2.annotate('text-m')
1356
 
        self.assertEquals(origins[0], ('text-a', 'a1\n'))
1357
 
        self.assertEquals(origins[1], ('text-b', 'b2\n'))
1358
 
        self.assertEquals(origins[2], ('text-d', 'd3\n'))
1359
 
 
1360
 
    def test_knit_join_plain_to_plain(self):
1361
 
        """Test joining a plain knit with a plain knit."""
1362
 
        self._test_join_with_factories(KnitPlainFactory(), KnitPlainFactory())
1363
 
 
1364
 
    def test_knit_join_anno_to_anno(self):
1365
 
        """Test joining an annotated knit with an annotated knit."""
1366
 
        self._test_join_with_factories(None, None)
1367
 
 
1368
 
    def test_knit_join_anno_to_plain(self):
1369
 
        """Test joining an annotated knit with a plain knit."""
1370
 
        self._test_join_with_factories(None, KnitPlainFactory())
1371
 
 
1372
 
    def test_knit_join_plain_to_anno(self):
1373
 
        """Test joining a plain knit with an annotated knit."""
1374
 
        self._test_join_with_factories(KnitPlainFactory(), None)
1375
886
 
1376
887
    def test_reannotate(self):
1377
888
        k1 = KnitVersionedFile('knit1', get_transport('.'),
1415
926
        k1.get_texts(('%d' % t) for t in range(3))
1416
927
        
1417
928
    def test_iter_lines_reads_in_order(self):
1418
 
        instrumented_t = get_transport('trace+memory:///')
 
929
        t = MemoryTransport()
 
930
        instrumented_t = TransportLogger(t)
1419
931
        k1 = KnitVersionedFile('id', instrumented_t, create=True, delta=True)
1420
 
        self.assertEqual([('get', 'id.kndx',)], instrumented_t._activity)
 
932
        self.assertEqual([('id.kndx',)], instrumented_t._calls)
1421
933
        # add texts with no required ordering
1422
934
        k1.add_lines('base', [], ['text\n'])
1423
935
        k1.add_lines('base2', [], ['text2\n'])
1424
936
        k1.clear_cache()
1425
 
        # clear the logged activity, but preserve the list instance in case of
1426
 
        # clones pointing at it.
1427
 
        del instrumented_t._activity[:]
 
937
        instrumented_t._calls = []
1428
938
        # request a last-first iteration
1429
 
        results = list(k1.iter_lines_added_or_present_in_versions(
1430
 
            ['base2', 'base']))
1431
 
        self.assertEqual(
1432
 
            [('readv', 'id.knit', [(0, 87), (87, 89)], False, None)],
1433
 
            instrumented_t._activity)
1434
 
        self.assertEqual([('text\n', 'base'), ('text2\n', 'base2')], results)
 
939
        results = list(k1.iter_lines_added_or_present_in_versions(['base2', 'base']))
 
940
        self.assertEqual([('id.knit', [(0, 87), (87, 89)])], instrumented_t._calls)
 
941
        self.assertEqual(['text\n', 'text2\n'], results)
 
942
 
 
943
    def test_create_empty_annotated(self):
 
944
        k1 = self.make_test_knit(True)
 
945
        # 0
 
946
        k1.add_lines('text-a', [], ['a\n', 'b\n'])
 
947
        k2 = k1.create_empty('t', MemoryTransport())
 
948
        self.assertTrue(isinstance(k2.factory, KnitAnnotateFactory))
 
949
        self.assertEqual(k1.delta, k2.delta)
 
950
        # the generic test checks for empty content and file class
1435
951
 
1436
952
    def test_knit_format(self):
1437
953
        # this tests that a new knit index file has the expected content
1455
971
        knit = KnitVersionedFile('test', get_transport('.'), access_mode='r')
1456
972
        self.assertEqual(['revid', 'revid2'], knit.versions())
1457
973
        # write a short write to the file and ensure that its ignored
1458
 
        indexfile = file('test.kndx', 'ab')
 
974
        indexfile = file('test.kndx', 'at')
1459
975
        indexfile.write('\nrevid3 line-delta 166 82 1 2 3 4 5 .phwoar:demo ')
1460
976
        indexfile.close()
1461
977
        # we should be able to load this file again
1466
982
        # and when reading it revid3 should now appear.
1467
983
        knit = KnitVersionedFile('test', get_transport('.'), access_mode='r')
1468
984
        self.assertEqual(['revid', 'revid2', 'revid3'], knit.versions())
1469
 
        self.assertEqual({'revid3':('revid2',)}, knit.get_parent_map(['revid3']))
 
985
        self.assertEqual(['revid2'], knit.get_parents('revid3'))
1470
986
 
1471
987
    def test_delay_create(self):
1472
988
        """Test that passing delay_create=True creates files late"""
1562
1078
        for plan_line, expected_line in zip(plan, AB_MERGE):
1563
1079
            self.assertEqual(plan_line, expected_line)
1564
1080
 
1565
 
    def test_get_stream_empty(self):
1566
 
        """Get a data stream for an empty knit file."""
1567
 
        k1 = self.make_test_knit()
1568
 
        format, data_list, reader_callable = k1.get_data_stream([])
1569
 
        self.assertEqual('knit-plain', format)
1570
 
        self.assertEqual([], data_list)
1571
 
        content = reader_callable(None)
1572
 
        self.assertEqual('', content)
1573
 
        self.assertIsInstance(content, str)
1574
 
 
1575
 
    def test_get_stream_one_version(self):
1576
 
        """Get a data stream for a single record out of a knit containing just
1577
 
        one record.
1578
 
        """
1579
 
        k1 = self.make_test_knit()
1580
 
        test_data = [
1581
 
            ('text-a', [], TEXT_1),
1582
 
            ]
1583
 
        expected_data_list = [
1584
 
            # version, options, length, parents
1585
 
            ('text-a', ['fulltext'], 122, ()),
1586
 
           ]
1587
 
        for version_id, parents, lines in test_data:
1588
 
            k1.add_lines(version_id, parents, split_lines(lines))
1589
 
 
1590
 
        format, data_list, reader_callable = k1.get_data_stream(['text-a'])
1591
 
        self.assertEqual('knit-plain', format)
1592
 
        self.assertEqual(expected_data_list, data_list)
1593
 
        # There's only one record in the knit, so the content should be the
1594
 
        # entire knit data file's contents.
1595
 
        self.assertEqual(k1.transport.get_bytes(k1._data._access._filename),
1596
 
                         reader_callable(None))
1597
 
        
1598
 
    def test_get_stream_get_one_version_of_many(self):
1599
 
        """Get a data stream for just one version out of a knit containing many
1600
 
        versions.
1601
 
        """
1602
 
        k1 = self.make_test_knit()
1603
 
        # Insert the same data as test_knit_join, as they seem to cover a range
1604
 
        # of cases (no parents, one parent, multiple parents).
1605
 
        test_data = [
1606
 
            ('text-a', [], TEXT_1),
1607
 
            ('text-b', ['text-a'], TEXT_1),
1608
 
            ('text-c', [], TEXT_1),
1609
 
            ('text-d', ['text-c'], TEXT_1),
1610
 
            ('text-m', ['text-b', 'text-d'], TEXT_1),
1611
 
            ]
1612
 
        expected_data_list = [
1613
 
            # version, options, length, parents
1614
 
            ('text-m', ['line-delta'], 84, ('text-b', 'text-d')),
1615
 
            ]
1616
 
        for version_id, parents, lines in test_data:
1617
 
            k1.add_lines(version_id, parents, split_lines(lines))
1618
 
 
1619
 
        format, data_list, reader_callable = k1.get_data_stream(['text-m'])
1620
 
        self.assertEqual('knit-plain', format)
1621
 
        self.assertEqual(expected_data_list, data_list)
1622
 
        self.assertRecordContentEqual(k1, 'text-m', reader_callable(None))
1623
 
        
1624
 
    def test_get_data_stream_unordered_index(self):
1625
 
        """Get a data stream when the knit index reports versions out of order.
1626
 
 
1627
 
        https://bugs.launchpad.net/bzr/+bug/164637
1628
 
        """
1629
 
        k1 = self.make_test_knit()
1630
 
        test_data = [
1631
 
            ('text-a', [], TEXT_1),
1632
 
            ('text-b', ['text-a'], TEXT_1),
1633
 
            ('text-c', [], TEXT_1),
1634
 
            ('text-d', ['text-c'], TEXT_1),
1635
 
            ('text-m', ['text-b', 'text-d'], TEXT_1),
1636
 
            ]
1637
 
        for version_id, parents, lines in test_data:
1638
 
            k1.add_lines(version_id, parents, split_lines(lines))
1639
 
        # monkey-patch versions method to return out of order, as if coming
1640
 
        # from multiple independently indexed packs
1641
 
        original_versions = k1.versions
1642
 
        k1.versions = lambda: reversed(original_versions())
1643
 
        expected_data_list = [
1644
 
            ('text-a', ['fulltext'], 122, ()),
1645
 
            ('text-b', ['line-delta'], 84, ('text-a',))]
1646
 
        # now check the fulltext is first and the delta second
1647
 
        format, data_list, _ = k1.get_data_stream(['text-a', 'text-b'])
1648
 
        self.assertEqual('knit-plain', format)
1649
 
        self.assertEqual(expected_data_list, data_list)
1650
 
        # and that's true if we ask for them in the opposite order too
1651
 
        format, data_list, _ = k1.get_data_stream(['text-b', 'text-a'])
1652
 
        self.assertEqual(expected_data_list, data_list)
1653
 
        # also try requesting more versions
1654
 
        format, data_list, _ = k1.get_data_stream([
1655
 
            'text-m', 'text-b', 'text-a'])
1656
 
        self.assertEqual([
1657
 
            ('text-a', ['fulltext'], 122, ()),
1658
 
            ('text-b', ['line-delta'], 84, ('text-a',)),
1659
 
            ('text-m', ['line-delta'], 84, ('text-b', 'text-d')),
1660
 
            ], data_list)
1661
 
 
1662
 
    def test_get_stream_ghost_parent(self):
1663
 
        """Get a data stream for a version with a ghost parent."""
1664
 
        k1 = self.make_test_knit()
1665
 
        # Test data
1666
 
        k1.add_lines('text-a', [], split_lines(TEXT_1))
1667
 
        k1.add_lines_with_ghosts('text-b', ['text-a', 'text-ghost'],
1668
 
                                 split_lines(TEXT_1))
1669
 
        # Expected data
1670
 
        expected_data_list = [
1671
 
            # version, options, length, parents
1672
 
            ('text-b', ['line-delta'], 84, ('text-a', 'text-ghost')),
1673
 
            ]
1674
 
        
1675
 
        format, data_list, reader_callable = k1.get_data_stream(['text-b'])
1676
 
        self.assertEqual('knit-plain', format)
1677
 
        self.assertEqual(expected_data_list, data_list)
1678
 
        self.assertRecordContentEqual(k1, 'text-b', reader_callable(None))
1679
 
    
1680
 
    def test_get_stream_get_multiple_records(self):
1681
 
        """Get a stream for multiple records of a knit."""
1682
 
        k1 = self.make_test_knit()
1683
 
        # Insert the same data as test_knit_join, as they seem to cover a range
1684
 
        # of cases (no parents, one parent, multiple parents).
1685
 
        test_data = [
1686
 
            ('text-a', [], TEXT_1),
1687
 
            ('text-b', ['text-a'], TEXT_1),
1688
 
            ('text-c', [], TEXT_1),
1689
 
            ('text-d', ['text-c'], TEXT_1),
1690
 
            ('text-m', ['text-b', 'text-d'], TEXT_1),
1691
 
            ]
1692
 
        for version_id, parents, lines in test_data:
1693
 
            k1.add_lines(version_id, parents, split_lines(lines))
1694
 
 
1695
 
        # This test is actually a bit strict as the order in which they're
1696
 
        # returned is not defined.  This matches the current (deterministic)
1697
 
        # behaviour.
1698
 
        expected_data_list = [
1699
 
            # version, options, length, parents
1700
 
            ('text-d', ['line-delta'], 84, ('text-c',)),
1701
 
            ('text-b', ['line-delta'], 84, ('text-a',)),
1702
 
            ]
1703
 
        # Note that even though we request the revision IDs in a particular
1704
 
        # order, the data stream may return them in any order it likes.  In this
1705
 
        # case, they'll be in the order they were inserted into the knit.
1706
 
        format, data_list, reader_callable = k1.get_data_stream(
1707
 
            ['text-d', 'text-b'])
1708
 
        self.assertEqual('knit-plain', format)
1709
 
        self.assertEqual(expected_data_list, data_list)
1710
 
        # must match order they're returned
1711
 
        self.assertRecordContentEqual(k1, 'text-d', reader_callable(84))
1712
 
        self.assertRecordContentEqual(k1, 'text-b', reader_callable(84))
1713
 
        self.assertEqual('', reader_callable(None),
1714
 
                         "There should be no more bytes left to read.")
1715
 
 
1716
 
    def test_get_stream_all(self):
1717
 
        """Get a data stream for all the records in a knit.
1718
 
 
1719
 
        This exercises fulltext records, line-delta records, records with
1720
 
        various numbers of parents, and reading multiple records out of the
1721
 
        callable.  These cases ought to all be exercised individually by the
1722
 
        other test_get_stream_* tests; this test is basically just paranoia.
1723
 
        """
1724
 
        k1 = self.make_test_knit()
1725
 
        # Insert the same data as test_knit_join, as they seem to cover a range
1726
 
        # of cases (no parents, one parent, multiple parents).
1727
 
        test_data = [
1728
 
            ('text-a', [], TEXT_1),
1729
 
            ('text-b', ['text-a'], TEXT_1),
1730
 
            ('text-c', [], TEXT_1),
1731
 
            ('text-d', ['text-c'], TEXT_1),
1732
 
            ('text-m', ['text-b', 'text-d'], TEXT_1),
1733
 
           ]
1734
 
        for version_id, parents, lines in test_data:
1735
 
            k1.add_lines(version_id, parents, split_lines(lines))
1736
 
 
1737
 
        # This test is actually a bit strict as the order in which they're
1738
 
        # returned is not defined.  This matches the current (deterministic)
1739
 
        # behaviour.
1740
 
        expected_data_list = [
1741
 
            # version, options, length, parents
1742
 
            ('text-a', ['fulltext'], 122, ()),
1743
 
            ('text-b', ['line-delta'], 84, ('text-a',)),
1744
 
            ('text-m', ['line-delta'], 84, ('text-b', 'text-d')),
1745
 
            ('text-c', ['fulltext'], 121, ()),
1746
 
            ('text-d', ['line-delta'], 84, ('text-c',)),
1747
 
            ]
1748
 
        format, data_list, reader_callable = k1.get_data_stream(
1749
 
            ['text-a', 'text-b', 'text-c', 'text-d', 'text-m'])
1750
 
        self.assertEqual('knit-plain', format)
1751
 
        self.assertEqual(expected_data_list, data_list)
1752
 
        for version_id, options, length, parents in expected_data_list:
1753
 
            bytes = reader_callable(length)
1754
 
            self.assertRecordContentEqual(k1, version_id, bytes)
1755
 
 
1756
 
    def assertKnitFilesEqual(self, knit1, knit2):
1757
 
        """Assert that the contents of the index and data files of two knits are
1758
 
        equal.
1759
 
        """
1760
 
        self.assertEqual(
1761
 
            knit1.transport.get_bytes(knit1._data._access._filename),
1762
 
            knit2.transport.get_bytes(knit2._data._access._filename))
1763
 
        self.assertEqual(
1764
 
            knit1.transport.get_bytes(knit1._index._filename),
1765
 
            knit2.transport.get_bytes(knit2._index._filename))
1766
 
 
1767
 
    def assertKnitValuesEqual(self, left, right):
1768
 
        """Assert that the texts, annotations and graph of left and right are
1769
 
        the same.
1770
 
        """
1771
 
        self.assertEqual(set(left.versions()), set(right.versions()))
1772
 
        for version in left.versions():
1773
 
            self.assertEqual(left.get_parents_with_ghosts(version),
1774
 
                right.get_parents_with_ghosts(version))
1775
 
            self.assertEqual(left.get_lines(version),
1776
 
                right.get_lines(version))
1777
 
            self.assertEqual(left.annotate(version),
1778
 
                right.annotate(version))
1779
 
 
1780
 
    def test_insert_data_stream_empty(self):
1781
 
        """Inserting a data stream with no records should not put any data into
1782
 
        the knit.
1783
 
        """
1784
 
        k1 = self.make_test_knit()
1785
 
        k1.insert_data_stream(
1786
 
            (k1.get_format_signature(), [], lambda ignored: ''))
1787
 
        self.assertEqual('', k1.transport.get_bytes(k1._data._access._filename),
1788
 
                         "The .knit should be completely empty.")
1789
 
        self.assertEqual(k1._index.HEADER,
1790
 
                         k1.transport.get_bytes(k1._index._filename),
1791
 
                         "The .kndx should have nothing apart from the header.")
1792
 
 
1793
 
    def test_insert_data_stream_one_record(self):
1794
 
        """Inserting a data stream with one record from a knit with one record
1795
 
        results in byte-identical files.
1796
 
        """
1797
 
        source = self.make_test_knit(name='source')
1798
 
        source.add_lines('text-a', [], split_lines(TEXT_1))
1799
 
        data_stream = source.get_data_stream(['text-a'])
1800
 
        target = self.make_test_knit(name='target')
1801
 
        target.insert_data_stream(data_stream)
1802
 
        self.assertKnitFilesEqual(source, target)
1803
 
 
1804
 
    def test_insert_data_stream_annotated_unannotated(self):
1805
 
        """Inserting an annotated datastream to an unannotated knit works."""
1806
 
        # case one - full texts.
1807
 
        source = self.make_test_knit(name='source', annotate=True)
1808
 
        target = self.make_test_knit(name='target', annotate=False)
1809
 
        source.add_lines('text-a', [], split_lines(TEXT_1))
1810
 
        target.insert_data_stream(source.get_data_stream(['text-a']))
1811
 
        self.assertKnitValuesEqual(source, target)
1812
 
        # case two - deltas.
1813
 
        source.add_lines('text-b', ['text-a'], split_lines(TEXT_2))
1814
 
        target.insert_data_stream(source.get_data_stream(['text-b']))
1815
 
        self.assertKnitValuesEqual(source, target)
1816
 
 
1817
 
    def test_insert_data_stream_unannotated_annotated(self):
1818
 
        """Inserting an unannotated datastream to an annotated knit works."""
1819
 
        # case one - full texts.
1820
 
        source = self.make_test_knit(name='source', annotate=False)
1821
 
        target = self.make_test_knit(name='target', annotate=True)
1822
 
        source.add_lines('text-a', [], split_lines(TEXT_1))
1823
 
        target.insert_data_stream(source.get_data_stream(['text-a']))
1824
 
        self.assertKnitValuesEqual(source, target)
1825
 
        # case two - deltas.
1826
 
        source.add_lines('text-b', ['text-a'], split_lines(TEXT_2))
1827
 
        target.insert_data_stream(source.get_data_stream(['text-b']))
1828
 
        self.assertKnitValuesEqual(source, target)
1829
 
 
1830
 
    def test_insert_data_stream_records_already_present(self):
1831
 
        """Insert a data stream where some records are alreday present in the
1832
 
        target, and some not.  Only the new records are inserted.
1833
 
        """
1834
 
        source = self.make_test_knit(name='source')
1835
 
        target = self.make_test_knit(name='target')
1836
 
        # Insert 'text-a' into both source and target
1837
 
        source.add_lines('text-a', [], split_lines(TEXT_1))
1838
 
        target.insert_data_stream(source.get_data_stream(['text-a']))
1839
 
        # Insert 'text-b' into just the source.
1840
 
        source.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
1841
 
        # Get a data stream of both text-a and text-b, and insert it.
1842
 
        data_stream = source.get_data_stream(['text-a', 'text-b'])
1843
 
        target.insert_data_stream(data_stream)
1844
 
        # The source and target will now be identical.  This means the text-a
1845
 
        # record was not added a second time.
1846
 
        self.assertKnitFilesEqual(source, target)
1847
 
 
1848
 
    def test_insert_data_stream_multiple_records(self):
1849
 
        """Inserting a data stream of all records from a knit with multiple
1850
 
        records results in byte-identical files.
1851
 
        """
1852
 
        source = self.make_test_knit(name='source')
1853
 
        source.add_lines('text-a', [], split_lines(TEXT_1))
1854
 
        source.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
1855
 
        source.add_lines('text-c', [], split_lines(TEXT_1))
1856
 
        data_stream = source.get_data_stream(['text-a', 'text-b', 'text-c'])
1857
 
        
1858
 
        target = self.make_test_knit(name='target')
1859
 
        target.insert_data_stream(data_stream)
1860
 
        
1861
 
        self.assertKnitFilesEqual(source, target)
1862
 
 
1863
 
    def test_insert_data_stream_ghost_parent(self):
1864
 
        """Insert a data stream with a record that has a ghost parent."""
1865
 
        # Make a knit with a record, text-a, that has a ghost parent.
1866
 
        source = self.make_test_knit(name='source')
1867
 
        source.add_lines_with_ghosts('text-a', ['text-ghost'],
1868
 
                                     split_lines(TEXT_1))
1869
 
        data_stream = source.get_data_stream(['text-a'])
1870
 
 
1871
 
        target = self.make_test_knit(name='target')
1872
 
        target.insert_data_stream(data_stream)
1873
 
 
1874
 
        self.assertKnitFilesEqual(source, target)
1875
 
 
1876
 
        # The target knit object is in a consistent state, i.e. the record we
1877
 
        # just added is immediately visible.
1878
 
        self.assertTrue(target.has_version('text-a'))
1879
 
        self.assertFalse(target.has_version('text-ghost'))
1880
 
        self.assertEqual({'text-a':('text-ghost',)},
1881
 
            target.get_parent_map(['text-a', 'text-ghost']))
1882
 
        self.assertEqual(split_lines(TEXT_1), target.get_lines('text-a'))
1883
 
 
1884
 
    def test_insert_data_stream_inconsistent_version_lines(self):
1885
 
        """Inserting a data stream which has different content for a version_id
1886
 
        than already exists in the knit will raise KnitCorrupt.
1887
 
        """
1888
 
        source = self.make_test_knit(name='source')
1889
 
        target = self.make_test_knit(name='target')
1890
 
        # Insert a different 'text-a' into both source and target
1891
 
        source.add_lines('text-a', [], split_lines(TEXT_1))
1892
 
        target.add_lines('text-a', [], split_lines(TEXT_2))
1893
 
        # Insert a data stream with conflicting content into the target
1894
 
        data_stream = source.get_data_stream(['text-a'])
1895
 
        self.assertRaises(
1896
 
            errors.KnitCorrupt, target.insert_data_stream, data_stream)
1897
 
 
1898
 
    def test_insert_data_stream_inconsistent_version_parents(self):
1899
 
        """Inserting a data stream which has different parents for a version_id
1900
 
        than already exists in the knit will raise KnitCorrupt.
1901
 
        """
1902
 
        source = self.make_test_knit(name='source')
1903
 
        target = self.make_test_knit(name='target')
1904
 
        # Insert a different 'text-a' into both source and target.  They differ
1905
 
        # only by the parents list, the content is the same.
1906
 
        source.add_lines_with_ghosts('text-a', [], split_lines(TEXT_1))
1907
 
        target.add_lines_with_ghosts('text-a', ['a-ghost'], split_lines(TEXT_1))
1908
 
        # Insert a data stream with conflicting content into the target
1909
 
        data_stream = source.get_data_stream(['text-a'])
1910
 
        self.assertRaises(
1911
 
            errors.KnitCorrupt, target.insert_data_stream, data_stream)
1912
 
 
1913
 
    def test_insert_data_stream_unknown_format(self):
1914
 
        """A data stream in a different format to the target knit cannot be
1915
 
        inserted.
1916
 
 
1917
 
        It will raise KnitDataStreamUnknown because the fallback code will fail
1918
 
        to make a knit. In future we may need KnitDataStreamIncompatible again,
1919
 
        for more exotic cases.
1920
 
        """
1921
 
        data_stream = ('fake-format-signature', [], lambda _: '')
1922
 
        target = self.make_test_knit(name='target')
1923
 
        self.assertRaises(
1924
 
            errors.KnitDataStreamUnknown,
1925
 
            target.insert_data_stream, data_stream)
1926
 
 
1927
 
    def test_insert_data_stream_bug_208418(self):
1928
 
        """You can insert a stream with an incompatible format, even when:
1929
 
          * the stream has a line-delta record,
1930
 
          * whose parent is in the target, also stored as a line-delta
1931
 
 
1932
 
        See <https://launchpad.net/bugs/208418>.
1933
 
        """
1934
 
        base_lines = split_lines(TEXT_1)
1935
 
        # Make the target
1936
 
        target = self.make_test_knit(name='target', annotate=True)
1937
 
        target.add_lines('version-1', [], base_lines)
1938
 
        target.add_lines('version-2', ['version-1'], base_lines + ['a\n'])
1939
 
        # The second record should be a delta.
1940
 
        self.assertEqual('line-delta', target._index.get_method('version-2'))
1941
 
        
1942
 
        # Make a source, with a different format, but the same data
1943
 
        source = self.make_test_knit(name='source', annotate=False)
1944
 
        source.add_lines('version-1', [], base_lines)
1945
 
        source.add_lines('version-2', ['version-1'], base_lines + ['a\n'])
1946
 
        # Now add another record, which should be stored as a delta against
1947
 
        # version-2.
1948
 
        source.add_lines('version-3', ['version-2'], base_lines + ['b\n'])
1949
 
        self.assertEqual('line-delta', source._index.get_method('version-3'))
1950
 
 
1951
 
        # Make a stream of the new version
1952
 
        data_stream = source.get_data_stream(['version-3'])
1953
 
        # And insert into the target
1954
 
        target.insert_data_stream(data_stream)
1955
 
        # No errors should have been raised.
1956
 
 
1957
 
 
1958
 
    #  * test that a stream of "already present version, then new version"
1959
 
    #    inserts correctly.
1960
 
 
1961
 
 
1962
 
    def assertMadeStreamKnit(self, source_knit, versions, target_knit):
1963
 
        """Assert that a knit made from a stream is as expected."""
1964
 
        a_stream = source_knit.get_data_stream(versions)
1965
 
        expected_data = a_stream[2](None)
1966
 
        a_stream = source_knit.get_data_stream(versions)
1967
 
        a_knit = target_knit._knit_from_datastream(a_stream)
1968
 
        self.assertEqual(source_knit.factory.__class__,
1969
 
            a_knit.factory.__class__)
1970
 
        self.assertIsInstance(a_knit._data._access, _StreamAccess)
1971
 
        self.assertIsInstance(a_knit._index, _StreamIndex)
1972
 
        self.assertEqual(a_knit._index.data_list, a_stream[1])
1973
 
        self.assertEqual(a_knit._data._access.data, expected_data)
1974
 
        self.assertEqual(a_knit.filename, target_knit.filename)
1975
 
        self.assertEqual(a_knit.transport, target_knit.transport)
1976
 
        self.assertEqual(a_knit._index, a_knit._data._access.stream_index)
1977
 
        self.assertEqual(target_knit, a_knit._data._access.backing_knit)
1978
 
        self.assertIsInstance(a_knit._data._access.orig_factory,
1979
 
            source_knit.factory.__class__)
1980
 
 
1981
 
    def test__knit_from_data_stream_empty(self):
1982
 
        """Create a knit object from a datastream."""
1983
 
        annotated = self.make_test_knit(name='source', annotate=True)
1984
 
        plain = self.make_test_knit(name='target', annotate=False)
1985
 
        # case 1: annotated source
1986
 
        self.assertMadeStreamKnit(annotated, [], annotated)
1987
 
        self.assertMadeStreamKnit(annotated, [], plain)
1988
 
        # case 2: plain source
1989
 
        self.assertMadeStreamKnit(plain, [], annotated)
1990
 
        self.assertMadeStreamKnit(plain, [], plain)
1991
 
 
1992
 
    def test__knit_from_data_stream_unknown_format(self):
1993
 
        annotated = self.make_test_knit(name='source', annotate=True)
1994
 
        self.assertRaises(errors.KnitDataStreamUnknown,
1995
 
            annotated._knit_from_datastream, ("unknown", None, None))
1996
 
 
1997
1081
 
1998
1082
TEXT_1 = """\
1999
1083
Banana cup cakes:
2097
1181
 
2098
1182
class TestKnitCaching(KnitTests):
2099
1183
    
2100
 
    def create_knit(self):
 
1184
    def create_knit(self, cache_add=False):
2101
1185
        k = self.make_test_knit(True)
 
1186
        if cache_add:
 
1187
            k.enable_cache()
 
1188
 
2102
1189
        k.add_lines('text-1', [], split_lines(TEXT_1))
2103
1190
        k.add_lines('text-2', [], split_lines(TEXT_2))
2104
1191
        return k
2108
1195
        # Nothing should be cached without setting 'enable_cache'
2109
1196
        self.assertEqual({}, k._data._cache)
2110
1197
 
 
1198
    def test_cache_add_and_clear(self):
 
1199
        k = self.create_knit(True)
 
1200
 
 
1201
        self.assertEqual(['text-1', 'text-2'], sorted(k._data._cache.keys()))
 
1202
 
 
1203
        k.clear_cache()
 
1204
        self.assertEqual({}, k._data._cache)
 
1205
 
2111
1206
    def test_cache_data_read_raw(self):
2112
1207
        k = self.create_knit()
2113
1208
 
2116
1211
 
2117
1212
        def read_one_raw(version):
2118
1213
            pos_map = k._get_components_positions([version])
2119
 
            method, index_memo, next = pos_map[version]
2120
 
            lst = list(k._data.read_records_iter_raw([(version, index_memo)]))
 
1214
            method, pos, size, next = pos_map[version]
 
1215
            lst = list(k._data.read_records_iter_raw([(version, pos, size)]))
2121
1216
            self.assertEqual(1, len(lst))
2122
1217
            return lst[0]
2123
1218
 
2137
1232
 
2138
1233
        def read_one(version):
2139
1234
            pos_map = k._get_components_positions([version])
2140
 
            method, index_memo, next = pos_map[version]
2141
 
            lst = list(k._data.read_records_iter([(version, index_memo)]))
 
1235
            method, pos, size, next = pos_map[version]
 
1236
            lst = list(k._data.read_records_iter([(version, pos, size)]))
2142
1237
            self.assertEqual(1, len(lst))
2143
1238
            return lst[0]
2144
1239
 
2182
1277
        """Adding versions to the index should update the lookup dict"""
2183
1278
        knit = self.make_test_knit()
2184
1279
        idx = knit._index
2185
 
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
 
1280
        idx.add_version('a-1', ['fulltext'], 0, 0, [])
2186
1281
        self.check_file_contents('test.kndx',
2187
1282
            '# bzr knit index 8\n'
2188
1283
            '\n'
2189
1284
            'a-1 fulltext 0 0  :'
2190
1285
            )
2191
 
        idx.add_versions([('a-2', ['fulltext'], (None, 0, 0), ['a-1']),
2192
 
                          ('a-3', ['fulltext'], (None, 0, 0), ['a-2']),
 
1286
        idx.add_versions([('a-2', ['fulltext'], 0, 0, ['a-1']),
 
1287
                          ('a-3', ['fulltext'], 0, 0, ['a-2']),
2193
1288
                         ])
2194
1289
        self.check_file_contents('test.kndx',
2195
1290
            '# bzr knit index 8\n'
2199
1294
            'a-3 fulltext 0 0 1 :'
2200
1295
            )
2201
1296
        self.assertEqual(['a-1', 'a-2', 'a-3'], idx._history)
2202
 
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, (), 0),
2203
 
                          'a-2':('a-2', ['fulltext'], 0, 0, ('a-1',), 1),
2204
 
                          'a-3':('a-3', ['fulltext'], 0, 0, ('a-2',), 2),
 
1297
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, [], 0),
 
1298
                          'a-2':('a-2', ['fulltext'], 0, 0, ['a-1'], 1),
 
1299
                          'a-3':('a-3', ['fulltext'], 0, 0, ['a-2'], 2),
2205
1300
                         }, idx._cache)
2206
1301
 
2207
1302
    def test_add_versions_fails_clean(self):
2218
1313
 
2219
1314
        knit = self.make_test_knit()
2220
1315
        idx = knit._index
2221
 
        idx.add_version('a-1', ['fulltext'], (None, 0, 0), [])
 
1316
        idx.add_version('a-1', ['fulltext'], 0, 0, [])
2222
1317
 
2223
1318
        class StopEarly(Exception):
2224
1319
            pass
2225
1320
 
2226
1321
        def generate_failure():
2227
1322
            """Add some entries and then raise an exception"""
2228
 
            yield ('a-2', ['fulltext'], (None, 0, 0), ('a-1',))
2229
 
            yield ('a-3', ['fulltext'], (None, 0, 0), ('a-2',))
 
1323
            yield ('a-2', ['fulltext'], 0, 0, ['a-1'])
 
1324
            yield ('a-3', ['fulltext'], 0, 0, ['a-2'])
2230
1325
            raise StopEarly()
2231
1326
 
2232
1327
        # Assert the pre-condition
2233
1328
        self.assertEqual(['a-1'], idx._history)
2234
 
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, (), 0)}, idx._cache)
 
1329
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, [], 0)}, idx._cache)
2235
1330
 
2236
1331
        self.assertRaises(StopEarly, idx.add_versions, generate_failure())
2237
1332
 
2238
1333
        # And it shouldn't be modified
2239
1334
        self.assertEqual(['a-1'], idx._history)
2240
 
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, (), 0)}, idx._cache)
 
1335
        self.assertEqual({'a-1':('a-1', ['fulltext'], 0, 0, [], 0)}, idx._cache)
2241
1336
 
2242
1337
    def test_knit_index_ignores_empty_files(self):
2243
1338
        # There was a race condition in older bzr, where a ^C at the right time
2254
1349
        t.put_bytes('test.kndx', '# not really a knit header\n\n')
2255
1350
 
2256
1351
        self.assertRaises(KnitHeaderError, self.make_test_knit)
2257
 
 
2258
 
 
2259
 
class TestGraphIndexKnit(KnitTests):
2260
 
    """Tests for knits using a GraphIndex rather than a KnitIndex."""
2261
 
 
2262
 
    def make_g_index(self, name, ref_lists=0, nodes=[]):
2263
 
        builder = GraphIndexBuilder(ref_lists)
2264
 
        for node, references, value in nodes:
2265
 
            builder.add_node(node, references, value)
2266
 
        stream = builder.finish()
2267
 
        trans = self.get_transport()
2268
 
        size = trans.put_file(name, stream)
2269
 
        return GraphIndex(trans, name, size)
2270
 
 
2271
 
    def two_graph_index(self, deltas=False, catch_adds=False):
2272
 
        """Build a two-graph index.
2273
 
 
2274
 
        :param deltas: If true, use underlying indices with two node-ref
2275
 
            lists and 'parent' set to a delta-compressed against tail.
2276
 
        """
2277
 
        # build a complex graph across several indices.
2278
 
        if deltas:
2279
 
            # delta compression inn the index
2280
 
            index1 = self.make_g_index('1', 2, [
2281
 
                (('tip', ), 'N0 100', ([('parent', )], [], )),
2282
 
                (('tail', ), '', ([], []))])
2283
 
            index2 = self.make_g_index('2', 2, [
2284
 
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], [('tail', )])),
2285
 
                (('separate', ), '', ([], []))])
2286
 
        else:
2287
 
            # just blob location and graph in the index.
2288
 
            index1 = self.make_g_index('1', 1, [
2289
 
                (('tip', ), 'N0 100', ([('parent', )], )),
2290
 
                (('tail', ), '', ([], ))])
2291
 
            index2 = self.make_g_index('2', 1, [
2292
 
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], )),
2293
 
                (('separate', ), '', ([], ))])
2294
 
        combined_index = CombinedGraphIndex([index1, index2])
2295
 
        if catch_adds:
2296
 
            self.combined_index = combined_index
2297
 
            self.caught_entries = []
2298
 
            add_callback = self.catch_add
2299
 
        else:
2300
 
            add_callback = None
2301
 
        return KnitGraphIndex(combined_index, deltas=deltas,
2302
 
            add_callback=add_callback)
2303
 
 
2304
 
    def test_get_ancestry(self):
2305
 
        # get_ancestry is defined as eliding ghosts, not erroring.
2306
 
        index = self.two_graph_index()
2307
 
        self.assertEqual([], index.get_ancestry([]))
2308
 
        self.assertEqual(['separate'], index.get_ancestry(['separate']))
2309
 
        self.assertEqual(['tail'], index.get_ancestry(['tail']))
2310
 
        self.assertEqual(['tail', 'parent'], index.get_ancestry(['parent']))
2311
 
        self.assertEqual(['tail', 'parent', 'tip'], index.get_ancestry(['tip']))
2312
 
        self.assertTrue(index.get_ancestry(['tip', 'separate']) in
2313
 
            (['tail', 'parent', 'tip', 'separate'],
2314
 
             ['separate', 'tail', 'parent', 'tip'],
2315
 
            ))
2316
 
        # and without topo_sort
2317
 
        self.assertEqual(set(['separate']),
2318
 
            set(index.get_ancestry(['separate'], topo_sorted=False)))
2319
 
        self.assertEqual(set(['tail']),
2320
 
            set(index.get_ancestry(['tail'], topo_sorted=False)))
2321
 
        self.assertEqual(set(['tail', 'parent']),
2322
 
            set(index.get_ancestry(['parent'], topo_sorted=False)))
2323
 
        self.assertEqual(set(['tail', 'parent', 'tip']),
2324
 
            set(index.get_ancestry(['tip'], topo_sorted=False)))
2325
 
        self.assertEqual(set(['separate', 'tail', 'parent', 'tip']),
2326
 
            set(index.get_ancestry(['tip', 'separate'])))
2327
 
        # asking for a ghost makes it go boom.
2328
 
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry, ['ghost'])
2329
 
 
2330
 
    def test_get_ancestry_with_ghosts(self):
2331
 
        index = self.two_graph_index()
2332
 
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
2333
 
        self.assertEqual(['separate'], index.get_ancestry_with_ghosts(['separate']))
2334
 
        self.assertEqual(['tail'], index.get_ancestry_with_ghosts(['tail']))
2335
 
        self.assertTrue(index.get_ancestry_with_ghosts(['parent']) in
2336
 
            (['tail', 'ghost', 'parent'],
2337
 
             ['ghost', 'tail', 'parent'],
2338
 
            ))
2339
 
        self.assertTrue(index.get_ancestry_with_ghosts(['tip']) in
2340
 
            (['tail', 'ghost', 'parent', 'tip'],
2341
 
             ['ghost', 'tail', 'parent', 'tip'],
2342
 
            ))
2343
 
        self.assertTrue(index.get_ancestry_with_ghosts(['tip', 'separate']) in
2344
 
            (['tail', 'ghost', 'parent', 'tip', 'separate'],
2345
 
             ['ghost', 'tail', 'parent', 'tip', 'separate'],
2346
 
             ['separate', 'tail', 'ghost', 'parent', 'tip'],
2347
 
             ['separate', 'ghost', 'tail', 'parent', 'tip'],
2348
 
            ))
2349
 
        # asking for a ghost makes it go boom.
2350
 
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry_with_ghosts, ['ghost'])
2351
 
 
2352
 
    def test_num_versions(self):
2353
 
        index = self.two_graph_index()
2354
 
        self.assertEqual(4, index.num_versions())
2355
 
 
2356
 
    def test_get_versions(self):
2357
 
        index = self.two_graph_index()
2358
 
        self.assertEqual(set(['tail', 'tip', 'parent', 'separate']),
2359
 
            set(index.get_versions()))
2360
 
 
2361
 
    def test_has_version(self):
2362
 
        index = self.two_graph_index()
2363
 
        self.assertTrue(index.has_version('tail'))
2364
 
        self.assertFalse(index.has_version('ghost'))
2365
 
 
2366
 
    def test_get_position(self):
2367
 
        index = self.two_graph_index()
2368
 
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position('tip'))
2369
 
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position('parent'))
2370
 
 
2371
 
    def test_get_method_deltas(self):
2372
 
        index = self.two_graph_index(deltas=True)
2373
 
        self.assertEqual('fulltext', index.get_method('tip'))
2374
 
        self.assertEqual('line-delta', index.get_method('parent'))
2375
 
 
2376
 
    def test_get_method_no_deltas(self):
2377
 
        # check that the parent-history lookup is ignored with deltas=False.
2378
 
        index = self.two_graph_index(deltas=False)
2379
 
        self.assertEqual('fulltext', index.get_method('tip'))
2380
 
        self.assertEqual('fulltext', index.get_method('parent'))
2381
 
 
2382
 
    def test_get_options_deltas(self):
2383
 
        index = self.two_graph_index(deltas=True)
2384
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
2385
 
        self.assertEqual(['line-delta'], index.get_options('parent'))
2386
 
 
2387
 
    def test_get_options_no_deltas(self):
2388
 
        # check that the parent-history lookup is ignored with deltas=False.
2389
 
        index = self.two_graph_index(deltas=False)
2390
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
2391
 
        self.assertEqual(['fulltext'], index.get_options('parent'))
2392
 
 
2393
 
    def test_get_parents_with_ghosts(self):
2394
 
        index = self.two_graph_index()
2395
 
        self.assertEqual(('tail', 'ghost'), index.get_parents_with_ghosts('parent'))
2396
 
        # and errors on ghosts.
2397
 
        self.assertRaises(errors.RevisionNotPresent,
2398
 
            index.get_parents_with_ghosts, 'ghost')
2399
 
 
2400
 
    def test_check_versions_present(self):
2401
 
        # ghosts should not be considered present
2402
 
        index = self.two_graph_index()
2403
 
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
2404
 
            ['ghost'])
2405
 
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
2406
 
            ['tail', 'ghost'])
2407
 
        index.check_versions_present(['tail', 'separate'])
2408
 
 
2409
 
    def catch_add(self, entries):
2410
 
        self.caught_entries.append(entries)
2411
 
 
2412
 
    def test_add_no_callback_errors(self):
2413
 
        index = self.two_graph_index()
2414
 
        self.assertRaises(errors.ReadOnlyError, index.add_version,
2415
 
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
2416
 
 
2417
 
    def test_add_version_smoke(self):
2418
 
        index = self.two_graph_index(catch_adds=True)
2419
 
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
2420
 
        self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
2421
 
            self.caught_entries)
2422
 
 
2423
 
    def test_add_version_delta_not_delta_index(self):
2424
 
        index = self.two_graph_index(catch_adds=True)
2425
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2426
 
            'new', 'no-eol,line-delta', (None, 0, 100), ['parent'])
2427
 
        self.assertEqual([], self.caught_entries)
2428
 
 
2429
 
    def test_add_version_same_dup(self):
2430
 
        index = self.two_graph_index(catch_adds=True)
2431
 
        # options can be spelt two different ways
2432
 
        index.add_version('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])
2433
 
        index.add_version('tip', 'no-eol,fulltext', (None, 0, 100), ['parent'])
2434
 
        # but neither should have added data.
2435
 
        self.assertEqual([[], []], self.caught_entries)
2436
 
        
2437
 
    def test_add_version_different_dup(self):
2438
 
        index = self.two_graph_index(deltas=True, catch_adds=True)
2439
 
        # change options
2440
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2441
 
            'tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])
2442
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2443
 
            'tip', 'line-delta,no-eol', (None, 0, 100), ['parent'])
2444
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2445
 
            'tip', 'fulltext', (None, 0, 100), ['parent'])
2446
 
        # position/length
2447
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2448
 
            'tip', 'fulltext,no-eol', (None, 50, 100), ['parent'])
2449
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2450
 
            'tip', 'fulltext,no-eol', (None, 0, 1000), ['parent'])
2451
 
        # parents
2452
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2453
 
            'tip', 'fulltext,no-eol', (None, 0, 100), [])
2454
 
        self.assertEqual([], self.caught_entries)
2455
 
        
2456
 
    def test_add_versions_nodeltas(self):
2457
 
        index = self.two_graph_index(catch_adds=True)
2458
 
        index.add_versions([
2459
 
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
2460
 
                ('new2', 'fulltext', (None, 0, 6), ['new']),
2461
 
                ])
2462
 
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
2463
 
            (('new2', ), ' 0 6', ((('new',),),))],
2464
 
            sorted(self.caught_entries[0]))
2465
 
        self.assertEqual(1, len(self.caught_entries))
2466
 
 
2467
 
    def test_add_versions_deltas(self):
2468
 
        index = self.two_graph_index(deltas=True, catch_adds=True)
2469
 
        index.add_versions([
2470
 
                ('new', 'fulltext,no-eol', (None, 50, 60), ['separate']),
2471
 
                ('new2', 'line-delta', (None, 0, 6), ['new']),
2472
 
                ])
2473
 
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
2474
 
            (('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
2475
 
            sorted(self.caught_entries[0]))
2476
 
        self.assertEqual(1, len(self.caught_entries))
2477
 
 
2478
 
    def test_add_versions_delta_not_delta_index(self):
2479
 
        index = self.two_graph_index(catch_adds=True)
2480
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2481
 
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
2482
 
        self.assertEqual([], self.caught_entries)
2483
 
 
2484
 
    def test_add_versions_random_id_accepted(self):
2485
 
        index = self.two_graph_index(catch_adds=True)
2486
 
        index.add_versions([], random_id=True)
2487
 
 
2488
 
    def test_add_versions_same_dup(self):
2489
 
        index = self.two_graph_index(catch_adds=True)
2490
 
        # options can be spelt two different ways
2491
 
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
2492
 
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
2493
 
        # but neither should have added data.
2494
 
        self.assertEqual([[], []], self.caught_entries)
2495
 
        
2496
 
    def test_add_versions_different_dup(self):
2497
 
        index = self.two_graph_index(deltas=True, catch_adds=True)
2498
 
        # change options
2499
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2500
 
            [('tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
2501
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2502
 
            [('tip', 'line-delta,no-eol', (None, 0, 100), ['parent'])])
2503
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2504
 
            [('tip', 'fulltext', (None, 0, 100), ['parent'])])
2505
 
        # position/length
2506
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2507
 
            [('tip', 'fulltext,no-eol', (None, 50, 100), ['parent'])])
2508
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2509
 
            [('tip', 'fulltext,no-eol', (None, 0, 1000), ['parent'])])
2510
 
        # parents
2511
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2512
 
            [('tip', 'fulltext,no-eol', (None, 0, 100), [])])
2513
 
        # change options in the second record
2514
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2515
 
            [('tip', 'fulltext,no-eol', (None, 0, 100), ['parent']),
2516
 
             ('tip', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
2517
 
        self.assertEqual([], self.caught_entries)
2518
 
 
2519
 
    def test_iter_parents(self):
2520
 
        index1 = self.make_g_index('1', 1, [
2521
 
        # no parents
2522
 
            (('r0', ), 'N0 100', ([], )),
2523
 
        # 1 parent
2524
 
            (('r1', ), '', ([('r0', )], ))])
2525
 
        index2 = self.make_g_index('2', 1, [
2526
 
        # 2 parents
2527
 
            (('r2', ), 'N0 100', ([('r1', ), ('r0', )], )),
2528
 
            ])
2529
 
        combined_index = CombinedGraphIndex([index1, index2])
2530
 
        index = KnitGraphIndex(combined_index)
2531
 
        # XXX TODO a ghost
2532
 
        # cases: each sample data individually:
2533
 
        self.assertEqual(set([('r0', ())]),
2534
 
            set(index.iter_parents(['r0'])))
2535
 
        self.assertEqual(set([('r1', ('r0', ))]),
2536
 
            set(index.iter_parents(['r1'])))
2537
 
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
2538
 
            set(index.iter_parents(['r2'])))
2539
 
        # no nodes returned for a missing node
2540
 
        self.assertEqual(set(),
2541
 
            set(index.iter_parents(['missing'])))
2542
 
        # 1 node returned with missing nodes skipped
2543
 
        self.assertEqual(set([('r1', ('r0', ))]),
2544
 
            set(index.iter_parents(['ghost1', 'r1', 'ghost'])))
2545
 
        # 2 nodes returned
2546
 
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
2547
 
            set(index.iter_parents(['r0', 'r1'])))
2548
 
        # 2 nodes returned, missing skipped
2549
 
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
2550
 
            set(index.iter_parents(['a', 'r0', 'b', 'r1', 'c'])))
2551
 
 
2552
 
 
2553
 
class TestNoParentsGraphIndexKnit(KnitTests):
2554
 
    """Tests for knits using KnitGraphIndex with no parents."""
2555
 
 
2556
 
    def make_g_index(self, name, ref_lists=0, nodes=[]):
2557
 
        builder = GraphIndexBuilder(ref_lists)
2558
 
        for node, references in nodes:
2559
 
            builder.add_node(node, references)
2560
 
        stream = builder.finish()
2561
 
        trans = self.get_transport()
2562
 
        size = trans.put_file(name, stream)
2563
 
        return GraphIndex(trans, name, size)
2564
 
 
2565
 
    def test_parents_deltas_incompatible(self):
2566
 
        index = CombinedGraphIndex([])
2567
 
        self.assertRaises(errors.KnitError, KnitGraphIndex, index,
2568
 
            deltas=True, parents=False)
2569
 
 
2570
 
    def two_graph_index(self, catch_adds=False):
2571
 
        """Build a two-graph index.
2572
 
 
2573
 
        :param deltas: If true, use underlying indices with two node-ref
2574
 
            lists and 'parent' set to a delta-compressed against tail.
2575
 
        """
2576
 
        # put several versions in the index.
2577
 
        index1 = self.make_g_index('1', 0, [
2578
 
            (('tip', ), 'N0 100'),
2579
 
            (('tail', ), '')])
2580
 
        index2 = self.make_g_index('2', 0, [
2581
 
            (('parent', ), ' 100 78'),
2582
 
            (('separate', ), '')])
2583
 
        combined_index = CombinedGraphIndex([index1, index2])
2584
 
        if catch_adds:
2585
 
            self.combined_index = combined_index
2586
 
            self.caught_entries = []
2587
 
            add_callback = self.catch_add
2588
 
        else:
2589
 
            add_callback = None
2590
 
        return KnitGraphIndex(combined_index, parents=False,
2591
 
            add_callback=add_callback)
2592
 
 
2593
 
    def test_get_ancestry(self):
2594
 
        # with no parents, ancestry is always just the key.
2595
 
        index = self.two_graph_index()
2596
 
        self.assertEqual([], index.get_ancestry([]))
2597
 
        self.assertEqual(['separate'], index.get_ancestry(['separate']))
2598
 
        self.assertEqual(['tail'], index.get_ancestry(['tail']))
2599
 
        self.assertEqual(['parent'], index.get_ancestry(['parent']))
2600
 
        self.assertEqual(['tip'], index.get_ancestry(['tip']))
2601
 
        self.assertTrue(index.get_ancestry(['tip', 'separate']) in
2602
 
            (['tip', 'separate'],
2603
 
             ['separate', 'tip'],
2604
 
            ))
2605
 
        # asking for a ghost makes it go boom.
2606
 
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry, ['ghost'])
2607
 
 
2608
 
    def test_get_ancestry_with_ghosts(self):
2609
 
        index = self.two_graph_index()
2610
 
        self.assertEqual([], index.get_ancestry_with_ghosts([]))
2611
 
        self.assertEqual(['separate'], index.get_ancestry_with_ghosts(['separate']))
2612
 
        self.assertEqual(['tail'], index.get_ancestry_with_ghosts(['tail']))
2613
 
        self.assertEqual(['parent'], index.get_ancestry_with_ghosts(['parent']))
2614
 
        self.assertEqual(['tip'], index.get_ancestry_with_ghosts(['tip']))
2615
 
        self.assertTrue(index.get_ancestry_with_ghosts(['tip', 'separate']) in
2616
 
            (['tip', 'separate'],
2617
 
             ['separate', 'tip'],
2618
 
            ))
2619
 
        # asking for a ghost makes it go boom.
2620
 
        self.assertRaises(errors.RevisionNotPresent, index.get_ancestry_with_ghosts, ['ghost'])
2621
 
 
2622
 
    def test_num_versions(self):
2623
 
        index = self.two_graph_index()
2624
 
        self.assertEqual(4, index.num_versions())
2625
 
 
2626
 
    def test_get_versions(self):
2627
 
        index = self.two_graph_index()
2628
 
        self.assertEqual(set(['tail', 'tip', 'parent', 'separate']),
2629
 
            set(index.get_versions()))
2630
 
 
2631
 
    def test_has_version(self):
2632
 
        index = self.two_graph_index()
2633
 
        self.assertTrue(index.has_version('tail'))
2634
 
        self.assertFalse(index.has_version('ghost'))
2635
 
 
2636
 
    def test_get_position(self):
2637
 
        index = self.two_graph_index()
2638
 
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position('tip'))
2639
 
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position('parent'))
2640
 
 
2641
 
    def test_get_method(self):
2642
 
        index = self.two_graph_index()
2643
 
        self.assertEqual('fulltext', index.get_method('tip'))
2644
 
        self.assertEqual(['fulltext'], index.get_options('parent'))
2645
 
 
2646
 
    def test_get_options(self):
2647
 
        index = self.two_graph_index()
2648
 
        self.assertEqual(['fulltext', 'no-eol'], index.get_options('tip'))
2649
 
        self.assertEqual(['fulltext'], index.get_options('parent'))
2650
 
 
2651
 
    def test_get_parents_with_ghosts(self):
2652
 
        index = self.two_graph_index()
2653
 
        self.assertEqual((), index.get_parents_with_ghosts('parent'))
2654
 
        # and errors on ghosts.
2655
 
        self.assertRaises(errors.RevisionNotPresent,
2656
 
            index.get_parents_with_ghosts, 'ghost')
2657
 
 
2658
 
    def test_check_versions_present(self):
2659
 
        index = self.two_graph_index()
2660
 
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
2661
 
            ['missing'])
2662
 
        self.assertRaises(RevisionNotPresent, index.check_versions_present,
2663
 
            ['tail', 'missing'])
2664
 
        index.check_versions_present(['tail', 'separate'])
2665
 
 
2666
 
    def catch_add(self, entries):
2667
 
        self.caught_entries.append(entries)
2668
 
 
2669
 
    def test_add_no_callback_errors(self):
2670
 
        index = self.two_graph_index()
2671
 
        self.assertRaises(errors.ReadOnlyError, index.add_version,
2672
 
            'new', 'fulltext,no-eol', (None, 50, 60), ['separate'])
2673
 
 
2674
 
    def test_add_version_smoke(self):
2675
 
        index = self.two_graph_index(catch_adds=True)
2676
 
        index.add_version('new', 'fulltext,no-eol', (None, 50, 60), [])
2677
 
        self.assertEqual([[(('new', ), 'N50 60')]],
2678
 
            self.caught_entries)
2679
 
 
2680
 
    def test_add_version_delta_not_delta_index(self):
2681
 
        index = self.two_graph_index(catch_adds=True)
2682
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2683
 
            'new', 'no-eol,line-delta', (None, 0, 100), [])
2684
 
        self.assertEqual([], self.caught_entries)
2685
 
 
2686
 
    def test_add_version_same_dup(self):
2687
 
        index = self.two_graph_index(catch_adds=True)
2688
 
        # options can be spelt two different ways
2689
 
        index.add_version('tip', 'fulltext,no-eol', (None, 0, 100), [])
2690
 
        index.add_version('tip', 'no-eol,fulltext', (None, 0, 100), [])
2691
 
        # but neither should have added data.
2692
 
        self.assertEqual([[], []], self.caught_entries)
2693
 
        
2694
 
    def test_add_version_different_dup(self):
2695
 
        index = self.two_graph_index(catch_adds=True)
2696
 
        # change options
2697
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2698
 
            'tip', 'no-eol,line-delta', (None, 0, 100), [])
2699
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2700
 
            'tip', 'line-delta,no-eol', (None, 0, 100), [])
2701
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2702
 
            'tip', 'fulltext', (None, 0, 100), [])
2703
 
        # position/length
2704
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2705
 
            'tip', 'fulltext,no-eol', (None, 50, 100), [])
2706
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2707
 
            'tip', 'fulltext,no-eol', (None, 0, 1000), [])
2708
 
        # parents
2709
 
        self.assertRaises(errors.KnitCorrupt, index.add_version,
2710
 
            'tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])
2711
 
        self.assertEqual([], self.caught_entries)
2712
 
        
2713
 
    def test_add_versions(self):
2714
 
        index = self.two_graph_index(catch_adds=True)
2715
 
        index.add_versions([
2716
 
                ('new', 'fulltext,no-eol', (None, 50, 60), []),
2717
 
                ('new2', 'fulltext', (None, 0, 6), []),
2718
 
                ])
2719
 
        self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
2720
 
            sorted(self.caught_entries[0]))
2721
 
        self.assertEqual(1, len(self.caught_entries))
2722
 
 
2723
 
    def test_add_versions_delta_not_delta_index(self):
2724
 
        index = self.two_graph_index(catch_adds=True)
2725
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2726
 
            [('new', 'no-eol,line-delta', (None, 0, 100), ['parent'])])
2727
 
        self.assertEqual([], self.caught_entries)
2728
 
 
2729
 
    def test_add_versions_parents_not_parents_index(self):
2730
 
        index = self.two_graph_index(catch_adds=True)
2731
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2732
 
            [('new', 'no-eol,fulltext', (None, 0, 100), ['parent'])])
2733
 
        self.assertEqual([], self.caught_entries)
2734
 
 
2735
 
    def test_add_versions_random_id_accepted(self):
2736
 
        index = self.two_graph_index(catch_adds=True)
2737
 
        index.add_versions([], random_id=True)
2738
 
 
2739
 
    def test_add_versions_same_dup(self):
2740
 
        index = self.two_graph_index(catch_adds=True)
2741
 
        # options can be spelt two different ways
2742
 
        index.add_versions([('tip', 'fulltext,no-eol', (None, 0, 100), [])])
2743
 
        index.add_versions([('tip', 'no-eol,fulltext', (None, 0, 100), [])])
2744
 
        # but neither should have added data.
2745
 
        self.assertEqual([[], []], self.caught_entries)
2746
 
        
2747
 
    def test_add_versions_different_dup(self):
2748
 
        index = self.two_graph_index(catch_adds=True)
2749
 
        # change options
2750
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2751
 
            [('tip', 'no-eol,line-delta', (None, 0, 100), [])])
2752
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2753
 
            [('tip', 'line-delta,no-eol', (None, 0, 100), [])])
2754
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2755
 
            [('tip', 'fulltext', (None, 0, 100), [])])
2756
 
        # position/length
2757
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2758
 
            [('tip', 'fulltext,no-eol', (None, 50, 100), [])])
2759
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2760
 
            [('tip', 'fulltext,no-eol', (None, 0, 1000), [])])
2761
 
        # parents
2762
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2763
 
            [('tip', 'fulltext,no-eol', (None, 0, 100), ['parent'])])
2764
 
        # change options in the second record
2765
 
        self.assertRaises(errors.KnitCorrupt, index.add_versions,
2766
 
            [('tip', 'fulltext,no-eol', (None, 0, 100), []),
2767
 
             ('tip', 'no-eol,line-delta', (None, 0, 100), [])])
2768
 
        self.assertEqual([], self.caught_entries)
2769
 
 
2770
 
    def test_iter_parents(self):
2771
 
        index = self.two_graph_index()
2772
 
        self.assertEqual(set([
2773
 
            ('tip', ()), ('tail', ()), ('parent', ()), ('separate', ())
2774
 
            ]),
2775
 
            set(index.iter_parents(['tip', 'tail', 'ghost', 'parent', 'separate'])))
2776
 
        self.assertEqual(set([('tip', ())]),
2777
 
            set(index.iter_parents(['tip'])))
2778
 
        self.assertEqual(set(),
2779
 
            set(index.iter_parents([])))
2780
 
 
2781
 
 
2782
 
class TestPackKnits(KnitTests):
2783
 
    """Tests that use a _PackAccess and KnitGraphIndex."""
2784
 
 
2785
 
    def test_get_data_stream_packs_ignores_pack_overhead(self):
2786
 
        # Packs have an encoding overhead that should not be included in the
2787
 
        # 'size' field of a data stream, because it is not returned by the
2788
 
        # raw_reading functions - it is why index_memo's are opaque, and
2789
 
        # get_data_stream was abusing this.
2790
 
        packname = 'test.pack'
2791
 
        transport = self.get_transport()
2792
 
        def write_data(bytes):
2793
 
            transport.append_bytes(packname, bytes)
2794
 
        writer = pack.ContainerWriter(write_data)
2795
 
        writer.begin()
2796
 
        index = InMemoryGraphIndex(2)
2797
 
        knit_index = KnitGraphIndex(index, add_callback=index.add_nodes,
2798
 
            deltas=True)
2799
 
        indices = {index:(transport, packname)}
2800
 
        access = _PackAccess(indices, writer=(writer, index))
2801
 
        k = KnitVersionedFile('test', get_transport('.'),
2802
 
            delta=True, create=True, index=knit_index, access_method=access)
2803
 
        # insert something into the knit
2804
 
        k.add_lines('text-1', [], ["foo\n"])
2805
 
        # get a data stream for it
2806
 
        stream = k.get_data_stream(['text-1'])
2807
 
        # if the stream has been incorrectly assembled, we will get a short read
2808
 
        # reading from the stream (as streams have no trailer)
2809
 
        expected_length = stream[1][0][2]
2810
 
        # we use -1 to do the read, so that if a trailer is added this test
2811
 
        # will fail and we'll adjust it to handle that case correctly, rather
2812
 
        # than allowing an over-read that is bogus.
2813
 
        self.assertEqual(expected_length, len(stream[2](-1)))
2814
 
 
2815
 
 
2816
 
class Test_StreamIndex(KnitTests):
2817
 
 
2818
 
    def get_index(self, knit, stream):
2819
 
        """Get a _StreamIndex from knit and stream."""
2820
 
        return knit._knit_from_datastream(stream)._index
2821
 
 
2822
 
    def assertIndexVersions(self, knit, versions):
2823
 
        """Check that the _StreamIndex versions are those of the stream."""
2824
 
        index = self.get_index(knit, knit.get_data_stream(versions))
2825
 
        self.assertEqual(set(index.get_versions()), set(versions))
2826
 
        # check we didn't get duplicates
2827
 
        self.assertEqual(len(index.get_versions()), len(versions))
2828
 
 
2829
 
    def assertIndexAncestry(self, knit, ancestry_versions, versions, result):
2830
 
        """Check the result of a get_ancestry call on knit."""
2831
 
        index = self.get_index(knit, knit.get_data_stream(versions))
2832
 
        self.assertEqual(
2833
 
            set(result),
2834
 
            set(index.get_ancestry(ancestry_versions, False)))
2835
 
 
2836
 
    def assertIterParents(self, knit, versions, parent_versions, result):
2837
 
        """Check the result of an iter_parents call on knit."""
2838
 
        index = self.get_index(knit, knit.get_data_stream(versions))
2839
 
        self.assertEqual(result, index.iter_parents(parent_versions))
2840
 
 
2841
 
    def assertGetMethod(self, knit, versions, version, result):
2842
 
        index = self.get_index(knit, knit.get_data_stream(versions))
2843
 
        self.assertEqual(result, index.get_method(version))
2844
 
 
2845
 
    def assertGetOptions(self, knit, version, options):
2846
 
        index = self.get_index(knit, knit.get_data_stream(version))
2847
 
        self.assertEqual(options, index.get_options(version))
2848
 
 
2849
 
    def assertGetPosition(self, knit, versions, version, result):
2850
 
        index = self.get_index(knit, knit.get_data_stream(versions))
2851
 
        if result[1] is None:
2852
 
            result = (result[0], index, result[2], result[3])
2853
 
        self.assertEqual(result, index.get_position(version))
2854
 
 
2855
 
    def assertGetParentsWithGhosts(self, knit, versions, version, parents):
2856
 
        index = self.get_index(knit, knit.get_data_stream(versions))
2857
 
        self.assertEqual(parents, index.get_parents_with_ghosts(version))
2858
 
 
2859
 
    def make_knit_with_4_versions_2_dags(self):
2860
 
        knit = self.make_test_knit()
2861
 
        knit.add_lines('a', [], ["foo"])
2862
 
        knit.add_lines('b', [], [])
2863
 
        knit.add_lines('c', ['b', 'a'], [])
2864
 
        knit.add_lines_with_ghosts('d', ['e', 'f'], [])
2865
 
        return knit
2866
 
 
2867
 
    def test_versions(self):
2868
 
        """The versions of a StreamIndex are those of the datastream."""
2869
 
        knit = self.make_knit_with_4_versions_2_dags()
2870
 
        # ask for most permutations, which catches bugs like falling back to the
2871
 
        # target knit, or showing ghosts, etc.
2872
 
        self.assertIndexVersions(knit, [])
2873
 
        self.assertIndexVersions(knit, ['a'])
2874
 
        self.assertIndexVersions(knit, ['b'])
2875
 
        self.assertIndexVersions(knit, ['c'])
2876
 
        self.assertIndexVersions(knit, ['d'])
2877
 
        self.assertIndexVersions(knit, ['a', 'b'])
2878
 
        self.assertIndexVersions(knit, ['b', 'c'])
2879
 
        self.assertIndexVersions(knit, ['a', 'c'])
2880
 
        self.assertIndexVersions(knit, ['a', 'b', 'c'])
2881
 
        self.assertIndexVersions(knit, ['a', 'b', 'c', 'd'])
2882
 
 
2883
 
    def test_construct(self):
2884
 
        """Constructing a StreamIndex generates index data."""
2885
 
        data_list = [('text-a', ['fulltext'], 127, []),
2886
 
            ('text-b', ['option'], 128, ['text-c'])]
2887
 
        index = _StreamIndex(data_list, None)
2888
 
        self.assertEqual({'text-a':(['fulltext'], (0, 127), []),
2889
 
            'text-b':(['option'], (127, 127 + 128), ['text-c'])},
2890
 
            index._by_version)
2891
 
 
2892
 
    def test_get_ancestry(self):
2893
 
        knit = self.make_knit_with_4_versions_2_dags()
2894
 
        self.assertIndexAncestry(knit, ['a'], ['a'], ['a'])
2895
 
        self.assertIndexAncestry(knit, ['b'], ['b'], ['b'])
2896
 
        self.assertIndexAncestry(knit, ['c'], ['c'], ['c'])
2897
 
        self.assertIndexAncestry(knit, ['c'], ['a', 'b', 'c'],
2898
 
            set(['a', 'b', 'c']))
2899
 
        self.assertIndexAncestry(knit, ['c', 'd'], ['a', 'b', 'c', 'd'],
2900
 
            set(['a', 'b', 'c', 'd']))
2901
 
 
2902
 
    def test_get_method(self):
2903
 
        knit = self.make_knit_with_4_versions_2_dags()
2904
 
        self.assertGetMethod(knit, ['a'], 'a', 'fulltext')
2905
 
        self.assertGetMethod(knit, ['c'], 'c', 'line-delta')
2906
 
        # get_method on a basis that is not in the datastream (but in the
2907
 
        # backing knit) returns 'fulltext', because thats what we'll create as
2908
 
        # we thunk across.
2909
 
        self.assertGetMethod(knit, ['c'], 'b', 'fulltext')
2910
 
 
2911
 
    def test_iter_parents(self):
2912
 
        knit = self.make_knit_with_4_versions_2_dags()
2913
 
        self.assertIterParents(knit, ['a'], ['a'], [('a', ())])
2914
 
        self.assertIterParents(knit, ['a', 'b'], ['a', 'b'],
2915
 
            [('a', ()), ('b', ())])
2916
 
        self.assertIterParents(knit, ['a', 'b', 'c'], ['a', 'b', 'c'],
2917
 
            [('a', ()), ('b', ()), ('c', ('b', 'a'))])
2918
 
        self.assertIterParents(knit, ['a', 'b', 'c', 'd'],
2919
 
            ['a', 'b', 'c', 'd'],
2920
 
            [('a', ()), ('b', ()), ('c', ('b', 'a')), ('d', ('e', 'f'))])
2921
 
        self.assertIterParents(knit, ['c'], ['a', 'b', 'c'],
2922
 
            [('c', ('b', 'a'))])
2923
 
 
2924
 
    def test_get_options(self):
2925
 
        knit = self.make_knit_with_4_versions_2_dags()
2926
 
        self.assertGetOptions(knit, 'a', ['no-eol', 'fulltext'])
2927
 
        self.assertGetOptions(knit, 'c', ['line-delta'])
2928
 
 
2929
 
    def test_get_parents_with_ghosts(self):
2930
 
        knit = self.make_knit_with_4_versions_2_dags()
2931
 
        self.assertGetParentsWithGhosts(knit, ['a'], 'a', ())
2932
 
        self.assertGetParentsWithGhosts(knit, ['c'], 'c', ('b', 'a'))
2933
 
        self.assertGetParentsWithGhosts(knit, ['d'], 'd', ('e', 'f'))
2934
 
 
2935
 
    def test_get_position(self):
2936
 
        knit = self.make_knit_with_4_versions_2_dags()
2937
 
        # get_position returns (thunk_flag, index(can be None), start, end) for
2938
 
        # _StreamAccess to use.
2939
 
        self.assertGetPosition(knit, ['a'], 'a', (False, None, 0, 78))
2940
 
        self.assertGetPosition(knit, ['a', 'c'], 'c', (False, None, 78, 156))
2941
 
        # get_position on a text that is not in the datastream (but in the
2942
 
        # backing knit) returns (True, 'versionid', None, None) - and then the
2943
 
        # access object can construct the relevant data as needed.
2944
 
        self.assertGetPosition(knit, ['a', 'c'], 'b', (True, 'b', None, None))
2945
 
 
2946
 
 
2947
 
class Test_StreamAccess(KnitTests):
2948
 
 
2949
 
    def get_index_access(self, knit, stream):
2950
 
        """Get a _StreamAccess from knit and stream."""
2951
 
        knit =  knit._knit_from_datastream(stream)
2952
 
        return knit._index, knit._data._access
2953
 
 
2954
 
    def assertGetRawRecords(self, knit, versions):
2955
 
        index, access = self.get_index_access(knit,
2956
 
            knit.get_data_stream(versions))
2957
 
        # check that every version asked for can be obtained from the resulting
2958
 
        # access object.
2959
 
        # batch
2960
 
        memos = []
2961
 
        for version in versions:
2962
 
            memos.append(knit._index.get_position(version))
2963
 
        original = {}
2964
 
        for version, data in zip(
2965
 
            versions, knit._data._access.get_raw_records(memos)):
2966
 
            original[version] = data
2967
 
        memos = []
2968
 
        for version in versions:
2969
 
            memos.append(index.get_position(version))
2970
 
        streamed = {}
2971
 
        for version, data in zip(versions, access.get_raw_records(memos)):
2972
 
            streamed[version] = data
2973
 
        self.assertEqual(original, streamed)
2974
 
        # individually
2975
 
        for version in versions:
2976
 
            data = list(access.get_raw_records(
2977
 
                [index.get_position(version)]))[0]
2978
 
            self.assertEqual(original[version], data)
2979
 
 
2980
 
    def make_knit_with_two_versions(self):
2981
 
        knit = self.make_test_knit()
2982
 
        knit.add_lines('a', [], ["foo"])
2983
 
        knit.add_lines('b', [], ["bar"])
2984
 
        return knit
2985
 
 
2986
 
    def test_get_raw_records(self):
2987
 
        knit = self.make_knit_with_two_versions()
2988
 
        self.assertGetRawRecords(knit, ['a', 'b'])
2989
 
        self.assertGetRawRecords(knit, ['a'])
2990
 
        self.assertGetRawRecords(knit, ['b'])
2991
 
    
2992
 
    def test_get_raw_record_from_backing_knit(self):
2993
 
        # the thunk layer should create an artificial A on-demand when needed.
2994
 
        source_knit = self.make_test_knit(name='plain', annotate=False)
2995
 
        target_knit = self.make_test_knit(name='annotated', annotate=True)
2996
 
        source_knit.add_lines("A", [], ["Foo\n"])
2997
 
        # Give the target A, so we can try to thunk across to it.
2998
 
        target_knit.join(source_knit)
2999
 
        index, access = self.get_index_access(target_knit,
3000
 
            source_knit.get_data_stream([]))
3001
 
        raw_data = list(access.get_raw_records([(True, "A", None, None)]))[0]
3002
 
        df = GzipFile(mode='rb', fileobj=StringIO(raw_data))
3003
 
        self.assertEqual(
3004
 
            'version A 1 5d36b88bb697a2d778f024048bafabd443d74503\n'
3005
 
            'Foo\nend A\n',
3006
 
            df.read())
3007
 
 
3008
 
    def test_asking_for_thunk_stream_is_not_plain_errors(self):
3009
 
        knit = self.make_test_knit(name='annotated', annotate=True)
3010
 
        knit.add_lines("A", [], ["Foo\n"])
3011
 
        index, access = self.get_index_access(knit,
3012
 
            knit.get_data_stream([]))
3013
 
        self.assertRaises(errors.KnitCorrupt,
3014
 
            list, access.get_raw_records([(True, "A", None, None)]))