~bzr-pqm/bzr/bzr.dev

4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2006-2010 Canonical Ltd
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
16
17
"""Tests for Knit data structure"""
18
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
19
from cStringIO import StringIO
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
20
import difflib
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
21
import gzip
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
22
import sys
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
23
2196.2.5 by John Arbash Meinel
Add an exception class when the knit index storage method is unknown, and properly test for it
24
from bzrlib import (
25
    errors,
2484.1.5 by John Arbash Meinel
Simplistic implementations of custom parsers for options and parents
26
    generate_ids,
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
27
    knit,
3350.8.12 by Robert Collins
Stacked make_mpdiffs.
28
    multiparent,
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
29
    osutils,
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
30
    pack,
4913.2.24 by John Arbash Meinel
Track down a few more import typos.
31
    tests,
2196.2.5 by John Arbash Meinel
Add an exception class when the knit index storage method is unknown, and properly test for it
32
    )
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
33
from bzrlib.errors import (
34
    RevisionAlreadyPresent,
35
    KnitHeaderError,
36
    RevisionNotPresent,
37
    NoSuchFile,
38
    )
2592.3.1 by Robert Collins
Allow giving KnitVersionedFile an index object to use rather than implicitly creating one.
39
from bzrlib.index import *
1684.3.3 by Robert Collins
Add a special cased weaves to knit converter.
40
from bzrlib.knit import (
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
41
    AnnotatedKnitContent,
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
42
    KnitContent,
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
43
    KnitSequenceMatcher,
44
    KnitVersionedFiles,
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
45
    PlainKnitContent,
4005.3.6 by Robert Collins
Support delta_closure=True with NetworkRecordStream to transmit deltas over the wire when full text extraction is required on the far end.
46
    _VFContentMapGenerator,
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
47
    _DirectPackAccess,
48
    _KndxIndex,
49
    _KnitGraphIndex,
50
    _KnitKeyAccess,
51
    make_file_factory,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
52
    )
3789.2.10 by John Arbash Meinel
The first function for KnitVersionedFiles can now retry on request.
53
from bzrlib.repofmt import pack_repo
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
54
from bzrlib.tests import (
55
    Feature,
3350.8.8 by Robert Collins
Stacking and knits don't play nice for annotation yet.
56
    KnownFailure,
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
57
    TestCase,
58
    TestCaseWithMemoryTransport,
59
    TestCaseWithTransport,
3787.1.1 by Robert Collins
Embed the failed text in sha1 knit errors.
60
    TestNotApplicable,
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
61
    )
2745.5.3 by Robert Collins
* Move transport logging into a new transport class
62
from bzrlib.transport import get_transport
1563.2.13 by Robert Collins
InterVersionedFile implemented.
63
from bzrlib.transport.memory import MemoryTransport
3052.2.3 by Robert Collins
Handle insert_data_stream of an unannotated stream into an annotated knit.
64
from bzrlib.tuned_gzip import GzipFile
3350.8.2 by Robert Collins
stacked get_parent_map.
65
from bzrlib.versionedfile import (
3350.8.6 by Robert Collins
get_record_stream stacking for delta access.
66
    AbsentContentFactory,
3350.8.2 by Robert Collins
stacked get_parent_map.
67
    ConstantMapper,
4005.3.6 by Robert Collins
Support delta_closure=True with NetworkRecordStream to transmit deltas over the wire when full text extraction is required on the far end.
68
    network_bytes_to_kind_and_offset,
3350.8.2 by Robert Collins
stacked get_parent_map.
69
    RecordingVersionedFilesDecorator,
70
    )
1684.3.3 by Robert Collins
Add a special cased weaves to knit converter.
71
72
4913.2.20 by John Arbash Meinel
Change all of the compiled_foo to compiled_foo_feature
73
compiled_knit_feature = tests.ModuleAvailableFeature(
74
                            'bzrlib._knit_load_data_pyx')
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
75
76
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
77
class KnitContentTestsMixin(object):
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
78
79
    def test_constructor(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
80
        content = self._make_content([])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
81
82
    def test_text(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
83
        content = self._make_content([])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
84
        self.assertEqual(content.text(), [])
85
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
86
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
87
        self.assertEqual(content.text(), ["text1", "text2"])
88
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
89
    def test_copy(self):
90
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
91
        copy = content.copy()
92
        self.assertIsInstance(copy, content.__class__)
93
        self.assertEqual(copy.annotate(), content.annotate())
94
95
    def assertDerivedBlocksEqual(self, source, target, noeol=False):
96
        """Assert that the derived matching blocks match real output"""
97
        source_lines = source.splitlines(True)
98
        target_lines = target.splitlines(True)
99
        def nl(line):
100
            if noeol and not line.endswith('\n'):
101
                return line + '\n'
102
            else:
103
                return line
104
        source_content = self._make_content([(None, nl(l)) for l in source_lines])
105
        target_content = self._make_content([(None, nl(l)) for l in target_lines])
106
        line_delta = source_content.line_delta(target_content)
107
        delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
108
            source_lines, target_lines))
109
        matcher = KnitSequenceMatcher(None, source_lines, target_lines)
110
        matcher_blocks = list(list(matcher.get_matching_blocks()))
111
        self.assertEqual(matcher_blocks, delta_blocks)
112
113
    def test_get_line_delta_blocks(self):
114
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'q\nc\n')
115
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1)
116
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1A)
117
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1B)
118
        self.assertDerivedBlocksEqual(TEXT_1B, TEXT_1A)
119
        self.assertDerivedBlocksEqual(TEXT_1A, TEXT_1B)
120
        self.assertDerivedBlocksEqual(TEXT_1A, '')
121
        self.assertDerivedBlocksEqual('', TEXT_1A)
122
        self.assertDerivedBlocksEqual('', '')
123
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd')
124
125
    def test_get_line_delta_blocks_noeol(self):
126
        """Handle historical knit deltas safely
127
128
        Some existing knit deltas don't consider the last line to differ
129
        when the only difference whether it has a final newline.
130
131
        New knit deltas appear to always consider the last line to differ
132
        in this case.
133
        """
134
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd\n', noeol=True)
135
        self.assertDerivedBlocksEqual('a\nb\nc\nd\n', 'a\nb\nc', noeol=True)
136
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'a\nb\nc', noeol=True)
137
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\n', noeol=True)
138
139
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
140
TEXT_1 = """\
141
Banana cup cakes:
142
143
- bananas
144
- eggs
145
- broken tea cups
146
"""
147
148
TEXT_1A = """\
149
Banana cup cake recipe
150
(serves 6)
151
152
- bananas
153
- eggs
154
- broken tea cups
155
- self-raising flour
156
"""
157
158
TEXT_1B = """\
159
Banana cup cake recipe
160
161
- bananas (do not use plantains!!!)
162
- broken tea cups
163
- flour
164
"""
165
166
delta_1_1a = """\
167
0,1,2
168
Banana cup cake recipe
169
(serves 6)
170
5,5,1
171
- self-raising flour
172
"""
173
174
TEXT_2 = """\
175
Boeuf bourguignon
176
177
- beef
178
- red wine
179
- small onions
180
- carrot
181
- mushrooms
182
"""
183
184
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
185
class TestPlainKnitContent(TestCase, KnitContentTestsMixin):
186
187
    def _make_content(self, lines):
188
        annotated_content = AnnotatedKnitContent(lines)
189
        return PlainKnitContent(annotated_content.text(), 'bogus')
190
191
    def test_annotate(self):
192
        content = self._make_content([])
193
        self.assertEqual(content.annotate(), [])
194
195
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
196
        self.assertEqual(content.annotate(),
197
            [("bogus", "text1"), ("bogus", "text2")])
198
199
    def test_line_delta(self):
200
        content1 = self._make_content([("", "a"), ("", "b")])
201
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
202
        self.assertEqual(content1.line_delta(content2),
203
            [(1, 2, 2, ["a", "c"])])
204
205
    def test_line_delta_iter(self):
206
        content1 = self._make_content([("", "a"), ("", "b")])
207
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
208
        it = content1.line_delta_iter(content2)
209
        self.assertEqual(it.next(), (1, 2, 2, ["a", "c"]))
210
        self.assertRaises(StopIteration, it.next)
211
212
213
class TestAnnotatedKnitContent(TestCase, KnitContentTestsMixin):
214
215
    def _make_content(self, lines):
216
        return AnnotatedKnitContent(lines)
217
218
    def test_annotate(self):
219
        content = self._make_content([])
220
        self.assertEqual(content.annotate(), [])
221
222
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
223
        self.assertEqual(content.annotate(),
224
            [("origin1", "text1"), ("origin2", "text2")])
