~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_versionedfile.py

  • Committer: John Arbash Meinel
  • Date: 2008-05-28 23:20:33 UTC
  • mto: This revision was merged to the branch mainline in revision 3458.
  • Revision ID: john@arbash-meinel.com-20080528232033-cx3l3yg845udklps
Bring back always in the form of 'override'.
Change the functions so that they are compatible with the released
definition, and just allow callers to supply override=False
if they want the new behavior.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
7
7
# it under the terms of the GNU General Public License as published by
8
8
# the Free Software Foundation; either version 2 of the License, or
9
9
# (at your option) any later version.
10
 
 
 
10
#
11
11
# This program is distributed in the hope that it will be useful,
12
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
14
# GNU General Public License for more details.
15
 
 
 
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
27
 
import bzrlib.errors as errors
 
28
from bzrlib import (
 
29
    errors,
 
30
    osutils,
 
31
    progress,
 
32
    )
28
33
from bzrlib.errors import (
29
 
                           RevisionNotPresent, 
 
34
                           RevisionNotPresent,
30
35
                           RevisionAlreadyPresent,
31
36
                           WeaveParentMismatch
32
37
                           )
33
 
from bzrlib.knit import KnitVersionedFile, \
34
 
     KnitAnnotateFactory
35
 
from bzrlib.tests import TestCaseWithTransport
36
 
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
 
38
from bzrlib import knit as _mod_knit
 
39
from bzrlib.knit import (
 
40
    make_file_knit,
 
41
    KnitAnnotateFactory,
 
42
    KnitPlainFactory,
 
43
    )
 
44
from bzrlib.symbol_versioning import one_four, one_five
 
45
from bzrlib.tests import TestCaseWithMemoryTransport, TestSkipped
 
46
from bzrlib.tests.http_utils import TestCaseWithWebserver
37
47
from bzrlib.trace import mutter
38
48
from bzrlib.transport import get_transport
39
49
from bzrlib.transport.memory import MemoryTransport
40
50
from bzrlib.tsort import topo_sort
 
51
from bzrlib.tuned_gzip import GzipFile
41
52
import bzrlib.versionedfile as versionedfile
42
53
from bzrlib.weave import WeaveFile
43
54
from bzrlib.weavefile import read_weave, write_weave
44
55
 
45
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
 
46
85
class VersionedFileTestMixIn(object):
47
86
    """A mixin test class for testing VersionedFiles.
48
87
 
51
90
    they are strictly controlled by their owning repositories.
52
91
    """
53
92
 
 
93
    def get_transaction(self):
 
94
        if not hasattr(self, '_transaction'):
 
95
            self._transaction = None
 
96
        return self._transaction
 
97
 
54
98
    def test_add(self):
55
99
        f = self.get_file()
56
100
        f.add_lines('r0', [], ['a\n', 'b\n'])
74
118
        f = self.reopen_file(create=True)
75
119
        verify_file(f)
76
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
 
77
381
    def test_adds_with_parent_texts(self):
78
382
        f = self.get_file()
79
383
        parent_texts = {}
80
 
        parent_texts['r0'] = f.add_lines('r0', [], ['a\n', 'b\n'])
 
384
        _, _, parent_texts['r0'] = f.add_lines('r0', [], ['a\n', 'b\n'])
81
385
        try:
82
 
            parent_texts['r1'] = f.add_lines_with_ghosts('r1',
83
 
                                                         ['r0', 'ghost'], 
84
 
                                                         ['b\n', 'c\n'],
85
 
                                                         parent_texts=parent_texts)
 
386
            _, _, parent_texts['r1'] = f.add_lines_with_ghosts('r1',
 
387
                ['r0', 'ghost'], ['b\n', 'c\n'], parent_texts=parent_texts)
86
388
        except NotImplementedError:
87
389
            # if the format doesn't support ghosts, just add normally.
88
 
            parent_texts['r1'] = f.add_lines('r1',
89
 
                                             ['r0'], 
90
 
                                             ['b\n', 'c\n'],
91
 
                                             parent_texts=parent_texts)
 
390
            _, _, parent_texts['r1'] = f.add_lines('r1',
 
391
                ['r0'], ['b\n', 'c\n'], parent_texts=parent_texts)
92
392
        f.add_lines('r2', ['r1'], ['c\n', 'd\n'], parent_texts=parent_texts)
93
393
        self.assertNotEqual(None, parent_texts['r0'])
94
394
        self.assertNotEqual(None, parent_texts['r1'])
122
422
            (errors.BzrBadParameterUnicode, NotImplementedError),
123
423
            vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
124
424
 
 
425
    def test_add_follows_left_matching_blocks(self):
 
426
        """If we change left_matching_blocks, delta changes
 
427
 
 
428
        Note: There are multiple correct deltas in this case, because
 
429
        we start with 1 "a" and we get 3.
 
430
        """
 
431
        vf = self.get_file()
 
432
        if isinstance(vf, WeaveFile):
 
433
            raise TestSkipped("WeaveFile ignores left_matching_blocks")
 
434
        vf.add_lines('1', [], ['a\n'])
 
435
        vf.add_lines('2', ['1'], ['a\n', 'a\n', 'a\n'],
 
436
                     left_matching_blocks=[(0, 0, 1), (1, 3, 0)])
 
437
        self.assertEqual(['a\n', 'a\n', 'a\n'], vf.get_lines('2'))
 
438
        vf.add_lines('3', ['1'], ['a\n', 'a\n', 'a\n'],
 
439
                     left_matching_blocks=[(0, 2, 1), (1, 3, 0)])
 
440
        self.assertEqual(['a\n', 'a\n', 'a\n'], vf.get_lines('3'))
 
441
 
125
442
    def test_inline_newline_throws(self):
126
443
        # \r characters are not permitted in lines being added
127
444
        vf = self.get_file()
