/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2484.1.12 by John Arbash Meinel
Switch the layout to use a matching _knit_load_data_py.py and _knit_load_data_c.pyx
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tests for Knit data structure"""
18
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
19
from cStringIO import StringIO
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
20
import difflib
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
21
import gzip
22
import sha
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
23
import sys
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
24
2196.2.5 by John Arbash Meinel
Add an exception class when the knit index storage method is unknown, and properly test for it
25
from bzrlib import (
26
    errors,
2484.1.5 by John Arbash Meinel
Simplistic implementations of custom parsers for options and parents
27
    generate_ids,
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
28
    knit,
3350.8.12 by Robert Collins
Stacked make_mpdiffs.
29
    multiparent,
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
30
    pack,
2196.2.5 by John Arbash Meinel
Add an exception class when the knit index storage method is unknown, and properly test for it
31
    )
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
32
from bzrlib.errors import (
33
    RevisionAlreadyPresent,
34
    KnitHeaderError,
35
    RevisionNotPresent,
36
    NoSuchFile,
37
    )
2592.3.1 by Robert Collins
Allow giving KnitVersionedFile an index object to use rather than implicitly creating one.
38
from bzrlib.index import *
1684.3.3 by Robert Collins
Add a special cased weaves to knit converter.
39
from bzrlib.knit import (
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
40
    AnnotatedKnitContent,
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
41
    KnitContent,
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
42
    KnitSequenceMatcher,
43
    KnitVersionedFiles,
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
44
    PlainKnitContent,
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
45
    _DirectPackAccess,
46
    _KndxIndex,
47
    _KnitGraphIndex,
48
    _KnitKeyAccess,
49
    make_file_factory,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
50
    )
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
51
from bzrlib.osutils import split_lines
3316.2.13 by Robert Collins
* ``VersionedFile.annotate_iter`` is deprecated. While in principal this
52
from bzrlib.symbol_versioning import one_four
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,
59
    )
2745.5.3 by Robert Collins
* Move transport logging into a new transport class
60
from bzrlib.transport import get_transport
1563.2.13 by Robert Collins
InterVersionedFile implemented.
61
from bzrlib.transport.memory import MemoryTransport
3052.2.3 by Robert Collins
Handle insert_data_stream of an unannotated stream into an annotated knit.
62
from bzrlib.tuned_gzip import GzipFile
3350.8.2 by Robert Collins
stacked get_parent_map.
63
from bzrlib.versionedfile import (
3350.8.6 by Robert Collins
get_record_stream stacking for delta access.
64
    AbsentContentFactory,
3350.8.2 by Robert Collins
stacked get_parent_map.
65
    ConstantMapper,
66
    RecordingVersionedFilesDecorator,
67
    )
1684.3.3 by Robert Collins
Add a special cased weaves to knit converter.
68
69
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
70
class _CompiledKnitFeature(Feature):
71
72
    def _probe(self):
73
        try:
2484.1.12 by John Arbash Meinel
Switch the layout to use a matching _knit_load_data_py.py and _knit_load_data_c.pyx
74
            import bzrlib._knit_load_data_c
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
75
        except ImportError:
76
            return False
77
        return True
78
79
    def feature_name(self):
2484.1.12 by John Arbash Meinel
Switch the layout to use a matching _knit_load_data_py.py and _knit_load_data_c.pyx
80
        return 'bzrlib._knit_load_data_c'
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
81
82
CompiledKnitFeature = _CompiledKnitFeature()
83
84
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
85
class KnitContentTestsMixin(object):
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
86
87
    def test_constructor(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
88
        content = self._make_content([])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
89
90
    def test_text(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
91
        content = self._make_content([])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
92
        self.assertEqual(content.text(), [])
93
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
94
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
95
        self.assertEqual(content.text(), ["text1", "text2"])
96
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
97
    def test_copy(self):
98
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
99
        copy = content.copy()
100
        self.assertIsInstance(copy, content.__class__)
101
        self.assertEqual(copy.annotate(), content.annotate())
102
103
    def assertDerivedBlocksEqual(self, source, target, noeol=False):
104
        """Assert that the derived matching blocks match real output"""
105
        source_lines = source.splitlines(True)
106
        target_lines = target.splitlines(True)
107
        def nl(line):
108
            if noeol and not line.endswith('\n'):
109
                return line + '\n'
110
            else:
111
                return line
112
        source_content = self._make_content([(None, nl(l)) for l in source_lines])
113
        target_content = self._make_content([(None, nl(l)) for l in target_lines])
114
        line_delta = source_content.line_delta(target_content)
115
        delta_blocks = list(KnitContent.get_line_delta_blocks(line_delta,
116
            source_lines, target_lines))
117
        matcher = KnitSequenceMatcher(None, source_lines, target_lines)
118
        matcher_blocks = list(list(matcher.get_matching_blocks()))
119
        self.assertEqual(matcher_blocks, delta_blocks)
120
121
    def test_get_line_delta_blocks(self):
122
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'q\nc\n')
123
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1)
124
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1A)
125
        self.assertDerivedBlocksEqual(TEXT_1, TEXT_1B)
126
        self.assertDerivedBlocksEqual(TEXT_1B, TEXT_1A)
127
        self.assertDerivedBlocksEqual(TEXT_1A, TEXT_1B)
128
        self.assertDerivedBlocksEqual(TEXT_1A, '')
129
        self.assertDerivedBlocksEqual('', TEXT_1A)
130
        self.assertDerivedBlocksEqual('', '')
131
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd')
132
133
    def test_get_line_delta_blocks_noeol(self):
134
        """Handle historical knit deltas safely
135
136
        Some existing knit deltas don't consider the last line to differ
137
        when the only difference whether it has a final newline.
138
139
        New knit deltas appear to always consider the last line to differ
140
        in this case.
141
        """
142
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\nd\n', noeol=True)
143
        self.assertDerivedBlocksEqual('a\nb\nc\nd\n', 'a\nb\nc', noeol=True)
144
        self.assertDerivedBlocksEqual('a\nb\nc\n', 'a\nb\nc', noeol=True)
145
        self.assertDerivedBlocksEqual('a\nb\nc', 'a\nb\nc\n', noeol=True)
146
147
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
148
TEXT_1 = """\
149
Banana cup cakes:
150
151
- bananas
152
- eggs
153
- broken tea cups
154
"""
155
156
TEXT_1A = """\
157
Banana cup cake recipe
158
(serves 6)
159
160
- bananas
161
- eggs
162
- broken tea cups
163
- self-raising flour
164
"""
165
166
TEXT_1B = """\
167
Banana cup cake recipe
168
169
- bananas (do not use plantains!!!)
170
- broken tea cups
171
- flour
172
"""
173
174
delta_1_1a = """\
175
0,1,2
176
Banana cup cake recipe
177
(serves 6)
178
5,5,1
179
- self-raising flour
180
"""
181
182
TEXT_2 = """\
183
Boeuf bourguignon
184
185
- beef
186
- red wine
187
- small onions
188
- carrot
189
- mushrooms
190
"""
191
192
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
193
class TestPlainKnitContent(TestCase, KnitContentTestsMixin):
194
195
    def _make_content(self, lines):
196
        annotated_content = AnnotatedKnitContent(lines)
197
        return PlainKnitContent(annotated_content.text(), 'bogus')
198
199
    def test_annotate(self):
200
        content = self._make_content([])
201
        self.assertEqual(content.annotate(), [])
202
203
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
204
        self.assertEqual(content.annotate(),
205
            [("bogus", "text1"), ("bogus", "text2")])
206
207
    def test_line_delta(self):
208
        content1 = self._make_content([("", "a"), ("", "b")])
209
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
210
        self.assertEqual(content1.line_delta(content2),
211
            [(1, 2, 2, ["a", "c"])])
212
213
    def test_line_delta_iter(self):
214
        content1 = self._make_content([("", "a"), ("", "b")])
215
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
216
        it = content1.line_delta_iter(content2)
217
        self.assertEqual(it.next(), (1, 2, 2, ["a", "c"]))
218
        self.assertRaises(StopIteration, it.next)
219
220
221
class TestAnnotatedKnitContent(TestCase, KnitContentTestsMixin):
222
223
    def _make_content(self, lines):
224
        return AnnotatedKnitContent(lines)
225
226
    def test_annotate(self):
227
        content = self._make_content([])
228
        self.assertEqual(content.annotate(), [])
229
230
        content = self._make_content([("origin1", "text1"), ("origin2", "text2")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
231
        self.assertEqual(content.annotate(),
232
            [("origin1", "text1"), ("origin2", "text2")])
233
234
    def test_line_delta(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
235
        content1 = self._make_content([("", "a"), ("", "b")])
236
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
237
        self.assertEqual(content1.line_delta(content2),
238
            [(1, 2, 2, [("", "a"), ("", "c")])])
239
240
    def test_line_delta_iter(self):
2794.1.2 by Robert Collins
Nuke versioned file add/get delta support, allowing easy simplification of unannotated Content, reducing memory copies and friction during commit on unannotated texts.
241
        content1 = self._make_content([("", "a"), ("", "b")])
242
        content2 = self._make_content([("", "a"), ("", "a"), ("", "c")])
2151.1.1 by John Arbash Meinel
(Dmitry Vasiliev) Tune KnitContent and add tests
243
        it = content1.line_delta_iter(content2)
244
        self.assertEqual(it.next(), (1, 2, 2, [("", "a"), ("", "c")]))
245
        self.assertRaises(StopIteration, it.next)
246
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
247
248
class MockTransport(object):
249
250
    def __init__(self, file_lines=None):
251
        self.file_lines = file_lines
252
        self.calls = []
2196.2.3 by John Arbash Meinel
Update tests and code to pass after merging bzr.dev
253
        # We have no base directory for the MockTransport
254
        self.base = ''
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
255
256
    def get(self, filename):
257
        if self.file_lines is None:
258
            raise NoSuchFile(filename)
259
        else:
260
            return StringIO("\n".join(self.file_lines))
261
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
262
    def readv(self, relpath, offsets):
263
        fp = self.get(relpath)
264
        for offset, size in offsets:
265
            fp.seek(offset)
266
            yield offset, fp.read(size)
267
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
268
    def __getattr__(self, name):
269
        def queue_call(*args, **kwargs):
270
            self.calls.append((name, args, kwargs))
271
        return queue_call
272
273
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
274
class KnitRecordAccessTestsMixin(object):
275
    """Tests for getting and putting knit records."""
276
277
    def test_add_raw_records(self):
278
        """Add_raw_records adds records retrievable later."""
279
        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.
280
        memos = access.add_raw_records([('key', 10)], '1234567890')
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
281
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
282
 
283
    def test_add_several_raw_records(self):
284
        """add_raw_records with many records and read some back."""
285
        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.
286
        memos = access.add_raw_records([('key', 10), ('key2', 2), ('key3', 5)],
287
            '12345678901234567')
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
288
        self.assertEqual(['1234567890', '12', '34567'],
289
            list(access.get_raw_records(memos)))
290
        self.assertEqual(['1234567890'],
291
            list(access.get_raw_records(memos[0:1])))
292
        self.assertEqual(['12'],
293
            list(access.get_raw_records(memos[1:2])))
294
        self.assertEqual(['34567'],
295
            list(access.get_raw_records(memos[2:3])))
296
        self.assertEqual(['1234567890', '34567'],
297
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
298
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
299
300
class TestKnitKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
301
    """Tests for the .kndx implementation."""
302
303
    def get_access(self):
304
        """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.
