~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_repository/test_write_group.py

  • Committer: Patch Queue Manager
  • Date: 2016-01-31 13:36:59 UTC
  • mfrom: (6613.1.5 1538480-match-hostname)
  • Revision ID: pqm@pqm.ubuntu.com-20160131133659-ouy92ee2wlv9xz8m
(vila) Use ssl.match_hostname instead of our own. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""Tests for repository write groups."""
18
18
 
19
 
import sys
20
 
 
21
19
from bzrlib import (
22
 
    branch,
23
 
    bzrdir,
24
20
    errors,
25
 
    graph,
26
 
    memorytree,
27
 
    osutils,
28
 
    remote,
29
 
    tests,
30
 
    versionedfile,
31
21
    )
32
22
from bzrlib.tests import (
33
23
    per_repository,
132
122
        # exception was not generated, or the exception was caught and
133
123
        # suppressed.  See also test_pack_repository's test of the same name.
134
124
        self.assertEqual(None, repo.abort_write_group(suppress_errors=True))
135
 
 
136
 
 
137
 
class TestGetMissingParentInventories(per_repository.TestCaseWithRepository):
138
 
 
139
 
    def test_empty_get_missing_parent_inventories(self):
140
 
        """A new write group has no missing parent inventories."""
141
 
        repo = self.make_repository('.')
142
 
        repo.lock_write()
143
 
        repo.start_write_group()
144
 
        try:
145
 
            self.assertEqual(set(), set(repo.get_missing_parent_inventories()))
146
 
        finally:
147
 
            repo.commit_write_group()
148
 
            repo.unlock()
149
 
 
150
 
    def branch_trunk_and_make_tree(self, trunk_repo, relpath):
151
 
        tree = self.make_branch_and_memory_tree('branch')
152
 
        trunk_repo.lock_read()
153
 
        self.addCleanup(trunk_repo.unlock)
154
 
        tree.branch.repository.fetch(trunk_repo, revision_id='rev-1')
155
 
        tree.set_parent_ids(['rev-1'])
156
 
        return tree 
157
 
 
158
 
    def make_first_commit(self, repo):
159
 
        trunk = repo.bzrdir.create_branch()
160
 
        tree = memorytree.MemoryTree.create_on_branch(trunk)
161
 
        tree.lock_write()
162
 
        tree.add([''], ['TREE_ROOT'], ['directory'])
163
 
        tree.add(['dir'], ['dir-id'], ['directory'])
164
 
        tree.add(['filename'], ['file-id'], ['file'])
165
 
        tree.put_file_bytes_non_atomic('file-id', 'content\n')
166
 
        tree.commit('Trunk commit', rev_id='rev-0')
167
 
        tree.commit('Trunk commit', rev_id='rev-1')
168
 
        tree.unlock()
169
 
 
170
 
    def make_new_commit_in_new_repo(self, trunk_repo, parents=None):
171
 
        tree = self.branch_trunk_and_make_tree(trunk_repo, 'branch')
172
 
        tree.set_parent_ids(parents)
173
 
        tree.commit('Branch commit', rev_id='rev-2')
174
 
        branch_repo = tree.branch.repository
175
 
        branch_repo.lock_read()
176
 
        self.addCleanup(branch_repo.unlock)
177
 
        return branch_repo
178
 
 
179
 
    def make_stackable_repo(self, relpath='trunk'):
180
 
        if isinstance(self.repository_format, remote.RemoteRepositoryFormat):
181
 
            # RemoteRepository by default builds a default format real
182
 
            # repository, but the default format is unstackble.  So explicitly
183
 
            # make a stackable real repository and use that.
184
 
            repo = self.make_repository(relpath, format='1.9')
185
 
            repo = bzrdir.BzrDir.open(self.get_url(relpath)).open_repository()
186
 
        else:
187
 
            repo = self.make_repository(relpath)
188
 
        if not repo._format.supports_external_lookups:
189
 
            raise tests.TestNotApplicable('format not stackable')
190
 
        repo.bzrdir._format.set_branch_format(branch.BzrBranchFormat7())
191
 
        return repo
192
 
 
193
 
    def reopen_repo_and_resume_write_group(self, repo):
194
 
        try:
195
 
            resume_tokens = repo.suspend_write_group()
196
 
        except errors.UnsuspendableWriteGroup:
197
 
            # If we got this far, and this repo does not support resuming write
198
 
            # groups, then get_missing_parent_inventories works in all
199
 
            # cases this repo supports.
200
 
            repo.unlock()
201
 
            return
202
 
        repo.unlock()
203
 
        reopened_repo = repo.bzrdir.open_repository()
204
 
        reopened_repo.lock_write()
205
 
        self.addCleanup(reopened_repo.unlock)
206
 
        reopened_repo.resume_write_group(resume_tokens)
207
 
        return reopened_repo
208
 
 
209
 
    def test_ghost_revision(self):
210
 
        """A parent inventory may be absent if all the needed texts are present.