137
454
        except NotImplementedError:
138
455
            pass
139
456
 
140
 
    def test_get_delta(self):
141
 
        f = self.get_file()
142
 
        sha1s = self._setup_for_deltas(f)
143
 
        expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
144
 
                          [(0, 0, 1, [('base', 'line\n')])])
145
 
        self.assertEqual(expected_delta, f.get_delta('base'))
146
 
        next_parent = 'base'
147
 
        text_name = 'chain1-'
148
 
        for depth in range(26):
149
 
            new_version = text_name + '%s' % depth
150
 
            expected_delta = (next_parent, sha1s[depth], 
151
 
                              False,
152
 
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
153
 
            self.assertEqual(expected_delta, f.get_delta(new_version))
154
 
            next_parent = new_version
155
 
        next_parent = 'base'
156
 
        text_name = 'chain2-'
157
 
        for depth in range(26):
158
 
            new_version = text_name + '%s' % depth
159
 
            expected_delta = (next_parent, sha1s[depth], False,
160
 
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
161
 
            self.assertEqual(expected_delta, f.get_delta(new_version))
162
 
            next_parent = new_version
163
 
        # smoke test for eol support
164
 
        expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
165
 
        self.assertEqual(['line'], f.get_lines('noeol'))
166
 
        self.assertEqual(expected_delta, f.get_delta('noeol'))
167
 
 
168
 
    def test_get_deltas(self):
169
 
        f = self.get_file()
170
 
        sha1s = self._setup_for_deltas(f)
171
 
        deltas = f.get_deltas(f.versions())
172
 
        expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
173
 
                          [(0, 0, 1, [('base', 'line\n')])])
174
 
        self.assertEqual(expected_delta, deltas['base'])
175
 
        next_parent = 'base'
176
 
        text_name = 'chain1-'
177
 
        for depth in range(26):
178
 
            new_version = text_name + '%s' % depth
179
 
            expected_delta = (next_parent, sha1s[depth], 
180
 
                              False,
181
 
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
182
 
            self.assertEqual(expected_delta, deltas[new_version])
183
 
            next_parent = new_version
184
 
        next_parent = 'base'
185
 
        text_name = 'chain2-'
186
 
        for depth in range(26):
187
 
            new_version = text_name + '%s' % depth
188
 
            expected_delta = (next_parent, sha1s[depth], False,
189
 
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
190
 
            self.assertEqual(expected_delta, deltas[new_version])
191
 
            next_parent = new_version
192
 
        # smoke tests for eol support
193
 
        expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
194
 
        self.assertEqual(['line'], f.get_lines('noeol'))
195
 
        self.assertEqual(expected_delta, deltas['noeol'])
196
 
        # smoke tests for eol support - two noeol in a row same content
197
 
        expected_deltas = (('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True, 
198
 
                          [(0, 1, 2, [(u'noeolsecond', 'line\n'), (u'noeolsecond', 'line\n')])]),
199
 
                          ('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True, 
200
 
                           [(0, 0, 1, [('noeolsecond', 'line\n')]), (1, 1, 0, [])]))
201
 
        self.assertEqual(['line\n', 'line'], f.get_lines('noeolsecond'))
202
 
        self.assertTrue(deltas['noeolsecond'] in expected_deltas)
203
 
        # two no-eol in a row, different content
204
 
        expected_delta = ('noeolsecond', '8bb553a84e019ef1149db082d65f3133b195223b', True, 
205
 
                          [(1, 2, 1, [(u'noeolnotshared', 'phone\n')])])
206
 
        self.assertEqual(['line\n', 'phone'], f.get_lines('noeolnotshared'))
207
 
        self.assertEqual(expected_delta, deltas['noeolnotshared'])
208
 
        # eol folling a no-eol with content change
209
 
        expected_delta = ('noeol', 'a61f6fb6cfc4596e8d88c34a308d1e724caf8977', False, 
210
 
                          [(0, 1, 1, [(u'eol', 'phone\n')])])
211
 
        self.assertEqual(['phone\n'], f.get_lines('eol'))
212
 
        self.assertEqual(expected_delta, deltas['eol'])
213
 
        # eol folling a no-eol with content change
214
 
        expected_delta = ('noeol', '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
215
 
                          [(0, 1, 1, [(u'eolline', 'line\n')])])
216
 
        self.assertEqual(['line\n'], f.get_lines('eolline'))
217
 
        self.assertEqual(expected_delta, deltas['eolline'])
218
 
        # eol with no parents
219
 
        expected_delta = (None, '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, 
220
 
                          [(0, 0, 1, [(u'noeolbase', 'line\n')])])
221
 
        self.assertEqual(['line'], f.get_lines('noeolbase'))
222
 
        self.assertEqual(expected_delta, deltas['noeolbase'])
223
 
        # eol with two parents, in inverse insertion order
224
 
        expected_deltas = (('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
225
 
                            [(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]),
226
 
                           ('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
227
 
                            [(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]))
228
 
        self.assertEqual(['line'], f.get_lines('eolbeforefirstparent'))
229
 
        #self.assertTrue(deltas['eolbeforefirstparent'] in expected_deltas)
 
457
    def test_add_reserved(self):
 
458
        vf = self.get_file()
 
459
        self.assertRaises(errors.ReservedId,
 
460
            vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
 
461
 
 
462
    def test_add_lines_nostoresha(self):
 
463
        """When nostore_sha is supplied using old content raises."""
 
464
        vf = self.get_file()
 
465
        empty_text = ('a', [])
 
466
        sample_text_nl = ('b', ["foo\n", "bar\n"])
 
467
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
468
        shas = []
 
469
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
 
470
            sha, _, _ = vf.add_lines(version, [], lines)
 
471
            shas.append(sha)
 
472
        # we now have a copy of all the lines in the vf.
 
473
        for sha, (version, lines) in zip(
 
474
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
 
475
            self.assertRaises(errors.ExistingContent,
 
476
                vf.add_lines, version + "2", [], lines,
 
477
                nostore_sha=sha)
 
478
            # and no new version should have been added.
 
479
            self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
 
480
                version + "2")
 
481
 
 
482
    def test_add_lines_with_ghosts_nostoresha(self):
 
483
        """When nostore_sha is supplied using old content raises."""
 
484
        vf = self.get_file()
 
485
        empty_text = ('a', [])
 
486
        sample_text_nl = ('b', ["foo\n", "bar\n"])
 
487
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
488
        shas = []
 
489
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
 
490
            sha, _, _ = vf.add_lines(version, [], lines)
 
491
            shas.append(sha)
 
492
        # we now have a copy of all the lines in the vf.
 
493
        # is the test applicable to this vf implementation?
 
494
        try:
 
495
            vf.add_lines_with_ghosts('d', [], [])
 
496
        except NotImplementedError:
 
497
            raise TestSkipped("add_lines_with_ghosts is optional")
 
498
        for sha, (version, lines) in zip(
 
499
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
 
500
            self.assertRaises(errors.ExistingContent,
 
501
                vf.add_lines_with_ghosts, version + "2", [], lines,
 
502
                nostore_sha=sha)
 
503
            # and no new version should have been added.
 
504
            self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
 
505
                version + "2")
 
506
 
 
507
    def test_add_lines_return_value(self):
 
508
        # add_lines should return the sha1 and the text size.
 
509
        vf = self.get_file()
 
510
        empty_text = ('a', [])
 
511
        sample_text_nl = ('b', ["foo\n", "bar\n"])
 
512
        sample_text_no_nl = ('c', ["foo\n", "bar"])
 
513
        # check results for the three cases:
 
514
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
 
515
            # the first two elements are the same for all versioned files:
 
516
            # - the digest and the size of the text. For some versioned files
 
517
            #   additional data is returned in additional tuple elements.
 
518
            result = vf.add_lines(version, [], lines)
 
519
            self.assertEqual(3, len(result))
 
520
            self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
 
521
                result[0:2])
 
522
        # parents should not affect the result:
 
523
        lines = sample_text_nl[1]
 
524
        self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
 
525
            vf.add_lines('d', ['b', 'c'], lines)[0:2])
 
526
 
 
527
    def test_get_reserved(self):
 
528
        vf = self.get_file()
 
529
        self.assertRaises(errors.ReservedId, vf.get_texts, ['b:'])
 
530
        self.assertRaises(errors.ReservedId, vf.get_lines, 'b:')
 
531
        self.assertRaises(errors.ReservedId, vf.get_text, 'b:')
 
532
 
 
533
    def test_make_mpdiffs(self):
 
534
        from bzrlib import multiparent
 
535
        vf = self.get_file('foo')
 
536
        sha1s = self._setup_for_deltas(vf)
 
537
        new_vf = self.get_file('bar')
 
538
        for version in multiparent.topo_iter(vf):
 
539
            mpdiff = vf.make_mpdiffs([version])[0]
 
540
            new_vf.add_mpdiffs([(version, vf.get_parent_map([version])[version],
 
541
                                 vf.get_sha1s([version])[0], mpdiff)])
 
542
            self.assertEqualDiff(vf.get_text(version),
 
543
                                 new_vf.get_text(version))
230
544
 
231
545
    def _setup_for_deltas(self, f):
232
 
        self.assertRaises(errors.RevisionNotPresent, f.get_delta, 'base')
 
546
        self.assertFalse(f.has_version('base'))
233
547
        # add texts that should trip the knit maximum delta chain threshold
234
548
        # as well as doing parallel chains of data in knits.
235
549
        # this is done by two chains of 25 insertions
298
612
            next_parent = new_version
299
613
        return sha1s
300
614
 
301
 
    def test_add_delta(self):
302
 
        # tests for the add-delta facility.
303
 
        # at this point, optimising for speed, we assume no checks when deltas are inserted.
304
 
        # this may need to be revisited.
305
 
        source = self.get_file('source')
306
 
        source.add_lines('base', [], ['line\n'])
307
 
        next_parent = 'base'
308
 
        text_name = 'chain1-'
309
 
        text = ['line\n']
310
 
        for depth in range(26):
311
 
            new_version = text_name + '%s' % depth
312
 
            text = text + ['line\n']
313
 
            source.add_lines(new_version, [next_parent], text)
314
 
            next_parent = new_version
315
 
        next_parent = 'base'
316
 
        text_name = 'chain2-'
317
 
        text = ['line\n']
318
 
        for depth in range(26):
319
 
            new_version = text_name + '%s' % depth
320
 
            text = text + ['line\n']
321
 
            source.add_lines(new_version, [next_parent], text)
322
 
            next_parent = new_version
323
 
        source.add_lines('noeol', ['base'], ['line'])
324
 
        
325
 
        target = self.get_file('target')
326
 
        for version in source.versions():
327
 
            parent, sha1, noeol, delta = source.get_delta(version)
328
 
            target.add_delta(version,
329
 
                             source.get_parents(version),
330
 
                             parent,
331
 
                             sha1,
332
 
                             noeol,
333
 
                             delta)
334
 
        self.assertRaises(RevisionAlreadyPresent,
335
 
                          target.add_delta, 'base', [], None, '', False, [])
336
 
        for version in source.versions():
337
 
            self.assertEqual(source.get_lines(version),
338
 
                             target.get_lines(version))
339
 
 
340
615
    def test_ancestry(self):
341
616
        f = self.get_file()
342
617
        self.assertEqual([], f.get_ancestry([]))
365
640
        self.assertRaises(RevisionNotPresent,
366
641
            f.get_ancestry, ['rM', 'rX'])
367
642
 
 
643
        self.assertEqual(set(f.get_ancestry('rM')),
 
644
            set(f.get_ancestry('rM', topo_sorted=False)))
 
645
 
368
646
    def test_mutate_after_finish(self):
 
647
        self._transaction = 'before'
369
648
        f = self.get_file()
370
 
        f.transaction_finished()
371
 
        self.assertRaises(errors.OutSideTransaction, f.add_delta, '', [], '', '', False, [])
 
649
        self._transaction = 'after'
372
650
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
373
651
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
374
 
        self.assertRaises(errors.OutSideTransaction, f.fix_parents, '', [])
375
 
        self.assertRaises(errors.OutSideTransaction, f.join, '')
376
 
        self.assertRaises(errors.OutSideTransaction, f.clone_text, 'base', 'bar', ['foo'])
 
652
        self.assertRaises(errors.OutSideTransaction, self.applyDeprecated,
 
653
            one_five, f.join, '')
377
654
        
378
 
    def test_clear_cache(self):
379
 
        f = self.get_file()
380
 
        # on a new file it should not error
381
 
        f.clear_cache()
382
 
        # and after adding content, doing a clear_cache and a get should work.
383
 
        f.add_lines('0', [], ['a'])
384
 
        f.clear_cache()
385
 
        self.assertEqual(['a'], f.get_lines('0'))
386
 
 
387
 
    def test_clone_text(self):
388
 
        f = self.get_file()
389
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
390
 
        f.clone_text('r1', 'r0', ['r0'])
391
 
        def verify_file(f):
392
 
            self.assertEquals(f.get_lines('r1'), f.get_lines('r0'))
393
 
            self.assertEquals(f.get_lines('r1'), ['a\n', 'b\n'])
394
 
            self.assertEquals(f.get_parents('r1'), ['r0'])
395
 
    
396
 
            self.assertRaises(RevisionNotPresent,
397
 
                f.clone_text, 'r2', 'rX', [])
398
 
            self.assertRaises(RevisionAlreadyPresent,
399
 
                f.clone_text, 'r1', 'r0', [])
400
 
        verify_file(f)
401
 
        verify_file(self.reopen_file())
402
 
 
403
 
    def test_create_empty(self):
404
 
        f = self.get_file()
405
 
        f.add_lines('0', [], ['a\n'])
406
 
        new_f = f.create_empty('t', MemoryTransport())
407
 
        # smoke test, specific types should check it is honoured correctly for
408
 
        # non type attributes
409
 
        self.assertEqual([], new_f.versions())
410
 
        self.assertTrue(isinstance(new_f, f.__class__))
411
 
 
412
655
    def test_copy_to(self):
413
656
        f = self.get_file()
414
657
        f.add_lines('0', [], ['a\n'])
415
658
        t = MemoryTransport()
416
659
        f.copy_to('foo', t)
417
 
        for suffix in f.__class__.get_suffixes():
 
660
        for suffix in self.get_factory().get_suffixes():
418
661
            self.assertTrue(t.has('foo' + suffix))
419
662
 
420
663
    def test_get_suffixes(self):
421
664
        f = self.get_file()
422
 
        # should be the same
423
 
        self.assertEqual(f.__class__.get_suffixes(), f.__class__.get_suffixes())
424
665
        # and should be a list
425
 
        self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
426
 
 
427
 
    def build_graph(self, file, graph):
428
 
        for node in topo_sort(graph.items()):
429
 
            file.add_lines(node, graph[node], [])
430
 
 
431
 
    def test_get_graph(self):
432
 
        f = self.get_file()
433
 
        graph = {
434
 
            'v1': [],
435
 
            'v2': ['v1'],
436
 
            'v3': ['v2']}
437
 
        self.build_graph(f, graph)
438
 
        self.assertEqual(graph, f.get_graph())
439
 
    
440
 
    def test_get_graph_partial(self):
441
 
        f = self.get_file()
442
 
        complex_graph = {}
443
 
        simple_a = {
444
 
            'c': [],
445
 
            'b': ['c'],
446
 
            'a': ['b'],
447
 
            }
448
 
        complex_graph.update(simple_a)
449
 
        simple_b = {
450
 
            'c': [],
451
 
            'b': ['c'],
452
 
            }
453
 
        complex_graph.update(simple_b)
454
 
        simple_gam = {
455
 
            'c': [],
456
 
            'oo': [],
457
 
            'bar': ['oo', 'c'],
458
 
            'gam': ['bar'],
459
 
            }
460
 
        complex_graph.update(simple_gam)
461
 
        simple_b_gam = {}
462
 
        simple_b_gam.update(simple_gam)
463
 
        simple_b_gam.update(simple_b)
464
 
        self.build_graph(f, complex_graph)
465
 
        self.assertEqual(simple_a, f.get_graph(['a']))
466
 
        self.assertEqual(simple_b, f.get_graph(['b']))
467
 
        self.assertEqual(simple_gam, f.get_graph(['gam']))
468
 
        self.assertEqual(simple_b_gam, f.get_graph(['b', 'gam']))
469
 
 
470
 
    def test_get_parents(self):
 
666
        self.assertTrue(isinstance(self.get_factory().get_suffixes(), list))
 
667
 
 
668
    def test_get_parent_map(self):
471
669
        f = self.get_file()
472
670
        f.add_lines('r0', [], ['a\n', 'b\n'])
473
 
        f.add_lines('r1', [], ['a\n', 'b\n'])
 
671
        self.assertEqual(
 
672
            {'r0':()}, f.get_parent_map(['r0']))
 
673
        f.add_lines('r1', ['r0'], ['a\n', 'b\n'])
 
674
        self.assertEqual(
 
675
            {'r1':('r0',)}, f.get_parent_map(['r1']))
 
676
        self.assertEqual(
 
677
            {'r0':(),
 
678
             'r1':('r0',)},
 
679
            f.get_parent_map(['r0', 'r1']))
474
680
        f.add_lines('r2', [], ['a\n', 'b\n'])
475
681
        f.add_lines('r3', [], ['a\n', 'b\n'])
476
682
        f.add_lines('m', ['r0', 'r1', 'r2', 'r3'], ['a\n', 'b\n'])
477
 
        self.assertEquals(f.get_parents('m'), ['r0', 'r1', 'r2', 'r3'])
478
 
 
479
 
        self.assertRaises(RevisionNotPresent,
480
 
            f.get_parents, 'y')
 
683
        self.assertEqual(
 
684
            {'m':('r0', 'r1', 'r2', 'r3')}, f.get_parent_map(['m']))
 
685
        self.assertEqual({}, f.get_parent_map('y'))
 
686
        self.assertEqual(
 
687
            {'r0':(),
 
688
             'r1':('r0',)},
 
689
            f.get_parent_map(['r0', 'y', 'r1']))
481
690
 
482
691
    def test_annotate(self):
483
692
        f = self.get_file()
490
699
        self.assertRaises(RevisionNotPresent,
491
700
            f.annotate, 'foo')
492
701
 
493
 
    def test_walk(self):
494
 
        # tests that walk returns all the inclusions for the requested
495
 
        # revisions as well as the revisions changes themselves.
496
 
        f = self.get_file('1')
497
 
        f.add_lines('r0', [], ['a\n', 'b\n'])
498
 
        f.add_lines('r1', ['r0'], ['c\n', 'b\n'])
499
 
        f.add_lines('rX', ['r1'], ['d\n', 'b\n'])
500
 
        f.add_lines('rY', ['r1'], ['c\n', 'e\n'])
501
 
 
502
 
        lines = {}
503
 
        for lineno, insert, dset, text in f.walk(['rX', 'rY']):
504
 
            lines[text] = (insert, dset)
505
 
 
506
 
        self.assertTrue(lines['a\n'], ('r0', set(['r1'])))
507
 
        self.assertTrue(lines['b\n'], ('r0', set(['rY'])))
508
 
        self.assertTrue(lines['c\n'], ('r1', set(['rX'])))
509
 
        self.assertTrue(lines['d\n'], ('rX', set([])))
510
 
        self.assertTrue(lines['e\n'], ('rY', set([])))
511
 
 
512
702
    def test_detection(self):
513
703
        # Test weaves detect corruption.
514
704
        #
543
733
        # versions in the weave 
544
734
        # the ordering here is to make a tree so that dumb searches have
545
735
        # more changes to muck up.
 
736
 
 
737
        class InstrumentedProgress(progress.DummyProgress):
 
738
 
 
739
            def __init__(self):
 
740
 
 
741
                progress.DummyProgress.__init__(self)
 
742
                self.updates = []
 
743
 
 
744
            def update(self, msg=None, current=None, total=None):
 
745
                self.updates.append((msg, current, total))
 
746
 
546
747
        vf = self.get_file()
547
748
        # add a base to get included
548
749
        vf.add_lines('base', [], ['base\n'])
556
757
        vf.add_lines('otherchild',
557
758
                     ['lancestor', 'base'],
558
759
                     ['base\n', 'lancestor\n', 'otherchild\n'])
559
 
        def iter_with_versions(versions):
 
760
        def iter_with_versions(versions, expected):
560
761
            # now we need to see what lines are returned, and how often.
561
 
            lines = {'base\n':0,
562
 
                     'lancestor\n':0,
563
 
                     'rancestor\n':0,
564
 
                     'child\n':0,
565
 
                     'otherchild\n':0,
566
 
                     }
 
762
            lines = {}
 
763
            progress = InstrumentedProgress()
567
764
            # iterate over the lines
568
 
            for line in vf.iter_lines_added_or_present_in_versions(versions):
 
765
            for line in vf.iter_lines_added_or_present_in_versions(versions,
 
766
                pb=progress):
 
767
                lines.setdefault(line, 0)
569
768
                lines[line] += 1
 
769
            if []!= progress.updates:
 
770
                self.assertEqual(expected, progress.updates)
570
771
            return lines
571
 
        lines = iter_with_versions(['child', 'otherchild'])
 
772
        lines = iter_with_versions(['child', 'otherchild'],
 
773
                                   [('Walking content.', 0, 2),
 
774
                                    ('Walking content.', 1, 2),
 
775
                                    ('Walking content.', 2, 2)])
572
776
        # we must see child and otherchild
573
 
        self.assertTrue(lines['child\n'] > 0)
574
 
        self.assertTrue(lines['otherchild\n'] > 0)
 
777
        self.assertTrue(lines[('child\n', 'child')] > 0)
 
778
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
575
779
        # we dont care if we got more than that.
576
780
        
577
781
        # test all lines
578
 
        lines = iter_with_versions(None)
 
782
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
 
783
                                          ('Walking content.', 1, 5),
 
784
                                          ('Walking content.', 2, 5),
 
785
                                          ('Walking content.', 3, 5),
 
786
                                          ('Walking content.', 4, 5),
 
787
                                          ('Walking content.', 5, 5)])
579
788
        # all lines must be seen at least once
580
 
        self.assertTrue(lines['base\n'] > 0)
581
 
        self.assertTrue(lines['lancestor\n'] > 0)
582
 
        self.assertTrue(lines['rancestor\n'] > 0)
583
 
        self.assertTrue(lines['child\n'] > 0)
584
 
        self.assertTrue(lines['otherchild\n'] > 0)
585
 
 
586
 
    def test_fix_parents(self):
587
 
        # some versioned files allow incorrect parents to be corrected after
588
 
        # insertion - this may not fix ancestry..
589
 
        # if they do not supported, they just do not implement it.
590
 
        # we test this as an interface test to ensure that those that *do*
591
 
        # implementent it get it right.
592
 
        vf = self.get_file()
593
 
        vf.add_lines('notbase', [], [])
594
 
        vf.add_lines('base', [], [])
595
 
        try:
596
 
            vf.fix_parents('notbase', ['base'])
597
 
        except NotImplementedError:
598
 
            return
599
 
        self.assertEqual(['base'], vf.get_parents('notbase'))
600
 
        # open again, check it stuck.
601
 
        vf = self.get_file()
602
 
        self.assertEqual(['base'], vf.get_parents('notbase'))
603
 
 
604
 
    def test_fix_parents_with_ghosts(self):
605
 
        # when fixing parents, ghosts that are listed should not be ghosts
606
 
        # anymore.
607
 
        vf = self.get_file()
608
 
 
609
 
        try:
610
 
            vf.add_lines_with_ghosts('notbase', ['base', 'stillghost'], [])
611
 
        except NotImplementedError:
612
 
            return
613
 
        vf.add_lines('base', [], [])
614
 
        vf.fix_parents('notbase', ['base', 'stillghost'])
615
 
        self.assertEqual(['base'], vf.get_parents('notbase'))
616
 
        # open again, check it stuck.
617
 
        vf = self.get_file()
618
 
        self.assertEqual(['base'], vf.get_parents('notbase'))
619
 
        # and check the ghosts
620
 
        self.assertEqual(['base', 'stillghost'],
621
 
                         vf.get_parents_with_ghosts('notbase'))
 
789
        self.assertTrue(lines[('base\n', 'base')] > 0)
 
790
        self.assertTrue(lines[('lancestor\n', 'lancestor')] > 0)
 
791
        self.assertTrue(lines[('rancestor\n', 'rancestor')] > 0)
 
792
        self.assertTrue(lines[('child\n', 'child')] > 0)
 
793
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
622
794
 
623
795
    def test_add_lines_with_ghosts(self):
624
796
        # some versioned file formats allow lines to be added with parent
627
799
        # add_lines_with_ghosts api.
628
800
        vf = self.get_file()
629
801
        # add a revision with ghost parents
 
802
        # The preferred form is utf8, but we should translate when needed
 
803
        parent_id_unicode = u'b\xbfse'
 
804
        parent_id_utf8 = parent_id_unicode.encode('utf8')
630
805
        try:
631
 
            vf.add_lines_with_ghosts(u'notbxbfse', [u'b\xbfse'], [])
 
806
            vf.add_lines_with_ghosts('notbxbfse', [parent_id_utf8], [])
632
807
        except NotImplementedError:
633
808
            # check the other ghost apis are also not implemented
634
 
            self.assertRaises(NotImplementedError, vf.has_ghost, 'foo')
635
809
            self.assertRaises(NotImplementedError, vf.get_ancestry_with_ghosts, ['foo'])
636
810
            self.assertRaises(NotImplementedError, vf.get_parents_with_ghosts, 'foo')
637
 
            self.assertRaises(NotImplementedError, vf.get_graph_with_ghosts)
638
811
            return
 
812
        vf = self.reopen_file()
639
813
        # test key graph related apis: getncestry, _graph, get_parents
640
814
        # has_version
641
815
        # - these are ghost unaware and must not be reflect ghosts
642
 
        self.assertEqual([u'notbxbfse'], vf.get_ancestry(u'notbxbfse'))
643
 
        self.assertEqual([], vf.get_parents(u'notbxbfse'))
644
 
        self.assertEqual({u'notbxbfse':[]}, vf.get_graph())
645
 
        self.assertFalse(vf.has_version(u'b\xbfse'))
 
816
        self.assertEqual(['notbxbfse'], vf.get_ancestry('notbxbfse'))
 
817
        self.assertFalse(vf.has_version(parent_id_utf8))
646
818
        # we have _with_ghost apis to give us ghost information.
647
 
        self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
648
 
        self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
649
 
        self.assertEqual({u'notbxbfse':[u'b\xbfse']}, vf.get_graph_with_ghosts())
650
 
        self.assertTrue(vf.has_ghost(u'b\xbfse'))
 
819
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry_with_ghosts(['notbxbfse']))
 
820
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
651
821
        # if we add something that is a ghost of another, it should correct the
652
822
        # results of the prior apis
653
 
        vf.add_lines(u'b\xbfse', [], [])
654
 
        self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry([u'notbxbfse']))
