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.
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.
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
# TODO: might be nice to create a versionedfile with some type of corruption
22
# considered typical and check that it can be detected/corrected.
24
from itertools import chain
25
from StringIO import StringIO
22
import bzrlib.errors as errors
23
33
from bzrlib.errors import (
25
35
RevisionAlreadyPresent,
26
36
WeaveParentMismatch
28
from bzrlib.knit import KnitVersionedFile, \
30
from bzrlib.tests import TestCaseWithTransport
31
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
38
from bzrlib import knit as _mod_knit
39
from bzrlib.knit import (
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
32
47
from bzrlib.trace import mutter
33
48
from bzrlib.transport import get_transport
34
49
from bzrlib.transport.memory import MemoryTransport
50
from bzrlib.tsort import topo_sort
51
from bzrlib.tuned_gzip import GzipFile
35
52
import bzrlib.versionedfile as versionedfile
36
53
from bzrlib.weave import WeaveFile
37
from bzrlib.weavefile import read_weave
54
from bzrlib.weavefile import read_weave, write_weave
57
def get_diamond_vf(f, trailing_eol=True, left_only=False):
58
"""Get a diamond graph to exercise deltas and merges.
60
:param trailing_eol: If True end the last line with \n.
64
'base': (('origin',),),
66
'right': (('base',),),
67
'merged': (('left',), ('right',)),
69
# insert a diamond graph to exercise deltas and merges.
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])
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])
40
85
class VersionedFileTestMixIn(object):
64
114
self.assertRaises(RevisionAlreadyPresent,
65
115
f.add_lines, 'r1', [], [])
67
f = self.reopen_file()
117
# this checks that reopen with create=True does not break anything.
118
f = self.reopen_file(create=True)
121
def test_get_record_stream_empty(self):
122
"""get_record_stream is a replacement for get_data_stream."""
124
entries = f.get_record_stream([], 'unordered', False)
125
self.assertEqual([], list(entries))
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'])
134
def capture_stream(self, f, entries, on_seen, parents):
135
"""Capture a stream for testing."""
136
for factory in entries:
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),
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'],
150
self.capture_stream(f, entries, seen.add, parents)
151
self.assertEqual(set([('base',), ('left',), ('right',), ('merged',)]),
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)
160
self.capture_stream(f, entries, seen.append, parents)
161
self.assertSubset([tuple(seen)],
163
(('base',), ('left',), ('right',), ('merged',)),
164
(('base',), ('right',), ('left',), ('merged',)),
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'],
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),
182
self.assertSubset([tuple(seen)],
184
(('base',), ('left',), ('right',), ('merged',)),
185
(('base',), ('right',), ('left',), ('merged',)),
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'],
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.
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),
206
self.assertEqual(set([('base',), ('left',), ('right',), ('merged',)]),
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'],
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)
218
def assertAbsentRecord(self, f, parents, entries):
219
"""Helper for test_get_record_stream_missing_records_are_absent."""
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)
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),
234
set([('base',), ('left',), ('right',), ('merged',), ('or',)]),
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'],
243
self.capture_stream(f, versionedfile.filter_absent(entries), seen.add,
245
self.assertEqual(set([('base',), ('left',), ('right',), ('merged',)]),
248
def test_insert_record_stream_empty(self):
249
"""Inserting an empty record stream should work."""
252
f.insert_record_stream([])
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))
262
def test_insert_record_stream_fulltexts(self):
263
"""Any file should accept a stream of fulltexts."""
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',
270
f.insert_record_stream(stream)
271
self.assertIdenticalVersionedFile(f, source)
273
def test_insert_record_stream_fulltexts_noeol(self):
274
"""Any file should accept a stream of fulltexts."""
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',
281
f.insert_record_stream(stream)
282
self.assertIdenticalVersionedFile(f, source)
284
def test_insert_record_stream_annotated_knits(self):
285
"""Any file should accept a stream from plain knits."""
287
source = make_file_knit('source', get_transport(self.get_url('.')),
289
get_diamond_vf(source)
290
stream = source.get_record_stream(source.versions(), 'topological',
292
f.insert_record_stream(stream)
293
self.assertIdenticalVersionedFile(f, source)
295
def test_insert_record_stream_annotated_knits_noeol(self):
296
"""Any file should accept a stream from plain knits."""
298
source = make_file_knit('source', get_transport(self.get_url('.')),
300
get_diamond_vf(source, trailing_eol=False)
301
stream = source.get_record_stream(source.versions(), 'topological',
303
f.insert_record_stream(stream)
304
self.assertIdenticalVersionedFile(f, source)
306
def test_insert_record_stream_plain_knits(self):
307
"""Any file should accept a stream from plain knits."""
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',
314
f.insert_record_stream(stream)
315
self.assertIdenticalVersionedFile(f, source)
317
def test_insert_record_stream_plain_knits_noeol(self):
318
"""Any file should accept a stream from plain knits."""
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',
325
f.insert_record_stream(stream)
326
self.assertIdenticalVersionedFile(f, source)
328
def test_insert_record_stream_existing_keys(self):
329
"""Inserting keys already in a file should not error."""
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',
338
f.insert_record_stream(stream)
339
self.assertIdenticalVersionedFile(f, source)
341
def test_insert_record_stream_missing_keys(self):
342
"""Inserting a stream with absent keys should raise an error."""
344
source = make_file_knit('source', get_transport(self.get_url('.')),
345
create=True, factory=KnitPlainFactory())
346
stream = source.get_record_stream(['missing'], 'topological',
348
self.assertRaises(errors.RevisionNotPresent, f.insert_record_stream,
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')
362
target.insert_record_stream(entries)
363
except RevisionNotPresent:
364
# Must not have corrupted the file.
367
self.assertIdenticalVersionedFile(f, target)
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('.')),
374
get_diamond_vf(source)
375
entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
377
self.assertRaises(RevisionNotPresent, f.insert_record_stream, entries)
379
self.assertFalse(f.has_version('merged'))
70
381
def test_adds_with_parent_texts(self):
71
382
f = self.get_file()
73
parent_texts['r0'] = f.add_lines('r0', [], ['a\n', 'b\n'])
384
_, _, parent_texts['r0'] = f.add_lines('r0', [], ['a\n', 'b\n'])
75
parent_texts['r1'] = f.add_lines_with_ghosts('r1',
78
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)
79
388
except NotImplementedError:
80
389
# if the format doesn't support ghosts, just add normally.
81
parent_texts['r1'] = f.add_lines('r1',
84
parent_texts=parent_texts)
390
_, _, parent_texts['r1'] = f.add_lines('r1',
391
['r0'], ['b\n', 'c\n'], parent_texts=parent_texts)
85
392
f.add_lines('r2', ['r1'], ['c\n', 'd\n'], parent_texts=parent_texts)
86
393
self.assertNotEqual(None, parent_texts['r0'])
87
394
self.assertNotEqual(None, parent_texts['r1'])
105
412
f = self.reopen_file()
108
def test_get_delta(self):
110
sha1s = self._setup_for_deltas(f)
111
expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
112
[(0, 0, 1, [('base', 'line\n')])])
113
self.assertEqual(expected_delta, f.get_delta('base'))
115
text_name = 'chain1-'
116
for depth in range(26):
117
new_version = text_name + '%s' % depth
118
expected_delta = (next_parent, sha1s[depth],
120
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
121
self.assertEqual(expected_delta, f.get_delta(new_version))
122
next_parent = new_version
124
text_name = 'chain2-'
125
for depth in range(26):
126
new_version = text_name + '%s' % depth
127
expected_delta = (next_parent, sha1s[depth], False,
128
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
129
self.assertEqual(expected_delta, f.get_delta(new_version))
130
next_parent = new_version
131
# smoke test for eol support
132
expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
133
self.assertEqual(['line'], f.get_lines('noeol'))
134
self.assertEqual(expected_delta, f.get_delta('noeol'))
136
def test_get_deltas(self):
138
sha1s = self._setup_for_deltas(f)
139
deltas = f.get_deltas(f.versions())
140
expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
141
[(0, 0, 1, [('base', 'line\n')])])
142
self.assertEqual(expected_delta, deltas['base'])
144
text_name = 'chain1-'
145
for depth in range(26):
146
new_version = text_name + '%s' % depth
147
expected_delta = (next_parent, sha1s[depth],
149
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
150
self.assertEqual(expected_delta, deltas[new_version])
151
next_parent = new_version
153
text_name = 'chain2-'
154
for depth in range(26):
155
new_version = text_name + '%s' % depth
156
expected_delta = (next_parent, sha1s[depth], False,
157
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
158
self.assertEqual(expected_delta, deltas[new_version])
159
next_parent = new_version
160
# smoke tests for eol support
161
expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
162
self.assertEqual(['line'], f.get_lines('noeol'))
163
self.assertEqual(expected_delta, deltas['noeol'])
164
# smoke tests for eol support - two noeol in a row same content
165
expected_deltas = (('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True,
166
[(0, 1, 2, [(u'noeolsecond', 'line\n'), (u'noeolsecond', 'line\n')])]),
167
('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True,
168
[(0, 0, 1, [('noeolsecond', 'line\n')]), (1, 1, 0, [])]))
169
self.assertEqual(['line\n', 'line'], f.get_lines('noeolsecond'))
170
self.assertTrue(deltas['noeolsecond'] in expected_deltas)
171
# two no-eol in a row, different content
172
expected_delta = ('noeolsecond', '8bb553a84e019ef1149db082d65f3133b195223b', True,
173
[(1, 2, 1, [(u'noeolnotshared', 'phone\n')])])
174
self.assertEqual(['line\n', 'phone'], f.get_lines('noeolnotshared'))
175
self.assertEqual(expected_delta, deltas['noeolnotshared'])
176
# eol folling a no-eol with content change
177
expected_delta = ('noeol', 'a61f6fb6cfc4596e8d88c34a308d1e724caf8977', False,
178
[(0, 1, 1, [(u'eol', 'phone\n')])])
179
self.assertEqual(['phone\n'], f.get_lines('eol'))
180
self.assertEqual(expected_delta, deltas['eol'])
181
# eol folling a no-eol with content change
182
expected_delta = ('noeol', '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
183
[(0, 1, 1, [(u'eolline', 'line\n')])])
184
self.assertEqual(['line\n'], f.get_lines('eolline'))
185
self.assertEqual(expected_delta, deltas['eolline'])
186
# eol with no parents
187
expected_delta = (None, '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
188
[(0, 0, 1, [(u'noeolbase', 'line\n')])])
189
self.assertEqual(['line'], f.get_lines('noeolbase'))
190
self.assertEqual(expected_delta, deltas['noeolbase'])
191
# eol with two parents, in inverse insertion order
192
expected_deltas = (('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
193
[(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]),
194
('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
195
[(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]))
196
self.assertEqual(['line'], f.get_lines('eolbeforefirstparent'))
197
#self.assertTrue(deltas['eolbeforefirstparent'] in expected_deltas)
415
def test_add_unicode_content(self):
416
# unicode content is not permitted in versioned files.
417
# versioned files version sequences of bytes only.
419
self.assertRaises(errors.BzrBadParameterUnicode,
420
vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
422
(errors.BzrBadParameterUnicode, NotImplementedError),
423
vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
425
def test_add_follows_left_matching_blocks(self):
426
"""If we change left_matching_blocks, delta changes
428
Note: There are multiple correct deltas in this case, because
429
we start with 1 "a" and we get 3.
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'))
442
def test_inline_newline_throws(self):
443
# \r characters are not permitted in lines being added
445
self.assertRaises(errors.BzrBadParameterContainsNewline,
446
vf.add_lines, 'a', [], ['a\n\n'])
448
(errors.BzrBadParameterContainsNewline, NotImplementedError),
449
vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
450
# but inline CR's are allowed
451
vf.add_lines('a', [], ['a\r\n'])
453
vf.add_lines_with_ghosts('b', [], ['a\r\n'])
454
except NotImplementedError:
457
def test_add_reserved(self):
459
self.assertRaises(errors.ReservedId,
460
vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
462
def test_add_lines_nostoresha(self):
463
"""When nostore_sha is supplied using old content raises."""
465
empty_text = ('a', [])
466
sample_text_nl = ('b', ["foo\n", "bar\n"])
467
sample_text_no_nl = ('c', ["foo\n", "bar"])
469
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
470
sha, _, _ = vf.add_lines(version, [], lines)
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,
478
# and no new version should have been added.
479
self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
482
def test_add_lines_with_ghosts_nostoresha(self):
483
"""When nostore_sha is supplied using old content raises."""
485
empty_text = ('a', [])
486
sample_text_nl = ('b', ["foo\n", "bar\n"])
487
sample_text_no_nl = ('c', ["foo\n", "bar"])
489
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
490
sha, _, _ = vf.add_lines(version, [], lines)
492
# we now have a copy of all the lines in the vf.
493
# is the test applicable to this vf implementation?
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,
503
# and no new version should have been added.
504
self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
507
def test_add_lines_return_value(self):
508
# add_lines should return the sha1 and the text size.
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))),
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])
527
def test_get_reserved(self):
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:')
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))
199
545
def _setup_for_deltas(self, f):
200
self.assertRaises(errors.RevisionNotPresent, f.get_delta, 'base')
546
self.assertFalse(f.has_version('base'))
201
547
# add texts that should trip the knit maximum delta chain threshold
202
548
# as well as doing parallel chains of data in knits.
203
549
# this is done by two chains of 25 insertions
333
640
self.assertRaises(RevisionNotPresent,
334
641
f.get_ancestry, ['rM', 'rX'])
643
self.assertEqual(set(f.get_ancestry('rM')),
644
set(f.get_ancestry('rM', topo_sorted=False)))
336
646
def test_mutate_after_finish(self):
647
self._transaction = 'before'
337
648
f = self.get_file()
338
f.transaction_finished()
339
self.assertRaises(errors.OutSideTransaction, f.add_delta, '', [], '', '', False, [])
649
self._transaction = 'after'
340
650
self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
341
651
self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
342
self.assertRaises(errors.OutSideTransaction, f.fix_parents, '', [])
343
self.assertRaises(errors.OutSideTransaction, f.join, '')
344
self.assertRaises(errors.OutSideTransaction, f.clone_text, 'base', 'bar', ['foo'])
652
self.assertRaises(errors.OutSideTransaction, self.applyDeprecated,
653
one_five, f.join, '')
346
def test_clear_cache(self):
348
# on a new file it should not error
350
# and after adding content, doing a clear_cache and a get should work.
351
f.add_lines('0', [], ['a'])
353
self.assertEqual(['a'], f.get_lines('0'))
355
def test_clone_text(self):
357
f.add_lines('r0', [], ['a\n', 'b\n'])
358
f.clone_text('r1', 'r0', ['r0'])
360
self.assertEquals(f.get_lines('r1'), f.get_lines('r0'))
361
self.assertEquals(f.get_lines('r1'), ['a\n', 'b\n'])
362
self.assertEquals(f.get_parents('r1'), ['r0'])
364
self.assertRaises(RevisionNotPresent,
365
f.clone_text, 'r2', 'rX', [])
366
self.assertRaises(RevisionAlreadyPresent,
367
f.clone_text, 'r1', 'r0', [])
369
verify_file(self.reopen_file())
371
def test_create_empty(self):
373
f.add_lines('0', [], ['a\n'])
374
new_f = f.create_empty('t', MemoryTransport())
375
# smoke test, specific types should check it is honoured correctly for
376
# non type attributes
377
self.assertEqual([], new_f.versions())
378
self.assertTrue(isinstance(new_f, f.__class__))
380
655
def test_copy_to(self):
381
656
f = self.get_file()
382
657
f.add_lines('0', [], ['a\n'])
383
658
t = MemoryTransport()
384
659
f.copy_to('foo', t)
385
for suffix in f.__class__.get_suffixes():
660
for suffix in self.get_factory().get_suffixes():
386
661
self.assertTrue(t.has('foo' + suffix))
388
663
def test_get_suffixes(self):
389
664
f = self.get_file()
391
self.assertEqual(f.__class__.get_suffixes(), f.__class__.get_suffixes())
392
665
# and should be a list
393
self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
395
def test_get_graph(self):
397
f.add_lines('v1', [], ['hello\n'])
398
f.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
399
f.add_lines('v3', ['v2'], ['hello\n', 'cruel\n', 'world\n'])
400
self.assertEqual({'v1': [],
405
def test_get_parents(self):
666
self.assertTrue(isinstance(self.get_factory().get_suffixes(), list))
668
def test_get_parent_map(self):
406
669
f = self.get_file()
407
670
f.add_lines('r0', [], ['a\n', 'b\n'])
408
f.add_lines('r1', [], ['a\n', 'b\n'])
672
{'r0':()}, f.get_parent_map(['r0']))
673
f.add_lines('r1', ['r0'], ['a\n', 'b\n'])
675
{'r1':('r0',)}, f.get_parent_map(['r1']))
679
f.get_parent_map(['r0', 'r1']))
409
680
f.add_lines('r2', [], ['a\n', 'b\n'])
410
681
f.add_lines('r3', [], ['a\n', 'b\n'])
411
682
f.add_lines('m', ['r0', 'r1', 'r2', 'r3'], ['a\n', 'b\n'])
412
self.assertEquals(f.get_parents('m'), ['r0', 'r1', 'r2', 'r3'])
414
self.assertRaises(RevisionNotPresent,
684
{'m':('r0', 'r1', 'r2', 'r3')}, f.get_parent_map(['m']))
685
self.assertEqual({}, f.get_parent_map('y'))
689
f.get_parent_map(['r0', 'y', 'r1']))
417
691
def test_annotate(self):
418
692
f = self.get_file()
491
757
vf.add_lines('otherchild',
492
758
['lancestor', 'base'],
493
759
['base\n', 'lancestor\n', 'otherchild\n'])
494
def iter_with_versions(versions):
760
def iter_with_versions(versions, expected):
495
761
# now we need to see what lines are returned, and how often.
763
progress = InstrumentedProgress()
502
764
# iterate over the lines
503
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,
767
lines.setdefault(line, 0)
769
if []!= progress.updates:
770
self.assertEqual(expected, progress.updates)
506
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)])
507
776
# we must see child and otherchild
508
self.assertTrue(lines['child\n'] > 0)
509
self.assertTrue(lines['otherchild\n'] > 0)
777
self.assertTrue(lines[('child\n', 'child')] > 0)
778
self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
510
779
# we dont care if we got more than that.
513
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)])
514
788
# all lines must be seen at least once
515
self.assertTrue(lines['base\n'] > 0)
516
self.assertTrue(lines['lancestor\n'] > 0)
517
self.assertTrue(lines['rancestor\n'] > 0)
518
self.assertTrue(lines['child\n'] > 0)
519
self.assertTrue(lines['otherchild\n'] > 0)
521
def test_fix_parents(self):
522
# some versioned files allow incorrect parents to be corrected after
523
# insertion - this may not fix ancestry..
524
# if they do not supported, they just do not implement it.
525
# we test this as an interface test to ensure that those that *do*
526
# implementent it get it right.
528
vf.add_lines('notbase', [], [])
529
vf.add_lines('base', [], [])
531
vf.fix_parents('notbase', ['base'])
532
except NotImplementedError:
534
self.assertEqual(['base'], vf.get_parents('notbase'))
535
# open again, check it stuck.
537
self.assertEqual(['base'], vf.get_parents('notbase'))
539
def test_fix_parents_with_ghosts(self):
540
# when fixing parents, ghosts that are listed should not be ghosts
545
vf.add_lines_with_ghosts('notbase', ['base', 'stillghost'], [])
546
except NotImplementedError:
548
vf.add_lines('base', [], [])
549
vf.fix_parents('notbase', ['base', 'stillghost'])
550
self.assertEqual(['base'], vf.get_parents('notbase'))
551
# open again, check it stuck.
553
self.assertEqual(['base'], vf.get_parents('notbase'))
554
# and check the ghosts
555
self.assertEqual(['base', 'stillghost'],
556
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)
558
795
def test_add_lines_with_ghosts(self):
559
796
# some versioned file formats allow lines to be added with parent
562
799
# add_lines_with_ghosts api.
563
800
vf = self.get_file()
564
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')
566
vf.add_lines_with_ghosts(u'notbxbfse', [u'b\xbfse'], [])
806
vf.add_lines_with_ghosts('notbxbfse', [parent_id_utf8], [])
567
807
except NotImplementedError:
568
808
# check the other ghost apis are also not implemented
569
self.assertRaises(NotImplementedError, vf.has_ghost, 'foo')
570
809
self.assertRaises(NotImplementedError, vf.get_ancestry_with_ghosts, ['foo'])
571
810
self.assertRaises(NotImplementedError, vf.get_parents_with_ghosts, 'foo')
572
self.assertRaises(NotImplementedError, vf.get_graph_with_ghosts)
812
vf = self.reopen_file()
574
813
# test key graph related apis: getncestry, _graph, get_parents
576
815
# - these are ghost unaware and must not be reflect ghosts
577
self.assertEqual([u'notbxbfse'], vf.get_ancestry(u'notbxbfse'))
578
self.assertEqual([], vf.get_parents(u'notbxbfse'))
579
self.assertEqual({u'notbxbfse':[]}, vf.get_graph())
580
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))
581
818
# we have _with_ghost apis to give us ghost information.
582
self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
583
self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
584
self.assertEqual({u'notbxbfse':[u'b\xbfse']}, vf.get_graph_with_ghosts())
585
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'))
586
821
# if we add something that is a ghost of another, it should correct the
587
822
# results of the prior apis
588
vf.add_lines(u'b\xbfse', [], [])
589
self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry([u'notbxbfse']))
590
self.assertEqual([u'b\xbfse'], vf.get_parents(u'notbxbfse'))
591
self.assertEqual({u'b\xbfse':[],
592
u'notbxbfse':[u'b\xbfse'],
595
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))
596
828
# we have _with_ghost apis to give us ghost information.
597
self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
598
self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
599
self.assertEqual({u'b\xbfse':[],
600
u'notbxbfse':[u'b\xbfse'],
602
vf.get_graph_with_ghosts())
603
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'))
605
833
def test_add_lines_with_ghosts_after_normal_revs(self):
606
834
# some versioned file formats allow lines to be added with parent
627
854
factory = self.get_factory()
628
855
vf = factory('id', transport, 0777, create=True, access_mode='w')
629
856
vf = factory('id', transport, access_mode='r')
630
self.assertRaises(errors.ReadOnlyError, vf.add_delta, '', [], '', '', False, [])
631
857
self.assertRaises(errors.ReadOnlyError, vf.add_lines, 'base', [], [])
632
858
self.assertRaises(errors.ReadOnlyError,
633
859
vf.add_lines_with_ghosts,
637
self.assertRaises(errors.ReadOnlyError, vf.fix_parents, 'base', [])
638
self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
639
self.assertRaises(errors.ReadOnlyError, vf.clone_text, 'base', 'bar', ['foo'])
863
self.assertRaises(errors.ReadOnlyError, self.applyDeprecated, one_five,
866
def test_get_sha1s(self):
867
# check the sha1 data is available
870
vf.add_lines('a', [], ['a\n'])
871
# the same file, different metadata
872
vf.add_lines('b', ['a'], ['a\n'])
873
# a file differing only in last newline.
874
vf.add_lines('c', [], ['a'])
875
self.assertEqual(['3f786850e387550fdab836ed7e6dc881de23001b',
876
'86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
877
'3f786850e387550fdab836ed7e6dc881de23001b'],
878
vf.get_sha1s(['a', 'c', 'b']))
642
class TestWeave(TestCaseWithTransport, VersionedFileTestMixIn):
881
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
644
883
def get_file(self, name='foo'):
645
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)
647
887
def get_file_corrupted_text(self):
648
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)
649
890
w.add_lines('v1', [], ['hello\n'])
650
891
w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
706
949
knit.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
709
def reopen_file(self, name='foo'):
710
return KnitVersionedFile(name, get_transport(self.get_url('.')), delta=True)
952
def reopen_file(self, name='foo', create=False):
953
return self.get_file(name, create)
712
955
def test_detection(self):
713
print "TODO for merging: create a corrupted knit."
714
956
knit = self.get_file()
717
959
def test_no_implicit_create(self):
718
self.assertRaises(errors.NoSuchFile,
721
get_transport(self.get_url('.')))
960
self.assertRaises(errors.NoSuchFile, self.get_factory(), 'foo',
961
get_transport(self.get_url('.')))
964
class TestPlaintextKnit(TestKnit):
965
"""Test a knit with no cached annotations"""
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())
973
class TestPlanMergeVersionedFile(TestCaseWithMemoryTransport):
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])
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', [],
986
self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a:', None,
988
self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a:', [],
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))
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'])
1006
def test_ancestry_uses_all_versionedfiles(self):
1008
self.assertEqual(set(['A', 'B', 'C', 'D', 'E:']),
1009
self.plan_merge_vf.get_ancestry('E:', topo_sorted=False))
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"')
1016
def test_get_parents(self):
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']))
1027
}, self.plan_merge_vf.get_parent_map(['B', 'D', 'E:', 'F']))
1029
def test_get_lines(self):
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"')
724
1039
class InterString(versionedfile.InterVersionedFile):
810
1134
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
812
1136
def get_file(self):
813
return KnitVersionedFile('foo', get_transport(self.get_url('.')),
1137
return make_file_knit('foo', get_transport(self.get_url('.')),
1138
delta=True, create=True, get_scope=self.get_transaction)
1140
def get_factory(self):
1141
return make_file_knit
1144
class MergeCasesMixin(object):
1146
def doMerge(self, base, a, b, mp):
1147
from cStringIO import StringIO
1148
from textwrap import dedent
1154
w.add_lines('text0', [], map(addcrlf, base))
1155
w.add_lines('text1', ['text0'], map(addcrlf, a))
1156
w.add_lines('text2', ['text0'], map(addcrlf, b))
1158
self.log_contents(w)
1160
self.log('merge plan:')
1161
p = list(w.plan_merge('text1', 'text2'))
1162
for state, line in p:
1164
self.log('%12s | %s' % (state, line[:-1]))
1168
mt.writelines(w.weave_merge(p))
1170
self.log(mt.getvalue())
1172
mp = map(addcrlf, mp)
1173
self.assertEqual(mt.readlines(), mp)
1176
def testOneInsert(self):
1182
def testSeparateInserts(self):
1183
self.doMerge(['aaa', 'bbb', 'ccc'],
1184
['aaa', 'xxx', 'bbb', 'ccc'],
1185
['aaa', 'bbb', 'yyy', 'ccc'],
1186
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
1188
def testSameInsert(self):
1189
self.doMerge(['aaa', 'bbb', 'ccc'],
1190
['aaa', 'xxx', 'bbb', 'ccc'],
1191
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
1192
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
1193
overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
1194
def testOverlappedInsert(self):
1195
self.doMerge(['aaa', 'bbb'],
1196
['aaa', 'xxx', 'yyy', 'bbb'],
1197
['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1199
# really it ought to reduce this to
1200
# ['aaa', 'xxx', 'yyy', 'bbb']
1203
def testClashReplace(self):
1204
self.doMerge(['aaa'],
1207
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
1210
def testNonClashInsert1(self):
1211
self.doMerge(['aaa'],
1214
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
1217
def testNonClashInsert2(self):
1218
self.doMerge(['aaa'],
1224
def testDeleteAndModify(self):
1225
"""Clashing delete and modification.
1227
If one side modifies a region and the other deletes it then
1228
there should be a conflict with one side blank.
1231
#######################################
1232
# skippd, not working yet
1235
self.doMerge(['aaa', 'bbb', 'ccc'],
1236
['aaa', 'ddd', 'ccc'],
1238
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
1240
def _test_merge_from_strings(self, base, a, b, expected):
1242
w.add_lines('text0', [], base.splitlines(True))
1243
w.add_lines('text1', ['text0'], a.splitlines(True))
1244
w.add_lines('text2', ['text0'], b.splitlines(True))
1245
self.log('merge plan:')
1246
p = list(w.plan_merge('text1', 'text2'))
1247
for state, line in p:
1249
self.log('%12s | %s' % (state, line[:-1]))
1250
self.log('merge result:')
1251
result_text = ''.join(w.weave_merge(p))
1252
self.log(result_text)
1253
self.assertEqualDiff(result_text, expected)
1255
def test_weave_merge_conflicts(self):
1256
# does weave merge properly handle plans that end with unchanged?
1257
result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
1258
self.assertEqual(result, 'hello\n')
1260
def test_deletion_extended(self):
1261
"""One side deletes, the other deletes more.
1278
self._test_merge_from_strings(base, a, b, result)
1280
def test_deletion_overlap(self):
1281
"""Delete overlapping regions with no other conflict.
1283
Arguably it'd be better to treat these as agreement, rather than
1284
conflict, but for now conflict is safer.
1312
self._test_merge_from_strings(base, a, b, result)
1314
def test_agreement_deletion(self):
1315
"""Agree to delete some lines, without conflicts."""
1337
self._test_merge_from_strings(base, a, b, result)
1339
def test_sync_on_deletion(self):
1340
"""Specific case of merge where we can synchronize incorrectly.
1342
A previous version of the weave merge concluded that the two versions
1343
agreed on deleting line 2, and this could be a synchronization point.
1344
Line 1 was then considered in isolation, and thought to be deleted on
1347
It's better to consider the whole thing as a disagreement region.
1358
a's replacement line 2
1371
a's replacement line 2
1378
self._test_merge_from_strings(base, a, b, result)
1381
class TestKnitMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1383
def get_file(self, name='foo'):
1384
return make_file_knit(name, get_transport(self.get_url('.')),
814
1385
delta=True, create=True)
816
def get_factory(self):
817
return KnitVersionedFile
1387
def log_contents(self, w):
1391
class TestWeaveMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1393
def get_file(self, name='foo'):
1394
return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1396
def log_contents(self, w):
1397
self.log('weave is:')
1399
write_weave(w, tmpf)
1400
self.log(tmpf.getvalue())
1402
overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1403
'xxx', '>>>>>>> ', 'bbb']
1406
class TestContentFactoryAdaption(TestCaseWithMemoryTransport):
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
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),
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)
1430
def get_knit(self, annotated=True):
1432
factory = KnitAnnotateFactory()
1434
factory = KnitPlainFactory()
1435
return make_file_knit('knit', self.get_transport('.'), delta=True,
1436
create=True, factory=factory)
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
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))
1459
'version origin 1 b284f94827db1fa2970d9e2014f080413b547a7e\n'
1462
GzipFile(mode='rb', fileobj=StringIO(ft_data)).read())
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())
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))
1476
'version origin 1 00e364d235126be43292ab09cb4686cf703ddc17\n'
1479
GzipFile(mode='rb', fileobj=StringIO(ft_data)).read())
1481
'version merged 3 ed8bce375198ea62444dc71952b22cfc2b09226d\n'
1482
'2,2,2\nright\nmerged\nend merged\n',
1483
GzipFile(mode='rb', fileobj=StringIO(delta_data)).read())
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)
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)
1513
def test_unannotated_to_fulltext(self):
1514
"""Test adapting unannotated knits to full texts.
1516
This is used for -> weaves, and for -> annotated knits.
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)
1530
def test_unannotated_to_fulltext_no_eol(self):
1531
"""Test adapting unannotated knits to full texts.
1533
This is used for -> weaves, and for -> annotated knits.
1535
# we need a full text, and a delta
1536
f, parents = get_diamond_vf(self.get_knit(annotated=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)