/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
3825.4.3 by Andrew Bennetts
Conditionally replace LocalURLServer in the test rather than changing the default_transport behaviour of per_repository tests.
31
from bzrlib.transport import local, memory
4634.35.10 by Andrew Bennetts
Move tests to per_repository_chk.
32
from bzrlib.tests import TestNotApplicable
3689.1.1 by John Arbash Meinel
Rename repository_implementations tests into per_repository tests
33
from bzrlib.tests.per_repository import TestCaseWithRepository
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
34
35
36
class TestWriteGroup(TestCaseWithRepository):
37
38
    def test_start_write_group_unlocked_needs_write_lock(self):
39
        repo = self.make_repository('.')
40
        self.assertRaises(errors.NotWriteLocked, repo.start_write_group)
41
42
    def test_start_write_group_read_locked_needs_write_lock(self):
43
        repo = self.make_repository('.')
44
        repo.lock_read()
45
        try:
46
            self.assertRaises(errors.NotWriteLocked, repo.start_write_group)
47
        finally:
48
            repo.unlock()
49
50
    def test_start_write_group_write_locked_gets_None(self):
51
        repo = self.make_repository('.')
52
        repo.lock_write()
53
        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.
54
        repo.commit_write_group()
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
55
        repo.unlock()
56
57
    def test_start_write_group_twice_errors(self):
58
        repo = self.make_repository('.')
59
        repo.lock_write()
60
        repo.start_write_group()
61
        try:
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
62
            # 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``
63
            # really to be sure it's used right, not for signalling
64
            # semantic information.
65
            self.assertRaises(errors.BzrError, repo.start_write_group)
66
        finally:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
67
            repo.commit_write_group()
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
68
            repo.unlock()
69
4431.3.7 by Jonathan Lange
Cherrypick bzr.dev 4470, resolving conflicts.
70
    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``
71
        repo = self.make_repository('.')
72
        repo.lock_write()
73
        repo.start_write_group()
4431.3.7 by Jonathan Lange
Cherrypick bzr.dev 4470, resolving conflicts.
74
        # commit_write_group can either return None (for repositories without
75
        # isolated transactions) or a hint for pack(). So we only check it
76
        # works in this interface test, because all repositories are exercised.
77
        repo.commit_write_group()
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
78
        repo.unlock()
79
2592.3.242 by Martin Pool
New method TestCase.call_catch_warnings
80
    def test_unlock_in_write_group(self):
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
81
        repo = self.make_repository('.')
82
        repo.lock_write()
83
        repo.start_write_group()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
84
        # 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``
85
        # really to be sure it's used right, not for signalling
86
        # semantic information.
4634.85.10 by Andrew Bennetts
Change test_unlock_in_write_group to expect a log_exception_quietly rather than a raise.
87
        self.assertLogsError(errors.BzrError, repo.unlock)
2592.3.244 by Martin Pool
unlock while in a write group now aborts the write group, unlocks, and errors.
88
        # after this error occurs, the repository is unlocked, and the write
89
        # group is gone.  you've had your chance, and you blew it. ;-)
90
        self.assertFalse(repo.is_locked())
91
        self.assertRaises(errors.BzrError, repo.commit_write_group)
92
        self.assertRaises(errors.BzrError, repo.unlock)
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
93
94
    def test_is_in_write_group(self):
95
        repo = self.make_repository('.')
96
        self.assertFalse(repo.is_in_write_group())
97
        repo.lock_write()
98
        repo.start_write_group()
99
        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.
100
        repo.commit_write_group()
101
        self.assertFalse(repo.is_in_write_group())
102
        # abort also removes the in_write_group status.
103
        repo.start_write_group()
104
        self.assertTrue(repo.is_in_write_group())
105
        repo.abort_write_group()
106
        self.assertFalse(repo.is_in_write_group())
107
        repo.unlock()
108
109
    def test_abort_write_group_gets_None(self):
110
        repo = self.make_repository('.')
111
        repo.lock_write()
112
        repo.start_write_group()
113
        self.assertEqual(None, repo.abort_write_group())
2617.6.1 by Robert Collins
* New method on Repository - ``start_write_group``, ``end_write_group``
114
        repo.unlock()
3825.4.1 by Andrew Bennetts
Add suppress_errors to abort_write_group.
115
116
    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.
117
        if self.transport_server is local.LocalURLServer:
118
            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.
119
        self.vfs_transport_factory = memory.MemoryServer
3825.4.1 by Andrew Bennetts
Add suppress_errors to abort_write_group.
120
        repo = self.make_repository('repo')
