/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

First attempt to merge .dev and resolve the conflicts (but tests are 
failing)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
2
2
# Authors: Robert Collins <robert.collins@canonical.com>
3
 
# -*- coding: utf-8 -*-
 
3
#          and others
4
4
#
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
24
24
rather than in tests/branch_implementations/*.py.
25
25
"""
26
26
 
27
 
from bzrlib.repository import (_legacy_formats,
28
 
                               RepositoryFormat,
29
 
                               RepositoryTestProviderAdapter,
30
 
                               )
31
 
                            
 
27
from bzrlib import (
 
28
    repository,
 
29
    )
 
30
from bzrlib.revision import NULL_REVISION
 
31
from bzrlib.repofmt import (
 
32
    weaverepo,
 
33
    )
 
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,
 
40
    )
32
41
from bzrlib.tests import (
33
42
                          adapt_modules,
34
43
                          default_transport,
35
 
                          TestLoader,
36
 
                          TestSuite,
 
44
                          iter_suite_tests,
 
45
                          multiply_scenarios,
 
46
                          multiply_tests_from_modules,
 
47
                          TestScenarioApplier,
37
48
                          )
38
 
 
39
 
 
40
 
def test_suite():
41
 
    result = TestSuite()
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',
49
 
        ]
50
 
    adapter = RepositoryTestProviderAdapter(
 
49
from bzrlib.tests.bzrdir_implementations.test_bzrdir import TestCaseWithBzrDir
 
50
from bzrlib.transport.memory import MemoryServer
 
51
 
 
52
 
 
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.
 
56
 
 
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 
 
60
        instance.
 
61
    :returns: Scenarios of [(scenario_name, {parameter_name: value})]
 
62
    """
 
63
    result = []
 
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,
 
72
             })
 
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)
 
78
    return result
 
79
 
 
80
 
 
81
def all_repository_format_scenarios():
 
82
    """Return a list of test scenarios for parameterising repository tests.
 
83
    """
 
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],
51
91
        default_transport,
52
92
        # None here will cause a readonly decorator to be created
53
93
        # by the TestCaseWithTransport.get_readonly_transport method.
54
 
        None,
55
 
        [(format, format._matchingbzrdir) for format in 
56
 
         RepositoryFormat._formats.values() + _legacy_formats])
57
 
    loader = TestLoader()
58
 
    adapt_modules(test_repository_implementations, adapter, loader, result)
 
94
        None)
 
95
    format_scenarios.extend(formats_to_scenarios(
 
96
        [('-default', RemoteRepositoryFormat())],
 
97
        SmartTCPServer_for_testing,
 
98
        ReadonlySmartTCPServer_for_testing,
 
99
        MemoryServer))
 
100
    format_scenarios.extend(formats_to_scenarios(
 
101
        [('-v2', RemoteRepositoryFormat())],
 
102
        SmartTCPServer_for_testing_v2_only,
 
103
        ReadonlySmartTCPServer_for_testing_v2_only,
 
104
        MemoryServer))
 
105
    return format_scenarios
 
106
 
 
107
 
 
108
class TestCaseWithRepository(TestCaseWithBzrDir):
 
109
 
 
110
    def make_repository(self, relpath, format=None):
 
111
        if format is 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)
 
117
            return repo
 
118
        else:
 
119
            return super(TestCaseWithRepository, self).make_repository(
 
120
                relpath, format=format)
 
121
 
 
122
 
 
123
class BrokenRepoScenario(object):
 
124
    """Base class for defining scenarios for testing check and reconcile.
 
125
 
 
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.
 
140
    
 
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
 
144
            this occurs.
 
145
    """
 
146
 
 
147
    def __init__(self, test_case):
 
148
        self.test_case = test_case
 
149
 
 
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)
 
157
 
 
158
    def add_revision(self, repo, revision_id, inv, parent_ids):
 
159
        return self.test_case.add_revision(repo, revision_id, inv, parent_ids)
 
160
 
 
161
    def corrected_fulltexts(self):
 
162
        return []
 
163
 
 
164
    def repository_text_key_index(self):
 
165
        result = {}
 
166
        if self.versioned_root:
 
167
            result.update(self.versioned_repository_text_keys())
 
168
        result.update(self.repository_text_keys())
 
169
        return result
 
170
 
 
171
 
 
172
class UndamagedRepositoryScenario(BrokenRepoScenario):
 
173
    """A scenario where the repository has no damage.
 
174
 
 
175
    It has a single revision, 'rev1a', with a single file.
 
176
    """
 
177
 
 
178
    def all_versions_after_reconcile(self):
 
179
        return ('rev1a', )
 
180
 
 
181
    def populated_parents(self):
 
182
        return (((), 'rev1a'), )
 
183
 
 
184
    def corrected_parents(self):
 
185
        # Same as the populated parents, because there was nothing wrong.
 
186
        return self.populated_parents()
 
187
 
 
188
    def check_regexes(self, repo):
 
189
        return ["0 unreferenced text versions"]
 
190
 
 
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()
 
197
 
 
198
    def repository_text_key_references(self):
 
199
        result = {}
 
200
        if self.versioned_root:
 
201
            result.update({('TREE_ROOT', 'rev1a'): True})
 
202
        result.update({('a-file-id', 'rev1a'): True})
 
203
        return result
 
204
 
 
205
    def repository_text_keys(self):
 
206
        return {('a-file-id', 'rev1a'):[NULL_REVISION]}
 
207
 
 
208
    def versioned_repository_text_keys(self):
 
209
        return {('TREE_ROOT', 'rev1a'):[NULL_REVISION]}
 
210
 
 
211
 
 
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.
 
215
    
 
216
    Reconcile should remove 'rev1b' from the parents list of 'a-file' in
 
217
    'rev2', preserving 'rev1a' as a parent.
 
218
    """
 
219
 
 
220
    def all_versions_after_reconcile(self):
 
221
        return ('rev1a', 'rev2')
 
222
 
 
223
    def populated_parents(self):
 
224
        return (
 
225
            ((), 'rev1a'),
 
226
            ((), 'rev1b'), # Will be gc'd
 
227
            (('rev1a', 'rev1b'), 'rev2')) # Will have parents trimmed
 
228
 
 
229
    def corrected_parents(self):
 
230
        return (
 
231
            ((), 'rev1a'),
 
232
            (None, 'rev1b'),
 
233
            (('rev1a',), 'rev2'))
 
234
 
 
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",
 
239
                ]
 
240
 
 
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, [])
 
246
 
 
247
        # make rev1b, which has no Revision, but has an Inventory, and
 
248
        # a-file
 
249
        inv = self.make_one_file_inventory(
 
250
            repo, 'rev1b', [], root_revision='rev1b')
 
251
        repo.add_inventory('rev1b', inv, [])
 
252
 
 
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()
 
260
 
 
261
    def repository_text_key_references(self):
 
262
        result = {}
 
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})
 
268
        return result
 
269
 
 
270
    def repository_text_keys(self):
 
271
        return {('a-file-id', 'rev1a'):[NULL_REVISION],
 
272
                ('a-file-id', 'rev2'):[('a-file-id', 'rev1a')]}
 
273
 
 
274
    def versioned_repository_text_keys(self):
 
275
        return {('TREE_ROOT', 'rev1a'):[NULL_REVISION],
 
276
                ('TREE_ROOT', 'rev2'):[('TREE_ROOT', 'rev1a')]}
 
277
 
 
278
 
 
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.
 
283
 
 
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).
 
286
    """
 
287
 
 
288
    def all_versions_after_reconcile(self):
 
289
        return ('rev2', 'rev3')
 
290
 
 
291
    def populated_parents(self):
 
292
        return (
 
293
            ((), 'rev2'),
 
294
            (('rev1c',), 'rev3'))
 
295
 
 
296
    def corrected_parents(self):
 
297
        return (
 
298
            ((), 'rev2'),
 
299
            ((), 'rev3'))
 
300
 
 
301
    def check_regexes(self, repo):
 
302
        return [r"\* a-file-id version rev3 has parents "
 
303
                r"\('rev1c',\) but should have \(\)",
 
304
                ]
 
305
 
 
306
    def populate_repository(self, repo):
 
307
        # make rev2, with a-file
 
308
        # a-file is sane
 
309
        inv = self.make_one_file_inventory(repo, 'rev2', [])
 
310
        self.add_revision(repo, 'rev2', inv, [])
 
311
 
 
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', [])
 
319
 
 
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()
 
326
 
 
327
    def repository_text_key_references(self):
 
328
        result = {}
 
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})
 
334
        return result
 
335
 
 
336
    def repository_text_keys(self):
 
337
        return {('a-file-id', 'rev2'):[NULL_REVISION],
 
338
                ('a-file-id', 'rev3'):[NULL_REVISION]}
 
339
 
 
340
    def versioned_repository_text_keys(self):
 
341
        return {('TREE_ROOT', 'rev2'):[NULL_REVISION],
 
342
                ('TREE_ROOT', 'rev3'):[NULL_REVISION]}
 
343
 
 
344
 
 
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
 
351
    'rev5').
 
352
 
 
353
    Reconcile should remove the file parents that are not referenced by any
 
354
    inventory.
 
355
    """
 
356
 
 
357
    def all_versions_after_reconcile(self):
 
358
        return ('rev1a', 'rev2c', 'rev4', 'rev5')
 
359
 
 
360
    def populated_parents(self):
 
361
        return [
 
362
            (('rev1a',), 'rev2'),
 
363
            (('rev1a',), 'rev2b'),
 
364
            (('rev2',), 'rev3'),
 
365
            (('rev2',), 'rev4'),
 
366
            (('rev2', 'rev2c'), 'rev5')]
 
367
 
 
368
    def corrected_parents(self):
 
369
        return (
 
370
            # rev2 and rev2b have been removed.
 
371
            (None, 'rev2'),
 
372
            (None, 'rev2b'),
 
373
            # rev3's accessible parent inventories all have rev1a as the last
 
374
            # modifier.
 
375
            (('rev1a',), 'rev3'),
 
376
            # rev1a features in both rev4's parents but should only appear once
 
377
            # in the result
 
378
            (('rev1a',), 'rev4'),
 
379
            # rev2c is the head of rev1a and rev2c, the inventory provided
 
380
            # per-file last-modified revisions.
 
381
            (('rev2c',), 'rev5'))
 
382
 
 
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.
 
387
            count = 9
 
388
        else:
 
389
            count = 3
 
390
        return [
 
391
            # will be gc'd
 
392
            r"unreferenced version: {rev2} in a-file-id",
 
393
            r"unreferenced version: {rev2b} in a-file-id",
 
394
            # will be corrected
 
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,
 
402
            ]
 
403
 
 
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, [])
 
409
 
 
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'])
 
416
 
 
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'])
 
426
 
 
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
 
431
        # a-file-rev2b.
 
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'])
 
436
 
 
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
 
444
        # completely wrong.
 
445
        inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
 
446
        self.add_revision(repo, 'rev4', inv, ['rev2', 'rev2b'])
 
447
 
 
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'])
 
452
 
 
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()
 
462
 
 
463
    def repository_text_key_references(self):
 
464
        result = {}
 
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})
 
478
        return result
 
479
 
 
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')]}
 
486
 
 
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')]}
 
497
 
 
498
 
 
499
class UnreferencedFileParentsFromNoOpMergeScenario(BrokenRepoScenario):
 
500
    """
 
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.
 
504
    """
 
505
 
 
506
    def all_versions_after_reconcile(self):
 
507
        return ('rev1a', 'rev1b', 'rev2', 'rev4')
 
508
 
 
509
    def populated_parents(self):
 
510
        return (
 
511
            ((), 'rev1a'),
 
512
            ((), 'rev1b'),
 
513
            (('rev1a', 'rev1b'), 'rev2'),
 
514
            (None, 'rev3'),
 
515
            (('rev2',), 'rev4'),
 
516
            )
 
517
 
 
518
    def corrected_parents(self):
 
519
        return (
 
520
            ((), 'rev1a'),
 
521
            ((), 'rev1b'),
 
522
            ((), 'rev2'),
 
523
            (None, 'rev3'),
 
524
            (('rev2',), 'rev4'),
 
525
            )
 
526
 
 
527
    def corrected_fulltexts(self):
 
528
        return ['rev2']
 
529
 
 
530
    def check_regexes(self, repo):
 
531
        return []
 
532
 
 
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, [])
 
538
 
 
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, [])
 
546
 
 
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'])
 
554
 
 
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'])
 
561
 
 
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()
 
566
 
 
567
    def repository_text_key_references(self):
 
568
        result = {}
 
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})
 
579
        return result
 
580
 
 
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')]}
 
586
 
 
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')]}
 
594
 
 
595
 
 
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
 
600
    ['good-parent'].
 
601
    """
 
602
 
 
603
    def all_versions_after_reconcile(self):
 
604
        return ('bad-parent', 'good-parent', 'broken-revision')
 
605
 
 
606
    def populated_parents(self):
 
607
        return (
 
608
            ((), 'bad-parent'),
 
609
            (('bad-parent',), 'good-parent'),
 
610
            (('good-parent', 'bad-parent'), 'broken-revision'))
 
611
 
 
612
    def corrected_parents(self):
 
613
        return (
 
614
            ((), 'bad-parent'),
 
615
            (('bad-parent',), 'good-parent'),
 
616
            (('good-parent',), 'broken-revision'))
 
617
 
 
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.
 
622
            count = 3
 
623
        else:
 
624
            count = 1
 
625
        return (
 
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',\)"))
 
630
 
 
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, ())
 
635
        
 
636
        inv = self.make_one_file_inventory(
 
637
            repo, 'good-parent', ('bad-parent',))
 
638
        self.add_revision(repo, 'good-parent', inv, ('bad-parent',))
 
639
        
 
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()
 
644
 
 
645
    def repository_text_key_references(self):
 
646
        result = {}
 
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})
 
654
        return result
 
655
             
 
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')]}
 
661
 
 
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')]}
 
667
 
 
668
 
 
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.
 
672
 
 
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.
 
677
    """
 
678
 
 
679
    def all_versions_after_reconcile(self):
 
680
        return ('basis', 'current')
 
681
 
 
682
    def populated_parents(self):
 
683
        return (
 
684
            ((), 'basis'),
 
685
            (('basis',), 'modified-something-else'),
 
686
            (('modified-something-else',), 'current'))
 
687
 
 
688
    def corrected_parents(self):
 
689
        return (
 
690
            ((), 'basis'),
 
691
            (None, 'modified-something-else'),
 
692
            (('basis',), 'current'))
 
693
 
 
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.
 
698
            count = 3
 
699
        else:
 
700
            count = 1
 
701
        return (
 
702
            "%d inconsistent parents" % count,
 
703
            r"\* a-file-id version current has parents "
 
704
            r"\('modified-something-else',\) but should have \('basis',\)",
 
705
            )
 
706
 
 
707
    def populate_repository(self, repo):
 
708
        inv = self.make_one_file_inventory(repo, 'basis', ())
 
709
        self.add_revision(repo, 'basis', inv, ())
 
710
 
 
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',))
 
717
 
 
718
        # The 'current' revision has 'modified-something-else' as its parent,
 
719
        # but the 'current' version of 'a-file' should have 'basis' as its
 
720
        # parent.
 
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()
 
725
 
 
726
    def repository_text_key_references(self):
 
727
        result = {}
 
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})
 
734
        return result
 
735
 
 
736
    def repository_text_keys(self):
 
737
        return {('a-file-id', 'basis'): [NULL_REVISION],
 
738
                ('a-file-id', 'current'): [('a-file-id', 'basis')]}
 
739
 
 
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')]}
 
746
            
 
747
 
 
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.
 
751
 
 
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).
 
758
    """
 
759
 
 
760
    def all_versions_after_reconcile(self):
 
761
        return ['parent-1', 'parent-2', 'broken-revision-1-2',
 
762
                'broken-revision-2-1']
 
763
 
 
764
    def populated_parents(self):
 
765
        return (
 
766
            ((), 'parent-1'),
 
767
            ((), 'parent-2'),
 
768
            (('parent-2', 'parent-1'), 'broken-revision-1-2'),
 
769
            (('parent-1', 'parent-2'), 'broken-revision-2-1'))
 
770
 
 
771
    def corrected_parents(self):
 
772
        return (
 
773
            ((), 'parent-1'),
 
774
            ((), 'parent-2'),
 
775
            (('parent-1', 'parent-2'), 'broken-revision-1-2'),
 
776
            (('parent-2', 'parent-1'), 'broken-revision-2-1'))
 
777
 
 
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.
 
782
            count = 4
 
783
        else:
 
784
            count = 2
 
785
        return (
 
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'\)")
 
793
 
 
794
    def populate_repository(self, repo):
 
795
        inv = self.make_one_file_inventory(repo, 'parent-1', [])
 
796
        self.add_revision(repo, 'parent-1', inv, [])
 
797
 
 
798
        inv = self.make_one_file_inventory(repo, 'parent-2', [])
 
799
        self.add_revision(repo, 'parent-2', inv, [])
 
800
 
 
801
        inv = self.make_one_file_inventory(
 
802
            repo, 'broken-revision-1-2', ['parent-2', 'parent-1'])
 
803
        self.add_revision(
 
804
            repo, 'broken-revision-1-2', inv, ['parent-1', 'parent-2'])
 
805
 
 
806
        inv = self.make_one_file_inventory(
 
807
            repo, 'broken-revision-2-1', ['parent-1', 'parent-2'])
 
808
        self.add_revision(
 
809
            repo, 'broken-revision-2-1', inv, ['parent-2', 'parent-1'])
 
810
        self.versioned_root = repo.supports_rich_root()
 
811
 
 
812
    def repository_text_key_references(self):
 
813
        result = {}
 
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})
 
823
        return result
 
824
 
 
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]}
 
832
 
 
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]}
 
840
               
 
841
 
 
842
all_broken_scenario_classes = [
 
843
    UndamagedRepositoryScenario,
 
844
    FileParentIsNotInRevisionAncestryScenario,
 
845
    FileParentHasInaccessibleInventoryScenario,
 
846
    FileParentsNotReferencedByAnyInventoryScenario,
 
847
    TooManyParentsScenario,
 
848
    ClaimedFileParentDidNotModifyFileScenario,
 
849
    IncorrectlyOrderedParentsScenario,
 
850
    UnreferencedFileParentsFromNoOpMergeScenario,
 
851
    ]
 
852
 
 
853
 
 
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',
 
861
        'test_break_lock',
 
862
        'test_check',
 
863
        # test_check_reconcile is intentionally omitted, see below.
 
864
        'test_commit_builder',
 
865
        'test_fetch',
 
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',
 
874
        'test_pack',
 
875
        'test_reconcile',
 
876
        'test_repository',
 
877
        'test_revision',
 
878
        'test_statistics',
 
879
        'test_write_group',
 
880
        ]
 
881
    module_name_list = [prefix + module_name
 
882
                        for module_name in test_repository_modules]
 
883
 
 
884
    # add the tests for the sub modules
 
885
 
 
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,
 
889
                                                format_scenarios,
 
890
                                                loader))
 
891
 
 
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
 
900
    adapt_modules(
 
901
        [prefix + 'test_check_reconcile'],
 
902
        broken_scenario_applier, loader, result)
 
903
 
59
904
    return result