655
 
        self.assertEqual([u'b\xbfse'], vf.get_parents(u'notbxbfse'))
656
 
        self.assertEqual({u'b\xbfse':[],
657
 
                          u'notbxbfse':[u'b\xbfse'],
658
 
                          },
659
 
                         vf.get_graph())
660
 
        self.assertTrue(vf.has_version(u'b\xbfse'))
 
823
        vf.add_lines(parent_id_utf8, [], [])
 
824
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry(['notbxbfse']))
 
825
        self.assertEqual({'notbxbfse':(parent_id_utf8,)},
 
826
            vf.get_parent_map(['notbxbfse']))
 
827
        self.assertTrue(vf.has_version(parent_id_utf8))
661
828
        # we have _with_ghost apis to give us ghost information.
662
 
        self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
663
 
        self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
664
 
        self.assertEqual({u'b\xbfse':[],
665
 
                          u'notbxbfse':[u'b\xbfse'],
666
 
                          },
667
 
                         vf.get_graph_with_ghosts())
668
 
        self.assertFalse(vf.has_ghost(u'b\xbfse'))
 
829
        self.assertEqual([parent_id_utf8, 'notbxbfse'],
 
830
            vf.get_ancestry_with_ghosts(['notbxbfse']))
 
831
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
669
832
 
