/brz/remove-bazaar

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