211
 
        i.e., a ghost revision isn't (necessarily) considered to be a missing
212
 
        parent inventory.
213
 
        """
214
 
        # Make a trunk with one commit.
215
 
        trunk_repo = self.make_stackable_repo()
216
 
        self.make_first_commit(trunk_repo)
217
 
        trunk_repo.lock_read()
218
 
        self.addCleanup(trunk_repo.unlock)
219
 
        # Branch the trunk, add a new commit.
220
 
        branch_repo = self.make_new_commit_in_new_repo(
221
 
            trunk_repo, parents=['rev-1', 'ghost-rev'])
222
 
        inv = branch_repo.get_inventory('rev-2')
223
 
        # Make a new repo stacked on trunk, and then copy into it:
224
 
        #  - all texts in rev-2
225
 
        #  - the new inventory (rev-2)
226
 
        #  - the new revision (rev-2)
227
 
        repo = self.make_stackable_repo('stacked')
228
 
        repo.lock_write()
229
 
        repo.start_write_group()
230
 
        # Add all texts from in rev-2 inventory.  Note that this has to exclude
231
 
        # the root if the repo format does not support rich roots.
232
 
        rich_root = branch_repo._format.rich_root_data
233
 
        all_texts = [
234
 
            (ie.file_id, ie.revision) for ie in inv.iter_just_entries()
235
 
             if rich_root or inv.id2path(ie.file_id) != '']
236
 
        repo.texts.insert_record_stream(
237
 
            branch_repo.texts.get_record_stream(all_texts, 'unordered', False))
238
 
        # Add inventory and revision for rev-2.
239
 
        repo.add_inventory('rev-2', inv, ['rev-1', 'ghost-rev'])
240
 
        repo.revisions.insert_record_stream(
241
 
            branch_repo.revisions.get_record_stream(
242
 
                [('rev-2',)], 'unordered', False))
243
 
        # Now, no inventories are reported as missing, even though there is a
244
 
        # ghost.
245
 
        self.assertEqual(set(), repo.get_missing_parent_inventories())
246
 
        # Resuming the write group does not affect
247
 
        # get_missing_parent_inventories.
248
 
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
249
 
        self.assertEqual(set(), reopened_repo.get_missing_parent_inventories())
250
 
        reopened_repo.abort_write_group()
251
 
 
252
 
    def test_get_missing_parent_inventories(self):
253
 
        """A stacked repo with a single revision and inventory (no parent
254
 
        inventory) in it must have all the texts in its inventory (even if not
255
 
        changed w.r.t. to the absent parent), otherwise it will report missing
256
 
        texts/parent inventory.
257
 
 
258
 
        The core of this test is that a file was changed in rev-1, but in a
259
 
        stacked repo that only has rev-2
