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