~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_branch.py

  • Committer: Martin
  • Date: 2010-05-16 15:18:43 UTC
  • mfrom: (5235 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5239.
  • Revision ID: gzlist@googlemail.com-20100516151843-lu53u7caehm3ie3i
Merge bzr.dev to resolve conflicts in NEWS and _chk_map_pyx

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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 the Branch facility that are not interface  tests.
18
18
 
19
 
For interface tests see tests/branch_implementations/*.py.
 
19
For interface tests see tests/per_branch/*.py.
20
20
 
21
21
For concrete class tests see this file, and for meta-branch tests
22
22
also see this file.
23
23
"""
24
24
 
25
 
from StringIO import StringIO
 
25
from cStringIO import StringIO
26
26
 
27
27
from bzrlib import (
28
28
    branch as _mod_branch,
29
29
    bzrdir,
30
30
    config,
31
31
    errors,
 
32
    tests,
32
33
    trace,
 
34
    transport,
33
35
    urlutils,
34
36
    )
35
 
from bzrlib.branch import (
36
 
    Branch,
37
 
    BranchHooks,
38
 
    BranchFormat,
39
 
    BranchReferenceFormat,
40
 
    BzrBranch5,
41
 
    BzrBranchFormat5,
42
 
    BzrBranchFormat6,
43
 
    PullResult,
44
 
    _run_with_write_locked_target,
45
 
    )
46
 
from bzrlib.bzrdir import (BzrDirMetaFormat1, BzrDirMeta1,
47
 
                           BzrDir, BzrDirFormat)
48
 
from bzrlib.errors import (NotBranchError,
49
 
                           UnknownFormatError,
50
 
                           UnknownHook,
51
 
                           UnsupportedFormatError,
52
 
                           )
53
 
 
54
 
from bzrlib.tests import TestCase, TestCaseWithTransport
55
 
from bzrlib.transport import get_transport
56
 
 
57
 
 
58
 
class TestDefaultFormat(TestCase):
 
37
 
 
38
 
 
39
class TestDefaultFormat(tests.TestCase):
59
40
 
60
41
    def test_default_format(self):
61
42
        # update this if you change the default branch format
62
 
        self.assertIsInstance(BranchFormat.get_default_format(),
63
 
                BzrBranchFormat6)
 
43
        self.assertIsInstance(_mod_branch.BranchFormat.get_default_format(),
 
44
                _mod_branch.BzrBranchFormat7)
64
45
 
65
46
    def test_default_format_is_same_as_bzrdir_default(self):
66
47
        # XXX: it might be nice if there was only one place the default was
67
48
        # set, but at the moment that's not true -- mbp 20070814 --
68
49
        # https://bugs.launchpad.net/bzr/+bug/132376
69
 
        self.assertEqual(BranchFormat.get_default_format(),
70
 
                BzrDirFormat.get_default_format().get_branch_format())
 
50
        self.assertEqual(
 
51
            _mod_branch.BranchFormat.get_default_format(),
 
52
            bzrdir.BzrDirFormat.get_default_format().get_branch_format())
71
53
 
72
54
    def test_get_set_default_format(self):
73
55
        # set the format and then set it back again
74
 
        old_format = BranchFormat.get_default_format()
75
 
        BranchFormat.set_default_format(SampleBranchFormat())
 
56
        old_format = _mod_branch.BranchFormat.get_default_format()
 
57
        _mod_branch.BranchFormat.set_default_format(SampleBranchFormat())
76
58
        try:
77
59
            # the default branch format is used by the meta dir format
78
60
            # which is not the default bzrdir format at this point
79
 
            dir = BzrDirMetaFormat1().initialize('memory:///')
 
61
            dir = bzrdir.BzrDirMetaFormat1().initialize('memory:///')
80
62
            result = dir.create_branch()
81
63
            self.assertEqual(result, 'A branch')
82
64
        finally:
83
 
            BranchFormat.set_default_format(old_format)
84
 
        self.assertEqual(old_format, BranchFormat.get_default_format())
85
 
 
86
 
 
87
 
class TestBranchFormat5(TestCaseWithTransport):
 
65
            _mod_branch.BranchFormat.set_default_format(old_format)
 
66
        self.assertEqual(old_format,
 
67
                         _mod_branch.BranchFormat.get_default_format())
 
68
 
 
69
 
 
70
class TestBranchFormat5(tests.TestCaseWithTransport):
88
71
    """Tests specific to branch format 5"""
89
72
 
90
73
    def test_branch_format_5_uses_lockdir(self):
91
74
        url = self.get_url()
92
 
        bzrdir = BzrDirMetaFormat1().initialize(url)
93
 
        bzrdir.create_repository()
94
 
        branch = bzrdir.create_branch()
 
75
        bdir = bzrdir.BzrDirMetaFormat1().initialize(url)
 
76
        bdir.create_repository()
 
77
        branch = bdir.create_branch()
95
78
        t = self.get_transport()
96
79
        self.log("branch instance is %r" % branch)
97
 
        self.assert_(isinstance(branch, BzrBranch5))
 
80
        self.assert_(isinstance(branch, _mod_branch.BzrBranch5))
98
81
        self.assertIsDirectory('.', t)
99
82
        self.assertIsDirectory('.bzr/branch', t)
100
83
        self.assertIsDirectory('.bzr/branch/lock', t)
101
84
        branch.lock_write()
102
 
        try:
103
 
            self.assertIsDirectory('.bzr/branch/lock/held', t)
104
 
        finally:
105
 
            branch.unlock()
 
85
        self.addCleanup(branch.unlock)
 
86
        self.assertIsDirectory('.bzr/branch/lock/held', t)
106
87
 
107
88
    def test_set_push_location(self):
108
89
        from bzrlib.config import (locations_config_filename,
131
112
    # recursive section - that is, it appends the branch name.
132
113
 
133
114
 
134
 
class SampleBranchFormat(BranchFormat):
 
115
class SampleBranchFormat(_mod_branch.BranchFormat):
135
116
    """A sample format
136
117
 
137
118
    this format is initializable, unsupported to aid in testing the
142
123
        """See BzrBranchFormat.get_format_string()."""
143
124
        return "Sample branch format."
144
125
 
145
 
    def initialize(self, a_bzrdir):
 
126
    def initialize(self, a_bzrdir, name=None):
146
127
        """Format 4 branches cannot be created."""
147
 
        t = a_bzrdir.get_branch_transport(self)
 
128
        t = a_bzrdir.get_branch_transport(self, name=name)
148
129
        t.put_bytes('format', self.get_format_string())
149
130
        return 'A branch'
150
131
 
151
132
    def is_supported(self):
152
133
        return False
153
134
 
154
 
    def open(self, transport, _found=False, ignore_fallbacks=False):
 
135
    def open(self, transport, name=None, _found=False, ignore_fallbacks=False):
155
136
        return "opened branch."
156
137
 
157
138
 
158
 
class TestBzrBranchFormat(TestCaseWithTransport):
 
139
class TestBzrBranchFormat(tests.TestCaseWithTransport):
159
140
    """Tests for the BzrBranchFormat facility."""
160
141
 
161
142
    def test_find_format(self):
167
148
            dir = format._matchingbzrdir.initialize(url)
168
149
            dir.create_repository()
169
150
            format.initialize(dir)
170
 
            found_format = BranchFormat.find_format(dir)
 
151
            found_format = _mod_branch.BranchFormat.find_format(dir)
171
152
            self.failUnless(isinstance(found_format, format.__class__))
172
 
        check_format(BzrBranchFormat5(), "bar")
 
153
        check_format(_mod_branch.BzrBranchFormat5(), "bar")
173
154
 
174
155
    def test_find_format_not_branch(self):
175
156
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
176
 
        self.assertRaises(NotBranchError,
177
 
                          BranchFormat.find_format,
 
157
        self.assertRaises(errors.NotBranchError,
 
158
                          _mod_branch.BranchFormat.find_format,
178
159
                          dir)
179
160
 
180
161
    def test_find_format_unknown_format(self):
181
162
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
182
163
        SampleBranchFormat().initialize(dir)
183
 
        self.assertRaises(UnknownFormatError,
184
 
                          BranchFormat.find_format,
 
164
        self.assertRaises(errors.UnknownFormatError,
 
165
                          _mod_branch.BranchFormat.find_format,
185
166
                          dir)
186
167
 
187
168
    def test_register_unregister_format(self):
191
172
        # make a branch
192
173
        format.initialize(dir)
193
174
        # register a format for it.
194
 
        BranchFormat.register_format(format)
 
175
        _mod_branch.BranchFormat.register_format(format)
195
176
        # which branch.Open will refuse (not supported)
196
 
        self.assertRaises(UnsupportedFormatError, Branch.open, self.get_url())
 
177
        self.assertRaises(errors.UnsupportedFormatError,
 
178
                          _mod_branch.Branch.open, self.get_url())
197
179
        self.make_branch_and_tree('foo')
198
180
        # but open_downlevel will work
199
 
        self.assertEqual(format.open(dir), bzrdir.BzrDir.open(self.get_url()).open_branch(unsupported=True))
 
181
        self.assertEqual(
 
182
            format.open(dir),
 
183
            bzrdir.BzrDir.open(self.get_url()).open_branch(unsupported=True))
200
184
        # unregister the format
201
 
        BranchFormat.unregister_format(format)
 
185
        _mod_branch.BranchFormat.unregister_format(format)
202
186
        self.make_branch_and_tree('bar')
203
187
 
204
188
 
215
199
        raise NotImplementedError(self.get_class)
216
200
 
217
201
    def test_creation(self):
218
 
        format = BzrDirMetaFormat1()
 
202
        format = bzrdir.BzrDirMetaFormat1()
219
203
        format.set_branch_format(_mod_branch.BzrBranchFormat6())
220
204
        branch = self.make_branch('a', format=format)
221
205
        self.assertIsInstance(branch, self.get_class())
228
212
        branch = self.make_branch('a', format=self.get_format_name())
229
213
        self.failUnlessExists('a/.bzr/branch/last-revision')
230
214
        self.failIfExists('a/.bzr/branch/revision-history')
 
215
        self.failIfExists('a/.bzr/branch/references')
231
216
 
232
217
    def test_config(self):
233
218
        """Ensure that all configuration data is stored in the branch"""
307
292
                         'locations.conf')
308
293
 
309
294
 
310
 
class TestBranch6(TestBranch67, TestCaseWithTransport):
 
295
class TestBranch6(TestBranch67, tests.TestCaseWithTransport):
311
296
 
312
297
    def get_class(self):
313
298
        return _mod_branch.BzrBranch6
328
313
        self.assertRaises(errors.UnstackableBranchFormat, branch.get_stacked_on_url)
329
314
 
330
315
 
331
 
class TestBranch7(TestBranch67, TestCaseWithTransport):
 
316
class TestBranch7(TestBranch67, tests.TestCaseWithTransport):
332
317
 
333
318
    def get_class(self):
334
319
        return _mod_branch.BzrBranch7
335
320
 
336
321
    def get_format_name(self):
337
 
        return "development"
 
322
        return "1.9"
338
323
 
339
324
    def get_format_name_subtree(self):
340
325
        return "development-subtree"
378
363
        self.assertTrue(branch.repository.has_revision(revid))
379
364
 
380
365
 
381
 
class TestBranchReference(TestCaseWithTransport):
 
366
class BzrBranch8(tests.TestCaseWithTransport):
 
367
 
 
368
    def make_branch(self, location, format=None):
 
369
        if format is None:
 
370
            format = bzrdir.format_registry.make_bzrdir('1.9')
 
371
            format.set_branch_format(_mod_branch.BzrBranchFormat8())
 
372
        return tests.TestCaseWithTransport.make_branch(
 
373
            self, location, format=format)
 
374
 
 
375
    def create_branch_with_reference(self):
 
376
        branch = self.make_branch('branch')
 
377
        branch._set_all_reference_info({'file-id': ('path', 'location')})
 
378
        return branch
 
379
 
 
380
    @staticmethod
 
381
    def instrument_branch(branch, gets):
 
382
        old_get = branch._transport.get
 
383
        def get(*args, **kwargs):
 
384
            gets.append((args, kwargs))
 
385
            return old_get(*args, **kwargs)
 
386
        branch._transport.get = get
 
387
 
 
388
    def test_reference_info_caching_read_locked(self):
 
389
        gets = []
 
390
        branch = self.create_branch_with_reference()
 
391
        branch.lock_read()
 
392
        self.addCleanup(branch.unlock)
 
393
        self.instrument_branch(branch, gets)
 
394
        branch.get_reference_info('file-id')
 
395
        branch.get_reference_info('file-id')
 
396
        self.assertEqual(1, len(gets))
 
397
 
 
398
    def test_reference_info_caching_read_unlocked(self):
 
399
        gets = []
 
400
        branch = self.create_branch_with_reference()
 
401
        self.instrument_branch(branch, gets)
 
402
        branch.get_reference_info('file-id')
 
403
        branch.get_reference_info('file-id')
 
404
        self.assertEqual(2, len(gets))
 
405
 
 
406
    def test_reference_info_caching_write_locked(self):
 
407
        gets = []
 
408
        branch = self.make_branch('branch')
 
409
        branch.lock_write()
 
410
        self.instrument_branch(branch, gets)
 
411
        self.addCleanup(branch.unlock)
 
412
        branch._set_all_reference_info({'file-id': ('path2', 'location2')})
 
413
        path, location = branch.get_reference_info('file-id')
 
414
        self.assertEqual(0, len(gets))
 
415
        self.assertEqual('path2', path)
 
416
        self.assertEqual('location2', location)
 
417
 
 
418
    def test_reference_info_caches_cleared(self):
 
419
        branch = self.make_branch('branch')
 
420
        branch.lock_write()
 
421
        branch.set_reference_info('file-id', 'path2', 'location2')
 
422
        branch.unlock()
 
423
        doppelganger = _mod_branch.Branch.open('branch')
 
424
        doppelganger.set_reference_info('file-id', 'path3', 'location3')
 
425
        self.assertEqual(('path3', 'location3'),
 
426
                         branch.get_reference_info('file-id'))
 
427
 
 
428
class TestBranchReference(tests.TestCaseWithTransport):
382
429
    """Tests for the branch reference facility."""
383
430
 
384
431
    def test_create_open_reference(self):
385
432
        bzrdirformat = bzrdir.BzrDirMetaFormat1()
386
 
        t = get_transport(self.get_url('.'))
 
433
        t = transport.get_transport(self.get_url('.'))
387
434
        t.mkdir('repo')
388
435
        dir = bzrdirformat.initialize(self.get_url('repo'))
389
436
        dir.create_repository()
390
437
        target_branch = dir.create_branch()
391
438
        t.mkdir('branch')
392
439
        branch_dir = bzrdirformat.initialize(self.get_url('branch'))
393
 
        made_branch = BranchReferenceFormat().initialize(branch_dir, target_branch)
 
440
        made_branch = _mod_branch.BranchReferenceFormat().initialize(
 
441
            branch_dir, target_branch=target_branch)
394
442
        self.assertEqual(made_branch.base, target_branch.base)
395
443
        opened_branch = branch_dir.open_branch()
396
444
        self.assertEqual(opened_branch.base, target_branch.base)
407
455
            _mod_branch.BranchReferenceFormat().get_reference(checkout.bzrdir))
408
456
 
409
457
 
410
 
class TestHooks(TestCase):
 
458
class TestHooks(tests.TestCaseWithTransport):
411
459
 
412
460
    def test_constructor(self):
413
461
        """Check that creating a BranchHooks instance has the right defaults."""
414
 
        hooks = BranchHooks()
 
462
        hooks = _mod_branch.BranchHooks()
415
463
        self.assertTrue("set_rh" in hooks, "set_rh not in %s" % hooks)
416
464
        self.assertTrue("post_push" in hooks, "post_push not in %s" % hooks)
417
465
        self.assertTrue("post_commit" in hooks, "post_commit not in %s" % hooks)
418
466
        self.assertTrue("pre_commit" in hooks, "pre_commit not in %s" % hooks)
419
467
        self.assertTrue("post_pull" in hooks, "post_pull not in %s" % hooks)
420
 
        self.assertTrue("post_uncommit" in hooks, "post_uncommit not in %s" % hooks)
 
468
        self.assertTrue("post_uncommit" in hooks,
 
469
                        "post_uncommit not in %s" % hooks)
421
470
        self.assertTrue("post_change_branch_tip" in hooks,
422
471
                        "post_change_branch_tip not in %s" % hooks)
 
472
        self.assertTrue("post_branch_init" in hooks,
 
473
                        "post_branch_init not in %s" % hooks)
 
474
        self.assertTrue("post_switch" in hooks,
 
475
                        "post_switch not in %s" % hooks)
423
476
 
424
477
    def test_installed_hooks_are_BranchHooks(self):
425
478
        """The installed hooks object should be a BranchHooks."""
426
479
        # the installed hooks are saved in self._preserved_hooks.
427
480
        self.assertIsInstance(self._preserved_hooks[_mod_branch.Branch][1],
428
 
            BranchHooks)
429
 
 
430
 
 
431
 
class TestPullResult(TestCase):
 
481
                              _mod_branch.BranchHooks)
 
482
 
 
483
    def test_post_branch_init_hook(self):
 
484
        calls = []
 
485
        _mod_branch.Branch.hooks.install_named_hook('post_branch_init',
 
486
            calls.append, None)
 
487
        self.assertLength(0, calls)
 
488
        branch = self.make_branch('a')
 
489
        self.assertLength(1, calls)
 
490
        params = calls[0]
 
491
        self.assertIsInstance(params, _mod_branch.BranchInitHookParams)
 
492
        self.assertTrue(hasattr(params, 'bzrdir'))
 
493
        self.assertTrue(hasattr(params, 'branch'))
 
494
 
 
495
    def test_post_switch_hook(self):
 
496
        from bzrlib import switch
 
497
        calls = []
 
498
        _mod_branch.Branch.hooks.install_named_hook('post_switch',
 
499
            calls.append, None)
 
500
        tree = self.make_branch_and_tree('branch-1')
 
501
        self.build_tree(['branch-1/file-1'])
 
502
        tree.add('file-1')
 
503
        tree.commit('rev1')
 
504
        to_branch = tree.bzrdir.sprout('branch-2').open_branch()
 
505
        self.build_tree(['branch-1/file-2'])
 
506
        tree.add('file-2')
 
507
        tree.remove('file-1')
 
508
        tree.commit('rev2')
 
509
        checkout = tree.branch.create_checkout('checkout')
 
510
        self.assertLength(0, calls)
 
511
        switch.switch(checkout.bzrdir, to_branch)
 
512
        self.assertLength(1, calls)
 
513
        params = calls[0]
 
514
        self.assertIsInstance(params, _mod_branch.SwitchHookParams)
 
515
        self.assertTrue(hasattr(params, 'to_branch'))
 
516
        self.assertTrue(hasattr(params, 'revision_id'))
 
517
 
 
518
 
 
519
class TestBranchOptions(tests.TestCaseWithTransport):
 
520
 
 
521
    def setUp(self):
 
522
        super(TestBranchOptions, self).setUp()
 
523
        self.branch = self.make_branch('.')
 
524
        self.config = self.branch.get_config()
 
525
 
 
526
    def check_append_revisions_only(self, expected_value, value=None):
 
527
        """Set append_revisions_only in config and check its interpretation."""
 
528
        if value is not None:
 
529
            self.config.set_user_option('append_revisions_only', value)
 
530
        self.assertEqual(expected_value,
 
531
                         self.branch._get_append_revisions_only())
 
532
 
 
533
    def test_valid_append_revisions_only(self):
 
534
        self.assertEquals(None,
 
535
                          self.config.get_user_option('append_revisions_only'))
 
536
        self.check_append_revisions_only(None)
 
537
        self.check_append_revisions_only(False, 'False')
 
538
        self.check_append_revisions_only(True, 'True')
 
539
        # The following values will cause compatibility problems on projects
 
540
        # using older bzr versions (<2.2) but are accepted
 
541
        self.check_append_revisions_only(False, 'false')
 
542
        self.check_append_revisions_only(True, 'true')
 
543
 
 
544
    def test_invalid_append_revisions_only(self):
 
545
        """Ensure warning is noted on invalid settings"""
 
546
        self.warnings = []
 
547
        def warning(*args):
 
548
            self.warnings.append(args[0] % args[1:])
 
549
        self.overrideAttr(trace, 'warning', warning)
 
550
        self.check_append_revisions_only(None, 'not-a-bool')
 
551
        self.assertLength(1, self.warnings)
 
552
        self.assertEqual(
 
553
            'Value "not-a-bool" is not a boolean for "append_revisions_only"',
 
554
            self.warnings[0])
 
555
 
 
556
 
 
557
class TestPullResult(tests.TestCase):
432
558
 
433
559
    def test_pull_result_to_int(self):
434
560
        # to support old code, the pull result can be used as an int
435
 
        r = PullResult()
 
561
        r = _mod_branch.PullResult()
436
562
        r.old_revno = 10
437
563
        r.new_revno = 20
438
564
        # this usage of results is not recommended for new code (because it
441
567
        a = "%d revisions pulled" % r
442
568
        self.assertEqual(a, "10 revisions pulled")
443
569
 
 
570
    def test_report_changed(self):
 
571
        r = _mod_branch.PullResult()
 
572
        r.old_revid = "old-revid"
 
573
        r.old_revno = 10
 
574
        r.new_revid = "new-revid"
 
575
        r.new_revno = 20
 
576
        f = StringIO()
 
577
        r.report(f)
 
578
        self.assertEqual("Now on revision 20.\n", f.getvalue())
 
579
 
 
580
    def test_report_unchanged(self):
 
581
        r = _mod_branch.PullResult()
 
582
        r.old_revid = "same-revid"
 
583
        r.new_revid = "same-revid"
 
584
        f = StringIO()
 
585
        r.report(f)
 
586
        self.assertEqual("No revisions to pull.\n", f.getvalue())
444
587
 
445
588
 
446
589
class _StubLockable(object):
467
610
    """Helper for TestRunWithWriteLockedTarget."""
468
611
 
469
612
 
470
 
class TestRunWithWriteLockedTarget(TestCase):
 
613
class TestRunWithWriteLockedTarget(tests.TestCase):
471
614
    """Tests for _run_with_write_locked_target."""
472
615
 
473
616
    def setUp(self):
474
 
        TestCase.setUp(self)
 
617
        tests.TestCase.setUp(self)
475
618
        self._calls = []
476
619
 
477
620
    def func_that_returns_ok(self):
484
627
 
485
628
    def test_success_unlocks(self):
486
629
        lockable = _StubLockable(self._calls)
487
 
        result = _run_with_write_locked_target(
 
630
        result = _mod_branch._run_with_write_locked_target(
488
631
            lockable, self.func_that_returns_ok)
489
632
        self.assertEqual('ok', result)
490
633
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
492
635
    def test_exception_unlocks_and_propagates(self):
493
636
        lockable = _StubLockable(self._calls)
494
637
        self.assertRaises(_ErrorFromCallable,
495
 
            _run_with_write_locked_target, lockable, self.func_that_raises)
 
638
                          _mod_branch._run_with_write_locked_target,
 
639
                          lockable, self.func_that_raises)
496
640
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
497
641
 
498
642
    def test_callable_succeeds_but_error_during_unlock(self):
499
643
        lockable = _StubLockable(self._calls, unlock_exc=_ErrorFromUnlock())
500
644
        self.assertRaises(_ErrorFromUnlock,
501
 
            _run_with_write_locked_target, lockable, self.func_that_returns_ok)
 
645
                          _mod_branch._run_with_write_locked_target,
 
646
                          lockable, self.func_that_returns_ok)
502
647
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
503
648
 
504
649
    def test_error_during_unlock_does_not_mask_original_error(self):
505
650
        lockable = _StubLockable(self._calls, unlock_exc=_ErrorFromUnlock())
506
651
        self.assertRaises(_ErrorFromCallable,
507
 
            _run_with_write_locked_target, lockable, self.func_that_raises)
 
652
                          _mod_branch._run_with_write_locked_target,
 
653
                          lockable, self.func_that_raises)
508
654
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
509
655
 
510
656