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