670
833
    def test_add_lines_with_ghosts_after_normal_revs(self):
671
834
        # some versioned file formats allow lines to be added with parent
675
838
        vf = self.get_file()
676
839
        # probe for ghost support
677
840
        try:
678
 
            vf.has_ghost('hoo')
 
841
            vf.add_lines_with_ghosts('base', [], ['line\n', 'line_b\n'])
679
842
        except NotImplementedError:
680
843
            return
681
 
        vf.add_lines_with_ghosts('base', [], ['line\n', 'line_b\n'])
682
844
        vf.add_lines_with_ghosts('references_ghost',
683
845
                                 ['base', 'a_ghost'],
684
846
                                 ['line\n', 'line_b\n', 'line_c\n'])
692
854
        factory = self.get_factory()
693
855
        vf = factory('id', transport, 0777, create=True, access_mode='w')
694
856
        vf = factory('id', transport, access_mode='r')
695
 
        self.assertRaises(errors.ReadOnlyError, vf.add_delta, '', [], '', '', False, [])
696
857
        self.assertRaises(errors.ReadOnlyError, vf.add_lines, 'base', [], [])
697
858
        self.assertRaises(errors.ReadOnlyError,
698
859
                          vf.add_lines_with_ghosts,
699
860
                          'base',
700
861
                          [],
701
862
                          [])
