/brz/remove-bazaar

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