/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
1
# Copyright (C) 2007-2011 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Tests for repository write groups."""
18
19
import sys
20
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
21
from breezy import (
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
22
    branch,
6472.2.3 by Jelmer Vernooij
More control dir.
23
    controldir,
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
24
    errors,
25
    memorytree,
26
    tests,
6670.4.1 by Jelmer Vernooij
Update imports.
27
    )
28
from breezy.bzr import (
6670.4.3 by Jelmer Vernooij
Fix more imports.
29
    branch as bzrbranch,
6670.4.14 by Jelmer Vernooij
Move remote to breezy.bzr.
30
    remote,
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
31
    versionedfile,
32
    )
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
33
from breezy.tests.per_repository_vf import (
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
34
    TestCaseWithRepository,
35
    all_repository_vf_format_scenarios,
36
    )
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
37
from breezy.tests.scenarios import load_tests_apply_scenarios
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
38
39
40
load_tests = load_tests_apply_scenarios
41
42
43
class TestGetMissingParentInventories(TestCaseWithRepository):
44
45
    scenarios = all_repository_vf_format_scenarios()
46
47
    def test_empty_get_missing_parent_inventories(self):
48
        """A new write group has no missing parent inventories."""
49
        repo = self.make_repository('.')
50
        repo.lock_write()
51
        repo.start_write_group()
52
        try:
53
            self.assertEqual(set(), set(repo.get_missing_parent_inventories()))
54
        finally:
55
            repo.commit_write_group()
56
            repo.unlock()
57
58
    def branch_trunk_and_make_tree(self, trunk_repo, relpath):
59
        tree = self.make_branch_and_memory_tree('branch')
60
        trunk_repo.lock_read()
61
        self.addCleanup(trunk_repo.unlock)
62
        tree.branch.repository.fetch(trunk_repo, revision_id='rev-1')
63
        tree.set_parent_ids(['rev-1'])
64
        return tree 
65
66
    def make_first_commit(self, repo):
6653.6.1 by Jelmer Vernooij
Rename a number of attributes from bzrdir to controldir.
67
        trunk = repo.controldir.create_branch()
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
68
        tree = memorytree.MemoryTree.create_on_branch(trunk)
69
        tree.lock_write()
70
        tree.add([''], ['TREE_ROOT'], ['directory'])
71
        tree.add(['dir'], ['dir-id'], ['directory'])
72
        tree.add(['filename'], ['file-id'], ['file'])
73
        tree.put_file_bytes_non_atomic('file-id', 'content\n')
74
        tree.commit('Trunk commit', rev_id='rev-0')
75
        tree.commit('Trunk commit', rev_id='rev-1')
76
        tree.unlock()
77
78
    def make_new_commit_in_new_repo(self, trunk_repo, parents=None):
79
        tree = self.branch_trunk_and_make_tree(trunk_repo, 'branch')
80
        tree.set_parent_ids(parents)
81
        tree.commit('Branch commit', rev_id='rev-2')
82
        branch_repo = tree.branch.repository
83
        branch_repo.lock_read()
84
        self.addCleanup(branch_repo.unlock)
85
        return branch_repo
86
87
    def make_stackable_repo(self, relpath='trunk'):
88
        if isinstance(self.repository_format, remote.RemoteRepositoryFormat):
89
            # RemoteRepository by default builds a default format real
90
            # repository, but the default format is unstackble.  So explicitly
91
            # make a stackable real repository and use that.
92
            repo = self.make_repository(relpath, format='1.9')
6472.2.3 by Jelmer Vernooij
More control dir.
93
            dir = controldir.ControlDir.open(self.get_url(relpath))
94
            repo = dir.open_repository()
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
95
        else:
96
            repo = self.make_repository(relpath)
97
        if not repo._format.supports_external_lookups:
98
            raise tests.TestNotApplicable('format not stackable')