702
 
        self.assertRaises(errors.ReadOnlyError, vf.fix_parents, 'base', [])
703
 
        self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
704
 
        self.assertRaises(errors.ReadOnlyError, vf.clone_text, 'base', 'bar', ['foo'])
 
863
        self.assertRaises(errors.ReadOnlyError, self.applyDeprecated, one_five,
 
864
            vf.join, 'base')
705
865
    
706
 
    def test_get_sha1(self):
 
866
    def test_get_sha1s(self):
707
867
        # check the sha1 data is available
708
868
        vf = self.get_file()
709
869
        # a simple file
712
872
        vf.add_lines('b', ['a'], ['a\n'])
713
873
        # a file differing only in last newline.
714
874
        vf.add_lines('c', [], ['a'])
715
 
        self.assertEqual(
716
 
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('a'))
717
 
        self.assertEqual(
718
 
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('b'))
719
 
        self.assertEqual(
720
 
            '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', vf.get_sha1('c'))
 
875
        self.assertEqual(['3f786850e387550fdab836ed7e6dc881de23001b',
 
876
                          '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
 
877
                          '3f786850e387550fdab836ed7e6dc881de23001b'],
 
878
                          vf.get_sha1s(['a', 'c', 'b']))
721
879
        
722
880
 
723
 
