/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
1
# Copyright (C) 2007 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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
16
17
"""Tests for repository write groups."""
18
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
19
import sys
20
4343.3.2 by John Arbash Meinel
All stacking tests seem to be passing for dev6 repos
21
from bzrlib import (
22
    bzrdir,
23
    errors,
24
    graph,
25
    memorytree,
26
    osutils,
27
    remote,
4343.3.6 by John Arbash Meinel
Take out the _delta_across_repos flag.
28
    versionedfile,
4343.3.2 by John Arbash Meinel
All stacking tests seem to be passing for dev6 repos
29
    )
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
30
from bzrlib.branch import BzrBranchFormat7
31
from bzrlib.inventory import InventoryDirectory
3825.4.3 by Andrew Bennetts
Conditionally replace LocalURLServer in the test rather than changing the default_transport behaviour of per_repository tests.
32
from bzrlib.transport import local, memory
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
33
from bzrlib.tests import KnownFailure, TestNotApplicable
3689.1.1 by John Arbash Meinel
Rename repository_implementations tests into per_repository tests
34
from bzrlib.tests.per_repository import TestCaseWithRepository
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
35
36
37
class TestWriteGroup(TestCaseWithRepository):
38
39
    def test_start_write_group_unlocked_needs_write_lock(self):
40
        repo = self.make_repository('.')
41
        self.assertRaises(errors.NotWriteLocked, repo.start_write_group)
42
43
    def test_start_write_group_read_locked_needs_write_lock(self):
44
        repo = self.make_repository('.')
45
        repo.lock_read()
46
        try:
47
            self.assertRaises(errors.NotWriteLocked, repo.start_write_group)
48
        finally:
49
            repo.unlock()
50
51
    def test_start_write_group_write_locked_gets_None(self):
52
        repo = self.make_repository('.')
53
        repo.lock_write()
54
        self.assertEqual(None, repo.start_write_group())
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
55
        repo.commit_write_group()
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
56
        repo.unlock()
57
58
    def test_start_write_group_twice_errors(self):
59
        repo = self.make_repository('.')
60
        repo.lock_write()
61
        repo.start_write_group()
62
        try:
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
63
            # don't need a specific exception for now - this is
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
64
            # really to be sure it's used right, not for signalling
65
            # semantic information.
66
            self.assertRaises(errors.BzrError, repo.start_write_group)
67
        finally:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
68
            repo.commit_write_group()
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
69
            repo.unlock()
70
4431.3.7 by Jonathan Lange
Cherrypick bzr.dev 4470, resolving conflicts.
71
    def test_commit_write_group_does_not_error(self):
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
72
        repo = self.make_repository('.')
73
        repo.lock_write()
74
        repo.start_write_group()
4431.3.7 by Jonathan Lange
Cherrypick bzr.dev 4470, resolving conflicts.
75
        # commit_write_group can either return None (for repositories without
76
        # isolated transactions) or a hint for pack(). So we only check it
77
        # works in this interface test, because all repositories are exercised.
78
        repo.commit_write_group()
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
79
        repo.unlock()
80
2592.3.242 by Martin Pool
New method TestCase.call_catch_warnings
81
    def test_unlock_in_write_group(self):
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
82
        repo = self.make_repository('.')
83
        repo.lock_write()
84
        repo.start_write_group()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
85
        # don't need a specific exception for now - this is
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
86
        # really to be sure it's used right, not for signalling
87
        # semantic information.
88
        self.assertRaises(errors.BzrError, repo.unlock)
2592.3.244 by Martin Pool
unlock while in a write group now aborts the write group, unlocks, and errors.
89
        # after this error occurs, the repository is unlocked, and the write
90
        # group is gone.  you've had your chance, and you blew it. ;-)
91
        self.assertFalse(repo.is_locked())
92
        self.assertRaises(errors.BzrError, repo.commit_write_group)
93
        self.assertRaises(errors.BzrError, repo.unlock)
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
94
95
    def test_is_in_write_group(self):
96
        repo = self.make_repository('.')
97
        self.assertFalse(repo.is_in_write_group())
98
        repo.lock_write()
99
        repo.start_write_group()
100
        self.assertTrue(repo.is_in_write_group())
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
101
        repo.commit_write_group()
102
        self.assertFalse(repo.is_in_write_group())
103
        # abort also removes the in_write_group status.
104
        repo.start_write_group()
105
        self.assertTrue(repo.is_in_write_group())