6653.6.4 by Jelmer Vernooij
Merge trunk.
99
        repo.controldir._format.set_branch_format(bzrbranch.BzrBranchFormat7())
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
100
        return repo
101
102
    def reopen_repo_and_resume_write_group(self, repo):
103
        try:
104
            resume_tokens = repo.suspend_write_group()
105
        except errors.UnsuspendableWriteGroup:
106
            # If we got this far, and this repo does not support resuming write
107
            # groups, then get_missing_parent_inventories works in all
108
            # cases this repo supports.
109
            repo.unlock()
110
            return
111
        repo.unlock()
6653.6.1 by Jelmer Vernooij
Rename a number of attributes from bzrdir to controldir.
112
        reopened_repo = repo.controldir.open_repository()
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
113
        reopened_repo.lock_write()
114
        self.addCleanup(reopened_repo.unlock)
115
        reopened_repo.resume_write_group(resume_tokens)
116
        return reopened_repo
117
118
    def test_ghost_revision(self):
119
        """A parent inventory may be absent if all the needed texts are present.
120
        i.e., a ghost revision isn't (necessarily) considered to be a missing
121
        parent inventory.
122
        """
123
        # Make a trunk with one commit.
124
        trunk_repo = self.make_stackable_repo()
125
        self.make_first_commit(trunk_repo)
126
        trunk_repo.lock_read()
127
        self.addCleanup(trunk_repo.unlock)
128
        # Branch the trunk, add a new commit.
129
        branch_repo = self.make_new_commit_in_new_repo(
130
            trunk_repo, parents=['rev-1', 'ghost-rev'])
131
        inv = branch_repo.get_inventory('rev-2')
132
        # Make a new repo stacked on trunk, and then copy into it:
133
        #  - all texts in rev-2
134
        #  - the new inventory (rev-2)
135
        #  - the new revision (rev-2)
136
        repo = self.make_stackable_repo('stacked')
137
        repo.lock_write()
138
        repo.start_write_group()
139
        # Add all texts from in rev-2 inventory.  Note that this has to exclude
140
        # the root if the repo format does not support rich roots.
141
        rich_root = branch_repo._format.rich_root_data
142
        all_texts = [
143
            (ie.file_id, ie.revision) for ie in inv.iter_just_entries()
144
             if rich_root or inv.id2path(ie.file_id) != '']
145
        repo.texts.insert_record_stream(
146
            branch_repo.texts.get_record_stream(all_texts, 'unordered', False))
147
        # Add inventory and revision for rev-2.
148
        repo.add_inventory('rev-2', inv, ['rev-1', 'ghost-rev'])
149
        repo.revisions.insert_record_stream(
150
            branch_repo.revisions.get_record_stream(
151
                [('rev-2',)], 'unordered', False))
152
        # Now, no inventories are reported as missing, even though there is a
153
        # ghost.
154
        self.assertEqual(set(), repo.get_missing_parent_inventories())
155
        # Resuming the write group does not affect
156
        # get_missing_parent_inventories.
157
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
158
        self.assertEqual(set(), reopened_repo.get_missing_parent_inventories())
159
        reopened_repo.abort_write_group()
160
161
    def test_get_missing_parent_inventories(self):
162
        """A stacked repo with a single revision and inventory (no parent
163
        inventory) in it must have all the texts in its inventory (even if not
164
        changed w.r.t. to the absent parent), otherwise it will report missing
165
        texts/parent inventory.
166
167
        The core of this test is that a file was changed in rev-1, but in a
168
        stacked repo that only has rev-2
169
        """
170
        # Make a trunk with one commit.
171
        trunk_repo = self.make_stackable_repo()
172
        self.make_first_commit(trunk_repo)
173
        trunk_repo.lock_read()
174
        self.addCleanup(trunk_repo.unlock)
175
        # Branch the trunk, add a new commit.
176
        branch_repo = self.make_new_commit_in_new_repo(
177
            trunk_repo, parents=['rev-1'])
