/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
1
# Copyright (C) 2006-2010 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Tests for reconciliation of repositories."""
18
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
19
import breezy
20
from breezy import (
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
21
    errors,
22
    )
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
23
from breezy.bzrdir import BzrDir
24
from breezy.inventory import Inventory
25
from breezy.reconcile import reconcile, Reconciler
26
from breezy.revision import Revision
27
from breezy.tests import TestSkipped
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
28
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
29
from breezy.tests.per_repository_vf.helpers import (
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
30
    TestCaseWithBrokenRevisionIndex,
31
    )
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
32
from breezy.tests.per_repository_vf import (
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
33
    TestCaseWithRepository,
34
    all_repository_vf_format_scenarios,
35
    )
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
36
from breezy.tests.matchers import MatchesAncestry
37
from breezy.tests.scenarios import load_tests_apply_scenarios
38
from breezy.uncommit import uncommit
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
39
40
41
load_tests = load_tests_apply_scenarios
42
43
44
class TestReconcile(TestCaseWithRepository):
45
46
    scenarios = all_repository_vf_format_scenarios()
47
48
    def checkUnreconciled(self, d, reconciler):
49
        """Check that d did not get reconciled."""
50
        # nothing should have been fixed yet:
51
        self.assertEqual(0, reconciler.inconsistent_parents)
52
        # and no garbage inventories
53
        self.assertEqual(0, reconciler.garbage_inventories)
54
        self.checkNoBackupInventory(d)
55
56
    def checkNoBackupInventory(self, aBzrDir):
57
        """Check that there is no backup inventory in aBzrDir."""
58
        repo = aBzrDir.open_repository()
59
        for path in repo.control_transport.list_dir('.'):
60
            self.assertFalse('inventory.backup' in path)
61
62
63
class TestBadRevisionParents(TestCaseWithBrokenRevisionIndex):
64
65
    scenarios = all_repository_vf_format_scenarios()
66
67
    def test_aborts_if_bad_parents_in_index(self):
68
        """Reconcile refuses to proceed if the revision index is wrong when
69
        checked against the revision texts, so that it does not generate broken
70
        data.
71
72
        Ideally reconcile would fix this, but until we implement that we just
73
        make sure we safely detect this problem.
74
        """
75
        repo = self.make_repo_with_extra_ghost_index()
76
        reconciler = repo.reconcile(thorough=True)
77
        self.assertTrue(reconciler.aborted,
78
            "reconcile should have aborted due to bad parents.")
79
80
    def test_does_not_abort_on_clean_repo(self):
81
        repo = self.make_repository('.')
82
        reconciler = repo.reconcile(thorough=True)
83
        self.assertFalse(reconciler.aborted,
84
            "reconcile should not have aborted on an unbroken repository.")
85
86
87
class TestsNeedingReweave(TestReconcile):
88
89
    def setUp(self):
90
        super(TestsNeedingReweave, self).setUp()
91
92
        t = self.get_transport()
93
        # an empty inventory with no revision for testing with.
94
        repo = self.make_repository('inventory_without_revision')
95
        repo.lock_write()
96
        repo.start_write_group()
97
        inv = Inventory(revision_id='missing')
98
        inv.root.revision = 'missing'
99
        repo.add_inventory('missing', inv, [])
100
        repo.commit_write_group()
101
        repo.unlock()
102
103
        def add_commit(repo, revision_id, parent_ids):
104
            repo.lock_write()
105
            repo.start_write_group()
106
            inv = Inventory(revision_id=revision_id)
107
            inv.root.revision = revision_id
108
            root_id = inv.root.file_id
109
            sha1 = repo.add_inventory(revision_id, inv, parent_ids)
110
            repo.texts.add_lines((root_id, revision_id), [], [])
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
111
            rev = breezy.revision.Revision(timestamp=0,
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
112
                                           timezone=None,
113
                                           committer="Foo Bar <foo@example.com>",
114
                                           message="Message",
115
                                           inventory_sha1=sha1,
116
                                           revision_id=revision_id)
117
            rev.parent_ids = parent_ids
118
            repo.add_revision(revision_id, rev)
