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