/brz/remove-bazaar

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