/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_versionedfile.py

Merge bzr.dev and resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
# TODO: might be nice to create a versionedfile with some type of corruption
22
22
# considered typical and check that it can be detected/corrected.
23
23
 
 
24
from itertools import chain
24
25
from StringIO import StringIO
25
26
 
26
27
import bzrlib
34
35
                           RevisionAlreadyPresent,
35
36
                           WeaveParentMismatch
36
37
                           )
 
38
from bzrlib import knit as _mod_knit
37
39
from bzrlib.knit import (
38
40
    make_file_knit,
39
41
    KnitAnnotateFactory,
40
42
    KnitPlainFactory,
41
43
    )
42
 
from bzrlib.symbol_versioning import one_four
 
44
from bzrlib.symbol_versioning import one_four, one_five
43
45
from bzrlib.tests import TestCaseWithMemoryTransport, TestSkipped
44
46
from bzrlib.tests.http_utils import TestCaseWithWebserver
45
47
from bzrlib.trace import mutter
46
48
from bzrlib.transport import get_transport
47
49
from bzrlib.transport.memory import MemoryTransport
48
50
from bzrlib.tsort import topo_sort
 
51
from bzrlib.tuned_gzip import GzipFile
49
52
import bzrlib.versionedfile as versionedfile
50
53
from bzrlib.weave import WeaveFile
51
54
from bzrlib.weavefile import read_weave, write_weave
52
55
 
53
56
 
 
57
def get_diamond_vf(f, trailing_eol=True, left_only=False):
 
58
    """Get a diamond graph to exercise deltas and merges.
 
59
    
 
60
    :param trailing_eol: If True end the last line with \n.
 
61
    """
 
62
    parents = {
 
63
        'origin': (),
 
64
        'base': (('origin',),),
 
65
        'left': (('base',),),
 
66
        'right': (('base',),),
 
67
        'merged': (('left',), ('right',)),
 
68
        }
 
69
    # insert a diamond graph to exercise deltas and merges.
 
70
    if trailing_eol:
 
71
        last_char = '\n'
 
72
    else:
 
73
        last_char = ''
 
74
    f.add_lines('origin', [], ['origin' + last_char])
 
75
    f.add_lines('base', ['origin'], ['base' + last_char])
 
76
    f.add_lines('left', ['base'], ['base\n', 'left' + last_char])
 
77
    if not left_only:
 
78
        f.add_lines('right', ['base'],
 
79
            ['base\n', 'right' + last_char])
 
80
        f.add_lines('merged', ['left', 'right'],
 
81
            ['base\n', 'left\n', 'right\n', 'merged' + last_char])
 
82
    return f, parents
 
83
 
 
84
 