225
226
    def test_line_delta(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
227
        content1 = self._make_content([("", "a"), ("", "b")])
228
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
229
        self.assertEqual(content1.line_delta(content2),
230
            [(1, 2, 2, [("", "a"), ("", "c")])])
231
232
    def test_line_delta_iter(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
233
        content1 = self._make_content([("", "a"), ("", "b")])
234
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
235
        it = content1.line_delta_iter(content2)
236
        self.assertEqual(it.next(), (1, 2, 2, [("", "a"), ("", "c")]))
237
        self.assertRaises(StopIteration, it.next)
238
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
239
240
class MockTransport(object):
241
242
    def __init__(self, file_lines=None):
243
        self.file_lines = file_lines
244
        self.calls = []
2196.2.3 by John Arbash Meinel
Update tests and code to pass after merging bzr.dev
245
        # We have no base directory for the MockTransport
246
        self.base = ''
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
247
248
    def get(self, filename):
249
        if self.file_lines is None:
250
            raise NoSuchFile(filename)
251
        else:
252
            return StringIO("\n".join(self.file_lines))
253
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
254
    def readv(self, relpath, offsets):
255
        fp = self.get(relpath)
256
        for offset, size in offsets:
257
            fp.seek(offset)
258
            yield offset, fp.read(size)
259
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
260
    def __getattr__(self, name):
261
        def queue_call(*args, **kwargs):
262
            self.calls.append((name, args, kwargs))
263
        return queue_call
264
265
3789.2.2 by John Arbash Meinel
Test that a readv() failing after yielding data will still raise Retry
266
class MockReadvFailingTransport(MockTransport):
267
    """Fail in the middle of a readv() result.
268
3789.2.3 by John Arbash Meinel
Change the mocking a bit, so we can be sure it is failing at the right time.
269
    This Transport will successfully yield the first two requested hunks, but
270
    raise NoSuchFile for the rest.
3789.2.2 by John Arbash Meinel
Test that a readv() failing after yielding data will still raise Retry
271
    """
272
273
    def readv(self, relpath, offsets):
3789.2.3 by John Arbash Meinel
Change the mocking a bit, so we can be sure it is failing at the right time.
274
        count = 0
3789.2.2 by John Arbash Meinel
Test that a readv() failing after yielding data will still raise Retry
275
        for result in MockTransport.readv(self, relpath, offsets):
3789.2.3 by John Arbash Meinel
Change the mocking a bit, so we can be sure it is failing at the right time.
276
            count += 1
277
            # we use 2 because the first offset is the pack header, the second
278
            # is the first actual content requset
279
            if count > 2:
3789.2.2 by John Arbash Meinel
Test that a readv() failing after yielding data will still raise Retry
280
                raise errors.NoSuchFile(relpath)
281
            yield result
282
283
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
284
class KnitRecordAccessTestsMixin(object):
285
    """Tests for getting and putting knit records."""
286
287
    def test_add_raw_records(self):
288
        """Add_raw_records adds records retrievable later."""
289
        access = self.get_access()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
290
        memos = access.add_raw_records([('key', 10)], '1234567890')
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
291
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
292
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
293
    def test_add_several_raw_records(self):
294
        """add_raw_records with many records and read some back."""
295
        access = self.get_access()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
296
        memos = access.add_raw_records([('key', 10), ('key2', 2), ('key3', 5)],
297
            '12345678901234567')
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
298
        self.assertEqual(['1234567890', '12', '34567'],
299
            list(access.get_raw_records(memos)))
300
        self.assertEqual(['1234567890'],
301
            list(access.get_raw_records(memos[0:1])))
302
        self.assertEqual(['12'],
303
            list(access.get_raw_records(memos[1:2])))
304
        self.assertEqual(['34567'],
305
            list(access.get_raw_records(memos[2:3])))
306
        self.assertEqual(['1234567890', '34567'],
307
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
308
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
309
310
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
311
    """Tests for the .kndx implementation."""
312
313
    def get_access(self):
314
        """Get a .knit style access instance."""
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
315
        mapper = ConstantMapper("foo")
316
        access = _KnitKeyAccess(self.get_transport(), mapper)
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
317
        return access
3789.2.6 by John Arbash Meinel
Make _DirectPackAccess.reload_or_raise maintain the logic.
318
319
320
class _TestException(Exception):
321
    """Just an exception for local tests to use."""
322
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
323
324
class TestPackKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
325
    """Tests for the pack based access."""
326
327
    def get_access(self):
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
328
        return self._get_access()[0]
329
330
    def _get_access(self, packname='packfile', index='FOO'):
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
331
        transport = self.get_transport()
332
        def write_data(bytes):
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
333
            transport.append_bytes(packname, bytes)
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
334
        writer = pack.ContainerWriter(write_data)
335
        writer.begin()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
336
        access = _DirectPackAccess({})
337
        access.set_writer(writer, index, (transport, packname))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
338
        return access, writer
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
339
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
340
    def make_pack_file(self):
341
        """Create a pack file with 2 records."""
342
        access, writer = self._get_access(packname='packname', index='foo')
343
        memos = []
344
        memos.extend(access.add_raw_records([('key1', 10)], '1234567890'))
345
        memos.extend(access.add_raw_records([('key2', 5)], '12345'))
346
        writer.end()
347
        return memos
348
3789.2.11 by John Arbash Meinel
KnitVersionedFile.get_record_stream now retries *and* fails correctly.
349
    def make_vf_for_retrying(self):
3789.2.10 by John Arbash Meinel
The first function for KnitVersionedFiles can now retry on request.
350
        """Create 3 packs and a reload function.
351
352
        Originally, 2 pack files will have the data, but one will be missing.
353
        And then the third will be used in place of the first two if reload()
354
        is called.
355
356
        :return: (versioned_file, reload_counter)
357
            versioned_file  a KnitVersionedFiles using the packs for access
358
        """
4617.7.1 by Robert Collins
Lock the format knit retry tests depend on - knits aren't used for 2a formats.
359
        builder = self.make_branch_builder('.', format="1.9")
4454.3.59 by John Arbash Meinel
Track down why the annotate retry code was failing.
360
        builder.start_series()
361
        builder.build_snapshot('rev-1', None, [
362
            ('add', ('', 'root-id', 'directory', None)),
363
            ('add', ('file', 'file-id', 'file', 'content\nrev 1\n')),
364
            ])
365
        builder.build_snapshot('rev-2', ['rev-1'], [
366
            ('modify', ('file-id', 'content\nrev 2\n')),
367
            ])
368
        builder.build_snapshot('rev-3', ['rev-2'], [
369
            ('modify', ('file-id', 'content\nrev 3\n')),
370
            ])
371
        builder.finish_series()
372
        b = builder.get_branch()
373
        b.lock_write()
374
        self.addCleanup(b.unlock)
4145.1.6 by Robert Collins
More test fallout, but all caught now.
375
        # Pack these three revisions into another pack file, but don't remove
376
        # the originals
4454.3.59 by John Arbash Meinel
Track down why the annotate retry code was failing.
377
        repo = b.repository
4145.1.6 by Robert Collins
More test fallout, but all caught now.
378
        collection = repo._pack_collection
379
        collection.ensure_loaded()
380
        orig_packs = collection.packs
381
        packer = pack_repo.Packer(collection, orig_packs, '.testpack')
382
        new_pack = packer.pack()
383
        # forget about the new pack
384
        collection.reset()
385
        repo.refresh_data()
4454.3.59 by John Arbash Meinel
Track down why the annotate retry code was failing.
386
        vf = repo.revisions
3789.2.10 by John Arbash Meinel
The first function for KnitVersionedFiles can now retry on request.
387
        # Set up a reload() function that switches to using the new pack file
388
        new_index = new_pack.revision_index
389
        access_tuple = new_pack.access_tuple()
390
        reload_counter = [0, 0, 0]
391
        def reload():
392
            reload_counter[0] += 1
393
            if reload_counter[1] > 0:
394
                # We already reloaded, nothing more to do
395
                reload_counter[2] += 1
396
                return False
397
            reload_counter[1] += 1
398
            vf._index._graph_index._indices[:] = [new_index]
399
            vf._access._indices.clear()
400
            vf._access._indices[new_index] = access_tuple
401
            return True
3789.2.11 by John Arbash Meinel
KnitVersionedFile.get_record_stream now retries *and* fails correctly.
402
        # Delete one of the pack files so the data will need to be reloaded. We
3789.2.12 by John Arbash Meinel
iter_lines_added_or_present now retries.
403
        # will delete the file with 'rev-2' in it
3789.2.10 by John Arbash Meinel
The first function for KnitVersionedFiles can now retry on request.
404
        trans, name = orig_packs[1].access_tuple()
405
        trans.delete(name)
406
        # We don't have the index trigger reloading because we want to test
407
        # that we reload when the .pack disappears
408
        vf._access._reload_func = reload
409
        return vf, reload_counter
410
3789.2.6 by John Arbash Meinel
Make _DirectPackAccess.reload_or_raise maintain the logic.
411
    def make_reload_func(self, return_val=True):
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
412
        reload_called = [0]
413
        def reload():
414
            reload_called[0] += 1
3789.2.6 by John Arbash Meinel
Make _DirectPackAccess.reload_or_raise maintain the logic.
415
            return return_val
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
416
        return reload_called, reload
417
3789.2.6 by John Arbash Meinel
Make _DirectPackAccess.reload_or_raise maintain the logic.
418
    def make_retry_exception(self):
419
        # We raise a real exception so that sys.exc_info() is properly
420
        # populated
421
        try:
422
            raise _TestException('foobar')
423
        except _TestException, e:
3789.2.29 by John Arbash Meinel
RetryWithNewPacks requires another argument.
424
            retry_exc = errors.RetryWithNewPacks(None, reload_occurred=False,
3789.2.6 by John Arbash Meinel
Make _DirectPackAccess.reload_or_raise maintain the logic.
425
                                                 exc_info=sys.exc_info())
426
        return retry_exc
427
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
428
    def test_read_from_several_packs(self):
429
        access, writer = self._get_access()
430
        memos = []
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
431
        memos.extend(access.add_raw_records([('key', 10)], '1234567890'))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
432
        writer.end()
433
        access, writer = self._get_access('pack2', 'FOOBAR')
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
434
        memos.extend(access.add_raw_records([('key', 5)], '12345'))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
435
        writer.end()
436
        access, writer = self._get_access('pack3', 'BAZ')
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
437
        memos.extend(access.add_raw_records([('key', 5)], 'alpha'))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
438
        writer.end()
439
        transport = self.get_transport()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
440
        access = _DirectPackAccess({"FOO":(transport, 'packfile'),
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
441
            "FOOBAR":(transport, 'pack2'),
442
            "BAZ":(transport, 'pack3')})
443
        self.assertEqual(['1234567890', '12345', 'alpha'],
444
            list(access.get_raw_records(memos)))
445
        self.assertEqual(['1234567890'],
446
            list(access.get_raw_records(memos[0:1])))
447
        self.assertEqual(['12345'],
448
            list(access.get_raw_records(memos[1:2])))
449
        self.assertEqual(['alpha'],
450
            list(access.get_raw_records(memos[2:3])))
451
        self.assertEqual(['1234567890', 'alpha'],
452
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
453
2592.3.70 by Robert Collins
Allow setting a writer after creating a knit._PackAccess object.
454
    def test_set_writer(self):
455
        """The writer should be settable post construction."""
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
456
        access = _DirectPackAccess({})
2592.3.70 by Robert Collins
Allow setting a writer after creating a knit._PackAccess object.
457
        transport = self.get_transport()
458
        packname = 'packfile'
459
        index = 'foo'
460
        def write_data(bytes):
461
            transport.append_bytes(packname, bytes)
462
        writer = pack.ContainerWriter(write_data)
463
        writer.begin()
464
        access.set_writer(writer, index, (transport, packname))
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
465
        memos = access.add_raw_records([('key', 10)], '1234567890')
2592.3.70 by Robert Collins
Allow setting a writer after creating a knit._PackAccess object.
466
        writer.end()
467
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
468
3789.2.1 by John Arbash Meinel
_DirectPackAccess can now raise RetryWithNewPacks when we think something has happened.
469
    def test_missing_index_raises_retry(self):
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
470
        memos = self.make_pack_file()
3789.2.1 by John Arbash Meinel
_DirectPackAccess can now raise RetryWithNewPacks when we think something has happened.
471
        transport = self.get_transport()
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
472
        reload_called, reload_func = self.make_reload_func()
473
        # Note that the index key has changed from 'foo' to 'bar'
474
        access = _DirectPackAccess({'bar':(transport, 'packname')},
475
                                   reload_func=reload_func)
3789.2.1 by John Arbash Meinel
_DirectPackAccess can now raise RetryWithNewPacks when we think something has happened.
476
        e = self.assertListRaises(errors.RetryWithNewPacks,
477
                                  access.get_raw_records, memos)
478
        # Because a key was passed in which does not match our index list, we
479
        # assume that the listing was already reloaded
480
        self.assertTrue(e.reload_occurred)
481
        self.assertIsInstance(e.exc_info, tuple)
482
        self.assertIs(e.exc_info[0], KeyError)
483
        self.assertIsInstance(e.exc_info[1], KeyError)
484
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
485
    def test_missing_index_raises_key_error_with_no_reload(self):
486
        memos = self.make_pack_file()
487
        transport = self.get_transport()
488
        # Note that the index key has changed from 'foo' to 'bar'
489
        access = _DirectPackAccess({'bar':(transport, 'packname')})
490
        e = self.assertListRaises(KeyError, access.get_raw_records, memos)
491
3789.2.1 by John Arbash Meinel
_DirectPackAccess can now raise RetryWithNewPacks when we think something has happened.
492
    def test_missing_file_raises_retry(self):
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
493
        memos = self.make_pack_file()
494
        transport = self.get_transport()
495
        reload_called, reload_func = self.make_reload_func()
496
        # Note that the 'filename' has been changed to 'different-packname'
497
        access = _DirectPackAccess({'foo':(transport, 'different-packname')},
498
                                   reload_func=reload_func)
499
        e = self.assertListRaises(errors.RetryWithNewPacks,
500
                                  access.get_raw_records, memos)
501
        # The file has gone missing, so we assume we need to reload
502
        self.assertFalse(e.reload_occurred)
503
        self.assertIsInstance(e.exc_info, tuple)
504
        self.assertIs(e.exc_info[0], errors.NoSuchFile)
505
        self.assertIsInstance(e.exc_info[1], errors.NoSuchFile)
506
        self.assertEqual('different-packname', e.exc_info[1].path)
507
508
    def test_missing_file_raises_no_such_file_with_no_reload(self):
509
        memos = self.make_pack_file()
510
        transport = self.get_transport()
511
        # Note that the 'filename' has been changed to 'different-packname'
3789.2.1 by John Arbash Meinel
_DirectPackAccess can now raise RetryWithNewPacks when we think something has happened.
512
        access = _DirectPackAccess({'foo':(transport, 'different-packname')})
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
513
        e = self.assertListRaises(errors.NoSuchFile,
3789.2.1 by John Arbash Meinel
_DirectPackAccess can now raise RetryWithNewPacks when we think something has happened.
514
                                  access.get_raw_records, memos)
515
3789.2.2 by John Arbash Meinel
Test that a readv() failing after yielding data will still raise Retry
516
    def test_failing_readv_raises_retry(self):
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
517
        memos = self.make_pack_file()
518
        transport = self.get_transport()
519
        failing_transport = MockReadvFailingTransport(
520
                                [transport.get_bytes('packname')])
521
        reload_called, reload_func = self.make_reload_func()
522
        access = _DirectPackAccess({'foo':(failing_transport, 'packname')},
523
                                   reload_func=reload_func)
524
        # Asking for a single record will not trigger the Mock failure
525
        self.assertEqual(['1234567890'],
526
            list(access.get_raw_records(memos[:1])))
527
        self.assertEqual(['12345'],
528
            list(access.get_raw_records(memos[1:2])))
529
        # A multiple offset readv() will fail mid-way through
530
        e = self.assertListRaises(errors.RetryWithNewPacks,
531
                                  access.get_raw_records, memos)
532
        # The file has gone missing, so we assume we need to reload
533
        self.assertFalse(e.reload_occurred)
534
        self.assertIsInstance(e.exc_info, tuple)
535
        self.assertIs(e.exc_info[0], errors.NoSuchFile)
536
        self.assertIsInstance(e.exc_info[1], errors.NoSuchFile)
537
        self.assertEqual('packname', e.exc_info[1].path)
538
539
    def test_failing_readv_raises_no_such_file_with_no_reload(self):
540
        memos = self.make_pack_file()
541
        transport = self.get_transport()
542
        failing_transport = MockReadvFailingTransport(
543
                                [transport.get_bytes('packname')])
544
        reload_called, reload_func = self.make_reload_func()
3789.2.2 by John Arbash Meinel
Test that a readv() failing after yielding data will still raise Retry
545
        access = _DirectPackAccess({'foo':(failing_transport, 'packname')})
3789.2.3 by John Arbash Meinel
Change the mocking a bit, so we can be sure it is failing at the right time.
546
        # Asking for a single record will not trigger the Mock failure
547
        self.assertEqual(['1234567890'],
548
            list(access.get_raw_records(memos[:1])))
549
        self.assertEqual(['12345'],
550
            list(access.get_raw_records(memos[1:2])))
551
        # A multiple offset readv() will fail mid-way through
3789.2.5 by John Arbash Meinel
Change _DirectPackAccess to only raise Retry when _reload_func is defined.
552
        e = self.assertListRaises(errors.NoSuchFile,
3789.2.2 by John Arbash Meinel
Test that a readv() failing after yielding data will still raise Retry
553
                                  access.get_raw_records, memos)
554
3789.2.6 by John Arbash Meinel
Make _DirectPackAccess.reload_or_raise maintain the logic.
555
    def test_reload_or_raise_no_reload(self):
556
        access = _DirectPackAccess({}, reload_func=None)
557
        retry_exc = self.make_retry_exception()
558
        # Without a reload_func, we will just re-raise the original exception
559
        self.assertRaises(_TestException, access.reload_or_raise, retry_exc)
560
561
    def test_reload_or_raise_reload_changed(self):
562
        reload_called, reload_func = self.make_reload_func(return_val=True)
563
        access = _DirectPackAccess({}, reload_func=reload_func)
564
        retry_exc = self.make_retry_exception()
565
        access.reload_or_raise(retry_exc)
566
        self.assertEqual([1], reload_called)
567
        retry_exc.reload_occurred=True
568
        access.reload_or_raise(retry_exc)
569
        self.assertEqual([2], reload_called)
570
571
    def test_reload_or_raise_reload_no_change(self):
572
        reload_called, reload_func = self.make_reload_func(return_val=False)
573
        access = _DirectPackAccess({}, reload_func=reload_func)
574
        retry_exc = self.make_retry_exception()
575
        # If reload_occurred is False, then we consider it an error to have
576
        # reload_func() return False (no changes).
577
        self.assertRaises(_TestException, access.reload_or_raise, retry_exc)
578
        self.assertEqual([1], reload_called)
579
        retry_exc.reload_occurred=True
580
        # If reload_occurred is True, then we assume nothing changed because
581
        # it had changed earlier, but didn't change again
582
        access.reload_or_raise(retry_exc)
583
        self.assertEqual([2], reload_called)
584
3789.2.13 by John Arbash Meinel
KnitVersionedFile.annotate() now retries when appropriate.
585
    def test_annotate_retries(self):
586
        vf, reload_counter = self.make_vf_for_retrying()
587
        # It is a little bit bogus to annotate the Revision VF, but it works,
588
        # as we have ancestry stored there
589
        key = ('rev-3',)
590
        reload_lines = vf.annotate(key)
591
        self.assertEqual([1, 1, 0], reload_counter)
592
        plain_lines = vf.annotate(key)
593
        self.assertEqual([1, 1, 0], reload_counter) # No extra reloading
594
        if reload_lines != plain_lines:
595
            self.fail('Annotation was not identical with reloading.')
596
        # Now delete the packs-in-use, which should trigger another reload, but
597
        # this time we just raise an exception because we can't recover
598
        for trans, name in vf._access._indices.itervalues():
599
            trans.delete(name)
600
        self.assertRaises(errors.NoSuchFile, vf.annotate, key)
601
        self.assertEqual([2, 1, 1], reload_counter)
602
3789.2.10 by John Arbash Meinel
The first function for KnitVersionedFiles can now retry on request.
603
    def test__get_record_map_retries(self):
3789.2.11 by John Arbash Meinel
KnitVersionedFile.get_record_stream now retries *and* fails correctly.
604
        vf, reload_counter = self.make_vf_for_retrying()
3789.2.12 by John Arbash Meinel
iter_lines_added_or_present now retries.
605
        keys = [('rev-1',), ('rev-2',), ('rev-3',)]
3789.2.10 by John Arbash Meinel
The first function for KnitVersionedFiles can now retry on request.
606
        records = vf._get_record_map(keys)
607
        self.assertEqual(keys, sorted(records.keys()))
3789.2.11 by John Arbash Meinel
KnitVersionedFile.get_record_stream now retries *and* fails correctly.
608
        self.assertEqual([1, 1, 0], reload_counter)
609
        # Now delete the packs-in-use, which should trigger another reload, but
610
        # this time we just raise an exception because we can't recover
611
        for trans, name in vf._access._indices.itervalues():
612
            trans.delete(name)
613
        self.assertRaises(errors.NoSuchFile, vf._get_record_map, keys)
614
        self.assertEqual([2, 1, 1], reload_counter)
615
616
    def test_get_record_stream_retries(self):
617
        vf, reload_counter = self.make_vf_for_retrying()
3789.2.12 by John Arbash Meinel
iter_lines_added_or_present now retries.
618
        keys = [('rev-1',), ('rev-2',), ('rev-3',)]
3789.2.11 by John Arbash Meinel
KnitVersionedFile.get_record_stream now retries *and* fails correctly.
619
        record_stream = vf.get_record_stream(keys, 'topological', False)
620
        record = record_stream.next()
621
        self.assertEqual(('rev-1',), record.key)
622
        self.assertEqual([0, 0, 0], reload_counter)
623
        record = record_stream.next()
624
        self.assertEqual(('rev-2',), record.key)
625
        self.assertEqual([1, 1, 0], reload_counter)
3789.2.12 by John Arbash Meinel
iter_lines_added_or_present now retries.
626
        record = record_stream.next()
627
        self.assertEqual(('rev-3',), record.key)
628
        self.assertEqual([1, 1, 0], reload_counter)
3789.2.11 by John Arbash Meinel
KnitVersionedFile.get_record_stream now retries *and* fails correctly.
629
        # Now delete all pack files, and see that we raise the right error
630
        for trans, name in vf._access._indices.itervalues():
631
            trans.delete(name)
632
        self.assertListRaises(errors.NoSuchFile,
633
            vf.get_record_stream, keys, 'topological', False)
634
3789.2.12 by John Arbash Meinel
iter_lines_added_or_present now retries.
635
    def test_iter_lines_added_or_present_in_keys_retries(self):
636
        vf, reload_counter = self.make_vf_for_retrying()
637
        keys = [('rev-1',), ('rev-2',), ('rev-3',)]
638
        # Unfortunately, iter_lines_added_or_present_in_keys iterates the
639
        # result in random order (determined by the iteration order from a
640
        # set()), so we don't have any solid way to trigger whether data is
641
        # read before or after. However we tried to delete the middle node to
642
        # exercise the code well.
643
        # What we care about is that all lines are always yielded, but not
644
        # duplicated
645
        count = 0
646
        reload_lines = sorted(vf.iter_lines_added_or_present_in_keys(keys))
647
        self.assertEqual([1, 1, 0], reload_counter)
648
        # Now do it again, to make sure the result is equivalent
649
        plain_lines = sorted(vf.iter_lines_added_or_present_in_keys(keys))
650
        self.assertEqual([1, 1, 0], reload_counter) # No extra reloading
651
        self.assertEqual(plain_lines, reload_lines)
652
        self.assertEqual(21, len(plain_lines))
653
        # Now delete all pack files, and see that we raise the right error
654
        for trans, name in vf._access._indices.itervalues():
655
            trans.delete(name)
656
        self.assertListRaises(errors.NoSuchFile,
657
            vf.iter_lines_added_or_present_in_keys, keys)
658
        self.assertEqual([2, 1, 1], reload_counter)
659
3878.1.1 by John Arbash Meinel
KVF.get_record_stream('unordered') now returns the records based on I/O ordering.
660
    def test_get_record_stream_yields_disk_sorted_order(self):
661
        # if we get 'unordered' pick a semi-optimal order for reading. The
662
        # order should be grouped by pack file, and then by position in file
663
        repo = self.make_repository('test', format='pack-0.92')
664
        repo.lock_write()
665
        self.addCleanup(repo.unlock)
666
        repo.start_write_group()
667
        vf = repo.texts
668
        vf.add_lines(('f-id', 'rev-5'), [('f-id', 'rev-4')], ['lines\n'])
669
        vf.add_lines(('f-id', 'rev-1'), [], ['lines\n'])
670
        vf.add_lines(('f-id', 'rev-2'), [('f-id', 'rev-1')], ['lines\n'])
671
        repo.commit_write_group()
672
        # We inserted them as rev-5, rev-1, rev-2, we should get them back in
673
        # the same order
674
        stream = vf.get_record_stream([('f-id', 'rev-1'), ('f-id', 'rev-5'),
675
                                       ('f-id', 'rev-2')], 'unordered', False)
676
        keys = [r.key for r in stream]
677
        self.assertEqual([('f-id', 'rev-5'), ('f-id', 'rev-1'),
678
                          ('f-id', 'rev-2')], keys)
679
        repo.start_write_group()
680
        vf.add_lines(('f-id', 'rev-4'), [('f-id', 'rev-3')], ['lines\n'])
681
        vf.add_lines(('f-id', 'rev-3'), [('f-id', 'rev-2')], ['lines\n'])
682
        vf.add_lines(('f-id', 'rev-6'), [('f-id', 'rev-5')], ['lines\n'])
683
        repo.commit_write_group()
684
        # Request in random order, to make sure the output order isn't based on
685
        # the request
686
        request_keys = set(('f-id', 'rev-%d' % i) for i in range(1, 7))
687
        stream = vf.get_record_stream(request_keys, 'unordered', False)
688
        keys = [r.key for r in stream]
689
        # We want to get the keys back in disk order, but it doesn't matter
690
        # which pack we read from first. So this can come back in 2 orders
691
        alt1 = [('f-id', 'rev-%d' % i) for i in [4, 3, 6, 5, 1, 2]]
692
        alt2 = [('f-id', 'rev-%d' % i) for i in [5, 1, 2, 4, 3, 6]]
693
        if keys != alt1 and keys != alt2:
694
            self.fail('Returned key order did not match either expected order.'
695
                      ' expected %s or %s, not %s'
696
                      % (alt1, alt2, keys))
697
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
698
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
699
class LowLevelKnitDataTests(TestCase):
700
701
    def create_gz_content(self, text):
702
        sio = StringIO()
703
        gz_file = gzip.GzipFile(mode='wb', fileobj=sio)
704
        gz_file.write(text)
705
        gz_file.close()
706
        return sio.getvalue()
707
3789.2.4 by John Arbash Meinel
Add a multiple-record test, though it isn't quite what we want for the readv tests.
708
    def make_multiple_records(self):
709
        """Create the content for multiple records."""
710
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
711
        total_txt = []
712
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
713
                                        'foo\n'
714
                                        'bar\n'
715
                                        'end rev-id-1\n'
716
                                        % (sha1sum,))
717
        record_1 = (0, len(gz_txt), sha1sum)
718
        total_txt.append(gz_txt)
719
        sha1sum = osutils.sha('baz\n').hexdigest()
720
        gz_txt = self.create_gz_content('version rev-id-2 1 %s\n'
721
                                        'baz\n'
722
                                        'end rev-id-2\n'
723
                                        % (sha1sum,))
724
        record_2 = (record_1[1], len(gz_txt), sha1sum)
725
        total_txt.append(gz_txt)
726
        return total_txt, record_1, record_2
727
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
728
    def test_valid_knit_data(self):
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
729
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
730
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
731
                                        'foo\n'
732
                                        'bar\n'
733
                                        'end rev-id-1\n'
734
                                        % (sha1sum,))
735
        transport = MockTransport([gz_txt])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
736
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
737
        knit = KnitVersionedFiles(None, access)
738
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
739
740
        contents = list(knit._read_records_iter(records))
741
        self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'],
742
            '4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
743
744
        raw_contents = list(knit._read_records_iter_raw(records))
745
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
746
3789.2.4 by John Arbash Meinel
Add a multiple-record test, though it isn't quite what we want for the readv tests.
747
    def test_multiple_records_valid(self):
748
        total_txt, record_1, record_2 = self.make_multiple_records()
749
        transport = MockTransport([''.join(total_txt)])
750
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
751
        knit = KnitVersionedFiles(None, access)
752
        records = [(('rev-id-1',), (('rev-id-1',), record_1[0], record_1[1])),
753
                   (('rev-id-2',), (('rev-id-2',), record_2[0], record_2[1]))]
754
755
        contents = list(knit._read_records_iter(records))
756
        self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'], record_1[2]),
757
                          (('rev-id-2',), ['baz\n'], record_2[2])],
758
                         contents)