178
        inv = branch_repo.get_inventory('rev-2')
179
        # Make a new repo stacked on trunk, and copy the new commit's revision
180
        # and inventory records to it.
181
        repo = self.make_stackable_repo('stacked')
182
        repo.lock_write()
183
        repo.start_write_group()
184
        # Insert a single fulltext inv (using add_inventory because it's
185
        # simpler than insert_record_stream)
186
        repo.add_inventory('rev-2', inv, ['rev-1'])
187
        repo.revisions.insert_record_stream(
188
            branch_repo.revisions.get_record_stream(
189
                [('rev-2',)], 'unordered', False))
190
        # There should be no missing compression parents
191
        self.assertEqual(set(),
192
                repo.inventories.get_missing_compression_parent_keys())
193
        self.assertEqual(
6619.3.12 by Jelmer Vernooij
Use 2to3 set_literal fixer.
194
            {('inventories', 'rev-1')},
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
195
            repo.get_missing_parent_inventories())
196
        # Resuming the write group does not affect
197
        # get_missing_parent_inventories.
198
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
199
        self.assertEqual(
6619.3.12 by Jelmer Vernooij
Use 2to3 set_literal fixer.
200
            {('inventories', 'rev-1')},
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
201
            reopened_repo.get_missing_parent_inventories())
202
        # Adding the parent inventory satisfies get_missing_parent_inventories.
203
        reopened_repo.inventories.insert_record_stream(
204
            branch_repo.inventories.get_record_stream(
205
                [('rev-1',)], 'unordered', False))
206
        self.assertEqual(
207
            set(), reopened_repo.get_missing_parent_inventories())
208
        reopened_repo.abort_write_group()
209
210
    def test_get_missing_parent_inventories_check(self):
211
        builder = self.make_branch_builder('test')
212
        builder.build_snapshot('A-id', ['ghost-parent-id'], [
213
            ('add', ('', 'root-id', 'directory', None)),
214
            ('add', ('file', 'file-id', 'file', 'content\n'))],
215
            allow_leftmost_as_ghost=True)
216
        b = builder.get_branch()
217
        b.lock_read()
218
        self.addCleanup(b.unlock)
219
        repo = self.make_repository('test-repo')
220
        repo.lock_write()
221
        self.addCleanup(repo.unlock)
222
        repo.start_write_group()
223
        self.addCleanup(repo.abort_write_group)
224
        # Now, add the objects manually
225
        text_keys = [('file-id', 'A-id')]
226
        if repo.supports_rich_root():
227
            text_keys.append(('root-id', 'A-id'))
228
        # Directly add the texts, inventory, and revision object for 'A-id'
229
        repo.texts.insert_record_stream(b.repository.texts.get_record_stream(
230
            text_keys, 'unordered', True))
231
        repo.add_revision('A-id', b.repository.get_revision('A-id'),
232
                          b.repository.get_inventory('A-id'))
233
        get_missing = repo.get_missing_parent_inventories
234
        if repo._format.supports_external_lookups:
6619.3.12 by Jelmer Vernooij
Use 2to3 set_literal fixer.
235
            self.assertEqual({('inventories', 'ghost-parent-id')},
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
236
                get_missing(check_for_missing_texts=False))
237
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
238
            self.assertEqual(set(), get_missing())
239
        else:
240
            # If we don't support external lookups, we always return empty
241
            self.assertEqual(set(), get_missing(check_for_missing_texts=False))
242
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
243
            self.assertEqual(set(), get_missing())
244
245
    def test_insert_stream_passes_resume_info(self):
246
        repo = self.make_repository('test-repo')
247
        if (not repo._format.supports_external_lookups or
248
            isinstance(repo, remote.RemoteRepository)):
249
            raise tests.TestNotApplicable(
250
                'only valid for direct connections to resumable repos')
