145
348
self.assertRaises(errors.ReservedId,
146
349
vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
148
self.assertRaises(errors.ReservedId,
149
vf.add_delta, 'a:', [], None, 'sha1', False, ((0, 0, 0, []),))
351
def test_add_lines_nostoresha(self):
352
"""When nostore_sha is supplied using old content raises."""
354
empty_text = ('a', [])
355
sample_text_nl = ('b', ["foo\n", "bar\n"])
356
sample_text_no_nl = ('c', ["foo\n", "bar"])
358
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
359
sha, _, _ = vf.add_lines(version, [], lines)
361
# we now have a copy of all the lines in the vf.
362
for sha, (version, lines) in zip(
363
shas, (empty_text, sample_text_nl, sample_text_no_nl)):
364
self.assertRaises(errors.ExistingContent,
365
vf.add_lines, version + "2", [], lines,
367
# and no new version should have been added.
368
self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
371
def test_add_lines_with_ghosts_nostoresha(self):
372
"""When nostore_sha is supplied using old content raises."""
374
empty_text = ('a', [])
375
sample_text_nl = ('b', ["foo\n", "bar\n"])
376
sample_text_no_nl = ('c', ["foo\n", "bar"])
378
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
379
sha, _, _ = vf.add_lines(version, [], lines)
381
# we now have a copy of all the lines in the vf.
382
# is the test applicable to this vf implementation?
384
vf.add_lines_with_ghosts('d', [], [])
385
except NotImplementedError:
386
raise TestSkipped("add_lines_with_ghosts is optional")
387
for sha, (version, lines) in zip(
388
shas, (empty_text, sample_text_nl, sample_text_no_nl)):
389
self.assertRaises(errors.ExistingContent,
390
vf.add_lines_with_ghosts, version + "2", [], lines,
392
# and no new version should have been added.
393
self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
396
def test_add_lines_return_value(self):
397
# add_lines should return the sha1 and the text size.
399
empty_text = ('a', [])
400
sample_text_nl = ('b', ["foo\n", "bar\n"])
401
sample_text_no_nl = ('c', ["foo\n", "bar"])
402
# check results for the three cases:
403
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
404
# the first two elements are the same for all versioned files:
405
# - the digest and the size of the text. For some versioned files
406
# additional data is returned in additional tuple elements.
407
result = vf.add_lines(version, [], lines)
408
self.assertEqual(3, len(result))
409
self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
411
# parents should not affect the result:
412
lines = sample_text_nl[1]
413
self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
414
vf.add_lines('d', ['b', 'c'], lines)[0:2])
151
416
def test_get_reserved(self):
152
417
vf = self.get_file()
153
self.assertRaises(errors.ReservedId, vf.get_delta, 'b:')
154
418
self.assertRaises(errors.ReservedId, vf.get_texts, ['b:'])
155
419
self.assertRaises(errors.ReservedId, vf.get_lines, 'b:')
156
420
self.assertRaises(errors.ReservedId, vf.get_text, 'b:')
158
def test_get_delta(self):
160
sha1s = self._setup_for_deltas(f)
161
expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
162
[(0, 0, 1, [('base', 'line\n')])])
163
self.assertEqual(expected_delta, f.get_delta('base'))
165
text_name = 'chain1-'
166
for depth in range(26):
167
new_version = text_name + '%s' % depth
168
expected_delta = (next_parent, sha1s[depth],
170
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
171
self.assertEqual(expected_delta, f.get_delta(new_version))
172
next_parent = new_version
174
text_name = 'chain2-'
175
for depth in range(26):
176
new_version = text_name + '%s' % depth
177
expected_delta = (next_parent, sha1s[depth], False,
178
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
179
self.assertEqual(expected_delta, f.get_delta(new_version))
180
next_parent = new_version
181
# smoke test for eol support
182
expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
183
self.assertEqual(['line'], f.get_lines('noeol'))
184
self.assertEqual(expected_delta, f.get_delta('noeol'))
186
def test_get_deltas(self):
188
sha1s = self._setup_for_deltas(f)
189
deltas = f.get_deltas(f.versions())
190
expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
191
[(0, 0, 1, [('base', 'line\n')])])
192
self.assertEqual(expected_delta, deltas['base'])
194
text_name = 'chain1-'
195
for depth in range(26):
196
new_version = text_name + '%s' % depth
197
expected_delta = (next_parent, sha1s[depth],
199
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
200
self.assertEqual(expected_delta, deltas[new_version])
201
next_parent = new_version
203
text_name = 'chain2-'
204
for depth in range(26):
205
new_version = text_name + '%s' % depth
206
expected_delta = (next_parent, sha1s[depth], False,
207
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
208
self.assertEqual(expected_delta, deltas[new_version])
209
next_parent = new_version
210
# smoke tests for eol support
211
expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
212
self.assertEqual(['line'], f.get_lines('noeol'))
213
self.assertEqual(expected_delta, deltas['noeol'])
214
# smoke tests for eol support - two noeol in a row same content
215
expected_deltas = (('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True,
216
[(0, 1, 2, [('noeolsecond', 'line\n'), ('noeolsecond', 'line\n')])]),
217
('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True,
218
[(0, 0, 1, [('noeolsecond', 'line\n')]), (1, 1, 0, [])]))
219
self.assertEqual(['line\n', 'line'], f.get_lines('noeolsecond'))
220
self.assertTrue(deltas['noeolsecond'] in expected_deltas)
221
# two no-eol in a row, different content
222
expected_delta = ('noeolsecond', '8bb553a84e019ef1149db082d65f3133b195223b', True,
223
[(1, 2, 1, [('noeolnotshared', 'phone\n')])])
224
self.assertEqual(['line\n', 'phone'], f.get_lines('noeolnotshared'))
225
self.assertEqual(expected_delta, deltas['noeolnotshared'])
226
# eol folling a no-eol with content change
227
expected_delta = ('noeol', 'a61f6fb6cfc4596e8d88c34a308d1e724caf8977', False,
228
[(0, 1, 1, [('eol', 'phone\n')])])
229
self.assertEqual(['phone\n'], f.get_lines('eol'))
230
self.assertEqual(expected_delta, deltas['eol'])
231
# eol folling a no-eol with content change
232
expected_delta = ('noeol', '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
233
[(0, 1, 1, [('eolline', 'line\n')])])
234
self.assertEqual(['line\n'], f.get_lines('eolline'))
235
self.assertEqual(expected_delta, deltas['eolline'])
236
# eol with no parents
237
expected_delta = (None, '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
238
[(0, 0, 1, [('noeolbase', 'line\n')])])
239
self.assertEqual(['line'], f.get_lines('noeolbase'))
240
self.assertEqual(expected_delta, deltas['noeolbase'])
241
# eol with two parents, in inverse insertion order
242
expected_deltas = (('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
243
[(0, 1, 1, [('eolbeforefirstparent', 'line\n')])]),
244
('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
245
[(0, 1, 1, [('eolbeforefirstparent', 'line\n')])]))
246
self.assertEqual(['line'], f.get_lines('eolbeforefirstparent'))
247
#self.assertTrue(deltas['eolbeforefirstparent'] in expected_deltas)
422
def test_add_unchanged_last_line_noeol_snapshot(self):
423
"""Add a text with an unchanged last line with no eol should work."""
424
# Test adding this in a number of chain lengths; because the interface
425
# for VersionedFile does not allow forcing a specific chain length, we
426
# just use a small base to get the first snapshot, then a much longer
427
# first line for the next add (which will make the third add snapshot)
428
# and so on. 20 has been chosen as an aribtrary figure - knits use 200
429
# as a capped delta length, but ideally we would have some way of
430
# tuning the test to the store (e.g. keep going until a snapshot
432
for length in range(20):
434
vf = self.get_file('case-%d' % length)
437
for step in range(length):
438
version = prefix % step
439
lines = (['prelude \n'] * step) + ['line']
440
vf.add_lines(version, parents, lines)
441
version_lines[version] = lines
443
vf.add_lines('no-eol', parents, ['line'])
444
vf.get_texts(version_lines.keys())
445
self.assertEqualDiff('line', vf.get_text('no-eol'))
447
def test_get_texts_eol_variation(self):
448
# similar to the failure in <http://bugs.launchpad.net/234748>
450
sample_text_nl = ["line\n"]
451
sample_text_no_nl = ["line"]
458
lines = sample_text_nl
460
lines = sample_text_no_nl
461
# left_matching blocks is an internal api; it operates on the
462
# *internal* representation for a knit, which is with *all* lines
463
# being normalised to end with \n - even the final line in a no_nl
464
# file. Using it here ensures that a broken internal implementation
465
# (which is what this test tests) will generate a correct line
466
# delta (which is to say, an empty delta).
467
vf.add_lines(version, parents, lines,
468
left_matching_blocks=[(0, 0, 1)])
470
versions.append(version)
471
version_lines[version] = lines
473
vf.get_texts(versions)
474
vf.get_texts(reversed(versions))
476
def test_add_lines_with_matching_blocks_noeol_last_line(self):
477
"""Add a text with an unchanged last line with no eol should work."""
478
from bzrlib import multiparent
479
# Hand verified sha1 of the text we're adding.
480
sha1 = '6a1d115ec7b60afb664dc14890b5af5ce3c827a4'
481
# Create a mpdiff which adds a new line before the trailing line, and
482
# reuse the last line unaltered (which can cause annotation reuse).
483
# Test adding this in two situations:
484
# On top of a new insertion
485
vf = self.get_file('fulltext')
486
vf.add_lines('noeol', [], ['line'])
487
vf.add_lines('noeol2', ['noeol'], ['newline\n', 'line'],
488
left_matching_blocks=[(0, 1, 1)])
489
self.assertEqualDiff('newline\nline', vf.get_text('noeol2'))
491
vf = self.get_file('delta')
492
vf.add_lines('base', [], ['line'])
493
vf.add_lines('noeol', ['base'], ['prelude\n', 'line'])
494
vf.add_lines('noeol2', ['noeol'], ['newline\n', 'line'],
495
left_matching_blocks=[(1, 1, 1)])
496
self.assertEqualDiff('newline\nline', vf.get_text('noeol2'))
498
def test_make_mpdiffs(self):
499
from bzrlib import multiparent
500
vf = self.get_file('foo')
501
sha1s = self._setup_for_deltas(vf)
502
new_vf = self.get_file('bar')
503
for version in multiparent.topo_iter(vf):
504
mpdiff = vf.make_mpdiffs([version])[0]
505
new_vf.add_mpdiffs([(version, vf.get_parent_map([version])[version],
506
vf.get_sha1s([version])[version], mpdiff)])
507
self.assertEqualDiff(vf.get_text(version),
508
new_vf.get_text(version))
510
def test_make_mpdiffs_with_ghosts(self):
511
vf = self.get_file('foo')
513
vf.add_lines_with_ghosts('text', ['ghost'], ['line\n'])
514
except NotImplementedError:
515
# old Weave formats do not allow ghosts
517
self.assertRaises(errors.RevisionNotPresent, vf.make_mpdiffs, ['ghost'])
249
519
def _setup_for_deltas(self, f):
250
self.assertRaises(errors.RevisionNotPresent, f.get_delta, 'base')
520
self.assertFalse(f.has_version('base'))
251
521
# add texts that should trip the knit maximum delta chain threshold
252
522
# as well as doing parallel chains of data in knits.
253
523
# this is done by two chains of 25 insertions
1214
1244
overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1215
1245
'xxx', '>>>>>>> ', 'bbb']
1248
class TestContentFactoryAdaption(TestCaseWithMemoryTransport):
1250
def test_select_adaptor(self):
1251
"""Test expected adapters exist."""
1252
# One scenario for each lookup combination we expect to use.
1253
# Each is source_kind, requested_kind, adapter class
1255
('knit-delta-gz', 'fulltext', _mod_knit.DeltaPlainToFullText),
1256
('knit-ft-gz', 'fulltext', _mod_knit.FTPlainToFullText),
1257
('knit-annotated-delta-gz', 'knit-delta-gz',
1258
_mod_knit.DeltaAnnotatedToUnannotated),
1259
('knit-annotated-delta-gz', 'fulltext',
1260
_mod_knit.DeltaAnnotatedToFullText),
1261
('knit-annotated-ft-gz', 'knit-ft-gz',
1262
_mod_knit.FTAnnotatedToUnannotated),
1263
('knit-annotated-ft-gz', 'fulltext',
1264
_mod_knit.FTAnnotatedToFullText),
1266
for source, requested, klass in scenarios:
1267
adapter_factory = versionedfile.adapter_registry.get(
1268
(source, requested))
1269
adapter = adapter_factory(None)
1270
self.assertIsInstance(adapter, klass)
1272
def get_knit(self, annotated=True):
1273
mapper = ConstantMapper('knit')
1274
transport = self.get_transport()
1275
return make_file_factory(annotated, mapper)(transport)
1277
def helpGetBytes(self, f, ft_adapter, delta_adapter):
1278
"""Grab the interested adapted texts for tests."""
1279
# origin is a fulltext
1280
entries = f.get_record_stream([('origin',)], 'unordered', False)
1281
base = entries.next()
1282
ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
1283
# merged is both a delta and multiple parents.
1284
entries = f.get_record_stream([('merged',)], 'unordered', False)
1285
merged = entries.next()
1286
delta_data = delta_adapter.get_bytes(merged,
1287
merged.get_bytes_as(merged.storage_kind))
1288
return ft_data, delta_data
1290
def test_deannotation_noeol(self):
1291
"""Test converting annotated knits to unannotated knits."""
1292
# we need a full text, and a delta
1294
get_diamond_files(f, 1, trailing_eol=False)
1295
ft_data, delta_data = self.helpGetBytes(f,
1296
_mod_knit.FTAnnotatedToUnannotated(None),
1297
_mod_knit.DeltaAnnotatedToUnannotated(None))
1299
'version origin 1 b284f94827db1fa2970d9e2014f080413b547a7e\n'
1302
GzipFile(mode='rb', fileobj=StringIO(ft_data)).read())
1304
'version merged 4 32c2e79763b3f90e8ccde37f9710b6629c25a796\n'
1305
'1,2,3\nleft\nright\nmerged\nend merged\n',
1306
GzipFile(mode='rb', fileobj=StringIO(delta_data)).read())
1308
def test_deannotation(self):
1309
"""Test converting annotated knits to unannotated knits."""
1310
# we need a full text, and a delta
1312
get_diamond_files(f, 1)
1313
ft_data, delta_data = self.helpGetBytes(f,
1314
_mod_knit.FTAnnotatedToUnannotated(None),
1315
_mod_knit.DeltaAnnotatedToUnannotated(None))
1317
'version origin 1 00e364d235126be43292ab09cb4686cf703ddc17\n'
1320
GzipFile(mode='rb', fileobj=StringIO(ft_data)).read())
1322
'version merged 3 ed8bce375198ea62444dc71952b22cfc2b09226d\n'
1323
'2,2,2\nright\nmerged\nend merged\n',
1324
GzipFile(mode='rb', fileobj=StringIO(delta_data)).read())
1326
def test_annotated_to_fulltext_no_eol(self):
1327
"""Test adapting annotated knits to full texts (for -> weaves)."""
1328
# we need a full text, and a delta
1330
get_diamond_files(f, 1, trailing_eol=False)
1331
# Reconstructing a full text requires a backing versioned file, and it
1332
# must have the base lines requested from it.
1333
logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1334
ft_data, delta_data = self.helpGetBytes(f,
1335
_mod_knit.FTAnnotatedToFullText(None),
1336
_mod_knit.DeltaAnnotatedToFullText(logged_vf))
1337
self.assertEqual('origin', ft_data)
1338
self.assertEqual('base\nleft\nright\nmerged', delta_data)
1339
self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1340
True)], logged_vf.calls)
1342
def test_annotated_to_fulltext(self):
1343
"""Test adapting annotated knits to full texts (for -> weaves)."""
1344
# we need a full text, and a delta
1346
get_diamond_files(f, 1)
1347
# Reconstructing a full text requires a backing versioned file, and it
1348
# must have the base lines requested from it.
1349
logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1350
ft_data, delta_data = self.helpGetBytes(f,
1351
_mod_knit.FTAnnotatedToFullText(None),
1352
_mod_knit.DeltaAnnotatedToFullText(logged_vf))
1353
self.assertEqual('origin\n', ft_data)
1354
self.assertEqual('base\nleft\nright\nmerged\n', delta_data)
1355
self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1356
True)], logged_vf.calls)
1358
def test_unannotated_to_fulltext(self):
1359
"""Test adapting unannotated knits to full texts.
1361
This is used for -> weaves, and for -> annotated knits.
1363
# we need a full text, and a delta
1364
f = self.get_knit(annotated=False)
1365
get_diamond_files(f, 1)
1366
# Reconstructing a full text requires a backing versioned file, and it
1367
# must have the base lines requested from it.
1368
logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1369
ft_data, delta_data = self.helpGetBytes(f,
1370
_mod_knit.FTPlainToFullText(None),
1371
_mod_knit.DeltaPlainToFullText(logged_vf))
1372
self.assertEqual('origin\n', ft_data)
1373
self.assertEqual('base\nleft\nright\nmerged\n', delta_data)
1374
self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1375
True)], logged_vf.calls)
1377
def test_unannotated_to_fulltext_no_eol(self):
1378
"""Test adapting unannotated knits to full texts.
1380
This is used for -> weaves, and for -> annotated knits.
1382
# we need a full text, and a delta
1383
f = self.get_knit(annotated=False)
1384
get_diamond_files(f, 1, trailing_eol=False)
1385
# Reconstructing a full text requires a backing versioned file, and it
1386
# must have the base lines requested from it.
1387
logged_vf = versionedfile.RecordingVersionedFilesDecorator(f)
1388
ft_data, delta_data = self.helpGetBytes(f,
1389
_mod_knit.FTPlainToFullText(None),
1390
_mod_knit.DeltaPlainToFullText(logged_vf))
1391
self.assertEqual('origin', ft_data)
1392
self.assertEqual('base\nleft\nright\nmerged', delta_data)
1393
self.assertEqual([('get_record_stream', [('left',)], 'unordered',
1394
True)], logged_vf.calls)
1397
class TestKeyMapper(TestCaseWithMemoryTransport):
1398
"""Tests for various key mapping logic."""
1400
def test_identity_mapper(self):
1401
mapper = versionedfile.ConstantMapper("inventory")
1402
self.assertEqual("inventory", mapper.map(('foo@ar',)))
1403
self.assertEqual("inventory", mapper.map(('quux',)))
1405
def test_prefix_mapper(self):
1407
mapper = versionedfile.PrefixMapper()
1408
self.assertEqual("file-id", mapper.map(("file-id", "revision-id")))
1409
self.assertEqual("new-id", mapper.map(("new-id", "revision-id")))
1410
self.assertEqual(('file-id',), mapper.unmap("file-id"))
1411
self.assertEqual(('new-id',), mapper.unmap("new-id"))
1413
def test_hash_prefix_mapper(self):
1414
#format6: hash + plain
1415
mapper = versionedfile.HashPrefixMapper()
1416
self.assertEqual("9b/file-id", mapper.map(("file-id", "revision-id")))
1417
self.assertEqual("45/new-id", mapper.map(("new-id", "revision-id")))
1418
self.assertEqual(('file-id',), mapper.unmap("9b/file-id"))
1419
self.assertEqual(('new-id',), mapper.unmap("45/new-id"))
1421
def test_hash_escaped_mapper(self):
1422
#knit1: hash + escaped
1423
mapper = versionedfile.HashEscapedPrefixMapper()
1424
self.assertEqual("88/%2520", mapper.map((" ", "revision-id")))
1425
self.assertEqual("ed/fil%2545-%2549d", mapper.map(("filE-Id",
1427
self.assertEqual("88/ne%2557-%2549d", mapper.map(("neW-Id",
1429
self.assertEqual(('filE-Id',), mapper.unmap("ed/fil%2545-%2549d"))
1430
self.assertEqual(('neW-Id',), mapper.unmap("88/ne%2557-%2549d"))
1433
class TestVersionedFiles(TestCaseWithMemoryTransport):
1434
"""Tests for the multiple-file variant of VersionedFile."""
1436
def get_versionedfiles(self, relpath='files'):
1437
transport = self.get_transport(relpath)
1439
transport.mkdir('.')
1440
files = self.factory(transport)
1441
if self.cleanup is not None:
1442
self.addCleanup(lambda:self.cleanup(files))
1445
def test_annotate(self):
1446
files = self.get_versionedfiles()
1447
self.get_diamond_files(files)
1448
if self.key_length == 1:
1452
# introduced full text
1453
origins = files.annotate(prefix + ('origin',))
1455
(prefix + ('origin',), 'origin\n')],
1458
origins = files.annotate(prefix + ('base',))
1460
(prefix + ('base',), 'base\n')],
1463
origins = files.annotate(prefix + ('merged',))
1466
(prefix + ('base',), 'base\n'),
1467
(prefix + ('left',), 'left\n'),
1468
(prefix + ('right',), 'right\n'),
1469
(prefix + ('merged',), 'merged\n')
1473
# Without a graph everything is new.
1475
(prefix + ('merged',), 'base\n'),
1476
(prefix + ('merged',), 'left\n'),
1477
(prefix + ('merged',), 'right\n'),
1478
(prefix + ('merged',), 'merged\n')
1481
self.assertRaises(RevisionNotPresent,
1482
files.annotate, prefix + ('missing-key',))
1484
def test_construct(self):
1485
"""Each parameterised test can be constructed on a transport."""
1486
files = self.get_versionedfiles()
1488
def get_diamond_files(self, files, trailing_eol=True, left_only=False):
1489
return get_diamond_files(files, self.key_length,
1490
trailing_eol=trailing_eol, nograph=not self.graph,
1491
left_only=left_only)
1493
def test_add_lines_return(self):
1494
files = self.get_versionedfiles()
1495
# save code by using the stock data insertion helper.
1496
adds = self.get_diamond_files(files)
1498
# We can only validate the first 2 elements returned from add_lines.
1500
self.assertEqual(3, len(add))
1501
results.append(add[:2])
1502
if self.key_length == 1:
1504
('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1505
('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1506
('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1507
('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1508
('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1510
elif self.key_length == 2:
1512
('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1513
('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1514
('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1515
('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1516
('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1517
('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1518
('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1519
('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1520
('ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
1521
('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1524
def test_empty_lines(self):
1525
"""Empty files can be stored."""
1526
f = self.get_versionedfiles()
1527
key_a = self.get_simple_key('a')
1528
f.add_lines(key_a, [], [])
1529
self.assertEqual('',
1530
f.get_record_stream([key_a], 'unordered', True
1531
).next().get_bytes_as('fulltext'))
1532
key_b = self.get_simple_key('b')
1533
f.add_lines(key_b, self.get_parents([key_a]), [])
1534
self.assertEqual('',
1535
f.get_record_stream([key_b], 'unordered', True
1536
).next().get_bytes_as('fulltext'))
1538
def test_newline_only(self):
1539
f = self.get_versionedfiles()
1540
key_a = self.get_simple_key('a')
1541
f.add_lines(key_a, [], ['\n'])
1542
self.assertEqual('\n',
1543
f.get_record_stream([key_a], 'unordered', True
1544
).next().get_bytes_as('fulltext'))
1545
key_b = self.get_simple_key('b')
1546
f.add_lines(key_b, self.get_parents([key_a]), ['\n'])
1547
self.assertEqual('\n',
1548
f.get_record_stream([key_b], 'unordered', True
1549
).next().get_bytes_as('fulltext'))
1551
def test_get_record_stream_empty(self):
1552
"""An empty stream can be requested without error."""
1553
f = self.get_versionedfiles()
1554
entries = f.get_record_stream([], 'unordered', False)
1555
self.assertEqual([], list(entries))
1557
def assertValidStorageKind(self, storage_kind):
1558
"""Assert that storage_kind is a valid storage_kind."""
1559
self.assertSubset([storage_kind],
1560
['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1561
'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1562
'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1565
def capture_stream(self, f, entries, on_seen, parents):
1566
"""Capture a stream for testing."""
1567
for factory in entries:
1568
on_seen(factory.key)
1569
self.assertValidStorageKind(factory.storage_kind)
1570
self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1572
self.assertEqual(parents[factory.key], factory.parents)
1573
self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1576
def test_get_record_stream_interface(self):
1577
"""each item in a stream has to provide a regular interface."""
1578
files = self.get_versionedfiles()
1579
self.get_diamond_files(files)
1580
keys, _ = self.get_keys_and_sort_order()
1581
parent_map = files.get_parent_map(keys)
1582
entries = files.get_record_stream(keys, 'unordered', False)
1584
self.capture_stream(files, entries, seen.add, parent_map)
1585
self.assertEqual(set(keys), seen)
1587
def get_simple_key(self, suffix):
1588
"""Return a key for the object under test."""
1589
if self.key_length == 1:
1592
return ('FileA',) + (suffix,)
1594
def get_keys_and_sort_order(self):
1595
"""Get diamond test keys list, and their sort ordering."""
1596
if self.key_length == 1:
1597
keys = [('merged',), ('left',), ('right',), ('base',)]
1598
sort_order = {('merged',):2, ('left',):1, ('right',):1, ('base',):0}
1601
('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1603
('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1607
('FileA', 'merged'):2, ('FileA', 'left'):1, ('FileA', 'right'):1,
1608
('FileA', 'base'):0,
1609
('FileB', 'merged'):2, ('FileB', 'left'):1, ('FileB', 'right'):1,
1610
('FileB', 'base'):0,
1612
return keys, sort_order
1614
def test_get_record_stream_interface_ordered(self):
1615
"""each item in a stream has to provide a regular interface."""
1616
files = self.get_versionedfiles()
1617
self.get_diamond_files(files)
1618
keys, sort_order = self.get_keys_and_sort_order()
1619
parent_map = files.get_parent_map(keys)
1620
entries = files.get_record_stream(keys, 'topological', False)
1622
self.capture_stream(files, entries, seen.append, parent_map)
1623
self.assertStreamOrder(sort_order, seen, keys)
1625
def test_get_record_stream_interface_ordered_with_delta_closure(self):
1626
"""each item must be accessible as a fulltext."""
1627
files = self.get_versionedfiles()
1628
self.get_diamond_files(files)
1629
keys, sort_order = self.get_keys_and_sort_order()
1630
parent_map = files.get_parent_map(keys)
1631
entries = files.get_record_stream(keys, 'topological', True)
1633
for factory in entries:
1634
seen.append(factory.key)
1635
self.assertValidStorageKind(factory.storage_kind)
1636
self.assertSubset([factory.sha1],
1637
[None, files.get_sha1s([factory.key])[factory.key]])
1638
self.assertEqual(parent_map[factory.key], factory.parents)
1639
# self.assertEqual(files.get_text(factory.key),
1640
ft_bytes = factory.get_bytes_as('fulltext')
1641
self.assertIsInstance(ft_bytes, str)
1642
chunked_bytes = factory.get_bytes_as('chunked')
1643
self.assertEqualDiff(ft_bytes, ''.join(chunked_bytes))
1645
self.assertStreamOrder(sort_order, seen, keys)
1647
def assertStreamOrder(self, sort_order, seen, keys):
1648
self.assertEqual(len(set(seen)), len(keys))
1649
if self.key_length == 1:
1652
lows = {('FileA',):0, ('FileB',):0}
1654
self.assertEqual(set(keys), set(seen))
1657
sort_pos = sort_order[key]
1658
self.assertTrue(sort_pos >= lows[key[:-1]],
1659
"Out of order in sorted stream: %r, %r" % (key, seen))
1660
lows[key[:-1]] = sort_pos
1662
def test_get_record_stream_unknown_storage_kind_raises(self):
1663
"""Asking for a storage kind that the stream cannot supply raises."""
1664
files = self.get_versionedfiles()
1665
self.get_diamond_files(files)
1666
if self.key_length == 1:
1667
keys = [('merged',), ('left',), ('right',), ('base',)]
1670
('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1672
('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1675
parent_map = files.get_parent_map(keys)
1676
entries = files.get_record_stream(keys, 'unordered', False)
1677
# We track the contents because we should be able to try, fail a
1678
# particular kind and then ask for one that works and continue.
1680
for factory in entries:
1681
seen.add(factory.key)
1682
self.assertValidStorageKind(factory.storage_kind)
1683
self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1685
self.assertEqual(parent_map[factory.key], factory.parents)
1686
# currently no stream emits mpdiff
1687
self.assertRaises(errors.UnavailableRepresentation,
1688
factory.get_bytes_as, 'mpdiff')
1689
self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1691
self.assertEqual(set(keys), seen)
1693
def test_get_record_stream_missing_records_are_absent(self):
1694
files = self.get_versionedfiles()
1695
self.get_diamond_files(files)
1696
if self.key_length == 1:
1697
keys = [('merged',), ('left',), ('right',), ('absent',), ('base',)]
1700
('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1701
('FileA', 'absent'), ('FileA', 'base'),
1702
('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1703
('FileB', 'absent'), ('FileB', 'base'),
1704
('absent', 'absent'),
1706
parent_map = files.get_parent_map(keys)
1707
entries = files.get_record_stream(keys, 'unordered', False)
1708
self.assertAbsentRecord(files, keys, parent_map, entries)
1709
entries = files.get_record_stream(keys, 'topological', False)
1710
self.assertAbsentRecord(files, keys, parent_map, entries)
1712
def assertAbsentRecord(self, files, keys, parents, entries):
1713
"""Helper for test_get_record_stream_missing_records_are_absent."""
1715
for factory in entries:
1716
seen.add(factory.key)
1717
if factory.key[-1] == 'absent':
1718
self.assertEqual('absent', factory.storage_kind)
1719
self.assertEqual(None, factory.sha1)
1720
self.assertEqual(None, factory.parents)
1722
self.assertValidStorageKind(factory.storage_kind)
1723
self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1725
self.assertEqual(parents[factory.key], factory.parents)
1726
self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1728
self.assertEqual(set(keys), seen)
1730
def test_filter_absent_records(self):
1731
"""Requested missing records can be filter trivially."""
1732
files = self.get_versionedfiles()
1733
self.get_diamond_files(files)
1734
keys, _ = self.get_keys_and_sort_order()
1735
parent_map = files.get_parent_map(keys)
1736
# Add an absent record in the middle of the present keys. (We don't ask
1737
# for just absent keys to ensure that content before and after the
1738
# absent keys is still delivered).
1739
present_keys = list(keys)
1740
if self.key_length == 1:
1741
keys.insert(2, ('extra',))
1743
keys.insert(2, ('extra', 'extra'))
1744
entries = files.get_record_stream(keys, 'unordered', False)
1746
self.capture_stream(files, versionedfile.filter_absent(entries), seen.add,
1748
self.assertEqual(set(present_keys), seen)
1750
def get_mapper(self):
1751
"""Get a mapper suitable for the key length of the test interface."""
1752
if self.key_length == 1:
1753
return ConstantMapper('source')
1755
return HashEscapedPrefixMapper()
1757
def get_parents(self, parents):
1758
"""Get parents, taking self.graph into consideration."""
1764
def test_get_parent_map(self):
1765
files = self.get_versionedfiles()
1766
if self.key_length == 1:
1768
(('r0',), self.get_parents(())),
1769
(('r1',), self.get_parents((('r0',),))),
1770
(('r2',), self.get_parents(())),
1771
(('r3',), self.get_parents(())),
1772
(('m',), self.get_parents((('r0',),('r1',),('r2',),('r3',)))),
1776
(('FileA', 'r0'), self.get_parents(())),
1777
(('FileA', 'r1'), self.get_parents((('FileA', 'r0'),))),
1778
(('FileA', 'r2'), self.get_parents(())),
1779
(('FileA', 'r3'), self.get_parents(())),
1780
(('FileA', 'm'), self.get_parents((('FileA', 'r0'),
1781
('FileA', 'r1'), ('FileA', 'r2'), ('FileA', 'r3')))),
1783
for key, parents in parent_details:
1784
files.add_lines(key, parents, [])
1785
# immediately after adding it should be queryable.
1786
self.assertEqual({key:parents}, files.get_parent_map([key]))
1787
# We can ask for an empty set
1788
self.assertEqual({}, files.get_parent_map([]))
1789
# We can ask for many keys
1790
all_parents = dict(parent_details)
1791
self.assertEqual(all_parents, files.get_parent_map(all_parents.keys()))
1792
# Absent keys are just not included in the result.
1793
keys = all_parents.keys()
1794
if self.key_length == 1:
1795
keys.insert(1, ('missing',))
1797
keys.insert(1, ('missing', 'missing'))
1798
# Absent keys are just ignored
1799
self.assertEqual(all_parents, files.get_parent_map(keys))
1801
def test_get_sha1s(self):
1802
files = self.get_versionedfiles()
1803
self.get_diamond_files(files)
1804
if self.key_length == 1:
1805
keys = [('base',), ('origin',), ('left',), ('merged',), ('right',)]
1807
# ask for shas from different prefixes.
1809
('FileA', 'base'), ('FileB', 'origin'), ('FileA', 'left'),
1810
('FileA', 'merged'), ('FileB', 'right'),
1813
keys[0]: '51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',
1814
keys[1]: '00e364d235126be43292ab09cb4686cf703ddc17',
1815
keys[2]: 'a8478686da38e370e32e42e8a0c220e33ee9132f',
1816
keys[3]: 'ed8bce375198ea62444dc71952b22cfc2b09226d',
1817
keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
1819
files.get_sha1s(keys))
1821
def test_insert_record_stream_empty(self):
1822
"""Inserting an empty record stream should work."""
1823
files = self.get_versionedfiles()
1824
files.insert_record_stream([])
1826
def assertIdenticalVersionedFile(self, expected, actual):
1827
"""Assert that left and right have the same contents."""
1828
self.assertEqual(set(actual.keys()), set(expected.keys()))
1829
actual_parents = actual.get_parent_map(actual.keys())
1831
self.assertEqual(actual_parents, expected.get_parent_map(expected.keys()))
1833
for key, parents in actual_parents.items():
1834
self.assertEqual(None, parents)
1835
for key in actual.keys():
1836
actual_text = actual.get_record_stream(
1837
[key], 'unordered', True).next().get_bytes_as('fulltext')
1838
expected_text = expected.get_record_stream(
1839
[key], 'unordered', True).next().get_bytes_as('fulltext')
1840
self.assertEqual(actual_text, expected_text)
1842
def test_insert_record_stream_fulltexts(self):
1843
"""Any file should accept a stream of fulltexts."""
1844
files = self.get_versionedfiles()
1845
mapper = self.get_mapper()
1846
source_transport = self.get_transport('source')
1847
source_transport.mkdir('.')
1848
# weaves always output fulltexts.
1849
source = make_versioned_files_factory(WeaveFile, mapper)(
1851
self.get_diamond_files(source, trailing_eol=False)
1852
stream = source.get_record_stream(source.keys(), 'topological',
1854
files.insert_record_stream(stream)
1855
self.assertIdenticalVersionedFile(source, files)
1857
def test_insert_record_stream_fulltexts_noeol(self):
1858
"""Any file should accept a stream of fulltexts."""
1859
files = self.get_versionedfiles()
1860
mapper = self.get_mapper()
1861
source_transport = self.get_transport('source')
1862
source_transport.mkdir('.')
1863
# weaves always output fulltexts.
1864
source = make_versioned_files_factory(WeaveFile, mapper)(
1866
self.get_diamond_files(source, trailing_eol=False)
1867
stream = source.get_record_stream(source.keys(), 'topological',
1869
files.insert_record_stream(stream)
1870
self.assertIdenticalVersionedFile(source, files)
1872
def test_insert_record_stream_annotated_knits(self):
1873
"""Any file should accept a stream from plain knits."""
1874
files = self.get_versionedfiles()
1875
mapper = self.get_mapper()
1876
source_transport = self.get_transport('source')
1877
source_transport.mkdir('.')
1878
source = make_file_factory(True, mapper)(source_transport)
1879
self.get_diamond_files(source)
1880
stream = source.get_record_stream(source.keys(), 'topological',
1882
files.insert_record_stream(stream)
1883
self.assertIdenticalVersionedFile(source, files)
1885
def test_insert_record_stream_annotated_knits_noeol(self):
1886
"""Any file should accept a stream from plain knits."""
1887
files = self.get_versionedfiles()
1888
mapper = self.get_mapper()
1889
source_transport = self.get_transport('source')
1890
source_transport.mkdir('.')
1891
source = make_file_factory(True, mapper)(source_transport)
1892
self.get_diamond_files(source, trailing_eol=False)
1893
stream = source.get_record_stream(source.keys(), 'topological',
1895
files.insert_record_stream(stream)
1896
self.assertIdenticalVersionedFile(source, files)
1898
def test_insert_record_stream_plain_knits(self):
1899
"""Any file should accept a stream from plain knits."""
1900
files = self.get_versionedfiles()
1901
mapper = self.get_mapper()
1902
source_transport = self.get_transport('source')
1903
source_transport.mkdir('.')
1904
source = make_file_factory(False, mapper)(source_transport)
1905
self.get_diamond_files(source)
1906
stream = source.get_record_stream(source.keys(), 'topological',
1908
files.insert_record_stream(stream)
1909
self.assertIdenticalVersionedFile(source, files)
1911
def test_insert_record_stream_plain_knits_noeol(self):
1912
"""Any file should accept a stream from plain knits."""
1913
files = self.get_versionedfiles()
1914
mapper = self.get_mapper()
1915
source_transport = self.get_transport('source')
1916
source_transport.mkdir('.')
1917
source = make_file_factory(False, mapper)(source_transport)
1918
self.get_diamond_files(source, trailing_eol=False)
1919
stream = source.get_record_stream(source.keys(), 'topological',
1921
files.insert_record_stream(stream)
1922
self.assertIdenticalVersionedFile(source, files)
1924
def test_insert_record_stream_existing_keys(self):
1925
"""Inserting keys already in a file should not error."""
1926
files = self.get_versionedfiles()
1927
source = self.get_versionedfiles('source')
1928
self.get_diamond_files(source)
1929
# insert some keys into f.
1930
self.get_diamond_files(files, left_only=True)
1931
stream = source.get_record_stream(source.keys(), 'topological',
1933
files.insert_record_stream(stream)
1934
self.assertIdenticalVersionedFile(source, files)
1936
def test_insert_record_stream_missing_keys(self):
1937
"""Inserting a stream with absent keys should raise an error."""
1938
files = self.get_versionedfiles()
1939
source = self.get_versionedfiles('source')
1940
stream = source.get_record_stream([('missing',) * self.key_length],
1941
'topological', False)
1942
self.assertRaises(errors.RevisionNotPresent, files.insert_record_stream,
1945
def test_insert_record_stream_out_of_order(self):
1946
"""An out of order stream can either error or work."""
1947
files = self.get_versionedfiles()
1948
source = self.get_versionedfiles('source')
1949
self.get_diamond_files(source)
1950
if self.key_length == 1:
1951
origin_keys = [('origin',)]
1952
end_keys = [('merged',), ('left',)]
1953
start_keys = [('right',), ('base',)]
1955
origin_keys = [('FileA', 'origin'), ('FileB', 'origin')]
1956
end_keys = [('FileA', 'merged',), ('FileA', 'left',),
1957
('FileB', 'merged',), ('FileB', 'left',)]
1958
start_keys = [('FileA', 'right',), ('FileA', 'base',),
1959
('FileB', 'right',), ('FileB', 'base',)]
1960
origin_entries = source.get_record_stream(origin_keys, 'unordered', False)
1961
end_entries = source.get_record_stream(end_keys, 'topological', False)
1962
start_entries = source.get_record_stream(start_keys, 'topological', False)
1963
entries = chain(origin_entries, end_entries, start_entries)
1965
files.insert_record_stream(entries)
1966
except RevisionNotPresent:
1967
# Must not have corrupted the file.
1970
self.assertIdenticalVersionedFile(source, files)
1972
def test_insert_record_stream_delta_missing_basis_no_corruption(self):
1973
"""Insertion where a needed basis is not included aborts safely."""
1974
# We use a knit always here to be sure we are getting a binary delta.
1975
mapper = self.get_mapper()
1976
source_transport = self.get_transport('source')
1977
source_transport.mkdir('.')
1978
source = make_file_factory(False, mapper)(source_transport)
1979
self.get_diamond_files(source)
1980
entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
1981
files = self.get_versionedfiles()
1982
self.assertRaises(RevisionNotPresent, files.insert_record_stream,
1985
self.assertEqual({}, files.get_parent_map([]))
1987
def test_iter_lines_added_or_present_in_keys(self):
1988
# test that we get at least an equalset of the lines added by
1989
# versions in the store.
1990
# the ordering here is to make a tree so that dumb searches have
1991
# more changes to muck up.
1993
class InstrumentedProgress(progress.DummyProgress):
1997
progress.DummyProgress.__init__(self)
2000
def update(self, msg=None, current=None, total=None):
2001
self.updates.append((msg, current, total))
2003
files = self.get_versionedfiles()
2004
# add a base to get included
2005
files.add_lines(self.get_simple_key('base'), (), ['base\n'])
2006
# add a ancestor to be included on one side
2007
files.add_lines(self.get_simple_key('lancestor'), (), ['lancestor\n'])
2008
# add a ancestor to be included on the other side
2009
files.add_lines(self.get_simple_key('rancestor'),
2010
self.get_parents([self.get_simple_key('base')]), ['rancestor\n'])
2011
# add a child of rancestor with no eofile-nl
2012
files.add_lines(self.get_simple_key('child'),
2013
self.get_parents([self.get_simple_key('rancestor')]),
2014
['base\n', 'child\n'])
2015
# add a child of lancestor and base to join the two roots
2016
files.add_lines(self.get_simple_key('otherchild'),
2017
self.get_parents([self.get_simple_key('lancestor'),
2018
self.get_simple_key('base')]),
2019
['base\n', 'lancestor\n', 'otherchild\n'])
2020
def iter_with_keys(keys, expected):
2021
# now we need to see what lines are returned, and how often.
2023
progress = InstrumentedProgress()
2024
# iterate over the lines
2025
for line in files.iter_lines_added_or_present_in_keys(keys,
2027
lines.setdefault(line, 0)
2029
if []!= progress.updates:
2030
self.assertEqual(expected, progress.updates)
2032
lines = iter_with_keys(
2033
[self.get_simple_key('child'), self.get_simple_key('otherchild')],
2034
[('Walking content.', 0, 2),
2035
('Walking content.', 1, 2),
2036
('Walking content.', 2, 2)])
2037
# we must see child and otherchild
2038
self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2040
lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2041
# we dont care if we got more than that.
2044
lines = iter_with_keys(files.keys(),
2045
[('Walking content.', 0, 5),
2046
('Walking content.', 1, 5),
2047
('Walking content.', 2, 5),
2048
('Walking content.', 3, 5),
2049
('Walking content.', 4, 5),
2050
('Walking content.', 5, 5)])
2051
# all lines must be seen at least once
2052
self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2054
lines[('lancestor\n', self.get_simple_key('lancestor'))] > 0)
2056
lines[('rancestor\n', self.get_simple_key('rancestor'))] > 0)
2057
self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2059
lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2061
def test_make_mpdiffs(self):
2062
from bzrlib import multiparent
2063
files = self.get_versionedfiles('source')
2064
# add texts that should trip the knit maximum delta chain threshold
2065
# as well as doing parallel chains of data in knits.
2066
# this is done by two chains of 25 insertions
2067
files.add_lines(self.get_simple_key('base'), [], ['line\n'])
2068
files.add_lines(self.get_simple_key('noeol'),
2069
self.get_parents([self.get_simple_key('base')]), ['line'])
2070
# detailed eol tests:
2071
# shared last line with parent no-eol
2072
files.add_lines(self.get_simple_key('noeolsecond'),
2073
self.get_parents([self.get_simple_key('noeol')]),
2075
# differing last line with parent, both no-eol
2076
files.add_lines(self.get_simple_key('noeolnotshared'),
2077
self.get_parents([self.get_simple_key('noeolsecond')]),
2078
['line\n', 'phone'])
2079
# add eol following a noneol parent, change content
2080
files.add_lines(self.get_simple_key('eol'),
2081
self.get_parents([self.get_simple_key('noeol')]), ['phone\n'])
2082
# add eol following a noneol parent, no change content
2083
files.add_lines(self.get_simple_key('eolline'),
2084
self.get_parents([self.get_simple_key('noeol')]), ['line\n'])
2085
# noeol with no parents:
2086
files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2087
# noeol preceeding its leftmost parent in the output:
2088
# this is done by making it a merge of two parents with no common
2089
# anestry: noeolbase and noeol with the
2090
# later-inserted parent the leftmost.
2091
files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2092
self.get_parents([self.get_simple_key('noeolbase'),
2093
self.get_simple_key('noeol')]),
2095
# two identical eol texts
2096
files.add_lines(self.get_simple_key('noeoldup'),
2097
self.get_parents([self.get_simple_key('noeol')]), ['line'])
2098
next_parent = self.get_simple_key('base')
2099
text_name = 'chain1-'
2101
sha1s = {0 :'da6d3141cb4a5e6f464bf6e0518042ddc7bfd079',
2102
1 :'45e21ea146a81ea44a821737acdb4f9791c8abe7',
2103
2 :'e1f11570edf3e2a070052366c582837a4fe4e9fa',
2104
3 :'26b4b8626da827088c514b8f9bbe4ebf181edda1',
2105
4 :'e28a5510be25ba84d31121cff00956f9970ae6f6',
2106
5 :'d63ec0ce22e11dcf65a931b69255d3ac747a318d',
2107
6 :'2c2888d288cb5e1d98009d822fedfe6019c6a4ea',
2108
7 :'95c14da9cafbf828e3e74a6f016d87926ba234ab',
2109
8 :'779e9a0b28f9f832528d4b21e17e168c67697272',
2110
9 :'1f8ff4e5c6ff78ac106fcfe6b1e8cb8740ff9a8f',
2111
10:'131a2ae712cf51ed62f143e3fbac3d4206c25a05',
2112
11:'c5a9d6f520d2515e1ec401a8f8a67e6c3c89f199',
2113
12:'31a2286267f24d8bedaa43355f8ad7129509ea85',
2114
13:'dc2a7fe80e8ec5cae920973973a8ee28b2da5e0a',
2115
14:'2c4b1736566b8ca6051e668de68650686a3922f2',
2116
15:'5912e4ecd9b0c07be4d013e7e2bdcf9323276cde',
2117
16:'b0d2e18d3559a00580f6b49804c23fea500feab3',
2118
17:'8e1d43ad72f7562d7cb8f57ee584e20eb1a69fc7',
2119
18:'5cf64a3459ae28efa60239e44b20312d25b253f3',
2120
19:'1ebed371807ba5935958ad0884595126e8c4e823',
2121
20:'2aa62a8b06fb3b3b892a3292a068ade69d5ee0d3',
2122
21:'01edc447978004f6e4e962b417a4ae1955b6fe5d',
2123
22:'d8d8dc49c4bf0bab401e0298bb5ad827768618bb',
2124
23:'c21f62b1c482862983a8ffb2b0c64b3451876e3f',
2125
24:'c0593fe795e00dff6b3c0fe857a074364d5f04fc',
2126
25:'dd1a1cf2ba9cc225c3aff729953e6364bf1d1855',
2128
for depth in range(26):
2129
new_version = self.get_simple_key(text_name + '%s' % depth)
2130
text = text + ['line\n']
2131
files.add_lines(new_version, self.get_parents([next_parent]), text)
2132
next_parent = new_version
2133
next_parent = self.get_simple_key('base')
2134
text_name = 'chain2-'
2136
for depth in range(26):
2137
new_version = self.get_simple_key(text_name + '%s' % depth)
2138
text = text + ['line\n']
2139
files.add_lines(new_version, self.get_parents([next_parent]), text)
2140
next_parent = new_version
2141
target = self.get_versionedfiles('target')
2142
for key in multiparent.topo_iter_keys(files, files.keys()):
2143
mpdiff = files.make_mpdiffs([key])[0]
2144
parents = files.get_parent_map([key])[key] or []
2146
[(key, parents, files.get_sha1s([key])[key], mpdiff)])
2147
self.assertEqualDiff(
2148
files.get_record_stream([key], 'unordered',
2149
True).next().get_bytes_as('fulltext'),
2150
target.get_record_stream([key], 'unordered',
2151
True).next().get_bytes_as('fulltext')
2154
def test_keys(self):
2155
# While use is discouraged, versions() is still needed by aspects of
2157
files = self.get_versionedfiles()
2158
self.assertEqual(set(), set(files.keys()))
2159
if self.key_length == 1:
2162
key = ('foo', 'bar',)
2163
files.add_lines(key, (), [])
2164
self.assertEqual(set([key]), set(files.keys()))
2167
class VirtualVersionedFilesTests(TestCase):
2168
"""Basic tests for the VirtualVersionedFiles implementations."""
2170
def _get_parent_map(self, keys):
2173
if k in self._parent_map:
2174
ret[k] = self._parent_map[k]
2178
TestCase.setUp(self)
2180
self._parent_map = {}
2181
self.texts = VirtualVersionedFiles(self._get_parent_map,
2184
def test_add_lines(self):
2185
self.assertRaises(NotImplementedError,
2186
self.texts.add_lines, "foo", [], [])
2188
def test_add_mpdiffs(self):
2189
self.assertRaises(NotImplementedError,
2190
self.texts.add_mpdiffs, [])
2192
def test_check(self):
2193
self.assertTrue(self.texts.check())
2195
def test_insert_record_stream(self):
2196
self.assertRaises(NotImplementedError, self.texts.insert_record_stream,
2199
def test_get_sha1s_nonexistent(self):
2200
self.assertEquals({}, self.texts.get_sha1s([("NONEXISTENT",)]))
2202
def test_get_sha1s(self):
2203
self._lines["key"] = ["dataline1", "dataline2"]
2204
self.assertEquals({("key",): osutils.sha_strings(self._lines["key"])},
2205
self.texts.get_sha1s([("key",)]))
2207
def test_get_parent_map(self):
2208
self._parent_map = {"G": ("A", "B")}
2209
self.assertEquals({("G",): (("A",),("B",))},
2210
self.texts.get_parent_map([("G",), ("L",)]))
2212
def test_get_record_stream(self):
2213
self._lines["A"] = ["FOO", "BAR"]
2214
it = self.texts.get_record_stream([("A",)], "unordered", True)
2216
self.assertEquals("chunked", record.storage_kind)
2217
self.assertEquals("FOOBAR", record.get_bytes_as("fulltext"))
2218
self.assertEquals(["FOO", "BAR"], record.get_bytes_as("chunked"))
2220
def test_get_record_stream_absent(self):
2221
it = self.texts.get_record_stream([("A",)], "unordered", True)
2223
self.assertEquals("absent", record.storage_kind)
2225
def test_iter_lines_added_or_present_in_keys(self):
2226
self._lines["A"] = ["FOO", "BAR"]
2227
self._lines["B"] = ["HEY"]
2228
self._lines["C"] = ["Alberta"]
2229
it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2230
self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2234
class TestOrderingVersionedFilesDecorator(TestCaseWithMemoryTransport):
2236
def get_ordering_vf(self, key_priority):
2237
builder = self.make_branch_builder('test')
2238
builder.start_series()
2239
builder.build_snapshot('A', None, [
2240
('add', ('', 'TREE_ROOT', 'directory', None))])
2241
builder.build_snapshot('B', ['A'], [])
2242
builder.build_snapshot('C', ['B'], [])
2243
builder.build_snapshot('D', ['C'], [])
2244
builder.finish_series()
2245
b = builder.get_branch()
2247
self.addCleanup(b.unlock)
2248
vf = b.repository.inventories
2249
return versionedfile.OrderingVersionedFilesDecorator(vf, key_priority)
2251
def test_get_empty(self):
2252
vf = self.get_ordering_vf({})
2253
self.assertEqual([], vf.calls)
2255
def test_get_record_stream_topological(self):
2256
vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
2257
request_keys = [('B',), ('C',), ('D',), ('A',)]
2258
keys = [r.key for r in vf.get_record_stream(request_keys,
2259
'topological', False)]
2260
# We should have gotten the keys in topological order
2261
self.assertEqual([('A',), ('B',), ('C',), ('D',)], keys)
2262
# And recorded that the request was made
2263
self.assertEqual([('get_record_stream', request_keys, 'topological',
2266
def test_get_record_stream_ordered(self):
2267
vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
2268
request_keys = [('B',), ('C',), ('D',), ('A',)]
2269
keys = [r.key for r in vf.get_record_stream(request_keys,
2270
'unordered', False)]
2271
# They should be returned based on their priority
2272
self.assertEqual([('D',), ('B',), ('A',), ('C',)], keys)
2273
# And the request recorded
2274
self.assertEqual([('get_record_stream', request_keys, 'unordered',
2277
def test_get_record_stream_implicit_order(self):
2278
vf = self.get_ordering_vf({('B',): 2, ('D',): 1})
2279
request_keys = [('B',), ('C',), ('D',), ('A',)]
2280
keys = [r.key for r in vf.get_record_stream(request_keys,
2281
'unordered', False)]
2282
# A and C are not in the map, so they get sorted to the front. A comes
2283
# before C alphabetically, so it comes back first
2284
self.assertEqual([('A',), ('C',), ('D',), ('B',)], keys)
2285
# And the request recorded
2286
self.assertEqual([('get_record_stream', request_keys, 'unordered',