759
760
        raw_contents = list(knit._read_records_iter_raw(records))
761
        self.assertEqual([(('rev-id-1',), total_txt[0], record_1[2]),
762
                          (('rev-id-2',), total_txt[1], record_2[2])],
763
                         raw_contents)
764
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
765
    def test_not_enough_lines(self):
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
766
        sha1sum = osutils.sha('foo\n').hexdigest()
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
767
        # record says 2 lines data says 1
768
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
769
                                        'foo\n'
770
                                        'end rev-id-1\n'
771
                                        % (sha1sum,))
772
        transport = MockTransport([gz_txt])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
773
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
774
        knit = KnitVersionedFiles(None, access)
775
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
776
        self.assertRaises(errors.KnitCorrupt, list,
777
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
778
779
        # read_records_iter_raw won't detect that sort of mismatch/corruption
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
780
        raw_contents = list(knit._read_records_iter_raw(records))
781
        self.assertEqual([(('rev-id-1',),  gz_txt, sha1sum)], raw_contents)
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
782
783
    def test_too_many_lines(self):
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
784
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
785
        # record says 1 lines data says 2
786
        gz_txt = self.create_gz_content('version rev-id-1 1 %s\n'
787
                                        'foo\n'
788
                                        'bar\n'
789
                                        'end rev-id-1\n'
790
                                        % (sha1sum,))
791
        transport = MockTransport([gz_txt])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
792
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
793
        knit = KnitVersionedFiles(None, access)
794
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
795
        self.assertRaises(errors.KnitCorrupt, list,
796
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
797
798
        # read_records_iter_raw won't detect that sort of mismatch/corruption
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
799
        raw_contents = list(knit._read_records_iter_raw(records))
800
        self.assertEqual([(('rev-id-1',), gz_txt, sha1sum)], raw_contents)
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
801
802
    def test_mismatched_version_id(self):
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
803
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
804
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
805
                                        'foo\n'
806
                                        'bar\n'
807
                                        'end rev-id-1\n'
808
                                        % (sha1sum,))
809
        transport = MockTransport([gz_txt])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
810
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
811
        knit = KnitVersionedFiles(None, access)
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
812
        # We are asking for rev-id-2, but the data is rev-id-1
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
813
        records = [(('rev-id-2',), (('rev-id-2',), 0, len(gz_txt)))]
814
        self.assertRaises(errors.KnitCorrupt, list,
815
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
816
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
817
        # read_records_iter_raw detects mismatches in the header
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
818
        self.assertRaises(errors.KnitCorrupt, list,
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
819
            knit._read_records_iter_raw(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
820
821
    def test_uncompressed_data(self):
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
822
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
823
        txt = ('version rev-id-1 2 %s\n'
824
               'foo\n'
825
               'bar\n'
826
               'end rev-id-1\n'
827
               % (sha1sum,))
828
        transport = MockTransport([txt])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
829
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
830
        knit = KnitVersionedFiles(None, access)
831
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(txt)))]
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
832
833
        # We don't have valid gzip data ==> corrupt
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
834
        self.assertRaises(errors.KnitCorrupt, list,
835
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
836
837
        # read_records_iter_raw will notice the bad data
838
        self.assertRaises(errors.KnitCorrupt, list,
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
839
            knit._read_records_iter_raw(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
840
841
    def test_corrupted_data(self):
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
842
        sha1sum = osutils.sha('foo\nbar\n').hexdigest()
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
843
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
844
                                        'foo\n'
845
                                        'bar\n'
846
                                        'end rev-id-1\n'
847
                                        % (sha1sum,))
848
        # Change 2 bytes in the middle to \xff
849
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
850
        transport = MockTransport([gz_txt])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
851
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
852
        knit = KnitVersionedFiles(None, access)
853
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
854
        self.assertRaises(errors.KnitCorrupt, list,
855
            knit._read_records_iter(records))
856
        # read_records_iter_raw will barf on bad gz data
857
        self.assertRaises(errors.KnitCorrupt, list,
858
            knit._read_records_iter_raw(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
859
860
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
861
class LowLevelKnitIndexTests(TestCase):
862
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
863
    def get_knit_index(self, transport, name, mode):
864
        mapper = ConstantMapper(name)
2484.1.12 by John Arbash Meinel
Switch the layout to use a matching _knit_load_data_py.py and _knit_load_data_c.pyx
865
        from bzrlib._knit_load_data_py import _load_data_py
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
866
        self.overrideAttr(knit, '_load_data', _load_data_py)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
867
        allow_writes = lambda: 'w' in mode
868
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
869
870
    def test_create_file(self):
871
        transport = MockTransport()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
872
        index = self.get_knit_index(transport, "filename", "w")
873
        index.keys()
874
        call = transport.calls.pop(0)
875
        # call[1][1] is a StringIO - we can't test it by simple equality.
876
        self.assertEqual('put_file_non_atomic', call[0])
877
        self.assertEqual('filename.kndx', call[1][0])
878
        # With no history, _KndxIndex writes a new index:
879
        self.assertEqual(_KndxIndex.HEADER,
880
            call[1][1].getvalue())
881
        self.assertEqual({'create_parent_dir': True}, call[2])
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
882
883
    def test_read_utf8_version_id(self):
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
884
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
885
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
886
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
887
            _KndxIndex.HEADER,
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
888
            '%s option 0 1 :' % (utf8_revision_id,)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
889
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
890
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
891
        # _KndxIndex is a private class, and deals in utf8 revision_ids, not
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
892
        # Unicode revision_ids.
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
893
        self.assertEqual({(utf8_revision_id,):()},
894
            index.get_parent_map(index.keys()))
895
        self.assertFalse((unicode_revision_id,) in index.keys())
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
896
897
    def test_read_utf8_parents(self):
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
898
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
899
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
900
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
901
            _KndxIndex.HEADER,
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
902
            "version option 0 1 .%s :" % (utf8_revision_id,)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
903
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
904
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
905
        self.assertEqual({("version",):((utf8_revision_id,),)},
906
            index.get_parent_map(index.keys()))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
907
908
    def test_read_ignore_corrupted_lines(self):
909
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
910
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
911
            "corrupted",
912
            "corrupted options 0 1 .b .c ",
913
            "version options 0 1 :"
914
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
915
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
916
        self.assertEqual(1, len(index.keys()))
917
        self.assertEqual(set([("version",)]), index.keys())
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
918
919
    def test_read_corrupted_header(self):
2196.2.3 by John Arbash Meinel
Update tests and code to pass after merging bzr.dev
920
        transport = MockTransport(['not a bzr knit index header\n'])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
921
        index = self.get_knit_index(transport, "filename", "r")
922
        self.assertRaises(KnitHeaderError, index.keys)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
923
924
    def test_read_duplicate_entries(self):
925
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
926
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
927
            "parent options 0 1 :",
928
            "version options1 0 1 0 :",
929
            "version options2 1 2 .other :",
930
            "version options3 3 4 0 .other :"
931
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
932
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
933
        self.assertEqual(2, len(index.keys()))
2592.3.8 by Robert Collins
Remove unneeded pulib method lookup on private class _KnitIndex.
934
        # check that the index used is the first one written. (Specific
935
        # to KnitIndex style indices.
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
936
        self.assertEqual("1", index._dictionary_compress([("version",)]))
937
        self.assertEqual((("version",), 3, 4), index.get_position(("version",)))
938
        self.assertEqual(["options3"], index.get_options(("version",)))
939
        self.assertEqual({("version",):(("parent",), ("other",))},
940
            index.get_parent_map([("version",)]))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
941
942
    def test_read_compressed_parents(self):
943
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
944
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
945
            "a option 0 1 :",
946
            "b option 0 1 0 :",
947
            "c option 0 1 1 0 :",
948
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
949
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
950
        self.assertEqual({("b",):(("a",),), ("c",):(("b",), ("a",))},
951
            index.get_parent_map([("b",), ("c",)]))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
952
953
    def test_write_utf8_version_id(self):
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
954
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
955
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
956
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
957
            _KndxIndex.HEADER
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
958
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
959
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
960
        index.add_records([
961
            ((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
962
        call = transport.calls.pop(0)
963
        # call[1][1] is a StringIO - we can't test it by simple equality.
964
        self.assertEqual('put_file_non_atomic', call[0])
965
        self.assertEqual('filename.kndx', call[1][0])
966
        # With no history, _KndxIndex writes a new index:
967
        self.assertEqual(_KndxIndex.HEADER +
968
            "\n%s option 0 1  :" % (utf8_revision_id,),
969
            call[1][1].getvalue())
970
        self.assertEqual({'create_parent_dir': True}, call[2])
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
971
972
    def test_write_utf8_parents(self):
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
973
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
974
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
975
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
976
            _KndxIndex.HEADER
977
            ])
978
        index = self.get_knit_index(transport, "filename", "r")
979
        index.add_records([
980
            (("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
981
        call = transport.calls.pop(0)
982
        # call[1][1] is a StringIO - we can't test it by simple equality.
983
        self.assertEqual('put_file_non_atomic', call[0])
984
        self.assertEqual('filename.kndx', call[1][0])
985
        # With no history, _KndxIndex writes a new index:
986
        self.assertEqual(_KndxIndex.HEADER +
987
            "\nversion option 0 1 .%s :" % (utf8_revision_id,),
988
            call[1][1].getvalue())
989
        self.assertEqual({'create_parent_dir': True}, call[2])
990
991
    def test_keys(self):
992
        transport = MockTransport([
993
            _KndxIndex.HEADER
994
            ])
995
        index = self.get_knit_index(transport, "filename", "r")
996
997
        self.assertEqual(set(), index.keys())
998
999
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1000
        self.assertEqual(set([("a",)]), index.keys())
1001
1002
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
1003
        self.assertEqual(set([("a",)]), index.keys())
1004
1005
        index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
1006
        self.assertEqual(set([("a",), ("b",)]), index.keys())
1007
1008
    def add_a_b(self, index, random_id=None):
1009
        kwargs = {}
1010
        if random_id is not None:
1011
            kwargs["random_id"] = random_id
1012
        index.add_records([
1013
            (("a",), ["option"], (("a",), 0, 1), [("b",)]),
1014
            (("a",), ["opt"], (("a",), 1, 2), [("c",)]),
1015
            (("b",), ["option"], (("b",), 2, 3), [("a",)])
1016
            ], **kwargs)
1017
1018
    def assertIndexIsAB(self, index):
1019
        self.assertEqual({
1020
            ('a',): (('c',),),
1021
            ('b',): (('a',),),
1022
            },
1023
            index.get_parent_map(index.keys()))
1024
        self.assertEqual((("a",), 1, 2), index.get_position(("a",)))
1025
        self.assertEqual((("b",), 2, 3), index.get_position(("b",)))
1026
        self.assertEqual(["opt"], index.get_options(("a",)))
1027
1028
    def test_add_versions(self):
1029
        transport = MockTransport([
1030
            _KndxIndex.HEADER
1031
            ])
1032
        index = self.get_knit_index(transport, "filename", "r")
1033
1034
        self.add_a_b(index)
1035
        call = transport.calls.pop(0)
1036
        # call[1][1] is a StringIO - we can't test it by simple equality.
1037
        self.assertEqual('put_file_non_atomic', call[0])
1038
        self.assertEqual('filename.kndx', call[1][0])
1039
        # With no history, _KndxIndex writes a new index:
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1040
        self.assertEqual(
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1041
            _KndxIndex.HEADER +
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1042
            "\na option 0 1 .b :"
1043
            "\na opt 1 2 .c :"
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1044
            "\nb option 2 3 0 :",
1045
            call[1][1].getvalue())
1046
        self.assertEqual({'create_parent_dir': True}, call[2])
1047
        self.assertIndexIsAB(index)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1048
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1049
    def test_add_versions_random_id_is_accepted(self):
1050
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1051
            _KndxIndex.HEADER
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1052
            ])
1053
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1054
        self.add_a_b(index, random_id=True)
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1055
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1056
    def test_delay_create_and_add_versions(self):
1057
        transport = MockTransport()
1058
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1059
        index = self.get_knit_index(transport, "filename", "w")
1060
        # dir_mode=0777)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1061
        self.assertEqual([], transport.calls)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1062
        self.add_a_b(index)
1063
        #self.assertEqual(
1064
        #[    {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
1065
        #    kwargs)
1066
        # Two calls: one during which we load the existing index (and when its
1067
        # missing create it), then a second where we write the contents out.
1068
        self.assertEqual(2, len(transport.calls))
1069
        call = transport.calls.pop(0)
1070
        self.assertEqual('put_file_non_atomic', call[0])
1071
        self.assertEqual('filename.kndx', call[1][0])
1072
        # With no history, _KndxIndex writes a new index:
1073
        self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
1074
        self.assertEqual({'create_parent_dir': True}, call[2])
1075
        call = transport.calls.pop(0)
1076
        # call[1][1] is a StringIO - we can't test it by simple equality.
1077
        self.assertEqual('put_file_non_atomic', call[0])
1078
        self.assertEqual('filename.kndx', call[1][0])
1079
        # With no history, _KndxIndex writes a new index:
1080
        self.assertEqual(
1081
            _KndxIndex.HEADER +
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1082
            "\na option 0 1 .b :"
1083
            "\na opt 1 2 .c :"
1084
            "\nb option 2 3 0 :",
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1085
            call[1][1].getvalue())
1086
        self.assertEqual({'create_parent_dir': True}, call[2])
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1087
4039.3.5 by John Arbash Meinel
Add direct tests for _get_total_build_size.
1088
    def assertTotalBuildSize(self, size, keys, positions):
1089
        self.assertEqual(size,
1090
                         knit._get_total_build_size(None, keys, positions))
1091
1092
    def test__get_total_build_size(self):
1093
        positions = {
1094
            ('a',): (('fulltext', False), (('a',), 0, 100), None),
1095
            ('b',): (('line-delta', False), (('b',), 100, 21), ('a',)),
1096
            ('c',): (('line-delta', False), (('c',), 121, 35), ('b',)),
1097
            ('d',): (('line-delta', False), (('d',), 156, 12), ('b',)),
1098
            }
1099
        self.assertTotalBuildSize(100, [('a',)], positions)
1100
        self.assertTotalBuildSize(121, [('b',)], positions)
1101
        # c needs both a & b
1102
        self.assertTotalBuildSize(156, [('c',)], positions)
1103
        # we shouldn't count 'b' twice
1104
        self.assertTotalBuildSize(156, [('b',), ('c',)], positions)
1105
        self.assertTotalBuildSize(133, [('d',)], positions)
1106
        self.assertTotalBuildSize(168, [('c',), ('d',)], positions)
1107
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1108
    def test_get_position(self):
1109
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1110
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1111
            "a option 0 1 :",
1112
            "b option 1 2 :"
1113
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
1114
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1115
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1116
        self.assertEqual((("a",), 0, 1), index.get_position(("a",)))
1117
        self.assertEqual((("b",), 1, 2), index.get_position(("b",)))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1118
1119
    def test_get_method(self):
1120
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1121
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1122
            "a fulltext,unknown 0 1 :",
1123
            "b unknown,line-delta 1 2 :",
1124
            "c bad 3 4 :"
1125
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
1126
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1127
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
1128
        self.assertEqual("fulltext", index.get_method("a"))
1129
        self.assertEqual("line-delta", index.get_method("b"))
1130
        self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, "c")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1131
1132
    def test_get_options(self):
1133
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1134
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1135
            "a opt1 0 1 :",
1136
            "b opt2,opt3 1 2 :"
1137
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
1138
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1139
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
1140
        self.assertEqual(["opt1"], index.get_options("a"))
1141
        self.assertEqual(["opt2", "opt3"], index.get_options("b"))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1142
3287.5.6 by Robert Collins
Remove _KnitIndex.get_parents.
1143
    def test_get_parent_map(self):
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1144
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1145
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1146
            "a option 0 1 :",
1147
            "b option 1 2 0 .c :",
1148
            "c option 1 2 1 0 .e :"
1149
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
1150
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1151
3287.5.6 by Robert Collins
Remove _KnitIndex.get_parents.
1152
        self.assertEqual({
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1153
            ("a",):(),
1154
            ("b",):(("a",), ("c",)),
1155
            ("c",):(("b",), ("a",), ("e",)),
1156
            }, index.get_parent_map(index.keys()))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1157
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1158
    def test_impossible_parent(self):
1159
        """Test we get KnitCorrupt if the parent couldn't possibly exist."""
1160
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1161
            _KndxIndex.HEADER,
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1162
            "a option 0 1 :",
1163
            "b option 0 1 4 :"  # We don't have a 4th record
1164
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1165
        index = self.get_knit_index(transport, 'filename', 'r')
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
1166
        try:
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1167
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
1168
        except TypeError, e:
1169
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1170
                           ' not exceptions.IndexError')
1171
                and sys.version_info[0:2] >= (2,5)):
1172
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1173
                                  ' raising new style exceptions with python'
1174
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
1175
            else:
1176
                raise
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1177
1178
    def test_corrupted_parent(self):
1179
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1180
            _KndxIndex.HEADER,
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1181
            "a option 0 1 :",
1182
            "b option 0 1 :",
1183
            "c option 0 1 1v :", # Can't have a parent of '1v'
1184
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1185
        index = self.get_knit_index(transport, 'filename', 'r')
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
1186
        try:
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1187
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
1188
        except TypeError, e:
1189
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1190
                           ' not exceptions.ValueError')
1191
                and sys.version_info[0:2] >= (2,5)):
1192
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1193
                                  ' raising new style exceptions with python'
1194
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
1195
            else:
1196
                raise
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1197
1198
    def test_corrupted_parent_in_list(self):
1199
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1200
            _KndxIndex.HEADER,
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1201
            "a option 0 1 :",
1202
            "b option 0 1 :",
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
1203
            "c option 0 1 1 v :", # Can't have a parent of 'v'
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1204
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1205
        index = self.get_knit_index(transport, 'filename', 'r')
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
1206
        try:
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1207
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
1208
        except TypeError, e:
1209
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1210
                           ' not exceptions.ValueError')
1211
                and sys.version_info[0:2] >= (2,5)):
1212
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1213
                                  ' raising new style exceptions with python'
1214
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
1215
            else:
1216
                raise
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
1217
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1218
    def test_invalid_position(self):
1219
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1220
            _KndxIndex.HEADER,
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1221
            "a option 1v 1 :",
1222
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1223
        index = self.get_knit_index(transport, 'filename', 'r')
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1224
        try:
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1225
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1226
        except TypeError, e:
1227
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1228
                           ' not exceptions.ValueError')
1229
                and sys.version_info[0:2] >= (2,5)):