251
        # log calls to get_missing_parent_inventories, so that we can assert it
252
        # is called with the correct parameters
253
        call_log = []
254
        orig = repo.get_missing_parent_inventories
255
        def get_missing(check_for_missing_texts=True):
256
            call_log.append(check_for_missing_texts)
257
            return orig(check_for_missing_texts=check_for_missing_texts)
258
        repo.get_missing_parent_inventories = get_missing
259
        repo.lock_write()
260
        self.addCleanup(repo.unlock)
261
        sink = repo._get_sink()
262
        sink.insert_stream((), repo._format, [])
263
        self.assertEqual([False], call_log)
264
        del call_log[:]
265
        repo.start_write_group()
266
        # We need to insert something, or suspend_write_group won't actually
267
        # create a token
268
        repo.texts.insert_record_stream([versionedfile.FulltextContentFactory(
269
            ('file-id', 'rev-id'), (), None, 'lines\n')])
270
        tokens = repo.suspend_write_group()
271
        self.assertNotEqual([], tokens)
272
        sink.insert_stream((), repo._format, tokens)
273
        self.assertEqual([True], call_log)
274
275
    def test_insert_stream_without_locking_fails_without_lock(self):
276
        repo = self.make_repository('test-repo')
277
        sink = repo._get_sink()
278
        stream = [('texts', [versionedfile.FulltextContentFactory(
279
            ('file-id', 'rev-id'), (), None, 'lines\n')])]
280
        self.assertRaises(errors.ObjectNotLocked,
281
            sink.insert_stream_without_locking, stream, repo._format)
282
283
    def test_insert_stream_without_locking_fails_without_write_group(self):
284
        repo = self.make_repository('test-repo')
285
        self.addCleanup(repo.lock_write().unlock)
286
        sink = repo._get_sink()
287
        stream = [('texts', [versionedfile.FulltextContentFactory(
288
            ('file-id', 'rev-id'), (), None, 'lines\n')])]
289
        self.assertRaises(errors.BzrError,
290
            sink.insert_stream_without_locking, stream, repo._format)
291
292
    def test_insert_stream_without_locking(self):
293
        repo = self.make_repository('test-repo')
294
        self.addCleanup(repo.lock_write().unlock)
295
        repo.start_write_group()
296
        sink = repo._get_sink()
297
        stream = [('texts', [versionedfile.FulltextContentFactory(
298
            ('file-id', 'rev-id'), (), None, 'lines\n')])]
299
        missing_keys = sink.insert_stream_without_locking(stream, repo._format)
300
        repo.commit_write_group()
301
        self.assertEqual(set(), missing_keys)
302
303
304
class TestResumeableWriteGroup(TestCaseWithRepository):
305
306
    scenarios = all_repository_vf_format_scenarios()
307
308
    def make_write_locked_repo(self, relpath='repo'):
309
        repo = self.make_repository(relpath)
310
        repo.lock_write()
311
        self.addCleanup(repo.unlock)
312
        return repo
313
314
    def reopen_repo(self, repo):
6653.6.1 by Jelmer Vernooij
Rename a number of attributes from bzrdir to controldir.
315
        same_repo = repo.controldir.open_repository()
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
316
        same_repo.lock_write()
317
        self.addCleanup(same_repo.unlock)
318
        return same_repo
319
320
    def require_suspendable_write_groups(self, reason):
321
        repo = self.make_repository('__suspend_test')
322
        repo.lock_write()
323
        self.addCleanup(repo.unlock)
324
        repo.start_write_group()
325
        try:
326
            wg_tokens = repo.suspend_write_group()
327
        except errors.UnsuspendableWriteGroup:
328
            repo.abort_write_group()
329
            raise tests.TestNotApplicable(reason)
330
331
    def test_suspend_write_group(self):
332
        repo = self.make_write_locked_repo()
333
        repo.start_write_group()
334
        # Add some content so this isn't an empty write group (which may return