class TestWeave(TestCaseWithTransport, VersionedFileTestMixIn):
 
881
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
724
882
 
725
883
    def get_file(self, name='foo'):
726
 
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
 
884
        return WeaveFile(name, get_transport(self.get_url('.')), create=True,
 
885
            get_scope=self.get_transaction)
727
886
 
728
887
    def get_file_corrupted_text(self):
729
 
        w = WeaveFile('foo', get_transport(self.get_url('.')), create=True)
 
888
        w = WeaveFile('foo', get_transport(self.get_url('.')), create=True,
 
889
            get_scope=self.get_transaction)
730
890
        w.add_lines('v1', [], ['hello\n'])
731
891
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
732
892
        
760
920
        return w
761
921
 
762
922
    def reopen_file(self, name='foo', create=False):
763
 
        return WeaveFile(name, get_transport(self.get_url('.')), create=create)
 
923
        return WeaveFile(name, get_transport(self.get_url('.')), create=create,
 
924
            get_scope=self.get_transaction)
764
925
 
765
926
    def test_no_implicit_create(self):
766
927
        self.assertRaises(errors.NoSuchFile,
767
928
                          WeaveFile,
768
929
                          'foo',
769
 
                          get_transport(self.get_url('.')))
 
930
                          get_transport(self.get_url('.')),
 
