~bzr-pqm/bzr/bzr.dev

5684.2.1 by Jelmer Vernooij
Add bzrlib.tests.per_repository_vf.
1
# Copyright (C) 2011 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 repository implementations - tests a repository format."""
18
19
from bzrlib import (
5718.3.1 by Jelmer Vernooij
Skip more tests for repository formats that don't support the full
20
    errors,
5815.4.5 by Jelmer Vernooij
Use MetaDirVersionedFileRepositoryFormat (a Soyuz worthy name).
21
    gpg,
5819.2.7 by Jelmer Vernooij
Fix some imports.
22
    inventory,
5816.4.1 by Jelmer Vernooij
Move test_add_inventory_by_delta to per_repository_vf.
23
    repository as _mod_repository,
5819.2.7 by Jelmer Vernooij
Fix some imports.
24
    revision as _mod_revision,
5718.3.1 by Jelmer Vernooij
Skip more tests for repository formats that don't support the full
25
    tests,
5684.2.1 by Jelmer Vernooij
Add bzrlib.tests.per_repository_vf.
26
    versionedfile,
5815.4.5 by Jelmer Vernooij
Use MetaDirVersionedFileRepositoryFormat (a Soyuz worthy name).
27
    vf_repository,
5684.2.1 by Jelmer Vernooij
Add bzrlib.tests.per_repository_vf.
28
    )
29
5972.3.10 by Jelmer Vernooij
Deprecate Repository.get_ancestry.
30
from bzrlib.symbol_versioning import deprecated_in
5972.3.15 by Jelmer Vernooij
Use matchers.
31
from bzrlib.tests.matchers import MatchesAncestry
5684.2.4 by Jelmer Vernooij
Use scenarios attribute.
32
from bzrlib.tests.per_repository_vf import (
33
    TestCaseWithRepository,
34
    all_repository_vf_format_scenarios,
35
    )
36
from bzrlib.tests.scenarios import load_tests_apply_scenarios
37
38
39
load_tests = load_tests_apply_scenarios
5684.2.1 by Jelmer Vernooij
Add bzrlib.tests.per_repository_vf.
40
41
42
class TestRepository(TestCaseWithRepository):
43
5684.2.4 by Jelmer Vernooij
Use scenarios attribute.
44
    scenarios = all_repository_vf_format_scenarios()
45
5815.4.5 by Jelmer Vernooij
Use MetaDirVersionedFileRepositoryFormat (a Soyuz worthy name).
46
    def assertFormatAttribute(self, attribute, allowed_values):
47
        """Assert that the format has an attribute 'attribute'."""
48
        repo = self.make_repository('repo')
49
        self.assertSubset([getattr(repo._format, attribute)], allowed_values)
50
51
    def test_attribute__fetch_order(self):
52
        """Test the _fetch_order attribute."""
53
        self.assertFormatAttribute('_fetch_order', ('topological', 'unordered'))
54
55
    def test_attribute__fetch_uses_deltas(self):
56
        """Test the _fetch_uses_deltas attribute."""
57
        self.assertFormatAttribute('_fetch_uses_deltas', (True, False))
58
5684.2.1 by Jelmer Vernooij
Add bzrlib.tests.per_repository_vf.
59
    def test_attribute_inventories_store(self):
60
        """Test the existence of the inventories attribute."""
61
        tree = self.make_branch_and_tree('tree')
62
        repo = tree.branch.repository
63
        self.assertIsInstance(repo.inventories, versionedfile.VersionedFiles)
64
65
    def test_attribute_inventories_basics(self):
66
        """Test basic aspects of the inventories attribute."""
67
        tree = self.make_branch_and_tree('tree')
68
        repo = tree.branch.repository
69
        rev_id = (tree.commit('a'),)
70
        tree.lock_read()
71
        self.addCleanup(tree.unlock)
72
        self.assertEqual(set([rev_id]), set(repo.inventories.keys()))
73
74
    def test_attribute_revision_store(self):
75
        """Test the existence of the revisions attribute."""
76
        tree = self.make_branch_and_tree('tree')
77
        repo = tree.branch.repository
