~bzr-pqm/bzr/bzr.dev

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