1230
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1231
                                  ' raising new style exceptions with python'
1232
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
1233
            else:
1234
                raise
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1235
1236
    def test_invalid_size(self):
1237
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1238
            _KndxIndex.HEADER,
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1239
            "a option 1 1v :",
1240
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1241
        index = self.get_knit_index(transport, 'filename', 'r')
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1242
        try:
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1243
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1244
        except TypeError, e:
1245
            if (str(e) == ('exceptions must be strings, classes, or instances,'
1246
                           ' not exceptions.ValueError')
1247
                and sys.version_info[0:2] >= (2,5)):
1248
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
1249
                                  ' raising new style exceptions with python'
1250
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
1251
            else:
1252
                raise
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
1253
4011.5.7 by Andrew Bennetts
Remove leading underscore from _scan_unvalidate_index, explicitly NotImplementedError it for _KndxIndex.
1254
    def test_scan_unvalidated_index_not_implemented(self):
1255
        transport = MockTransport()
1256
        index = self.get_knit_index(transport, 'filename', 'r')
1257
        self.assertRaises(
1258
            NotImplementedError, index.scan_unvalidated_index,
1259
            'dummy graph_index')
4011.5.11 by Robert Collins
Polish the KnitVersionedFiles.scan_unvalidated_index api.
1260
        self.assertRaises(
1261
            NotImplementedError, index.get_missing_compression_parents)
4011.5.7 by Andrew Bennetts
Remove leading underscore from _scan_unvalidate_index, explicitly NotImplementedError it for _KndxIndex.
1262
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
1263
    def test_short_line(self):
1264
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1265
            _KndxIndex.HEADER,
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
1266
            "a option 0 10  :",
1267
            "b option 10 10 0", # This line isn't terminated, ignored
1268
            ])
1269
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1270
        self.assertEqual(set([('a',)]), index.keys())
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
1271
1272
    def test_skip_incomplete_record(self):
1273
        # A line with bogus data should just be skipped
1274
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1275
            _KndxIndex.HEADER,
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
1276
            "a option 0 10  :",
1277
            "b option 10 10 0", # This line isn't terminated, ignored
1278
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
1279
            ])
1280
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1281
        self.assertEqual(set([('a',), ('c',)]), index.keys())
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
1282
1283
    def test_trailing_characters(self):
1284
        # A line with bogus data should just be skipped
1285
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1286
            _KndxIndex.HEADER,
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
1287
            "a option 0 10  :",
1288
            "b option 10 10 0 :a", # This line has extra trailing characters
1289
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
1290
            ])
1291
        index = self.get_knit_index(transport, "filename", "r")
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1292
        self.assertEqual(set([('a',), ('c',)]), index.keys())
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
1293
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
1294
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
1295
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1296
4913.2.20 by John Arbash Meinel
Change all of the compiled_foo to compiled_foo_feature
1297
    _test_needs_features = [compiled_knit_feature]
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
1298
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1299
    def get_knit_index(self, transport, name, mode):
1300
        mapper = ConstantMapper(name)
4573.1.1 by Andrew Bennetts
Fix imports for _knit_load_data_pyx, which was recently renamed.
1301
        from bzrlib._knit_load_data_pyx import _load_data_c
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
1302
        self.overrideAttr(knit, '_load_data', _load_data_c)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1303
        allow_writes = lambda: mode == 'w'
4985.2.1 by Vincent Ladeuil
Deploy addAttrCleanup on the whole test suite.
1304
        return _KndxIndex(transport, mapper, lambda:None,
1305
                          allow_writes, lambda:True)
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
1306
1307
4454.3.28 by John Arbash Meinel
Continue breaking things to build it up cleanly.
1308
class Test_KnitAnnotator(TestCaseWithMemoryTransport):
1309
1310
    def make_annotator(self):
1311
        factory = knit.make_pack_factory(True, True, 1)
1312
        vf = factory(self.get_transport())
1313
        return knit._KnitAnnotator(vf)
1314
1315
    def test__expand_fulltext(self):
1316
        ann = self.make_annotator()
1317
        rev_key = ('rev-id',)
4454.3.36 by John Arbash Meinel
Only cache the content objects that we will reuse.
1318
        ann._num_compression_children[rev_key] = 1
4454.3.28 by John Arbash Meinel
Continue breaking things to build it up cleanly.
1319
        res = ann._expand_record(rev_key, (('parent-id',),), None,
1320
                           ['line1\n', 'line2\n'], ('fulltext', True))
1321
        # The content object and text lines should be cached appropriately
1322
        self.assertEqual(['line1\n', 'line2'], res)
1323
        content_obj = ann._content_objects[rev_key]
1324
        self.assertEqual(['line1\n', 'line2\n'], content_obj._lines)
1325
        self.assertEqual(res, content_obj.text())
1326
        self.assertEqual(res, ann._text_cache[rev_key])
1327
4454.3.30 by John Arbash Meinel
add a bit more work to be able to process 'pending_annotations'.
1328
    def test__expand_delta_comp_parent_not_available(self):
4454.3.28 by John Arbash Meinel
Continue breaking things to build it up cleanly.
1329
        # Parent isn't available yet, so we return nothing, but queue up this
1330
        # node for later processing
1331
        ann = self.make_annotator()
1332
        rev_key = ('rev-id',)
1333
        parent_key = ('parent-id',)
1334
        record = ['0,1,1\n', 'new-line\n']
1335
        details = ('line-delta', False)
1336
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
1337
                                 record, details)
1338
        self.assertEqual(None, res)
1339
        self.assertTrue(parent_key in ann._pending_deltas)
1340
        pending = ann._pending_deltas[parent_key]
1341
        self.assertEqual(1, len(pending))
1342
        self.assertEqual((rev_key, (parent_key,), record, details), pending[0])
1343
4454.3.33 by John Arbash Meinel
Change the _expand_record code to pop out old content objects.
1344
    def test__expand_record_tracks_num_children(self):
1345
        ann = self.make_annotator()
1346
        rev_key = ('rev-id',)
1347
        rev2_key = ('rev2-id',)
1348
        parent_key = ('parent-id',)
1349
        record = ['0,1,1\n', 'new-line\n']
1350
        details = ('line-delta', False)
1351
        ann._num_compression_children[parent_key] = 2
1352
        ann._expand_record(parent_key, (), None, ['line1\n', 'line2\n'],
1353
                           ('fulltext', False))
1354
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
1355
                                 record, details)
1356
        self.assertEqual({parent_key: 1}, ann._num_compression_children)
1357
        # Expanding the second child should remove the content object, and the
1358
        # num_compression_children entry
1359
        res = ann._expand_record(rev2_key, (parent_key,), parent_key,
1360
                                 record, details)
1361
        self.assertFalse(parent_key in ann._content_objects)
1362
        self.assertEqual({}, ann._num_compression_children)
4454.3.36 by John Arbash Meinel
Only cache the content objects that we will reuse.
1363
        # We should not cache the content_objects for rev2 and rev, because
1364
        # they do not have compression children of their own.
1365
        self.assertEqual({}, ann._content_objects)
4454.3.33 by John Arbash Meinel
Change the _expand_record code to pop out old content objects.
1366
4454.3.37 by John Arbash Meinel
Add tests tha left-matching-blocks gets populated.
1367
    def test__expand_delta_records_blocks(self):
1368
        ann = self.make_annotator()
1369
        rev_key = ('rev-id',)
1370
        parent_key = ('parent-id',)
1371
        record = ['0,1,1\n', 'new-line\n']
1372
        details = ('line-delta', True)
1373
        ann._num_compression_children[parent_key] = 2
1374
        ann._expand_record(parent_key, (), None,
1375
                           ['line1\n', 'line2\n', 'line3\n'],
1376
                           ('fulltext', False))
1377
        ann._expand_record(rev_key, (parent_key,), parent_key, record, details)
4454.3.38 by John Arbash Meinel
Start using left-matching-blocks during the actual annotation.
1378
        self.assertEqual({(rev_key, parent_key): [(1, 1, 1), (3, 3, 0)]},
1379
                         ann._matching_blocks)
4454.3.37 by John Arbash Meinel
Add tests tha left-matching-blocks gets populated.
1380
        rev2_key = ('rev2-id',)
1381
        record = ['0,1,1\n', 'new-line\n']
1382
        details = ('line-delta', False)
1383
        ann._expand_record(rev2_key, (parent_key,), parent_key, record, details)
1384
        self.assertEqual([(1, 1, 2), (3, 3, 0)],
4454.3.38 by John Arbash Meinel
Start using left-matching-blocks during the actual annotation.
1385
                         ann._matching_blocks[(rev2_key, parent_key)])
1386
1387
    def test__get_parent_ann_uses_matching_blocks(self):
1388
        ann = self.make_annotator()
1389
        rev_key = ('rev-id',)
1390
        parent_key = ('parent-id',)
1391
        parent_ann = [(parent_key,)]*3