119
            repo.commit_write_group()
120
            repo.unlock()
121
        # an empty inventory with no revision for testing with.
122
        # this is referenced by 'references_missing' to let us test
123
        # that all the cached data is correctly converted into ghost links
124
        # and the referenced inventory still cleaned.
125
        repo = self.make_repository('inventory_without_revision_and_ghost')
126
        repo.lock_write()
127
        repo.start_write_group()
128
        repo.add_inventory('missing', inv, [])
129
        repo.commit_write_group()
130
        repo.unlock()
131
        add_commit(repo, 'references_missing', ['missing'])
132
133
        # a inventory with no parents and the revision has parents..
134
        # i.e. a ghost.
135
        repo = self.make_repository('inventory_one_ghost')
136
        add_commit(repo, 'ghost', ['the_ghost'])
137
138
        # a inventory with a ghost that can be corrected now.
139
        t.copy_tree('inventory_one_ghost', 'inventory_ghost_present')
140
        bzrdir_url = self.get_url('inventory_ghost_present')
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
141
        bzrdir = BzrDir.open(bzrdir_url)
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
142
        repo = bzrdir.open_repository()
143
        add_commit(repo, 'the_ghost', [])
144
145
    def checkEmptyReconcile(self, **kwargs):
146
        """Check a reconcile on an empty repository."""
147
        self.make_repository('empty')
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
148
        d = BzrDir.open(self.get_url('empty'))
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
149
        # calling on a empty repository should do nothing
150
        reconciler = d.find_repository().reconcile(**kwargs)
151
        # no inconsistent parents should have been found
152
        self.assertEqual(0, reconciler.inconsistent_parents)
153
        # and no garbage inventories
154
        self.assertEqual(0, reconciler.garbage_inventories)
155
        # and no backup weave should have been needed/made.
156
        self.checkNoBackupInventory(d)
157
158
    def test_reconcile_empty(self):
159
        # in an empty repo, theres nothing to do.
160
        self.checkEmptyReconcile()
161
162
    def test_repo_has_reconcile_does_inventory_gc_attribute(self):
163
        repo = self.make_repository('repo')
164
        self.assertNotEqual(None, repo._reconcile_does_inventory_gc)
165
166
    def test_reconcile_empty_thorough(self):
167
        # reconcile should accept thorough=True
168
        self.checkEmptyReconcile(thorough=True)
169
170
    def test_convenience_reconcile_inventory_without_revision_reconcile(self):
171
        # smoke test for the all in one ui tool
172
        bzrdir_url = self.get_url('inventory_without_revision')
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
173
        bzrdir = BzrDir.open(bzrdir_url)
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
174
        repo = bzrdir.open_repository()
175
        if not repo._reconcile_does_inventory_gc:
176
            raise TestSkipped('Irrelevant test')
177
        reconcile(bzrdir)
178
        # now the backup should have it but not the current inventory
179
        repo = bzrdir.open_repository()
180
        self.check_missing_was_removed(repo)
181
182
    def test_reweave_inventory_without_revision(self):
183
        # an excess inventory on its own is only reconciled by using thorough
184
        d_url = self.get_url('inventory_without_revision')
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
185
        d = BzrDir.open(d_url)
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
186
        repo = d.open_repository()
187
        if not repo._reconcile_does_inventory_gc:
188
            raise TestSkipped('Irrelevant test')
189
        self.checkUnreconciled(d, repo.reconcile())
190
        reconciler = repo.reconcile(thorough=True)
191
        # no bad parents
192
        self.assertEqual(0, reconciler.inconsistent_parents)
193
        # and one garbage inventory
194
        self.assertEqual(1, reconciler.garbage_inventories)
195
        self.check_missing_was_removed(repo)
196
197
    def check_thorough_reweave_missing_revision(self, aBzrDir, reconcile,
198
            **kwargs):
199
        # actual low level test.
200
        repo = aBzrDir.open_repository()
5972.3.21 by Jelmer Vernooij
More test fixes.
201
        if not repo.has_revision('missing'):
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
202
            # the repo handles ghosts without corruption, so reconcile has
203
            # nothing to do here. Specifically, this test has the inventory