260
 
        """
261
 
        # Make a trunk with one commit.
262
 
        trunk_repo = self.make_stackable_repo()
263
 
        self.make_first_commit(trunk_repo)
264
 
        trunk_repo.lock_read()
265
 
        self.addCleanup(trunk_repo.unlock)
266
 
        # Branch the trunk, add a new commit.
267
 
        branch_repo = self.make_new_commit_in_new_repo(
268
 
            trunk_repo, parents=['rev-1'])
269
 
        inv = branch_repo.get_inventory('rev-2')
270
 
        # Make a new repo stacked on trunk, and copy the new commit's revision
271
 
        # and inventory records to it.
272
 
        repo = self.make_stackable_repo('stacked')
273
 
        repo.lock_write()
274
 
        repo.start_write_group()
275
 
        # Insert a single fulltext inv (using add_inventory because it's
276
 
        # simpler than insert_record_stream)
277
 
        repo.add_inventory('rev-2', inv, ['rev-1'])
278
 
        repo.revisions.insert_record_stream(
279
 
            branch_repo.revisions.get_record_stream(
280
 
                [('rev-2',)], 'unordered', False))
281
 
        # There should be no missing compression parents
282
 
        self.assertEqual(set(),
283
 
                repo.inventories.get_missing_compression_parent_keys())
284
 
        self.assertEqual(
285
 
            set([('inventories', 'rev-1')]),
286
 
            repo.get_missing_parent_inventories())
287
 
        # Resuming the write group does not affect
288
 
        # get_missing_parent_inventories.
289
 
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
290
 
        self.assertEqual(
291
 
            set([('inventories', 'rev-1')]),
292
 
            reopened_repo.get_missing_parent_inventories())
293
 
        # Adding the parent inventory satisfies get_missing_parent_inventories.
294
 
        reopened_repo.inventories.insert_record_stream(
295
 
            branch_repo.inventories.get_record_stream(
296
 
                [('rev-1',)], 'unordered', False))
297
 
        self.assertEqual(
298
 
            set(), reopened_repo.get_missing_parent_inventories())
299
 
        reopened_repo.abort_write_group()
300
 
 
301
 
    def test_get_missing_parent_inventories_check(self):
302
 
        builder = self.make_branch_builder('test')
303
 
        builder.build_snapshot('A-id', ['ghost-parent-id'], [
304
 
            ('add', ('', 'root-id', 'directory', None)),
305
 
            ('add', ('file', 'file-id', 'file', 'content\n'))],
306
 
            allow_leftmost_as_ghost=True)
307
 
        b = builder.get_branch()
308
 
        b.lock_read()
309
 
        self.addCleanup(b.unlock)
310
 
        repo = self.make_repository('test-repo')
311
 
        repo.lock_write()
312
 
        self.addCleanup(repo.unlock)
313
 
        repo.start_write_group()
314
 
        self.addCleanup(repo.abort_write_group)
315
 
        # Now, add the objects manually
316
 
        text_keys = [('file-id', 'A-id')]
317
 
        if repo.supports_rich_root():
318
 
            text_keys.append(('root-id', 'A-id'))
319
 
        # Directly add the texts, inventory, and revision object for 'A-id'
320
 
        repo.texts.insert_record_stream(b.repository.texts.get_record_stream(
321
 
            text_keys, 'unordered', True))
322
 
        repo.add_revision('A-id', b.repository.get_revision('A-id'),
323
 
                          b.repository.get_inventory('A-id'))
324
 
        get_missing = repo.get_missing_parent_inventories
325
 
        if repo._format.supports_external_lookups:
326
 
            self.assertEqual(set([('inventories', 'ghost-parent-id')]),
327
 
                get_missing(check_for_missing_texts=False))
328
 
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
329
 
            self.assertEqual(set(), get_missing())
330
 
        else:
331
 
            # If we don't support external lookups, we always return empty
332
 
            self.assertEqual(set(), get_missing(check_for_missing_texts=False))
333
 
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
334
 
            self.assertEqual(set(), get_missing())
335
 
 
336
 
    def test_insert_stream_passes_resume_info(self):
337
 
        repo = self.make_repository('test-repo')
338
 
        if (not repo._format.supports_external_lookups or
339
 
            isinstance(repo, remote.RemoteRepository)):
340
 
            raise tests.TestNotApplicable(
341
 
                'only valid for direct connections to resumable repos')
342
 
        # log calls to get_missing_parent_inventories, so that we can assert it
343
 
        # is called with the correct parameters
344
 
        call_log = []
345
 
        orig = repo.get_missing_parent_inventories
346
 
        def get_missing(check_for_missing_texts=True):
347
 
            call_log.append(check_for_missing_texts)
348
 
            return orig(check_for_missing_texts=check_for_missing_texts)
349
 
        repo.get_missing_parent_inventories = get_missing
350
 
        repo.lock_write()
351
 
        self.addCleanup(repo.unlock)
352
 
        sink = repo._get_sink()
353
 
        sink.insert_stream((), repo._format, [])
354
 
        self.assertEqual([False], call_log)
355
 
        del call_log[:]
356
 
        repo.start_write_group()
357
 
        # We need to insert something, or suspend_write_group won't actually
358
 
        # create a token
359
 
        repo.texts.insert_record_stream([versionedfile.FulltextContentFactory(
360
 
            ('file-id', 'rev-id'), (), None, 'lines\n')])
361
 
        tokens = repo.suspend_write_group()
362
 
        self.assertNotEqual([], tokens)
363
 
        sink.insert_stream((), repo._format, tokens)
364
 
        self.assertEqual([True], call_log)
365
 
 
366
 
 
367
 
class TestResumeableWriteGroup(per_repository.TestCaseWithRepository):
368
 
 
369
 
    def make_write_locked_repo(self, relpath='repo'):
370
 
        repo = self.make_repository(relpath)
371
 
        repo.lock_write()
372
 
        self.addCleanup(repo.unlock)
373
 
        return repo
374
 
 
375
 
    def reopen_repo(self, repo):
376
 
        same_repo = repo.bzrdir.open_repository()
377
 
        same_repo.lock_write()
378
 
        self.addCleanup(same_repo.unlock)
379
 
        return same_repo
380
 
 
381
 
    def require_suspendable_write_groups(self, reason):
382
 
        repo = self.make_repository('__suspend_test')
383
 
        repo.lock_write()
384
 
        self.addCleanup(repo.unlock)
385
 
        repo.start_write_group()
386
 
        try:
387
 
            wg_tokens = repo.suspend_write_group()
388
 
        except errors.UnsuspendableWriteGroup:
389
 
            repo.abort_write_group()
390
 
            raise tests.TestNotApplicable(reason)
391
 
 
392
 
    def test_suspend_write_group(self):
393
 
        repo = self.make_write_locked_repo()
394
 
        repo.start_write_group()
395
 
        # Add some content so this isn't an empty write group (which may return
396
 
        # 0 tokens)
397
 
        repo.texts.add_lines(('file-id', 'revid'), (), ['lines'])
398
 
        try:
399
 
            wg_tokens = repo.suspend_write_group()
400
 
        except errors.UnsuspendableWriteGroup:
401
 
            # The contract for repos that don't support suspending write groups
402
 
            # is that suspend_write_group raises UnsuspendableWriteGroup, but
403
 
            # is otherwise a no-op.  So we can still e.g. abort the write group
404
 
            # as usual.
405
 
            self.assertTrue(repo.is_in_write_group())
406
 
            repo.abort_write_group()
407
 
        else:
408
 
            # After suspending a write group we are no longer in a write group
409
 
            self.assertFalse(repo.is_in_write_group())
410
 
            # suspend_write_group returns a list of tokens, which are strs.  If
411
 
            # no other write groups were resumed, there will only be one token.
412
 
            self.assertEqual(1, len(wg_tokens))
413
 
            self.assertIsInstance(wg_tokens[0], str)
414
 
            # See also test_pack_repository's test of the same name.
415
 
 
416
 
    def test_resume_write_group_then_abort(self):
417
 
        repo = self.make_write_locked_repo()
418
 
        repo.start_write_group()
419
 
        # Add some content so this isn't an empty write group (which may return
420
 
        # 0 tokens)
421
 
        text_key = ('file-id', 'revid')
422
 
        repo.texts.add_lines(text_key, (), ['lines'])
423
 
        try:
424
 
            wg_tokens = repo.suspend_write_group()
425
 
        except errors.UnsuspendableWriteGroup:
426
 
            # If the repo does not support suspending write groups, it doesn't
427
 
            # support resuming them either.
428
 
            repo.abort_write_group()
429
 
            self.assertRaises(
430
 
                errors.UnsuspendableWriteGroup, repo.resume_write_group, [])
431
 
        else:
432
 
            #self.assertEqual([], list(repo.texts.keys()))
433
 
            same_repo = self.reopen_repo(repo)
434
 
            same_repo.resume_write_group(wg_tokens)
435
 
            self.assertEqual([text_key], list(same_repo.texts.keys()))
436
 
            self.assertTrue(same_repo.is_in_write_group())
437
 
            same_repo.abort_write_group()
438
 
            self.assertEqual([], list(repo.texts.keys()))
439
 
            # See also test_pack_repository's test of the same name.
440
 
 
441
 
    def test_multiple_resume_write_group(self):
442
 
        self.require_suspendable_write_groups(
443
 
            'Cannot test resume on repo that does not support suspending')
444
 
        repo = self.make_write_locked_repo()
445
 
        repo.start_write_group()
446
 
        # Add some content so this isn't an empty write group (which may return
447
 
        # 0 tokens)
448
 
        first_key = ('file-id', 'revid')
449
 
        repo.texts.add_lines(first_key, (), ['lines'])
450
 
        wg_tokens = repo.suspend_write_group()
451
 
        same_repo = self.reopen_repo(repo)
452
 
        same_repo.resume_write_group(wg_tokens)
453
 
        self.assertTrue(same_repo.is_in_write_group())
454
 
        second_key = ('file-id', 'second-revid')
455
 
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
456
 
        try:
457
 
            new_wg_tokens = same_repo.suspend_write_group()
458
 
        except:
459
 
            e = sys.exc_info()
460
 
            same_repo.abort_write_group(suppress_errors=True)
461
 
            raise e[0], e[1], e[2]
462
 
        self.assertEqual(2, len(new_wg_tokens))
463
 
        self.assertSubset(wg_tokens, new_wg_tokens)
464
 
        same_repo = self.reopen_repo(repo)
465
 
        same_repo.resume_write_group(new_wg_tokens)
466
 
        both_keys = set([first_key, second_key])
467
 
        self.assertEqual(both_keys, same_repo.texts.keys())
468
 
        same_repo.abort_write_group()
469
 
 
470
 
    def test_no_op_suspend_resume(self):
471
 
        self.require_suspendable_write_groups(
472
 
            'Cannot test resume on repo that does not support suspending')
473
 
        repo = self.make_write_locked_repo()
474
 
        repo.start_write_group()
475
 
        # Add some content so this isn't an empty write group (which may return
476
 
        # 0 tokens)
477
 
        text_key = ('file-id', 'revid')
478
 
        repo.texts.add_lines(text_key, (), ['lines'])
479
 
        wg_tokens = repo.suspend_write_group()
480
 
        same_repo = self.reopen_repo(repo)
481
 
        same_repo.resume_write_group(wg_tokens)
482
 
        new_wg_tokens = same_repo.suspend_write_group()
483
 
        self.assertEqual(wg_tokens, new_wg_tokens)
484
 
        same_repo = self.reopen_repo(repo)
485
 
        same_repo.resume_write_group(wg_tokens)
486
 
        self.assertEqual([text_key], list(same_repo.texts.keys()))
487
 
        same_repo.abort_write_group()
488
 
 
489
 
    def test_read_after_suspend_fails(self):
490
 
        self.require_suspendable_write_groups(
491
 
            'Cannot test suspend on repo that does not support suspending')
492
 
        repo = self.make_write_locked_repo()
493
 
        repo.start_write_group()
494
 
        # Add some content so this isn't an empty write group (which may return
495
 
        # 0 tokens)
496
 
        text_key = ('file-id', 'revid')
497
 
        repo.texts.add_lines(text_key, (), ['lines'])
498
 
        wg_tokens = repo.suspend_write_group()
499
 
        self.assertEqual([], list(repo.texts.keys()))
500
 
 
501
 
    def test_read_after_second_suspend_fails(self):
502
 
        self.require_suspendable_write_groups(
503
 
            'Cannot test suspend on repo that does not support suspending')
504
 
        repo = self.make_write_locked_repo()
505
 
        repo.start_write_group()
506
 
        # Add some content so this isn't an empty write group (which may return
507
 
        # 0 tokens)
508
 
        text_key = ('file-id', 'revid')
509
 
        repo.texts.add_lines(text_key, (), ['lines'])
510
 
        wg_tokens = repo.suspend_write_group()
511
 
        same_repo = self.reopen_repo(repo)
512
 
        same_repo.resume_write_group(wg_tokens)
513
 
        same_repo.suspend_write_group()
514
 
        self.assertEqual([], list(same_repo.texts.keys()))
515
 
 
516
 
    def test_read_after_resume_abort_fails(self):
517
 
        self.require_suspendable_write_groups(
518
 
            'Cannot test suspend on repo that does not support suspending')
519
 
        repo = self.make_write_locked_repo()
520
 
        repo.start_write_group()
521
 
        # Add some content so this isn't an empty write group (which may return
522
 
        # 0 tokens)
523
 
        text_key = ('file-id', 'revid')
524
 
        repo.texts.add_lines(text_key, (), ['lines'])
525
 
        wg_tokens = repo.suspend_write_group()
526
 
        same_repo = self.reopen_repo(repo)
527
 
        same_repo.resume_write_group(wg_tokens)
528
 
        same_repo.abort_write_group()
529
 
        self.assertEqual([], list(same_repo.texts.keys()))
530
 
 
531
 
    def test_cannot_resume_aborted_write_group(self):
532
 
        self.require_suspendable_write_groups(
533
 
            'Cannot test resume on repo that does not support suspending')
534
 
        repo = self.make_write_locked_repo()
535
 
        repo.start_write_group()
536
 
        # Add some content so this isn't an empty write group (which may return
537
 
        # 0 tokens)
538
 
        text_key = ('file-id', 'revid')
539
 
        repo.texts.add_lines(text_key, (), ['lines'])
540
 
        wg_tokens = repo.suspend_write_group()
541
 
        same_repo = self.reopen_repo(repo)
542
 
        same_repo.resume_write_group(wg_tokens)
543
 
        same_repo.abort_write_group()
544
 
        same_repo = self.reopen_repo(repo)
545
 
        self.assertRaises(
546
 
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
547
 
            wg_tokens)
548
 
 
549
 
    def test_commit_resumed_write_group_no_new_data(self):
550
 
        self.require_suspendable_write_groups(
551
 
            'Cannot test resume on repo that does not support suspending')
552
 
        repo = self.make_write_locked_repo()
553
 
        repo.start_write_group()
554
 
        # Add some content so this isn't an empty write group (which may return
555
 
        # 0 tokens)
556
 
        text_key = ('file-id', 'revid')
557
 
        repo.texts.add_lines(text_key, (), ['lines'])
558
 
        wg_tokens = repo.suspend_write_group()
559
 
        same_repo = self.reopen_repo(repo)
560
 
        same_repo.resume_write_group(wg_tokens)
561
 
        same_repo.commit_write_group()
562
 
        self.assertEqual([text_key], list(same_repo.texts.keys()))
563
 
        self.assertEqual(
564
 
            'lines', same_repo.texts.get_record_stream([text_key],
565
 
                'unordered', True).next().get_bytes_as('fulltext'))
566
 
        self.assertRaises(
567
 
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
568
 
            wg_tokens)
569
 
 
570
 
    def test_commit_resumed_write_group_plus_new_data(self):
571
 
        self.require_suspendable_write_groups(
572
 
            'Cannot test resume on repo that does not support suspending')
573
 
        repo = self.make_write_locked_repo()
574
 
        repo.start_write_group()
575
 
        # Add some content so this isn't an empty write group (which may return
576
 
        # 0 tokens)
577
 
        first_key = ('file-id', 'revid')
578
 
        repo.texts.add_lines(first_key, (), ['lines'])
579
 
        wg_tokens = repo.suspend_write_group()
580
 
        same_repo = self.reopen_repo(repo)
581
 
        same_repo.resume_write_group(wg_tokens)
582
 
        second_key = ('file-id', 'second-revid')
583
 
        same_repo.texts.add_lines(second_key, (first_key,), ['more lines'])
584
 
        same_repo.commit_write_group()
585
 
        self.assertEqual(
586
 
            set([first_key, second_key]), set(same_repo.texts.keys()))
587
 
        self.assertEqual(
588
 
            'lines', same_repo.texts.get_record_stream([first_key],
589
 
                'unordered', True).next().get_bytes_as('fulltext'))
590
 
        self.assertEqual(
591
 
            'more lines', same_repo.texts.get_record_stream([second_key],
592
 
                'unordered', True).next().get_bytes_as('fulltext'))
593
 
 
594
 
    def make_source_with_delta_record(self):
595
 
        # Make a source repository with a delta record in it.
596
 
        source_repo = self.make_write_locked_repo('source')
597
 
        source_repo.start_write_group()
598
 
        key_base = ('file-id', 'base')
599
 
        key_delta = ('file-id', 'delta')
600
 
        def text_stream():
601
 
            yield versionedfile.FulltextContentFactory(
602
 
                key_base, (), None, 'lines\n')
603
 
            yield versionedfile.FulltextContentFactory(
604
 
                key_delta, (key_base,), None, 'more\nlines\n')
605
 
        source_repo.texts.insert_record_stream(text_stream())
606
 
        source_repo.commit_write_group()
607
 
        return source_repo
608
 
 
609
 
    def test_commit_resumed_write_group_with_missing_parents(self):
610
 
        self.require_suspendable_write_groups(
611
 
            'Cannot test resume on repo that does not support suspending')
612
 
        source_repo = self.make_source_with_delta_record()
613
 
        key_base = ('file-id', 'base')
614
 
        key_delta = ('file-id', 'delta')
615
 
        # Start a write group, insert just a delta.
616
 
        repo = self.make_write_locked_repo()
617
 
        repo.start_write_group()
618
 
        stream = source_repo.texts.get_record_stream(
619
 
            [key_delta], 'unordered', False)
620
 
        repo.texts.insert_record_stream(stream)
621
 
        # It's either not commitable due to the missing compression parent, or
622
 
        # the stacked location has already filled in the fulltext.
623
 
        try:
624
 
            repo.commit_write_group()
625
 
        except errors.BzrCheckError:
626
 
            # It refused to commit because we have a missing parent
627
 
            pass
628
 
        else:
629
 
            same_repo = self.reopen_repo(repo)
630
 
            same_repo.lock_read()
631
 
            record = same_repo.texts.get_record_stream([key_delta],
632
 
                                                       'unordered', True).next()
633
 
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
634
 
            return
635
 
        # Merely suspending and resuming doesn't make it commitable either.
636
 
        wg_tokens = repo.suspend_write_group()
637
 
        same_repo = self.reopen_repo(repo)
638
 
        same_repo.resume_write_group(wg_tokens)
639
 
        self.assertRaises(
640
 
            errors.BzrCheckError, same_repo.commit_write_group)
641
 
        same_repo.abort_write_group()
642
 
 
643
 
    def test_commit_resumed_write_group_adding_missing_parents(self):
644
 
        self.require_suspendable_write_groups(
645
 
            'Cannot test resume on repo that does not support suspending')
646
 
        source_repo = self.make_source_with_delta_record()
647
 
        key_base = ('file-id', 'base')
648
 
        key_delta = ('file-id', 'delta')
649
 
        # Start a write group.
650
 
        repo = self.make_write_locked_repo()
651
 
        repo.start_write_group()
652
 
        # Add some content so this isn't an empty write group (which may return
653
 
        # 0 tokens)
654
 
        text_key = ('file-id', 'revid')
655
 
        repo.texts.add_lines(text_key, (), ['lines'])
656
 
        # Suspend it, then resume it.
657
 
        wg_tokens = repo.suspend_write_group()
658
 
        same_repo = self.reopen_repo(repo)
659
 
        same_repo.resume_write_group(wg_tokens)
660
 
        # Add a record with a missing compression parent
661
 
        stream = source_repo.texts.get_record_stream(
662
 
            [key_delta], 'unordered', False)
663
 
        same_repo.texts.insert_record_stream(stream)
664
 
        # Just like if we'd added that record without a suspend/resume cycle,
665
 
        # commit_write_group fails.
666
 
        try:
667
 
            same_repo.commit_write_group()
668
 
        except errors.BzrCheckError:
669
 
            pass
670
 
        else:
671
 
            # If the commit_write_group didn't fail, that is because the
672
 
            # insert_record_stream already gave it a fulltext.
673
 
            same_repo = self.reopen_repo(repo)
674
 
            same_repo.lock_read()
675
 
            record = same_repo.texts.get_record_stream([key_delta],
676
 
                                                       'unordered', True).next()
677
 
            self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
678
 
            return
679
 
        same_repo.abort_write_group()
680
 
 
681
 
    def test_add_missing_parent_after_resume(self):
682
 
        self.require_suspendable_write_groups(
683
 
            'Cannot test resume on repo that does not support suspending')
684
 
        source_repo = self.make_source_with_delta_record()
685
 
        key_base = ('file-id', 'base')
686
 
        key_delta = ('file-id', 'delta')
687
 
        # Start a write group, insert just a delta.
688
 
        repo = self.make_write_locked_repo()
689
 
        repo.start_write_group()
690
 
        stream = source_repo.texts.get_record_stream(
691
 
            [key_delta], 'unordered', False)
692
 
        repo.texts.insert_record_stream(stream)
693
 
        # Suspend it, then resume it.
694
 
        wg_tokens = repo.suspend_write_group()
695
 
        same_repo = self.reopen_repo(repo)
696
 
        same_repo.resume_write_group(wg_tokens)
697
 
        # Fill in the missing compression parent.
698
 
        stream = source_repo.texts.get_record_stream(
699
 
            [key_base], 'unordered', False)
700
 
        same_repo.texts.insert_record_stream(stream)
701
 
        same_repo.commit_write_group()
702
 
 
703
 
    def test_suspend_empty_initial_write_group(self):
704
 
        """Suspending a write group with no writes returns an empty token
705
 
        list.
706
 
        """
707
 
        self.require_suspendable_write_groups(
708
 
            'Cannot test suspend on repo that does not support suspending')
709
 
        repo = self.make_write_locked_repo()
710
 
        repo.start_write_group()
711
 
        wg_tokens = repo.suspend_write_group()
712
 
        self.assertEqual([], wg_tokens)
713
 
 
714
 
    def test_suspend_empty_initial_write_group(self):
715
 
        """Resuming an empty token list is equivalent to start_write_group."""
716
 
        self.require_suspendable_write_groups(
717
 
            'Cannot test resume on repo that does not support suspending')
718
 
        repo = self.make_write_locked_repo()
719
 
        repo.resume_write_group([])
720
 
        repo.abort_write_group()
721
 
 
722