~ubuntu-branches/debian/lenny/bzr/lenny

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): Thomas Viehmann
  • Date: 2008-08-22 20:06:37 UTC
  • mfrom: (3.1.63 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080822200637-kxobfsnjlzojhqra
Tags: 1.5-1.1
* Non-maintainer upload.
* Apply patch from upstream VCS to fix FTBFS in tools/rst2html.py
  with older docutils. Thanks to Olivier Tétard for digging it
  up.
  Closes: #494246.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
 
1
# Copyright (C) 2006, 2007 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
    SmartTCPServer_for_testing,
 
37
    ReadonlySmartTCPServer_for_testing,
 
38
    )
32
39
from bzrlib.tests import (
33
40
                          adapt_modules,
34
41
                          default_transport,
35
 
                          TestLoader,
36
 
                          TestSuite,
 
42
                          iter_suite_tests,
 
43
                          multiply_scenarios,
 
44
                          multiply_tests_from_modules,
 
45
                          TestScenarioApplier,
37
46
                          )
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(
 
47
from bzrlib.tests.bzrdir_implementations.test_bzrdir import TestCaseWithBzrDir
 
48
from bzrlib.transport.memory import MemoryServer
 
49
 
 
50
 
 
51
class RepositoryTestProviderAdapter(TestScenarioApplier):
 
52
    """A tool to generate a suite testing multiple repository formats at once.
 
53
 
 
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.
 
58
    """
 
59
 
 
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)
 
67
    
 
68
    def formats_to_scenarios(self, formats):
 
69
        """Transform the input formats to a list of scenarios.
 
70
 
 
71
        :param formats: A list of (repository_format, bzrdir_format).
 
72
        """
 
73
        result = []
 
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,
 
80
                 })
 
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)
 
86
        return result
 
87
 
 
88
 
 
89
class TestCaseWithRepository(TestCaseWithBzrDir):
 
90
 
 
91
    def make_repository(self, relpath, format=None):
 
92
        if format is 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)
 
98
            return repo
 
99
        else:
 
100
            return super(TestCaseWithRepository, self).make_repository(
 
101
                relpath, format=format)
 
102
 
 
103
 
 
104
class BrokenRepoScenario(object):
 
105
    """Base class for defining scenarios for testing check and reconcile.
 
106
 
 
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.
 
121
    
 
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
 
125
            this occurs.
 
126
    """
 
127
 
 
128
    def __init__(self, test_case):
 
129
        self.test_case = test_case
 
130
 
 
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)
 
138
 
 
139
    def add_revision(self, repo, revision_id, inv, parent_ids):
 
140
        return self.test_case.add_revision(repo, revision_id, inv, parent_ids)
 
141
 
 
142
    def corrected_fulltexts(self):
 
143
        return []
 
144
 
 
145
    def repository_text_key_index(self):
 
146
        result = {}
 
147
        if self.versioned_root:
 
148
            result.update(self.versioned_repository_text_keys())
 
149
        result.update(self.repository_text_keys())
 
150
        return result
 
151
 
 
152
 
 
153
class UndamagedRepositoryScenario(BrokenRepoScenario):
 
154
    """A scenario where the repository has no damage.
 
155
 
 
156
    It has a single revision, 'rev1a', with a single file.
 
157
    """
 
158
 
 
159
    def all_versions_after_reconcile(self):
 
160
        return ('rev1a', )
 
161
 
 
162
    def populated_parents(self):
 
163
        return (((), 'rev1a'), )
 
164
 
 
165
    def corrected_parents(self):
 
166
        # Same as the populated parents, because there was nothing wrong.
 
167
        return self.populated_parents()
 
168
 
 
169
    def check_regexes(self, repo):
 
170
        return ["0 unreferenced text versions"]
 
171
 
 
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()
 
178
 
 
179
    def repository_text_key_references(self):
 
180
        result = {}
 
181
        if self.versioned_root:
 
