24
24
rather than in tests/branch_implementations/*.py.
27
from bzrlib.repository import (_legacy_formats,
29
RepositoryTestProviderAdapter,
30
from bzrlib.revision import NULL_REVISION
31
from bzrlib.repofmt import (
34
from bzrlib.remote import RemoteBzrDirFormat, RemoteRepositoryFormat
35
from bzrlib.smart.server import (
36
ReadonlySmartTCPServer_for_testing,
37
ReadonlySmartTCPServer_for_testing_v2_only,
38
SmartTCPServer_for_testing,
39
SmartTCPServer_for_testing_v2_only,
32
41
from bzrlib.tests import (
46
multiply_tests_from_modules,
42
test_repository_implementations = [
43
'bzrlib.tests.repository_implementations.test_break_lock',
44
'bzrlib.tests.repository_implementations.test_commit_builder',
45
'bzrlib.tests.repository_implementations.test_fileid_involved',
46
'bzrlib.tests.repository_implementations.test_reconcile',
47
'bzrlib.tests.repository_implementations.test_repository',
48
'bzrlib.tests.repository_implementations.test_revision',
50
adapter = RepositoryTestProviderAdapter(
49
from bzrlib.tests.bzrdir_implementations.test_bzrdir import TestCaseWithBzrDir
50
from bzrlib.transport.memory import MemoryServer
53
def formats_to_scenarios(formats, transport_server, transport_readonly_server,
54
vfs_transport_factory=None):
55
"""Transform the input formats to a list of scenarios.
57
:param formats: A list of (scenario_name_suffix, repo_format)
58
where the scenario_name_suffix is to be appended to the format
59
name, and the repo_format is a RepositoryFormat subclass
61
:returns: Scenarios of [(scenario_name, {parameter_name: value})]
64
for scenario_name_suffix, repository_format in formats:
65
scenario_name = repository_format.__class__.__name__
66
scenario_name += scenario_name_suffix
67
scenario = (scenario_name,
68
{"transport_server":transport_server,
69
"transport_readonly_server":transport_readonly_server,
70
"bzrdir_format":repository_format._matchingbzrdir,
71
"repository_format":repository_format,
73
# Only override the test's vfs_transport_factory if one was
74
# specified, otherwise just leave the default in place.
75
if vfs_transport_factory:
76
scenario[1]['vfs_transport_factory'] = vfs_transport_factory
77
result.append(scenario)
81
def all_repository_format_scenarios():
82
"""Return a list of test scenarios for parameterising repository tests.
84
registry = repository.format_registry
85
all_formats = [registry.get(k) for k in registry.keys()]
86
all_formats.extend(weaverepo._legacy_formats)
87
# format_scenarios is all the implementations of Repository; i.e. all disk
88
# formats plus RemoteRepository.
89
format_scenarios = formats_to_scenarios(
90
[('', format) for format in all_formats],
52
92
# None here will cause a readonly decorator to be created
53
93
# by the TestCaseWithTransport.get_readonly_transport method.
55
[(format, format._matchingbzrdir) for format in
56
RepositoryFormat._formats.values() + _legacy_formats])
58
adapt_modules(test_repository_implementations, adapter, loader, result)
95
format_scenarios.extend(formats_to_scenarios(
96
[('-default', RemoteRepositoryFormat())],
97
SmartTCPServer_for_testing,
98
ReadonlySmartTCPServer_for_testing,
100
format_scenarios.extend(formats_to_scenarios(
101
[('-v2', RemoteRepositoryFormat())],
102
SmartTCPServer_for_testing_v2_only,
103
ReadonlySmartTCPServer_for_testing_v2_only,
105
return format_scenarios
108
class TestCaseWithRepository(TestCaseWithBzrDir):
110
def make_repository(self, relpath, format=None):
112
# Create a repository of the type we are trying to test.
113
made_control = self.make_bzrdir(relpath)
114
repo = self.repository_format.initialize(made_control)
115
if getattr(self, "repository_to_test_repository", None):
116
repo = self.repository_to_test_repository(repo)
119
return super(TestCaseWithRepository, self).make_repository(
120
relpath, format=format)
123
class BrokenRepoScenario(object):
124
"""Base class for defining scenarios for testing check and reconcile.
126
A subclass needs to define the following methods:
127
:populate_repository: a method to use to populate a repository with
128
sample revisions, inventories and file versions.
129
:all_versions_after_reconcile: all the versions in repository after
130
reconcile. run_test verifies that the text of each of these
131
versions of the file is unchanged by the reconcile.
132
:populated_parents: a list of (parents list, revision). Each version
133
of the file is verified to have the given parents before running
134
the reconcile. i.e. this is used to assert that the repo from the
135
factory is what we expect.
136
:corrected_parents: a list of (parents list, revision). Each version
137
of the file is verified to have the given parents after the
138
reconcile. i.e. this is used to assert that reconcile made the
139
changes we expect it to make.
141
A subclass may define the following optional method as well:
142
:corrected_fulltexts: a list of file versions that should be stored as
143
fulltexts (not deltas) after reconcile. run_test will verify that
147
def __init__(self, test_case):
148
self.test_case = test_case
150
def make_one_file_inventory(self, repo, revision, parents,
151
inv_revision=None, root_revision=None,
152
file_contents=None, make_file_version=True):
153
return self.test_case.make_one_file_inventory(
154
repo, revision, parents, inv_revision=inv_revision,
155
root_revision=root_revision, file_contents=file_contents,
156
make_file_version=make_file_version)
158
def add_revision(self, repo, revision_id, inv, parent_ids):
159
return self.test_case.add_revision(repo, revision_id, inv, parent_ids)
161
def corrected_fulltexts(self):
164
def repository_text_key_index(self):
166
if self.versioned_root:
167
result.update(self.versioned_repository_text_keys())
168
result.update(self.repository_text_keys())
172
class UndamagedRepositoryScenario(BrokenRepoScenario):
173
"""A scenario where the repository has no damage.
175
It has a single revision, 'rev1a', with a single file.
178
def all_versions_after_reconcile(self):
181
def populated_parents(self):
182
return (((), 'rev1a'), )
184
def corrected_parents(self):
185
# Same as the populated parents, because there was nothing wrong.
186
return self.populated_parents()
188
def check_regexes(self, repo):
189
return ["0 unreferenced text versions"]
191
def populate_repository(self, repo):
192
# make rev1a: A well-formed revision, containing 'a-file'
193
inv = self.make_one_file_inventory(
194
repo, 'rev1a', [], root_revision='rev1a')
195
self.add_revision(repo, 'rev1a', inv, [])
196
self.versioned_root = repo.supports_rich_root()
198
def repository_text_key_references(self):
200
if self.versioned_root:
201
result.update({('TREE_ROOT', 'rev1a'): True})
202
result.update({('a-file-id', 'rev1a'): True})
205
def repository_text_keys(self):
206
return {('a-file-id', 'rev1a'):[NULL_REVISION]}
208
def versioned_repository_text_keys(self):
209
return {('TREE_ROOT', 'rev1a'):[NULL_REVISION]}
212
class FileParentIsNotInRevisionAncestryScenario(BrokenRepoScenario):
213
"""A scenario where a revision 'rev2' has 'a-file' with a
214
parent 'rev1b' that is not in the revision ancestry.
216
Reconcile should remove 'rev1b' from the parents list of 'a-file' in
217
'rev2', preserving 'rev1a' as a parent.
220
def all_versions_after_reconcile(self):
221
return ('rev1a', 'rev2')
223
def populated_parents(self):
226
((), 'rev1b'), # Will be gc'd
227
(('rev1a', 'rev1b'), 'rev2')) # Will have parents trimmed
229
def corrected_parents(self):
233
(('rev1a',), 'rev2'))
235
def check_regexes(self, repo):
236
return [r"\* a-file-id version rev2 has parents \('rev1a', 'rev1b'\) "
237
r"but should have \('rev1a',\)",
238
"1 unreferenced text versions",
241
def populate_repository(self, repo):
242
# make rev1a: A well-formed revision, containing 'a-file'
243
inv = self.make_one_file_inventory(
244
repo, 'rev1a', [], root_revision='rev1a')
245
self.add_revision(repo, 'rev1a', inv, [])
247
# make rev1b, which has no Revision, but has an Inventory, and
249
inv = self.make_one_file_inventory(
250
repo, 'rev1b', [], root_revision='rev1b')
251
repo.add_inventory('rev1b', inv, [])
253
# make rev2, with a-file.
254
# a-file has 'rev1b' as an ancestor, even though this is not
255
# mentioned by 'rev1a', making it an unreferenced ancestor
256
inv = self.make_one_file_inventory(
257
repo, 'rev2', ['rev1a', 'rev1b'])
258
self.add_revision(repo, 'rev2', inv, ['rev1a'])
259
self.versioned_root = repo.supports_rich_root()
261
def repository_text_key_references(self):
263
if self.versioned_root:
264
result.update({('TREE_ROOT', 'rev1a'): True,
265
('TREE_ROOT', 'rev2'): True})
266
result.update({('a-file-id', 'rev1a'): True,
267
('a-file-id', 'rev2'): True})
270
def repository_text_keys(self):
271
return {('a-file-id', 'rev1a'):[NULL_REVISION],
272
('a-file-id', 'rev2'):[('a-file-id', 'rev1a')]}
274
def versioned_repository_text_keys(self):
275
return {('TREE_ROOT', 'rev1a'):[NULL_REVISION],
276
('TREE_ROOT', 'rev2'):[('TREE_ROOT', 'rev1a')]}
279
class FileParentHasInaccessibleInventoryScenario(BrokenRepoScenario):
280
"""A scenario where a revision 'rev3' containing 'a-file' modified in
281
'rev3', and with a parent which is in the revision ancestory, but whose
282
inventory cannot be accessed at all.
284
Reconcile should remove the file version parent whose inventory is
285
inaccessbile (i.e. remove 'rev1c' from the parents of a-file's rev3).
288
def all_versions_after_reconcile(self):
289
return ('rev2', 'rev3')
291
def populated_parents(self):
294
(('rev1c',), 'rev3'))
296
def corrected_parents(self):
301
def check_regexes(self, repo):
302
return [r"\* a-file-id version rev3 has parents "
303
r"\('rev1c',\) but should have \(\)",
306
def populate_repository(self, repo):
307
# make rev2, with a-file
309
inv = self.make_one_file_inventory(repo, 'rev2', [])
310
self.add_revision(repo, 'rev2', inv, [])
312
# make ghost revision rev1c, with a version of a-file present so
313
# that we generate a knit delta against this version. In real life
314
# the ghost might never have been present or rev3 might have been
315
# generated against a revision that was present at the time. So
316
# currently we have the full history of a-file present even though
317
# the inventory and revision objects are not.
318
self.make_one_file_inventory(repo, 'rev1c', [])
320
# make rev3 with a-file
321
# a-file refers to 'rev1c', which is a ghost in this repository, so
322
# a-file cannot have rev1c as its ancestor.
323
inv = self.make_one_file_inventory(repo, 'rev3', ['rev1c'])
324
self.add_revision(repo, 'rev3', inv, ['rev1c', 'rev1a'])
325
self.versioned_root = repo.supports_rich_root()
327
def repository_text_key_references(self):
329
if self.versioned_root:
330
result.update({('TREE_ROOT', 'rev2'): True,
331
('TREE_ROOT', 'rev3'): True})
332
result.update({('a-file-id', 'rev2'): True,
333
('a-file-id', 'rev3'): True})
336
def repository_text_keys(self):
337
return {('a-file-id', 'rev2'):[NULL_REVISION],
338
('a-file-id', 'rev3'):[NULL_REVISION]}
340
def versioned_repository_text_keys(self):
341
return {('TREE_ROOT', 'rev2'):[NULL_REVISION],
342
('TREE_ROOT', 'rev3'):[NULL_REVISION]}
345
class FileParentsNotReferencedByAnyInventoryScenario(BrokenRepoScenario):
346
"""A scenario where a repository with file 'a-file' which has extra
347
per-file versions that are not referenced by any inventory (even though
348
they have the same ID as actual revisions). The inventory of 'rev2'
349
references 'rev1a' of 'a-file', but there is a 'rev2' of 'some-file' stored
350
and erroneously referenced by later per-file versions (revisions 'rev4' and
353
Reconcile should remove the file parents that are not referenced by any
357
def all_versions_after_reconcile(self):
358
return ('rev1a', 'rev2c', 'rev4', 'rev5')
360
def populated_parents(self):
362
(('rev1a',), 'rev2'),
363
(('rev1a',), 'rev2b'),
366
(('rev2', 'rev2c'), 'rev5')]
368
def corrected_parents(self):
370
# rev2 and rev2b have been removed.
373
# rev3's accessible parent inventories all have rev1a as the last
375
(('rev1a',), 'rev3'),
376
# rev1a features in both rev4's parents but should only appear once
378
(('rev1a',), 'rev4'),
379
# rev2c is the head of rev1a and rev2c, the inventory provided
380
# per-file last-modified revisions.
381
(('rev2c',), 'rev5'))
383
def check_regexes(self, repo):
384
if repo.supports_rich_root():
385
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
386
# the expected count of errors.
392
r"unreferenced version: {rev2} in a-file-id",
393
r"unreferenced version: {rev2b} in a-file-id",
395
r"a-file-id version rev3 has parents \('rev2',\) "
396
r"but should have \('rev1a',\)",
397
r"a-file-id version rev5 has parents \('rev2', 'rev2c'\) "
398
r"but should have \('rev2c',\)",
399
r"a-file-id version rev4 has parents \('rev2',\) "
400
r"but should have \('rev1a',\)",
401
"%d inconsistent parents" % count,
404
def populate_repository(self, repo):
405
# make rev1a: A well-formed revision, containing 'a-file'
406
inv = self.make_one_file_inventory(
407
repo, 'rev1a', [], root_revision='rev1a')
408
self.add_revision(repo, 'rev1a', inv, [])
410
# make rev2, with a-file.
411
# a-file is unmodified from rev1a, and an unreferenced rev2 file
412
# version is present in the repository.
413
self.make_one_file_inventory(
414
repo, 'rev2', ['rev1a'], inv_revision='rev1a')
415
self.add_revision(repo, 'rev2', inv, ['rev1a'])
417
# make rev3 with a-file
418
# a-file has 'rev2' as its ancestor, but the revision in 'rev2' was
419
# rev1a so this is inconsistent with rev2's inventory - it should
420
# be rev1a, and at the revision level 1c is not present - it is a
421
# ghost, so only the details from rev1a are available for
422
# determining whether a delta is acceptable, or a full is needed,
423
# and what the correct parents are.
424
inv = self.make_one_file_inventory(repo, 'rev3', ['rev2'])
425
self.add_revision(repo, 'rev3', inv, ['rev1c', 'rev1a'])
427
# In rev2b, the true last-modifying-revision of a-file is rev1a,
428
# inherited from rev2, but there is a version rev2b of the file, which
429
# reconcile could remove, leaving no rev2b. Most importantly,
430
# revisions descending from rev2b should not have per-file parents of
432
# ??? This is to test deduplication in fixing rev4
433
inv = self.make_one_file_inventory(
434
repo, 'rev2b', ['rev1a'], inv_revision='rev1a')
435
self.add_revision(repo, 'rev2b', inv, ['rev1a'])
437
# rev4 is for testing that when the last modified of a file in
438
# multiple parent revisions is the same, that it only appears once
439
# in the generated per file parents list: rev2 and rev2b both
440
# descend from 1a and do not change the file a-file, so there should
441
# be no version of a-file 'rev2' or 'rev2b', but rev4 does change
442
# a-file, and is a merge of rev2 and rev2b, so it should end up with
443
# a parent of just rev1a - the starting file parents list is simply
445
inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
446
self.add_revision(repo, 'rev4', inv, ['rev2', 'rev2b'])
448
# rev2c changes a-file from rev1a, so the version it of a-file it
449
# introduces is a head revision when rev5 is checked.
450
inv = self.make_one_file_inventory(repo, 'rev2c', ['rev1a'])
451
self.add_revision(repo, 'rev2c', inv, ['rev1a'])
453
# rev5 descends from rev2 and rev2c; as rev2 does not alter a-file,
454
# but rev2c does, this should use rev2c as the parent for the per
455
# file history, even though more than one per-file parent is
456
# available, because we use the heads of the revision parents for
457
# the inventory modification revisions of the file to determine the
458
# parents for the per file graph.
459
inv = self.make_one_file_inventory(repo, 'rev5', ['rev2', 'rev2c'])
460
self.add_revision(repo, 'rev5', inv, ['rev2', 'rev2c'])
461
self.versioned_root = repo.supports_rich_root()
463
def repository_text_key_references(self):
465
if self.versioned_root:
466
result.update({('TREE_ROOT', 'rev1a'): True,
467
('TREE_ROOT', 'rev2'): True,
468
('TREE_ROOT', 'rev2b'): True,
469
('TREE_ROOT', 'rev2c'): True,
470
('TREE_ROOT', 'rev3'): True,
471
('TREE_ROOT', 'rev4'): True,
472
('TREE_ROOT', 'rev5'): True})
473
result.update({('a-file-id', 'rev1a'): True,
474
('a-file-id', 'rev2c'): True,
475
('a-file-id', 'rev3'): True,
476
('a-file-id', 'rev4'): True,
477
('a-file-id', 'rev5'): True})
480
def repository_text_keys(self):
481
return {('a-file-id', 'rev1a'): [NULL_REVISION],
482
('a-file-id', 'rev2c'): [('a-file-id', 'rev1a')],
483
('a-file-id', 'rev3'): [('a-file-id', 'rev1a')],
484
('a-file-id', 'rev4'): [('a-file-id', 'rev1a')],
485
('a-file-id', 'rev5'): [('a-file-id', 'rev2c')]}
487
def versioned_repository_text_keys(self):
488
return {('TREE_ROOT', 'rev1a'): [NULL_REVISION],
489
('TREE_ROOT', 'rev2'): [('TREE_ROOT', 'rev1a')],
490
('TREE_ROOT', 'rev2b'): [('TREE_ROOT', 'rev1a')],
491
('TREE_ROOT', 'rev2c'): [('TREE_ROOT', 'rev1a')],
492
('TREE_ROOT', 'rev3'): [('TREE_ROOT', 'rev1a')],
493
('TREE_ROOT', 'rev4'):
494
[('TREE_ROOT', 'rev2'), ('TREE_ROOT', 'rev2b')],
495
('TREE_ROOT', 'rev5'):
496
[('TREE_ROOT', 'rev2'), ('TREE_ROOT', 'rev2c')]}
499
class UnreferencedFileParentsFromNoOpMergeScenario(BrokenRepoScenario):
501
rev1a and rev1b with identical contents
502
rev2 revision has parents of [rev1a, rev1b]
503
There is a a-file:rev2 file version, not referenced by the inventory.
506
def all_versions_after_reconcile(self):
507
return ('rev1a', 'rev1b', 'rev2', 'rev4')
509
def populated_parents(self):
513
(('rev1a', 'rev1b'), 'rev2'),
518
def corrected_parents(self):
527
def corrected_fulltexts(self):
530
def check_regexes(self, repo):
533
def populate_repository(self, repo):
534
# make rev1a: A well-formed revision, containing 'a-file'
535
inv1a = self.make_one_file_inventory(
536
repo, 'rev1a', [], root_revision='rev1a')
537
self.add_revision(repo, 'rev1a', inv1a, [])
539
# make rev1b: A well-formed revision, containing 'a-file'
540
# rev1b of a-file has the exact same contents as rev1a.
541
file_contents = repo.revision_tree('rev1a').get_file_text('a-file-id')
542
inv = self.make_one_file_inventory(
543
repo, 'rev1b', [], root_revision='rev1b',
544
file_contents=file_contents)
545
self.add_revision(repo, 'rev1b', inv, [])
547
# make rev2, a merge of rev1a and rev1b, with a-file.
548
# a-file is unmodified from rev1a and rev1b, but a new version is
549
# wrongly present anyway.
550
inv = self.make_one_file_inventory(
551
repo, 'rev2', ['rev1a', 'rev1b'], inv_revision='rev1a',
552
file_contents=file_contents)
553
self.add_revision(repo, 'rev2', inv, ['rev1a', 'rev1b'])
555
# rev3: a-file unchanged from rev2, but wrongly referencing rev2 of the
556
# file in its inventory.
557
inv = self.make_one_file_inventory(
558
repo, 'rev3', ['rev2'], inv_revision='rev2',
559
file_contents=file_contents, make_file_version=False)
560
self.add_revision(repo, 'rev3', inv, ['rev2'])
562
# rev4: a modification of a-file on top of rev3.
563
inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
564
self.add_revision(repo, 'rev4', inv, ['rev3'])
565
self.versioned_root = repo.supports_rich_root()
567
def repository_text_key_references(self):
569
if self.versioned_root:
570
result.update({('TREE_ROOT', 'rev1a'): True,
571
('TREE_ROOT', 'rev1b'): True,
572
('TREE_ROOT', 'rev2'): True,
573
('TREE_ROOT', 'rev3'): True,
574
('TREE_ROOT', 'rev4'): True})
575
result.update({('a-file-id', 'rev1a'): True,
576
('a-file-id', 'rev1b'): True,
577
('a-file-id', 'rev2'): False,
578
('a-file-id', 'rev4'): True})
581
def repository_text_keys(self):
582
return {('a-file-id', 'rev1a'): [NULL_REVISION],
583
('a-file-id', 'rev1b'): [NULL_REVISION],
584
('a-file-id', 'rev2'): [NULL_REVISION],
585
('a-file-id', 'rev4'): [('a-file-id', 'rev2')]}
587
def versioned_repository_text_keys(self):
588
return {('TREE_ROOT', 'rev1a'): [NULL_REVISION],
589
('TREE_ROOT', 'rev1b'): [NULL_REVISION],
590
('TREE_ROOT', 'rev2'):
591
[('TREE_ROOT', 'rev1a'), ('TREE_ROOT', 'rev1b')],
592
('TREE_ROOT', 'rev3'): [('TREE_ROOT', 'rev2')],
593
('TREE_ROOT', 'rev4'): [('TREE_ROOT', 'rev3')]}
596
class TooManyParentsScenario(BrokenRepoScenario):
597
"""A scenario where 'broken-revision' of 'a-file' claims to have parents
598
['good-parent', 'bad-parent']. However 'bad-parent' is in the ancestry of
599
'good-parent', so the correct parent list for that file version are is just
603
def all_versions_after_reconcile(self):
604
return ('bad-parent', 'good-parent', 'broken-revision')
606
def populated_parents(self):
609
(('bad-parent',), 'good-parent'),
610
(('good-parent', 'bad-parent'), 'broken-revision'))
612
def corrected_parents(self):
615
(('bad-parent',), 'good-parent'),
616
(('good-parent',), 'broken-revision'))
618
def check_regexes(self, repo):
619
if repo.supports_rich_root():
620
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
621
# the expected count of errors.
626
' %d inconsistent parents' % count,
627
(r" \* a-file-id version broken-revision has parents "
628
r"\('good-parent', 'bad-parent'\) but "
629
r"should have \('good-parent',\)"))
631
def populate_repository(self, repo):
632
inv = self.make_one_file_inventory(
633
repo, 'bad-parent', (), root_revision='bad-parent')
634
self.add_revision(repo, 'bad-parent', inv, ())
636
inv = self.make_one_file_inventory(
637
repo, 'good-parent', ('bad-parent',))
638
self.add_revision(repo, 'good-parent', inv, ('bad-parent',))
640
inv = self.make_one_file_inventory(
641
repo, 'broken-revision', ('good-parent', 'bad-parent'))
642
self.add_revision(repo, 'broken-revision', inv, ('good-parent',))
643
self.versioned_root = repo.supports_rich_root()
645
def repository_text_key_references(self):
647
if self.versioned_root:
648
result.update({('TREE_ROOT', 'bad-parent'): True,
649
('TREE_ROOT', 'broken-revision'): True,
650
('TREE_ROOT', 'good-parent'): True})
651
result.update({('a-file-id', 'bad-parent'): True,
652
('a-file-id', 'broken-revision'): True,
653
('a-file-id', 'good-parent'): True})
656
def repository_text_keys(self):
657
return {('a-file-id', 'bad-parent'): [NULL_REVISION],
658
('a-file-id', 'broken-revision'):
659
[('a-file-id', 'good-parent')],
660
('a-file-id', 'good-parent'): [('a-file-id', 'bad-parent')]}
662
def versioned_repository_text_keys(self):
663
return {('TREE_ROOT', 'bad-parent'): [NULL_REVISION],
664
('TREE_ROOT', 'broken-revision'):
665
[('TREE_ROOT', 'good-parent')],
666
('TREE_ROOT', 'good-parent'): [('TREE_ROOT', 'bad-parent')]}
669
class ClaimedFileParentDidNotModifyFileScenario(BrokenRepoScenario):
670
"""A scenario where the file parent is the same as the revision parent, but
671
should not be because that revision did not modify the file.
673
Specifically, the parent revision of 'current' is
674
'modified-something-else', which does not modify 'a-file', but the
675
'current' version of 'a-file' erroneously claims that
676
'modified-something-else' is the parent file version.
679
def all_versions_after_reconcile(self):
680
return ('basis', 'current')
682
def populated_parents(self):
685
(('basis',), 'modified-something-else'),
686
(('modified-something-else',), 'current'))
688
def corrected_parents(self):
691
(None, 'modified-something-else'),
692
(('basis',), 'current'))
694
def check_regexes(self, repo):
695
if repo.supports_rich_root():
696
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
697
# the expected count of errors.
702
"%d inconsistent parents" % count,
703
r"\* a-file-id version current has parents "
704
r"\('modified-something-else',\) but should have \('basis',\)",
707
def populate_repository(self, repo):
708
inv = self.make_one_file_inventory(repo, 'basis', ())
709
self.add_revision(repo, 'basis', inv, ())
711
# 'modified-something-else' is a correctly recorded revision, but it
712
# does not modify the file we are looking at, so the inventory for that
713
# file in this revision points to 'basis'.
714
inv = self.make_one_file_inventory(
715
repo, 'modified-something-else', ('basis',), inv_revision='basis')
716
self.add_revision(repo, 'modified-something-else', inv, ('basis',))
718
# The 'current' revision has 'modified-something-else' as its parent,
719
# but the 'current' version of 'a-file' should have 'basis' as its
721
inv = self.make_one_file_inventory(
722
repo, 'current', ('modified-something-else',))
723
self.add_revision(repo, 'current', inv, ('modified-something-else',))
724
self.versioned_root = repo.supports_rich_root()
726
def repository_text_key_references(self):
728
if self.versioned_root:
729
result.update({('TREE_ROOT', 'basis'): True,
730
('TREE_ROOT', 'current'): True,
731
('TREE_ROOT', 'modified-something-else'): True})
732
result.update({('a-file-id', 'basis'): True,
733
('a-file-id', 'current'): True})
736
def repository_text_keys(self):
737
return {('a-file-id', 'basis'): [NULL_REVISION],
738
('a-file-id', 'current'): [('a-file-id', 'basis')]}
740
def versioned_repository_text_keys(self):
741
return {('TREE_ROOT', 'basis'): ['null:'],
742
('TREE_ROOT', 'current'):
743
[('TREE_ROOT', 'modified-something-else')],
744
('TREE_ROOT', 'modified-something-else'):
745
[('TREE_ROOT', 'basis')]}
748
class IncorrectlyOrderedParentsScenario(BrokenRepoScenario):
749
"""A scenario where the set parents of a version of a file are correct, but
750
the order of those parents is incorrect.
752
This defines a 'broken-revision-1-2' and a 'broken-revision-2-1' which both
753
have their file version parents reversed compared to the revision parents,
754
which is invalid. (We use two revisions with opposite orderings of the
755
same parents to make sure that accidentally relying on dictionary/set
756
ordering cannot make the test pass; the assumption is that while dict/set
757
iteration order is arbitrary, it is also consistent within a single test).
760
def all_versions_after_reconcile(self):
761
return ['parent-1', 'parent-2', 'broken-revision-1-2',
762
'broken-revision-2-1']
764
def populated_parents(self):
768
(('parent-2', 'parent-1'), 'broken-revision-1-2'),
769
(('parent-1', 'parent-2'), 'broken-revision-2-1'))
771
def corrected_parents(self):
775
(('parent-1', 'parent-2'), 'broken-revision-1-2'),
776
(('parent-2', 'parent-1'), 'broken-revision-2-1'))
778
def check_regexes(self, repo):
779
if repo.supports_rich_root():
780
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
781
# the expected count of errors.
786
"%d inconsistent parents" % count,
787
r"\* a-file-id version broken-revision-1-2 has parents "
788
r"\('parent-2', 'parent-1'\) but should have "
789
r"\('parent-1', 'parent-2'\)",
790
r"\* a-file-id version broken-revision-2-1 has parents "
791
r"\('parent-1', 'parent-2'\) but should have "
792
r"\('parent-2', 'parent-1'\)")
794
def populate_repository(self, repo):
795
inv = self.make_one_file_inventory(repo, 'parent-1', [])
796
self.add_revision(repo, 'parent-1', inv, [])
798
inv = self.make_one_file_inventory(repo, 'parent-2', [])
799
self.add_revision(repo, 'parent-2', inv, [])
801
inv = self.make_one_file_inventory(
802
repo, 'broken-revision-1-2', ['parent-2', 'parent-1'])
804
repo, 'broken-revision-1-2', inv, ['parent-1', 'parent-2'])
806
inv = self.make_one_file_inventory(
807
repo, 'broken-revision-2-1', ['parent-1', 'parent-2'])
809
repo, 'broken-revision-2-1', inv, ['parent-2', 'parent-1'])
810
self.versioned_root = repo.supports_rich_root()
812
def repository_text_key_references(self):
814
if self.versioned_root:
815
result.update({('TREE_ROOT', 'broken-revision-1-2'): True,
816
('TREE_ROOT', 'broken-revision-2-1'): True,
817
('TREE_ROOT', 'parent-1'): True,
818
('TREE_ROOT', 'parent-2'): True})
819
result.update({('a-file-id', 'broken-revision-1-2'): True,
820
('a-file-id', 'broken-revision-2-1'): True,
821
('a-file-id', 'parent-1'): True,
822
('a-file-id', 'parent-2'): True})
825
def repository_text_keys(self):
826
return {('a-file-id', 'broken-revision-1-2'):
827
[('a-file-id', 'parent-1'), ('a-file-id', 'parent-2')],
828
('a-file-id', 'broken-revision-2-1'):
829
[('a-file-id', 'parent-2'), ('a-file-id', 'parent-1')],
830
('a-file-id', 'parent-1'): [NULL_REVISION],
831
('a-file-id', 'parent-2'): [NULL_REVISION]}
833
def versioned_repository_text_keys(self):
834
return {('TREE_ROOT', 'broken-revision-1-2'):
835
[('TREE_ROOT', 'parent-1'), ('TREE_ROOT', 'parent-2')],
836
('TREE_ROOT', 'broken-revision-2-1'):
837
[('TREE_ROOT', 'parent-2'), ('TREE_ROOT', 'parent-1')],
838
('TREE_ROOT', 'parent-1'): [NULL_REVISION],
839
('TREE_ROOT', 'parent-2'): [NULL_REVISION]}
842
all_broken_scenario_classes = [
843
UndamagedRepositoryScenario,
844
FileParentIsNotInRevisionAncestryScenario,
845
FileParentHasInaccessibleInventoryScenario,
846
FileParentsNotReferencedByAnyInventoryScenario,
847
TooManyParentsScenario,
848
ClaimedFileParentDidNotModifyFileScenario,
849
IncorrectlyOrderedParentsScenario,
850
UnreferencedFileParentsFromNoOpMergeScenario,
854
def load_tests(basic_tests, module, loader):
855
result = loader.suiteClass()
856
# add the tests for this module
857
result.addTests(basic_tests)
858
prefix = 'bzrlib.tests.repository_implementations.'
859
test_repository_modules = [
860
'test_add_fallback_repository',
863
# test_check_reconcile is intentionally omitted, see below.
864
'test_commit_builder',
866
'test_fileid_involved',
867
'test_find_text_key_references',
868
'test__generate_text_key_index',
869
'test_get_parent_map',
870
'test_has_same_location',
871
'test_has_revisions',
872
'test_is_write_locked',
873
'test_iter_reverse_revision_history',
881
module_name_list = [prefix + module_name
882
for module_name in test_repository_modules]
884
# add the tests for the sub modules
886
# Parameterize repository_implementations test modules by format.
887
format_scenarios = all_repository_format_scenarios()
888
result.addTests(multiply_tests_from_modules(module_name_list,
892
# test_check_reconcile needs to be parameterized by format *and* by broken
893
# repository scenario.
894
broken_scenarios = [(s.__name__, {'scenario_class': s})
895
for s in all_broken_scenario_classes]
896
broken_scenarios_for_all_formats = multiply_scenarios(
897
format_scenarios, broken_scenarios)
898
broken_scenario_applier = TestScenarioApplier()
899
broken_scenario_applier.scenarios = broken_scenarios_for_all_formats
901
[prefix + 'test_check_reconcile'],
902
broken_scenario_applier, loader, result)