54
85
class VersionedFileTestMixIn(object):
55
86
    """A mixin test class for testing VersionedFiles.
56
87
 
87
118
        f = self.reopen_file(create=True)
88
119
        verify_file(f)
89
120
 
 
121
    def test_get_record_stream_empty(self):
 
122
        """get_record_stream is a replacement for get_data_stream."""
 
123
        f = self.get_file()
 
124
        entries = f.get_record_stream([], 'unordered', False)
 
125
        self.assertEqual([], list(entries))
 
126
 
 
127
    def assertValidStorageKind(self, storage_kind):
 
128
        """Assert that storage_kind is a valid storage_kind."""
 
129
        self.assertSubset([storage_kind],
 
130
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
 
131
             'knit-ft', 'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
 
132
             'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'])
 
133
 
 
134
    def capture_stream(self, f, entries, on_seen, parents):
 
135
        """Capture a stream for testing."""
 
136
        for factory in entries:
 
137
            on_seen(factory.key)
 
138
            self.assertValidStorageKind(factory.storage_kind)
 
139
            self.assertEqual(f.get_sha1s([factory.key[0]])[0], factory.sha1)
 
140
            self.assertEqual(parents[factory.key[0]], factory.parents)
 
141
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
 
142
                str)
 
143
 
 
144
    def test_get_record_stream_interface(self):
 
145
        """Each item in a stream has to provide a regular interface."""
 
146
        f, parents = get_diamond_vf(self.get_file())
 
147
        entries = f.get_record_stream(['merged', 'left', 'right', 'base'],
 
148
            'unordered', False)
 
149
        seen = set()
 
150
        self.capture_stream(f, entries, seen.add, parents)
 
151
        self.assertEqual(set([('base',), ('left',), ('right',), ('merged',)]),
 
152
            seen)
 
153
 
 
154
    def test_get_record_stream_interface_ordered(self):
 
155
        """Each item in a stream has to provide a regular interface."""
 
156
        f, parents = get_diamond_vf(self.get_file())
 
157
        entries = f.get_record_stream(['merged', 'left', 'right', 'base'],
 
158
            'topological', False)
 
159
        seen = []
 
160
        self.capture_stream(f, entries, seen.append, parents)
 
161
        self.assertSubset([tuple(seen)],
 
162
            (
 
163
             (('base',), ('left',), ('right',), ('merged',)),
 
164
             (('base',), ('right',), ('left',), ('merged',)),
 
165
            ))
 
166
 
 
167
    def test_get_record_stream_interface_ordered_with_delta_closure(self):
 
168
        """Each item in a stream has to provide a regular interface."""
 
169
        f, parents = get_diamond_vf(self.get_file())
 
170
        entries = f.get_record_stream(['merged', 'left', 'right', 'base'],
 
171
            'topological', True)
 
172
        seen = []
 
173
        for factory in entries:
 
174
            seen.append(factory.key)
 
175
            self.assertValidStorageKind(factory.storage_kind)
 
176
            self.assertEqual(f.get_sha1s([factory.key[0]])[0], factory.sha1)
 
177
            self.assertEqual(parents[factory.key[0]], factory.parents)
 
178
            self.assertEqual(f.get_text(factory.key[0]),
 
179
                factory.get_bytes_as('fulltext'))
 
180
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
 
181
                str)
 
182
        self.assertSubset([tuple(seen)],
 
183
            (
 
184
             (('base',), ('left',), ('right',), ('merged',)),
 
185
             (('base',), ('right',), ('left',), ('merged',)),
 
186
            ))
 
187
 
 
188
    def test_get_record_stream_unknown_storage_kind_raises(self):
 
189
        """Asking for a storage kind that the stream cannot supply raises."""
 
190
        f, parents = get_diamond_vf(self.get_file())
 
191
        entries = f.get_record_stream(['merged', 'left', 'right', 'base'],
 
192
            'unordered', False)
 
193
        # We track the contents because we should be able to try, fail a
 
194
        # particular kind and then ask for one that works and continue.
 
195
        seen = set()
 
196
        for factory in entries:
 
197
            seen.add(factory.key)
 
198
            self.assertValidStorageKind(factory.storage_kind)
 
199
            self.assertEqual(f.get_sha1s([factory.key[0]])[0], factory.sha1)
 
200
            self.assertEqual(parents[factory.key[0]], factory.parents)
 
201
            # currently no stream emits mpdiff
 
202
            self.assertRaises(errors.UnavailableRepresentation,
 
203
                factory.get_bytes_as, 'mpdiff')
 
204
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
 
205
                str)
 
206
        self.assertEqual(set([('base',), ('left',), ('right',), ('merged',)]),
 
207
            seen)
 
208
 
 
209
    def test_get_record_stream_missing_records_are_absent(self):
 
210
        f, parents = get_diamond_vf(self.get_file())
 
211
        entries = f.get_record_stream(['merged', 'left', 'right', 'or', 'base'],
 
212
            'unordered', False)
 
213
        self.assertAbsentRecord(f, parents, entries)
 
214
        entries = f.get_record_stream(['merged', 'left', 'right', 'or', 'base'],
 
215
            'topological', False)
 
216
        self.assertAbsentRecord(f, parents, entries)
 
217
 
 
218
    def assertAbsentRecord(self, f, parents, entries):
 
219
        """Helper for test_get_record_stream_missing_records_are_absent."""
 
220
        seen = set()
 
221
        for factory in entries:
 
222
            seen.add(factory.key)
 
223
            if factory.key == ('or',):
 
224
                self.assertEqual('absent', factory.storage_kind)
 
225
                self.assertEqual(None, factory.sha1)
 
226
                self.assertEqual(None, factory.parents)
 
227
            else:
 
228
                self.assertValidStorageKind(factory.storage_kind)
 
229
                self.assertEqual(f.get_sha1s([factory.key[0]])[0], factory.sha1)
 
230
                self.assertEqual(parents[factory.key[0]], factory.parents)
 
231
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
 
232
                    str)
 
233
        self.assertEqual(
 
234
            set([('base',), ('left',), ('right',), ('merged',), ('or',)]),
 
235
            seen)
 
236
 
 
237
    def test_filter_absent_records(self):
 
238
        """Requested missing records can be filter trivially."""
 
239
        f, parents = get_diamond_vf(self.get_file())
 
240
        entries = f.get_record_stream(['merged', 'left', 'right', 'extra', 'base'],
 
241
            'unordered', False)
 
242
        seen = set()
 
243
        self.capture_stream(f, versionedfile.filter_absent(entries), seen.add,
 
244
            parents)
 
245
        self.assertEqual(set([('base',), ('left',), ('right',), ('merged',)]),
 
246
            seen)
 
247
 
 
248
    def test_insert_record_stream_empty(self):
 
249
        """Inserting an empty record stream should work."""
 
250
        f = self.get_file()
 
251
        stream = []
 
252
        f.insert_record_stream([])
 
253
 
 
254
    def assertIdenticalVersionedFile(self, left, right):
 
255
        """Assert that left and right have the same contents."""
 
256
        self.assertEqual(set(left.versions()), set(right.versions()))
 
257
        self.assertEqual(left.get_parent_map(left.versions()),
 
258
            right.get_parent_map(right.versions()))
 
259
        for v in left.versions():
 
260
            self.assertEqual(left.get_text(v), right.get_text(v))
 
261
 
 
262
    def test_insert_record_stream_fulltexts(self):
 
263
        """Any file should accept a stream of fulltexts."""
 
264
        f = self.get_file()
 
265
        weave_vf = WeaveFile('source', get_transport(self.get_url('.')),
 
266
            create=True, get_scope=self.get_transaction)
 
267
        source, _ = get_diamond_vf(weave_vf)
 
268
        stream = source.get_record_stream(source.versions(), 'topological',
 
269
            False)
 
270
        f.insert_record_stream(stream)
 
271
        self.assertIdenticalVersionedFile(f, source)
 
272
 
 
273
    def test_insert_record_stream_fulltexts_noeol(self):
 
274
        """Any file should accept a stream of fulltexts."""
 
275
        f = self.get_file()
 
276
        weave_vf = WeaveFile('source', get_transport(self.get_url('.')),
 
277
            create=True, get_scope=self.get_transaction)
 
278
        source, _ = get_diamond_vf(weave_vf, trailing_eol=False)
 
279
        stream = source.get_record_stream(source.versions(), 'topological',
 
280
            False)
 
281
        f.insert_record_stream(stream)
 
282
        self.assertIdenticalVersionedFile(f, source)
 
283
 
 
284
    def test_insert_record_stream_annotated_knits(self):
 
285
        """Any file should accept a stream from plain knits."""
 
286
        f = self.get_file()
 
287
        source = make_file_knit('source', get_transport(self.get_url('.')),
 
288
            create=True)
 
289
        get_diamond_vf(source)
 
290
        stream = source.get_record_stream(source.versions(), 'topological',
 
291
            False)
 
292
        f.insert_record_stream(stream)
 
293
        self.assertIdenticalVersionedFile(f, source)
 
294
 
 
295
    def test_insert_record_stream_annotated_knits_noeol(self):
 
296
        """Any file should accept a stream from plain knits."""
 
297
        f = self.get_file()
 
298
        source = make_file_knit('source', get_transport(self.get_url('.')),
 
299
            create=True)
 
300
        get_diamond_vf(source, trailing_eol=False)
 
301
        stream = source.get_record_stream(source.versions(), 'topological',
 
302
            False)
 
303
        f.insert_record_stream(stream)
 
304
        self.assertIdenticalVersionedFile(f, source)
 
305
 
 
306
    def test_insert_record_stream_plain_knits(self):
 
307
        """Any file should accept a stream from plain knits."""
 
308
        f = self.get_file()
 
309
        source = make_file_knit('source', get_transport(self.get_url('.')),
 
310
            create=True, factory=KnitPlainFactory())
 
311
        get_diamond_vf(source)
 
312
        stream = source.get_record_stream(source.versions(), 'topological',
 
313
            False)
 
314
        f.insert_record_stream(stream)
 
315
        self.assertIdenticalVersionedFile(f, source)
 
316
 
 
317
    def test_insert_record_stream_plain_knits_noeol(self):
 
318
        """Any file should accept a stream from plain knits."""
 
319
        f = self.get_file()
 
320
        source = make_file_knit('source', get_transport(self.get_url('.')),
 
321
            create=True, factory=KnitPlainFactory())
 
322
        get_diamond_vf(source, trailing_eol=False)
 
323
        stream = source.get_record_stream(source.versions(), 'topological',
 
324
            False)
 
325
        f.insert_record_stream(stream)
 
326
        self.assertIdenticalVersionedFile(f, source)
 
327
 
 
328
    def test_insert_record_stream_existing_keys(self):
 
329
        """Inserting keys already in a file should not error."""
 
330
        f = self.get_file()
 
331
        source = make_file_knit('source', get_transport(self.get_url('.')),
 
332
            create=True, factory=KnitPlainFactory())
 
333
        get_diamond_vf(source)
 
334
        # insert some keys into f.
 
335
        get_diamond_vf(f, left_only=True)
 
336
        stream = source.get_record_stream(source.versions(), 'topological',
 
337
            False)
 
338
        f.insert_record_stream(stream)
 
339
        self.assertIdenticalVersionedFile(f, source)
 
340
 
 
341
    def test_insert_record_stream_missing_keys(self):
 
342
        """Inserting a stream with absent keys should raise an error."""
 
343
        f = self.get_file()
 
344
        source = make_file_knit('source', get_transport(self.get_url('.')),
 
345
            create=True, factory=KnitPlainFactory())
 
346
        stream = source.get_record_stream(['missing'], 'topological',
 
347
            False)
 
348
        self.assertRaises(errors.RevisionNotPresent, f.insert_record_stream,
 
349
            stream)
 
350
 
 
351
    def test_insert_record_stream_out_of_order(self):
 
352
        """An out of order stream can either error or work."""
 
353
        f, parents = get_diamond_vf(self.get_file())
 
354
        origin_entries = f.get_record_stream(['origin'], 'unordered', False)
 
355
        end_entries = f.get_record_stream(['merged', 'left'],
 
356
            'topological', False)
 
357
        start_entries = f.get_record_stream(['right', 'base'],
 
358
            'topological', False)
 
359
        entries = chain(origin_entries, end_entries, start_entries)
 
360
        target = self.get_file('target')
 
361
        try:
 
362
            target.insert_record_stream(entries)
 
363
        except RevisionNotPresent:
 
364
            # Must not have corrupted the file.
 
365
            target.check()
 
366
        else:
 
367
            self.assertIdenticalVersionedFile(f, target)
 
368
 
 
369
    def test_insert_record_stream_delta_missing_basis_no_corruption(self):
 
370
        """Insertion where a needed basis is not included aborts safely."""
 
371
        # Annotated source - deltas can be used in any knit.
 
372
        source = make_file_knit('source', get_transport(self.get_url('.')),
 
373
            create=True)
 
374
        get_diamond_vf(source)
 
375
        entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
 
376
        f = self.get_file()
 
377
        self.assertRaises(RevisionNotPresent, f.insert_record_stream, entries)
 
378
        f.check()
 
379
        self.assertFalse(f.has_version('merged'))
 
380
 
90
381
    def test_adds_with_parent_texts(self):
91
382
        f = self.get_file()
92
383
        parent_texts = {}
358
649
        self._transaction = 'after'
359
650
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
360
651
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
361
 
        self.assertRaises(errors.OutSideTransaction, f.join, '')
 
652
        self.assertRaises(errors.OutSideTransaction, self.applyDeprecated,
 
653
            one_five, f.join, '')
362
654
        
363
 
    def test_clone_text(self):
364
 
        f = self.get_file()
365
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
366
 
        self.applyDeprecated(one_four, f.clone_text, 'r1', 'r0', ['r0'])
367
 
        def verify_file(f):
368
 
            self.assertEquals(f.get_lines('r1'), f.get_lines('r0'))
369
 
            self.assertEquals(f.get_lines('r1'), ['a\n', 'b\n'])
370
 
            self.assertEqual({'r1':('r0',)}, f.get_parent_map(['r1']))
371
 
            self.assertRaises(RevisionNotPresent,
372
 
                self.applyDeprecated, one_four, f.clone_text, 'r2', 'rX', [])
373
 
            self.assertRaises(RevisionAlreadyPresent,
374
 
                self.applyDeprecated, one_four, f.clone_text, 'r1', 'r0', [])
375
 
        verify_file(f)
376
 
        verify_file(self.reopen_file())
377
 
 
378
655
    def test_copy_to(self):
379
656
        f = self.get_file()
380
657
        f.add_lines('0', [], ['a\n'])
388
665
        # and should be a list
389
666
        self.assertTrue(isinstance(self.get_factory().get_suffixes(), list))
390
667
 
391
 
    def build_graph(self, file, graph):
392
 
        for node in topo_sort(graph.items()):
393
 
            file.add_lines(node, graph[node], [])
394
 
 
395
 
    def test_get_graph(self):
396
 
        f = self.get_file()
397
 
        graph = {
398
 
            'v1': (),
399
 
            'v2': ('v1', ),
400
 
            'v3': ('v2', )}
401
 
        self.build_graph(f, graph)
402
 
        self.assertEqual(graph, self.applyDeprecated(one_four, f.get_graph))
403
 
    
404
 
    def test_get_graph_partial(self):
405
 
        f = self.get_file()
406
 
        complex_graph = {}
407
 
        simple_a = {
408
 
            'c': (),
409
 
            'b': ('c', ),
410
 
            'a': ('b', ),
411
 
            }
412
 
        complex_graph.update(simple_a)
413
 
        simple_b = {
414
 
            'c': (),
415
 
            'b': ('c', ),
416
 
            }
417
 
        complex_graph.update(simple_b)
418
 
        simple_gam = {
419
 
            'c': (),
420
 
            'oo': (),
421
 
            'bar': ('oo', 'c'),
422
 
            'gam': ('bar', ),
423
 
            }
424
 
        complex_graph.update(simple_gam)
425
 
        simple_b_gam = {}
426
 
        simple_b_gam.update(simple_gam)
427
 
        simple_b_gam.update(simple_b)
428
 
        self.build_graph(f, complex_graph)
429
 
        self.assertEqual(simple_a, self.applyDeprecated(one_four, f.get_graph,
430
 
            ['a']))
431
 
        self.assertEqual(simple_b, self.applyDeprecated(one_four, f.get_graph,
432
 
            ['b']))
433
 
        self.assertEqual(simple_gam, self.applyDeprecated(one_four,
434
 
            f.get_graph, ['gam']))
435
 
        self.assertEqual(simple_b_gam, self.applyDeprecated(one_four,
436
 
            f.get_graph, ['b', 'gam']))
437
 
 
438
 
    def test_get_parents(self):
439
 
        f = self.get_file()
440
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
441
 
        f.add_lines('r1', [], ['a\n', 'b\n'])
442
 
        f.add_lines('r2', [], ['a\n', 'b\n'])
443
 
        f.add_lines('r3', [], ['a\n', 'b\n'])
444
 
        f.add_lines('m', ['r0', 'r1', 'r2', 'r3'], ['a\n', 'b\n'])
445
 
        self.assertEqual(['r0', 'r1', 'r2', 'r3'],
446
 
            self.applyDeprecated(one_four, f.get_parents, 'm'))
447
 
        self.assertRaises(RevisionNotPresent,
448
 
            self.applyDeprecated, one_four, f.get_parents, 'y')
449
 
 
450
668
    def test_get_parent_map(self):
451
669
        f = self.get_file()
452
670
        f.add_lines('r0', [], ['a\n', 'b\n'])
510
728
        """Open the versioned file from disk again."""
511
729
        raise NotImplementedError(self.reopen_file)
512
730
 
513
 
    def test_iter_parents(self):
514
 
        """iter_parents returns the parents for many nodes."""
515
 
        f = self.get_file()
516
 
        # sample data:
517
 
        # no parents
518
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
519
 
        # 1 parents
520
 
        f.add_lines('r1', ['r0'], ['a\n', 'b\n'])
521
 
        # 2 parents
522
 
        f.add_lines('r2', ['r1', 'r0'], ['a\n', 'b\n'])
523
 
        # XXX TODO a ghost
524
 
        # cases: each sample data individually:
525
 
        self.assertEqual(set([('r0', ())]),
526
 
            set(self.applyDeprecated(one_four, f.iter_parents, ['r0'])))
527
 
        self.assertEqual(set([('r1', ('r0', ))]),
528
 
            set(self.applyDeprecated(one_four, f.iter_parents, ['r1'])))
529
 
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
530
 
            set(self.applyDeprecated(one_four, f.iter_parents, ['r2'])))
531
 
        # no nodes returned for a missing node
532
 
        self.assertEqual(set(),
533
 
            set(self.applyDeprecated(one_four, f.iter_parents, ['missing'])))
534
 
        # 1 node returned with missing nodes skipped
535
 
        self.assertEqual(set([('r1', ('r0', ))]),
536
 
            set(self.applyDeprecated(one_four, f.iter_parents, ['ghost1', 'r1',
537
 
                'ghost'])))
538
 
        # 2 nodes returned
539
 
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
540
 
            set(self.applyDeprecated(one_four, f.iter_parents, ['r0', 'r1'])))
541
 
        # 2 nodes returned, missing skipped
542
 
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
543
 
            set(self.applyDeprecated(one_four, f.iter_parents,
544
 
                ['a', 'r0', 'b', 'r1', 'c'])))
545
 
 
546
731
    def test_iter_lines_added_or_present_in_versions(self):
547
732
        # test that we get at least an equalset of the lines added by
548
733
        # versions in the weave 
629
814
        # has_version
630
815
        # - these are ghost unaware and must not be reflect ghosts
631
816
        self.assertEqual(['notbxbfse'], vf.get_ancestry('notbxbfse'))
632
 
        self.assertEqual([],
633
 
            self.applyDeprecated(one_four, vf.get_parents, 'notbxbfse'))
634
 
        self.assertEqual({'notbxbfse':()}, self.applyDeprecated(one_four,
635
 
            vf.get_graph))
636
817
        self.assertFalse(vf.has_version(parent_id_utf8))
637
818
        # we have _with_ghost apis to give us ghost information.
638
819
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry_with_ghosts(['notbxbfse']))
639
820
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
640
 
        self.assertEqual({'notbxbfse':(parent_id_utf8,)},
641
 
            self.applyDeprecated(one_four, vf.get_graph_with_ghosts))
642
 
        self.assertTrue(self.applyDeprecated(one_four, vf.has_ghost,
643
 
            parent_id_utf8))
644
821
        # if we add something that is a ghost of another, it should correct the
645
822
        # results of the prior apis
646
823
        vf.add_lines(parent_id_utf8, [], [])
647
824
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry(['notbxbfse']))
648
825
        self.assertEqual({'notbxbfse':(parent_id_utf8,)},
649
826
            vf.get_parent_map(['notbxbfse']))
650
 
        self.assertEqual({parent_id_utf8:(),
651
 
                          'notbxbfse':(parent_id_utf8, ),
652
 
                          },
653
 
                         self.applyDeprecated(one_four, vf.get_graph))
654
827
        self.assertTrue(vf.has_version(parent_id_utf8))
655
828
        # we have _with_ghost apis to give us ghost information.
656
829
        self.assertEqual([parent_id_utf8, 'notbxbfse'],
657
830
            vf.get_ancestry_with_ghosts(['notbxbfse']))
658
831
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
659
 
        self.assertEqual({parent_id_utf8:(),
660
 
                          'notbxbfse':(parent_id_utf8,),
661
 
                          },
662
 
            self.applyDeprecated(one_four, vf.get_graph_with_ghosts))
663
 
        self.assertFalse(self.applyDeprecated(one_four, vf.has_ghost,
664
 
            parent_id_utf8))
665
832
 
666
833
    def test_add_lines_with_ghosts_after_normal_revs(self):
667
834
        # some versioned file formats allow lines to be added with parent
693
860
                          'base',
694
861
                          [],
695
862
                          [])
696
 
        self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
 
863
        self.assertRaises(errors.ReadOnlyError, self.applyDeprecated, one_five,
 
864
            vf.join, 'base')
697
865
    
698
866
    def test_get_sha1s(self):
699
867
        # check the sha1 data is available
704
872
        vf.add_lines('b', ['a'], ['a\n'])
705
873
        # a file differing only in last newline.
706
874
        vf.add_lines('c', [], ['a'])
707
 
        # Deprecasted single-version API.
708
 
        self.assertEqual(
709
 
            '3f786850e387550fdab836ed7e6dc881de23001b',
710
 
            self.applyDeprecated(one_four, vf.get_sha1, 'a'))
711
 
        self.assertEqual(
712
 
            '3f786850e387550fdab836ed7e6dc881de23001b',
713
 
            self.applyDeprecated(one_four, vf.get_sha1, 'b'))
714
 
        self.assertEqual(
715
 
            '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
716
 
            self.applyDeprecated(one_four, vf.get_sha1, 'c'))
717
875
        self.assertEqual(['3f786850e387550fdab836ed7e6dc881de23001b',
718
876
                          '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
719
877
                          '3f786850e387550fdab836ed7e6dc881de23001b'],
778
936
 
779
937
class TestKnit(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
780
938
 
781
 
    def get_file(self, name='foo'):
782
 
        return self.get_factory()(name, get_transport(self.get_url('.')),
 
939
    def get_file(self, name='foo', create=True):
 
940
        return make_file_knit(name, get_transport(self.get_url('.')),
783
941
            delta=True, create=True, get_scope=self.get_transaction)
784
942
 
785
943
    def get_factory(self):
792
950
        return knit
793
951
 
794
952
    def reopen_file(self, name='foo', create=False):
795
 
        return self.get_factory()(name, get_transport(self.get_url('.')),
796
 
            delta=True,
797
 
            create=create)
 
953
        return self.get_file(name, create)
798
954
 
799
955
    def test_detection(self):
800
956
        knit = self.get_file()
808
964
class TestPlaintextKnit(TestKnit):
809
965
    """Test a knit with no cached annotations"""
810
966
 
811
 
    def get_factory(self):
812
 
        return make_file_knit
 
967
    def get_file(self, name='foo', create=True):
 
968
        return make_file_knit(name, get_transport(self.get_url('.')),
 
969
            delta=True, create=create, get_scope=self.get_transaction,
 
970
            factory=_mod_knit.KnitPlainFactory())
813
971
 
814
972
 
815
973
class TestPlanMergeVersionedFile(TestCaseWithMemoryTransport):
1243
1401
 
1244
1402
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1245
1403
                                'xxx', '>>>>>>> ', 'bbb']
 
1404
 
 
1405
 
 
1406
class TestContentFactoryAdaption(TestCaseWithMemoryTransport):
 
1407
 
 
1408
    def test_select_adaptor(self):
 
1409
        """Test expected adapters exist."""
 
1410
        # One scenario for each lookup combination we expect to use.
 
1411
        # Each is source_kind, requested_kind, adapter class
 
1412
        scenarios = [
 
1413
            ('knit-delta-gz', 'fulltext', _mod_knit.DeltaPlainToFullText),
 
1414
            ('knit-ft-gz', 'fulltext', _mod_knit.FTPlainToFullText),
 
1415
            ('knit-annotated-delta-gz', 'knit-delta-gz',
 
1416
                _mod_knit.DeltaAnnotatedToUnannotated),
 
1417
            ('knit-annotated-delta-gz', 'fulltext',
 
1418
                _mod_knit.DeltaAnnotatedToFullText),
 
1419
            ('knit-annotated-ft-gz', 'knit-ft-gz',
 
1420
                _mod_knit.FTAnnotatedToUnannotated),
 
1421
            ('knit-annotated-ft-gz', 'fulltext',
 
1422
                _mod_knit.FTAnnotatedToFullText),
 
1423
            ]
 
1424
        for source, requested, klass in scenarios:
 
1425
            adapter_factory = versionedfile.adapter_registry.get(
 
1426
                (source, requested))
 
1427
            adapter = adapter_factory(None)
 
1428
            self.assertIsInstance(adapter, klass)
 
1429
 
 
1430
    def get_knit(self, annotated=True):
 
1431
        if annotated:
 
1432
            factory = KnitAnnotateFactory()
 
1433
        else:
 
1434
            factory = KnitPlainFactory()
 
1435
        return make_file_knit('knit', self.get_transport('.'), delta=True,
 
1436
            create=True, factory=factory)
 
1437
 
 
1438
    def helpGetBytes(self, f, ft_adapter, delta_adapter):
 
1439
        """Grab the interested adapted texts for tests."""
 
1440
        # origin is a fulltext
 
1441
        entries = f.get_record_stream(['origin'], 'unordered', False)
 
1442
        base = entries.next()
 
1443
        ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
 
1444
        # merged is both a delta and multiple parents.
 
1445
        entries = f.get_record_stream(['merged'], 'unordered', False)
 
1446
        merged = entries.next()
 
1447
        delta_data = delta_adapter.get_bytes(merged,
 
1448
            merged.get_bytes_as(merged.storage_kind))
 
1449
        return ft_data, delta_data
 
1450
 
 
1451
    def test_deannotation_noeol(self):
 
1452
        """Test converting annotated knits to unannotated knits."""
 
1453
        # we need a full text, and a delta
 
1454
        f, parents = get_diamond_vf(self.get_knit(), trailing_eol=False)
 
1455
        ft_data, delta_data = self.helpGetBytes(f,
 
1456
            _mod_knit.FTAnnotatedToUnannotated(None),
 
1457
            _mod_knit.DeltaAnnotatedToUnannotated(None))
 
1458
        self.assertEqual(
 
1459
            'version origin 1 b284f94827db1fa2970d9e2014f080413b547a7e\n'
 
1460
            'origin\n'
 
1461
            'end origin\n',
 
1462
            GzipFile(mode='rb', fileobj=StringIO(ft_data)).read())
 
1463
        self.assertEqual(
 
1464
            'version merged 4 32c2e79763b3f90e8ccde37f9710b6629c25a796\n'
 
1465
            '1,2,3\nleft\nright\nmerged\nend merged\n',
 
1466
            GzipFile(mode='rb', fileobj=StringIO(delta_data)).read())
 
1467
 
 
1468
    def test_deannotation(self):
 
1469
        """Test converting annotated knits to unannotated knits."""
 
1470
        # we need a full text, and a delta
 
1471
        f, parents = get_diamond_vf(self.get_knit())
 
1472
        ft_data, delta_data = self.helpGetBytes(f,
 
1473
            _mod_knit.FTAnnotatedToUnannotated(None),
 
1474
            _mod_knit.DeltaAnnotatedToUnannotated(None))
 
1475
        self.assertEqual(
 
1476
            'version origin 1 00e364d235126be43292ab09cb4686cf703ddc17\n'
 
1477
            'origin\n'
 
1478
            'end origin\n',
 
1479
            GzipFile(mode='rb', fileobj=StringIO(ft_data)).read())
 
1480
        self.assertEqual(
 
1481
            'version merged 3 ed8bce375198ea62444dc71952b22cfc2b09226d\n'
 
1482
            '2,2,2\nright\nmerged\nend merged\n',
 
1483
            GzipFile(mode='rb', fileobj=StringIO(delta_data)).read())
 
1484
 
 
1485
    def test_annotated_to_fulltext_no_eol(self):
 
1486
        """Test adapting annotated knits to full texts (for -> weaves)."""
 
1487
        # we need a full text, and a delta
 
1488
        f, parents = get_diamond_vf(self.get_knit(), trailing_eol=False)
 
1489
        # Reconstructing a full text requires a backing versioned file, and it
 
1490
        # must have the base lines requested from it.
 
1491
        logged_vf = versionedfile.RecordingVersionedFileDecorator(f)
 
1492
        ft_data, delta_data = self.helpGetBytes(f,
 
1493
            _mod_knit.FTAnnotatedToFullText(None),
 
1494
            _mod_knit.DeltaAnnotatedToFullText(logged_vf))
 
1495
        self.assertEqual('origin', ft_data)
 
1496
        self.assertEqual('base\nleft\nright\nmerged', delta_data)
 
1497
        self.assertEqual([('get_lines', 'left')], logged_vf.calls)
 
1498
 
 
1499
    def test_annotated_to_fulltext(self):
 
1500
        """Test adapting annotated knits to full texts (for -> weaves)."""
 
1501
        # we need a full text, and a delta
 
1502
        f, parents = get_diamond_vf(self.get_knit())
 
1503
        # Reconstructing a full text requires a backing versioned file, and it
 
1504
        # must have the base lines requested from it.
 
1505
        logged_vf = versionedfile.RecordingVersionedFileDecorator(f)
 
1506
        ft_data, delta_data = self.helpGetBytes(f,
 
1507
            _mod_knit.FTAnnotatedToFullText(None),
 
1508
            _mod_knit.DeltaAnnotatedToFullText(logged_vf))
 
1509
        self.assertEqual('origin\n', ft_data)
 
1510
        self.assertEqual('base\nleft\nright\nmerged\n', delta_data)
 
1511
        self.assertEqual([('get_lines', 'left')], logged_vf.calls)
 
1512
 
 
1513
    def test_unannotated_to_fulltext(self):
 
1514
        """Test adapting unannotated knits to full texts.
 