121
        token = repo.lock_write()
122
        self.addCleanup(repo.unlock)
123
        repo.start_write_group()
124
        # Damage the repository on the filesystem
4327.1.9 by Vincent Ladeuil
Fix 4 more lock-related test failures.
125
        t = self.get_transport('')
126
        t.rename('repo', 'foo')
127
        self.addCleanup(t.rename, 'foo', 'repo')
3825.4.1 by Andrew Bennetts
Add suppress_errors to abort_write_group.
128
        # abort_write_group will not raise an error, because either an
129
        # exception was not generated, or the exception was caught and
130
        # suppressed.  See also test_pack_repository's test of the same name.
131
        self.assertEqual(None, repo.abort_write_group(suppress_errors=True))
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
132
4617.1.1 by Robert Collins
Teach the check for get_missing_parent_inventories that this test applies to direct repositories not RemoteRepository.
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')
4617.1.1 by Robert Collins
Teach the check for get_missing_parent_inventories that this test applies to direct repositories not RemoteRepository.
335
        if (not repo._format.supports_external_lookups or
336
            isinstance(repo, remote.RemoteRepository)):
337
            raise TestNotApplicable(
338
                'only valid for direct connections to resumable repos')
4343.3.30 by John Arbash Meinel
Add tests that when resuming a write group, we start checking if
339
        # log calls to get_missing_parent_inventories, so that we can assert it
340
        # is called with the correct parameters
341
        call_log = []
342
        orig = repo.get_missing_parent_inventories
343
        def get_missing(check_for_missing_texts=True):
344
            call_log.append(check_for_missing_texts)
345
            return orig(check_for_missing_texts=check_for_missing_texts)
346
        repo.get_missing_parent_inventories = get_missing
347
        repo.lock_write()
348
        self.addCleanup(repo.unlock)
349
        sink = repo._get_sink()
350
        sink.insert_stream((), repo._format, [])
351
        self.assertEqual([False], call_log)
352
        del call_log[:]
353
        repo.start_write_group()
354
        # We need to insert something, or suspend_write_group won't actually
355
        # create a token
356
        repo.texts.insert_record_stream([versionedfile.FulltextContentFactory(
357
            ('file-id', 'rev-id'), (), None, 'lines\n')])
358
        tokens = repo.suspend_write_group()
359
        self.assertNotEqual([], tokens)
360
        sink.insert_stream((), repo._format, tokens)
361
        self.assertEqual([True], call_log)
4343.3.29 by John Arbash Meinel
Add 'check_for_missing_texts' flag to get_missing_parent_inv..
362
4002.1.3 by Andrew Bennetts
Remove more duplication, put resumable write group tests in separate TestCase.
363
364
class TestResumeableWriteGroup(TestCaseWithRepository):
365
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
366
    def make_write_locked_repo(self, relpath='repo'):
367
        repo = self.make_repository(relpath)
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
368
        repo.lock_write()
369
        self.addCleanup(repo.unlock)
370
        return repo
371
372
    def reopen_repo(self, repo):
373
        same_repo = repo.bzrdir.open_repository()
374
        same_repo.lock_write()
375
        self.addCleanup(same_repo.unlock)
376
        return same_repo
377
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
378
    def require_suspendable_write_groups(self, reason):
379
        repo = self.make_repository('__suspend_test')
380
        repo.lock_write()
381
        self.addCleanup(repo.unlock)
382
        repo.start_write_group()
383
        try:
384
            wg_tokens = repo.suspend_write_group()
385
        except errors.UnsuspendableWriteGroup:
386
            repo.abort_write_group()
387
            raise TestNotApplicable(reason)
388
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
389
    def test_suspend_write_group(self):
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
390
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
391
        repo.start_write_group()
392
        # Add some content so this isn't an empty write group (which may return
393
        # 0 tokens)
394
        repo.texts.add_lines(('file-id', 'revid'), (), ['lines'])
395
        try:
396
            wg_tokens = repo.suspend_write_group()
397
        except errors.UnsuspendableWriteGroup:
398
            # The contract for repos that don't support suspending write groups
399
            # is that suspend_write_group raises UnsuspendableWriteGroup, but
400
            # is otherwise a no-op.  So we can still e.g. abort the write group
401
            # as usual.
402
            self.assertTrue(repo.is_in_write_group())
403
            repo.abort_write_group()
404
        else:
405
            # After suspending a write group we are no longer in a write group
406
            self.assertFalse(repo.is_in_write_group())