78
        self.assertIsInstance(repo.revisions,
79
            versionedfile.VersionedFiles)
80
81
    def test_attribute_revision_store_basics(self):
82
        """Test the basic behaviour of the revisions attribute."""
83
        tree = self.make_branch_and_tree('tree')
84
        repo = tree.branch.repository
85
        repo.lock_write()
86
        try:
87
            self.assertEqual(set(), set(repo.revisions.keys()))
88
            revid = (tree.commit("foo"),)
89
            self.assertEqual(set([revid]), set(repo.revisions.keys()))
90
            self.assertEqual({revid:()},
91
                repo.revisions.get_parent_map([revid]))
92
        finally:
93
            repo.unlock()
94
        tree2 = self.make_branch_and_tree('tree2')
95
        tree2.pull(tree.branch)
96
        left_id = (tree2.commit('left'),)
97
        right_id = (tree.commit('right'),)
98
        tree.merge_from_branch(tree2.branch)
99
        merge_id = (tree.commit('merged'),)
100
        repo.lock_read()
101
        self.addCleanup(repo.unlock)
102
        self.assertEqual(set([revid, left_id, right_id, merge_id]),
103
            set(repo.revisions.keys()))
104
        self.assertEqual({revid:(), left_id:(revid,), right_id:(revid,),
105
             merge_id:(right_id, left_id)},
106
            repo.revisions.get_parent_map(repo.revisions.keys()))
107
108
    def test_attribute_signature_store(self):
109
        """Test the existence of the signatures attribute."""
110
        tree = self.make_branch_and_tree('tree')
111
        repo = tree.branch.repository
112
        self.assertIsInstance(repo.signatures,
113
            versionedfile.VersionedFiles)
5718.3.1 by Jelmer Vernooij
Skip more tests for repository formats that don't support the full
114
115
    def test_exposed_versioned_files_are_marked_dirty(self):
116
        repo = self.make_repository('.')
117
        repo.lock_write()
118
        signatures = repo.signatures
119
        revisions = repo.revisions
120
        inventories = repo.inventories
121
        repo.unlock()
122
        self.assertRaises(errors.ObjectNotLocked,
123
            signatures.keys)
124
        self.assertRaises(errors.ObjectNotLocked,
125
            revisions.keys)
126
        self.assertRaises(errors.ObjectNotLocked,
127
            inventories.keys)
128
        self.assertRaises(errors.ObjectNotLocked,
129
            signatures.add_lines, ('foo',), [], [])
130
        self.assertRaises(errors.ObjectNotLocked,
131
            revisions.add_lines, ('foo',), [], [])
132
        self.assertRaises(errors.ObjectNotLocked,
133
            inventories.add_lines, ('foo',), [], [])
134
5815.4.5 by Jelmer Vernooij
Use MetaDirVersionedFileRepositoryFormat (a Soyuz worthy name).
135
    def test__get_sink(self):
136
        repo = self.make_repository('repo')
137
        sink = repo._get_sink()
138
        self.assertIsInstance(sink, vf_repository.StreamSink)
139
5819.2.2 by Jelmer Vernooij
Move get_serializer_format.
140
    def test_get_serializer_format(self):
141
        repo = self.make_repository('.')
142
        format = repo.get_serializer_format()
143
        self.assertEqual(repo._serializer.format_num, format)
144
5819.2.3 by Jelmer Vernooij
Move vf-specific test_add_revision_inventory_sha1 to per_repository_vf.
145
    def test_add_revision_inventory_sha1(self):
146
        inv = inventory.Inventory(revision_id='A')
147
        inv.root.revision = 'A'
148
        inv.root.file_id = 'fixed-root'
149
        # Insert the inventory on its own to an identical repository, to get
150
        # its sha1.
151
        reference_repo = self.make_repository('reference_repo')
152
        reference_repo.lock_write()
153
        reference_repo.start_write_group()
154
        inv_sha1 = reference_repo.add_inventory('A', inv, [])
155
        reference_repo.abort_write_group()
156
        reference_repo.unlock()
157
        # Now insert a revision with this inventory, and it should get the same
158
        # sha1.