305
        mapper = ConstantMapper("foo")
306
        access = _KnitKeyAccess(self.get_transport(), mapper)
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
307
        return access
308
    
309
310
class TestPackKnitAccess(TestCaseWithMemoryTransport, KnitRecordAccessTestsMixin):
311
    """Tests for the pack based access."""
312
313
    def get_access(self):
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
314
        return self._get_access()[0]
315
316
    def _get_access(self, packname='packfile', index='FOO'):
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
317
        transport = self.get_transport()
318
        def write_data(bytes):
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
319
            transport.append_bytes(packname, bytes)
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
320
        writer = pack.ContainerWriter(write_data)
321
        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.
322
        access = _DirectPackAccess({})
323
        access.set_writer(writer, index, (transport, packname))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
324
        return access, writer
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
325
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
326
    def test_read_from_several_packs(self):
327
        access, writer = self._get_access()
328
        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.
329
        memos.extend(access.add_raw_records([('key', 10)], '1234567890'))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
330
        writer.end()
331
        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.
332
        memos.extend(access.add_raw_records([('key', 5)], '12345'))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
333
        writer.end()
334
        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.
335
        memos.extend(access.add_raw_records([('key', 5)], 'alpha'))
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
336
        writer.end()
337
        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.
338
        access = _DirectPackAccess({"FOO":(transport, 'packfile'),
2592.3.67 by Robert Collins
More tests for bzrlib.knit._PackAccess.
339
            "FOOBAR":(transport, 'pack2'),
340
            "BAZ":(transport, 'pack3')})
341
        self.assertEqual(['1234567890', '12345', 'alpha'],
342
            list(access.get_raw_records(memos)))
343
        self.assertEqual(['1234567890'],
344
            list(access.get_raw_records(memos[0:1])))
345
        self.assertEqual(['12345'],
346
            list(access.get_raw_records(memos[1:2])))
347
        self.assertEqual(['alpha'],
348
            list(access.get_raw_records(memos[2:3])))
349
        self.assertEqual(['1234567890', 'alpha'],
350
            list(access.get_raw_records(memos[0:1] + memos[2:3])))
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
351
2592.3.70 by Robert Collins
Allow setting a writer after creating a knit._PackAccess object.
352
    def test_set_writer(self):