1392
        block_key = (rev_key, parent_key)
1393
        ann._annotations_cache[parent_key] = parent_ann
1394
        ann._matching_blocks[block_key] = [(0, 1, 1), (3, 3, 0)]
1395
        # We should not try to access any parent_lines content, because we know
1396
        # we already have the matching blocks
1397
        par_ann, blocks = ann._get_parent_annotations_and_matches(rev_key,
1398
                                        ['1\n', '2\n', '3\n'], parent_key)
1399
        self.assertEqual(parent_ann, par_ann)
1400
        self.assertEqual([(0, 1, 1), (3, 3, 0)], blocks)
1401
        self.assertEqual({}, ann._matching_blocks)
4454.3.37 by John Arbash Meinel
Add tests tha left-matching-blocks gets populated.
1402
4454.3.31 by John Arbash Meinel
Change the processing lines to now handle fallbacks properly.
1403
    def test__process_pending(self):
4454.3.30 by John Arbash Meinel
add a bit more work to be able to process 'pending_annotations'.
1404
        ann = self.make_annotator()
1405
        rev_key = ('rev-id',)
1406
        p1_key = ('p1-id',)
1407
        p2_key = ('p2-id',)
1408
        record = ['0,1,1\n', 'new-line\n']
1409
        details = ('line-delta', False)
1410
        p1_record = ['line1\n', 'line2\n']
4454.3.33 by John Arbash Meinel
Change the _expand_record code to pop out old content objects.
1411
        ann._num_compression_children[p1_key] = 1
4454.3.30 by John Arbash Meinel
add a bit more work to be able to process 'pending_annotations'.
1412
        res = ann._expand_record(rev_key, (p1_key,p2_key), p1_key,
1413
                                 record, details)
1414
        self.assertEqual(None, res)
1415
        # self.assertTrue(p1_key in ann._pending_deltas)
1416
        self.assertEqual({}, ann._pending_annotation)
1417
        # Now insert p1, and we should be able to expand the delta
1418
        res = ann._expand_record(p1_key, (), None, p1_record,
1419
                                 ('fulltext', False))
1420
        self.assertEqual(p1_record, res)
1421
        ann._annotations_cache[p1_key] = [(p1_key,)]*2
1422
        res = ann._process_pending(p1_key)
4454.3.31 by John Arbash Meinel
Change the processing lines to now handle fallbacks properly.
1423
        self.assertEqual([], res)
4454.3.30 by John Arbash Meinel
add a bit more work to be able to process 'pending_annotations'.
1424
        self.assertFalse(p1_key in ann._pending_deltas)
1425
        self.assertTrue(p2_key in ann._pending_annotation)
1426
        self.assertEqual({p2_key: [(rev_key, (p1_key, p2_key))]},
1427
                         ann._pending_annotation)
1428
        # Now fill in parent 2, and pending annotation should be satisfied
1429
        res = ann._expand_record(p2_key, (), None, [], ('fulltext', False))
4454.3.31 by John Arbash Meinel
Change the processing lines to now handle fallbacks properly.
1430
        ann._annotations_cache[p2_key] = []
1431
        res = ann._process_pending(p2_key)
1432
        self.assertEqual([rev_key], res)
1433
        self.assertEqual({}, ann._pending_annotation)
1434
        self.assertEqual({}, ann._pending_deltas)
4454.3.30 by John Arbash Meinel
add a bit more work to be able to process 'pending_annotations'.
1435
4454.3.28 by John Arbash Meinel
Continue breaking things to build it up cleanly.
1436
    def test_record_delta_removes_basis(self):
1437
        ann = self.make_annotator()
1438
        ann._expand_record(('parent-id',), (), None,
1439
                           ['line1\n', 'line2\n'], ('fulltext', False))
1440
        ann._num_compression_children['parent-id'] = 2
1441
4454.3.64 by John Arbash Meinel
Ensure that _KnitAnnotator also supports add_special_text.
1442
    def test_annotate_special_text(self):
1443
        ann = self.make_annotator()
1444
        vf = ann._vf
1445
        rev1_key = ('rev-1',)
1446
        rev2_key = ('rev-2',)
1447
        rev3_key = ('rev-3',)
1448
        spec_key = ('special:',)
1449
        vf.add_lines(rev1_key, [], ['initial content\n'])
1450
        vf.add_lines(rev2_key, [rev1_key], ['initial content\n',
1451
                                            'common content\n',
1452
                                            'content in 2\n'])
1453
        vf.add_lines(rev3_key, [rev1_key], ['initial content\n',
1454
                                            'common content\n',
1455
                                            'content in 3\n'])
1456
        spec_text = ('initial content\n'
1457
                     'common content\n'
1458
                     'content in 2\n'
1459
                     'content in 3\n')
1460
        ann.add_special_text(spec_key, [rev2_key, rev3_key], spec_text)
1461
        anns, lines = ann.annotate(spec_key)
1462
        self.assertEqual([(rev1_key,),
1463
                          (rev2_key, rev3_key),
1464
                          (rev2_key,),
1465
                          (rev3_key,),
1466
                         ], anns)
1467
        self.assertEqualDiff(spec_text, ''.join(lines))
1468
4454.3.28 by John Arbash Meinel
Continue breaking things to build it up cleanly.
1469
1684.3.3 by Robert Collins
Add a special cased weaves to knit converter.
1470
class KnitTests(TestCaseWithTransport):
1471
    """Class containing knit test helper routines."""
1563.2.16 by Robert Collins
Change WeaveStore into VersionedFileStore and make its versoined file class parameterisable.
1472
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1473
    def make_test_knit(self, annotate=False, name='test'):
1474
        mapper = ConstantMapper(name)
1475
        return make_file_factory(annotate, mapper)(self.get_transport())
1863.1.1 by John Arbash Meinel
Allow Versioned files to do caching if explicitly asked, and implement for Knit
1476
1477
3787.1.1 by Robert Collins
Embed the failed text in sha1 knit errors.
1478
class TestBadShaError(KnitTests):
1479
    """Tests for handling of sha errors."""
1480
4005.3.6 by Robert Collins
Support delta_closure=True with NetworkRecordStream to transmit deltas over the wire when full text extraction is required on the far end.
1481
    def test_sha_exception_has_text(self):
3787.1.1 by Robert Collins
Embed the failed text in sha1 knit errors.
1482
        # having the failed text included in the error allows for recovery.
1483
        source = self.make_test_knit()
1484
        target = self.make_test_knit(name="target")
1485
        if not source._max_delta_chain:
1486
            raise TestNotApplicable(
1487
                "cannot get delta-caused sha failures without deltas.")
1488
        # create a basis
1489
        basis = ('basis',)
1490
        broken = ('broken',)
1491
        source.add_lines(basis, (), ['foo\n'])
1492
        source.add_lines(broken, (basis,), ['foo\n', 'bar\n'])
1493
        # Seed target with a bad basis text
1494
        target.add_lines(basis, (), ['gam\n'])
1495
        target.insert_record_stream(
1496
            source.get_record_stream([broken], 'unordered', False))
1497
        err = self.assertRaises(errors.KnitCorrupt,
4005.3.6 by Robert Collins
Support delta_closure=True with NetworkRecordStream to transmit deltas over the wire when full text extraction is required on the far end.
1498
            target.get_record_stream([broken], 'unordered', True
1499
            ).next().get_bytes_as, 'chunked')
3787.1.1 by Robert Collins
Embed the failed text in sha1 knit errors.
1500
        self.assertEqual(['gam\n', 'bar\n'], err.content)
3787.1.2 by Robert Collins
Ensure SHA1KnitCorrupt formats ok.
1501
        # Test for formatting with live data
1502
        self.assertStartsWith(str(err), "Knit ")
3787.1.1 by Robert Collins
Embed the failed text in sha1 knit errors.
1503
1504
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
1505
class TestKnitIndex(KnitTests):
1506
1507
    def test_add_versions_dictionary_compresses(self):
1508
        """Adding versions to the index should update the lookup dict"""
1509
        knit = self.make_test_knit()
1510
        idx = knit._index
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1511
        idx.add_records([(('a-1',), ['fulltext'], (('a-1',), 0, 0), [])])
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
1512
        self.check_file_contents('test.kndx',
1513
            '# bzr knit index 8\n'
1514
            '\n'
1515
            'a-1 fulltext 0 0  :'
1516
            )
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1517
        idx.add_records([
1518
            (('a-2',), ['fulltext'], (('a-2',), 0, 0), [('a-1',)]),
1519
            (('a-3',), ['fulltext'], (('a-3',), 0, 0), [('a-2',)]),
1520
            ])
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
1521
        self.check_file_contents('test.kndx',
1522
            '# bzr knit index 8\n'
1523
            '\n'
1524
            'a-1 fulltext 0 0  :\n'
1525
            'a-2 fulltext 0 0 0 :\n'
1526
            'a-3 fulltext 0 0 1 :'
1527
            )
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1528
        self.assertEqual(set([('a-3',), ('a-1',), ('a-2',)]), idx.keys())
1529
        self.assertEqual({
1530
            ('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False)),
1531
            ('a-2',): ((('a-2',), 0, 0), None, (('a-1',),), ('fulltext', False)),
1532
            ('a-3',): ((('a-3',), 0, 0), None, (('a-2',),), ('fulltext', False)),
1533
            }, idx.get_build_details(idx.keys()))
1534
        self.assertEqual({('a-1',):(),
1535
            ('a-2',):(('a-1',),),
1536
            ('a-3',):(('a-2',),),},
1537
            idx.get_parent_map(idx.keys()))
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
1538
1539
    def test_add_versions_fails_clean(self):
1540
        """If add_versions fails in the middle, it restores a pristine state.
1541
1542
        Any modifications that are made to the index are reset if all versions
1543
        cannot be added.
1544
        """
1545
        # This cheats a little bit by passing in a generator which will
1546
        # raise an exception before the processing finishes
1547
        # Other possibilities would be to have an version with the wrong number
1548
        # of entries, or to make the backing transport unable to write any
1549
        # files.
1550
1551
        knit = self.make_test_knit()
1552
        idx = knit._index
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1553
        idx.add_records([(('a-1',), ['fulltext'], (('a-1',), 0, 0), [])])
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
1554
1555
        class StopEarly(Exception):
1556
            pass
1557
1558
        def generate_failure():
1559
            """Add some entries and then raise an exception"""
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1560
            yield (('a-2',), ['fulltext'], (None, 0, 0), ('a-1',))
1561
            yield (('a-3',), ['fulltext'], (None, 0, 0), ('a-2',))
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
1562
            raise StopEarly()
1563
1564
        # Assert the pre-condition
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1565
        def assertA1Only():
1566
            self.assertEqual(set([('a-1',)]), set(idx.keys()))
1567
            self.assertEqual(
1568
                {('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False))},
1569
                idx.get_build_details([('a-1',)]))
1570
            self.assertEqual({('a-1',):()}, idx.get_parent_map(idx.keys()))
1571
1572
        assertA1Only()
1573
        self.assertRaises(StopEarly, idx.add_records, generate_failure())
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
1574
        # And it shouldn't be modified
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1575
        assertA1Only()
2171.1.1 by John Arbash Meinel
Knit index files should ignore empty indexes rather than consider them corrupt.
1576
1577
    def test_knit_index_ignores_empty_files(self):
1578
        # There was a race condition in older bzr, where a ^C at the right time
1579
        # could leave an empty .kndx file, which bzr would later claim was a
1580
        # corrupted file since the header was not present. In reality, the file
1581
        # just wasn't created, so it should be ignored.
1582
        t = get_transport('.')
1583
        t.put_bytes('test.kndx', '')
1584
1585
        knit = self.make_test_knit()
1586
1587
    def test_knit_index_checks_header(self):
1588
        t = get_transport('.')
1589
        t.put_bytes('test.kndx', '# not really a knit header\n\n')
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1590
        k = self.make_test_knit()
1591
        self.assertRaises(KnitHeaderError, k.keys)
2592.3.2 by Robert Collins
Implement a get_graph for a new KnitGraphIndex that will implement a KnitIndex on top of the GraphIndex API.
1592
1593
1594
class TestGraphIndexKnit(KnitTests):
1595
    """Tests for knits using a GraphIndex rather than a KnitIndex."""
1596
1597
    def make_g_index(self, name, ref_lists=0, nodes=[]):
1598
        builder = GraphIndexBuilder(ref_lists)
1599
        for node, references, value in nodes:
1600
            builder.add_node(node, references, value)
1601
        stream = builder.finish()
1602
        trans = self.get_transport()
2890.2.1 by Robert Collins
* ``bzrlib.index.GraphIndex`` now requires a size parameter to the
1603
        size = trans.put_file(name, stream)
1604
        return GraphIndex(trans, name, size)
2592.3.2 by Robert Collins
Implement a get_graph for a new KnitGraphIndex that will implement a KnitIndex on top of the GraphIndex API.
1605
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1606
    def two_graph_index(self, deltas=False, catch_adds=False):
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1607
        """Build a two-graph index.
1608
1609
        :param deltas: If true, use underlying indices with two node-ref
1610
            lists and 'parent' set to a delta-compressed against tail.
1611
        """
2592.3.2 by Robert Collins
Implement a get_graph for a new KnitGraphIndex that will implement a KnitIndex on top of the GraphIndex API.
1612
        # build a complex graph across several indices.
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1613
        if deltas:
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1614
            # delta compression inn the index
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1615
            index1 = self.make_g_index('1', 2, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1616
                (('tip', ), 'N0 100', ([('parent', )], [], )),
1617
                (('tail', ), '', ([], []))])
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1618
            index2 = self.make_g_index('2', 2, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1619
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], [('tail', )])),
1620
                (('separate', ), '', ([], []))])
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1621
        else:
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1622
            # just blob location and graph in the index.
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1623
            index1 = self.make_g_index('1', 1, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1624
                (('tip', ), 'N0 100', ([('parent', )], )),
1625
                (('tail', ), '', ([], ))])
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1626
            index2 = self.make_g_index('2', 1, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1627
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], )),
1628
                (('separate', ), '', ([], ))])
2592.3.2 by Robert Collins
Implement a get_graph for a new KnitGraphIndex that will implement a KnitIndex on top of the GraphIndex API.
1629
        combined_index = CombinedGraphIndex([index1, index2])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1630
        if catch_adds:
1631
            self.combined_index = combined_index
1632
            self.caught_entries = []
1633
            add_callback = self.catch_add
1634
        else:
1635
            add_callback = None
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1636
        return _KnitGraphIndex(combined_index, lambda:True, deltas=deltas,
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1637
            add_callback=add_callback)
2592.3.4 by Robert Collins
Implement get_ancestry/get_ancestry_with_ghosts for KnitGraphIndex.
1638
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1639
    def test_keys(self):
1640
        index = self.two_graph_index()
1641
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1642
            set(index.keys()))
2592.3.9 by Robert Collins
Implement KnitGraphIndex.has_version.
1643
2592.3.10 by Robert Collins
Implement KnitGraphIndex.get_position.
1644
    def test_get_position(self):
1645
        index = self.two_graph_index()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1646
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position(('tip',)))
1647
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position(('parent',)))
2592.3.10 by Robert Collins
Implement KnitGraphIndex.get_position.
1648
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1649
    def test_get_method_deltas(self):
1650
        index = self.two_graph_index(deltas=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1651
        self.assertEqual('fulltext', index.get_method(('tip',)))
1652
        self.assertEqual('line-delta', index.get_method(('parent',)))
2592.3.11 by Robert Collins
Implement KnitGraphIndex.get_method.
1653
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1654
    def test_get_method_no_deltas(self):
1655
        # check that the parent-history lookup is ignored with deltas=False.
1656
        index = self.two_graph_index(deltas=False)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1657
        self.assertEqual('fulltext', index.get_method(('tip',)))
1658
        self.assertEqual('fulltext', index.get_method(('parent',)))
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1659
2592.3.14 by Robert Collins
Implement KnitGraphIndex.get_options.
1660
    def test_get_options_deltas(self):
1661
        index = self.two_graph_index(deltas=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1662
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1663
        self.assertEqual(['line-delta'], index.get_options(('parent',)))
2592.3.14 by Robert Collins
Implement KnitGraphIndex.get_options.
1664
1665
    def test_get_options_no_deltas(self):
1666
        # check that the parent-history lookup is ignored with deltas=False.
1667
        index = self.two_graph_index(deltas=False)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1668
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1669
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
1670
1671
    def test_get_parent_map(self):
1672
        index = self.two_graph_index()
1673
        self.assertEqual({('parent',):(('tail',), ('ghost',))},
1674
            index.get_parent_map([('parent',), ('ghost',)]))
2592.3.14 by Robert Collins
Implement KnitGraphIndex.get_options.
1675
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1676
    def catch_add(self, entries):
1677
        self.caught_entries.append(entries)
1678
1679
    def test_add_no_callback_errors(self):
1680
        index = self.two_graph_index()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1681
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1682
            [(('new',), 'fulltext,no-eol', (None, 50, 60), ['separate'])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1683
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1684
    def test_add_version_smoke(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1685
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1686
        index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60),
1687
            [('separate',)])])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1688
        self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1689
            self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1690
