~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_knit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-01-02 08:23:44 UTC
  • mfrom: (3140.1.9 find-branches)
  • Revision ID: pqm@pqm.ubuntu.com-20080102082344-qret383z2bdk1ud4
Optimize find_branches for standalone repositories (abentley)

Show diffs side-by-side

added added

removed removed

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