335
        # 0 tokens)
336
        repo.texts.add_lines(('file-id', 'revid'), (), ['lines'])
337
        try:
338
            wg_tokens = repo.suspend_write_group()
339
        except errors.UnsuspendableWriteGroup:
340
            # The contract for repos that don't support suspending write groups
341
            # is that suspend_write_group raises UnsuspendableWriteGroup, but
342
            # is otherwise a no-op.  So we can still e.g. abort the write group
343
            # as usual.
344
            self.assertTrue(repo.is_in_write_group())
345
            repo.abort_write_group()
346
        else:
347
            # After suspending a write group we are no longer in a write group
348
            self.assertFalse(repo.is_in_write_group())
349
            # suspend_write_group returns a list of tokens, which are strs.  If
350
            # no other write groups were resumed, there will only be one token.
351
            self.assertEqual(1, len(wg_tokens))
352
            self.assertIsInstance(wg_tokens[0], str)
353
            # See also test_pack_repository's test of the same name.
354
355
    def test_resume_write_group_then_abort(self):
356
        repo = self.make_write_locked_repo()
357
        repo.start_write_group()
358
        # Add some content so this isn't an empty write group (which may return
359
        # 0 tokens)
360
        text_key = ('file-id', 'revid')
361
        repo.texts.add_lines(text_key, (), ['lines'])
362
        try:
363
            wg_tokens = repo.suspend_write_group()
364
        except errors.UnsuspendableWriteGroup:
365
            # If the repo does not support suspending write groups, it doesn't
366
            # support resuming them either.
367
            repo.abort_write_group()
368
            self.assertRaises(
369
                errors.UnsuspendableWriteGroup, repo.resume_write_group, [])
370
        else:
371
            #self.assertEqual([], list(repo.texts.keys()))
372
            same_repo = self.reopen_repo(repo)
373
            same_repo.resume_write_group(wg_tokens)
374
            self.assertEqual([text_key], list(same_repo.texts.keys()))
375
            self.assertTrue(same_repo.is_in_write_group())
376
            same_repo.abort_write_group()
377
            self.assertEqual([], list(repo.texts.keys()))
378
            # See also test_pack_repository's test of the same name.
379
380
    def test_multiple_resume_write_group(self):
381
        self.require_suspendable_write_groups(
382
            'Cannot test resume on repo that does not support suspending')
383
        repo = self.make_write_locked_repo()
384
        repo.start_write_group()
385
        # Add some content so this isn't an empty write group (which may return
386
        # 0 tokens)
387
        first_key = ('file-id', 'revid')
388
        repo.texts.add_lines(first_key, (), ['lines'])
389
        wg_tokens = repo.suspend_write_group()
390
        same_repo = self.reopen_repo(repo)
391
        same_repo.resume_write_group(wg_tokens)
392
        self.assertTrue(same_repo.is_in_write_group())
393
        second_key = ('file-id', 'second-revid')
394
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
395
        try:
396
            new_wg_tokens = same_repo.suspend_write_group()
397
        except:
398
            same_repo.abort_write_group(suppress_errors=True)
6621.14.1 by Martin
Remove remaining uses of multi-argument raise
399
            raise
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
400
        self.assertEqual(2, len(new_wg_tokens))
401
        self.assertSubset(wg_tokens, new_wg_tokens)
402
        same_repo = self.reopen_repo(repo)
403
        same_repo.resume_write_group(new_wg_tokens)
6619.3.12 by Jelmer Vernooij
Use 2to3 set_literal fixer.
404
        both_keys = {first_key, second_key}
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
405
        self.assertEqual(both_keys, same_repo.texts.keys())
406
        same_repo.abort_write_group()
407
408
    def test_no_op_suspend_resume(self):
409
        self.require_suspendable_write_groups(
410
            'Cannot test resume on repo that does not support suspending')
411
        repo = self.make_write_locked_repo()