1691
    def test_add_version_delta_not_delta_index(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1692
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1693
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1694
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1695
        self.assertEqual([], self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1696
1697
    def test_add_version_same_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1698
        index = self.two_graph_index(catch_adds=True)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1699
        # options can be spelt two different ways
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1700
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1701
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
1702
        # position/length are ignored (because each pack could have fulltext or
1703
        # delta, and be at a different position.
1704
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1705
            [('parent',)])])
1706
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1707
            [('parent',)])])
1708
        # but neither should have added data:
1709
        self.assertEqual([[], [], [], []], self.caught_entries)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
1710
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1711
    def test_add_version_different_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1712
        index = self.two_graph_index(deltas=True, catch_adds=True)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1713
        # change options
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1714
        self.assertRaises(errors.KnitCorrupt, index.add_records,
3946.2.2 by Jelmer Vernooij
Remove matching test, fix handling of parentless indexes.
1715
            [(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1716
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1717
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1718
        # parents
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1719
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1720
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1721
        self.assertEqual([], self.caught_entries)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
1722
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1723
    def test_add_versions_nodeltas(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1724
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1725
        index.add_records([
1726
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1727
                (('new2',), 'fulltext', (None, 0, 6), [('new',)]),
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1728
                ])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1729
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
1730
            (('new2', ), ' 0 6', ((('new',),),))],
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1731
            sorted(self.caught_entries[0]))
1732
        self.assertEqual(1, len(self.caught_entries))
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1733
1734
    def test_add_versions_deltas(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1735
        index = self.two_graph_index(deltas=True, catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1736
        index.add_records([
1737
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1738
                (('new2',), 'line-delta', (None, 0, 6), [('new',)]),
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1739
                ])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1740
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
1741
            (('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1742
            sorted(self.caught_entries[0]))
1743
        self.assertEqual(1, len(self.caught_entries))
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1744
1745
    def test_add_versions_delta_not_delta_index(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1746
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1747
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1748
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1749
        self.assertEqual([], self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1750
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1751
    def test_add_versions_random_id_accepted(self):
1752
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1753
        index.add_records([], random_id=True)
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1754
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1755
    def test_add_versions_same_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1756
        index = self.two_graph_index(catch_adds=True)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1757
        # options can be spelt two different ways
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1758
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100),
1759
            [('parent',)])])
1760
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100),
1761
            [('parent',)])])
1762
        # position/length are ignored (because each pack could have fulltext or
1763
        # delta, and be at a different position.
1764
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1765
            [('parent',)])])
1766
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1767
            [('parent',)])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1768
        # but neither should have added data.
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1769
        self.assertEqual([[], [], [], []], self.caught_entries)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
1770
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1771
    def test_add_versions_different_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1772
        index = self.two_graph_index(deltas=True, catch_adds=True)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1773
        # change options
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1774
        self.assertRaises(errors.KnitCorrupt, index.add_records,
3946.2.2 by Jelmer Vernooij
Remove matching test, fix handling of parentless indexes.
1775
            [(('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1776
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1777
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1778
        # parents
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1779
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1780
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1781
        # change options in the second record
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1782
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1783
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)]),
3946.2.2 by Jelmer Vernooij
Remove matching test, fix handling of parentless indexes.
1784
             (('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1785
        self.assertEqual([], self.caught_entries)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1786
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1787
    def make_g_index_missing_compression_parent(self):
1788
        graph_index = self.make_g_index('missing_comp', 2,
1789
            [(('tip', ), ' 100 78',
1790
              ([('missing-parent', ), ('ghost', )], [('missing-parent', )]))])
1791
        return graph_index
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
1792
4257.4.14 by Andrew Bennetts
Add a unit test for _KnitGraphIndex.get_missing_parents, fix bug that it reveals.
1793
    def make_g_index_missing_parent(self):
1794
        graph_index = self.make_g_index('missing_parent', 2,
1795
            [(('parent', ), ' 100 78', ([], [])),
1796
             (('tip', ), ' 100 78',
1797
              ([('parent', ), ('missing-parent', )], [('parent', )])),
1798
              ])
1799
        return graph_index
1800
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1801
    def make_g_index_no_external_refs(self):
1802
        graph_index = self.make_g_index('no_external_refs', 2,
1803
            [(('rev', ), ' 100 78',
1804
              ([('parent', ), ('ghost', )], []))])
1805
        return graph_index
1806
4011.5.1 by Andrew Bennetts
Start to add _add_unvalidated_index/get_missing_compression_parents methods to _KnitGraphIndex.
1807
    def test_add_good_unvalidated_index(self):
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1808
        unvalidated = self.make_g_index_no_external_refs()
1809
        combined = CombinedGraphIndex([unvalidated])
4011.5.11 by Robert Collins
Polish the KnitVersionedFiles.scan_unvalidated_index api.
1810
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
4011.5.7 by Andrew Bennetts
Remove leading underscore from _scan_unvalidate_index, explicitly NotImplementedError it for _KndxIndex.
1811
        index.scan_unvalidated_index(unvalidated)
4011.5.1 by Andrew Bennetts
Start to add _add_unvalidated_index/get_missing_compression_parents methods to _KnitGraphIndex.
1812
        self.assertEqual(frozenset(), index.get_missing_compression_parents())
1813
4257.4.14 by Andrew Bennetts
Add a unit test for _KnitGraphIndex.get_missing_parents, fix bug that it reveals.
1814
    def test_add_missing_compression_parent_unvalidated_index(self):
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1815
        unvalidated = self.make_g_index_missing_compression_parent()
1816
        combined = CombinedGraphIndex([unvalidated])
4011.5.11 by Robert Collins
Polish the KnitVersionedFiles.scan_unvalidated_index api.
1817
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
4011.5.7 by Andrew Bennetts
Remove leading underscore from _scan_unvalidate_index, explicitly NotImplementedError it for _KndxIndex.
1818
        index.scan_unvalidated_index(unvalidated)
4011.5.6 by Andrew Bennetts
Make sure it's not possible to commit a pack write group when any versioned file has missing compression parents.
1819
        # This also checks that its only the compression parent that is
1820
        # examined, otherwise 'ghost' would also be reported as a missing
1821
        # parent.
4011.5.1 by Andrew Bennetts
Start to add _add_unvalidated_index/get_missing_compression_parents methods to _KnitGraphIndex.
1822
        self.assertEqual(
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1823
            frozenset([('missing-parent',)]),
1824
            index.get_missing_compression_parents())
4011.5.1 by Andrew Bennetts
Start to add _add_unvalidated_index/get_missing_compression_parents methods to _KnitGraphIndex.
1825
4257.4.14 by Andrew Bennetts
Add a unit test for _KnitGraphIndex.get_missing_parents, fix bug that it reveals.
1826
    def test_add_missing_noncompression_parent_unvalidated_index(self):
1827
        unvalidated = self.make_g_index_missing_parent()
1828
        combined = CombinedGraphIndex([unvalidated])
1829
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
1830
            track_external_parent_refs=True)
1831
        index.scan_unvalidated_index(unvalidated)
1832
        self.assertEqual(
1833
            frozenset([('missing-parent',)]), index.get_missing_parents())
1834
4257.4.15 by Andrew Bennetts
Add another test for _KnitGraphIndex.get_missing_parents().
1835
    def test_track_external_parent_refs(self):
1836
        g_index = self.make_g_index('empty', 2, [])
1837
        combined = CombinedGraphIndex([g_index])
1838
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
1839
            add_callback=self.catch_add, track_external_parent_refs=True)
1840
        self.caught_entries = []
1841
        index.add_records([
1842
            (('new-key',), 'fulltext,no-eol', (None, 50, 60),
1843
             [('parent-1',), ('parent-2',)])])
1844
        self.assertEqual(
1845
            frozenset([('parent-1',), ('parent-2',)]),
1846
            index.get_missing_parents())
1847
4011.5.1 by Andrew Bennetts
Start to add _add_unvalidated_index/get_missing_compression_parents methods to _KnitGraphIndex.
1848
    def test_add_unvalidated_index_with_present_external_references(self):
1849
        index = self.two_graph_index(deltas=True)
4011.5.10 by Andrew Bennetts
Replace XXX with better comment.
1850
        # Ugly hack to get at one of the underlying GraphIndex objects that
1851
        # two_graph_index built.
1852
        unvalidated = index._graph_index._indices[1]
1853
        # 'parent' is an external ref of _indices[1] (unvalidated), but is
1854
        # present in _indices[0].
4011.5.7 by Andrew Bennetts
Remove leading underscore from _scan_unvalidate_index, explicitly NotImplementedError it for _KndxIndex.
1855
        index.scan_unvalidated_index(unvalidated)
4011.5.1 by Andrew Bennetts
Start to add _add_unvalidated_index/get_missing_compression_parents methods to _KnitGraphIndex.
1856
        self.assertEqual(frozenset(), index.get_missing_compression_parents())
1857
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1858
    def make_new_missing_parent_g_index(self, name):
1859
        missing_parent = name + '-missing-parent'
1860
        graph_index = self.make_g_index(name, 2,
1861
            [((name + 'tip', ), ' 100 78',
1862
              ([(missing_parent, ), ('ghost', )], [(missing_parent, )]))])
1863
        return graph_index
1864
1865
    def test_add_mulitiple_unvalidated_indices_with_missing_parents(self):
1866
        g_index_1 = self.make_new_missing_parent_g_index('one')
1867
        g_index_2 = self.make_new_missing_parent_g_index('two')
1868
        combined = CombinedGraphIndex([g_index_1, g_index_2])
4011.5.11 by Robert Collins
Polish the KnitVersionedFiles.scan_unvalidated_index api.
1869
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
4011.5.7 by Andrew Bennetts
Remove leading underscore from _scan_unvalidate_index, explicitly NotImplementedError it for _KndxIndex.
1870
        index.scan_unvalidated_index(g_index_1)
1871
        index.scan_unvalidated_index(g_index_2)
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1872
        self.assertEqual(
1873
            frozenset([('one-missing-parent',), ('two-missing-parent',)]),
1874
            index.get_missing_compression_parents())
1875
1876
    def test_add_mulitiple_unvalidated_indices_with_mutual_dependencies(self):
1877
        graph_index_a = self.make_g_index('one', 2,
1878
            [(('parent-one', ), ' 100 78', ([('non-compression-parent',)], [])),
1879
             (('child-of-two', ), ' 100 78',
1880
              ([('parent-two',)], [('parent-two',)]))])
1881
        graph_index_b = self.make_g_index('two', 2,
1882
            [(('parent-two', ), ' 100 78', ([('non-compression-parent',)], [])),
1883
             (('child-of-one', ), ' 100 78',
1884
              ([('parent-one',)], [('parent-one',)]))])
1885
        combined = CombinedGraphIndex([graph_index_a, graph_index_b])
4011.5.11 by Robert Collins
Polish the KnitVersionedFiles.scan_unvalidated_index api.
1886
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
4011.5.7 by Andrew Bennetts
Remove leading underscore from _scan_unvalidate_index, explicitly NotImplementedError it for _KndxIndex.
1887
        index.scan_unvalidated_index(graph_index_a)
1888
        index.scan_unvalidated_index(graph_index_b)
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1889
        self.assertEqual(
1890
            frozenset([]), index.get_missing_compression_parents())
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
1891
4011.5.2 by Andrew Bennetts
Add more tests, improve existing tests, add GraphIndex._external_references()
1892
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1893
class TestNoParentsGraphIndexKnit(KnitTests):
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1894
    """Tests for knits using _KnitGraphIndex with no parents."""
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1895
1896
    def make_g_index(self, name, ref_lists=0, nodes=[]):
1897
        builder = GraphIndexBuilder(ref_lists)
1898
        for node, references in nodes:
1899
            builder.add_node(node, references)
1900
        stream = builder.finish()
1901
        trans = self.get_transport()
2890.2.1 by Robert Collins
* ``bzrlib.index.GraphIndex`` now requires a size parameter to the
1902
        size = trans.put_file(name, stream)
1903
        return GraphIndex(trans, name, size)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1904
4011.5.11 by Robert Collins
Polish the KnitVersionedFiles.scan_unvalidated_index api.
1905
    def test_add_good_unvalidated_index(self):
1906
        unvalidated = self.make_g_index('unvalidated')
1907
        combined = CombinedGraphIndex([unvalidated])
1908
        index = _KnitGraphIndex(combined, lambda: True, parents=False)
1909
        index.scan_unvalidated_index(unvalidated)
1910
        self.assertEqual(frozenset(),
1911
            index.get_missing_compression_parents())
1912
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1913
    def test_parents_deltas_incompatible(self):
1914
        index = CombinedGraphIndex([])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1915
        self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1916
            index, deltas=True, parents=False)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1917
1918
    def two_graph_index(self, catch_adds=False):
1919
        """Build a two-graph index.
1920
1921
        :param deltas: If true, use underlying indices with two node-ref
1922
            lists and 'parent' set to a delta-compressed against tail.
1923
        """
1924
        # put several versions in the index.
1925
        index1 = self.make_g_index('1', 0, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1926
            (('tip', ), 'N0 100'),
1927
            (('tail', ), '')])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1928
        index2 = self.make_g_index('2', 0, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1929
            (('parent', ), ' 100 78'),
1930
            (('separate', ), '')])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1931
        combined_index = CombinedGraphIndex([index1, index2])
1932
        if catch_adds:
1933
            self.combined_index = combined_index
1934
            self.caught_entries = []
1935
            add_callback = self.catch_add
1936
        else:
1937
            add_callback = None
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1938
        return _KnitGraphIndex(combined_index, lambda:True, parents=False,
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1939
            add_callback=add_callback)
1940
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1941
    def test_keys(self):
1942
        index = self.two_graph_index()
1943
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1944
            set(index.keys()))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1945
1946
    def test_get_position(self):
1947
        index = self.two_graph_index()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1948
        self.assertEqual((index._graph_index._indices[0], 0, 100),
1949
            index.get_position(('tip',)))
1950
        self.assertEqual((index._graph_index._indices[1], 100, 78),
1951
            index.get_position(('parent',)))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1952
1953
    def test_get_method(self):
1954
        index = self.two_graph_index()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1955
        self.assertEqual('fulltext', index.get_method(('tip',)))
1956
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1957
1958
    def test_get_options(self):
1959
        index = self.two_graph_index()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1960
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1961
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
1962
1963
    def test_get_parent_map(self):
1964
        index = self.two_graph_index()
1965
        self.assertEqual({('parent',):None},
1966
            index.get_parent_map([('parent',), ('ghost',)]))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1967
1968
    def catch_add(self, entries):
1969
        self.caught_entries.append(entries)
1970
1971
    def test_add_no_callback_errors(self):
1972
        index = self.two_graph_index()
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1973
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1974
            [(('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1975
1976
    def test_add_version_smoke(self):
1977
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1978
        index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60), [])])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1979
        self.assertEqual([[(('new', ), 'N50 60')]],
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1980
            self.caught_entries)
1981
1982
    def test_add_version_delta_not_delta_index(self):
1983
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1984
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1985
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1986
        self.assertEqual([], self.caught_entries)
1987
1988
    def test_add_version_same_dup(self):
1989
        index = self.two_graph_index(catch_adds=True)
1990
        # options can be spelt two different ways
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1991
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1992
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
1993
        # position/length are ignored (because each pack could have fulltext or
1994
        # delta, and be at a different position.
1995
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1996
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1997
        # but neither should have added data.
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1998
        self.assertEqual([[], [], [], []], self.caught_entries)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
1999
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2000
    def test_add_version_different_dup(self):
