/brz/remove-bazaar

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