353
        """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.
354
        access = _DirectPackAccess({})
2592.3.70 by Robert Collins
Allow setting a writer after creating a knit._PackAccess object.
355
        transport = self.get_transport()
356
        packname = 'packfile'
357
        index = 'foo'
358
        def write_data(bytes):
359
            transport.append_bytes(packname, bytes)
360
        writer = pack.ContainerWriter(write_data)
361
        writer.begin()
362
        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.
363
        memos = access.add_raw_records([('key', 10)], '1234567890')
2592.3.70 by Robert Collins
Allow setting a writer after creating a knit._PackAccess object.
364
        writer.end()
365
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
366
2592.3.66 by Robert Collins
Allow adaption of KnitData to pack files.
367
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
368
class LowLevelKnitDataTests(TestCase):
369
370
    def create_gz_content(self, text):
371
        sio = StringIO()
372
        gz_file = gzip.GzipFile(mode='wb', fileobj=sio)
373
        gz_file.write(text)
374
        gz_file.close()
375
        return sio.getvalue()
376
377
    def test_valid_knit_data(self):
378
        sha1sum = sha.new('foo\nbar\n').hexdigest()
379
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
380
                                        'foo\n'
381
                                        'bar\n'
382
                                        'end rev-id-1\n'
383
                                        % (sha1sum,))
384
        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.
385
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
386
        knit = KnitVersionedFiles(None, access)
387
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
388
389
        contents = list(knit._read_records_iter(records))
390
        self.assertEqual([(('rev-id-1',), ['foo\n', 'bar\n'],
391
            '4e48e2c9a3d2ca8a708cb0cc545700544efb5021')], contents)
392
393
        raw_contents = list(knit._read_records_iter_raw(records))
394
        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.
395
396
    def test_not_enough_lines(self):
397
        sha1sum = sha.new('foo\n').hexdigest()
398
        # record says 2 lines data says 1
399
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
400
                                        'foo\n'
401
                                        'end rev-id-1\n'
402
                                        % (sha1sum,))
403
        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.
404
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
405
        knit = KnitVersionedFiles(None, access)
406
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
407
        self.assertRaises(errors.KnitCorrupt, list,
408
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
409
410
        # 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.
411
        raw_contents = list(knit._read_records_iter_raw(records))
412
        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.
413
414
    def test_too_many_lines(self):
415
        sha1sum = sha.new('foo\nbar\n').hexdigest()
416
        # record says 1 lines data says 2
417
        gz_txt = self.create_gz_content('version rev-id-1 1 %s\n'
418
                                        'foo\n'
419
                                        'bar\n'
420
                                        'end rev-id-1\n'
421
                                        % (sha1sum,))
422
        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.
423
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
424
        knit = KnitVersionedFiles(None, access)
425
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
426
        self.assertRaises(errors.KnitCorrupt, list,
427
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
428
429
        # 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.
430
        raw_contents = list(knit._read_records_iter_raw(records))
431
        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.
432
433
    def test_mismatched_version_id(self):
434
        sha1sum = sha.new('foo\nbar\n').hexdigest()
435
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
436
                                        'foo\n'
437
                                        'bar\n'
438
                                        'end rev-id-1\n'
439
                                        % (sha1sum,))
440
        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.
441
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
442
        knit = KnitVersionedFiles(None, access)
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
443
        # 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.
444
        records = [(('rev-id-2',), (('rev-id-2',), 0, len(gz_txt)))]
445
        self.assertRaises(errors.KnitCorrupt, list,
446
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
447
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
448
        # 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.
449
        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.
450
            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.
451
452
    def test_uncompressed_data(self):
453
        sha1sum = sha.new('foo\nbar\n').hexdigest()
454
        txt = ('version rev-id-1 2 %s\n'
455
               'foo\n'
456
               'bar\n'
457
               'end rev-id-1\n'
458
               % (sha1sum,))
459
        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.
460
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
461
        knit = KnitVersionedFiles(None, access)
462
        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.
463
464
        # 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.
465
        self.assertRaises(errors.KnitCorrupt, list,
466
            knit._read_records_iter(records))
2329.1.1 by John Arbash Meinel
Update _KnitData parser to raise more helpful errors when it detects corruption.
467
468
        # read_records_iter_raw will notice the bad data
469
        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.
470
            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.
471
472
    def test_corrupted_data(self):
473
        sha1sum = sha.new('foo\nbar\n').hexdigest()
474
        gz_txt = self.create_gz_content('version rev-id-1 2 %s\n'
475
                                        'foo\n'
476
                                        'bar\n'
477
                                        'end rev-id-1\n'
478
                                        % (sha1sum,))
479
        # Change 2 bytes in the middle to \xff
480
        gz_txt = gz_txt[:10] + '\xff\xff' + gz_txt[12:]
481
        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.
482
        access = _KnitKeyAccess(transport, ConstantMapper('filename'))
483
        knit = KnitVersionedFiles(None, access)
484
        records = [(('rev-id-1',), (('rev-id-1',), 0, len(gz_txt)))]
485
        self.assertRaises(errors.KnitCorrupt, list,
486
            knit._read_records_iter(records))
487
        # read_records_iter_raw will barf on bad gz data
488
        self.assertRaises(errors.KnitCorrupt, list,
489
            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.
490
491
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
492
class LowLevelKnitIndexTests(TestCase):
493
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
494
    def get_knit_index(self, transport, name, mode):
495
        mapper = ConstantMapper(name)
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
496
        orig = knit._load_data
497
        def reset():
498
            knit._load_data = orig
499
        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
500
        from bzrlib._knit_load_data_py import _load_data_py
501
        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.
502
        allow_writes = lambda: 'w' in mode
503
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
504
505
    def test_create_file(self):
506
        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.
507
        index = self.get_knit_index(transport, "filename", "w")
508
        index.keys()
509
        call = transport.calls.pop(0)
510
        # call[1][1] is a StringIO - we can't test it by simple equality.
511
        self.assertEqual('put_file_non_atomic', call[0])
512
        self.assertEqual('filename.kndx', call[1][0])
513
        # With no history, _KndxIndex writes a new index:
514
        self.assertEqual(_KndxIndex.HEADER,
515
            call[1][1].getvalue())
516
        self.assertEqual({'create_parent_dir': True}, call[2])
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
517
518
    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
519
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
520
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
521
        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.
522
            _KndxIndex.HEADER,
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
523
            '%s option 0 1 :' % (utf8_revision_id,)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
524
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
525
        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.
526
        # _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
527
        # 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.
528
        self.assertEqual({(utf8_revision_id,):()},
529
            index.get_parent_map(index.keys()))
530
        self.assertFalse((unicode_revision_id,) in index.keys())
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
531
532
    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
533
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
534
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
535
        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.
536
            _KndxIndex.HEADER,
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
537
            "version option 0 1 .%s :" % (utf8_revision_id,)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
538
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
539
        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.
540
        self.assertEqual({("version",):((utf8_revision_id,),)},
541
            index.get_parent_map(index.keys()))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
542
543
    def test_read_ignore_corrupted_lines(self):
544
        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.
545
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
546
            "corrupted",
547
            "corrupted options 0 1 .b .c ",
548
            "version options 0 1 :"
549
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
550
        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.
551
        self.assertEqual(1, len(index.keys()))
552
        self.assertEqual(set([("version",)]), index.keys())
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
553
554
    def test_read_corrupted_header(self):
2196.2.3 by John Arbash Meinel
Update tests and code to pass after merging bzr.dev
555
        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.
556
        index = self.get_knit_index(transport, "filename", "r")
557
        self.assertRaises(KnitHeaderError, index.keys)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
558
559
    def test_read_duplicate_entries(self):
560
        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.
561
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
562
            "parent options 0 1 :",
563
            "version options1 0 1 0 :",
564
            "version options2 1 2 .other :",
565
            "version options3 3 4 0 .other :"
566
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
567
        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.
568
        self.assertEqual(2, len(index.keys()))
2592.3.8 by Robert Collins
Remove unneeded pulib method lookup on private class _KnitIndex.
569
        # check that the index used is the first one written. (Specific
570
        # 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.
571
        self.assertEqual("1", index._dictionary_compress([("version",)]))
572
        self.assertEqual((("version",), 3, 4), index.get_position(("version",)))
573
        self.assertEqual(["options3"], index.get_options(("version",)))
574
        self.assertEqual({("version",):(("parent",), ("other",))},
575
            index.get_parent_map([("version",)]))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
576
577
    def test_read_compressed_parents(self):
578
        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.
579
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
580
            "a option 0 1 :",
581
            "b option 0 1 0 :",
582
            "c option 0 1 1 0 :",
583
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
584
        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.
585
        self.assertEqual({("b",):(("a",),), ("c",):(("b",), ("a",))},
586
            index.get_parent_map([("b",), ("c",)]))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
587
588
    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
589
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
590
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
591
        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.
592
            _KndxIndex.HEADER
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
593
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
594
        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.
595
        index.add_records([
596
            ((utf8_revision_id,), ["option"], ((utf8_revision_id,), 0, 1), [])])
597
        call = transport.calls.pop(0)
598
        # call[1][1] is a StringIO - we can't test it by simple equality.
599
        self.assertEqual('put_file_non_atomic', call[0])
600
        self.assertEqual('filename.kndx', call[1][0])
601
        # With no history, _KndxIndex writes a new index:
602
        self.assertEqual(_KndxIndex.HEADER +
603
            "\n%s option 0 1  :" % (utf8_revision_id,),
604
            call[1][1].getvalue())
605
        self.assertEqual({'create_parent_dir': True}, call[2])
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
606
607
    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
608
        unicode_revision_id = u"version-\N{CYRILLIC CAPITAL LETTER A}"
609
        utf8_revision_id = unicode_revision_id.encode('utf-8')
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
610
        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.
611
            _KndxIndex.HEADER
612
            ])
613
        index = self.get_knit_index(transport, "filename", "r")
614
        index.add_records([
615
            (("version",), ["option"], (("version",), 0, 1), [(utf8_revision_id,)])])
616
        call = transport.calls.pop(0)
617
        # call[1][1] is a StringIO - we can't test it by simple equality.
618
        self.assertEqual('put_file_non_atomic', call[0])
619
        self.assertEqual('filename.kndx', call[1][0])
620
        # With no history, _KndxIndex writes a new index:
621
        self.assertEqual(_KndxIndex.HEADER +
622
            "\nversion option 0 1 .%s :" % (utf8_revision_id,),
623
            call[1][1].getvalue())
624
        self.assertEqual({'create_parent_dir': True}, call[2])
625
626
    def test_keys(self):
627
        transport = MockTransport([
628
            _KndxIndex.HEADER
629
            ])
630
        index = self.get_knit_index(transport, "filename", "r")
631
632
        self.assertEqual(set(), index.keys())
633
634
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
635
        self.assertEqual(set([("a",)]), index.keys())
636
637
        index.add_records([(("a",), ["option"], (("a",), 0, 1), [])])
638
        self.assertEqual(set([("a",)]), index.keys())
639
640
        index.add_records([(("b",), ["option"], (("b",), 0, 1), [])])
641
        self.assertEqual(set([("a",), ("b",)]), index.keys())
642
643
    def add_a_b(self, index, random_id=None):
644
        kwargs = {}
645
        if random_id is not None:
646
            kwargs["random_id"] = random_id
647
        index.add_records([
648
            (("a",), ["option"], (("a",), 0, 1), [("b",)]),
649
            (("a",), ["opt"], (("a",), 1, 2), [("c",)]),
650
            (("b",), ["option"], (("b",), 2, 3), [("a",)])
651
            ], **kwargs)
652
653
    def assertIndexIsAB(self, index):
654
        self.assertEqual({
655
            ('a',): (('c',),),
656
            ('b',): (('a',),),
657
            },
658
            index.get_parent_map(index.keys()))
659
        self.assertEqual((("a",), 1, 2), index.get_position(("a",)))
660
        self.assertEqual((("b",), 2, 3), index.get_position(("b",)))
661
        self.assertEqual(["opt"], index.get_options(("a",)))
662
663
    def test_add_versions(self):
664
        transport = MockTransport([
665
            _KndxIndex.HEADER
666
            ])
667
        index = self.get_knit_index(transport, "filename", "r")
668
669
        self.add_a_b(index)
670
        call = transport.calls.pop(0)
671
        # call[1][1] is a StringIO - we can't test it by simple equality.
672
        self.assertEqual('put_file_non_atomic', call[0])
673
        self.assertEqual('filename.kndx', call[1][0])
674
        # With no history, _KndxIndex writes a new index:
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
675
        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.
676
            _KndxIndex.HEADER +
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
677
            "\na option 0 1 .b :"
678
            "\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.
679
            "\nb option 2 3 0 :",
680
            call[1][1].getvalue())
681
        self.assertEqual({'create_parent_dir': True}, call[2])
682
        self.assertIndexIsAB(index)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
683
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
684
    def test_add_versions_random_id_is_accepted(self):
685
        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.
686
            _KndxIndex.HEADER
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
687
            ])
688
        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.
689
        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
690
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
691
    def test_delay_create_and_add_versions(self):
692
        transport = MockTransport()
693
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
694
        index = self.get_knit_index(transport, "filename", "w")
695
        # dir_mode=0777)
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
696
        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.
697
        self.add_a_b(index)
698
        #self.assertEqual(
699
        #[    {"dir_mode": 0777, "create_parent_dir": True, "mode": "wb"},
700
        #    kwargs)
701
        # Two calls: one during which we load the existing index (and when its
702
        # missing create it), then a second where we write the contents out.
703
        self.assertEqual(2, len(transport.calls))
704
        call = transport.calls.pop(0)
705
        self.assertEqual('put_file_non_atomic', call[0])
706
        self.assertEqual('filename.kndx', call[1][0])
707
        # With no history, _KndxIndex writes a new index:
708
        self.assertEqual(_KndxIndex.HEADER, call[1][1].getvalue())
709
        self.assertEqual({'create_parent_dir': True}, call[2])
710
        call = transport.calls.pop(0)
711
        # call[1][1] is a StringIO - we can't test it by simple equality.
712
        self.assertEqual('put_file_non_atomic', call[0])
713
        self.assertEqual('filename.kndx', call[1][0])
714
        # With no history, _KndxIndex writes a new index:
715
        self.assertEqual(
716
            _KndxIndex.HEADER +
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
717
            "\na option 0 1 .b :"
718
            "\na opt 1 2 .c :"
719
            "\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.
720
            call[1][1].getvalue())
721
        self.assertEqual({'create_parent_dir': True}, call[2])
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
722
723
    def test_get_position(self):
724
        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.
725
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
726
            "a option 0 1 :",
727
            "b option 1 2 :"
728
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
729
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
730
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
731
        self.assertEqual((("a",), 0, 1), index.get_position(("a",)))
732
        self.assertEqual((("b",), 1, 2), index.get_position(("b",)))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
733
734
    def test_get_method(self):
735
        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.
736
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
737
            "a fulltext,unknown 0 1 :",
738
            "b unknown,line-delta 1 2 :",
739
            "c bad 3 4 :"
740
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
741
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
742
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
743
        self.assertEqual("fulltext", index.get_method("a"))
744
        self.assertEqual("line-delta", index.get_method("b"))
745
        self.assertRaises(errors.KnitIndexUnknownMethod, index.get_method, "c")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
746
747
    def test_get_options(self):
748
        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.
749
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
750
            "a opt1 0 1 :",
751
            "b opt2,opt3 1 2 :"
752
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
753
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
754
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
755
        self.assertEqual(["opt1"], index.get_options("a"))
756
        self.assertEqual(["opt2", "opt3"], index.get_options("b"))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
757
3287.5.6 by Robert Collins
Remove _KnitIndex.get_parents.
758
    def test_get_parent_map(self):
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
759
        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.
760
            _KndxIndex.HEADER,
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
761
            "a option 0 1 :",
762
            "b option 1 2 0 .c :",
763
            "c option 1 2 1 0 .e :"
764
            ])
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
765
        index = self.get_knit_index(transport, "filename", "r")
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
766
3287.5.6 by Robert Collins
Remove _KnitIndex.get_parents.
767
        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.
768
            ("a",):(),
769
            ("b",):(("a",), ("c",)),
770
            ("c",):(("b",), ("a",), ("e",)),
771
            }, index.get_parent_map(index.keys()))
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
772
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
773
    def test_impossible_parent(self):
774
        """Test we get KnitCorrupt if the parent couldn't possibly exist."""