182
            result.update({('TREE_ROOT', 'rev1a'): True})
 
183
        result.update({('a-file-id', 'rev1a'): True})
 
184
        return result
 
185
 
 
186
    def repository_text_keys(self):
 
187
        return {('a-file-id', 'rev1a'):[NULL_REVISION]}
 
188
 
 
189
    def versioned_repository_text_keys(self):
 
190
        return {('TREE_ROOT', 'rev1a'):[NULL_REVISION]}
 
191
 
 
192
 
 
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.
 
196
    
 
197
    Reconcile should remove 'rev1b' from the parents list of 'a-file' in
 
198
    'rev2', preserving 'rev1a' as a parent.
 
199
    """
 
200
 
 
201
    def all_versions_after_reconcile(self):
 
202
        return ('rev1a', 'rev2')
 
203
 
 
204
    def populated_parents(self):
 
205
        return (
 
206
            ((), 'rev1a'),
 
207
            ((), 'rev1b'), # Will be gc'd
 
208
            (('rev1a', 'rev1b'), 'rev2')) # Will have parents trimmed
 
209
 
 
210
    def corrected_parents(self):
 
211
        return (
 
212
            ((), 'rev1a'),
 
213
            (None, 'rev1b'),
 
214
            (('rev1a',), 'rev2'))
 
215
 
 
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",
 
220
                ]
 
221
 
 
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, [])
 
227
 
 
228
        # make rev1b, which has no Revision, but has an Inventory, and
 
229
        # a-file
 
230
        inv = self.make_one_file_inventory(
 
231
            repo, 'rev1b', [], root_revision='rev1b')
 
232
        repo.add_inventory('rev1b', inv, [])
 
233
 
 
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()
 
241
 
 
242
    def repository_text_key_references(self):
 
243
        result = {}
 
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})
 
249
        return result
 
250
 
 
251
    def repository_text_keys(self):
 
252
        return {('a-file-id', 'rev1a'):[NULL_REVISION],
 
253
                ('a-file-id', 'rev2'):[('a-file-id', 'rev1a')]}
 
254
 
 
255
    def versioned_repository_text_keys(self):
 
256
        return {('TREE_ROOT', 'rev1a'):[NULL_REVISION],
 
257
                ('TREE_ROOT', 'rev2'):[('TREE_ROOT', 'rev1a')]}
 
258
 
 
259
 
 
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.
 
264
 
 
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).
 
267
    """
 
268
 
 
269
    def all_versions_after_reconcile(self):
 
270
        return ('rev2', 'rev3')
 
271
 
 
272
    def populated_parents(self):
 
273
        return (
 
274
            ((), 'rev2'),
 
275
            (('rev1c',), 'rev3'))
 
276
 
 
277
    def corrected_parents(self):
 
278
        return (
 
279
            ((), 'rev2'),
 
280
            ((), 'rev3'))
 
281
 
 
282
    def check_regexes(self, repo):
 
283
        return [r"\* a-file-id version rev3 has parents "
 
284
                r"\('rev1c',\) but should have \(\)",
 
285
                ]
 
286
 
 
287
    def populate_repository(self, repo):
 
288
        # make rev2, with a-file
 
289
        # a-file is sane
 
290
        inv = self.make_one_file_inventory(repo, 'rev2', [])
 
291
        self.add_revision(repo, 'rev2', inv, [])
 
292
 
 
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', [])
 
300
 
 
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()
 
307
 
 
308
    def repository_text_key_references(self):
 
309
        result = {}
 
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})
 
315
        return result
 
316
 
 
317
    def repository_text_keys(self):
 
318
        return {('a-file-id', 'rev2'):[NULL_REVISION],
 
319
                ('a-file-id', 'rev3'):[NULL_REVISION]}
 
320
 
 
321
    def versioned_repository_text_keys(self):
 
322
        return {('TREE_ROOT', 'rev2'):[NULL_REVISION],
 
323
                ('TREE_ROOT', 'rev3'):[NULL_REVISION]}
 