106
        repo.abort_write_group()
107
        self.assertFalse(repo.is_in_write_group())
108
        repo.unlock()
109
110
    def test_abort_write_group_gets_None(self):
111
        repo = self.make_repository('.')
112
        repo.lock_write()
113
        repo.start_write_group()
114
        self.assertEqual(None, repo.abort_write_group())
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
115
        repo.unlock()
3825.4.1 by Andrew Bennetts
Add suppress_errors to abort_write_group.
116
117
    def test_abort_write_group_does_not_raise_when_suppressed(self):
3825.4.3 by Andrew Bennetts
Conditionally replace LocalURLServer in the test rather than changing the default_transport behaviour of per_repository tests.
118
        if self.transport_server is local.LocalURLServer:
119
            self.transport_server = None
3825.4.2 by Andrew Bennetts
Run the abort_write_group tests against a memory transport to avoid platform-specific limits on changing files that may be in use.
120
        self.vfs_transport_factory = memory.MemoryServer
3825.4.1 by Andrew Bennetts
Add suppress_errors to abort_write_group.
121
        repo = self.make_repository('repo')
122
        token = repo.lock_write()
123
        self.addCleanup(repo.unlock)
124
        repo.start_write_group()
125
        # Damage the repository on the filesystem
4327.1.9 by Vincent Ladeuil
Fix 4 more lock-related test failures.
126
        t = self.get_transport('')
127
        t.rename('repo', 'foo')
128
        self.addCleanup(t.rename, 'foo', 'repo')
3825.4.1 by Andrew Bennetts
Add suppress_errors to abort_write_group.
129
        # abort_write_group will not raise an error, because either an
130
        # exception was not generated, or the exception was caught and
131
        # suppressed.  See also test_pack_repository's test of the same name.