2001
        index = self.two_graph_index(catch_adds=True)
2002
        # change options
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2003
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2004
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2005
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2006
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
2007
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2008
            [(('tip',), 'fulltext', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2009
        # parents
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2010
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2011
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2012
        self.assertEqual([], self.caught_entries)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
2013
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2014
    def test_add_versions(self):
2015
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2016
        index.add_records([
2017
                (('new',), 'fulltext,no-eol', (None, 50, 60), []),
2018
                (('new2',), 'fulltext', (None, 0, 6), []),
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2019
                ])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
2020
        self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2021
            sorted(self.caught_entries[0]))
2022
        self.assertEqual(1, len(self.caught_entries))
2023
2024
    def test_add_versions_delta_not_delta_index(self):
2025
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2026
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2027
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2028
        self.assertEqual([], self.caught_entries)
2029
2030
    def test_add_versions_parents_not_parents_index(self):
2031
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2032
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2033
            [(('new',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2034
        self.assertEqual([], self.caught_entries)
2035
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
2036
    def test_add_versions_random_id_accepted(self):
2037
        index = self.two_graph_index(catch_adds=True)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2038
        index.add_records([], random_id=True)
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
2039
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2040
    def test_add_versions_same_dup(self):
2041
        index = self.two_graph_index(catch_adds=True)
2042
        # options can be spelt two different ways
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2043
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
2044
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
2045
        # position/length are ignored (because each pack could have fulltext or
2046
        # delta, and be at a different position.
2047
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
2048
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2049
        # but neither should have added data.
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2050
        self.assertEqual([[], [], [], []], self.caught_entries)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
2051
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2052
    def test_add_versions_different_dup(self):
2053
        index = self.two_graph_index(catch_adds=True)
2054
        # change options
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2055
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2056
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2057
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2058
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
2059
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2060
            [(('tip',), 'fulltext', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2061
        # parents
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2062
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2063
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2064
        # change options in the second record
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
2065
        self.assertRaises(errors.KnitCorrupt, index.add_records,
2066
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), []),
2067
             (('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
2068
        self.assertEqual([], self.caught_entries)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2069
2070
4039.3.6 by John Arbash Meinel
Turn _split_by_prefix into a classmethod, and add direct tests.
2071
class TestKnitVersionedFiles(KnitTests):
2072
4039.3.7 by John Arbash Meinel
Some direct tests for _group_keys_for_io
2073
    def assertGroupKeysForIo(self, exp_groups, keys, non_local_keys,
2074
                             positions, _min_buffer_size=None):
2075
        kvf = self.make_test_knit()
2076
        if _min_buffer_size is None:
2077
            _min_buffer_size = knit._STREAM_MIN_BUFFER_SIZE
2078
        self.assertEqual(exp_groups, kvf._group_keys_for_io(keys,
2079
                                        non_local_keys, positions,
2080
                                        _min_buffer_size=_min_buffer_size))
2081
4039.3.6 by John Arbash Meinel
Turn _split_by_prefix into a classmethod, and add direct tests.
2082
    def assertSplitByPrefix(self, expected_map, expected_prefix_order,
2083
                            keys):
2084
        split, prefix_order = KnitVersionedFiles._split_by_prefix(keys)
2085
        self.assertEqual(expected_map, split)
2086
        self.assertEqual(expected_prefix_order, prefix_order)
2087
4039.3.7 by John Arbash Meinel
Some direct tests for _group_keys_for_io
2088
    def test__group_keys_for_io(self):
2089
        ft_detail = ('fulltext', False)
2090
        ld_detail = ('line-delta', False)
2091
        f_a = ('f', 'a')
2092
        f_b = ('f', 'b')
2093
        f_c = ('f', 'c')
2094
        g_a = ('g', 'a')
2095
        g_b = ('g', 'b')
2096
        g_c = ('g', 'c')
2097
        positions = {
2098
            f_a: (ft_detail, (f_a, 0, 100), None),
2099
            f_b: (ld_detail, (f_b, 100, 21), f_a),
2100
            f_c: (ld_detail, (f_c, 180, 15), f_b),
2101
            g_a: (ft_detail, (g_a, 121, 35), None),
2102
            g_b: (ld_detail, (g_b, 156, 12), g_a),
2103
            g_c: (ld_detail, (g_c, 195, 13), g_a),
2104
            }
2105
        self.assertGroupKeysForIo([([f_a], set())],
2106
                                  [f_a], [], positions)
2107
        self.assertGroupKeysForIo([([f_a], set([f_a]))],
2108
                                  [f_a], [f_a], positions)
2109
        self.assertGroupKeysForIo([([f_a, f_b], set([]))],
2110
                                  [f_a, f_b], [], positions)
2111
        self.assertGroupKeysForIo([([f_a, f_b], set([f_b]))],
2112
                                  [f_a, f_b], [f_b], positions)
2113
        self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
2114
                                  [f_a, g_a, f_b, g_b], [], positions)
2115
        self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
2116
                                  [f_a, g_a, f_b, g_b], [], positions,
2117
                                  _min_buffer_size=150)
2118
        self.assertGroupKeysForIo([([f_a, f_b], set()), ([g_a, g_b], set())],
2119
                                  [f_a, g_a, f_b, g_b], [], positions,
2120
                                  _min_buffer_size=100)
2121
        self.assertGroupKeysForIo([([f_c], set()), ([g_b], set())],
2122
                                  [f_c, g_b], [], positions,
2123
                                  _min_buffer_size=125)
2124
        self.assertGroupKeysForIo([([g_b, f_c], set())],
2125
                                  [g_b, f_c], [], positions,
2126
                                  _min_buffer_size=125)
2127
4039.3.6 by John Arbash Meinel
Turn _split_by_prefix into a classmethod, and add direct tests.
2128
    def test__split_by_prefix(self):
2129
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2130
                                  'g': [('g', 'b'), ('g', 'a')],
2131
                                 }, ['f', 'g'],
2132
                                 [('f', 'a'), ('g', 'b'),
2133
                                  ('g', 'a'), ('f', 'b')])
2134
2135
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2136
                                  'g': [('g', 'b'), ('g', 'a')],
2137
                                 }, ['f', 'g'],
2138
                                 [('f', 'a'), ('f', 'b'),
2139
                                  ('g', 'b'), ('g', 'a')])
2140
2141
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2142
                                  'g': [('g', 'b'), ('g', 'a')],
2143
                                 }, ['f', 'g'],
2144
                                 [('f', 'a'), ('f', 'b'),
2145
                                  ('g', 'b'), ('g', 'a')])
2146
2147
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
2148
                                  'g': [('g', 'b'), ('g', 'a')],
2149
                                  '': [('a',), ('b',)]
2150
                                 }, ['f', 'g', ''],
2151
                                 [('f', 'a'), ('g', 'b'),
2152
                                  ('a',), ('b',),
2153
                                  ('g', 'a'), ('f', 'b')])
2154
2155
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2156
class TestStacking(KnitTests):
2157
2158
    def get_basis_and_test_knit(self):
2159
        basis = self.make_test_knit(name='basis')
3350.8.2 by Robert Collins
stacked get_parent_map.
2160
        basis = RecordingVersionedFilesDecorator(basis)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2161
        test = self.make_test_knit(name='test')
2162
        test.add_fallback_versioned_files(basis)
2163
        return basis, test
2164
2165
    def test_add_fallback_versioned_files(self):
2166
        basis = self.make_test_knit(name='basis')
2167
        test = self.make_test_knit(name='test')
2168
        # It must not error; other tests test that the fallback is referred to
2169
        # when accessing data.
2170
        test.add_fallback_versioned_files(basis)
2171
2172
    def test_add_lines(self):
3350.8.9 by Robert Collins
define behaviour for add_lines with stacked storage.
2173
        # lines added to the test are not added to the basis
2174
        basis, test = self.get_basis_and_test_knit()
2175
        key = ('foo',)
2176
        key_basis = ('bar',)
2177
        key_cross_border = ('quux',)
2178
        key_delta = ('zaphod',)
2179
        test.add_lines(key, (), ['foo\n'])
2180
        self.assertEqual({}, basis.get_parent_map([key]))
2181
        # lines added to the test that reference across the stack do a
2182
        # fulltext.
2183
        basis.add_lines(key_basis, (), ['foo\n'])
2184
        basis.calls = []
2185
        test.add_lines(key_cross_border, (key_basis,), ['foo\n'])
2186
        self.assertEqual('fulltext', test._index.get_method(key_cross_border))
3830.3.10 by Martin Pool
Update more stacking effort tests
2187
        # we don't even need to look at the basis to see that this should be
2188
        # stored as a fulltext
2189
        self.assertEqual([], basis.calls)
3350.8.9 by Robert Collins
define behaviour for add_lines with stacked storage.
2190
        # Subsequent adds do delta.
3350.8.14 by Robert Collins
Review feedback.
2191
        basis.calls = []
3350.8.9 by Robert Collins
define behaviour for add_lines with stacked storage.
2192
        test.add_lines(key_delta, (key_cross_border,), ['foo\n'])
2193
        self.assertEqual('line-delta', test._index.get_method(key_delta))
