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
SmartTCPServer_for_testing,
37
ReadonlySmartTCPServer_for_testing,
32
39
from bzrlib.tests import (
44
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(
47
from bzrlib.tests.bzrdir_implementations.test_bzrdir import TestCaseWithBzrDir
48
from bzrlib.transport.memory import MemoryServer
51
class RepositoryTestProviderAdapter(TestScenarioApplier):
52
"""A tool to generate a suite testing multiple repository formats at once.
54
This is done by copying the test once for each transport and injecting
55
the transport_server, transport_readonly_server, and bzrdir_format and
56
repository_format classes into each copy. Each copy is also given a new id()
57
to make it easy to identify.
60
def __init__(self, transport_server, transport_readonly_server, formats,
61
vfs_transport_factory=None):
62
TestScenarioApplier.__init__(self)
63
self._transport_server = transport_server
64
self._transport_readonly_server = transport_readonly_server
65
self._vfs_transport_factory = vfs_transport_factory
66
self.scenarios = self.formats_to_scenarios(formats)
68
def formats_to_scenarios(self, formats):
69
"""Transform the input formats to a list of scenarios.
71
:param formats: A list of (repository_format, bzrdir_format).
74
for repository_format, bzrdir_format in formats:
75
scenario = (repository_format.__class__.__name__,
76
{"transport_server":self._transport_server,
77
"transport_readonly_server":self._transport_readonly_server,
78
"bzrdir_format":bzrdir_format,
79
"repository_format":repository_format,
81
# Only override the test's vfs_transport_factory if one was
82
# specified, otherwise just leave the default in place.
83
if self._vfs_transport_factory:
84
scenario[1]['vfs_transport_factory'] = self._vfs_transport_factory
85
result.append(scenario)
89
class TestCaseWithRepository(TestCaseWithBzrDir):
91
def make_repository(self, relpath, format=None):
93
# Create a repository of the type we are trying to test.
94
made_control = self.make_bzrdir(relpath)
95
repo = self.repository_format.initialize(made_control)
96
if getattr(self, "repository_to_test_repository", None):
97
repo = self.repository_to_test_repository(repo)
100
return super(TestCaseWithRepository, self).make_repository(
101
relpath, format=format)
104
class BrokenRepoScenario(object):
105
"""Base class for defining scenarios for testing check and reconcile.
107
A subclass needs to define the following methods:
108
:populate_repository: a method to use to populate a repository with
109
sample revisions, inventories and file versions.
110
:all_versions_after_reconcile: all the versions in repository after
111
reconcile. run_test verifies that the text of each of these
112
versions of the file is unchanged by the reconcile.
113
:populated_parents: a list of (parents list, revision). Each version
114
of the file is verified to have the given parents before running
115
the reconcile. i.e. this is used to assert that the repo from the
116
factory is what we expect.
117
:corrected_parents: a list of (parents list, revision). Each version
118
of the file is verified to have the given parents after the
119
reconcile. i.e. this is used to assert that reconcile made the
120
changes we expect it to make.
122
A subclass may define the following optional method as well:
123
:corrected_fulltexts: a list of file versions that should be stored as
124
fulltexts (not deltas) after reconcile. run_test will verify that
128
def __init__(self, test_case):
129
self.test_case = test_case
131
def make_one_file_inventory(self, repo, revision, parents,
132
inv_revision=None, root_revision=None,
133
file_contents=None, make_file_version=True):
134
return self.test_case.make_one_file_inventory(
135
repo, revision, parents, inv_revision=inv_revision,
136
root_revision=root_revision, file_contents=file_contents,
137
make_file_version=make_file_version)
139
def add_revision(self, repo, revision_id, inv, parent_ids):
140
return self.test_case.add_revision(repo, revision_id, inv, parent_ids)
142
def corrected_fulltexts(self):
145
def repository_text_key_index(self):
147
if self.versioned_root:
148
result.update(self.versioned_repository_text_keys())
149
result.update(self.repository_text_keys())
153
class UndamagedRepositoryScenario(BrokenRepoScenario):
154
"""A scenario where the repository has no damage.
156
It has a single revision, 'rev1a', with a single file.
159
def all_versions_after_reconcile(self):
162
def populated_parents(self):
163
return (((), 'rev1a'), )
165
def corrected_parents(self):
166
# Same as the populated parents, because there was nothing wrong.
167
return self.populated_parents()
169
def check_regexes(self, repo):
170
return ["0 unreferenced text versions"]
172
def populate_repository(self, repo):
173
# make rev1a: A well-formed revision, containing 'a-file'
174
inv = self.make_one_file_inventory(
175
repo, 'rev1a', [], root_revision='rev1a')
176
self.add_revision(repo, 'rev1a', inv, [])
177
self.versioned_root = repo.supports_rich_root()
179
def repository_text_key_references(self):
181
if self.versioned_root:
182
result.update({('TREE_ROOT', 'rev1a'): True})
183
result.update({('a-file-id', 'rev1a'): True})
186
def repository_text_keys(self):
187
return {('a-file-id', 'rev1a'):[NULL_REVISION]}
189
def versioned_repository_text_keys(self):
190
return {('TREE_ROOT', 'rev1a'):[NULL_REVISION]}
193
class FileParentIsNotInRevisionAncestryScenario(BrokenRepoScenario):
194
"""A scenario where a revision 'rev2' has 'a-file' with a
195
parent 'rev1b' that is not in the revision ancestry.
197
Reconcile should remove 'rev1b' from the parents list of 'a-file' in
198
'rev2', preserving 'rev1a' as a parent.
201
def all_versions_after_reconcile(self):
202
return ('rev1a', 'rev2')
204
def populated_parents(self):
207
((), 'rev1b'), # Will be gc'd
208
(('rev1a', 'rev1b'), 'rev2')) # Will have parents trimmed
210
def corrected_parents(self):
214
(('rev1a',), 'rev2'))
216
def check_regexes(self, repo):
217
return [r"\* a-file-id version rev2 has parents \('rev1a', 'rev1b'\) "
218
r"but should have \('rev1a',\)",
219
"1 unreferenced text versions",
222
def populate_repository(self, repo):
223
# make rev1a: A well-formed revision, containing 'a-file'
224
inv = self.make_one_file_inventory(
225
repo, 'rev1a', [], root_revision='rev1a')
226
self.add_revision(repo, 'rev1a', inv, [])
228
# make rev1b, which has no Revision, but has an Inventory, and
230
inv = self.make_one_file_inventory(
231
repo, 'rev1b', [], root_revision='rev1b')
232
repo.add_inventory('rev1b', inv, [])
234
# make rev2, with a-file.
235
# a-file has 'rev1b' as an ancestor, even though this is not
236
# mentioned by 'rev1a', making it an unreferenced ancestor
237
inv = self.make_one_file_inventory(
238
repo, 'rev2', ['rev1a', 'rev1b'])
239
self.add_revision(repo, 'rev2', inv, ['rev1a'])
240
self.versioned_root = repo.supports_rich_root()
242
def repository_text_key_references(self):
244
if self.versioned_root:
245
result.update({('TREE_ROOT', 'rev1a'): True,
246
('TREE_ROOT', 'rev2'): True})
247
result.update({('a-file-id', 'rev1a'): True,
248
('a-file-id', 'rev2'): True})
251
def repository_text_keys(self):
252
return {('a-file-id', 'rev1a'):[NULL_REVISION],
253
('a-file-id', 'rev2'):[('a-file-id', 'rev1a')]}
255
def versioned_repository_text_keys(self):
256
return {('TREE_ROOT', 'rev1a'):[NULL_REVISION],
257
('TREE_ROOT', 'rev2'):[('TREE_ROOT', 'rev1a')]}
260
class FileParentHasInaccessibleInventoryScenario(BrokenRepoScenario):
261
"""A scenario where a revision 'rev3' containing 'a-file' modified in
262
'rev3', and with a parent which is in the revision ancestory, but whose
263
inventory cannot be accessed at all.
265
Reconcile should remove the file version parent whose inventory is
266
inaccessbile (i.e. remove 'rev1c' from the parents of a-file's rev3).
269
def all_versions_after_reconcile(self):
270
return ('rev2', 'rev3')
272
def populated_parents(self):
275
(('rev1c',), 'rev3'))
277
def corrected_parents(self):
282
def check_regexes(self, repo):
283
return [r"\* a-file-id version rev3 has parents "
284
r"\('rev1c',\) but should have \(\)",
287
def populate_repository(self, repo):
288
# make rev2, with a-file
290
inv = self.make_one_file_inventory(repo, 'rev2', [])
291
self.add_revision(repo, 'rev2', inv, [])
293
# make ghost revision rev1c, with a version of a-file present so
294
# that we generate a knit delta against this version. In real life
295
# the ghost might never have been present or rev3 might have been
296
# generated against a revision that was present at the time. So
297
# currently we have the full history of a-file present even though
298
# the inventory and revision objects are not.
299
self.make_one_file_inventory(repo, 'rev1c', [])
301
# make rev3 with a-file
302
# a-file refers to 'rev1c', which is a ghost in this repository, so
303
# a-file cannot have rev1c as its ancestor.
304
inv = self.make_one_file_inventory(repo, 'rev3', ['rev1c'])
305
self.add_revision(repo, 'rev3', inv, ['rev1c', 'rev1a'])
306
self.versioned_root = repo.supports_rich_root()
308
def repository_text_key_references(self):
310
if self.versioned_root:
311
result.update({('TREE_ROOT', 'rev2'): True,
312
('TREE_ROOT', 'rev3'): True})
313
result.update({('a-file-id', 'rev2'): True,
314
('a-file-id', 'rev3'): True})
317
def repository_text_keys(self):
318
return {('a-file-id', 'rev2'):[NULL_REVISION],
319
('a-file-id', 'rev3'):[NULL_REVISION]}
321
def versioned_repository_text_keys(self):
322
return {('TREE_ROOT', 'rev2'):[NULL_REVISION],
323
('TREE_ROOT', 'rev3'):[NULL_REVISION]}
326
class FileParentsNotReferencedByAnyInventoryScenario(BrokenRepoScenario):
327
"""A scenario where a repository with file 'a-file' which has extra
328
per-file versions that are not referenced by any inventory (even though
329
they have the same ID as actual revisions). The inventory of 'rev2'
330
references 'rev1a' of 'a-file', but there is a 'rev2' of 'some-file' stored
331
and erroneously referenced by later per-file versions (revisions 'rev4' and
334
Reconcile should remove the file parents that are not referenced by any
338
def all_versions_after_reconcile(self):
339
return ('rev1a', 'rev2c', 'rev4', 'rev5')
341
def populated_parents(self):
343
(('rev1a',), 'rev2'),
344
(('rev1a',), 'rev2b'),
347
(('rev2', 'rev2c'), 'rev5')]
349
def corrected_parents(self):
351
# rev2 and rev2b have been removed.
354
# rev3's accessible parent inventories all have rev1a as the last
356
(('rev1a',), 'rev3'),
357
# rev1a features in both rev4's parents but should only appear once
359
(('rev1a',), 'rev4'),
360
# rev2c is the head of rev1a and rev2c, the inventory provided
361
# per-file last-modified revisions.
362
(('rev2c',), 'rev5'))
364
def check_regexes(self, repo):
365
if repo.supports_rich_root():
366
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
367
# the expected count of errors.
372
"%d inconsistent parents" % count,
374
r"unreferenced version: {rev2} in a-file-id",
375
r"unreferenced version: {rev2b} in a-file-id",
377
r"a-file-id version rev3 has parents \('rev2',\) "
378
r"but should have \('rev1a',\)",
379
r"a-file-id version rev5 has parents \('rev2', 'rev2c'\) "
380
r"but should have \('rev2c',\)",
381
r"a-file-id version rev4 has parents \('rev2',\) "
382
r"but should have \('rev1a',\)",
385
def populate_repository(self, repo):
386
# make rev1a: A well-formed revision, containing 'a-file'
387
inv = self.make_one_file_inventory(
388
repo, 'rev1a', [], root_revision='rev1a')
389
self.add_revision(repo, 'rev1a', inv, [])
391
# make rev2, with a-file.
392
# a-file is unmodified from rev1a, and an unreferenced rev2 file
393
# version is present in the repository.
394
self.make_one_file_inventory(
395
repo, 'rev2', ['rev1a'], inv_revision='rev1a')
396
self.add_revision(repo, 'rev2', inv, ['rev1a'])
398
# make rev3 with a-file
399
# a-file has 'rev2' as its ancestor, but the revision in 'rev2' was
400
# rev1a so this is inconsistent with rev2's inventory - it should
401
# be rev1a, and at the revision level 1c is not present - it is a
402
# ghost, so only the details from rev1a are available for
403
# determining whether a delta is acceptable, or a full is needed,
404
# and what the correct parents are.
405
inv = self.make_one_file_inventory(repo, 'rev3', ['rev2'])
406
self.add_revision(repo, 'rev3', inv, ['rev1c', 'rev1a'])
408
# In rev2b, the true last-modifying-revision of a-file is rev1a,
409
# inherited from rev2, but there is a version rev2b of the file, which
410
# reconcile could remove, leaving no rev2b. Most importantly,
411
# revisions descending from rev2b should not have per-file parents of
413
# ??? This is to test deduplication in fixing rev4
414
inv = self.make_one_file_inventory(
415
repo, 'rev2b', ['rev1a'], inv_revision='rev1a')
416
self.add_revision(repo, 'rev2b', inv, ['rev1a'])
418
# rev4 is for testing that when the last modified of a file in
419
# multiple parent revisions is the same, that it only appears once
420
# in the generated per file parents list: rev2 and rev2b both
421
# descend from 1a and do not change the file a-file, so there should
422
# be no version of a-file 'rev2' or 'rev2b', but rev4 does change
423
# a-file, and is a merge of rev2 and rev2b, so it should end up with
424
# a parent of just rev1a - the starting file parents list is simply
426
inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
427
self.add_revision(repo, 'rev4', inv, ['rev2', 'rev2b'])
429
# rev2c changes a-file from rev1a, so the version it of a-file it
430
# introduces is a head revision when rev5 is checked.
431
inv = self.make_one_file_inventory(repo, 'rev2c', ['rev1a'])
432
self.add_revision(repo, 'rev2c', inv, ['rev1a'])
434
# rev5 descends from rev2 and rev2c; as rev2 does not alter a-file,
435
# but rev2c does, this should use rev2c as the parent for the per
436
# file history, even though more than one per-file parent is
437
# available, because we use the heads of the revision parents for
438
# the inventory modification revisions of the file to determine the
439
# parents for the per file graph.
440
inv = self.make_one_file_inventory(repo, 'rev5', ['rev2', 'rev2c'])
441
self.add_revision(repo, 'rev5', inv, ['rev2', 'rev2c'])
442
self.versioned_root = repo.supports_rich_root()
444
def repository_text_key_references(self):
446
if self.versioned_root:
447
result.update({('TREE_ROOT', 'rev1a'): True,
448
('TREE_ROOT', 'rev2'): True,
449
('TREE_ROOT', 'rev2b'): True,
450
('TREE_ROOT', 'rev2c'): True,
451
('TREE_ROOT', 'rev3'): True,
452
('TREE_ROOT', 'rev4'): True,
453
('TREE_ROOT', 'rev5'): True})
454
result.update({('a-file-id', 'rev1a'): True,
455
('a-file-id', 'rev2c'): True,
456
('a-file-id', 'rev3'): True,
457
('a-file-id', 'rev4'): True,
458
('a-file-id', 'rev5'): True})
461
def repository_text_keys(self):
462
return {('a-file-id', 'rev1a'): [NULL_REVISION],
463
('a-file-id', 'rev2c'): [('a-file-id', 'rev1a')],
464
('a-file-id', 'rev3'): [('a-file-id', 'rev1a')],
465
('a-file-id', 'rev4'): [('a-file-id', 'rev1a')],
466
('a-file-id', 'rev5'): [('a-file-id', 'rev2c')]}
468
def versioned_repository_text_keys(self):
469
return {('TREE_ROOT', 'rev1a'): [NULL_REVISION],
470
('TREE_ROOT', 'rev2'): [('TREE_ROOT', 'rev1a')],
471
('TREE_ROOT', 'rev2b'): [('TREE_ROOT', 'rev1a')],
472
('TREE_ROOT', 'rev2c'): [('TREE_ROOT', 'rev1a')],
473
('TREE_ROOT', 'rev3'): [('TREE_ROOT', 'rev1a')],
474
('TREE_ROOT', 'rev4'):
475
[('TREE_ROOT', 'rev2'), ('TREE_ROOT', 'rev2b')],
476
('TREE_ROOT', 'rev5'):
477
[('TREE_ROOT', 'rev2'), ('TREE_ROOT', 'rev2c')]}
480
class UnreferencedFileParentsFromNoOpMergeScenario(BrokenRepoScenario):
482
rev1a and rev1b with identical contents
483
rev2 revision has parents of [rev1a, rev1b]
484
There is a a-file:rev2 file version, not referenced by the inventory.
487
def all_versions_after_reconcile(self):
488
return ('rev1a', 'rev1b', 'rev2', 'rev4')
490
def populated_parents(self):
494
(('rev1a', 'rev1b'), 'rev2'),
499
def corrected_parents(self):
508
def corrected_fulltexts(self):
511
def check_regexes(self, repo):
514
def populate_repository(self, repo):
515
# make rev1a: A well-formed revision, containing 'a-file'
516
inv1a = self.make_one_file_inventory(
517
repo, 'rev1a', [], root_revision='rev1a')
518
self.add_revision(repo, 'rev1a', inv1a, [])
520
# make rev1b: A well-formed revision, containing 'a-file'
521
# rev1b of a-file has the exact same contents as rev1a.
522
file_contents = repo.revision_tree('rev1a').get_file_text('a-file-id')
523
inv = self.make_one_file_inventory(
524
repo, 'rev1b', [], root_revision='rev1b',
525
file_contents=file_contents)
526
self.add_revision(repo, 'rev1b', inv, [])
528
# make rev2, a merge of rev1a and rev1b, with a-file.
529
# a-file is unmodified from rev1a and rev1b, but a new version is
530
# wrongly present anyway.
531
inv = self.make_one_file_inventory(
532
repo, 'rev2', ['rev1a', 'rev1b'], inv_revision='rev1a',
533
file_contents=file_contents)
534
self.add_revision(repo, 'rev2', inv, ['rev1a', 'rev1b'])
536
# rev3: a-file unchanged from rev2, but wrongly referencing rev2 of the
537
# file in its inventory.
538
inv = self.make_one_file_inventory(
539
repo, 'rev3', ['rev2'], inv_revision='rev2',
540
file_contents=file_contents, make_file_version=False)
541
self.add_revision(repo, 'rev3', inv, ['rev2'])
543
# rev4: a modification of a-file on top of rev3.
544
inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
545
self.add_revision(repo, 'rev4', inv, ['rev3'])
546
self.versioned_root = repo.supports_rich_root()
548
def repository_text_key_references(self):
550
if self.versioned_root:
551
result.update({('TREE_ROOT', 'rev1a'): True,
552
('TREE_ROOT', 'rev1b'): True,
553
('TREE_ROOT', 'rev2'): True,
554
('TREE_ROOT', 'rev3'): True,
555
('TREE_ROOT', 'rev4'): True})
556
result.update({('a-file-id', 'rev1a'): True,
557
('a-file-id', 'rev1b'): True,
558
('a-file-id', 'rev2'): False,
559
('a-file-id', 'rev4'): True})
562
def repository_text_keys(self):
563
return {('a-file-id', 'rev1a'): [NULL_REVISION],
564
('a-file-id', 'rev1b'): [NULL_REVISION],
565
('a-file-id', 'rev2'): [NULL_REVISION],
566
('a-file-id', 'rev4'): [('a-file-id', 'rev2')]}
568
def versioned_repository_text_keys(self):
569
return {('TREE_ROOT', 'rev1a'): [NULL_REVISION],
570
('TREE_ROOT', 'rev1b'): [NULL_REVISION],
571
('TREE_ROOT', 'rev2'):
572
[('TREE_ROOT', 'rev1a'), ('TREE_ROOT', 'rev1b')],
573
('TREE_ROOT', 'rev3'): [('TREE_ROOT', 'rev2')],
574
('TREE_ROOT', 'rev4'): [('TREE_ROOT', 'rev3')]}
577
class TooManyParentsScenario(BrokenRepoScenario):
578
"""A scenario where 'broken-revision' of 'a-file' claims to have parents
579
['good-parent', 'bad-parent']. However 'bad-parent' is in the ancestry of
580
'good-parent', so the correct parent list for that file version are is just
584
def all_versions_after_reconcile(self):
585
return ('bad-parent', 'good-parent', 'broken-revision')
587
def populated_parents(self):
590
(('bad-parent',), 'good-parent'),
591
(('good-parent', 'bad-parent'), 'broken-revision'))
593
def corrected_parents(self):
596
(('bad-parent',), 'good-parent'),
597
(('good-parent',), 'broken-revision'))
599
def check_regexes(self, repo):
600
if repo.supports_rich_root():
601
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
602
# the expected count of errors.
607
' %d inconsistent parents' % count,
608
(r" \* a-file-id version broken-revision has parents "
609
r"\('good-parent', 'bad-parent'\) but "
610
r"should have \('good-parent',\)"))
612
def populate_repository(self, repo):
613
inv = self.make_one_file_inventory(
614
repo, 'bad-parent', (), root_revision='bad-parent')
615
self.add_revision(repo, 'bad-parent', inv, ())
617
inv = self.make_one_file_inventory(
618
repo, 'good-parent', ('bad-parent',))
619
self.add_revision(repo, 'good-parent', inv, ('bad-parent',))
621
inv = self.make_one_file_inventory(
622
repo, 'broken-revision', ('good-parent', 'bad-parent'))
623
self.add_revision(repo, 'broken-revision', inv, ('good-parent',))
624
self.versioned_root = repo.supports_rich_root()
626
def repository_text_key_references(self):
628
if self.versioned_root:
629
result.update({('TREE_ROOT', 'bad-parent'): True,
630
('TREE_ROOT', 'broken-revision'): True,
631
('TREE_ROOT', 'good-parent'): True})
632
result.update({('a-file-id', 'bad-parent'): True,
633
('a-file-id', 'broken-revision'): True,
634
('a-file-id', 'good-parent'): True})
637
def repository_text_keys(self):
638
return {('a-file-id', 'bad-parent'): [NULL_REVISION],
639
('a-file-id', 'broken-revision'):
640
[('a-file-id', 'good-parent')],
641
('a-file-id', 'good-parent'): [('a-file-id', 'bad-parent')]}
643
def versioned_repository_text_keys(self):
644
return {('TREE_ROOT', 'bad-parent'): [NULL_REVISION],
645
('TREE_ROOT', 'broken-revision'):
646
[('TREE_ROOT', 'good-parent')],
647
('TREE_ROOT', 'good-parent'): [('TREE_ROOT', 'bad-parent')]}
650
class ClaimedFileParentDidNotModifyFileScenario(BrokenRepoScenario):
651
"""A scenario where the file parent is the same as the revision parent, but
652
should not be because that revision did not modify the file.
654
Specifically, the parent revision of 'current' is
655
'modified-something-else', which does not modify 'a-file', but the
656
'current' version of 'a-file' erroneously claims that
657
'modified-something-else' is the parent file version.
660
def all_versions_after_reconcile(self):
661
return ('basis', 'current')
663
def populated_parents(self):
666
(('basis',), 'modified-something-else'),
667
(('modified-something-else',), 'current'))
669
def corrected_parents(self):
672
(None, 'modified-something-else'),
673
(('basis',), 'current'))
675
def check_regexes(self, repo):
676
if repo.supports_rich_root():
677
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
678
# the expected count of errors.
683
"%d inconsistent parents" % count,
684
r"\* a-file-id version current has parents "
685
r"\('modified-something-else',\) but should have \('basis',\)",
688
def populate_repository(self, repo):
689
inv = self.make_one_file_inventory(repo, 'basis', ())
690
self.add_revision(repo, 'basis', inv, ())
692
# 'modified-something-else' is a correctly recorded revision, but it
693
# does not modify the file we are looking at, so the inventory for that
694
# file in this revision points to 'basis'.
695
inv = self.make_one_file_inventory(
696
repo, 'modified-something-else', ('basis',), inv_revision='basis')
697
self.add_revision(repo, 'modified-something-else', inv, ('basis',))
699
# The 'current' revision has 'modified-something-else' as its parent,
700
# but the 'current' version of 'a-file' should have 'basis' as its
702
inv = self.make_one_file_inventory(
703
repo, 'current', ('modified-something-else',))
704
self.add_revision(repo, 'current', inv, ('modified-something-else',))
705
self.versioned_root = repo.supports_rich_root()
707
def repository_text_key_references(self):
709
if self.versioned_root:
710
result.update({('TREE_ROOT', 'basis'): True,
711
('TREE_ROOT', 'current'): True,
712
('TREE_ROOT', 'modified-something-else'): True})
713
result.update({('a-file-id', 'basis'): True,
714
('a-file-id', 'current'): True})
717
def repository_text_keys(self):
718
return {('a-file-id', 'basis'): [NULL_REVISION],
719
('a-file-id', 'current'): [('a-file-id', 'basis')]}
721
def versioned_repository_text_keys(self):
722
return {('TREE_ROOT', 'basis'): ['null:'],
723
('TREE_ROOT', 'current'):
724
[('TREE_ROOT', 'modified-something-else')],
725
('TREE_ROOT', 'modified-something-else'):
726
[('TREE_ROOT', 'basis')]}
729
class IncorrectlyOrderedParentsScenario(BrokenRepoScenario):
730
"""A scenario where the set parents of a version of a file are correct, but
731
the order of those parents is incorrect.
733
This defines a 'broken-revision-1-2' and a 'broken-revision-2-1' which both
734
have their file version parents reversed compared to the revision parents,
735
which is invalid. (We use two revisions with opposite orderings of the
736
same parents to make sure that accidentally relying on dictionary/set
737
ordering cannot make the test pass; the assumption is that while dict/set
738
iteration order is arbitrary, it is also consistent within a single test).
741
def all_versions_after_reconcile(self):
742
return ['parent-1', 'parent-2', 'broken-revision-1-2',
743
'broken-revision-2-1']
745
def populated_parents(self):
749
(('parent-2', 'parent-1'), 'broken-revision-1-2'),
750
(('parent-1', 'parent-2'), 'broken-revision-2-1'))
752
def corrected_parents(self):
756
(('parent-1', 'parent-2'), 'broken-revision-1-2'),
757
(('parent-2', 'parent-1'), 'broken-revision-2-1'))
759
def check_regexes(self, repo):
760
if repo.supports_rich_root():
761
# TREE_ROOT will be wrong; but we're not testing it. so just adjust
762
# the expected count of errors.
767
"%d inconsistent parents" % count,
768
r"\* a-file-id version broken-revision-1-2 has parents "
769
r"\('parent-2', 'parent-1'\) but should have "
770
r"\('parent-1', 'parent-2'\)",
771
r"\* a-file-id version broken-revision-2-1 has parents "
772
r"\('parent-1', 'parent-2'\) but should have "
773
r"\('parent-2', 'parent-1'\)")
775
def populate_repository(self, repo):
776
inv = self.make_one_file_inventory(repo, 'parent-1', [])
777
self.add_revision(repo, 'parent-1', inv, [])
779
inv = self.make_one_file_inventory(repo, 'parent-2', [])
780
self.add_revision(repo, 'parent-2', inv, [])
782
inv = self.make_one_file_inventory(
783
repo, 'broken-revision-1-2', ['parent-2', 'parent-1'])
785
repo, 'broken-revision-1-2', inv, ['parent-1', 'parent-2'])
787
inv = self.make_one_file_inventory(
788
repo, 'broken-revision-2-1', ['parent-1', 'parent-2'])
790
repo, 'broken-revision-2-1', inv, ['parent-2', 'parent-1'])
791
self.versioned_root = repo.supports_rich_root()
793
def repository_text_key_references(self):
795
if self.versioned_root:
796
result.update({('TREE_ROOT', 'broken-revision-1-2'): True,
797
('TREE_ROOT', 'broken-revision-2-1'): True,
798
('TREE_ROOT', 'parent-1'): True,
799
('TREE_ROOT', 'parent-2'): True})
800
result.update({('a-file-id', 'broken-revision-1-2'): True,
801
('a-file-id', 'broken-revision-2-1'): True,
802
('a-file-id', 'parent-1'): True,
803
('a-file-id', 'parent-2'): True})
806
def repository_text_keys(self):
807
return {('a-file-id', 'broken-revision-1-2'):
808
[('a-file-id', 'parent-1'), ('a-file-id', 'parent-2')],
809
('a-file-id', 'broken-revision-2-1'):
810
[('a-file-id', 'parent-2'), ('a-file-id', 'parent-1')],
811
('a-file-id', 'parent-1'): [NULL_REVISION],
812
('a-file-id', 'parent-2'): [NULL_REVISION]}
814
def versioned_repository_text_keys(self):
815
return {('TREE_ROOT', 'broken-revision-1-2'):
816
[('TREE_ROOT', 'parent-1'), ('TREE_ROOT', 'parent-2')],
817
('TREE_ROOT', 'broken-revision-2-1'):
818
[('TREE_ROOT', 'parent-2'), ('TREE_ROOT', 'parent-1')],
819
('TREE_ROOT', 'parent-1'): [NULL_REVISION],
820
('TREE_ROOT', 'parent-2'): [NULL_REVISION]}
823
all_broken_scenario_classes = [
824
UndamagedRepositoryScenario,
825
FileParentIsNotInRevisionAncestryScenario,
826
FileParentHasInaccessibleInventoryScenario,
827
FileParentsNotReferencedByAnyInventoryScenario,
828
TooManyParentsScenario,
829
ClaimedFileParentDidNotModifyFileScenario,
830
IncorrectlyOrderedParentsScenario,
831
UnreferencedFileParentsFromNoOpMergeScenario,
835
def load_tests(basic_tests, module, loader):
836
result = loader.suiteClass()
837
# add the tests for this module
838
result.addTests(basic_tests)
840
registry = repository.format_registry
841
all_formats = [registry.get(k) for k in registry.keys()]
842
all_formats.extend(weaverepo._legacy_formats)
843
disk_format_adapter = RepositoryTestProviderAdapter(
51
844
default_transport,
52
845
# None here will cause a readonly decorator to be created
53
846
# 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)
848
[(format, format._matchingbzrdir) for format in all_formats])
850
remote_repo_adapter = RepositoryTestProviderAdapter(
851
SmartTCPServer_for_testing,
852
ReadonlySmartTCPServer_for_testing,
853
[(RemoteRepositoryFormat(), RemoteBzrDirFormat())],
857
# format_scenarios is all the implementations of Repository; i.e. all disk
858
# formats plus RemoteRepository.
859
format_scenarios = (disk_format_adapter.scenarios +
860
remote_repo_adapter.scenarios)
862
prefix = 'bzrlib.tests.repository_implementations.'
863
test_repository_modules = [
866
# test_check_reconcile is intentionally omitted, see below.
867
'test_commit_builder',
869
'test_fileid_involved',
870
'test_find_text_key_references',
871
'test__generate_text_key_index',
872
'test_has_same_location',
873
'test_has_revisions',
874
'test_is_write_locked',
875
'test_iter_reverse_revision_history',
883
module_name_list = [prefix + module_name
884
for module_name in test_repository_modules]
886
# add the tests for the sub modules
888
# Parameterize repository_implementations test modules by format.
889
result.addTests(multiply_tests_from_modules(module_name_list,
893
# test_check_reconcile needs to be parameterized by format *and* by broken
894
# repository scenario.
895
broken_scenarios = [(s.__name__, {'scenario_class': s})
896
for s in all_broken_scenario_classes]
897
broken_scenarios_for_all_formats = multiply_scenarios(
898
format_scenarios, broken_scenarios)
899
broken_scenario_applier = TestScenarioApplier()
900
broken_scenario_applier.scenarios = broken_scenarios_for_all_formats
902
[prefix + 'test_check_reconcile'],
903
broken_scenario_applier, loader, result)