159
        repo = self.make_repository('repo')
160
        repo.lock_write()
161
        repo.start_write_group()
162
        root_id = inv.root.file_id
163
        repo.texts.add_lines(('fixed-root', 'A'), [], [])
164
        repo.add_revision('A', _mod_revision.Revision(
165
                'A', committer='B', timestamp=0,
166
                timezone=0, message='C'), inv=inv)
167
        repo.commit_write_group()
168
        repo.unlock()
169
        repo.lock_read()
170
        self.assertEquals(inv_sha1, repo.get_revision('A').inventory_sha1)
171
        repo.unlock()
172
5815.4.5 by Jelmer Vernooij
Use MetaDirVersionedFileRepositoryFormat (a Soyuz worthy name).
173
    def test_install_revisions(self):
174
        wt = self.make_branch_and_tree('source')
175
        wt.commit('A', allow_pointless=True, rev_id='A')
176
        repo = wt.branch.repository
177
        repo.lock_write()
178
        repo.start_write_group()
179
        repo.sign_revision('A', gpg.LoopbackGPGStrategy(None))
180
        repo.commit_write_group()
181
        repo.unlock()
182
        repo.lock_read()
183
        self.addCleanup(repo.unlock)
184
        repo2 = self.make_repository('repo2')
185
        revision = repo.get_revision('A')
186
        tree = repo.revision_tree('A')
187
        signature = repo.get_signature_text('A')
188
        repo2.lock_write()
189
        self.addCleanup(repo2.unlock)
190
        vf_repository.install_revisions(repo2, [(revision, tree, signature)])
191
        self.assertEqual(revision, repo2.get_revision('A'))
192
        self.assertEqual(signature, repo2.get_signature_text('A'))
193
5815.4.7 by Jelmer Vernooij
Move attribute test for texts.
194
    def test_attribute_text_store(self):
195
        """Test the existence of the texts attribute."""
196
        tree = self.make_branch_and_tree('tree')
197
        repo = tree.branch.repository
198
        self.assertIsInstance(repo.texts,
199
            versionedfile.VersionedFiles)
200
5815.4.9 by Jelmer Vernooij
Move more tests.
201
    def test_iter_inventories_is_ordered(self):
202
        # just a smoke test
203
        tree = self.make_branch_and_tree('a')
204
        first_revision = tree.commit('')
205
        second_revision = tree.commit('')
206
        tree.lock_read()
207
        self.addCleanup(tree.unlock)
208
        revs = (first_revision, second_revision)
209
        invs = tree.branch.repository.iter_inventories(revs)
210
        for rev_id, inv in zip(revs, invs):
211
            self.assertEqual(rev_id, inv.revision_id)
212
            self.assertIsInstance(inv, inventory.CommonInventory)
213
214
    def test_item_keys_introduced_by(self):
215
        # Make a repo with one revision and one versioned file.
216
        tree = self.make_branch_and_tree('t')
217
        self.build_tree(['t/foo'])
218
        tree.add('foo', 'file1')
219
        tree.commit('message', rev_id='rev_id')
220
        repo = tree.branch.repository
221
        repo.lock_write()
222
        repo.start_write_group()
223
        try:
224
            repo.sign_revision('rev_id', gpg.LoopbackGPGStrategy(None))
225
        except errors.UnsupportedOperation:
226
            signature_texts = []
227
        else:
228
            signature_texts = ['rev_id']
229
        repo.commit_write_group()
230
        repo.unlock()
231
        repo.lock_read()
232
        self.addCleanup(repo.unlock)
233
234
        # Item keys will be in this order, for maximum convenience for
235
        # generating data to insert into knit repository:
236
        #   * files
237
        #   * inventory
238
        #   * signatures
239
        #   * revisions
240
        expected_item_keys = [
241
            ('file', 'file1', ['rev_id']),
242
            ('inventory', None, ['rev_id']),
243
            ('signatures', None, signature_texts),
244
            ('revisions', None, ['rev_id'])]
245
        item_keys = list(repo.item_keys_introduced_by(['rev_id']))
246
        item_keys = [
247
            (kind, file_id, list(versions))
248
            for (kind, file_id, versions) in item_keys]