931
                          get_scope=self.get_transaction)
770
932
 
771
933
    def get_factory(self):
772
934
        return WeaveFile
773
935
 
774
936
 
775
 
class TestKnit(TestCaseWithTransport, VersionedFileTestMixIn):
 
937
class TestKnit(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
776
938
 
777
 
    def get_file(self, name='foo'):
778
 
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
779
 
                                 delta=True, create=True)
 
939
    def get_file(self, name='foo', create=True):
 
940
        return make_file_knit(name, get_transport(self.get_url('.')),
 
941
            delta=True, create=True, get_scope=self.get_transaction)
780
942
 
781
943
    def get_factory(self):
782
 
        return KnitVersionedFile
 
944
        return make_file_knit
783
945
 
784
946
    def get_file_corrupted_text(self):
785
947
        knit = self.get_file()
788
950
        return knit
789
951
 
790
952
    def reopen_file(self, name='foo', create=False):
791
 
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
792
 
            delta=True,
793
 
            create=create)
 
953
        return self.get_file(name, create)
794
954
 
795
955
    def test_detection(self):
796
956
        knit = self.get_file()
797
957
        knit.check()
798
958
 
799
959
    def test_no_implicit_create(self):
800
 
        self.assertRaises(errors.NoSuchFile,
801
 
                          KnitVersionedFile,
802
 
                          'foo',
803
 
                          get_transport(self.get_url('.')))
 
960
        self.assertRaises(errors.NoSuchFile, self.get_factory(), 'foo',
 
961
            get_transport(self.get_url('.')))
 
962
 
 
963
 
 
964
class TestPlaintextKnit(TestKnit):
 
965
    """Test a knit with no cached annotations"""
 
966
 
 
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())
 
971
 
 
972
 
 
973
class TestPlanMergeVersionedFile(TestCaseWithMemoryTransport):
 
974
 
 
975
    def setUp(self):
 
976
        TestCaseWithMemoryTransport.setUp(self)
 
977
        self.vf1 = make_file_knit('root', self.get_transport(), create=True)
 
978
        self.vf2 = make_file_knit('root', self.get_transport(), create=True)
 