204
            # 'missing' present and the revision 'missing' missing, so clearly
205
            # 'missing' cannot be reported in the present ancestry -> missing
206
            # is something that can be filled as a ghost.
207
            expected_inconsistent_parents = 0
208
        else:
209
            expected_inconsistent_parents = 1
210
        reconciler = reconcile(**kwargs)
211
        # some number of inconsistent parents should have been found
212
        self.assertEqual(expected_inconsistent_parents,
213
                         reconciler.inconsistent_parents)
214
        # and one garbage inventories
215
        self.assertEqual(1, reconciler.garbage_inventories)
216
        # now the backup should have it but not the current inventory
217
        repo = aBzrDir.open_repository()
218
        self.check_missing_was_removed(repo)
219
        # and the parent list for 'references_missing' should have that
220
        # revision a ghost now.
5972.3.21 by Jelmer Vernooij
More test fixes.
221
        self.assertFalse(repo.has_revision('missing'))
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
222
223
    def check_missing_was_removed(self, repo):
224
        if repo._reconcile_backsup_inventory:
225
            backed_up = False
226
            for path in repo.control_transport.list_dir('.'):
227
                if 'inventory.backup' in path:
228
                    backed_up = True
229
            self.assertTrue(backed_up)
230
            # Not clear how to do this at an interface level:
231
            # self.assertTrue('missing' in backup.versions())
232
        self.assertRaises(errors.NoSuchRevision, repo.get_inventory, 'missing')
233
234
    def test_reweave_inventory_without_revision_reconciler(self):
235
        # smoke test for the all in one Reconciler class,
236
        # other tests use the lower level repo.reconcile()
237
        d_url = self.get_url('inventory_without_revision_and_ghost')
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
238
        d = BzrDir.open(d_url)
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
239
        if not d.open_repository()._reconcile_does_inventory_gc:
240
            raise TestSkipped('Irrelevant test')
241
        def reconcile():
242
            reconciler = Reconciler(d)
243
            reconciler.reconcile()
244
            return reconciler
245
        self.check_thorough_reweave_missing_revision(d, reconcile)
246
247
    def test_reweave_inventory_without_revision_and_ghost(self):
248
        # actual low level test.
249
        d_url = self.get_url('inventory_without_revision_and_ghost')
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
250
        d = BzrDir.open(d_url)
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
251
        repo = d.open_repository()
252
        if not repo._reconcile_does_inventory_gc:
253
            raise TestSkipped('Irrelevant test')
254
        # nothing should have been altered yet : inventories without
255
        # revisions are not data loss incurring for current format
256
        self.check_thorough_reweave_missing_revision(d, repo.reconcile,
257
            thorough=True)
258
259
    def test_reweave_inventory_preserves_a_revision_with_ghosts(self):
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
260
        d = BzrDir.open(self.get_url('inventory_one_ghost'))
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
261
        reconciler = d.open_repository().reconcile(thorough=True)
262
        # no inconsistent parents should have been found:
263
        # the lack of a parent for ghost is normal
264
        self.assertEqual(0, reconciler.inconsistent_parents)
265
        # and one garbage inventories
266
        self.assertEqual(0, reconciler.garbage_inventories)
267
        # now the current inventory should still have 'ghost'
268
        repo = d.open_repository()
269
        repo.get_inventory('ghost')
5972.3.21 by Jelmer Vernooij
More test fixes.
270
        self.assertThat(['ghost', 'the_ghost'], MatchesAncestry(repo, 'ghost'))
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
271
272
    def test_reweave_inventory_fixes_ancestryfor_a_present_ghost(self):
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
273
        d = BzrDir.open(self.get_url('inventory_ghost_present'))
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
274
        repo = d.open_repository()
5972.3.15 by Jelmer Vernooij
Use matchers.
275
        m = MatchesAncestry(repo, 'ghost')
5972.3.18 by Jelmer Vernooij
Fix typos.
276
        if m.match(['the_ghost', 'ghost']) is None:
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
277
            # the repo handles ghosts without corruption, so reconcile has
278
            # nothing to do
279
            return