775
        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.
776
            _KndxIndex.HEADER,
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
777
            "a option 0 1 :",
778
            "b option 0 1 4 :"  # We don't have a 4th record
779
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
780
        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.
781
        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.
782
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
783
        except TypeError, e:
784
            if (str(e) == ('exceptions must be strings, classes, or instances,'
785
                           ' not exceptions.IndexError')
786
                and sys.version_info[0:2] >= (2,5)):
787
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
788
                                  ' raising new style exceptions with python'
789
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
790
            else:
791
                raise
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
792
793
    def test_corrupted_parent(self):
794
        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.
795
            _KndxIndex.HEADER,
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
796
            "a option 0 1 :",
797
            "b option 0 1 :",
798
            "c option 0 1 1v :", # Can't have a parent of '1v'
799
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
800
        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.
801
        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.
802
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
803
        except TypeError, e:
804
            if (str(e) == ('exceptions must be strings, classes, or instances,'
805
                           ' not exceptions.ValueError')
806
                and sys.version_info[0:2] >= (2,5)):
807
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
808
                                  ' raising new style exceptions with python'
809
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
810
            else:
811
                raise
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
812
813
    def test_corrupted_parent_in_list(self):
814
        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.
815
            _KndxIndex.HEADER,
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
816
            "a option 0 1 :",
817
            "b option 0 1 :",
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
818
            "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.
819
            ])
3350.6.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
        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.
821
        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.
822
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.17 by John Arbash Meinel
Workaround for Pyrex <0.9.5 and python >=2.5 incompatibilities.
823
        except TypeError, e:
824
            if (str(e) == ('exceptions must be strings, classes, or instances,'
825
                           ' not exceptions.ValueError')
826
                and sys.version_info[0:2] >= (2,5)):
827
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
828
                                  ' raising new style exceptions with python'
829
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
830
            else:
831
                raise
2484.1.13 by John Arbash Meinel
Add a test that KnitCorrupt is raised when parent strings are invalid.
832
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
833
    def test_invalid_position(self):
834
        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.
835
            _KndxIndex.HEADER,
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
836
            "a option 1v 1 :",
837
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
838
        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.
839
        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.
840
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
841
        except TypeError, e:
842
            if (str(e) == ('exceptions must be strings, classes, or instances,'
843
                           ' not exceptions.ValueError')
844
                and sys.version_info[0:2] >= (2,5)):
845
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
846
                                  ' raising new style exceptions with python'
847
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
848
            else:
849
                raise
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
850
851
    def test_invalid_size(self):
852
        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.
853
            _KndxIndex.HEADER,
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
854
            "a option 1 1v :",
855
            ])
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
856
        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.
857
        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.
858
            self.assertRaises(errors.KnitCorrupt, index.keys)
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
859
        except TypeError, e:
860
            if (str(e) == ('exceptions must be strings, classes, or instances,'
861
                           ' not exceptions.ValueError')
862
                and sys.version_info[0:2] >= (2,5)):
863
                self.knownFailure('Pyrex <0.9.5 fails with TypeError when'
864
                                  ' raising new style exceptions with python'
865
                                  ' >=2.5')
2484.1.19 by John Arbash Meinel
Don't suppress the TypeError if it doesn't match our requirements.
866
            else:
867
                raise
2484.1.18 by John Arbash Meinel
Test that we properly verify the size and position strings.
868
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
869
    def test_short_line(self):
870
        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.
871
            _KndxIndex.HEADER,
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
872
            "a option 0 10  :",
873
            "b option 10 10 0", # This line isn't terminated, ignored
874
            ])
875
        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.
876
        self.assertEqual(set([('a',)]), index.keys())
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
877
878
    def test_skip_incomplete_record(self):
879
        # A line with bogus data should just be skipped
880
        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.
881
            _KndxIndex.HEADER,
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
882
            "a option 0 10  :",
883
            "b option 10 10 0", # This line isn't terminated, ignored
884
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
885
            ])
886
        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.
887
        self.assertEqual(set([('a',), ('c',)]), index.keys())
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
888
889
    def test_trailing_characters(self):
890
        # A line with bogus data should just be skipped
891
        transport = MockTransport([
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
892
            _KndxIndex.HEADER,
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
893
            "a option 0 10  :",
894
            "b option 10 10 0 :a", # This line has extra trailing characters
895
            "c option 20 10 0 :", # Properly terminated, and starts with '\n'
896
            ])
897
        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.
898
        self.assertEqual(set([('a',), ('c',)]), index.keys())
2484.1.24 by John Arbash Meinel
Add direct tests of how we handle incomplete/'broken' lines
899
2158.3.1 by Dmitry Vasiliev
KnitIndex tests/fixes/optimizations
900
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
901
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
902
903
    _test_needs_features = [CompiledKnitFeature]
904
3350.6.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
    def get_knit_index(self, transport, name, mode):
906
        mapper = ConstantMapper(name)
2484.1.1 by John Arbash Meinel
Add an initial function to read knit indexes in pyrex.
907
        orig = knit._load_data
908
        def reset():
909
            knit._load_data = orig
910
        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
911
        from bzrlib._knit_load_data_c import _load_data_c
912
        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.
913
        allow_writes = lambda: mode == 'w'
914
        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.
915
916
1684.3.3 by Robert Collins
Add a special cased weaves to knit converter.
917
class KnitTests(TestCaseWithTransport):
918
    """Class containing knit test helper routines."""
1563.2.16 by Robert Collins
Change WeaveStore into VersionedFileStore and make its versoined file class parameterisable.
919
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
920
    def make_test_knit(self, annotate=False, name='test'):
921
        mapper = ConstantMapper(name)
922
        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
923
924
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
925
class TestKnitIndex(KnitTests):
926
927
    def test_add_versions_dictionary_compresses(self):
928
        """Adding versions to the index should update the lookup dict"""
929
        knit = self.make_test_knit()
930
        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.
931
        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
932
        self.check_file_contents('test.kndx',
933
            '# bzr knit index 8\n'
934
            '\n'
935
            'a-1 fulltext 0 0  :'
936
            )
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
937
        idx.add_records([
938
            (('a-2',), ['fulltext'], (('a-2',), 0, 0), [('a-1',)]),
939
            (('a-3',), ['fulltext'], (('a-3',), 0, 0), [('a-2',)]),
940
            ])
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
941
        self.check_file_contents('test.kndx',
942
            '# bzr knit index 8\n'
943
            '\n'
944
            'a-1 fulltext 0 0  :\n'
945
            'a-2 fulltext 0 0 0 :\n'
946
            'a-3 fulltext 0 0 1 :'
947
            )
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
948
        self.assertEqual(set([('a-3',), ('a-1',), ('a-2',)]), idx.keys())