979
        self.plan_merge_vf = versionedfile._PlanMergeVersionedFile('root',
 
980
            [self.vf1, self.vf2])
 
981
 
 
982
    def test_add_lines(self):
 
983
        self.plan_merge_vf.add_lines('a:', [], [])
 
984
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a', [],
 
985
                          [])
 
986
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a:', None,
 
987
                          [])
 
988
        self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a:', [],
 
989
                          None)
 
990
 
 
991
    def test_ancestry(self):
 
992
        self.vf1.add_lines('A', [], [])
 
993
        self.vf1.add_lines('B', ['A'], [])
 
994
        self.plan_merge_vf.add_lines('C:', ['B'], [])
 
995
        self.plan_merge_vf.add_lines('D:', ['C:'], [])
 
996
        self.assertEqual(set(['A', 'B', 'C:', 'D:']),
 
997
            self.plan_merge_vf.get_ancestry('D:', topo_sorted=False))
 
998
 
 
999
    def setup_abcde(self):
 
1000
        self.vf1.add_lines('A', [], ['a'])
 
1001
        self.vf1.add_lines('B', ['A'], ['b'])
 
1002
        self.vf2.add_lines('C', [], ['c'])
 
1003
        self.vf2.add_lines('D', ['C'], ['d'])
 
1004
        self.plan_merge_vf.add_lines('E:', ['B', 'D'], ['e'])
 
1005
 
 
1006
    def test_ancestry_uses_all_versionedfiles(self):
 
1007
        self.setup_abcde()
 
1008
        self.assertEqual(set(['A', 'B', 'C', 'D', 'E:']),
 
1009
            self.plan_merge_vf.get_ancestry('E:', topo_sorted=False))
 
1010
 
 
1011
    def test_ancestry_raises_revision_not_present(self):
 
1012
        error = self.assertRaises(errors.RevisionNotPresent,
 
1013
                                  self.plan_merge_vf.get_ancestry, 'E:', False)
 
1014
        self.assertContainsRe(str(error), '{E:} not present in "root"')
 
1015
 
 
1016
    def test_get_parents(self):
 
1017
        self.setup_abcde()
 
1018
        self.assertEqual({'B':('A',)}, self.plan_merge_vf.get_parent_map(['B']))
 
1019
        self.assertEqual({'D':('C',)}, self.plan_merge_vf.get_parent_map(['D']))
 
1020
        self.assertEqual({'E:':('B', 'D')},
 
1021
            self.plan_merge_vf.get_parent_map(['E:']))
 
1022
        self.assertEqual({}, self.plan_merge_vf.get_parent_map(['F']))
 
1023
        self.assertEqual({
 
1024
                'B':('A',),
 
1025
                'D':('C',),
 
1026
                'E:':('B', 'D'),
 
1027
                }, self.plan_merge_vf.get_parent_map(['B', 'D', 'E:', 'F']))
 
1028
 
 
1029
    def test_get_lines(self):
 
1030
        self.setup_abcde()
 
1031
        self.assertEqual(['a'], self.plan_merge_vf.get_lines('A'))
 
1032
        self.assertEqual(['c'], self.plan_merge_vf.get_lines('C'))
 
1033
        self.assertEqual(['e'], self.plan_merge_vf.get_lines('E:'))
 
1034
        error = self.assertRaises(errors.RevisionNotPresent,
 
1035
                                  self.plan_merge_vf.get_lines, 'F')
 
1036
        self.assertContainsRe(str(error), '{F} not present in "root"')
804
1037
 
805
1038
 
806
1039
class InterString(versionedfile.InterVersionedFile):
821
1054
# if we make the registry a separate class though we still need to 
822
1055
# test the behaviour in the active registry to catch failure-to-handle-
823
1056
# stange-objects
824
 
class TestInterVersionedFile(TestCaseWithTransport):
 
1057
class TestInterVersionedFile(TestCaseWithMemoryTransport):
825
1058
 
826
1059
    def test_get_default_inter_versionedfile(self):
827
1060
        # test that the InterVersionedFile.get(a, b) probes
870
1103
 
871
1104
class TestReadonlyHttpMixin(object):
872
1105
 
 
1106
    def get_transaction(self):
 
1107
        return 1
 
1108
 
873
1109
    def test_readonly_http_works(self):
874
1110
        # we should be able to read from http with a versioned file.
875
1111
        vf = self.get_file()
888
1124
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
889
1125
 
890
1126
    def get_file(self):
891
 
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
 
1127
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True,
 
1128
            get_scope=self.get_transaction)
892
1129
 
893
1130
    def get_factory(self):
894
1131
        return WeaveFile
897
1134
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
898
1135
 
899
1136
    def get_file(self):
900
 
        return KnitVersionedFile('foo', get_transport(self.get_url('.')),
901
 
                                 delta=True, create=True)
 
1137
        return make_file_knit('foo', get_transport(self.get_url('.')),
 
1138
            delta=True, create=True, get_scope=self.get_transaction)
902
1139
 
903
1140
    def get_factory(self):
904
 
        return KnitVersionedFile
 
1141
        return make_file_knit
905
1142
 
906
1143
 
907
1144
class MergeCasesMixin(object):
1141
1378
        self._test_merge_from_strings(base, a, b, result)
1142
1379
 
1143
1380
 
1144
 
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
 
1381
class TestKnitMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1145
1382
 
1146
1383
    def get_file(self, name='foo'):
1147
 
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
 
1384
        return make_file_knit(name, get_transport(self.get_url('.')),
1148
1385
                                 delta=True, create=True)
1149
1386
 
1150
1387
    def log_contents(self, w):
1151
1388
        pass
1152
1389
 
1153
1390
 
1154
 
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
 
1391
class TestWeaveMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1155
1392
 
1156
1393
    def get_file(self, name='foo'):
1157
1394
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1164
1401
 
1165
1402
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1166
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