407
            # suspend_write_group returns a list of tokens, which are strs.  If
408
            # no other write groups were resumed, there will only be one token.
409
            self.assertEqual(1, len(wg_tokens))
410
            self.assertIsInstance(wg_tokens[0], str)
411
            # See also test_pack_repository's test of the same name.
412
413
    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.
414
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
415
        repo.start_write_group()
416
        # Add some content so this isn't an empty write group (which may return
417
        # 0 tokens)
418
        text_key = ('file-id', 'revid')
419
        repo.texts.add_lines(text_key, (), ['lines'])
420
        try:
421
            wg_tokens = repo.suspend_write_group()
422
        except errors.UnsuspendableWriteGroup:
423
            # If the repo does not support suspending write groups, it doesn't
424
            # support resuming them either.
425
            repo.abort_write_group()
426
            self.assertRaises(
427
                errors.UnsuspendableWriteGroup, repo.resume_write_group, [])
428
        else:
429
            #self.assertEqual([], list(repo.texts.keys()))
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
430
            same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
431
            same_repo.resume_write_group(wg_tokens)
432
            self.assertEqual([text_key], list(same_repo.texts.keys()))
433
            self.assertTrue(same_repo.is_in_write_group())
434
            same_repo.abort_write_group()
435
            self.assertEqual([], list(repo.texts.keys()))
436
            # See also test_pack_repository's test of the same name.
437
438
    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).
439
        self.require_suspendable_write_groups(
440
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
441
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
442
        repo.start_write_group()
443
        # Add some content so this isn't an empty write group (which may return
444
        # 0 tokens)
445
        first_key = ('file-id', 'revid')
446
        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).
447
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
448
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
449
        same_repo.resume_write_group(wg_tokens)
450
        self.assertTrue(same_repo.is_in_write_group())
451
        second_key = ('file-id', 'second-revid')
452
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
453
        try:
454
            new_wg_tokens = same_repo.suspend_write_group()
455
        except:
456
            e = sys.exc_info()
457
            same_repo.abort_write_group(suppress_errors=True)
458
            raise e[0], e[1], e[2]
459
        self.assertEqual(2, len(new_wg_tokens))
460
        self.assertSubset(wg_tokens, new_wg_tokens)
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
461
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
462
        same_repo.resume_write_group(new_wg_tokens)
463
        both_keys = set([first_key, second_key])
464
        self.assertEqual(both_keys, same_repo.texts.keys())
465
        same_repo.abort_write_group()
466
467
    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).
468
        self.require_suspendable_write_groups(
469
            '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.
470
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
471
        repo.start_write_group()
472
        # Add some content so this isn't an empty write group (which may return
473
        # 0 tokens)
474
        text_key = ('file-id', 'revid')
475
        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).
476
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
477
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
478
        same_repo.resume_write_group(wg_tokens)
479
        new_wg_tokens = same_repo.suspend_write_group()
480
        self.assertEqual(wg_tokens, new_wg_tokens)
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
481
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
482
        same_repo.resume_write_group(wg_tokens)
483
        self.assertEqual([text_key], list(same_repo.texts.keys()))
484
        same_repo.abort_write_group()
485
486
    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).
487
        self.require_suspendable_write_groups(
488
            'Cannot test suspend on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
489
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
490
        repo.start_write_group()
491
        # Add some content so this isn't an empty write group (which may return
492
        # 0 tokens)
493
        text_key = ('file-id', 'revid')
494
        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).
495
        wg_tokens = repo.suspend_write_group()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
496
        self.assertEqual([], list(repo.texts.keys()))
497
498
    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).
499
        self.require_suspendable_write_groups(
500
            'Cannot test suspend on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
501
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
502
        repo.start_write_group()
503
        # Add some content so this isn't an empty write group (which may return
504
        # 0 tokens)
505
        text_key = ('file-id', 'revid')
506
        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).
507
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
508
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
509
        same_repo.resume_write_group(wg_tokens)
510
        same_repo.suspend_write_group()
511
        self.assertEqual([], list(same_repo.texts.keys()))
512
513
    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).
514
        self.require_suspendable_write_groups(
515
            'Cannot test suspend on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
516
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
517
        repo.start_write_group()
518
        # Add some content so this isn't an empty write group (which may return
519
        # 0 tokens)
520
        text_key = ('file-id', 'revid')
521
        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).
522
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
523
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
524
        same_repo.resume_write_group(wg_tokens)
525
        same_repo.abort_write_group()
526
        self.assertEqual([], list(same_repo.texts.keys()))
527
528
    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).