949
        self.assertEqual({
950
            ('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False)),
951
            ('a-2',): ((('a-2',), 0, 0), None, (('a-1',),), ('fulltext', False)),
952
            ('a-3',): ((('a-3',), 0, 0), None, (('a-2',),), ('fulltext', False)),
953
            }, idx.get_build_details(idx.keys()))
954
        self.assertEqual({('a-1',):(),
955
            ('a-2',):(('a-1',),),
956
            ('a-3',):(('a-2',),),},
957
            idx.get_parent_map(idx.keys()))
2102.2.1 by John Arbash Meinel
Fix bug #64789 _KnitIndex.add_versions() should dict compress new revisions
958
959
    def test_add_versions_fails_clean(self):
960
        """If add_versions fails in the middle, it restores a pristine state.
961
962
        Any modifications that are made to the index are reset if all versions
963
        cannot be added.
964
        """
965
        # This cheats a little bit by passing in a generator which will
966
        # raise an exception before the processing finishes
967
        # Other possibilities would be to have an version with the wrong number
968
        # of entries, or to make the backing transport unable to write any
969
        # files.
970
971
        knit = self.make_test_knit()
972
        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.
973
        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
974
975
        class StopEarly(Exception):
976
            pass
977
978
        def generate_failure():
979
            """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.
980
            yield (('a-2',), ['fulltext'], (None, 0, 0), ('a-1',))
981
            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
982
            raise StopEarly()
983
984
        # 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.
985
        def assertA1Only():
986
            self.assertEqual(set([('a-1',)]), set(idx.keys()))
987
            self.assertEqual(
988
                {('a-1',): ((('a-1',), 0, 0), None, (), ('fulltext', False))},
989
                idx.get_build_details([('a-1',)]))
990
            self.assertEqual({('a-1',):()}, idx.get_parent_map(idx.keys()))
991
992
        assertA1Only()
993
        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
994
        # 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.
995
        assertA1Only()
2171.1.1 by John Arbash Meinel
Knit index files should ignore empty indexes rather than consider them corrupt.
996
997
    def test_knit_index_ignores_empty_files(self):
998
        # There was a race condition in older bzr, where a ^C at the right time
999
        # could leave an empty .kndx file, which bzr would later claim was a
1000
        # corrupted file since the header was not present. In reality, the file
1001
        # just wasn't created, so it should be ignored.
1002
        t = get_transport('.')
1003
        t.put_bytes('test.kndx', '')
1004
1005
        knit = self.make_test_knit()
1006
1007
    def test_knit_index_checks_header(self):
1008
        t = get_transport('.')
1009
        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.
1010
        k = self.make_test_knit()
1011
        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.
1012
1013
1014
class TestGraphIndexKnit(KnitTests):
1015
    """Tests for knits using a GraphIndex rather than a KnitIndex."""
1016
1017
    def make_g_index(self, name, ref_lists=0, nodes=[]):
1018
        builder = GraphIndexBuilder(ref_lists)
1019
        for node, references, value in nodes:
1020
            builder.add_node(node, references, value)
1021
        stream = builder.finish()
1022
        trans = self.get_transport()
2890.2.1 by Robert Collins
* ``bzrlib.index.GraphIndex`` now requires a size parameter to the
1023
        size = trans.put_file(name, stream)
1024
        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.
1025
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1026
    def two_graph_index(self, deltas=False, catch_adds=False):
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1027
        """Build a two-graph index.
1028
1029
        :param deltas: If true, use underlying indices with two node-ref
1030
            lists and 'parent' set to a delta-compressed against tail.
1031
        """
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.
1032
        # build a complex graph across several indices.
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1033
        if deltas:
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1034
            # delta compression inn the index
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1035
            index1 = self.make_g_index('1', 2, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1036
                (('tip', ), 'N0 100', ([('parent', )], [], )),
1037
                (('tail', ), '', ([], []))])
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1038
            index2 = self.make_g_index('2', 2, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1039
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], [('tail', )])),
1040
                (('separate', ), '', ([], []))])
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1041
        else:
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1042
            # just blob location and graph in the index.
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1043
            index1 = self.make_g_index('1', 1, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1044
                (('tip', ), 'N0 100', ([('parent', )], )),
1045
                (('tail', ), '', ([], ))])
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1046
            index2 = self.make_g_index('2', 1, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1047
                (('parent', ), ' 100 78', ([('tail', ), ('ghost', )], )),
1048
                (('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.
1049
        combined_index = CombinedGraphIndex([index1, index2])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1050
        if catch_adds:
1051
            self.combined_index = combined_index
1052
            self.caught_entries = []
1053
            add_callback = self.catch_add
1054
        else:
1055
            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.
1056
        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.
1057
            add_callback=add_callback)
2592.3.4 by Robert Collins
Implement get_ancestry/get_ancestry_with_ghosts for KnitGraphIndex.
1058
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1059
    def test_keys(self):
1060
        index = self.two_graph_index()
1061
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1062
            set(index.keys()))
2592.3.9 by Robert Collins
Implement KnitGraphIndex.has_version.
1063
2592.3.10 by Robert Collins
Implement KnitGraphIndex.get_position.
1064
    def test_get_position(self):
1065
        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.
1066
        self.assertEqual((index._graph_index._indices[0], 0, 100), index.get_position(('tip',)))
1067
        self.assertEqual((index._graph_index._indices[1], 100, 78), index.get_position(('parent',)))
2592.3.10 by Robert Collins
Implement KnitGraphIndex.get_position.
1068
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1069
    def test_get_method_deltas(self):
1070
        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.
1071
        self.assertEqual('fulltext', index.get_method(('tip',)))
1072
        self.assertEqual('line-delta', index.get_method(('parent',)))
2592.3.11 by Robert Collins
Implement KnitGraphIndex.get_method.
1073
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1074
    def test_get_method_no_deltas(self):
1075
        # check that the parent-history lookup is ignored with deltas=False.
1076
        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.
1077
        self.assertEqual('fulltext', index.get_method(('tip',)))
1078
        self.assertEqual('fulltext', index.get_method(('parent',)))
2592.3.13 by Robert Collins
Implement KnitGraphIndex.get_method.
1079
2592.3.14 by Robert Collins
Implement KnitGraphIndex.get_options.
1080
    def test_get_options_deltas(self):
1081
        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.
1082
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1083
        self.assertEqual(['line-delta'], index.get_options(('parent',)))
2592.3.14 by Robert Collins
Implement KnitGraphIndex.get_options.
1084
1085
    def test_get_options_no_deltas(self):
1086
        # check that the parent-history lookup is ignored with deltas=False.
1087
        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.
1088
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1089
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
1090
1091
    def test_get_parent_map(self):
1092
        index = self.two_graph_index()
1093
        self.assertEqual({('parent',):(('tail',), ('ghost',))},
1094
            index.get_parent_map([('parent',), ('ghost',)]))
2592.3.14 by Robert Collins
Implement KnitGraphIndex.get_options.
1095
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1096
    def catch_add(self, entries):
1097
        self.caught_entries.append(entries)
1098
1099
    def test_add_no_callback_errors(self):
1100
        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.
1101
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1102
            [(('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.
1103
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1104
    def test_add_version_smoke(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1105
        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.
1106
        index.add_records([(('new',), 'fulltext,no-eol', (None, 50, 60),
1107
            [('separate',)])])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1108
        self.assertEqual([[(('new', ), 'N50 60', ((('separate',),),))]],
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1109
            self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1110
1111
    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.
1112
        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.
1113
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1114
            [(('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.
1115
        self.assertEqual([], self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1116
1117
    def test_add_version_same_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1118
        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.
1119
        # 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.
1120
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1121
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
1122
        # position/length are ignored (because each pack could have fulltext or
1123
        # delta, and be at a different position.
1124
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1125
            [('parent',)])])
1126
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1127
            [('parent',)])])
1128
        # but neither should have added data:
1129
        self.assertEqual([[], [], [], []], self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1130
        
1131
    def test_add_version_different_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1132
        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.
1133
        # 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.
1134
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1135
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1136
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1137
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [('parent',)])])
1138
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1139
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1140
        # 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.
1141
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1142
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1143
        self.assertEqual([], self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1144
        
1145
    def test_add_versions_nodeltas(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1146
        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.
1147
        index.add_records([
1148
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1149
                (('new2',), 'fulltext', (None, 0, 6), [('new',)]),
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1150
                ])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1151
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),),)),
1152
            (('new2', ), ' 0 6', ((('new',),),))],
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1153
            sorted(self.caught_entries[0]))
1154
        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.
1155
1156
    def test_add_versions_deltas(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1157
        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.
1158
        index.add_records([
1159
                (('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)]),
1160
                (('new2',), 'line-delta', (None, 0, 6), [('new',)]),
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1161
                ])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1162
        self.assertEqual([(('new', ), 'N50 60', ((('separate',),), ())),
1163
            (('new2', ), ' 0 6', ((('new',),), (('new',),), ))],
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1164
            sorted(self.caught_entries[0]))
1165
        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.
1166
1167
    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.
1168
        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.
1169
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1170
            [(('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.
1171
        self.assertEqual([], self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1172
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1173
    def test_add_versions_random_id_accepted(self):
1174
        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.
1175
        index.add_records([], random_id=True)
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1176
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1177
    def test_add_versions_same_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1178
        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.
1179
        # 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.
1180
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100),
1181
            [('parent',)])])
1182
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100),
1183
            [('parent',)])])
1184
        # position/length are ignored (because each pack could have fulltext or
1185
        # delta, and be at a different position.
1186
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100),
1187
            [('parent',)])])
1188
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000),
1189
            [('parent',)])])
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1190
        # 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.
1191
        self.assertEqual([[], [], [], []], self.caught_entries)
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1192
        
1193
    def test_add_versions_different_dup(self):
2592.3.19 by Robert Collins
Change KnitGraphIndex from returning data to performing a callback on insertions.
1194
        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.
1195
        # 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.
1196
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1197
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
1198
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1199
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [('parent',)])])
1200
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1201
            [(('tip',), 'fulltext', (None, 0, 100), [('parent',)])])
2592.3.17 by Robert Collins
Add add_version(s) to KnitGraphIndex, completing the required api for KnitVersionedFile.
1202
        # 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.
1203
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1204
            [(('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.
1205
        # 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.
1206
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1207
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)]),
1208
             (('tip',), '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.
1209
        self.assertEqual([], self.caught_entries)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1210
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
1211
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1212
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.
1213
    """Tests for knits using _KnitGraphIndex with no parents."""
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1214
1215
    def make_g_index(self, name, ref_lists=0, nodes=[]):
1216
        builder = GraphIndexBuilder(ref_lists)
1217
        for node, references in nodes:
1218
            builder.add_node(node, references)
1219
        stream = builder.finish()
1220
        trans = self.get_transport()
2890.2.1 by Robert Collins
* ``bzrlib.index.GraphIndex`` now requires a size parameter to the
1221
        size = trans.put_file(name, stream)
1222
        return GraphIndex(trans, name, size)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1223
1224
    def test_parents_deltas_incompatible(self):
1225
        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.
1226
        self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1227
            index, deltas=True, parents=False)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1228
1229
    def two_graph_index(self, catch_adds=False):
1230
        """Build a two-graph index.
1231
1232
        :param deltas: If true, use underlying indices with two node-ref
1233
            lists and 'parent' set to a delta-compressed against tail.
1234
        """
1235
        # put several versions in the index.
1236
        index1 = self.make_g_index('1', 0, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1237
            (('tip', ), 'N0 100'),
1238
            (('tail', ), '')])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1239
        index2 = self.make_g_index('2', 0, [
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1240
            (('parent', ), ' 100 78'),
1241
            (('separate', ), '')])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1242
        combined_index = CombinedGraphIndex([index1, index2])
1243
        if catch_adds:
1244
            self.combined_index = combined_index
1245
            self.caught_entries = []
1246
            add_callback = self.catch_add
1247
        else:
1248
            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.
1249
        return _KnitGraphIndex(combined_index, lambda:True, parents=False,
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1250
            add_callback=add_callback)
1251
3350.6.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
    def test_keys(self):
1253
        index = self.two_graph_index()
1254
        self.assertEqual(set([('tail',), ('tip',), ('parent',), ('separate',)]),
1255
            set(index.keys()))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1256
1257
    def test_get_position(self):
1258
        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.
1259
        self.assertEqual((index._graph_index._indices[0], 0, 100),
1260
            index.get_position(('tip',)))
1261
        self.assertEqual((index._graph_index._indices[1], 100, 78),
1262
            index.get_position(('parent',)))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1263
1264
    def test_get_method(self):
1265
        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.
1266
        self.assertEqual('fulltext', index.get_method(('tip',)))
1267
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1268
1269
    def test_get_options(self):
1270
        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.
1271
        self.assertEqual(['fulltext', 'no-eol'], index.get_options(('tip',)))
1272
        self.assertEqual(['fulltext'], index.get_options(('parent',)))
1273
1274
    def test_get_parent_map(self):
1275
        index = self.two_graph_index()
1276
        self.assertEqual({('parent',):None},
1277
            index.get_parent_map([('parent',), ('ghost',)]))
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1278
1279
    def catch_add(self, entries):
1280
        self.caught_entries.append(entries)
1281
1282
    def test_add_no_callback_errors(self):
1283
        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.
1284
        self.assertRaises(errors.ReadOnlyError, index.add_records,
1285
            [(('new',), 'fulltext,no-eol', (None, 50, 60), [('separate',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1286
1287
    def test_add_version_smoke(self):
1288
        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.
1289
        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.
1290
        self.assertEqual([[(('new', ), 'N50 60')]],
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1291
            self.caught_entries)
1292
1293
    def test_add_version_delta_not_delta_index(self):
1294
        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.
1295
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1296
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1297
        self.assertEqual([], self.caught_entries)
1298
1299
    def test_add_version_same_dup(self):
1300
        index = self.two_graph_index(catch_adds=True)
1301
        # 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.
1302
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1303
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
1304
        # position/length are ignored (because each pack could have fulltext or
1305
        # delta, and be at a different position.
1306
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1307
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1308
        # 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.
1309
        self.assertEqual([[], [], [], []], self.caught_entries)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1310
        
1311
    def test_add_version_different_dup(self):
1312
        index = self.two_graph_index(catch_adds=True)
1313
        # 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.
1314
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1315
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
1316
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1317
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
1318
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1319
            [(('tip',), 'fulltext', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1320
        # 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.
1321
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1322
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1323
        self.assertEqual([], self.caught_entries)
1324
        
1325
    def test_add_versions(self):
1326
        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.
1327
        index.add_records([
1328
                (('new',), 'fulltext,no-eol', (None, 50, 60), []),
1329
                (('new2',), 'fulltext', (None, 0, 6), []),
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1330
                ])
2624.2.5 by Robert Collins
Change bzrlib.index.Index keys to be 1-tuples, not strings.
1331
        self.assertEqual([(('new', ), 'N50 60'), (('new2', ), ' 0 6')],
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1332
            sorted(self.caught_entries[0]))
1333
        self.assertEqual(1, len(self.caught_entries))
1334
1335
    def test_add_versions_delta_not_delta_index(self):
1336
        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.
1337
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1338
            [(('new',), 'no-eol,line-delta', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1339
        self.assertEqual([], self.caught_entries)
1340
1341
    def test_add_versions_parents_not_parents_index(self):
1342
        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.
1343
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1344
            [(('new',), 'no-eol,fulltext', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1345
        self.assertEqual([], self.caught_entries)
1346
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1347
    def test_add_versions_random_id_accepted(self):
1348
        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.
1349
        index.add_records([], random_id=True)
2841.2.1 by Robert Collins
* Commit no longer checks for new text keys during insertion when the
1350
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1351
    def test_add_versions_same_dup(self):
1352
        index = self.two_graph_index(catch_adds=True)
1353
        # 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.
1354
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1355
        index.add_records([(('tip',), 'no-eol,fulltext', (None, 0, 100), [])])
1356
        # position/length are ignored (because each pack could have fulltext or
1357
        # delta, and be at a different position.
1358
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 50, 100), [])])
1359
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1360
        # 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.
1361
        self.assertEqual([[], [], [], []], self.caught_entries)
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1362
        
1363
    def test_add_versions_different_dup(self):
1364
        index = self.two_graph_index(catch_adds=True)
1365
        # 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.
1366
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1367
            [(('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
1368
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1369
            [(('tip',), 'line-delta,no-eol', (None, 0, 100), [])])
1370
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1371
            [(('tip',), 'fulltext', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1372
        # 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.
1373
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1374
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1375
        # 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.
1376
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1377
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), []),
1378
             (('tip',), 'no-eol,line-delta', (None, 0, 100), [])])
2592.3.34 by Robert Collins
Rough unfactored support for parentless KnitGraphIndexs.
1379
        self.assertEqual([], self.caught_entries)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1380
1381
1382
class TestStacking(KnitTests):
1383
1384
    def get_basis_and_test_knit(self):
1385
        basis = self.make_test_knit(name='basis')
3350.8.2 by Robert Collins
stacked get_parent_map.
1386
        basis = RecordingVersionedFilesDecorator(basis)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1387
        test = self.make_test_knit(name='test')
1388
        test.add_fallback_versioned_files(basis)
1389
        return basis, test
1390
1391
    def test_add_fallback_versioned_files(self):
1392
        basis = self.make_test_knit(name='basis')
1393
        test = self.make_test_knit(name='test')
1394
        # It must not error; other tests test that the fallback is referred to
1395
        # when accessing data.
1396
        test.add_fallback_versioned_files(basis)
1397
1398
    def test_add_lines(self):
3350.8.9 by Robert Collins
define behaviour for add_lines with stacked storage.
1399
        # lines added to the test are not added to the basis
1400
        basis, test = self.get_basis_and_test_knit()
1401
        key = ('foo',)
1402
        key_basis = ('bar',)
1403
        key_cross_border = ('quux',)
1404
        key_delta = ('zaphod',)
1405
        test.add_lines(key, (), ['foo\n'])
1406
        self.assertEqual({}, basis.get_parent_map([key]))
1407
        # lines added to the test that reference across the stack do a
1408
        # fulltext.
1409
        basis.add_lines(key_basis, (), ['foo\n'])
1410
        basis.calls = []
1411
        test.add_lines(key_cross_border, (key_basis,), ['foo\n'])
1412
        self.assertEqual('fulltext', test._index.get_method(key_cross_border))
1413
        self.assertEqual([("get_parent_map", set([key_basis]))], basis.calls)
1414
        # Subsequent adds do delta.
3350.8.14 by Robert Collins
Review feedback.
1415
        basis.calls = []
3350.8.9 by Robert Collins
define behaviour for add_lines with stacked storage.
1416
        test.add_lines(key_delta, (key_cross_border,), ['foo\n'])
1417
        self.assertEqual('line-delta', test._index.get_method(key_delta))
1418
        self.assertEqual([], basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1419
1420
    def test_annotate(self):
3350.8.8 by Robert Collins
Stacking and knits don't play nice for annotation yet.
1421
        # annotations from the test knit are answered without asking the basis
1422
        basis, test = self.get_basis_and_test_knit()
1423
        key = ('foo',)
1424
        key_basis = ('bar',)
1425
        key_missing = ('missing',)
1426
        test.add_lines(key, (), ['foo\n'])
1427
        details = test.annotate(key)
1428
        self.assertEqual([(key, 'foo\n')], details)
1429
        self.assertEqual([], basis.calls)
1430
        # But texts that are not in the test knit are looked for in the basis
1431
        # directly.
1432
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1433
        basis.calls = []
1434
        details = test.annotate(key_basis)
1435
        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.
1436
        # Not optimised to date:
1437
        # self.assertEqual([("annotate", key_basis)], basis.calls)
1438
        self.assertEqual([('get_parent_map', set([key_basis])),
1439
            ('get_parent_map', set([key_basis])),
1440
            ('get_parent_map', set([key_basis])),
1441
            ('get_record_stream', [key_basis], 'unordered', True)],
1442
            basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1443
1444
    def test_check(self):
3517.4.19 by Martin Pool
Update test for knit.check() to expect it to recurse into fallback vfs
1445
        # At the moment checking a stacked knit does implicitly check the
1446
        # fallback files.  
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1447
        basis, test = self.get_basis_and_test_knit()
1448
        test.check()
1449
1450
    def test_get_parent_map(self):
3350.8.2 by Robert Collins
stacked get_parent_map.
1451
        # parents in the test knit are answered without asking the basis
1452
        basis, test = self.get_basis_and_test_knit()
1453
        key = ('foo',)
1454
        key_basis = ('bar',)
1455
        key_missing = ('missing',)
1456
        test.add_lines(key, (), [])
1457
        parent_map = test.get_parent_map([key])
1458
        self.assertEqual({key: ()}, parent_map)
1459
        self.assertEqual([], basis.calls)
1460
        # But parents that are not in the test knit are looked for in the basis
1461
        basis.add_lines(key_basis, (), [])
1462
        basis.calls = []
1463
        parent_map = test.get_parent_map([key, key_basis, key_missing])
1464
        self.assertEqual({key: (),
1465
            key_basis: ()}, parent_map)
1466
        self.assertEqual([("get_parent_map", set([key_basis, key_missing]))],
1467
            basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1468
3350.8.7 by Robert Collins
get_record_stream for fulltexts working (but note extreme memory use!).
1469
    def test_get_record_stream_unordered_fulltexts(self):
1470
        # records from the test knit are answered without asking the basis:
1471
        basis, test = self.get_basis_and_test_knit()
1472
        key = ('foo',)
1473
        key_basis = ('bar',)
1474
        key_missing = ('missing',)
1475
        test.add_lines(key, (), ['foo\n'])
1476
        records = list(test.get_record_stream([key], 'unordered', True))
1477
        self.assertEqual(1, len(records))
1478
        self.assertEqual([], basis.calls)
1479
        # Missing (from test knit) objects are retrieved from the basis:
1480
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1481
        basis.calls = []
1482
        records = list(test.get_record_stream([key_basis, key_missing],
1483
            'unordered', True))
1484
        self.assertEqual(2, len(records))
1485
        calls = list(basis.calls)
1486
        for record in records:
1487
            self.assertSubset([record.key], (key_basis, key_missing))
1488
            if record.key == key_missing:
1489
                self.assertIsInstance(record, AbsentContentFactory)
1490
            else:
1491
                reference = list(basis.get_record_stream([key_basis],
1492
                    'unordered', True))[0]
1493
                self.assertEqual(reference.key, record.key)
1494
                self.assertEqual(reference.sha1, record.sha1)
1495
                self.assertEqual(reference.storage_kind, record.storage_kind)
1496
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
1497
                    record.get_bytes_as(record.storage_kind))
1498
                self.assertEqual(reference.get_bytes_as('fulltext'),
1499
                    record.get_bytes_as('fulltext'))
3350.8.14 by Robert Collins
Review feedback.
1500
        # 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!).
1501
        # ask which fallbacks have which parents.
1502
        self.assertEqual([
1503
            ("get_parent_map", set([key_basis, key_missing])),
1504
            ("get_record_stream", [key_basis], 'unordered', True)],
1505
            calls)
1506
1507
    def test_get_record_stream_ordered_fulltexts(self):
1508
        # ordering is preserved down into the fallback store.
1509
        basis, test = self.get_basis_and_test_knit()
1510
        key = ('foo',)
1511
        key_basis = ('bar',)
1512
        key_basis_2 = ('quux',)
1513
        key_missing = ('missing',)
1514
        test.add_lines(key, (key_basis,), ['foo\n'])
1515
        # Missing (from test knit) objects are retrieved from the basis:
1516
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
1517
        basis.add_lines(key_basis_2, (), ['quux\n'])
1518
        basis.calls = []
1519
        # ask for in non-topological order
1520
        records = list(test.get_record_stream(
1521
            [key, key_basis, key_missing, key_basis_2], 'topological', True))
1522
        self.assertEqual(4, len(records))
1523
        results = []
1524
        for record in records:
1525
            self.assertSubset([record.key],
1526
                (key_basis, key_missing, key_basis_2, key))
1527
            if record.key == key_missing:
1528
                self.assertIsInstance(record, AbsentContentFactory)
1529
            else:
1530
                results.append((record.key, record.sha1, record.storage_kind,
1531
                    record.get_bytes_as('fulltext')))
1532
        calls = list(basis.calls)
1533
        order = [record[0] for record in results]
1534
        self.assertEqual([key_basis_2, key_basis, key], order)
1535
        for result in results:
1536
            if result[0] == key:
1537
                source = test
1538
            else:
1539
                source = basis
1540
            record = source.get_record_stream([result[0]], 'unordered',
1541
                True).next()
1542
            self.assertEqual(record.key, result[0])
1543
            self.assertEqual(record.sha1, result[1])
1544
            self.assertEqual(record.storage_kind, result[2])
1545
            self.assertEqual(record.get_bytes_as('fulltext'), result[3])
3350.8.14 by Robert Collins
Review feedback.
1546
        # 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!).
1547
        # ask which fallbacks have which parents.
1548
        self.assertEqual([
1549
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
1550
            # unordered is asked for by the underlying worker as it still
1551
            # buffers everything while answering - which is a problem!
1552
            ("get_record_stream", [key_basis_2, key_basis], 'unordered', True)],
1553
            calls)
1554
3350.8.6 by Robert Collins
get_record_stream stacking for delta access.
1555
    def test_get_record_stream_unordered_deltas(self):
1556
        # records from the test knit are answered without asking the basis:
1557
        basis, test = self.get_basis_and_test_knit()
1558
        key = ('foo',)
1559
        key_basis = ('bar',)
1560
        key_missing = ('missing',)
1561
        test.add_lines(key, (), ['foo\n'])
1562
        records = list(test.get_record_stream([key], 'unordered', False))
1563
        self.assertEqual(1, len(records))
1564
        self.assertEqual([], basis.calls)
1565
        # Missing (from test knit) objects are retrieved from the basis:
1566
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1567
        basis.calls = []
1568
        records = list(test.get_record_stream([key_basis, key_missing],
1569
            'unordered', False))
1570
        self.assertEqual(2, len(records))
1571
        calls = list(basis.calls)
1572
        for record in records:
1573
            self.assertSubset([record.key], (key_basis, key_missing))
1574
            if record.key == key_missing:
1575
                self.assertIsInstance(record, AbsentContentFactory)
1576
            else:
1577
                reference = list(basis.get_record_stream([key_basis],
1578
                    'unordered', False))[0]
1579
                self.assertEqual(reference.key, record.key)
1580
                self.assertEqual(reference.sha1, record.sha1)
1581
                self.assertEqual(reference.storage_kind, record.storage_kind)
1582
                self.assertEqual(reference.get_bytes_as(reference.storage_kind),
1583
                    record.get_bytes_as(record.storage_kind))
3350.8.14 by Robert Collins
Review feedback.
1584
        # 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.
1585
        # ask which fallbacks have which parents.
1586
        self.assertEqual([
1587
            ("get_parent_map", set([key_basis, key_missing])),
1588
            ("get_record_stream", [key_basis], 'unordered', False)],
1589
            calls)
1590
1591
    def test_get_record_stream_ordered_deltas(self):
1592
        # ordering is preserved down into the fallback store.
1593
        basis, test = self.get_basis_and_test_knit()
1594
        key = ('foo',)
1595
        key_basis = ('bar',)
1596
        key_basis_2 = ('quux',)
1597
        key_missing = ('missing',)
1598
        test.add_lines(key, (key_basis,), ['foo\n'])
1599
        # Missing (from test knit) objects are retrieved from the basis:
1600
        basis.add_lines(key_basis, (key_basis_2,), ['foo\n', 'bar\n'])
1601
        basis.add_lines(key_basis_2, (), ['quux\n'])
1602
        basis.calls = []
1603
        # ask for in non-topological order
1604
        records = list(test.get_record_stream(
1605
            [key, key_basis, key_missing, key_basis_2], 'topological', False))
1606
        self.assertEqual(4, len(records))
1607
        results = []
1608
        for record in records:
1609
            self.assertSubset([record.key],
1610
                (key_basis, key_missing, key_basis_2, key))
1611
            if record.key == key_missing:
1612
                self.assertIsInstance(record, AbsentContentFactory)
1613
            else:
1614
                results.append((record.key, record.sha1, record.storage_kind,
1615
                    record.get_bytes_as(record.storage_kind)))
1616
        calls = list(basis.calls)
1617
        order = [record[0] for record in results]
1618
        self.assertEqual([key_basis_2, key_basis, key], order)
1619
        for result in results:
1620
            if result[0] == key:
1621
                source = test
1622
            else:
1623
                source = basis
1624
            record = source.get_record_stream([result[0]], 'unordered',
1625
                False).next()
1626
            self.assertEqual(record.key, result[0])
1627
            self.assertEqual(record.sha1, result[1])
1628
            self.assertEqual(record.storage_kind, result[2])
1629
            self.assertEqual(record.get_bytes_as(record.storage_kind), result[3])
3350.8.14 by Robert Collins
Review feedback.
1630
        # 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.
1631
        # ask which fallbacks have which parents.
1632
        self.assertEqual([
1633
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
1634
            ("get_record_stream", [key_basis_2, key_basis], 'topological', False)],
1635
            calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1636
1637
    def test_get_sha1s(self):
3350.8.3 by Robert Collins
VF.get_sha1s needed changing to be stackable.
1638
        # sha1's in the test knit are answered without asking the basis
1639
        basis, test = self.get_basis_and_test_knit()
1640
        key = ('foo',)
1641
        key_basis = ('bar',)
1642
        key_missing = ('missing',)
1643
        test.add_lines(key, (), ['foo\n'])
1644
        key_sha1sum = sha.new('foo\n').hexdigest()
1645
        sha1s = test.get_sha1s([key])
1646
        self.assertEqual({key: key_sha1sum}, sha1s)
1647
        self.assertEqual([], basis.calls)
1648
        # But texts that are not in the test knit are looked for in the basis
1649
        # directly (rather than via text reconstruction) so that remote servers
1650
        # etc don't have to answer with full content.
1651
        basis.add_lines(key_basis, (), ['foo\n', 'bar\n'])
1652
        basis_sha1sum = sha.new('foo\nbar\n').hexdigest()
1653
        basis.calls = []
1654
        sha1s = test.get_sha1s([key, key_missing, key_basis])
1655
        self.assertEqual({key: key_sha1sum,
1656
            key_basis: basis_sha1sum}, sha1s)
1657
        self.assertEqual([("get_sha1s", set([key_basis, key_missing]))],
1658
            basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1659
1660
    def test_insert_record_stream(self):
3350.8.10 by Robert Collins
Stacked insert_record_stream.
1661
        # records are inserted as normal; insert_record_stream builds on
3350.8.14 by Robert Collins
Review feedback.
1662
        # add_lines, so a smoke test should be all that's needed:
3350.8.10 by Robert Collins
Stacked insert_record_stream.
1663
        key = ('foo',)
1664
        key_basis = ('bar',)
1665
        key_delta = ('zaphod',)
1666
        basis, test = self.get_basis_and_test_knit()
1667
        source = self.make_test_knit(name='source')
1668
        basis.add_lines(key_basis, (), ['foo\n'])
1669
        basis.calls = []
1670
        source.add_lines(key_basis, (), ['foo\n'])
1671
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
1672
        stream = source.get_record_stream([key_delta], 'unordered', False)
1673
        test.insert_record_stream(stream)
1674
        self.assertEqual([("get_parent_map", set([key_basis]))],
1675
            basis.calls)
1676
        self.assertEqual({key_delta:(key_basis,)},
1677
            test.get_parent_map([key_delta]))
1678
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
1679
            'unordered', True).next().get_bytes_as('fulltext'))
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1680
1681
    def test_iter_lines_added_or_present_in_keys(self):
3350.8.5 by Robert Collins
Iter_lines_added_or_present_in_keys stacks.
1682
        # Lines from the basis are returned, and lines for a given key are only
1683
        # returned once. 
1684
        key1 = ('foo1',)
1685
        key2 = ('foo2',)
1686
        # all sources are asked for keys:
1687
        basis, test = self.get_basis_and_test_knit()
1688
        basis.add_lines(key1, (), ["foo"])
1689
        basis.calls = []
1690
        lines = list(test.iter_lines_added_or_present_in_keys([key1]))
1691
        self.assertEqual([("foo\n", key1)], lines)
1692
        self.assertEqual([("iter_lines_added_or_present_in_keys", set([key1]))],
1693
            basis.calls)
1694
        # keys in both are not duplicated:
1695
        test.add_lines(key2, (), ["bar\n"])
1696
        basis.add_lines(key2, (), ["bar\n"])
1697
        basis.calls = []
1698
        lines = list(test.iter_lines_added_or_present_in_keys([key2]))
1699
        self.assertEqual([("bar\n", key2)], lines)
1700
        self.assertEqual([], basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1701
1702
    def test_keys(self):
3350.8.4 by Robert Collins
Vf.keys() stacking support.
1703
        key1 = ('foo1',)
1704
        key2 = ('foo2',)
1705
        # all sources are asked for keys:
1706
        basis, test = self.get_basis_and_test_knit()
1707
        keys = test.keys()
1708
        self.assertEqual(set(), set(keys))
1709
        self.assertEqual([("keys",)], basis.calls)
1710
        # keys from a basis are returned:
1711
        basis.add_lines(key1, (), [])
1712
        basis.calls = []
1713
        keys = test.keys()
1714
        self.assertEqual(set([key1]), set(keys))
1715
        self.assertEqual([("keys",)], basis.calls)
1716
        # keys in both are not duplicated:
1717
        test.add_lines(key2, (), [])
1718
        basis.add_lines(key2, (), [])
1719
        basis.calls = []
1720
        keys = test.keys()
1721
        self.assertEqual(2, len(keys))
1722
        self.assertEqual(set([key1, key2]), set(keys))
1723
        self.assertEqual([("keys",)], basis.calls)
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1724
1725
    def test_add_mpdiffs(self):
3350.8.11 by Robert Collins
Stacked add_mpdiffs.
1726
        # records are inserted as normal; add_mpdiff builds on
3350.8.14 by Robert Collins
Review feedback.
1727
        # add_lines, so a smoke test should be all that's needed:
3350.8.11 by Robert Collins
Stacked add_mpdiffs.
1728
        key = ('foo',)
1729
        key_basis = ('bar',)
1730
        key_delta = ('zaphod',)
1731
        basis, test = self.get_basis_and_test_knit()
1732
        source = self.make_test_knit(name='source')
1733
        basis.add_lines(key_basis, (), ['foo\n'])
1734
        basis.calls = []
1735
        source.add_lines(key_basis, (), ['foo\n'])
1736
        source.add_lines(key_delta, (key_basis,), ['bar\n'])
1737
        diffs = source.make_mpdiffs([key_delta])
1738
        test.add_mpdiffs([(key_delta, (key_basis,),
1739
            source.get_sha1s([key_delta])[key_delta], diffs[0])])
1740
        self.assertEqual([("get_parent_map", set([key_basis])),
3350.8.12 by Robert Collins
Stacked make_mpdiffs.
1741
            ('get_record_stream', [key_basis], 'unordered', True),
1742
            ('get_parent_map', set([key_basis]))],
3350.8.11 by Robert Collins
Stacked add_mpdiffs.
1743
            basis.calls)
1744
        self.assertEqual({key_delta:(key_basis,)},
1745
            test.get_parent_map([key_delta]))
1746
        self.assertEqual('bar\n', test.get_record_stream([key_delta],
1747
            'unordered', True).next().get_bytes_as('fulltext'))
3350.8.1 by Robert Collins
KnitVersionedFiles.add_fallback_versioned_files exists.
1748
1749
    def test_make_mpdiffs(self):
3350.8.12 by Robert Collins
Stacked make_mpdiffs.
1750
        # Generating an mpdiff across a stacking boundary should detect parent
1751
        # texts regions.
1752
        key = ('foo',)
1753
        key_left = ('bar',)
1754
        key_right = ('zaphod',)
1755
        basis, test = self.get_basis_and_test_knit()
1756
        basis.add_lines(key_left, (), ['bar\n'])
1757
        basis.add_lines(key_right, (), ['zaphod\n'])
1758
        basis.calls = []
1759
        test.add_lines(key, (key_left, key_right),
1760
            ['bar\n', 'foo\n', 'zaphod\n'])
1761
        diffs = test.make_mpdiffs([key])
1762
        self.assertEqual([
1763
            multiparent.MultiParent([multiparent.ParentText(0, 0, 0, 1),
1764
                multiparent.NewText(['foo\n']),
1765
                multiparent.ParentText(1, 0, 2, 1)])],
1766
            diffs)
1767
        self.assertEqual(4, len(basis.calls))
1768
        self.assertEqual([
1769
            ("get_parent_map", set([key_left, key_right])),
1770
            ("get_parent_map", set([key_left, key_right])),
1771
            ("get_parent_map", set([key_left, key_right])),
1772
            ],
1773
            basis.calls[:3])
3350.8.14 by Robert Collins
Review feedback.
1774
        last_call = basis.calls[3]
1775
        self.assertEqual('get_record_stream', last_call[0])
1776
        self.assertEqual(set([key_left, key_right]), set(last_call[1]))
1777
        self.assertEqual('unordered', last_call[2])
1778
        self.assertEqual(True, last_call[3])