~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_repository/__init__.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2012, 2016 Canonical Ltd
2
2
# Authors: Robert Collins <robert.collins@canonical.com>
3
3
#          and others
4
4
#
27
27
from bzrlib import (
28
28
    repository,
29
29
    )
30
 
from bzrlib.revision import NULL_REVISION
31
 
from bzrlib.repofmt import (
32
 
    weaverepo,
33
 
    )
34
 
from bzrlib.remote import RemoteBzrDirFormat, RemoteRepositoryFormat
 
30
from bzrlib.remote import RemoteRepositoryFormat
35
31
from bzrlib.tests import (
36
32
    default_transport,
37
 
    multiply_scenarios,
38
33
    multiply_tests,
39
34
    test_server,
40
35
    )
73
68
def all_repository_format_scenarios():
74
69
    """Return a list of test scenarios for parameterising repository tests.
75
70
    """
76
 
    registry = repository.format_registry
77
 
    all_formats = [registry.get(k) for k in registry.keys()]
78
 
    all_formats.extend(weaverepo._legacy_formats)
 
71
    all_formats = repository.format_registry._get_all()
79
72
    # format_scenarios is all the implementations of Repository; i.e. all disk
80
73
    # formats plus RemoteRepository.
81
74
    format_scenarios = formats_to_scenarios(
99
92
 
100
93
class TestCaseWithRepository(TestCaseWithControlDir):
101
94
 
102
 
    def make_repository(self, relpath, shared=False, format=None):
103
 
        if format is None:
 
95
    def get_default_format(self):
 
96
        format = self.repository_format._matchingbzrdir
 
97
        self.assertEqual(format.repository_format, self.repository_format)
 
98
        return format
 
99
 
 
100
    def make_repository(self, relpath, shared=None, format=None):
 
101
        format = self.resolve_format(format)
 
102
        repo = super(TestCaseWithRepository, self).make_repository(
 
103
            relpath, shared=shared, format=format)
 
104
        if format is None or format.repository_format is self.repository_format:
104
105
            # Create a repository of the type we are trying to test.
105
 
            made_control = self.make_bzrdir(relpath)
106
 
            repo = self.repository_format.initialize(made_control,
107
 
                    shared=shared)
108
106
            if getattr(self, "repository_to_test_repository", None):
109
107
                repo = self.repository_to_test_repository(repo)
110
 
            return repo
111
 
        else:
112
 
            return super(TestCaseWithRepository, self).make_repository(
113
 
                relpath, shared=shared, format=format)
114
 
 
115
 
 
116
 
class BrokenRepoScenario(object):
117
 
    """Base class for defining scenarios for testing check and reconcile.
118
 
 
119
 
    A subclass needs to define the following methods:
120
 
        :populate_repository: a method to use to populate a repository with
121
 
            sample revisions, inventories and file versions.
122
 
        :all_versions_after_reconcile: all the versions in repository after
123
 
            reconcile.  run_test verifies that the text of each of these
124
 
            versions of the file is unchanged by the reconcile.
125
 
        :populated_parents: a list of (parents list, revision).  Each version
126
 
            of the file is verified to have the given parents before running
127
 
            the reconcile.  i.e. this is used to assert that the repo from the
128
 
            factory is what we expect.
129
 
        :corrected_parents: a list of (parents list, revision).  Each version
130
 
            of the file is verified to have the given parents after the
131
 
            reconcile.  i.e. this is used to assert that reconcile made the
132
 
            changes we expect it to make.
133
 
 
134
 
    A subclass may define the following optional method as well:
135
 
        :corrected_fulltexts: a list of file versions that should be stored as
136
 
            fulltexts (not deltas) after reconcile.  run_test will verify that
137
 
            this occurs.
138
 
    """
139
 
 
140
 
    def __init__(self, test_case):
141
 
        self.test_case = test_case
142
 
 
143
 
    def make_one_file_inventory(self, repo, revision, parents,
144
 
                                inv_revision=None, root_revision=None,
145
 
                                file_contents=None, make_file_version=True):
146
 
        return self.test_case.make_one_file_inventory(
147
 
            repo, revision, parents, inv_revision=inv_revision,
148
 
            root_revision=root_revision, file_contents=file_contents,
149
 
            make_file_version=make_file_version)
150
 
 
151
 
    def add_revision(self, repo, revision_id, inv, parent_ids):
152
 
        return self.test_case.add_revision(repo, revision_id, inv, parent_ids)
153
 
 
154
 
    def corrected_fulltexts(self):
155
 
        return []
156
 
 
157
 
    def repository_text_key_index(self):
158
 
        result = {}
159
 
        if self.versioned_root:
160
 
            result.update(self.versioned_repository_text_keys())
161
 
        result.update(self.repository_text_keys())
162
 
        return result
163
 
 
164
 
 
165
 
class UndamagedRepositoryScenario(BrokenRepoScenario):
166
 
    """A scenario where the repository has no damage.
167
 
 
168
 
    It has a single revision, 'rev1a', with a single file.
169
 
    """
170
 
 
171
 
    def all_versions_after_reconcile(self):
172
 
        return ('rev1a', )
173
 
 
174
 
    def populated_parents(self):
175
 
        return (((), 'rev1a'), )
176
 
 
177
 
    def corrected_parents(self):
178
 
        # Same as the populated parents, because there was nothing wrong.
179
 
        return self.populated_parents()
180
 
 
181
 
    def check_regexes(self, repo):
182
 
        return ["0 unreferenced text versions"]
183
 
 
184
 
    def populate_repository(self, repo):
185
 
        # make rev1a: A well-formed revision, containing 'a-file'
186
 
        inv = self.make_one_file_inventory(
187
 
            repo, 'rev1a', [], root_revision='rev1a')
188
 
        self.add_revision(repo, 'rev1a', inv, [])
189
 
        self.versioned_root = repo.supports_rich_root()
190
 
 
191
 
    def repository_text_key_references(self):
192
 
        result = {}
193
 
        if self.versioned_root:
194
 
            result.update({('TREE_ROOT', 'rev1a'): True})
195
 
        result.update({('a-file-id', 'rev1a'): True})
196
 
        return result
197
 
 
198
 
    def repository_text_keys(self):
199
 
        return {('a-file-id', 'rev1a'):[NULL_REVISION]}
200
 
 
201
 
    def versioned_repository_text_keys(self):
202
 
        return {('TREE_ROOT', 'rev1a'):[NULL_REVISION]}
203
 
 
204
 
 
205
 
class FileParentIsNotInRevisionAncestryScenario(BrokenRepoScenario):
206
 
    """A scenario where a revision 'rev2' has 'a-file' with a
207
 
    parent 'rev1b' that is not in the revision ancestry.
208
 
 
209
 
    Reconcile should remove 'rev1b' from the parents list of 'a-file' in
210
 
    'rev2', preserving 'rev1a' as a parent.
211
 
    """
212
 
 
213
 
    def all_versions_after_reconcile(self):
214
 
        return ('rev1a', 'rev2')
215
 
 
216
 
    def populated_parents(self):
217
 
        return (
218
 
            ((), 'rev1a'),
219
 
            ((), 'rev1b'), # Will be gc'd
220
 
            (('rev1a', 'rev1b'), 'rev2')) # Will have parents trimmed
221
 
 
222
 
    def corrected_parents(self):
223
 
        return (
224
 
            ((), 'rev1a'),
225
 
            (None, 'rev1b'),
226
 
            (('rev1a',), 'rev2'))
227
 
 
228
 
    def check_regexes(self, repo):
229
 
        return [r"\* a-file-id version rev2 has parents \('rev1a', 'rev1b'\) "
230
 
                r"but should have \('rev1a',\)",
231
 
                "1 unreferenced text versions",
232
 
                ]
233
 
 
234
 
    def populate_repository(self, repo):
235
 
        # make rev1a: A well-formed revision, containing 'a-file'
236
 
        inv = self.make_one_file_inventory(
237
 
            repo, 'rev1a', [], root_revision='rev1a')
238
 
        self.add_revision(repo, 'rev1a', inv, [])
239
 
 
240
 
        # make rev1b, which has no Revision, but has an Inventory, and
241
 
        # a-file
242
 
        inv = self.make_one_file_inventory(
243
 
            repo, 'rev1b', [], root_revision='rev1b')
244
 
        repo.add_inventory('rev1b', inv, [])
245
 
 
246
 
        # make rev2, with a-file.
247
 
        # a-file has 'rev1b' as an ancestor, even though this is not
248
 
        # mentioned by 'rev1a', making it an unreferenced ancestor
249
 
        inv = self.make_one_file_inventory(
250
 
            repo, 'rev2', ['rev1a', 'rev1b'])
251
 
        self.add_revision(repo, 'rev2', inv, ['rev1a'])
252
 
        self.versioned_root = repo.supports_rich_root()
253
 
 
254
 
    def repository_text_key_references(self):
255
 
        result = {}
256
 
        if self.versioned_root:
257
 
            result.update({('TREE_ROOT', 'rev1a'): True,
258
 
                           ('TREE_ROOT', 'rev2'): True})
259
 
        result.update({('a-file-id', 'rev1a'): True,
260
 
                       ('a-file-id', 'rev2'): True})
261
 
        return result
262
 
 
263
 
    def repository_text_keys(self):
264
 
        return {('a-file-id', 'rev1a'):[NULL_REVISION],
265
 
                ('a-file-id', 'rev2'):[('a-file-id', 'rev1a')]}
266
 
 
267
 
    def versioned_repository_text_keys(self):
268
 
        return {('TREE_ROOT', 'rev1a'):[NULL_REVISION],
269
 
                ('TREE_ROOT', 'rev2'):[('TREE_ROOT', 'rev1a')]}
270
 
 
271
 
 
272
 
class FileParentHasInaccessibleInventoryScenario(BrokenRepoScenario):
273
 
    """A scenario where a revision 'rev3' containing 'a-file' modified in
274
 
    'rev3', and with a parent which is in the revision ancestory, but whose
275
 
    inventory cannot be accessed at all.
276
 
 
277
 
    Reconcile should remove the file version parent whose inventory is
278
 
    inaccessbile (i.e. remove 'rev1c' from the parents of a-file's rev3).
279
 
    """
280
 
 
281
 
    def all_versions_after_reconcile(self):
282
 
        return ('rev2', 'rev3')
283
 
 
284
 
    def populated_parents(self):
285
 
        return (
286
 
            ((), 'rev2'),
287
 
            (('rev1c',), 'rev3'))
288
 
 
289
 
    def corrected_parents(self):
290
 
        return (
291
 
            ((), 'rev2'),
292
 
            ((), 'rev3'))
293
 
 
294
 
    def check_regexes(self, repo):
295
 
        return [r"\* a-file-id version rev3 has parents "
296
 
                r"\('rev1c',\) but should have \(\)",
297
 
                ]
298
 
 
299
 
    def populate_repository(self, repo):
300
 
        # make rev2, with a-file
301
 
        # a-file is sane
302
 
        inv = self.make_one_file_inventory(repo, 'rev2', [])
303
 
        self.add_revision(repo, 'rev2', inv, [])
304
 
 
305
 
        # make ghost revision rev1c, with a version of a-file present so
306
 
        # that we generate a knit delta against this version.  In real life
307
 
        # the ghost might never have been present or rev3 might have been
308
 
        # generated against a revision that was present at the time.  So
309
 
        # currently we have the full history of a-file present even though
310
 
        # the inventory and revision objects are not.
311
 
        self.make_one_file_inventory(repo, 'rev1c', [])
312
 
 
313
 
        # make rev3 with a-file
314
 
        # a-file refers to 'rev1c', which is a ghost in this repository, so
315
 
        # a-file cannot have rev1c as its ancestor.
316
 
        inv = self.make_one_file_inventory(repo, 'rev3', ['rev1c'])
317
 
        self.add_revision(repo, 'rev3', inv, ['rev1c', 'rev1a'])
318
 
        self.versioned_root = repo.supports_rich_root()
319
 
 
320
 
    def repository_text_key_references(self):
321
 
        result = {}
322
 
        if self.versioned_root:
323
 
            result.update({('TREE_ROOT', 'rev2'): True,
324
 
                           ('TREE_ROOT', 'rev3'): True})
325
 
        result.update({('a-file-id', 'rev2'): True,
326
 
                       ('a-file-id', 'rev3'): True})
327
 
        return result
328
 
 
329
 
    def repository_text_keys(self):
330
 
        return {('a-file-id', 'rev2'):[NULL_REVISION],
331
 
                ('a-file-id', 'rev3'):[NULL_REVISION]}
332
 
 
333
 
    def versioned_repository_text_keys(self):
334
 
        return {('TREE_ROOT', 'rev2'):[NULL_REVISION],
335
 
                ('TREE_ROOT', 'rev3'):[NULL_REVISION]}
336
 
 
337
 
 
338
 
class FileParentsNotReferencedByAnyInventoryScenario(BrokenRepoScenario):
339
 
    """A scenario where a repository with file 'a-file' which has extra
340
 
    per-file versions that are not referenced by any inventory (even though
341
 
    they have the same ID as actual revisions).  The inventory of 'rev2'
342
 
    references 'rev1a' of 'a-file', but there is a 'rev2' of 'some-file' stored
343
 
    and erroneously referenced by later per-file versions (revisions 'rev4' and
344
 
    'rev5').
345
 
 
346
 
    Reconcile should remove the file parents that are not referenced by any
347
 
    inventory.
348
 
    """
349
 
 
350
 
    def all_versions_after_reconcile(self):
351
 
        return ('rev1a', 'rev2c', 'rev4', 'rev5')
352
 
 
353
 
    def populated_parents(self):
354
 
        return [
355
 
            (('rev1a',), 'rev2'),
356
 
            (('rev1a',), 'rev2b'),
357
 
            (('rev2',), 'rev3'),
358
 
            (('rev2',), 'rev4'),
359
 
            (('rev2', 'rev2c'), 'rev5')]
360
 
 
361
 
    def corrected_parents(self):
362
 
        return (
363
 
            # rev2 and rev2b have been removed.
364
 
            (None, 'rev2'),
365
 
            (None, 'rev2b'),
366
 
            # rev3's accessible parent inventories all have rev1a as the last
367
 
            # modifier.
368
 
            (('rev1a',), 'rev3'),
369
 
            # rev1a features in both rev4's parents but should only appear once
370
 
            # in the result
371
 
            (('rev1a',), 'rev4'),
372
 
            # rev2c is the head of rev1a and rev2c, the inventory provided
373
 
            # per-file last-modified revisions.
374
 
            (('rev2c',), 'rev5'))
375
 
 
376
 
    def check_regexes(self, repo):
377
 
        if repo.supports_rich_root():
378
 
            # TREE_ROOT will be wrong; but we're not testing it. so just adjust
379
 
            # the expected count of errors.
380
 
            count = 9
381
 
        else:
382
 
            count = 3
383
 
        return [
384
 
            # will be gc'd
385
 
            r"unreferenced version: {rev2} in a-file-id",
386
 
            r"unreferenced version: {rev2b} in a-file-id",
387
 
            # will be corrected
388
 
            r"a-file-id version rev3 has parents \('rev2',\) "
389
 
            r"but should have \('rev1a',\)",
390
 
            r"a-file-id version rev5 has parents \('rev2', 'rev2c'\) "
391
 
            r"but should have \('rev2c',\)",
392
 
            r"a-file-id version rev4 has parents \('rev2',\) "
393
 
            r"but should have \('rev1a',\)",
394
 
            "%d inconsistent parents" % count,
395
 
            ]
396
 
 
397
 
    def populate_repository(self, repo):
398
 
        # make rev1a: A well-formed revision, containing 'a-file'
399
 
        inv = self.make_one_file_inventory(
400
 
            repo, 'rev1a', [], root_revision='rev1a')
401
 
        self.add_revision(repo, 'rev1a', inv, [])
402
 
 
403
 
        # make rev2, with a-file.
404
 
        # a-file is unmodified from rev1a, and an unreferenced rev2 file
405
 
        # version is present in the repository.
406
 
        self.make_one_file_inventory(
407
 
            repo, 'rev2', ['rev1a'], inv_revision='rev1a')
408
 
        self.add_revision(repo, 'rev2', inv, ['rev1a'])
409
 
 
410
 
        # make rev3 with a-file
411
 
        # a-file has 'rev2' as its ancestor, but the revision in 'rev2' was
412
 
        # rev1a so this is inconsistent with rev2's inventory - it should
413
 
        # be rev1a, and at the revision level 1c is not present - it is a
414
 
        # ghost, so only the details from rev1a are available for
415
 
        # determining whether a delta is acceptable, or a full is needed,
416
 
        # and what the correct parents are.
417
 
        inv = self.make_one_file_inventory(repo, 'rev3', ['rev2'])
418
 
        self.add_revision(repo, 'rev3', inv, ['rev1c', 'rev1a'])
419
 
 
420
 
        # In rev2b, the true last-modifying-revision of a-file is rev1a,
421
 
        # inherited from rev2, but there is a version rev2b of the file, which
422
 
        # reconcile could remove, leaving no rev2b.  Most importantly,
423
 
        # revisions descending from rev2b should not have per-file parents of
424
 
        # a-file-rev2b.
425
 
        # ??? This is to test deduplication in fixing rev4
426
 
        inv = self.make_one_file_inventory(
427
 
            repo, 'rev2b', ['rev1a'], inv_revision='rev1a')
428
 
        self.add_revision(repo, 'rev2b', inv, ['rev1a'])
429
 
 
430
 
        # rev4 is for testing that when the last modified of a file in
431
 
        # multiple parent revisions is the same, that it only appears once
432
 
        # in the generated per file parents list: rev2 and rev2b both
433
 
        # descend from 1a and do not change the file a-file, so there should
434
 
        # be no version of a-file 'rev2' or 'rev2b', but rev4 does change
435
 
        # a-file, and is a merge of rev2 and rev2b, so it should end up with
436
 
        # a parent of just rev1a - the starting file parents list is simply
437
 
        # completely wrong.
438
 
        inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
439
 
        self.add_revision(repo, 'rev4', inv, ['rev2', 'rev2b'])
440
 
 
441
 
        # rev2c changes a-file from rev1a, so the version it of a-file it
442
 
        # introduces is a head revision when rev5 is checked.
443
 
        inv = self.make_one_file_inventory(repo, 'rev2c', ['rev1a'])
444
 
        self.add_revision(repo, 'rev2c', inv, ['rev1a'])
445
 
 
446
 
        # rev5 descends from rev2 and rev2c; as rev2 does not alter a-file,
447
 
        # but rev2c does, this should use rev2c as the parent for the per
448
 
        # file history, even though more than one per-file parent is
449
 
        # available, because we use the heads of the revision parents for
450
 
        # the inventory modification revisions of the file to determine the
451
 
        # parents for the per file graph.
452
 
        inv = self.make_one_file_inventory(repo, 'rev5', ['rev2', 'rev2c'])
453
 
        self.add_revision(repo, 'rev5', inv, ['rev2', 'rev2c'])
454
 
        self.versioned_root = repo.supports_rich_root()
455
 
 
456
 
    def repository_text_key_references(self):
457
 
        result = {}
458
 
        if self.versioned_root:
459
 
            result.update({('TREE_ROOT', 'rev1a'): True,
460
 
                           ('TREE_ROOT', 'rev2'): True,
461
 
                           ('TREE_ROOT', 'rev2b'): True,
462
 
                           ('TREE_ROOT', 'rev2c'): True,
463
 
                           ('TREE_ROOT', 'rev3'): True,
464
 
                           ('TREE_ROOT', 'rev4'): True,
465
 
                           ('TREE_ROOT', 'rev5'): True})
466
 
        result.update({('a-file-id', 'rev1a'): True,
467
 
                       ('a-file-id', 'rev2c'): True,
468
 
                       ('a-file-id', 'rev3'): True,
469
 
                       ('a-file-id', 'rev4'): True,
470
 
                       ('a-file-id', 'rev5'): True})
471
 
        return result
472
 
 
473
 
    def repository_text_keys(self):
474
 
        return {('a-file-id', 'rev1a'): [NULL_REVISION],
475
 
                 ('a-file-id', 'rev2c'): [('a-file-id', 'rev1a')],
476
 
                 ('a-file-id', 'rev3'): [('a-file-id', 'rev1a')],
477
 
                 ('a-file-id', 'rev4'): [('a-file-id', 'rev1a')],
478
 
                 ('a-file-id', 'rev5'): [('a-file-id', 'rev2c')]}
479
 
 
480
 
    def versioned_repository_text_keys(self):
481
 
        return {('TREE_ROOT', 'rev1a'): [NULL_REVISION],
482
 
                ('TREE_ROOT', 'rev2'): [('TREE_ROOT', 'rev1a')],
483
 
                ('TREE_ROOT', 'rev2b'): [('TREE_ROOT', 'rev1a')],
484
 
                ('TREE_ROOT', 'rev2c'): [('TREE_ROOT', 'rev1a')],
485
 
                ('TREE_ROOT', 'rev3'): [('TREE_ROOT', 'rev1a')],
486
 
                ('TREE_ROOT', 'rev4'):
487
 
                    [('TREE_ROOT', 'rev2'), ('TREE_ROOT', 'rev2b')],
488
 
                ('TREE_ROOT', 'rev5'):
489
 
                    [('TREE_ROOT', 'rev2'), ('TREE_ROOT', 'rev2c')]}
490
 
 
491
 
 
492
 
class UnreferencedFileParentsFromNoOpMergeScenario(BrokenRepoScenario):
493
 
    """
494
 
    rev1a and rev1b with identical contents
495
 
    rev2 revision has parents of [rev1a, rev1b]
496
 
    There is a a-file:rev2 file version, not referenced by the inventory.
497
 
    """
498
 
 
499
 
    def all_versions_after_reconcile(self):
500
 
        return ('rev1a', 'rev1b', 'rev2', 'rev4')
501
 
 
502
 
    def populated_parents(self):
503
 
        return (
504
 
            ((), 'rev1a'),
505
 
            ((), 'rev1b'),
506
 
            (('rev1a', 'rev1b'), 'rev2'),
507
 
            (None, 'rev3'),
508
 
            (('rev2',), 'rev4'),
509
 
            )
510
 
 
511
 
    def corrected_parents(self):
512
 
        return (
513
 
            ((), 'rev1a'),
514
 
            ((), 'rev1b'),
515
 
            ((), 'rev2'),
516
 
            (None, 'rev3'),
517
 
            (('rev2',), 'rev4'),
518
 
            )
519
 
 
520
 
    def corrected_fulltexts(self):
521
 
        return ['rev2']
522
 
 
523
 
    def check_regexes(self, repo):
524
 
        return []
525
 
 
526
 
    def populate_repository(self, repo):
527
 
        # make rev1a: A well-formed revision, containing 'a-file'
528
 
        inv1a = self.make_one_file_inventory(
529
 
            repo, 'rev1a', [], root_revision='rev1a')
530
 
        self.add_revision(repo, 'rev1a', inv1a, [])
531
 
 
532
 
        # make rev1b: A well-formed revision, containing 'a-file'
533
 
        # rev1b of a-file has the exact same contents as rev1a.
534
 
        file_contents = repo.revision_tree('rev1a').get_file_text('a-file-id')
535
 
        inv = self.make_one_file_inventory(
536
 
            repo, 'rev1b', [], root_revision='rev1b',
537
 
            file_contents=file_contents)
538
 
        self.add_revision(repo, 'rev1b', inv, [])
539
 
 
540
 
        # make rev2, a merge of rev1a and rev1b, with a-file.
541
 
        # a-file is unmodified from rev1a and rev1b, but a new version is
542
 
        # wrongly present anyway.
543
 
        inv = self.make_one_file_inventory(
544
 
            repo, 'rev2', ['rev1a', 'rev1b'], inv_revision='rev1a',
545
 
            file_contents=file_contents)
546
 
        self.add_revision(repo, 'rev2', inv, ['rev1a', 'rev1b'])
547
 
 
548
 
        # rev3: a-file unchanged from rev2, but wrongly referencing rev2 of the
549
 
        # file in its inventory.
550
 
        inv = self.make_one_file_inventory(
551
 
            repo, 'rev3', ['rev2'], inv_revision='rev2',
552
 
            file_contents=file_contents, make_file_version=False)
553
 
        self.add_revision(repo, 'rev3', inv, ['rev2'])
554
 
 
555
 
        # rev4: a modification of a-file on top of rev3.
556
 
        inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
557
 
        self.add_revision(repo, 'rev4', inv, ['rev3'])
558
 
        self.versioned_root = repo.supports_rich_root()
559
 
 
560
 
    def repository_text_key_references(self):
561
 
        result = {}
562
 
        if self.versioned_root:
563
 
            result.update({('TREE_ROOT', 'rev1a'): True,
564
 
                           ('TREE_ROOT', 'rev1b'): True,
565
 
                           ('TREE_ROOT', 'rev2'): True,
566
 
                           ('TREE_ROOT', 'rev3'): True,
567
 
                           ('TREE_ROOT', 'rev4'): True})
568
 
        result.update({('a-file-id', 'rev1a'): True,
569
 
                       ('a-file-id', 'rev1b'): True,
570
 
                       ('a-file-id', 'rev2'): False,
571
 
                       ('a-file-id', 'rev4'): True})
572
 
        return result
573
 
 
574
 
    def repository_text_keys(self):
575
 
        return {('a-file-id', 'rev1a'): [NULL_REVISION],
576
 
                ('a-file-id', 'rev1b'): [NULL_REVISION],
577
 
                ('a-file-id', 'rev2'): [NULL_REVISION],
578
 
                ('a-file-id', 'rev4'): [('a-file-id', 'rev2')]}
579
 
 
580
 
    def versioned_repository_text_keys(self):
581
 
        return {('TREE_ROOT', 'rev1a'): [NULL_REVISION],
582
 
                ('TREE_ROOT', 'rev1b'): [NULL_REVISION],
583
 
                ('TREE_ROOT', 'rev2'):
584
 
                    [('TREE_ROOT', 'rev1a'), ('TREE_ROOT', 'rev1b')],
585
 
                ('TREE_ROOT', 'rev3'): [('TREE_ROOT', 'rev2')],
586
 
                ('TREE_ROOT', 'rev4'): [('TREE_ROOT', 'rev3')]}
587
 
 
588
 
 
589
 
class TooManyParentsScenario(BrokenRepoScenario):
590
 
    """A scenario where 'broken-revision' of 'a-file' claims to have parents
591
 
    ['good-parent', 'bad-parent'].  However 'bad-parent' is in the ancestry of
592
 
    'good-parent', so the correct parent list for that file version are is just
593
 
    ['good-parent'].
594
 
    """
595
 
 
596
 
    def all_versions_after_reconcile(self):
597
 
        return ('bad-parent', 'good-parent', 'broken-revision')
598
 
 
599
 
    def populated_parents(self):
600
 
        return (
601
 
            ((), 'bad-parent'),
602
 
            (('bad-parent',), 'good-parent'),
603
 
            (('good-parent', 'bad-parent'), 'broken-revision'))
604
 
 
605
 
    def corrected_parents(self):
606
 
        return (
607
 
            ((), 'bad-parent'),
608
 
            (('bad-parent',), 'good-parent'),
609
 
            (('good-parent',), 'broken-revision'))
610
 
 
611
 
    def check_regexes(self, repo):
612
 
        if repo.supports_rich_root():
613
 
            # TREE_ROOT will be wrong; but we're not testing it. so just adjust
614
 
            # the expected count of errors.
615
 
            count = 3
616
 
        else:
617
 
            count = 1
618
 
        return (
619
 
            '     %d inconsistent parents' % count,
620
 
            (r"      \* a-file-id version broken-revision has parents "
621
 
             r"\('good-parent', 'bad-parent'\) but "
622
 
             r"should have \('good-parent',\)"))
623
 
 
624
 
    def populate_repository(self, repo):
625
 
        inv = self.make_one_file_inventory(
626
 
            repo, 'bad-parent', (), root_revision='bad-parent')
627
 
        self.add_revision(repo, 'bad-parent', inv, ())
628
 
 
629
 
        inv = self.make_one_file_inventory(
630
 
            repo, 'good-parent', ('bad-parent',))
631
 
        self.add_revision(repo, 'good-parent', inv, ('bad-parent',))
632
 
 
633
 
        inv = self.make_one_file_inventory(
634
 
            repo, 'broken-revision', ('good-parent', 'bad-parent'))
635
 
        self.add_revision(repo, 'broken-revision', inv, ('good-parent',))
636
 
        self.versioned_root = repo.supports_rich_root()
637
 
 
638
 
    def repository_text_key_references(self):
639
 
        result = {}
640
 
        if self.versioned_root:
641
 
            result.update({('TREE_ROOT', 'bad-parent'): True,
642
 
                           ('TREE_ROOT', 'broken-revision'): True,
643
 
                           ('TREE_ROOT', 'good-parent'): True})
644
 
        result.update({('a-file-id', 'bad-parent'): True,
645
 
                       ('a-file-id', 'broken-revision'): True,
646
 
                       ('a-file-id', 'good-parent'): True})
647
 
        return result
648
 
 
649
 
    def repository_text_keys(self):
650
 
        return {('a-file-id', 'bad-parent'): [NULL_REVISION],
651
 
                ('a-file-id', 'broken-revision'):
652
 
                    [('a-file-id', 'good-parent')],
653
 
                ('a-file-id', 'good-parent'): [('a-file-id', 'bad-parent')]}
654
 
 
655
 
    def versioned_repository_text_keys(self):
656
 
        return {('TREE_ROOT', 'bad-parent'): [NULL_REVISION],
657
 
                ('TREE_ROOT', 'broken-revision'):
658
 
                    [('TREE_ROOT', 'good-parent')],
659
 
                ('TREE_ROOT', 'good-parent'): [('TREE_ROOT', 'bad-parent')]}
660
 
 
661
 
 
662
 
class ClaimedFileParentDidNotModifyFileScenario(BrokenRepoScenario):
663
 
    """A scenario where the file parent is the same as the revision parent, but
664
 
    should not be because that revision did not modify the file.
665
 
 
666
 
    Specifically, the parent revision of 'current' is
667
 
    'modified-something-else', which does not modify 'a-file', but the
668
 
    'current' version of 'a-file' erroneously claims that
669
 
    'modified-something-else' is the parent file version.
670
 
    """
671
 
 
672
 
    def all_versions_after_reconcile(self):
673
 
        return ('basis', 'current')
674
 
 
675
 
    def populated_parents(self):
676
 
        return (
677
 
            ((), 'basis'),
678
 
            (('basis',), 'modified-something-else'),
679
 
            (('modified-something-else',), 'current'))
680
 
 
681
 
    def corrected_parents(self):
682
 
        return (
683
 
            ((), 'basis'),
684
 
            (None, 'modified-something-else'),
685
 
            (('basis',), 'current'))
686
 
 
687
 
    def check_regexes(self, repo):
688
 
        if repo.supports_rich_root():
689
 
            # TREE_ROOT will be wrong; but we're not testing it. so just adjust
690
 
            # the expected count of errors.
691
 
            count = 3
692
 
        else:
693
 
            count = 1
694
 
        return (
695
 
            "%d inconsistent parents" % count,
696
 
            r"\* a-file-id version current has parents "
697
 
            r"\('modified-something-else',\) but should have \('basis',\)",
698
 
            )
699
 
 
700
 
    def populate_repository(self, repo):
701
 
        inv = self.make_one_file_inventory(repo, 'basis', ())
702
 
        self.add_revision(repo, 'basis', inv, ())
703
 
 
704
 
        # 'modified-something-else' is a correctly recorded revision, but it
705
 
        # does not modify the file we are looking at, so the inventory for that
706
 
        # file in this revision points to 'basis'.
707
 
        inv = self.make_one_file_inventory(
708
 
            repo, 'modified-something-else', ('basis',), inv_revision='basis')
709
 
        self.add_revision(repo, 'modified-something-else', inv, ('basis',))
710
 
 
711
 
        # The 'current' revision has 'modified-something-else' as its parent,
712
 
        # but the 'current' version of 'a-file' should have 'basis' as its
713
 
        # parent.
714
 
        inv = self.make_one_file_inventory(
715
 
            repo, 'current', ('modified-something-else',))
716
 
        self.add_revision(repo, 'current', inv, ('modified-something-else',))
717
 
        self.versioned_root = repo.supports_rich_root()
718
 
 
719
 
    def repository_text_key_references(self):
720
 
        result = {}
721
 
        if self.versioned_root:
722
 
            result.update({('TREE_ROOT', 'basis'): True,
723
 
                           ('TREE_ROOT', 'current'): True,
724
 
                           ('TREE_ROOT', 'modified-something-else'): True})
725
 
        result.update({('a-file-id', 'basis'): True,
726
 
                       ('a-file-id', 'current'): True})
727
 
        return result
728
 
 
729
 
    def repository_text_keys(self):
730
 
        return {('a-file-id', 'basis'): [NULL_REVISION],
731
 
                ('a-file-id', 'current'): [('a-file-id', 'basis')]}
732
 
 
733
 
    def versioned_repository_text_keys(self):
734
 
        return {('TREE_ROOT', 'basis'): ['null:'],
735
 
                ('TREE_ROOT', 'current'):
736
 
                    [('TREE_ROOT', 'modified-something-else')],
737
 
                ('TREE_ROOT', 'modified-something-else'):
738
 
                    [('TREE_ROOT', 'basis')]}
739
 
 
740
 
 
741
 
class IncorrectlyOrderedParentsScenario(BrokenRepoScenario):
742
 
    """A scenario where the set parents of a version of a file are correct, but
743
 
    the order of those parents is incorrect.
744
 
 
745
 
    This defines a 'broken-revision-1-2' and a 'broken-revision-2-1' which both
746
 
    have their file version parents reversed compared to the revision parents,
747
 
    which is invalid.  (We use two revisions with opposite orderings of the
748
 
    same parents to make sure that accidentally relying on dictionary/set
749
 
    ordering cannot make the test pass; the assumption is that while dict/set
750
 
    iteration order is arbitrary, it is also consistent within a single test).
751
 
    """
752
 
 
753
 
    def all_versions_after_reconcile(self):
754
 
        return ['parent-1', 'parent-2', 'broken-revision-1-2',
755
 
                'broken-revision-2-1']
756
 
 
757
 
    def populated_parents(self):
758
 
        return (
759
 
            ((), 'parent-1'),
760
 
            ((), 'parent-2'),
761
 
            (('parent-2', 'parent-1'), 'broken-revision-1-2'),
762
 
            (('parent-1', 'parent-2'), 'broken-revision-2-1'))
763
 
 
764
 
    def corrected_parents(self):
765
 
        return (
766
 
            ((), 'parent-1'),
767
 
            ((), 'parent-2'),
768
 
            (('parent-1', 'parent-2'), 'broken-revision-1-2'),
769
 
            (('parent-2', 'parent-1'), 'broken-revision-2-1'))
770
 
 
771
 
    def check_regexes(self, repo):
772
 
        if repo.supports_rich_root():
773
 
            # TREE_ROOT will be wrong; but we're not testing it. so just adjust
774
 
            # the expected count of errors.
775
 
            count = 4
776
 
        else:
777
 
            count = 2
778
 
        return (
779
 
            "%d inconsistent parents" % count,
780
 
            r"\* a-file-id version broken-revision-1-2 has parents "
781
 
            r"\('parent-2', 'parent-1'\) but should have "
782
 
            r"\('parent-1', 'parent-2'\)",
783
 
            r"\* a-file-id version broken-revision-2-1 has parents "
784
 
            r"\('parent-1', 'parent-2'\) but should have "
785
 
            r"\('parent-2', 'parent-1'\)")
786
 
 
787
 
    def populate_repository(self, repo):
788
 
        inv = self.make_one_file_inventory(repo, 'parent-1', [])
789
 
        self.add_revision(repo, 'parent-1', inv, [])
790
 
 
791
 
        inv = self.make_one_file_inventory(repo, 'parent-2', [])
792
 
        self.add_revision(repo, 'parent-2', inv, [])
793
 
 
794
 
        inv = self.make_one_file_inventory(
795
 
            repo, 'broken-revision-1-2', ['parent-2', 'parent-1'])
796
 
        self.add_revision(
797
 
            repo, 'broken-revision-1-2', inv, ['parent-1', 'parent-2'])
798
 
 
799
 
        inv = self.make_one_file_inventory(
800
 
            repo, 'broken-revision-2-1', ['parent-1', 'parent-2'])
801
 
        self.add_revision(
802
 
            repo, 'broken-revision-2-1', inv, ['parent-2', 'parent-1'])
803
 
        self.versioned_root = repo.supports_rich_root()
804
 
 
805
 
    def repository_text_key_references(self):
806
 
        result = {}
807
 
        if self.versioned_root:
808
 
            result.update({('TREE_ROOT', 'broken-revision-1-2'): True,
809
 
                           ('TREE_ROOT', 'broken-revision-2-1'): True,
810
 
                           ('TREE_ROOT', 'parent-1'): True,
811
 
                           ('TREE_ROOT', 'parent-2'): True})
812
 
        result.update({('a-file-id', 'broken-revision-1-2'): True,
813
 
                       ('a-file-id', 'broken-revision-2-1'): True,
814
 
                       ('a-file-id', 'parent-1'): True,
815
 
                       ('a-file-id', 'parent-2'): True})
816
 
        return result
817
 
 
818
 
    def repository_text_keys(self):
819
 
        return {('a-file-id', 'broken-revision-1-2'):
820
 
                    [('a-file-id', 'parent-1'), ('a-file-id', 'parent-2')],
821
 
                ('a-file-id', 'broken-revision-2-1'):
822
 
                    [('a-file-id', 'parent-2'), ('a-file-id', 'parent-1')],
823
 
                ('a-file-id', 'parent-1'): [NULL_REVISION],
824
 
                ('a-file-id', 'parent-2'): [NULL_REVISION]}
825
 
 
826
 
    def versioned_repository_text_keys(self):
827
 
        return {('TREE_ROOT', 'broken-revision-1-2'):
828
 
                    [('TREE_ROOT', 'parent-1'), ('TREE_ROOT', 'parent-2')],
829
 
                ('TREE_ROOT', 'broken-revision-2-1'):
830
 
                    [('TREE_ROOT', 'parent-2'), ('TREE_ROOT', 'parent-1')],
831
 
                ('TREE_ROOT', 'parent-1'): [NULL_REVISION],
832
 
                ('TREE_ROOT', 'parent-2'): [NULL_REVISION]}
833
 
 
834
 
 
835
 
all_broken_scenario_classes = [
836
 
    UndamagedRepositoryScenario,
837
 
    FileParentIsNotInRevisionAncestryScenario,
838
 
    FileParentHasInaccessibleInventoryScenario,
839
 
    FileParentsNotReferencedByAnyInventoryScenario,
840
 
    TooManyParentsScenario,
841
 
    ClaimedFileParentDidNotModifyFileScenario,
842
 
    IncorrectlyOrderedParentsScenario,
843
 
    UnreferencedFileParentsFromNoOpMergeScenario,
844
 
    ]
 
108
        return repo
845
109
 
846
110
 
847
111
def load_tests(standard_tests, module, loader):
848
112
    prefix = 'bzrlib.tests.per_repository.'
849
113
    test_repository_modules = [
850
114
        'test_add_fallback_repository',
851
 
        'test_add_inventory_by_delta',
852
115
        'test_break_lock',
853
116
        'test_check',
854
 
        # test_check_reconcile is intentionally omitted, see below.
855
117
        'test_commit_builder',
856
118
        'test_fetch',
857
 
        'test_fileid_involved',
858
 
        'test_find_text_key_references',
859
 
        'test__generate_text_key_index',
 
119
        'test_file_graph',
860
120
        'test_get_parent_map',
861
121
        'test_has_same_location',
862
122
        'test_has_revisions',
863
 
        'test_is_write_locked',
864
 
        'test_iter_reverse_revision_history',
865
 
        'test_merge_directive',
 
123
        'test_locking',
866
124
        'test_pack',
867
125
        'test_reconcile',
868
126
        'test_refresh_data',
869
127
        'test_repository',
870
128
        'test_revision',
 
129
        'test_signatures',
871
130
        'test_statistics',
872
131
        'test_write_group',
873
132
        ]
875
134
    submod_tests = loader.loadTestsFromModuleNames(
876
135
        [prefix + module_name for module_name in test_repository_modules])
877
136
    format_scenarios = all_repository_format_scenarios()
878
 
    multiply_tests(submod_tests, format_scenarios, standard_tests)
879
 
 
880
 
    # test_check_reconcile needs to be parameterized by format *and* by broken
881
 
    # repository scenario.
882
 
    broken_scenarios = [(s.__name__, {'scenario_class': s})
883
 
                        for s in all_broken_scenario_classes]
884
 
    broken_scenarios_for_all_formats = multiply_scenarios(
885
 
        format_scenarios, broken_scenarios)
886
 
    return multiply_tests(
887
 
        loader.loadTestsFromModuleNames([prefix + 'test_check_reconcile']),
888
 
        broken_scenarios_for_all_formats, standard_tests)
 
137
    return multiply_tests(submod_tests, format_scenarios, standard_tests)