529
        self.require_suspendable_write_groups(
530
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
531
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
532
        repo.start_write_group()
533
        # Add some content so this isn't an empty write group (which may return
534
        # 0 tokens)
535
        text_key = ('file-id', 'revid')
536
        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).
537
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
538
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
539
        same_repo.resume_write_group(wg_tokens)
540
        same_repo.abort_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
541
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
542
        self.assertRaises(
4002.1.7 by Andrew Bennetts
Rename UnresumableWriteGroups to UnresumableWriteGroup.
543
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
544
            wg_tokens)
545
546
    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).
547
        self.require_suspendable_write_groups(
548
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
549
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
550
        repo.start_write_group()
551
        # Add some content so this isn't an empty write group (which may return
552
        # 0 tokens)
553
        text_key = ('file-id', 'revid')
554
        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).
555
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
556
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
557
        same_repo.resume_write_group(wg_tokens)
558
        same_repo.commit_write_group()
559
        self.assertEqual([text_key], list(same_repo.texts.keys()))
560
        self.assertEqual(
561
            'lines', same_repo.texts.get_record_stream([text_key],
562
                'unordered', True).next().get_bytes_as('fulltext'))
563
        self.assertRaises(
4002.1.7 by Andrew Bennetts
Rename UnresumableWriteGroups to UnresumableWriteGroup.
564
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
565
            wg_tokens)
566
567
    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).
568
        self.require_suspendable_write_groups(
569
            'Cannot test resume on repo that does not support suspending')
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
570
        repo = self.make_write_locked_repo()
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
571
        repo.start_write_group()
572
        # Add some content so this isn't an empty write group (which may return
573
        # 0 tokens)
574
        first_key = ('file-id', 'revid')
575
        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).
576
        wg_tokens = repo.suspend_write_group()
4002.1.2 by Andrew Bennetts
Refactor out some duplication in write_group tests.
577
        same_repo = self.reopen_repo(repo)
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
578
        same_repo.resume_write_group(wg_tokens)
579
        second_key = ('file-id', 'second-revid')
580
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
581
        same_repo.commit_write_group()
582
        self.assertEqual(
583
            set([first_key, second_key]), set(same_repo.texts.keys()))
584
        self.assertEqual(
585
            'lines', same_repo.texts.get_record_stream([first_key],
586
                'unordered', True).next().get_bytes_as('fulltext'))
587
        self.assertEqual(
588
            'more lines', same_repo.texts.get_record_stream([second_key],
589
                'unordered', True).next().get_bytes_as('fulltext'))
590
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
591
    def make_source_with_delta_record(self):
592
        # Make a source repository with a delta record in it.
593
        source_repo = self.make_write_locked_repo('source')
594
        source_repo.start_write_group()
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
595
        key_base = ('file-id', 'base')
596
        key_delta = ('file-id', 'delta')
4343.3.6 by John Arbash Meinel
Take out the _delta_across_repos flag.
597
        def text_stream():
598
            yield versionedfile.FulltextContentFactory(
599
                key_base, (), None, 'lines\n')
600
            yield versionedfile.FulltextContentFactory(
601
                key_delta, (key_base,), None, 'more\nlines\n')
602
        source_repo.texts.insert_record_stream(text_stream())
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
603
        source_repo.commit_write_group()
604
        return source_repo
605
4002.1.12 by Andrew Bennetts
Add another test, fix the code so it passes, and remove some cruft.
606
    def test_commit_resumed_write_group_with_missing_parents(self):
607
        self.require_suspendable_write_groups(
608
            'Cannot test resume on repo that does not support suspending')
609
        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.
610
        key_base = ('file-id', 'base')
611
        key_delta = ('file-id', 'delta')
4002.1.12 by Andrew Bennetts
Add another test, fix the code so it passes, and remove some cruft.
612
        # Start a write group, insert just a delta.
613
        repo = self.make_write_locked_repo()
614
        repo.start_write_group()
615
        stream = source_repo.texts.get_record_stream(
616
            [key_delta], 'unordered', False)
617
        repo.texts.insert_record_stream(stream)
4343.3.6 by John Arbash Meinel
Take out the _delta_across_repos flag.
618
        # It's either not commitable due to the missing compression parent, or
619
        # the stacked location has already filled in the fulltext.
620
        try:
621
            repo.commit_write_group()
622
        except errors.BzrCheckError:
623
            # It refused to commit because we have a missing parent
624
            pass
625
        else:
626
            same_repo = self.reopen_repo(repo)
627
            same_repo.lock_read()