324
 
 
325
 
 
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
 
332
    'rev5').
 
333
 
 
334
    Reconcile should remove the file parents that are not referenced by any
 
335
    inventory.
 
336
    """
 
337
 
 
338
    def all_versions_after_reconcile(self):
 
339
        return ('rev1a', 'rev2c', 'rev4', 'rev5')
 
340
 
 
341
    def populated_parents(self):
 
342
        return [
 
343
            (('rev1a',), 'rev2'),
 
344
            (('rev1a',), 'rev2b'),
 
345
            (('rev2',), 'rev3'),
 
346
            (('rev2',), 'rev4'),
 
347
            (('rev2', 'rev2c'), 'rev5')]
 
348
 
 
349
    def corrected_parents(self):
 
350
        return (
 
351
            # rev2 and rev2b have been removed.
 
352
            (None, 'rev2'),
 
353
            (None, 'rev2b'),
 
354
            # rev3's accessible parent inventories all have rev1a as the last
 
355
            # modifier.
 
356
            (('rev1a',), 'rev3'),
 
357
            # rev1a features in both rev4's parents but should only appear once
 
358
            # in the result
 
359
            (('rev1a',), 'rev4'),
 
360
            # rev2c is the head of rev1a and rev2c, the inventory provided
 
361
            # per-file last-modified revisions.
 
362
            (('rev2c',), 'rev5'))
 
363
 
 
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.
 
368
            count = 9
 
369
        else:
 
370
            count = 3
 
371
        return [
 
372
            "%d inconsistent parents" % count,
 
373
            # will be gc'd
 
374
            r"unreferenced version: {rev2} in a-file-id",
 
375
            r"unreferenced version: {rev2b} in a-file-id",
 
376
            # will be corrected
 
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',\)",
 
383
            ]
 
384
 
 
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, [])
 
390
 
 
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'])
 
397
 
 
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'])
 
407
 
 
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
 
412
        # a-file-rev2b.
 
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'])
 
417
 
 
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
 
425
        # completely wrong.
 
426
        inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
 
427
        self.add_revision(repo, 'rev4', inv, ['rev2', 'rev2b'])
 
428
 
 
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'])
 
433
 
 
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()
 
443
 
 
444
    def repository_text_key_references(self):
 
445
        result = {}
 
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})
 
459
        return result
 
460
 
 
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')]}
 
467
 
 
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')]}
 
478
 
 
479
 
 
480
class UnreferencedFileParentsFromNoOpMergeScenario(BrokenRepoScenario):
 
481
    """
 
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.
 
485
    """
 
486
 
 
487
    def all_versions_after_reconcile(self):
 
488
        return ('rev1a', 'rev1b', 'rev2', 'rev4')
 
489
 
 
490
    def populated_parents(self):
 
491
        return (
 
492
            ((), 'rev1a'),
 
493
            ((), 'rev1b'),
 
494
            (('rev1a', 'rev1b'), 'rev2'),
 
495
            (None, 'rev3'),
 
496
            (('rev2',), 'rev4'),
 
497
            )
 
498
 
 
499
    def corrected_parents(self):
 
500
        return (
 
501
            ((), 'rev1a'),
 
502
            ((), 'rev1b'),
 
503
            ((), 'rev2'),
 
504
            (None, 'rev3'),
 
505
            (('rev2',), 'rev4'),
 
506
            )
 
507
 
 
508
    def corrected_fulltexts(self):
 
509
        return ['rev2']
 
510
 
 
511
    def check_regexes(self, repo):
 
512
        return []
 
513
 
 
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, [])
 
519
 
 
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, [])
 
527
 
 
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'])
 
535
 
 
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'])
 
542
 
 
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()
 
547
 
 
548
    def repository_text_key_references(self):
 
549
        result = {}
 
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})
 
560
        return result
 
561
 
 
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')]}
 
567
 
 
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')]}
 
575
 
 
576
 
 
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
 
581
    ['good-parent'].
 
582
    """
 