1515
        
 
1516
        This is used for -> weaves, and for -> annotated knits.
 
1517
        """
 
1518
        # we need a full text, and a delta
 
1519
        f, parents = get_diamond_vf(self.get_knit(annotated=False))
 
1520
        # Reconstructing a full text requires a backing versioned file, and it
 
1521
        # must have the base lines requested from it.
 
1522
        logged_vf = versionedfile.RecordingVersionedFileDecorator(f)
 
1523
        ft_data, delta_data = self.helpGetBytes(f,
 
1524
            _mod_knit.FTPlainToFullText(None),
 
1525
            _mod_knit.DeltaPlainToFullText(logged_vf))
 
1526
        self.assertEqual('origin\n', ft_data)
 
1527
        self.assertEqual('base\nleft\nright\nmerged\n', delta_data)
 
1528
        self.assertEqual([('get_lines', 'left')], logged_vf.calls)
 
1529
 
 
1530
    def test_unannotated_to_fulltext_no_eol(self):
 
1531
        """Test adapting unannotated knits to full texts.
 
1532
        
 
1533
        This is used for -> weaves, and for -> annotated knits.
 
1534
        """
 
1535
        # we need a full text, and a delta
 
1536
        f, parents = get_diamond_vf(self.get_knit(annotated=False),
 
1537
            trailing_eol=False)
 
1538
        # Reconstructing a full text requires a backing versioned file, and it
 
1539
        # must have the base lines requested from it.
 
1540
        logged_vf = versionedfile.RecordingVersionedFileDecorator(f)
 
1541
        ft_data, delta_data = self.helpGetBytes(f,
 
1542
            _mod_knit.FTPlainToFullText(None),
 
1543
            _mod_knit.DeltaPlainToFullText(logged_vf))
 
1544
        self.assertEqual('origin', ft_data)
 
1545
        self.assertEqual('base\nleft\nright\nmerged', delta_data)
 
1546
        self.assertEqual([('get_lines', 'left')], logged_vf.calls)
 
1547