628
            record = same_repo.texts.get_record_stream([key_delta],
629
                                                       'unordered', True).next()
630
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
631
            return
4002.1.12 by Andrew Bennetts
Add another test, fix the code so it passes, and remove some cruft.
632
        # Merely suspending and resuming doesn't make it commitable either.
633
        wg_tokens = repo.suspend_write_group()
634
        same_repo = self.reopen_repo(repo)
635
        same_repo.resume_write_group(wg_tokens)
636
        self.assertRaises(
637
            errors.BzrCheckError, same_repo.commit_write_group)
638
        same_repo.abort_write_group()
639
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
640
    def test_commit_resumed_write_group_adding_missing_parents(self):
641
        self.require_suspendable_write_groups(
642
            '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.
643
        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.
644
        key_base = ('file-id', 'base')
645
        key_delta = ('file-id', 'delta')
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
646
        # Start a write group.
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
647
        repo = self.make_write_locked_repo()
648
        repo.start_write_group()
649
        # Add some content so this isn't an empty write group (which may return
650
        # 0 tokens)
651
        text_key = ('file-id', 'revid')
652
        repo.texts.add_lines(text_key, (), ['lines'])
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
653
        # Suspend it, then resume it.
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
654
        wg_tokens = repo.suspend_write_group()
655
        same_repo = self.reopen_repo(repo)
656
        same_repo.resume_write_group(wg_tokens)
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
657
        # Add a record with a missing compression parent
658
        stream = source_repo.texts.get_record_stream(
659
            [key_delta], 'unordered', False)
660
        same_repo.texts.insert_record_stream(stream)
661
        # Just like if we'd added that record without a suspend/resume cycle,
662
        # commit_write_group fails.
4343.3.6 by John Arbash Meinel
Take out the _delta_across_repos flag.
663
        try:
664
            same_repo.commit_write_group()
665
        except errors.BzrCheckError:
666
            pass
667
        else:
668
            # If the commit_write_group didn't fail, that is because the
669
            # insert_record_stream already gave it a fulltext.
670
            same_repo = self.reopen_repo(repo)
671
            same_repo.lock_read()
672
            record = same_repo.texts.get_record_stream([key_delta],
673
                                                       'unordered', True).next()
674
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
675
            return
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
676
        same_repo.abort_write_group()
677
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
678
    def test_add_missing_parent_after_resume(self):
679
        self.require_suspendable_write_groups(
680
            'Cannot test resume on repo that does not support suspending')
681
        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.
682
        key_base = ('file-id', 'base')
683
        key_delta = ('file-id', 'delta')
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
684
        # Start a write group, insert just a delta.
685
        repo = self.make_write_locked_repo()
686
        repo.start_write_group()
687
        stream = source_repo.texts.get_record_stream(
688
            [key_delta], 'unordered', False)
689
        repo.texts.insert_record_stream(stream)
690
        # Suspend it, then resume it.
691
        wg_tokens = repo.suspend_write_group()
692
        same_repo = self.reopen_repo(repo)
4002.1.11 by Andrew Bennetts
Fix latest test.
693
        same_repo.resume_write_group(wg_tokens)
4002.1.10 by Andrew Bennetts
Fix some existing tests, add a new test that fails.
694
        # Fill in the missing compression parent.
695
        stream = source_repo.texts.get_record_stream(
696
            [key_base], 'unordered', False)
697
        same_repo.texts.insert_record_stream(stream)
698
        same_repo.commit_write_group()
699
4002.1.4 by Andrew Bennetts
Remove a little more duplication, add some more tests (some passing, some not).
700
    def test_suspend_empty_initial_write_group(self):
701
        """Suspending a write group with no writes returns an empty token
702
        list.
703
        """
704
        self.require_suspendable_write_groups(
705
            'Cannot test suspend on repo that does not support suspending')
706
        repo = self.make_write_locked_repo()
707
        repo.start_write_group()
708
        wg_tokens = repo.suspend_write_group()
709
        self.assertEqual([], wg_tokens)
710
711
    def test_suspend_empty_initial_write_group(self):
712
        """Resuming an empty token list is equivalent to start_write_group."""
713
        self.require_suspendable_write_groups(
714
            'Cannot test resume on repo that does not support suspending')
715
        repo = self.make_write_locked_repo()
716
        repo.resume_write_group([])
717
        repo.abort_write_group()
4032.1.2 by John Arbash Meinel
Track down a few more files that have trailing whitespace.
718
4002.1.1 by Andrew Bennetts
Implement suspend_write_group/resume_write_group.
719