583
 
 
584
    def all_versions_after_reconcile(self):
 
585
        return ('bad-parent', 'good-parent', 'broken-revision')
 
586
 
 
587
    def populated_parents(self):
 
588
        return (
 
589
            ((), 'bad-parent'),
 
590
            (('bad-parent',), 'good-parent'),
 
591
            (('good-parent', 'bad-parent'), 'broken-revision'))
 
592
 
 
593
    def corrected_parents(self):
 
594
        return (
 
595
            ((), 'bad-parent'),
 
596
            (('bad-parent',), 'good-parent'),
 
597
            (('good-parent',), 'broken-revision'))
 
598
 
 
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.
 
603
            count = 3
 
604
        else:
 
605
            count = 1
 
606
        return (
 
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',\)"))
 
611
 
 
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, ())
 
616
        
 
617
        inv = self.make_one_file_inventory(
 
618
            repo, 'good-parent', ('bad-parent',))
 
619
        self.add_revision(repo, 'good-parent', inv, ('bad-parent',))
 
620
        
 
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()
 
625
 
 
626
    def repository_text_key_references(self):
 
627
        result = {}
 
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})
 
635
        return result
 
636
             
 
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')]}
 
642
 
 
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')]}
 
648
 
 
649
 
 
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.
 
653
 
 
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.
 
658
    """
 
659
 
 
660
    def all_versions_after_reconcile(self):
 
661
        return ('basis', 'current')
 
662
 
 
663
    def populated_parents(self):
 
664
        return (
 
665
            ((), 'basis'),
 
666
            (('basis',), 'modified-something-else'),
 
667
            (('modified-something-else',), 'current'))
 
668
 
 
669
    def corrected_parents(self):
 
670
        return (
 
671
            ((), 'basis'),
 
672
            (None, 'modified-something-else'),
 
673
            (('basis',), 'current'))
 
674
 
 
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.
 
679
            count = 3
 
680
        else:
 
681
            count = 1
 
682
        return (
 
683
            "%d inconsistent parents" % count,
 
684
            r"\* a-file-id version current has parents "
 
685
            r"\('modified-something-else',\) but should have \('basis',\)",
 
686
            )
 
687
 
 
688
    def populate_repository(self, repo):
 
689
        inv = self.make_one_file_inventory(repo, 'basis', ())
 
690
        self.add_revision(repo, 'basis', inv, ())
 
691
 
 
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',))
 
698
 
 
699
        # The 'current' revision has 'modified-something-else' as its parent,
 
700
        # but the 'current' version of 'a-file' should have 'basis' as its
 
701
        # parent.
 
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()
 
706
 
 
707
    def repository_text_key_references(self):
 
708
        result = {}
 
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})
 
715
        return result
 
716
 
 
717
    def repository_text_keys(self):
 
718
        return {('a-file-id', 'basis'): [NULL_REVISION],
 
719
                ('a-file-id', 'current'): [('a-file-id', 'basis')]}
 
720
 
 
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')]}
 
727
            
 
728
 
 
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.
 
732
 
 
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).
 
739
    """
 
740
 
 
741
    def all_versions_after_reconcile(self):
 
742
        return ['parent-1', 'parent-2', 'broken-revision-1-2',
 
743
                'broken-revision-2-1']
 
744
 
 
745
    def populated_parents(self):
 
746
        return (
 
747
            ((), 'parent-1'),
 
748
            ((), 'parent-2'),
 
749
            (('parent-2', 'parent-1'), 'broken-revision-1-2'),
 
750
            (('parent-1', 'parent-2'), 'broken-revision-2-1'))
 
751
 
 
752
    def corrected_parents(self):
 