412
        repo.start_write_group()
413
        # Add some content so this isn't an empty write group (which may return
414
        # 0 tokens)
415
        text_key = ('file-id', 'revid')
416
        repo.texts.add_lines(text_key, (), ['lines'])
417
        wg_tokens = repo.suspend_write_group()
418
        same_repo = self.reopen_repo(repo)
419
        same_repo.resume_write_group(wg_tokens)
420
        new_wg_tokens = same_repo.suspend_write_group()
421
        self.assertEqual(wg_tokens, new_wg_tokens)
422
        same_repo = self.reopen_repo(repo)
423
        same_repo.resume_write_group(wg_tokens)
424
        self.assertEqual([text_key], list(same_repo.texts.keys()))
425
        same_repo.abort_write_group()
426
427
    def test_read_after_suspend_fails(self):
428
        self.require_suspendable_write_groups(
429
            'Cannot test suspend on repo that does not support suspending')
430
        repo = self.make_write_locked_repo()
431
        repo.start_write_group()
432
        # Add some content so this isn't an empty write group (which may return
433
        # 0 tokens)
434
        text_key = ('file-id', 'revid')
435
        repo.texts.add_lines(text_key, (), ['lines'])
436
        wg_tokens = repo.suspend_write_group()
437
        self.assertEqual([], list(repo.texts.keys()))
438
439
    def test_read_after_second_suspend_fails(self):
440
        self.require_suspendable_write_groups(
441
            'Cannot test suspend on repo that does not support suspending')
442
        repo = self.make_write_locked_repo()
443
        repo.start_write_group()
444
        # Add some content so this isn't an empty write group (which may return
445
        # 0 tokens)
446
        text_key = ('file-id', 'revid')
447
        repo.texts.add_lines(text_key, (), ['lines'])
448
        wg_tokens = repo.suspend_write_group()
449
        same_repo = self.reopen_repo(repo)
450
        same_repo.resume_write_group(wg_tokens)
451
        same_repo.suspend_write_group()
452
        self.assertEqual([], list(same_repo.texts.keys()))
453
454
    def test_read_after_resume_abort_fails(self):
455
        self.require_suspendable_write_groups(
456
            'Cannot test suspend on repo that does not support suspending')
457
        repo = self.make_write_locked_repo()
458
        repo.start_write_group()
459
        # Add some content so this isn't an empty write group (which may return
460
        # 0 tokens)
461
        text_key = ('file-id', 'revid')
462
        repo.texts.add_lines(text_key, (), ['lines'])
463
        wg_tokens = repo.suspend_write_group()
464
        same_repo = self.reopen_repo(repo)
465
        same_repo.resume_write_group(wg_tokens)
466
        same_repo.abort_write_group()
467
        self.assertEqual([], list(same_repo.texts.keys()))
468
469
    def test_cannot_resume_aborted_write_group(self):
470
        self.require_suspendable_write_groups(
471
            'Cannot test resume on repo that does not support suspending')
472
        repo = self.make_write_locked_repo()
473
        repo.start_write_group()
474
        # Add some content so this isn't an empty write group (which may return
475
        # 0 tokens)
476
        text_key = ('file-id', 'revid')
477
        repo.texts.add_lines(text_key, (), ['lines'])
478
        wg_tokens = repo.suspend_write_group()
479
        same_repo = self.reopen_repo(repo)
480
        same_repo.resume_write_group(wg_tokens)
481
        same_repo.abort_write_group()
482
        same_repo = self.reopen_repo(repo)
483
        self.assertRaises(
484
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
485
            wg_tokens)
486
487
    def test_commit_resumed_write_group_no_new_data(self):
488
        self.require_suspendable_write_groups(
489
            'Cannot test resume on repo that does not support suspending')
490
        repo = self.make_write_locked_repo()
491
        repo.start_write_group()
492
        # Add some content so this isn't an empty write group (which may return