5972.3.15 by Jelmer Vernooij
Use matchers.
280
        self.assertThat(['ghost'], m)
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
281
        reconciler = repo.reconcile()
282
        # this is a data corrupting error, so a normal reconcile should fix it.
283
        # one inconsistent parents should have been found : the
284
        # available but not reference parent for ghost.
285
        self.assertEqual(1, reconciler.inconsistent_parents)
286
        # and no garbage inventories
287
        self.assertEqual(0, reconciler.garbage_inventories)
288
        # now the current inventory should still have 'ghost'
289
        repo = d.open_repository()
290
        repo.get_inventory('ghost')
291
        repo.get_inventory('the_ghost')
5972.3.15 by Jelmer Vernooij
Use matchers.
292
        self.assertThat(['the_ghost', 'ghost'],
293
            MatchesAncestry(repo, 'ghost'))
294
        self.assertThat(['the_ghost'],
295
            MatchesAncestry(repo, 'the_ghost'))
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
296
297
    def test_text_from_ghost_revision(self):
298
        repo = self.make_repository('text-from-ghost')
299
        inv = Inventory(revision_id='final-revid')
300
        inv.root.revision = 'root-revid'
301
        ie = inv.add_path('bla', 'file', 'myfileid')
302
        ie.revision = 'ghostrevid'
303
        ie.text_size = 42
304
        ie.text_sha1 = "bee68c8acd989f5f1765b4660695275948bf5c00"
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
305
        rev = breezy.revision.Revision(timestamp=0,
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
306
                                       timezone=None,
307
                                       committer="Foo Bar <foo@example.com>",
308
                                       message="Message",
309
                                       revision_id='final-revid')
310
        repo.lock_write()
311
        try:
312
            repo.start_write_group()
313
            try:
314
                repo.add_revision('final-revid', rev, inv)
315
                try:
316
                    repo.texts.add_lines(('myfileid', 'ghostrevid'),
317
                        (('myfileid', 'ghost-text-parent'),),
318
                        ["line1\n", "line2\n"])
319
                except errors.RevisionNotPresent:
320
                    raise TestSkipped("text ghost parents not supported")
321
                if repo.supports_rich_root():
322
                    root_id = inv.root.file_id
323
                    repo.texts.add_lines((inv.root.file_id, inv.root.revision),
324
                        [], [])
325
            finally:
326
                repo.commit_write_group()
327
        finally:
328
            repo.unlock()
329
        repo.reconcile(thorough=True)
330
331
332
class TestReconcileWithIncorrectRevisionCache(TestReconcile):
333
    """Ancestry data gets cached in knits and weaves should be reconcilable.
334
335
    This class tests that reconcile can correct invalid caches (such as after
336
    a reconcile).
337
    """
338
339
    def setUp(self):
340
        self.reduceLockdirTimeout()
341
        super(TestReconcileWithIncorrectRevisionCache, self).setUp()
342
343
        t = self.get_transport()
344
        # we need a revision with two parents in the wrong order
345
        # which should trigger reinsertion.
346
        # and another with the first one correct but the other two not
347
        # which should not trigger reinsertion.
348
        # these need to be in different repositories so that we don't
349
        # trigger a reconcile based on the other case.
350
        # there is no api to construct a broken knit repository at
351
        # this point. if we ever encounter a bad graph in a knit repo
352
        # we should add a lower level api to allow constructing such cases.
353
354
        # first off the common logic:
6182.1.1 by Jelmer Vernooij
Fix reconcile tests.
355
        self.first_tree = self.make_branch_and_tree('wrong-first-parent')
356
        self.second_tree = self.make_branch_and_tree(
357
            'reversed-secondary-parents')
358
        for t in [self.first_tree, self.second_tree]:
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
359
            t.commit('1', rev_id='1')
360
            uncommit(t.branch, tree=t)
361
            t.commit('2', rev_id='2')
362
            uncommit(t.branch, tree=t)
363
            t.commit('3', rev_id='3')
364
            uncommit(t.branch, tree=t)
365
        #second_tree = self.make_branch_and_tree('reversed-secondary-parents')
366
        #second_tree.pull(tree) # XXX won't copy the repo?