132
        self.assertEqual(None, repo.abort_write_group(suppress_errors=True))
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
133
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
134
class TestGetMissingParentInventories(TestCaseWithRepository):
135
4257.4.6 by Andrew Bennetts
Make get_missing_parent_inventories work for all repo formats (it's a no-op for unstackable formats).
136
    def test_empty_get_missing_parent_inventories(self):
137
        """A new write group has no missing parent inventories."""
138
        repo = self.make_repository('.')
139
        repo.lock_write()
140
        repo.start_write_group()
141
        try:
142
            self.assertEqual(set(), set(repo.get_missing_parent_inventories()))
143
        finally:
144
            repo.commit_write_group()
145
            repo.unlock()
146
4309.1.5 by Andrew Bennetts
Remove lots of cruft.
147
    def branch_trunk_and_make_tree(self, trunk_repo, relpath):
148
        tree = self.make_branch_and_memory_tree('branch')
149
        trunk_repo.lock_read()
150
        self.addCleanup(trunk_repo.unlock)
151
        tree.branch.repository.fetch(trunk_repo, revision_id='rev-1')
152
        tree.set_parent_ids(['rev-1'])
153
        return tree 
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
154
155
    def make_first_commit(self, repo):
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
156
        trunk = repo.bzrdir.create_branch()
4309.1.5 by Andrew Bennetts
Remove lots of cruft.
157
        tree = memorytree.MemoryTree.create_on_branch(trunk)
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
158
        tree.lock_write()
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
159
        tree.add([''], ['TREE_ROOT'], ['directory'])
160
        tree.add(['dir'], ['dir-id'], ['directory'])
161
        tree.add(['filename'], ['file-id'], ['file'])
162
        tree.put_file_bytes_non_atomic('file-id', 'content\n')
163
        tree.commit('Trunk commit', rev_id='rev-0')
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
164
        tree.commit('Trunk commit', rev_id='rev-1')
165
        tree.unlock()
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
166
167
    def make_new_commit_in_new_repo(self, trunk_repo, parents=None):
168
        tree = self.branch_trunk_and_make_tree(trunk_repo, 'branch')
169
        tree.set_parent_ids(parents)
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
170
        tree.commit('Branch commit', rev_id='rev-2')
171
        branch_repo = tree.branch.repository
172
        branch_repo.lock_read()
4257.4.9 by Andrew Bennetts
Add more test coverage, but make a long test even longer in the process.
173
        self.addCleanup(branch_repo.unlock)
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
174
        return branch_repo
175
4309.1.5 by Andrew Bennetts
Remove lots of cruft.
176
    def make_stackable_repo(self, relpath='trunk'):
177
        if isinstance(self.repository_format, remote.RemoteRepositoryFormat):
178
            # RemoteRepository by default builds a default format real
179
            # repository, but the default format is unstackble.  So explicitly
180
            # make a stackable real repository and use that.
181
            repo = self.make_repository(relpath, format='1.9')
182
            repo = bzrdir.BzrDir.open(self.get_url(relpath)).open_repository()
183
        else:
184
            repo = self.make_repository(relpath)
185
        if not repo._format.supports_external_lookups:
186
            raise TestNotApplicable('format not stackable')
187
        repo.bzrdir._format.set_branch_format(BzrBranchFormat7())
188
        return repo
189
190
    def reopen_repo_and_resume_write_group(self, repo):
191
        try:
192
            resume_tokens = repo.suspend_write_group()
193
        except errors.UnsuspendableWriteGroup:
194
            # If we got this far, and this repo does not support resuming write
195
            # groups, then get_missing_parent_inventories works in all
196
            # cases this repo supports.
197
            repo.unlock()
198
            return
199
        repo.unlock()
200
        reopened_repo = repo.bzrdir.open_repository()
201
        reopened_repo.lock_write()
202
        self.addCleanup(reopened_repo.unlock)
203
        reopened_repo.resume_write_group(resume_tokens)
204
        return reopened_repo
205
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
206
    def test_ghost_revision(self):
207
        """A parent inventory may be absent if all the needed texts are present.
208
        i.e., a ghost revision isn't (necessarily) considered to be a missing
209
        parent inventory.
210
        """
211
        # Make a trunk with one commit.
212
        trunk_repo = self.make_stackable_repo()
213
        self.make_first_commit(trunk_repo)
214
        trunk_repo.lock_read()
215
        self.addCleanup(trunk_repo.unlock)
216
        # Branch the trunk, add a new commit.
217
        branch_repo = self.make_new_commit_in_new_repo(
218
            trunk_repo, parents=['rev-1', 'ghost-rev'])
219
        inv = branch_repo.get_inventory('rev-2')
220
        # Make a new repo stacked on trunk, and then copy into it:
221
        #  - all texts in rev-2
222
        #  - the new inventory (rev-2)
223
        #  - the new revision (rev-2)
224
        repo = self.make_stackable_repo('stacked')
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
225
        repo.lock_write()
226
        repo.start_write_group()
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
227
        # Add all texts from in rev-2 inventory.  Note that this has to exclude
228
        # the root if the repo format does not support rich roots.
229
        rich_root = branch_repo._format.rich_root_data
230
        all_texts = [
231
            (ie.file_id, ie.revision) for ie in inv.iter_just_entries()
232
             if rich_root or inv.id2path(ie.file_id) != '']
233
        repo.texts.insert_record_stream(
234
            branch_repo.texts.get_record_stream(all_texts, 'unordered', False))
235
        # Add inventory and revision for rev-2.
236
        repo.add_inventory('rev-2', inv, ['rev-1', 'ghost-rev'])
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
237
        repo.revisions.insert_record_stream(
238
            branch_repo.revisions.get_record_stream(
239
                [('rev-2',)], 'unordered', False))
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
240
        # Now, no inventories are reported as missing, even though there is a
241
        # ghost.
242
        self.assertEqual(set(), repo.get_missing_parent_inventories())
243
        # Resuming the write group does not affect
244
        # get_missing_parent_inventories.
245
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
246
        self.assertEqual(set(), reopened_repo.get_missing_parent_inventories())
247
        reopened_repo.abort_write_group()
248
249
    def test_get_missing_parent_inventories(self):
250
        """A stacked repo with a single revision and inventory (no parent
251
        inventory) in it must have all the texts in its inventory (even if not
252
        changed w.r.t. to the absent parent), otherwise it will report missing
253
        texts/parent inventory.
4343.3.29 by John Arbash Meinel
Add 'check_for_missing_texts' flag to get_missing_parent_inv..
254
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
255
        The core of this test is that a file was changed in rev-1, but in a
4343.3.29 by John Arbash Meinel
Add 'check_for_missing_texts' flag to get_missing_parent_inv..
256
        stacked repo that only has rev-2
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
257
        """
258
        # Make a trunk with one commit.
259
        trunk_repo = self.make_stackable_repo()
260
        self.make_first_commit(trunk_repo)
4309.1.4 by Andrew Bennetts
Remove some cruft.
261
        trunk_repo.lock_read()
262
        self.addCleanup(trunk_repo.unlock)
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
263
        # Branch the trunk, add a new commit.
4309.1.4 by Andrew Bennetts
Remove some cruft.
264
        branch_repo = self.make_new_commit_in_new_repo(
265
            trunk_repo, parents=['rev-1'])
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
266
        inv = branch_repo.get_inventory('rev-2')
267
        # Make a new repo stacked on trunk, and copy the new commit's revision
268
        # and inventory records to it.
269
        repo = self.make_stackable_repo('stacked')
270
        repo.lock_write()
271
        repo.start_write_group()
4309.1.4 by Andrew Bennetts
Remove some cruft.
272
        # Insert a single fulltext inv (using add_inventory because it's
273
        # simpler than insert_record_stream)
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
274
        repo.add_inventory('rev-2', inv, ['rev-1'])
275
        repo.revisions.insert_record_stream(
276
            branch_repo.revisions.get_record_stream(
277
                [('rev-2',)], 'unordered', False))
278
        # There should be no missing compression parents
279
        self.assertEqual(set(),
280
                repo.inventories.get_missing_compression_parent_keys())
281
        self.assertEqual(
282
            set([('inventories', 'rev-1')]),
283
            repo.get_missing_parent_inventories())
284
        # Resuming the write group does not affect
285
        # get_missing_parent_inventories.
286
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
4257.4.9 by Andrew Bennetts
Add more test coverage, but make a long test even longer in the process.
287
        self.assertEqual(
288
            set([('inventories', 'rev-1')]),
4257.4.10 by Andrew Bennetts
Observe new revisions in _KnitGraphIndex.add_record rather than iterating all the uncommitted packs' indices.
289
            reopened_repo.get_missing_parent_inventories())
4309.1.3 by Andrew Bennetts
Start testing more cases, and start factoring those tests a little more clearly.
290
        # Adding the parent inventory satisfies get_missing_parent_inventories.
4257.4.10 by Andrew Bennetts
Observe new revisions in _KnitGraphIndex.add_record rather than iterating all the uncommitted packs' indices.
291
        reopened_repo.inventories.insert_record_stream(
4257.4.9 by Andrew Bennetts
Add more test coverage, but make a long test even longer in the process.
292
            branch_repo.inventories.get_record_stream(
293
                [('rev-1',)], 'unordered', False))
294
        self.assertEqual(
4257.4.10 by Andrew Bennetts
Observe new revisions in _KnitGraphIndex.add_record rather than iterating all the uncommitted packs' indices.
295
            set(), reopened_repo.get_missing_parent_inventories())
296
        reopened_repo.abort_write_group()
4257.4.8 by Andrew Bennetts
Add a (messy) test for get_missing_parent_inventories.
297
4343.3.29 by John Arbash Meinel
Add 'check_for_missing_texts' flag to get_missing_parent_inv..
298
    def test_get_missing_parent_inventories_check(self):
299
        builder = self.make_branch_builder('test')
300
        builder.build_snapshot('A-id', ['ghost-parent-id'], [
301
            ('add', ('', 'root-id', 'directory', None)),
302
            ('add', ('file', 'file-id', 'file', 'content\n'))],
303
            allow_leftmost_as_ghost=True)
304
        b = builder.get_branch()
305
        b.lock_read()
306
        self.addCleanup(b.unlock)
307
        repo = self.make_repository('test-repo')
308
        repo.lock_write()
309
        self.addCleanup(repo.unlock)
310
        repo.start_write_group()
311
        self.addCleanup(repo.abort_write_group)
312
        # Now, add the objects manually
313
        text_keys = [('file-id', 'A-id')]
314
        if repo.supports_rich_root():
315
            text_keys.append(('root-id', 'A-id'))
316
        # Directly add the texts, inventory, and revision object for 'A-id'
317
        repo.texts.insert_record_stream(b.repository.texts.get_record_stream(
318
            text_keys, 'unordered', True))
319
        repo.add_revision('A-id', b.repository.get_revision('A-id'),
320
                          b.repository.get_inventory('A-id'))
4343.3.30 by John Arbash Meinel
Add tests that when resuming a write group, we start checking if
321
        get_missing = repo.get_missing_parent_inventories
4343.3.29 by John Arbash Meinel
Add 'check_for_missing_texts' flag to get_missing_parent_inv..
322
        if repo._format.supports_external_lookups:
323
            self.assertEqual(set([('inventories', 'ghost-parent-id')]),
4343.3.30 by John Arbash Meinel
Add tests that when resuming a write group, we start checking if
324
                get_missing(check_for_missing_texts=False))
325
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
326
            self.assertEqual(set(), get_missing())
4343.3.29 by John Arbash Meinel
Add 'check_for_missing_texts' flag to get_missing_parent_inv..
327
        else:
4343.3.30 by John Arbash Meinel
Add tests that when resuming a write group, we start checking if
328
            # If we don't support external lookups, we always return empty
329
            self.assertEqual(set(), get_missing(check_for_missing_texts=False))
330
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
331
            self.assertEqual(set(), get_missing())
332
333
    def test_insert_stream_passes_resume_info(self):
334
        repo = self.make_repository('test-repo')
335
        if not repo._format.supports_external_lookups:
336
            raise TestNotApplicable('only valid in resumable repos')
337
        # log calls to get_missing_parent_inventories, so that we can assert it
338
        # is called with the correct parameters
339
        call_log = []
340
        orig = repo.get_missing_parent_inventories
341
        def get_missing(check_for_missing_texts=True):
342
            call_log.append(check_for_missing_texts)
343
            return orig(check_for_missing_texts=check_for_missing_texts)
344
        repo.get_missing_parent_inventories = get_missing
345
        repo.lock_write()
346
        self.addCleanup(repo.unlock)
347
        sink = repo._get_sink()
348
        sink.insert_stream((), repo._format, [])
349
        self.assertEqual([False], call_log)
350
        del call_log[:]
351
        repo.start_write_group()
352
        # We need to insert something, or suspend_write_group won't actually
353
        # create a token
354
        repo.texts.insert_record_stream([versionedfile.FulltextContentFactory(
355
            ('file-id', 'rev-id'), (), None, 'lines\n')])
356
        tokens = repo.suspend_write_group()
357
        self.assertNotEqual([], tokens)
358
        sink.insert_stream((), repo._format, tokens)
359
        self.assertEqual([True], call_log)
4343.3.29 by John Arbash Meinel
Add 'check_for_missing_texts' flag to get_missing_parent_inv..
360
4002.1.3 by Andrew Bennetts
Remove more duplication, put resumable write group tests in separate TestCase.
361
362
class TestResumeableWriteGroup(TestCaseWithRepository):
363
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
364
    def make_write_locked_repo(self, relpath='repo'):
365
        repo = self.make_repository(relpath)
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
366
        repo.lock_write()
367
        self.addCleanup(repo.unlock)
368
        return repo
369
370
    def reopen_repo(self, repo):
371
        same_repo = repo.bzrdir.open_repository()
372
        same_repo.lock_write()
373
        self.addCleanup(same_repo.unlock)
374
        return same_repo
375
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
376
    def require_suspendable_write_groups(self, reason):
377
        repo = self.make_repository('__suspend_test')
378
        repo.lock_write()
379
        self.addCleanup(repo.unlock)
380
        repo.start_write_group()
381
        try:
382
            wg_tokens = repo.suspend_write_group()
383
        except errors.UnsuspendableWriteGroup:
384
            repo.abort_write_group()
385
            raise TestNotApplicable(reason)
386
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
387
    def test_suspend_write_group(self):
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
388
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
389
        repo.start_write_group()
390
        # Add some content so this isn't an empty write group (which may return
391
        # 0 tokens)
392
        repo.texts.add_lines(('file-id', 'revid'), (), ['lines'])
393
        try:
394
            wg_tokens = repo.suspend_write_group()
395
        except errors.UnsuspendableWriteGroup:
396
            # The contract for repos that don't support suspending write groups
397
            # is that suspend_write_group raises UnsuspendableWriteGroup, but
398
            # is otherwise a no-op.  So we can still e.g. abort the write group
399
            # as usual.
400
            self.assertTrue(repo.is_in_write_group())
401
            repo.abort_write_group()
402
        else:
403
            # After suspending a write group we are no longer in a write group
404
            self.assertFalse(repo.is_in_write_group())
405
            # suspend_write_group returns a list of tokens, which are strs.  If
406
            # no other write groups were resumed, there will only be one token.
407
            self.assertEqual(1, len(wg_tokens))
408
            self.assertIsInstance(wg_tokens[0], str)
409
            # See also test_pack_repository's test of the same name.
410
411
    def test_resume_write_group_then_abort(self):
4002.1.3 by Andrew Bennetts
Remove more duplication, put resumable write group tests in separate TestCase.
412
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
413
        repo.start_write_group()
414
        # Add some content so this isn't an empty write group (which may return
415
        # 0 tokens)
416
        text_key = ('file-id', 'revid')
417
        repo.texts.add_lines(text_key, (), ['lines'])
418
        try:
419
            wg_tokens = repo.suspend_write_group()
420
        except errors.UnsuspendableWriteGroup:
421
            # If the repo does not support suspending write groups, it doesn't
422
            # support resuming them either.
423
            repo.abort_write_group()
424
            self.assertRaises(
425
                errors.UnsuspendableWriteGroup, repo.resume_write_group, [])
426
        else:
427
            #self.assertEqual([], list(repo.texts.keys()))
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
428
            same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
429
            same_repo.resume_write_group(wg_tokens)
430
            self.assertEqual([text_key], list(same_repo.texts.keys()))
431
            self.assertTrue(same_repo.is_in_write_group())
432
            same_repo.abort_write_group()
433
            self.assertEqual([], list(repo.texts.keys()))
434
            # See also test_pack_repository's test of the same name.
435
436
    def test_multiple_resume_write_group(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
437
        self.require_suspendable_write_groups(
438
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
439
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
440
        repo.start_write_group()
441
        # Add some content so this isn't an empty write group (which may return
442
        # 0 tokens)
443
        first_key = ('file-id', 'revid')
444
        repo.texts.add_lines(first_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
445
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
446
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
447
        same_repo.resume_write_group(wg_tokens)
448
        self.assertTrue(same_repo.is_in_write_group())
449
        second_key = ('file-id', 'second-revid')
450
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
451
        try:
452
            new_wg_tokens = same_repo.suspend_write_group()
453
        except:
454
            e = sys.exc_info()
455
            same_repo.abort_write_group(suppress_errors=True)
456
            raise e[0], e[1], e[2]
457
        self.assertEqual(2, len(new_wg_tokens))
458
        self.assertSubset(wg_tokens, new_wg_tokens)
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
459
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
460
        same_repo.resume_write_group(new_wg_tokens)
461
        both_keys = set([first_key, second_key])
462
        self.assertEqual(both_keys, same_repo.texts.keys())
463
        same_repo.abort_write_group()
464
465
    def test_no_op_suspend_resume(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
466
        self.require_suspendable_write_groups(
467
            'Cannot test resume on repo that does not support suspending')
4002.1.3 by Andrew Bennetts
Remove more duplication, put resumable write group tests in separate TestCase.
468
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
469
        repo.start_write_group()
470
        # Add some content so this isn't an empty write group (which may return
471
        # 0 tokens)
472
        text_key = ('file-id', 'revid')
473
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
474
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
475
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
476
        same_repo.resume_write_group(wg_tokens)
477
        new_wg_tokens = same_repo.suspend_write_group()
478
        self.assertEqual(wg_tokens, new_wg_tokens)
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
479
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
480
        same_repo.resume_write_group(wg_tokens)
481
        self.assertEqual([text_key], list(same_repo.texts.keys()))
482
        same_repo.abort_write_group()
483
484
    def test_read_after_suspend_fails(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
485
        self.require_suspendable_write_groups(
486
            'Cannot test suspend on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
487
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
488
        repo.start_write_group()
489
        # Add some content so this isn't an empty write group (which may return
490
        # 0 tokens)
491
        text_key = ('file-id', 'revid')
492
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
493
        wg_tokens = repo.suspend_write_group()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
494
        self.assertEqual([], list(repo.texts.keys()))
495
496
    def test_read_after_second_suspend_fails(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
497
        self.require_suspendable_write_groups(
498
            'Cannot test suspend on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
499
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
500
        repo.start_write_group()
501
        # Add some content so this isn't an empty write group (which may return
502
        # 0 tokens)
503
        text_key = ('file-id', 'revid')
504
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
505
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
506
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
507
        same_repo.resume_write_group(wg_tokens)
508
        same_repo.suspend_write_group()
509
        self.assertEqual([], list(same_repo.texts.keys()))
510
511
    def test_read_after_resume_abort_fails(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
512
        self.require_suspendable_write_groups(
513
            'Cannot test suspend on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
514
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
515
        repo.start_write_group()
516
        # Add some content so this isn't an empty write group (which may return
517
        # 0 tokens)
518
        text_key = ('file-id', 'revid')
519
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
520
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
521
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
522
        same_repo.resume_write_group(wg_tokens)
523
        same_repo.abort_write_group()
524
        self.assertEqual([], list(same_repo.texts.keys()))
525
526
    def test_cannot_resume_aborted_write_group(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
527
        self.require_suspendable_write_groups(
528
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
529
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
530
        repo.start_write_group()
531
        # Add some content so this isn't an empty write group (which may return
532
        # 0 tokens)
533
        text_key = ('file-id', 'revid')
534
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
535
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
536
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
537
        same_repo.resume_write_group(wg_tokens)
538
        same_repo.abort_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
539
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
540
        self.assertRaises(
4002.1.7 by Andrew Bennetts
Rename UnresumableWriteGroups to UnresumableWriteGroup.
541
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
542
            wg_tokens)
543
544
    def test_commit_resumed_write_group_no_new_data(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
545
        self.require_suspendable_write_groups(
546
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
547
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
548
        repo.start_write_group()
549
        # Add some content so this isn't an empty write group (which may return
550
        # 0 tokens)
551
        text_key = ('file-id', 'revid')
552
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
553
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
554
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
555
        same_repo.resume_write_group(wg_tokens)
556
        same_repo.commit_write_group()
557
        self.assertEqual([text_key], list(same_repo.texts.keys()))
558
        self.assertEqual(
559
            'lines', same_repo.texts.get_record_stream([text_key],
560
                'unordered', True).next().get_bytes_as('fulltext'))
561
        self.assertRaises(
4002.1.7 by Andrew Bennetts
Rename UnresumableWriteGroups to UnresumableWriteGroup.
562
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
563
            wg_tokens)
564
565
    def test_commit_resumed_write_group_plus_new_data(self):
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
566
        self.require_suspendable_write_groups(
567
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
568
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
569
        repo.start_write_group()
570
        # Add some content so this isn't an empty write group (which may return
571
        # 0 tokens)
572
        first_key = ('file-id', 'revid')
573
        repo.texts.add_lines(first_key, (), ['lines'])
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
574
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
575
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
576
        same_repo.resume_write_group(wg_tokens)
577
        second_key = ('file-id', 'second-revid')
578
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
579
        same_repo.commit_write_group()
580
        self.assertEqual(
581
            set([first_key, second_key]), set(same_repo.texts.keys()))
582
        self.assertEqual(
583
            'lines', same_repo.texts.get_record_stream([first_key],
584
                'unordered', True).next().get_bytes_as('fulltext'))
585
        self.assertEqual(
586
            'more lines', same_repo.texts.get_record_stream([second_key],
587
                'unordered', True).next().get_bytes_as('fulltext'))
588
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
589
    def make_source_with_delta_record(self):
590
        # Make a source repository with a delta record in it.
591
        source_repo = self.make_write_locked_repo('source')
592
        source_repo.start_write_group()
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
593
        key_base = ('file-id', 'base')
594
        key_delta = ('file-id', 'delta')
4343.3.6 by John Arbash Meinel
Take out the _delta_across_repos flag.
595
        def text_stream():
596
            yield versionedfile.FulltextContentFactory(
597
                key_base, (), None, 'lines\n')
598
            yield versionedfile.FulltextContentFactory(
599
                key_delta, (key_base,), None, 'more\nlines\n')
600
        source_repo.texts.insert_record_stream(text_stream())
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
601
        source_repo.commit_write_group()
602
        return source_repo
603
4002.1.12 by Andrew Bennetts
Add another test, fix the code so it passes, and remove some cruft.
604
    def test_commit_resumed_write_group_with_missing_parents(self):
605
        self.require_suspendable_write_groups(
606
            'Cannot test resume on repo that does not support suspending')
607
        source_repo = self.make_source_with_delta_record()
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
608
        key_base = ('file-id', 'base')
609
        key_delta = ('file-id', 'delta')
4002.1.12 by Andrew Bennetts
Add another test, fix the code so it passes, and remove some cruft.
610
        # Start a write group, insert just a delta.
611
        repo = self.make_write_locked_repo()
612
        repo.start_write_group()
613
        stream = source_repo.texts.get_record_stream(
614
            [key_delta], 'unordered', False)
615
        repo.texts.insert_record_stream(stream)
4343.3.6 by John Arbash Meinel
Take out the _delta_across_repos flag.
616
        # It's either not commitable due to the missing compression parent, or
617
        # the stacked location has already filled in the fulltext.
618
        try:
619
            repo.commit_write_group()
620
        except errors.BzrCheckError:
621
            # It refused to commit because we have a missing parent
622
            pass
623
        else:
624
            same_repo = self.reopen_repo(repo)
625
            same_repo.lock_read()
626
            record = same_repo.texts.get_record_stream([key_delta],
627
                                                       'unordered', True).next()
628
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
629
            return
4002.1.12 by Andrew Bennetts
Add another test, fix the code so it passes, and remove some cruft.
630
        # Merely suspending and resuming doesn't make it commitable either.
631
        wg_tokens = repo.suspend_write_group()
632
        same_repo = self.reopen_repo(repo)
633
        same_repo.resume_write_group(wg_tokens)
634
        self.assertRaises(
635
            errors.BzrCheckError, same_repo.commit_write_group)
636
        same_repo.abort_write_group()
637
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
638
    def test_commit_resumed_write_group_adding_missing_parents(self):
639
        self.require_suspendable_write_groups(
640
            'Cannot test resume on repo that does not support suspending')
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
641
        source_repo = self.make_source_with_delta_record()
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
642
        key_base = ('file-id', 'base')
643
        key_delta = ('file-id', 'delta')
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
644
        # Start a write group.
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
645
        repo = self.make_write_locked_repo()
646
        repo.start_write_group()
647
        # Add some content so this isn't an empty write group (which may return
648
        # 0 tokens)
649
        text_key = ('file-id', 'revid')
650
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
651
        # Suspend it, then resume it.
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
652
        wg_tokens = repo.suspend_write_group()
653
        same_repo = self.reopen_repo(repo)
654
        same_repo.resume_write_group(wg_tokens)
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
655
        # Add a record with a missing compression parent
656
        stream = source_repo.texts.get_record_stream(
657
            [key_delta], 'unordered', False)
658
        same_repo.texts.insert_record_stream(stream)
659
        # Just like if we'd added that record without a suspend/resume cycle,
660
        # commit_write_group fails.
4343.3.6 by John Arbash Meinel
Take out the _delta_across_repos flag.
661
        try:
662
            same_repo.commit_write_group()
663
        except errors.BzrCheckError:
664
            pass
665
        else:
666
            # If the commit_write_group didn't fail, that is because the
667
            # insert_record_stream already gave it a fulltext.
668
            same_repo = self.reopen_repo(repo)
669
            same_repo.lock_read()
670
            record = same_repo.texts.get_record_stream([key_delta],
671
                                                       'unordered', True).next()
672
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
673
            return
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
674
        same_repo.abort_write_group()
675
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
676
    def test_add_missing_parent_after_resume(self):
677
        self.require_suspendable_write_groups(
678
            'Cannot test resume on repo that does not support suspending')
679
        source_repo = self.make_source_with_delta_record()
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
680
        key_base = ('file-id', 'base')
681
        key_delta = ('file-id', 'delta')
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
682
        # Start a write group, insert just a delta.
683
        repo = self.make_write_locked_repo()
684
        repo.start_write_group()
685
        stream = source_repo.texts.get_record_stream(
686
            [key_delta], 'unordered', False)
687
        repo.texts.insert_record_stream(stream)
688
        # Suspend it, then resume it.
689
        wg_tokens = repo.suspend_write_group()
690
        same_repo = self.reopen_repo(repo)
4002.1.11 by Andrew Bennetts
Fix latest test.
691
        same_repo.resume_write_group(wg_tokens)
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
692
        # Fill in the missing compression parent.
693
        stream = source_repo.texts.get_record_stream(
694
            [key_base], 'unordered', False)
695
        same_repo.texts.insert_record_stream(stream)
696
        same_repo.commit_write_group()
697
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
698
    def test_suspend_empty_initial_write_group(self):
699
        """Suspending a write group with no writes returns an empty token
700
        list.
701
        """
702
        self.require_suspendable_write_groups(
703
            'Cannot test suspend on repo that does not support suspending')
704
        repo = self.make_write_locked_repo()
705
        repo.start_write_group()
706
        wg_tokens = repo.suspend_write_group()
707
        self.assertEqual([], wg_tokens)
708
709
    def test_suspend_empty_initial_write_group(self):
710
        """Resuming an empty token list is equivalent to start_write_group."""
711
        self.require_suspendable_write_groups(
712
            'Cannot test resume on repo that does not support suspending')
713
        repo = self.make_write_locked_repo()
714
        repo.resume_write_group([])
715
        repo.abort_write_group()
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
716
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
717