/brz/remove-bazaar

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