6182.1.1 by Jelmer Vernooij
Fix reconcile tests.
367
        repo_secondary = self.second_tree.branch.repository
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
368
369
        # now setup the wrong-first parent case
6182.1.1 by Jelmer Vernooij
Fix reconcile tests.
370
        repo = self.first_tree.branch.repository
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
371
        repo.lock_write()
372
        repo.start_write_group()
373
        inv = Inventory(revision_id='wrong-first-parent')
374
        inv.root.revision = 'wrong-first-parent'
375
        if repo.supports_rich_root():
376
            root_id = inv.root.file_id
377
            repo.texts.add_lines((root_id, 'wrong-first-parent'), [], [])
378
        sha1 = repo.add_inventory('wrong-first-parent', inv, ['2', '1'])
379
        rev = Revision(timestamp=0,
380
                       timezone=None,
381
                       committer="Foo Bar <foo@example.com>",
382
                       message="Message",
383
                       inventory_sha1=sha1,
384
                       revision_id='wrong-first-parent')
385
        rev.parent_ids = ['1', '2']
386
        repo.add_revision('wrong-first-parent', rev)
387
        repo.commit_write_group()
388
        repo.unlock()
389
390
        # now setup the wrong-secondary parent case
391
        repo = repo_secondary
392
        repo.lock_write()
393
        repo.start_write_group()
394
        inv = Inventory(revision_id='wrong-secondary-parent')
395
        inv.root.revision = 'wrong-secondary-parent'
396
        if repo.supports_rich_root():
397
            root_id = inv.root.file_id
398
            repo.texts.add_lines((root_id, 'wrong-secondary-parent'), [], [])
399
        sha1 = repo.add_inventory('wrong-secondary-parent', inv, ['1', '3', '2'])
400
        rev = Revision(timestamp=0,
401
                       timezone=None,
402
                       committer="Foo Bar <foo@example.com>",
403
                       message="Message",
404
                       inventory_sha1=sha1,
405
                       revision_id='wrong-secondary-parent')
406
        rev.parent_ids = ['1', '2', '3']
407
        repo.add_revision('wrong-secondary-parent', rev)
408
        repo.commit_write_group()
409
        repo.unlock()
410
411
    def test_reconcile_wrong_order(self):
412
        # a wrong order in primary parents is optionally correctable
6182.1.1 by Jelmer Vernooij
Fix reconcile tests.
413
        repo = self.first_tree.branch.repository
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
414
        repo.lock_read()
415
        try:
416
            g = repo.get_graph()
417
            if g.get_parent_map(['wrong-first-parent'])['wrong-first-parent'] \
418
                == ('1', '2'):
419
                raise TestSkipped('wrong-first-parent is not setup for testing')
420
        finally:
421
            repo.unlock()
6182.1.1 by Jelmer Vernooij
Fix reconcile tests.
422
        self.checkUnreconciled(repo.bzrdir, repo.reconcile())
5802.2.1 by Jelmer Vernooij
Move some vf specific tests to bt.per_repository_vf
423
        # nothing should have been altered yet : inventories without
424
        # revisions are not data loss incurring for current format
425
        reconciler = repo.reconcile(thorough=True)
426
        # these show up as inconsistent parents
427
        self.assertEqual(1, reconciler.inconsistent_parents)
428
        # and no garbage inventories
429
        self.assertEqual(0, reconciler.garbage_inventories)
430
        # and should have been fixed:
431
        repo.lock_read()
432
        self.addCleanup(repo.unlock)
433
        g = repo.get_graph()
434
        self.assertEqual(
435
            {'wrong-first-parent':('1', '2')},
436
            g.get_parent_map(['wrong-first-parent']))
437
438
    def test_reconcile_wrong_order_secondary_inventory(self):
439
        # a wrong order in the parents for inventories is ignored.
6182.1.1 by Jelmer Vernooij
Fix reconcile tests.
440
        repo = self.second_tree.branch.repository
441
        self.checkUnreconciled(repo.bzrdir, repo.reconcile())
442
        self.checkUnreconciled(repo.bzrdir, repo.reconcile(thorough=True))