249
250
        if repo.supports_rich_root():
251
            # Check for the root versioned file in the item_keys, then remove
252
            # it from streamed_names so we can compare that with
253
            # expected_record_names.
254
            # Note that the file keys can be in any order, so this test is
255
            # written to allow that.
256
            inv = repo.get_inventory('rev_id')
257
            root_item_key = ('file', inv.root.file_id, ['rev_id'])
258
            self.assertTrue(root_item_key in item_keys)
259
            item_keys.remove(root_item_key)
260
261
        self.assertEqual(expected_item_keys, item_keys)
262
5815.5.9 by Jelmer Vernooij
Remove dependencies on texts.
263
    def test_attribute_text_store_basics(self):
264
        """Test the basic behaviour of the text store."""
265
        tree = self.make_branch_and_tree('tree')
266
        repo = tree.branch.repository
267
        file_id = "Foo:Bar"
268
        file_key = (file_id,)
269
        tree.lock_write()
270
        try:
271
            self.assertEqual(set(), set(repo.texts.keys()))
272
            tree.add(['foo'], [file_id], ['file'])
273
            tree.put_file_bytes_non_atomic(file_id, 'content\n')
274
            try:
275
                rev_key = (tree.commit("foo"),)
276
            except errors.IllegalPath:
277
                raise tests.TestNotApplicable(
278
                    'file_id %r cannot be stored on this'
279
                    ' platform for this repo format' % (file_id,))
280
            if repo._format.rich_root_data:
281
                root_commit = (tree.get_root_id(),) + rev_key
282
                keys = set([root_commit])
283
                parents = {root_commit:()}
284
            else:
285
                keys = set()
286
                parents = {}
287
            keys.add(file_key + rev_key)
288
            parents[file_key + rev_key] = ()
289
            self.assertEqual(keys, set(repo.texts.keys()))
290
            self.assertEqual(parents,
291
                repo.texts.get_parent_map(repo.texts.keys()))
292
        finally:
293
            tree.unlock()
294
        tree2 = self.make_branch_and_tree('tree2')
295
        tree2.pull(tree.branch)
296
        tree2.put_file_bytes_non_atomic('Foo:Bar', 'right\n')
297
        right_key = (tree2.commit('right'),)
298
        keys.add(file_key + right_key)
299
        parents[file_key + right_key] = (file_key + rev_key,)
300
        tree.put_file_bytes_non_atomic('Foo:Bar', 'left\n')
301
        left_key = (tree.commit('left'),)
302
        keys.add(file_key + left_key)
303
        parents[file_key + left_key] = (file_key + rev_key,)
304
        tree.merge_from_branch(tree2.branch)
305
        tree.put_file_bytes_non_atomic('Foo:Bar', 'merged\n')
306
        try:
307
            tree.auto_resolve()
308
        except errors.UnsupportedOperation:
309
            pass
310
        merge_key = (tree.commit('merged'),)
311
        keys.add(file_key + merge_key)
312
        parents[file_key + merge_key] = (file_key + left_key,
313
                                         file_key + right_key)
314
        repo.lock_read()
315
        self.addCleanup(repo.unlock)
316
        self.assertEqual(keys, set(repo.texts.keys()))
317
        self.assertEqual(parents, repo.texts.get_parent_map(repo.texts.keys()))
318
5718.3.1 by Jelmer Vernooij
Skip more tests for repository formats that don't support the full
319
320
class TestCaseWithComplexRepository(TestCaseWithRepository):
321
322
    scenarios = all_repository_vf_format_scenarios()
323
324
    def setUp(self):
325
        super(TestCaseWithComplexRepository, self).setUp()
326
        tree_a = self.make_branch_and_tree('a')
327
        self.bzrdir = tree_a.branch.bzrdir
328
        # add a corrupt inventory 'orphan'
329
        # this may need some generalising for knits.
330
        tree_a.lock_write()
331
        try:
332
            tree_a.branch.repository.start_write_group()
333
            try:
334
                inv_file = tree_a.branch.repository.inventories
335
                inv_file.add_lines(('orphan',), [], [])