493
        # 0 tokens)
494
        text_key = ('file-id', 'revid')
495
        repo.texts.add_lines(text_key, (), ['lines'])
496
        wg_tokens = repo.suspend_write_group()
497
        same_repo = self.reopen_repo(repo)
498
        same_repo.resume_write_group(wg_tokens)
499
        same_repo.commit_write_group()
500
        self.assertEqual([text_key], list(same_repo.texts.keys()))
501
        self.assertEqual(
502
            'lines', same_repo.texts.get_record_stream([text_key],
503
                'unordered', True).next().get_bytes_as('fulltext'))
504
        self.assertRaises(
505
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
506
            wg_tokens)
507
508
    def test_commit_resumed_write_group_plus_new_data(self):
509
        self.require_suspendable_write_groups(
510
            'Cannot test resume on repo that does not support suspending')
511
        repo = self.make_write_locked_repo()
512
        repo.start_write_group()
513
        # Add some content so this isn't an empty write group (which may return
514
        # 0 tokens)
515
        first_key = ('file-id', 'revid')
516
        repo.texts.add_lines(first_key, (), ['lines'])
517
        wg_tokens = repo.suspend_write_group()
518
        same_repo = self.reopen_repo(repo)
519
        same_repo.resume_write_group(wg_tokens)
520
        second_key = ('file-id', 'second-revid')
521
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
522
        same_repo.commit_write_group()
523
        self.assertEqual(
6619.3.12 by Jelmer Vernooij
Use 2to3 set_literal fixer.
524
            {first_key, second_key}, set(same_repo.texts.keys()))
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
525
        self.assertEqual(
526
            'lines', same_repo.texts.get_record_stream([first_key],
527
                'unordered', True).next().get_bytes_as('fulltext'))
528
        self.assertEqual(
529
            'more lines', same_repo.texts.get_record_stream([second_key],
530
                'unordered', True).next().get_bytes_as('fulltext'))
531
532
    def make_source_with_delta_record(self):
533
        # Make a source repository with a delta record in it.
534
        source_repo = self.make_write_locked_repo('source')
535
        source_repo.start_write_group()
536
        key_base = ('file-id', 'base')
537
        key_delta = ('file-id', 'delta')
538
        def text_stream():
539
            yield versionedfile.FulltextContentFactory(
540
                key_base, (), None, 'lines\n')
541
            yield versionedfile.FulltextContentFactory(
542
                key_delta, (key_base,), None, 'more\nlines\n')
543
        source_repo.texts.insert_record_stream(text_stream())
544
        source_repo.commit_write_group()
545
        return source_repo
546
547
    def test_commit_resumed_write_group_with_missing_parents(self):
548
        self.require_suspendable_write_groups(
549
            'Cannot test resume on repo that does not support suspending')
550
        source_repo = self.make_source_with_delta_record()
551
        key_base = ('file-id', 'base')
552
        key_delta = ('file-id', 'delta')
553
        # Start a write group, insert just a delta.
554
        repo = self.make_write_locked_repo()
555
        repo.start_write_group()
556
        stream = source_repo.texts.get_record_stream(
557
            [key_delta], 'unordered', False)
558
        repo.texts.insert_record_stream(stream)
559
        # It's either not commitable due to the missing compression parent, or
560
        # the stacked location has already filled in the fulltext.
561
        try:
562
            repo.commit_write_group()
563
        except errors.BzrCheckError:
564
            # It refused to commit because we have a missing parent
565
            pass
566
        else:
567
            same_repo = self.reopen_repo(repo)
568
            same_repo.lock_read()
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
569
            record = next(same_repo.texts.get_record_stream([key_delta],
570
                                                       'unordered', True))
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
571
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
572
            return
573
        # Merely suspending and resuming doesn't make it commitable either.
574
        wg_tokens = repo.suspend_write_group()
575
        same_repo = self.reopen_repo(repo)
