13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Tests for the Branch facility that are not interface tests.
19
For interface tests see tests/per_branch/*.py.
19
For interface tests see tests/branch_implementations/*.py.
21
21
For concrete class tests see this file, and for meta-branch tests
22
22
also see this file.
25
from cStringIO import StringIO
25
from StringIO import StringIO
27
27
from bzrlib import (
28
28
branch as _mod_branch,
39
class TestDefaultFormat(tests.TestCase):
35
from bzrlib.branch import (
39
BranchReferenceFormat,
45
from bzrlib.bzrdir import (BzrDirMetaFormat1, BzrDirMeta1,
47
from bzrlib.errors import (NotBranchError,
50
UnsupportedFormatError,
53
from bzrlib.tests import TestCase, TestCaseWithTransport
54
from bzrlib.transport import get_transport
56
class TestDefaultFormat(TestCase):
41
58
def test_default_format(self):
42
59
# update this if you change the default branch format
43
self.assertIsInstance(_mod_branch.BranchFormat.get_default_format(),
44
_mod_branch.BzrBranchFormat7)
60
self.assertIsInstance(BranchFormat.get_default_format(),
46
63
def test_default_format_is_same_as_bzrdir_default(self):
47
64
# XXX: it might be nice if there was only one place the default was
48
# set, but at the moment that's not true -- mbp 20070814 --
65
# set, but at the moment that's not true -- mbp 20070814 --
49
66
# https://bugs.launchpad.net/bzr/+bug/132376
51
_mod_branch.BranchFormat.get_default_format(),
52
bzrdir.BzrDirFormat.get_default_format().get_branch_format())
67
self.assertEqual(BranchFormat.get_default_format(),
68
BzrDirFormat.get_default_format().get_branch_format())
54
70
def test_get_set_default_format(self):
55
71
# set the format and then set it back again
56
old_format = _mod_branch.BranchFormat.get_default_format()
57
_mod_branch.BranchFormat.set_default_format(SampleBranchFormat())
72
old_format = BranchFormat.get_default_format()
73
BranchFormat.set_default_format(SampleBranchFormat())
59
75
# the default branch format is used by the meta dir format
60
76
# which is not the default bzrdir format at this point
61
dir = bzrdir.BzrDirMetaFormat1().initialize('memory:///')
77
dir = BzrDirMetaFormat1().initialize('memory:///')
62
78
result = dir.create_branch()
63
79
self.assertEqual(result, 'A branch')
65
_mod_branch.BranchFormat.set_default_format(old_format)
66
self.assertEqual(old_format,
67
_mod_branch.BranchFormat.get_default_format())
70
class TestBranchFormat5(tests.TestCaseWithTransport):
81
BranchFormat.set_default_format(old_format)
82
self.assertEqual(old_format, BranchFormat.get_default_format())
85
class TestBranchFormat5(TestCaseWithTransport):
71
86
"""Tests specific to branch format 5"""
73
88
def test_branch_format_5_uses_lockdir(self):
74
89
url = self.get_url()
75
bdir = bzrdir.BzrDirMetaFormat1().initialize(url)
76
bdir.create_repository()
77
branch = bdir.create_branch()
90
bzrdir = BzrDirMetaFormat1().initialize(url)
91
bzrdir.create_repository()
92
branch = bzrdir.create_branch()
78
93
t = self.get_transport()
79
94
self.log("branch instance is %r" % branch)
80
self.assert_(isinstance(branch, _mod_branch.BzrBranch5))
95
self.assert_(isinstance(branch, BzrBranch5))
81
96
self.assertIsDirectory('.', t)
82
97
self.assertIsDirectory('.bzr/branch', t)
83
98
self.assertIsDirectory('.bzr/branch/lock', t)
84
99
branch.lock_write()
85
self.addCleanup(branch.unlock)
86
self.assertIsDirectory('.bzr/branch/lock/held', t)
101
self.assertIsDirectory('.bzr/branch/lock/held', t)
88
105
def test_set_push_location(self):
89
106
from bzrlib.config import (locations_config_filename,
90
107
ensure_config_dir_exists)
91
108
ensure_config_dir_exists()
92
109
fn = locations_config_filename()
93
# write correct newlines to locations.conf
94
# by default ConfigObj uses native line-endings for new files
95
# but uses already existing line-endings if file is not empty
98
f.write('# comment\n')
102
110
branch = self.make_branch('.', format='knit')
103
111
branch.set_push_location('foo')
104
112
local_path = urlutils.local_path_from_url(branch.base[:-1])
105
self.assertFileEqual("# comment\n"
113
self.assertFileEqual("[%s]\n"
107
114
"push_location = foo\n"
108
"push_location:policy = norecurse\n" % local_path,
115
"push_location:policy = norecurse" % local_path,
111
118
# TODO RBC 20051029 test getting a push location from a branch in a
112
119
# recursive section - that is, it appends the branch name.
115
class SampleBranchFormat(_mod_branch.BranchFormat):
122
class SampleBranchFormat(BranchFormat):
116
123
"""A sample format
118
this format is initializable, unsupported to aid in testing the
125
this format is initializable, unsupported to aid in testing the
119
126
open and open_downlevel routines.
123
130
"""See BzrBranchFormat.get_format_string()."""
124
131
return "Sample branch format."
126
def initialize(self, a_bzrdir, name=None):
133
def initialize(self, a_bzrdir):
127
134
"""Format 4 branches cannot be created."""
128
t = a_bzrdir.get_branch_transport(self, name=name)
135
t = a_bzrdir.get_branch_transport(self)
129
136
t.put_bytes('format', self.get_format_string())
130
137
return 'A branch'
132
139
def is_supported(self):
135
def open(self, transport, name=None, _found=False, ignore_fallbacks=False):
142
def open(self, transport, _found=False):
136
143
return "opened branch."
139
class TestBzrBranchFormat(tests.TestCaseWithTransport):
146
class TestBzrBranchFormat(TestCaseWithTransport):
140
147
"""Tests for the BzrBranchFormat facility."""
142
149
def test_find_format(self):
143
150
# is the right format object found for a branch?
144
151
# create a branch with a few known format objects.
145
# this is not quite the same as
152
# this is not quite the same as
146
153
self.build_tree(["foo/", "bar/"])
147
154
def check_format(format, url):
148
155
dir = format._matchingbzrdir.initialize(url)
149
156
dir.create_repository()
150
157
format.initialize(dir)
151
found_format = _mod_branch.BranchFormat.find_format(dir)
158
found_format = BranchFormat.find_format(dir)
152
159
self.failUnless(isinstance(found_format, format.__class__))
153
check_format(_mod_branch.BzrBranchFormat5(), "bar")
160
check_format(BzrBranchFormat5(), "bar")
155
162
def test_find_format_not_branch(self):
156
163
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
157
self.assertRaises(errors.NotBranchError,
158
_mod_branch.BranchFormat.find_format,
164
self.assertRaises(NotBranchError,
165
BranchFormat.find_format,
161
168
def test_find_format_unknown_format(self):
162
169
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
163
170
SampleBranchFormat().initialize(dir)
164
self.assertRaises(errors.UnknownFormatError,
165
_mod_branch.BranchFormat.find_format,
171
self.assertRaises(UnknownFormatError,
172
BranchFormat.find_format,
168
175
def test_register_unregister_format(self):
173
180
format.initialize(dir)
174
181
# register a format for it.
175
_mod_branch.BranchFormat.register_format(format)
182
BranchFormat.register_format(format)
176
183
# which branch.Open will refuse (not supported)
177
self.assertRaises(errors.UnsupportedFormatError,
178
_mod_branch.Branch.open, self.get_url())
184
self.assertRaises(UnsupportedFormatError, Branch.open, self.get_url())
179
185
self.make_branch_and_tree('foo')
180
186
# but open_downlevel will work
183
bzrdir.BzrDir.open(self.get_url()).open_branch(unsupported=True))
187
self.assertEqual(format.open(dir), bzrdir.BzrDir.open(self.get_url()).open_branch(unsupported=True))
184
188
# unregister the format
185
_mod_branch.BranchFormat.unregister_format(format)
189
BranchFormat.unregister_format(format)
186
190
self.make_branch_and_tree('bar')
189
class TestBranch67(object):
190
"""Common tests for both branch 6 and 7 which are mostly the same."""
192
def get_format_name(self):
193
raise NotImplementedError(self.get_format_name)
195
def get_format_name_subtree(self):
196
raise NotImplementedError(self.get_format_name)
199
raise NotImplementedError(self.get_class)
193
class TestBranch6(TestCaseWithTransport):
201
195
def test_creation(self):
202
format = bzrdir.BzrDirMetaFormat1()
196
format = BzrDirMetaFormat1()
203
197
format.set_branch_format(_mod_branch.BzrBranchFormat6())
204
198
branch = self.make_branch('a', format=format)
205
self.assertIsInstance(branch, self.get_class())
206
branch = self.make_branch('b', format=self.get_format_name())
207
self.assertIsInstance(branch, self.get_class())
199
self.assertIsInstance(branch, _mod_branch.BzrBranch6)
200
branch = self.make_branch('b', format='dirstate-tags')
201
self.assertIsInstance(branch, _mod_branch.BzrBranch6)
208
202
branch = _mod_branch.Branch.open('a')
209
self.assertIsInstance(branch, self.get_class())
203
self.assertIsInstance(branch, _mod_branch.BzrBranch6)
211
205
def test_layout(self):
212
branch = self.make_branch('a', format=self.get_format_name())
206
branch = self.make_branch('a', format='dirstate-tags')
213
207
self.failUnlessExists('a/.bzr/branch/last-revision')
214
208
self.failIfExists('a/.bzr/branch/revision-history')
215
self.failIfExists('a/.bzr/branch/references')
217
210
def test_config(self):
218
211
"""Ensure that all configuration data is stored in the branch"""
219
branch = self.make_branch('a', format=self.get_format_name())
212
branch = self.make_branch('a', format='dirstate-tags')
220
213
branch.set_parent('http://bazaar-vcs.org')
221
214
self.failIfExists('a/.bzr/branch/parent')
222
215
self.assertEqual('http://bazaar-vcs.org', branch.get_parent())
229
222
self.assertEqual('ftp://bazaar-vcs.org', branch.get_bound_location())
231
224
def test_set_revision_history(self):
232
builder = self.make_branch_builder('.', format=self.get_format_name())
233
builder.build_snapshot('foo', None,
234
[('add', ('', None, 'directory', None))],
236
builder.build_snapshot('bar', None, [], message='bar')
237
branch = builder.get_branch()
239
self.addCleanup(branch.unlock)
240
branch.set_revision_history(['foo', 'bar'])
241
branch.set_revision_history(['foo'])
242
self.assertRaises(errors.NotLefthandHistory,
243
branch.set_revision_history, ['bar'])
225
tree = self.make_branch_and_memory_tree('.',
226
format='dirstate-tags')
230
tree.commit('foo', rev_id='foo')
231
tree.commit('bar', rev_id='bar')
232
tree.branch.set_revision_history(['foo', 'bar'])
233
tree.branch.set_revision_history(['foo'])
234
self.assertRaises(errors.NotLefthandHistory,
235
tree.branch.set_revision_history, ['bar'])
245
239
def do_checkout_test(self, lightweight=False):
246
tree = self.make_branch_and_tree('source',
247
format=self.get_format_name_subtree())
240
tree = self.make_branch_and_tree('source', format='dirstate-with-subtree')
248
241
subtree = self.make_branch_and_tree('source/subtree',
249
format=self.get_format_name_subtree())
242
format='dirstate-with-subtree')
250
243
subsubtree = self.make_branch_and_tree('source/subtree/subsubtree',
251
format=self.get_format_name_subtree())
244
format='dirstate-with-subtree')
252
245
self.build_tree(['source/subtree/file',
253
246
'source/subtree/subsubtree/file'])
254
247
subsubtree.add('file')
291
284
self.assertEqual(warnings[0], 'Value "new" is masked by "old" from '
292
285
'locations.conf')
295
class TestBranch6(TestBranch67, tests.TestCaseWithTransport):
298
return _mod_branch.BzrBranch6
300
def get_format_name(self):
301
return "dirstate-tags"
303
def get_format_name_subtree(self):
304
return "dirstate-with-subtree"
306
def test_set_stacked_on_url_errors(self):
307
branch = self.make_branch('a', format=self.get_format_name())
308
self.assertRaises(errors.UnstackableBranchFormat,
309
branch.set_stacked_on_url, None)
311
def test_default_stacked_location(self):
312
branch = self.make_branch('a', format=self.get_format_name())
313
self.assertRaises(errors.UnstackableBranchFormat, branch.get_stacked_on_url)
316
class TestBranch7(TestBranch67, tests.TestCaseWithTransport):
319
return _mod_branch.BzrBranch7
321
def get_format_name(self):
324
def get_format_name_subtree(self):
325
return "development-subtree"
327
def test_set_stacked_on_url_unstackable_repo(self):
328
repo = self.make_repository('a', format='dirstate-tags')
329
control = repo.bzrdir
330
branch = _mod_branch.BzrBranchFormat7().initialize(control)
331
target = self.make_branch('b')
332
self.assertRaises(errors.UnstackableRepositoryFormat,
333
branch.set_stacked_on_url, target.base)
335
def test_clone_stacked_on_unstackable_repo(self):
336
repo = self.make_repository('a', format='dirstate-tags')
337
control = repo.bzrdir
338
branch = _mod_branch.BzrBranchFormat7().initialize(control)
339
# Calling clone should not raise UnstackableRepositoryFormat.
340
cloned_bzrdir = control.clone('cloned')
342
def _test_default_stacked_location(self):
343
branch = self.make_branch('a', format=self.get_format_name())
344
self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
346
def test_stack_and_unstack(self):
347
branch = self.make_branch('a', format=self.get_format_name())
348
target = self.make_branch_and_tree('b', format=self.get_format_name())
349
branch.set_stacked_on_url(target.branch.base)
350
self.assertEqual(target.branch.base, branch.get_stacked_on_url())
351
revid = target.commit('foo')
352
self.assertTrue(branch.repository.has_revision(revid))
353
branch.set_stacked_on_url(None)
354
self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
355
self.assertFalse(branch.repository.has_revision(revid))
357
def test_open_opens_stacked_reference(self):
358
branch = self.make_branch('a', format=self.get_format_name())
359
target = self.make_branch_and_tree('b', format=self.get_format_name())
360
branch.set_stacked_on_url(target.branch.base)
361
branch = branch.bzrdir.open_branch()
362
revid = target.commit('foo')
363
self.assertTrue(branch.repository.has_revision(revid))
366
class BzrBranch8(tests.TestCaseWithTransport):
368
def make_branch(self, location, format=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)
375
def create_branch_with_reference(self):
376
branch = self.make_branch('branch')
377
branch._set_all_reference_info({'file-id': ('path', 'location')})
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
388
def test_reference_info_caching_read_locked(self):
390
branch = self.create_branch_with_reference()
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))
398
def test_reference_info_caching_read_unlocked(self):
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))
406
def test_reference_info_caching_write_locked(self):
408
branch = self.make_branch('branch')
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)
418
def test_reference_info_caches_cleared(self):
419
branch = self.make_branch('branch')
421
branch.set_reference_info('file-id', 'path2', 'location2')
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'))
428
class TestBranchReference(tests.TestCaseWithTransport):
287
class TestBranchReference(TestCaseWithTransport):
429
288
"""Tests for the branch reference facility."""
431
290
def test_create_open_reference(self):
432
291
bzrdirformat = bzrdir.BzrDirMetaFormat1()
433
t = transport.get_transport(self.get_url('.'))
292
t = get_transport(self.get_url('.'))
435
294
dir = bzrdirformat.initialize(self.get_url('repo'))
436
295
dir.create_repository()
437
296
target_branch = dir.create_branch()
438
297
t.mkdir('branch')
439
298
branch_dir = bzrdirformat.initialize(self.get_url('branch'))
440
made_branch = _mod_branch.BranchReferenceFormat().initialize(
441
branch_dir, target_branch=target_branch)
299
made_branch = BranchReferenceFormat().initialize(branch_dir, target_branch)
442
300
self.assertEqual(made_branch.base, target_branch.base)
443
301
opened_branch = branch_dir.open_branch()
444
302
self.assertEqual(opened_branch.base, target_branch.base)
455
313
_mod_branch.BranchReferenceFormat().get_reference(checkout.bzrdir))
458
class TestHooks(tests.TestCaseWithTransport):
316
class TestHooks(TestCase):
460
318
def test_constructor(self):
461
319
"""Check that creating a BranchHooks instance has the right defaults."""
462
hooks = _mod_branch.BranchHooks()
320
hooks = BranchHooks()
463
321
self.assertTrue("set_rh" in hooks, "set_rh not in %s" % hooks)
464
322
self.assertTrue("post_push" in hooks, "post_push not in %s" % hooks)
465
323
self.assertTrue("post_commit" in hooks, "post_commit not in %s" % hooks)
466
324
self.assertTrue("pre_commit" in hooks, "pre_commit not in %s" % hooks)
467
325
self.assertTrue("post_pull" in hooks, "post_pull not in %s" % hooks)
468
self.assertTrue("post_uncommit" in hooks,
469
"post_uncommit not in %s" % hooks)
470
self.assertTrue("post_change_branch_tip" in hooks,
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)
326
self.assertTrue("post_uncommit" in hooks, "post_uncommit not in %s" % hooks)
477
328
def test_installed_hooks_are_BranchHooks(self):
478
329
"""The installed hooks object should be a BranchHooks."""
479
330
# the installed hooks are saved in self._preserved_hooks.
480
self.assertIsInstance(self._preserved_hooks[_mod_branch.Branch][1],
481
_mod_branch.BranchHooks)
483
def test_post_branch_init_hook(self):
485
_mod_branch.Branch.hooks.install_named_hook('post_branch_init',
487
self.assertLength(0, calls)
488
branch = self.make_branch('a')
489
self.assertLength(1, calls)
491
self.assertIsInstance(params, _mod_branch.BranchInitHookParams)
492
self.assertTrue(hasattr(params, 'bzrdir'))
493
self.assertTrue(hasattr(params, 'branch'))
495
def test_post_switch_hook(self):
496
from bzrlib import switch
498
_mod_branch.Branch.hooks.install_named_hook('post_switch',
500
tree = self.make_branch_and_tree('branch-1')
501
self.build_tree(['branch-1/file-1'])
504
to_branch = tree.bzrdir.sprout('branch-2').open_branch()
505
self.build_tree(['branch-1/file-2'])
507
tree.remove('file-1')
509
checkout = tree.branch.create_checkout('checkout')
510
self.assertLength(0, calls)
511
switch.switch(checkout.bzrdir, to_branch)
512
self.assertLength(1, calls)
514
self.assertIsInstance(params, _mod_branch.SwitchHookParams)
515
self.assertTrue(hasattr(params, 'to_branch'))
516
self.assertTrue(hasattr(params, 'revision_id'))
519
class TestBranchOptions(tests.TestCaseWithTransport):
522
super(TestBranchOptions, self).setUp()
523
self.branch = self.make_branch('.')
524
self.config = self.branch.get_config()
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())
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')
544
def test_invalid_append_revisions_only(self):
545
"""Ensure warning is noted on invalid settings"""
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)
553
'Value "not-a-bool" is not a boolean for "append_revisions_only"',
557
class TestPullResult(tests.TestCase):
331
self.assertIsInstance(self._preserved_hooks[_mod_branch.Branch], BranchHooks)
334
class TestPullResult(TestCase):
559
336
def test_pull_result_to_int(self):
560
337
# to support old code, the pull result can be used as an int
561
r = _mod_branch.PullResult()
564
341
# this usage of results is not recommended for new code (because it
566
343
# it's still supported
567
344
a = "%d revisions pulled" % r
568
345
self.assertEqual(a, "10 revisions pulled")
570
def test_report_changed(self):
571
r = _mod_branch.PullResult()
572
r.old_revid = "old-revid"
574
r.new_revid = "new-revid"
578
self.assertEqual("Now on revision 20.\n", f.getvalue())
580
def test_report_unchanged(self):
581
r = _mod_branch.PullResult()
582
r.old_revid = "same-revid"
583
r.new_revid = "same-revid"
586
self.assertEqual("No revisions to pull.\n", f.getvalue())
589
class _StubLockable(object):
590
"""Helper for TestRunWithWriteLockedTarget."""
592
def __init__(self, calls, unlock_exc=None):
594
self.unlock_exc = unlock_exc
596
def lock_write(self):
597
self.calls.append('lock_write')
600
self.calls.append('unlock')
601
if self.unlock_exc is not None:
602
raise self.unlock_exc
605
class _ErrorFromCallable(Exception):
606
"""Helper for TestRunWithWriteLockedTarget."""
609
class _ErrorFromUnlock(Exception):
610
"""Helper for TestRunWithWriteLockedTarget."""
613
class TestRunWithWriteLockedTarget(tests.TestCase):
614
"""Tests for _run_with_write_locked_target."""
617
tests.TestCase.setUp(self)
620
def func_that_returns_ok(self):
621
self._calls.append('func called')
624
def func_that_raises(self):
625
self._calls.append('func called')
626
raise _ErrorFromCallable()
628
def test_success_unlocks(self):
629
lockable = _StubLockable(self._calls)
630
result = _mod_branch._run_with_write_locked_target(
631
lockable, self.func_that_returns_ok)
632
self.assertEqual('ok', result)
633
self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
635
def test_exception_unlocks_and_propagates(self):
636
lockable = _StubLockable(self._calls)
637
self.assertRaises(_ErrorFromCallable,
638
_mod_branch._run_with_write_locked_target,
639
lockable, self.func_that_raises)
640
self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
642
def test_callable_succeeds_but_error_during_unlock(self):
643
lockable = _StubLockable(self._calls, unlock_exc=_ErrorFromUnlock())
644
self.assertRaises(_ErrorFromUnlock,
645
_mod_branch._run_with_write_locked_target,
646
lockable, self.func_that_returns_ok)
647
self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
649
def test_error_during_unlock_does_not_mask_original_error(self):
650
lockable = _StubLockable(self._calls, unlock_exc=_ErrorFromUnlock())
651
self.assertRaises(_ErrorFromCallable,
652
_mod_branch._run_with_write_locked_target,
653
lockable, self.func_that_raises)
654
self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)