336
            except:
337
                tree_a.branch.repository.commit_write_group()
338
                raise
339
            else:
340
                tree_a.branch.repository.abort_write_group()
341
        finally:
342
            tree_a.unlock()
343
        # add a real revision 'rev1'
344
        tree_a.commit('rev1', rev_id='rev1', allow_pointless=True)
345
        # add a real revision 'rev2' based on rev1
346
        tree_a.commit('rev2', rev_id='rev2', allow_pointless=True)
347
        # add a reference to a ghost
348
        tree_a.add_parent_tree_id('ghost1')
349
        try:
350
            tree_a.commit('rev3', rev_id='rev3', allow_pointless=True)
351
        except errors.RevisionNotPresent:
352
            raise tests.TestNotApplicable(
353
                "Cannot test with ghosts for this format.")
354
        # add another reference to a ghost, and a second ghost.
355
        tree_a.add_parent_tree_id('ghost1')
356
        tree_a.add_parent_tree_id('ghost2')
357
        tree_a.commit('rev4', rev_id='rev4', allow_pointless=True)
358
359
    def test_revision_trees(self):
360
        revision_ids = ['rev1', 'rev2', 'rev3', 'rev4']
361
        repository = self.bzrdir.open_repository()
362
        repository.lock_read()
363
        self.addCleanup(repository.unlock)
364
        trees1 = list(repository.revision_trees(revision_ids))
365
        trees2 = [repository.revision_tree(t) for t in revision_ids]
366
        self.assertEqual(len(trees1), len(trees2))
367
        for tree1, tree2 in zip(trees1, trees2):
368
            self.assertFalse(tree2.changes_from(tree1).has_changed())
369
370
    def test_get_deltas_for_revisions(self):
371
        repository = self.bzrdir.open_repository()
372
        repository.lock_read()
373
        self.addCleanup(repository.unlock)
374
        revisions = [repository.get_revision(r) for r in
375
                     ['rev1', 'rev2', 'rev3', 'rev4']]
376
        deltas1 = list(repository.get_deltas_for_revisions(revisions))
377
        deltas2 = [repository.get_revision_delta(r.revision_id) for r in
378
                   revisions]
379
        self.assertEqual(deltas1, deltas2)
380
381
    def test_all_revision_ids(self):
382
        # all_revision_ids -> all revisions
383
        self.assertEqual(set(['rev1', 'rev2', 'rev3', 'rev4']),
384
            set(self.bzrdir.open_repository().all_revision_ids()))
385
386
    def test_get_ancestry_missing_revision(self):
387
        # get_ancestry(revision that is in some data but not fully installed
388
        # -> NoSuchRevision
5972.3.10 by Jelmer Vernooij
Deprecate Repository.get_ancestry.
389
        repo = self.bzrdir.open_repository()
5718.3.1 by Jelmer Vernooij
Skip more tests for repository formats that don't support the full
390
        self.assertRaises(errors.NoSuchRevision,
5972.3.15 by Jelmer Vernooij
Use matchers.
391
            self.applyDeprecated, deprecated_in((2, 4, 0)),
392
            repo.get_ancestry, 'orphan')
5718.3.1 by Jelmer Vernooij
Skip more tests for repository formats that don't support the full
393
394
    def test_get_unordered_ancestry(self):
395
        repo = self.bzrdir.open_repository()
5972.3.10 by Jelmer Vernooij
Deprecate Repository.get_ancestry.
396
        self.assertEqual(
5972.3.15 by Jelmer Vernooij
Use matchers.
397
            set(self.applyDeprecated(deprecated_in((2, 4, 0)),
398
                repo.get_ancestry, 'rev3')),
399
            set(self.applyDeprecated(deprecated_in((2, 4, 0)),
400
                repo.get_ancestry, 'rev3', topo_sorted=False)))
5718.3.1 by Jelmer Vernooij
Skip more tests for repository formats that don't support the full
401
402
    def test_reserved_id(self):
403
        repo = self.make_repository('repository')
404
        repo.lock_write()
405
        repo.start_write_group()
406
        try:
407
            self.assertRaises(errors.ReservedId, repo.add_inventory,
408
                'reserved:', None, None)
409
            self.assertRaises(errors.ReservedId, repo.add_inventory_by_delta,
410
                "foo", [], 'reserved:', None)
411
            self.assertRaises(errors.ReservedId, repo.add_revision,
412
                'reserved:', None)
413
        finally:
414
            repo.abort_write_group()
415
            repo.unlock()
5815.4.13 by Jelmer Vernooij
Move corruption tests to bt.per_repository_vf.
416
417
5815.4.14 by Jelmer Vernooij
Fix imports.
418
class TestCaseWithCorruptRepository(TestCaseWithRepository):
419
420
    scenarios = all_repository_vf_format_scenarios()
5815.4.13 by Jelmer Vernooij
Move corruption tests to bt.per_repository_vf.
421
422
    def setUp(self):
423
        super(TestCaseWithCorruptRepository, self).setUp()
424
        # a inventory with no parents and the revision has parents..
425
        # i.e. a ghost.
426
        repo = self.make_repository('inventory_with_unnecessary_ghost')
427
        repo.lock_write()
428
        repo.start_write_group()
429
        inv = inventory.Inventory(revision_id = 'ghost')
430
        inv.root.revision = 'ghost'
431
        if repo.supports_rich_root():
432
            root_id = inv.root.file_id
433
            repo.texts.add_lines((root_id, 'ghost'), [], [])
434
        sha1 = repo.add_inventory('ghost', inv, [])
435
        rev = _mod_revision.Revision(
436
            timestamp=0, timezone=None, committer="Foo Bar <foo@example.com>",
437
            message="Message", inventory_sha1=sha1, revision_id='ghost')
438
        rev.parent_ids = ['the_ghost']
439
        try:
440
            repo.add_revision('ghost', rev)
441
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
442
            raise tests.TestNotApplicable(
443
                "Cannot test with ghosts for this format.")
444
445
        inv = inventory.Inventory(revision_id = 'the_ghost')
446
        inv.root.revision = 'the_ghost'
447
        if repo.supports_rich_root():
448
            root_id = inv.root.file_id
449
            repo.texts.add_lines((root_id, 'the_ghost'), [], [])
450
        sha1 = repo.add_inventory('the_ghost', inv, [])
451
        rev = _mod_revision.Revision(
452
            timestamp=0, timezone=None, committer="Foo Bar <foo@example.com>",
453
            message="Message", inventory_sha1=sha1, revision_id='the_ghost')
454
        rev.parent_ids = []
455
        repo.add_revision('the_ghost', rev)
456
        # check its setup usefully
457
        inv_weave = repo.inventories
458
        possible_parents = (None, (('ghost',),))
459
        self.assertSubset(inv_weave.get_parent_map([('ghost',)])[('ghost',)],
460
            possible_parents)
461
        repo.commit_write_group()
462
        repo.unlock()
463
464
    def test_corrupt_revision_access_asserts_if_reported_wrong(self):
465
        repo_url = self.get_url('inventory_with_unnecessary_ghost')
466
        repo = _mod_repository.Repository.open(repo_url)
5972.3.15 by Jelmer Vernooij
Use matchers.
467
        m = MatchesAncestry(repo, 'ghost')
5815.4.13 by Jelmer Vernooij
Move corruption tests to bt.per_repository_vf.
468
        reported_wrong = False
469
        try:
5972.3.21 by Jelmer Vernooij
More test fixes.
470
            if m.match(['the_ghost', 'ghost']) is not None:
5815.4.13 by Jelmer Vernooij
Move corruption tests to bt.per_repository_vf.
471
                reported_wrong = True
472
        except errors.CorruptRepository:
473
            # caught the bad data:
474
            return
475
        if not reported_wrong:
476
            return
477
        self.assertRaises(errors.CorruptRepository, repo.get_revision, 'ghost')
478
479
    def test_corrupt_revision_get_revision_reconcile(self):
480
        repo_url = self.get_url('inventory_with_unnecessary_ghost')
481
        repo = _mod_repository.Repository.open(repo_url)
482
        repo.get_revision_reconcile('ghost')