576
        same_repo.resume_write_group(wg_tokens)
577
        self.assertRaises(
578
            errors.BzrCheckError, same_repo.commit_write_group)
579
        same_repo.abort_write_group()
580
581
    def test_commit_resumed_write_group_adding_missing_parents(self):
582
        self.require_suspendable_write_groups(
583
            'Cannot test resume on repo that does not support suspending')
584
        source_repo = self.make_source_with_delta_record()
585
        key_base = ('file-id', 'base')
586
        key_delta = ('file-id', 'delta')
587
        # Start a write group.
588
        repo = self.make_write_locked_repo()
589
        repo.start_write_group()
590
        # Add some content so this isn't an empty write group (which may return
591
        # 0 tokens)
592
        text_key = ('file-id', 'revid')
593
        repo.texts.add_lines(text_key, (), ['lines'])
594
        # Suspend it, then resume it.
595
        wg_tokens = repo.suspend_write_group()
596
        same_repo = self.reopen_repo(repo)
597
        same_repo.resume_write_group(wg_tokens)
598
        # Add a record with a missing compression parent
599
        stream = source_repo.texts.get_record_stream(
600
            [key_delta], 'unordered', False)
601
        same_repo.texts.insert_record_stream(stream)
602
        # Just like if we'd added that record without a suspend/resume cycle,
603
        # commit_write_group fails.
604
        try:
605
            same_repo.commit_write_group()
606
        except errors.BzrCheckError:
607
            pass
608
        else:
609
            # If the commit_write_group didn't fail, that is because the
610
            # insert_record_stream already gave it a fulltext.
611
            same_repo = self.reopen_repo(repo)
612
            same_repo.lock_read()
6634.2.1 by Martin
Apply 2to3 next fixer and make compatible
613
            record = next(same_repo.texts.get_record_stream([key_delta],
614
                                                       'unordered', True))
5844.2.2 by Jelmer Vernooij
Move vf-specific write group tests to per_repository_vf.
615
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
616
            return
617
        same_repo.abort_write_group()
618
619
    def test_add_missing_parent_after_resume(self):
620
        self.require_suspendable_write_groups(
621
            'Cannot test resume on repo that does not support suspending')
622
        source_repo = self.make_source_with_delta_record()
623
        key_base = ('file-id', 'base')
624
        key_delta = ('file-id', 'delta')
625
        # Start a write group, insert just a delta.
626
        repo = self.make_write_locked_repo()
627
        repo.start_write_group()
628
        stream = source_repo.texts.get_record_stream(
629
            [key_delta], 'unordered', False)
630
        repo.texts.insert_record_stream(stream)
631
        # Suspend it, then resume it.
632
        wg_tokens = repo.suspend_write_group()
633
        same_repo = self.reopen_repo(repo)
634
        same_repo.resume_write_group(wg_tokens)
635
        # Fill in the missing compression parent.
636
        stream = source_repo.texts.get_record_stream(
637
            [key_base], 'unordered', False)
638
        same_repo.texts.insert_record_stream(stream)
639
        same_repo.commit_write_group()
640
641
    def test_suspend_empty_initial_write_group(self):
642
        """Suspending a write group with no writes returns an empty token
643
        list.
644
        """
645
        self.require_suspendable_write_groups(
646
            'Cannot test suspend on repo that does not support suspending')
647
        repo = self.make_write_locked_repo()
648
        repo.start_write_group()
649
        wg_tokens = repo.suspend_write_group()
650
        self.assertEqual([], wg_tokens)
651
652
    def test_resume_empty_initial_write_group(self):
653
        """Resuming an empty token list is equivalent to start_write_group."""
654
        self.require_suspendable_write_groups(
655
            'Cannot test resume on repo that does not support suspending')
656
        repo = self.make_write_locked_repo()
657
        repo.resume_write_group([])
658
        repo.abort_write_group()