2194
        self.assertEqual([], basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2195
2196
    def test_annotate(self):
3350.8.8 by Robert Collins
Stacking and knits don't play nice for annotation yet.
2197
        # annotations from the test knit are answered without asking the basis
2198
        basis, test = self.get_basis_and_test_knit()
2199
        key = ('foo',)
2200
        key_basis = ('bar',)
2201
        key_missing = ('missing',)
2202
        test.add_lines(key, (), ['foo\n'])
2203
        details = test.annotate(key)
2204
        self.assertEqual([(key, 'foo\n')], details)
2205
        self.assertEqual([], basis.calls)
2206
        # But texts that are not in the test knit are looked for in the basis
2207
        # directly.
2208
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
2209
        basis.calls = []
2210
        details = test.annotate(key_basis)
2211
        self.assertEqual([(key_basis, 'foo\n'), (key_basis, 'bar\n')], details)
3350.9.1 by Robert Collins
Redo annotate more simply, using just the public interfaces for VersionedFiles.
2212
        # Not optimised to date:
2213
        # self.assertEqual([("annotate", key_basis)], basis.calls)
2214
        self.assertEqual([('get_parent_map', set([key_basis])),
2215
            ('get_parent_map', set([key_basis])),
4537.3.5 by John Arbash Meinel
Fix 3 tests that assumed it would use 'unordered' in the fallback,
2216
            ('get_record_stream', [key_basis], 'topological', True)],
3350.9.1 by Robert Collins
Redo annotate more simply, using just the public interfaces for VersionedFiles.
2217
            basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2218
2219
    def test_check(self):
3517.4.19 by Martin Pool
Update test for knit.check() to expect it to recurse into fallback vfs
2220
        # At the moment checking a stacked knit does implicitly check the
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
2221
        # fallback files.
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2222
        basis, test = self.get_basis_and_test_knit()
2223
        test.check()
2224
2225
    def test_get_parent_map(self):
3350.8.2 by Robert Collins
stacked get_parent_map.
2226
        # parents in the test knit are answered without asking the basis
2227
        basis, test = self.get_basis_and_test_knit()
2228
        key = ('foo',)
2229
        key_basis = ('bar',)
2230
        key_missing = ('missing',)
2231
        test.add_lines(key, (), [])
2232
        parent_map = test.get_parent_map([key])
2233
        self.assertEqual({key: ()}, parent_map)
2234
        self.assertEqual([], basis.calls)
2235
        # But parents that are not in the test knit are looked for in the basis
2236
        basis.add_lines(key_basis, (), [])
2237
        basis.calls = []
2238
        parent_map = test.get_parent_map([key, key_basis, key_missing])
2239
        self.assertEqual({key: (),
2240
            key_basis: ()}, parent_map)
2241
        self.assertEqual([("get_parent_map", set([key_basis, key_missing]))],
2242
            basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2243
3350.8.7 by Robert Collins
get_record_stream for fulltexts working (but note extreme memory use!).
2244
    def test_get_record_stream_unordered_fulltexts(self):
2245
        # records from the test knit are answered without asking the basis:
2246
        basis, test = self.get_basis_and_test_knit()
2247
        key = ('foo',)
2248
        key_basis = ('bar',)
2249
        key_missing = ('missing',)
2250
        test.add_lines(key, (), ['foo\n'])
2251
        records = list(test.get_record_stream([key], 'unordered', True))
2252
        self.assertEqual(1, len(records))
2253
        self.assertEqual([], basis.calls)
2254
        # Missing (from test knit) objects are retrieved from the basis:
2255
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
2256
        basis.calls = []
2257
        records = list(test.get_record_stream([key_basis, key_missing],
2258
            'unordered', True))
2259
        self.assertEqual(2, len(records))
2260
        calls = list(basis.calls)
2261
        for record in records:
2262
            self.assertSubset([record.key], (key_basis, key_missing))
2263
            if record.key == key_missing:
2264
                self.assertIsInstance(record, AbsentContentFactory)
2265
            else:
2266
                reference = list(basis.get_record_stream([key_basis],
2267
                    'unordered', True))[0]
2268
                self.assertEqual(reference.key, record.key)
2269
                self.assertEqual(reference.sha1, record.sha1)
2270
                self.assertEqual(reference.storage_kind, record.storage_kind)
2271
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
2272
                    record.get_bytes_as(record.storage_kind))
2273
                self.assertEqual(reference.get_bytes_as('fulltext'),
2274
                    record.get_bytes_as('fulltext'))
3350.8.14 by Robert Collins
Review feedback.
2275
        # It's not strictly minimal, but it seems reasonable for now for it to
3350.8.7 by Robert Collins
get_record_stream for fulltexts working (but note extreme memory use!).
2276
        # ask which fallbacks have which parents.
2277
        self.assertEqual([
2278
            ("get_parent_map", set([key_basis, key_missing])),
2279
            ("get_record_stream", [key_basis], 'unordered', True)],
2280
            calls)
2281
2282
    def test_get_record_stream_ordered_fulltexts(self):
2283
        # ordering is preserved down into the fallback store.
2284
        basis, test = self.get_basis_and_test_knit()
2285
        key = ('foo',)
2286
        key_basis = ('bar',)
2287
        key_basis_2 = ('quux',)
2288
        key_missing = ('missing',)
2289
        test.add_lines(key, (key_basis,), ['foo\n'])
2290
        # Missing (from test knit) objects are retrieved from the basis:
2291
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
2292
        basis.add_lines(key_basis_2, (), ['quux\n'])
2293
        basis.calls = []
2294
        # ask for in non-topological order
2295
        records = list(test.get_record_stream(
2296
            [key, key_basis, key_missing, key_basis_2], 'topological', True))
2297
        self.assertEqual(4, len(records))
2298
        results = []
2299
        for record in records:
2300
            self.assertSubset([record.key],
2301
                (key_basis, key_missing, key_basis_2, key))
2302
            if record.key == key_missing:
2303
                self.assertIsInstance(record, AbsentContentFactory)
2304
            else:
2305
                results.append((record.key, record.sha1, record.storage_kind,
2306
                    record.get_bytes_as('fulltext')))
2307
        calls = list(basis.calls)
2308
        order = [record[0] for record in results]
2309
        self.assertEqual([key_basis_2, key_basis, key], order)
2310
        for result in results:
2311
            if result[0] == key:
2312
                source = test
2313
            else:
2314
                source = basis
2315
            record = source.get_record_stream([result[0]], 'unordered',
2316
                True).next()
2317
            self.assertEqual(record.key, result[0])
2318
            self.assertEqual(record.sha1, result[1])
4005.3.6 by Robert Collins
Support delta_closure=True with NetworkRecordStream to transmit deltas over the wire when full text extraction is required on the far end.
2319
            # We used to check that the storage kind matched, but actually it
2320
            # depends on whether it was sourced from the basis, or in a single
2321
            # group, because asking for full texts returns proxy objects to a
2322
            # _ContentMapGenerator object; so checking the kind is unneeded.
3350.8.7 by Robert Collins
get_record_stream for fulltexts working (but note extreme memory use!).
2323
            self.assertEqual(record.get_bytes_as('fulltext'), result[3])
3350.8.14 by Robert Collins
Review feedback.
2324
        # It's not strictly minimal, but it seems reasonable for now for it to
3350.8.7 by Robert Collins
get_record_stream for fulltexts working (but note extreme memory use!).
2325
        # ask which fallbacks have which parents.
2326
        self.assertEqual([
2327
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
4537.3.5 by John Arbash Meinel
Fix 3 tests that assumed it would use 'unordered' in the fallback,
2328
            # topological is requested from the fallback, because that is what
2329
            # was requested at the top level.
2330
            ("get_record_stream", [key_basis_2, key_basis], 'topological', True)],
3350.8.7 by Robert Collins
get_record_stream for fulltexts working (but note extreme memory use!).
2331
            calls)
2332
3350.8.6 by Robert Collins
get_record_stream stacking for delta access.
2333
    def test_get_record_stream_unordered_deltas(self):
2334
        # records from the test knit are answered without asking the basis:
2335
        basis, test = self.get_basis_and_test_knit()
2336
        key = ('foo',)
2337
        key_basis = ('bar',)
2338
        key_missing = ('missing',)
2339
        test.add_lines(key, (), ['foo\n'])
2340
        records = list(test.get_record_stream([key], 'unordered', False))
2341
        self.assertEqual(1, len(records))
2342
        self.assertEqual([], basis.calls)
2343
        # Missing (from test knit) objects are retrieved from the basis:
2344
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
2345
        basis.calls = []
2346
        records = list(test.get_record_stream([key_basis, key_missing],
2347
            'unordered', False))
2348
        self.assertEqual(2, len(records))
2349
        calls = list(basis.calls)
2350
        for record in records:
2351
            self.assertSubset([record.key], (key_basis, key_missing))
2352
            if record.key == key_missing:
2353
                self.assertIsInstance(record, AbsentContentFactory)
2354
            else:
2355
                reference = list(basis.get_record_stream([key_basis],
2356
                    'unordered', False))[0]
2357
                self.assertEqual(reference.key, record.key)
2358
                self.assertEqual(reference.sha1, record.sha1)
2359
                self.assertEqual(reference.storage_kind, record.storage_kind)
2360
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
2361
                    record.get_bytes_as(record.storage_kind))
3350.8.14 by Robert Collins
Review feedback.
2362
        # It's not strictly minimal, but it seems reasonable for now for it to
3350.8.6 by Robert Collins
get_record_stream stacking for delta access.
2363
        # ask which fallbacks have which parents.
2364
        self.assertEqual([
2365
            ("get_parent_map", set([key_basis, key_missing])),
2366
            ("get_record_stream", [key_basis], 'unordered', False)],
2367
            calls)
2368
2369
    def test_get_record_stream_ordered_deltas(self):
2370
        # ordering is preserved down into the fallback store.
2371
        basis, test = self.get_basis_and_test_knit()
2372
        key = ('foo',)
2373
        key_basis = ('bar',)
2374
        key_basis_2 = ('quux',)
2375
        key_missing = ('missing',)
2376
        test.add_lines(key, (key_basis,), ['foo\n'])
2377
        # Missing (from test knit) objects are retrieved from the basis:
2378
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
2379
        basis.add_lines(key_basis_2, (), ['quux\n'])
2380
        basis.calls = []
2381
        # ask for in non-topological order
2382
        records = list(test.get_record_stream(
2383
            [key, key_basis, key_missing, key_basis_2], 'topological', False))
2384
        self.assertEqual(4, len(records))
2385
        results = []
2386
        for record in records:
2387
            self.assertSubset([record.key],
2388
                (key_basis, key_missing, key_basis_2, key))
2389
            if record.key == key_missing:
2390
                self.assertIsInstance(record, AbsentContentFactory)
2391
            else:
2392
                results.append((record.key, record.sha1, record.storage_kind,
2393
                    record.get_bytes_as(record.storage_kind)))
2394
        calls = list(basis.calls)
2395
        order = [record[0] for record in results]
2396
        self.assertEqual([key_basis_2, key_basis, key], order)
2397
        for result in results:
2398
            if result[0] == key:
2399
                source = test
2400
            else:
2401
                source = basis
2402
            record = source.get_record_stream([result[0]], 'unordered',
2403
                False).next()
2404
            self.assertEqual(record.key, result[0])
2405
            self.assertEqual(record.sha1, result[1])
2406
            self.assertEqual(record.storage_kind, result[2])
2407
            self.assertEqual(record.get_bytes_as(record.storage_kind), result[3])
3350.8.14 by Robert Collins
Review feedback.
2408
        # It's not strictly minimal, but it seems reasonable for now for it to
3350.8.6 by Robert Collins
get_record_stream stacking for delta access.
2409
        # ask which fallbacks have which parents.
2410
        self.assertEqual([
2411
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
2412
            ("get_record_stream", [key_basis_2, key_basis], 'topological', False)],
2413
            calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2414
2415
    def test_get_sha1s(self):
3350.8.3 by Robert Collins
VF.get_sha1s needed changing to be stackable.
2416
        # sha1's in the test knit are answered without asking the basis
2417
        basis, test = self.get_basis_and_test_knit()
2418
        key = ('foo',)
2419
        key_basis = ('bar',)
2420
        key_missing = ('missing',)
2421
        test.add_lines(key, (), ['foo\n'])
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
2422
        key_sha1sum = osutils.sha('foo\n').hexdigest()
3350.8.3 by Robert Collins
VF.get_sha1s needed changing to be stackable.
2423
        sha1s = test.get_sha1s([key])
2424
        self.assertEqual({key: key_sha1sum}, sha1s)
2425
        self.assertEqual([], basis.calls)
2426
        # But texts that are not in the test knit are looked for in the basis
2427
        # directly (rather than via text reconstruction) so that remote servers
2428
        # etc don't have to answer with full content.
2429
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
2430
        basis_sha1sum = osutils.sha('foo\nbar\n').hexdigest()
3350.8.3 by Robert Collins
VF.get_sha1s needed changing to be stackable.
2431
        basis.calls = []
2432
        sha1s = test.get_sha1s([key, key_missing, key_basis])
2433
        self.assertEqual({key: key_sha1sum,
2434
            key_basis: basis_sha1sum}, sha1s)
2435
        self.assertEqual([("get_sha1s", set([key_basis, key_missing]))],
2436
            basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2437
2438
    def test_insert_record_stream(self):
3350.8.10 by Robert Collins
Stacked insert_record_stream.
2439
        # records are inserted as normal; insert_record_stream builds on
3350.8.14 by Robert Collins
Review feedback.
2440
        # add_lines, so a smoke test should be all that's needed:
3350.8.10 by Robert Collins
Stacked insert_record_stream.
2441
        key = ('foo',)
2442
        key_basis = ('bar',)
2443
        key_delta = ('zaphod',)
2444
        basis, test = self.get_basis_and_test_knit()
2445
        source = self.make_test_knit(name='source')
2446
        basis.add_lines(key_basis, (), ['foo\n'])
2447
        basis.calls = []
2448
        source.add_lines(key_basis, (), ['foo\n'])
2449
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
2450
        stream = source.get_record_stream([key_delta], 'unordered', False)
2451
        test.insert_record_stream(stream)
3830.3.9 by Martin Pool
Simplify kvf insert_record_stream; add has_key shorthand methods; update stacking effort tests
2452
        # XXX: this does somewhat too many calls in making sure of whether it
2453
        # has to recreate the full text.
2454
        self.assertEqual([("get_parent_map", set([key_basis])),
2455
             ('get_parent_map', set([key_basis])),
3830.3.10 by Martin Pool
Update more stacking effort tests
2456
             ('get_record_stream', [key_basis], 'unordered', True)],
3350.8.10 by Robert Collins
Stacked insert_record_stream.
2457
            basis.calls)
2458
        self.assertEqual({key_delta:(key_basis,)},
2459
            test.get_parent_map([key_delta]))
2460
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
2461
            'unordered', True).next().get_bytes_as('fulltext'))
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2462
2463
    def test_iter_lines_added_or_present_in_keys(self):
3350.8.5 by Robert Collins
Iter_lines_added_or_present_in_keys stacks.
2464
        # Lines from the basis are returned, and lines for a given key are only
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
2465
        # returned once.
3350.8.5 by Robert Collins
Iter_lines_added_or_present_in_keys stacks.
2466
        key1 = ('foo1',)
2467
        key2 = ('foo2',)
2468
        # all sources are asked for keys:
2469
        basis, test = self.get_basis_and_test_knit()
2470
        basis.add_lines(key1, (), ["foo"])
2471
        basis.calls = []
2472
        lines = list(test.iter_lines_added_or_present_in_keys([key1]))
2473
        self.assertEqual([("foo\n", key1)], lines)
2474
        self.assertEqual([("iter_lines_added_or_present_in_keys", set([key1]))],
2475
            basis.calls)
2476
        # keys in both are not duplicated:
2477
        test.add_lines(key2, (), ["bar\n"])
2478
        basis.add_lines(key2, (), ["bar\n"])
2479
        basis.calls = []
2480
        lines = list(test.iter_lines_added_or_present_in_keys([key2]))
2481
        self.assertEqual([("bar\n", key2)], lines)
2482
        self.assertEqual([], basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2483
2484
    def test_keys(self):
3350.8.4 by Robert Collins
Vf.keys() stacking support.
2485
        key1 = ('foo1',)
2486
        key2 = ('foo2',)
2487
        # all sources are asked for keys:
2488
        basis, test = self.get_basis_and_test_knit()
2489
        keys = test.keys()
2490
        self.assertEqual(set(), set(keys))
2491
        self.assertEqual([("keys",)], basis.calls)
2492
        # keys from a basis are returned:
2493
        basis.add_lines(key1, (), [])
2494
        basis.calls = []
2495
        keys = test.keys()
2496
        self.assertEqual(set([key1]), set(keys))
2497
        self.assertEqual([("keys",)], basis.calls)
2498
        # keys in both are not duplicated:
2499
        test.add_lines(key2, (), [])
2500
        basis.add_lines(key2, (), [])
2501
        basis.calls = []
2502
        keys = test.keys()
2503
        self.assertEqual(2, len(keys))
2504
        self.assertEqual(set([key1, key2]), set(keys))
2505
        self.assertEqual([("keys",)], basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2506
2507
    def test_add_mpdiffs(self):
3350.8.11 by Robert Collins
Stacked add_mpdiffs.
2508
        # records are inserted as normal; add_mpdiff builds on
3350.8.14 by Robert Collins
Review feedback.
2509
        # add_lines, so a smoke test should be all that's needed:
3350.8.11 by Robert Collins
Stacked add_mpdiffs.
2510
        key = ('foo',)
2511
        key_basis = ('bar',)
2512
        key_delta = ('zaphod',)
2513
        basis, test = self.get_basis_and_test_knit()
2514
        source = self.make_test_knit(name='source')
2515
        basis.add_lines(key_basis, (), ['foo\n'])
2516
        basis.calls = []
2517
        source.add_lines(key_basis, (), ['foo\n'])
2518
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
2519
        diffs = source.make_mpdiffs([key_delta])
2520
        test.add_mpdiffs([(key_delta, (key_basis,),
2521
            source.get_sha1s([key_delta])[key_delta], diffs[0])])
2522
        self.assertEqual([("get_parent_map", set([key_basis])),
3830.3.10 by Martin Pool
Update more stacking effort tests
2523
            ('get_record_stream', [key_basis], 'unordered', True),],
3350.8.11 by Robert Collins
Stacked add_mpdiffs.
2524
            basis.calls)
2525
        self.assertEqual({key_delta:(key_basis,)},
2526
            test.get_parent_map([key_delta]))
2527
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
2528
            'unordered', True).next().get_bytes_as('fulltext'))
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
2529
2530
    def test_make_mpdiffs(self):
3350.8.12 by Robert Collins
Stacked make_mpdiffs.
2531
        # Generating an mpdiff across a stacking boundary should detect parent
2532
        # texts regions.
2533
        key = ('foo',)
2534
        key_left = ('bar',)
2535
        key_right = ('zaphod',)
2536
        basis, test = self.get_basis_and_test_knit()
2537
        basis.add_lines(key_left, (), ['bar\n'])
2538
        basis.add_lines(key_right, (), ['zaphod\n'])
2539
        basis.calls = []
2540
        test.add_lines(key, (key_left, key_right),
2541
            ['bar\n', 'foo\n', 'zaphod\n'])
2542
        diffs = test.make_mpdiffs([key])
2543
        self.assertEqual([
2544
            multiparent.MultiParent([multiparent.ParentText(0, 0, 0, 1),
2545
                multiparent.NewText(['foo\n']),
2546
                multiparent.ParentText(1, 0, 2, 1)])],
2547
            diffs)
3830.3.10 by Martin Pool
Update more stacking effort tests
2548
        self.assertEqual(3, len(basis.calls))
3350.8.12 by Robert Collins
Stacked make_mpdiffs.
2549
        self.assertEqual([
2550
            ("get_parent_map", set([key_left, key_right])),
2551
            ("get_parent_map", set([key_left, key_right])),
2552
            ],
3830.3.10 by Martin Pool
Update more stacking effort tests
2553
            basis.calls[:-1])
2554
        last_call = basis.calls[-1]
3350.8.14 by Robert Collins
Review feedback.
2555
        self.assertEqual('get_record_stream', last_call[0])
2556
        self.assertEqual(set([key_left, key_right]), set(last_call[1]))
4537.3.5 by John Arbash Meinel
Fix 3 tests that assumed it would use 'unordered' in the fallback,
2557
        self.assertEqual('topological', last_call[2])
3350.8.14 by Robert Collins
Review feedback.
2558
        self.assertEqual(True, last_call[3])
4005.3.6 by Robert Collins
Support delta_closure=True with NetworkRecordStream to transmit deltas over the wire when full text extraction is required on the far end.
2559
2560
2561
class TestNetworkBehaviour(KnitTests):
2562
    """Tests for getting data out of/into knits over the network."""
2563
2564
    def test_include_delta_closure_generates_a_knit_delta_closure(self):
2565
        vf = self.make_test_knit(name='test')
2566
        # put in three texts, giving ft, delta, delta
2567
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
2568
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
2569
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
2570
        # But heuristics could interfere, so check what happened:
2571
        self.assertEqual(['knit-ft-gz', 'knit-delta-gz', 'knit-delta-gz'],
2572
            [record.storage_kind for record in
2573
             vf.get_record_stream([('base',), ('d1',), ('d2',)],
2574
                'topological', False)])
2575
        # generate a stream of just the deltas include_delta_closure=True,
2576
        # serialise to the network, and check that we get a delta closure on the wire.
2577
        stream = vf.get_record_stream([('d1',), ('d2',)], 'topological', True)
2578
        netb = [record.get_bytes_as(record.storage_kind) for record in stream]
2579
        # The first bytes should be a memo from _ContentMapGenerator, and the
2580
        # second bytes should be empty (because its a API proxy not something
2581
        # for wire serialisation.
2582
        self.assertEqual('', netb[1])
2583
        bytes = netb[0]
2584
        kind, line_end = network_bytes_to_kind_and_offset(bytes)
2585
        self.assertEqual('knit-delta-closure', kind)
2586
2587
2588
class TestContentMapGenerator(KnitTests):
2589
    """Tests for ContentMapGenerator"""
2590
2591
    def test_get_record_stream_gives_records(self):
2592
        vf = self.make_test_knit(name='test')
2593
        # put in three texts, giving ft, delta, delta
2594
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
2595
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
2596
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
2597
        keys = [('d1',), ('d2',)]
2598
        generator = _VFContentMapGenerator(vf, keys,
2599
            global_map=vf.get_parent_map(keys))
2600
        for record in generator.get_record_stream():
2601
            if record.key == ('d1',):
2602
                self.assertEqual('d1\n', record.get_bytes_as('fulltext'))
2603
            else:
2604
                self.assertEqual('d2\n', record.get_bytes_as('fulltext'))
2605
2606
    def test_get_record_stream_kinds_are_raw(self):
2607
        vf = self.make_test_knit(name='test')
2608
        # put in three texts, giving ft, delta, delta
2609
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
2610
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
2611
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
2612
        keys = [('base',), ('d1',), ('d2',)]
2613
        generator = _VFContentMapGenerator(vf, keys,
2614
            global_map=vf.get_parent_map(keys))
2615
        kinds = {('base',): 'knit-delta-closure',
2616
            ('d1',): 'knit-delta-closure-ref',
2617
            ('d2',): 'knit-delta-closure-ref',
2618
            }
2619
        for record in generator.get_record_stream():
2620
            self.assertEqual(kinds[record.key], record.storage_kind)