753
        return (
 
754
            ((), 'parent-1'),
 
755
            ((), 'parent-2'),
 
756
            (('parent-1', 'parent-2'), 'broken-revision-1-2'),
 
757
            (('parent-2', 'parent-1'), 'broken-revision-2-1'))
 
758
 
 
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.
 
763
            count = 4
 
764
        else:
 
765
            count = 2
 
766
        return (
 
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'\)")
 
774
 
 
775
    def populate_repository(self, repo):
 
776
        inv = self.make_one_file_inventory(repo, 'parent-1', [])
 
777
        self.add_revision(repo, 'parent-1', inv, [])
 
778
 
 
779
        inv = self.make_one_file_inventory(repo, 'parent-2', [])
 
780
        self.add_revision(repo, 'parent-2', inv, [])
 
781
 
 
782
        inv = self.make_one_file_inventory(
 
783
            repo, 'broken-revision-1-2', ['parent-2', 'parent-1'])
 
784
        self.add_revision(
 
785
            repo, 'broken-revision-1-2', inv, ['parent-1', 'parent-2'])
 
786
 
 
787
        inv = self.make_one_file_inventory(
 
788
            repo, 'broken-revision-2-1', ['parent-1', 'parent-2'])
 
789
        self.add_revision(
 
790
            repo, 'broken-revision-2-1', inv, ['parent-2', 'parent-1'])
 
791
        self.versioned_root = repo.supports_rich_root()
 
792
 
 
793
    def repository_text_key_references(self):
 
794
        result = {}
 
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})
 
804
        return result
 
805
 
 
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]}
 
813
 
 
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]}
 
821
               
 
822
 
 
823
all_broken_scenario_classes = [
 
824
    UndamagedRepositoryScenario,
 
825
    FileParentIsNotInRevisionAncestryScenario,
 
826
    FileParentHasInaccessibleInventoryScenario,
 
827
    FileParentsNotReferencedByAnyInventoryScenario,
 
828
    TooManyParentsScenario,
 
829
    ClaimedFileParentDidNotModifyFileScenario,
 
830
    IncorrectlyOrderedParentsScenario,
 
831
    UnreferencedFileParentsFromNoOpMergeScenario,
 
832
    ]
 
833
    
 
834
 
 
835
def load_tests(basic_tests, module, loader):
 
836
    result = loader.suiteClass()
 
837
    # add the tests for this module
 
838
    result.addTests(basic_tests)
 
839
 
 
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.
54
847
        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)
 
848
        [(format, format._matchingbzrdir) for format in all_formats])
 
849
 
 
850
    remote_repo_adapter = RepositoryTestProviderAdapter(
 
851
        SmartTCPServer_for_testing,
 
852
        ReadonlySmartTCPServer_for_testing,
 
853
        [(RemoteRepositoryFormat(), RemoteBzrDirFormat())],
 
854
        MemoryServer
 
855
        )
 
856
 
 
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)
 
861
 
 
862
    prefix = 'bzrlib.tests.repository_implementations.'
 
863
    test_repository_modules = [
 
864
        'test_break_lock',
 
865
        'test_check',
 
866
        # test_check_reconcile is intentionally omitted, see below.
 
867
        'test_commit_builder',
 
868
        'test_fetch',
 
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',
 
876
        'test_pack',
 
877
        'test_reconcile',
 
878
        'test_repository',
 
879
        'test_revision',
 
880
        'test_statistics',
 
881
        'test_write_group',
 
882
        ]
 
883
    module_name_list = [prefix + module_name
 
884
                        for module_name in test_repository_modules]
 
885
 
 
886
    # add the tests for the sub modules
 
887
 
 
888
    # Parameterize repository_implementations test modules by format.
 
889
    result.addTests(multiply_tests_from_modules(module_name_list,
 
890
                                                format_scenarios,
 
891
                                                loader))
 
892
 
 
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
 
901
    adapt_modules(
 
902
        [prefix + 'test_check_reconcile'],
 
903
        broken_scenario_applier, loader, result)
 
904
 
59
905
    return result