1
# Copyright (C) 2006-2011 Canonical Ltd
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.
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.
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
17
"""Tests for LockDir"""
32
from bzrlib.errors import (
39
from bzrlib.lockdir import (
43
from bzrlib.tests import (
46
TestCaseWithTransport,
49
# These tests are run on the default transport provided by the test framework
50
# (typically a local disk transport). That can be changed by the --transport
51
# option to bzr selftest. The required properties of the transport
52
# implementation are tested separately. (The main requirement is just that
53
# they don't allow overwriting nonempty directories.)
56
class TestLockDir(TestCaseWithTransport):
57
"""Test LockDir operations"""
59
def logging_report_function(self, fmt, *args):
60
self._logged_reports.append((fmt, args))
62
def setup_log_reporter(self, lock_dir):
63
self._logged_reports = []
64
lock_dir._report_function = self.logging_report_function
66
def test_00_lock_creation(self):
67
"""Creation of lock file on a transport"""
68
t = self.get_transport()
69
lf = LockDir(t, 'test_lock')
70
self.assertFalse(lf.is_held)
72
def test_01_lock_repr(self):
73
"""Lock string representation"""
74
lf = LockDir(self.get_transport(), 'test_lock')
76
self.assertContainsRe(r, r'^LockDir\(.*/test_lock\)$')
78
def test_02_unlocked_peek(self):
79
lf = LockDir(self.get_transport(), 'test_lock')
80
self.assertEqual(lf.peek(), None)
83
return LockDir(self.get_transport(), 'test_lock')
85
def test_unlock_after_break_raises(self):
90
ld2.force_break(ld2.peek())
91
self.assertRaises(LockBroken, ld.unlock)
93
def test_03_readonly_peek(self):
94
lf = LockDir(self.get_readonly_transport(), 'test_lock')
95
self.assertEqual(lf.peek(), None)
97
def test_10_lock_uncontested(self):
98
"""Acquire and release a lock"""
99
t = self.get_transport()
100
lf = LockDir(t, 'test_lock')
104
self.assertTrue(lf.is_held)
107
self.assertFalse(lf.is_held)
109
def test_11_create_readonly_transport(self):
110
"""Fail to create lock on readonly transport"""
111
t = self.get_readonly_transport()
112
lf = LockDir(t, 'test_lock')
113
self.assertRaises(LockFailed, lf.create)
115
def test_12_lock_readonly_transport(self):
116
"""Fail to lock on readonly transport"""
117
lf = LockDir(self.get_transport(), 'test_lock')
119
lf = LockDir(self.get_readonly_transport(), 'test_lock')
120
self.assertRaises(LockFailed, lf.attempt_lock)
122
def test_20_lock_contested(self):
123
"""Contention to get a lock"""
124
t = self.get_transport()
125
lf1 = LockDir(t, 'test_lock')
128
lf2 = LockDir(t, 'test_lock')
130
# locking is between LockDir instances; aliases within
131
# a single process are not detected
133
self.fail('Failed to detect lock collision')
134
except LockContention, e:
135
self.assertEqual(e.lock, lf2)
136
self.assertContainsRe(str(e),
137
r'^Could not acquire.*test_lock.*$')
140
def test_20_lock_peek(self):
141
"""Peek at the state of a lock"""
142
t = self.get_transport()
143
lf1 = LockDir(t, 'test_lock')
146
self.addCleanup(lf1.unlock)
147
# lock is held, should get some info on it
149
self.assertEqual(set(info1.info_dict.keys()),
150
set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
151
# should get the same info if we look at it through a different
153
info2 = LockDir(t, 'test_lock').peek()
154
self.assertEqual(info1, info2)
155
# locks which are never used should be not-held
156
self.assertEqual(LockDir(t, 'other_lock').peek(), None)
158
def test_21_peek_readonly(self):
159
"""Peek over a readonly transport"""
160
t = self.get_transport()
161
lf1 = LockDir(t, 'test_lock')
163
lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
164
self.assertEqual(lf2.peek(), None)
166
self.addCleanup(lf1.unlock)
168
self.assertTrue(info2)
169
self.assertEqual(info2.get('nonce'), lf1.nonce)
171
def test_30_lock_wait_fail(self):
172
"""Wait on a lock, then fail
174
We ask to wait up to 400ms; this should fail within at most one
175
second. (Longer times are more realistic but we don't want the test
176
suite to take too long, and this should do for now.)
178
t = self.get_transport()
179
lf1 = LockDir(t, 'test_lock')
181
lf2 = LockDir(t, 'test_lock')
182
self.setup_log_reporter(lf2)
186
self.assertRaises(LockContention, lf2.wait_lock,
187
timeout=0.4, poll=0.1)
189
# it should only take about 0.4 seconds, but we allow more time in
190
# case the machine is heavily loaded
191
self.assertTrue(after - before <= 8.0,
192
"took %f seconds to detect lock contention" % (after - before))
195
self.assertEqual(1, len(self._logged_reports))
196
self.assertContainsRe(self._logged_reports[0][0],
197
r'Unable to obtain lock .* held by jrandom@example\.com on .*'
198
r' \(process #\d+\), acquired .* ago\.\n'
199
r'Will continue to try until \d{2}:\d{2}:\d{2}, unless '
200
r'you press Ctrl-C.\n'
201
r'See "bzr help break-lock" for more.')
203
def test_31_lock_wait_easy(self):
204
"""Succeed when waiting on a lock with no contention.
206
t = self.get_transport()
207
lf1 = LockDir(t, 'test_lock')
209
self.setup_log_reporter(lf1)
212
lf1.wait_lock(timeout=0.4, poll=0.1)
214
self.assertTrue(after - before <= 1.0)
217
self.assertEqual([], self._logged_reports)
219
def test_40_confirm_easy(self):
220
"""Confirm a lock that's already held"""
221
t = self.get_transport()
222
lf1 = LockDir(t, 'test_lock')
225
self.addCleanup(lf1.unlock)
228
def test_41_confirm_not_held(self):
229
"""Confirm a lock that's already held"""
230
t = self.get_transport()
231
lf1 = LockDir(t, 'test_lock')
233
self.assertRaises(LockNotHeld, lf1.confirm)
235
def test_42_confirm_broken_manually(self):
236
"""Confirm a lock broken by hand"""
237
t = self.get_transport()
238
lf1 = LockDir(t, 'test_lock')
241
t.move('test_lock', 'lock_gone_now')
242
self.assertRaises(LockBroken, lf1.confirm)
244
t.move('lock_gone_now', 'test_lock')
247
def test_43_break(self):
248
"""Break a lock whose caller has forgotten it"""
249
t = self.get_transport()
250
lf1 = LockDir(t, 'test_lock')
253
# we incorrectly discard the lock object without unlocking it
255
# someone else sees it's still locked
256
lf2 = LockDir(t, 'test_lock')
257
holder_info = lf2.peek()
258
self.assertTrue(holder_info)
259
lf2.force_break(holder_info)
260
# now we should be able to take it
262
self.addCleanup(lf2.unlock)
265
def test_44_break_already_released(self):
266
"""Lock break races with regular release"""
267
t = self.get_transport()
268
lf1 = LockDir(t, 'test_lock')
271
# someone else sees it's still locked
272
lf2 = LockDir(t, 'test_lock')
273
holder_info = lf2.peek()
274
# in the interim the lock is released
276
# break should succeed
277
lf2.force_break(holder_info)
278
# now we should be able to take it
280
self.addCleanup(lf2.unlock)
283
def test_45_break_mismatch(self):
284
"""Lock break races with someone else acquiring it"""
285
t = self.get_transport()
286
lf1 = LockDir(t, 'test_lock')
289
# someone else sees it's still locked
290
lf2 = LockDir(t, 'test_lock')
291
holder_info = lf2.peek()
292
# in the interim the lock is released
294
lf3 = LockDir(t, 'test_lock')
296
# break should now *fail*
297
self.assertRaises(LockBreakMismatch, lf2.force_break,
301
def test_46_fake_read_lock(self):
302
t = self.get_transport()
303
lf1 = LockDir(t, 'test_lock')
308
def test_50_lockdir_representation(self):
309
"""Check the on-disk representation of LockDirs is as expected.
311
There should always be a top-level directory named by the lock.
312
When the lock is held, there should be a lockname/held directory
313
containing an info file.
315
t = self.get_transport()
316
lf1 = LockDir(t, 'test_lock')
318
self.assertTrue(t.has('test_lock'))
320
self.assertTrue(t.has('test_lock/held/info'))
322
self.assertFalse(t.has('test_lock/held/info'))
324
def test_break_lock(self):
325
# the ui based break_lock routine should Just Work (tm)
326
ld1 = self.get_lock()
327
ld2 = self.get_lock()
330
# do this without IO redirection to ensure it doesn't prompt.
331
self.assertRaises(AssertionError, ld1.break_lock)
332
orig_factory = bzrlib.ui.ui_factory
333
bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
336
self.assertRaises(LockBroken, ld1.unlock)
338
bzrlib.ui.ui_factory = orig_factory
340
def test_break_lock_corrupt_info(self):
341
"""break_lock works even if the info file is corrupt (and tells the UI
345
ld2 = self.get_lock()
348
ld.transport.put_bytes_non_atomic('test_lock/held/info', '\0')
350
class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
354
def get_boolean(self, prompt):
355
self.prompts.append(('boolean', prompt))
358
ui = LoggingUIFactory()
359
self.overrideAttr(bzrlib.ui, 'ui_factory', ui)
361
self.assertLength(1, ui.prompts)
362
self.assertEqual('boolean', ui.prompts[0][0])
363
self.assertStartsWith(ui.prompts[0][1], 'Break (corrupt LockDir')
364
self.assertRaises(LockBroken, ld.unlock)
366
def test_break_lock_missing_info(self):
367
"""break_lock works even if the info file is missing (and tells the UI
371
ld2 = self.get_lock()
374
ld.transport.delete('test_lock/held/info')
376
class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
380
def get_boolean(self, prompt):
381
self.prompts.append(('boolean', prompt))
384
ui = LoggingUIFactory()
385
orig_factory = bzrlib.ui.ui_factory
386
bzrlib.ui.ui_factory = ui
389
self.assertRaises(LockBroken, ld.unlock)
390
self.assertLength(0, ui.prompts)
392
bzrlib.ui.ui_factory = orig_factory
393
# Suppress warnings due to ld not being unlocked
394
# XXX: if lock_broken hook was invoked in this case, this hack would
395
# not be necessary. - Andrew Bennetts, 2010-09-06.
396
del self._lock_actions[:]
398
def test_create_missing_base_directory(self):
399
"""If LockDir.path doesn't exist, it can be created
401
Some people manually remove the entire lock/ directory trying
402
to unlock a stuck repository/branch/etc. Rather than failing
403
after that, just create the lock directory when needed.
405
t = self.get_transport()
406
lf1 = LockDir(t, 'test_lock')
409
self.assertTrue(t.has('test_lock'))
412
self.assertFalse(t.has('test_lock'))
414
# This will create 'test_lock' if it needs to
416
self.assertTrue(t.has('test_lock'))
417
self.assertTrue(t.has('test_lock/held/info'))
420
self.assertFalse(t.has('test_lock/held/info'))
422
def test_display_form(self):
423
ld1 = self.get_lock()
427
info_list = ld1.peek().to_readable_dict()
430
self.assertEqual(info_list['user'], u'jrandom@example.com')
431
self.assertContainsRe(info_list['pid'], '^\d+$')
432
self.assertContainsRe(info_list['time_ago'], r'^\d+ seconds? ago$')
434
def test_lock_without_email(self):
435
global_config = config.GlobalConfig()
436
# Intentionally has no email address
437
global_config.set_user_option('email', 'User Identity')
438
ld1 = self.get_lock()
443
def test_lock_permission(self):
444
self.requireFeature(features.not_running_as_root)
445
if not osutils.supports_posix_readonly():
446
raise tests.TestSkipped('Cannot induce a permission failure')
447
ld1 = self.get_lock()
448
lock_path = ld1.transport.local_abspath('test_lock')
450
osutils.make_readonly(lock_path)
451
self.assertRaises(errors.LockFailed, ld1.attempt_lock)
453
def test_lock_by_token(self):
454
ld1 = self.get_lock()
455
token = ld1.lock_write()
456
self.addCleanup(ld1.unlock)
457
self.assertNotEqual(None, token)
458
ld2 = self.get_lock()
459
t2 = ld2.lock_write(token)
460
self.addCleanup(ld2.unlock)
461
self.assertEqual(token, t2)
463
def test_lock_with_buggy_rename(self):
464
# test that lock acquisition handles servers which pretend they
465
# renamed correctly but that actually fail
466
t = transport.get_transport_from_url(
467
'brokenrename+' + self.get_url())
468
ld1 = LockDir(t, 'test_lock')
471
ld2 = LockDir(t, 'test_lock')
472
# we should fail to lock
473
e = self.assertRaises(errors.LockContention, ld2.attempt_lock)
474
# now the original caller should succeed in unlocking
476
# and there should be nothing left over
477
self.assertEquals([], t.list_dir('test_lock'))
479
def test_failed_lock_leaves_no_trash(self):
480
# if we fail to acquire the lock, we don't leave pending directories
481
# behind -- https://bugs.launchpad.net/bzr/+bug/109169
482
ld1 = self.get_lock()
483
ld2 = self.get_lock()
484
# should be nothing before we start
486
t = self.get_transport().clone('test_lock')
489
self.assertEquals(a, t.list_dir('.'))
492
# when held, that's all we see
494
self.addCleanup(ld1.unlock)
496
# second guy should fail
497
self.assertRaises(errors.LockContention, ld2.attempt_lock)
501
def test_no_lockdir_info(self):
502
"""We can cope with empty info files."""
503
# This seems like a fairly common failure case - see
504
# <https://bugs.launchpad.net/bzr/+bug/185103> and all its dupes.
505
# Processes are often interrupted after opening the file
506
# before the actual contents are committed.
507
t = self.get_transport()
509
t.mkdir('test_lock/held')
510
t.put_bytes('test_lock/held/info', '')
511
lf = LockDir(t, 'test_lock')
513
formatted_info = info.to_readable_dict()
515
dict(user='<unknown>', hostname='<unknown>', pid='<unknown>',
516
time_ago='(unknown)'),
519
def test_corrupt_lockdir_info(self):
520
"""We can cope with corrupt (and thus unparseable) info files."""
521
# This seems like a fairly common failure case too - see
522
# <https://bugs.launchpad.net/bzr/+bug/619872> for instance.
523
# In particular some systems tend to fill recently created files with
524
# nul bytes after recovering from a system crash.
525
t = self.get_transport()
527
t.mkdir('test_lock/held')
528
t.put_bytes('test_lock/held/info', '\0')
529
lf = LockDir(t, 'test_lock')
530
self.assertRaises(errors.LockCorrupt, lf.peek)
531
# Currently attempt_lock gives LockContention, but LockCorrupt would be
532
# a reasonable result too.
534
(errors.LockCorrupt, errors.LockContention), lf.attempt_lock)
535
self.assertRaises(errors.LockCorrupt, lf.validate_token, 'fake token')
537
def test_missing_lockdir_info(self):
538
"""We can cope with absent info files."""
539
t = self.get_transport()
541
t.mkdir('test_lock/held')
542
lf = LockDir(t, 'test_lock')
543
# In this case we expect the 'not held' result from peek, because peek
544
# cannot be expected to notice that there is a 'held' directory with no
546
self.assertEqual(None, lf.peek())
547
# And lock/unlock may work or give LockContention (but not any other
551
except LockContention:
552
# LockContention is ok, and expected on Windows
555
# no error is ok, and expected on POSIX (because POSIX allows
556
# os.rename over an empty directory).
558
# Currently raises TokenMismatch, but LockCorrupt would be reasonable
561
(errors.TokenMismatch, errors.LockCorrupt),
562
lf.validate_token, 'fake token')
565
class TestLockDirHooks(TestCaseWithTransport):
568
super(TestLockDirHooks, self).setUp()
572
return LockDir(self.get_transport(), 'test_lock')
574
def record_hook(self, result):
575
self._calls.append(result)
577
def test_LockDir_acquired_success(self):
578
# the LockDir.lock_acquired hook fires when a lock is acquired.
579
LockDir.hooks.install_named_hook('lock_acquired',
580
self.record_hook, 'record_hook')
583
self.assertEqual([], self._calls)
584
result = ld.attempt_lock()
585
lock_path = ld.transport.abspath(ld.path)
586
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
588
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
590
def test_LockDir_acquired_fail(self):
591
# the LockDir.lock_acquired hook does not fire on failure.
594
ld2 = self.get_lock()
596
# install a lock hook now, when the disk lock is locked
597
LockDir.hooks.install_named_hook('lock_acquired',
598
self.record_hook, 'record_hook')
599
self.assertRaises(errors.LockContention, ld.attempt_lock)
600
self.assertEqual([], self._calls)
602
self.assertEqual([], self._calls)
604
def test_LockDir_released_success(self):
605
# the LockDir.lock_released hook fires when a lock is acquired.
606
LockDir.hooks.install_named_hook('lock_released',
607
self.record_hook, 'record_hook')
610
self.assertEqual([], self._calls)
611
result = ld.attempt_lock()
612
self.assertEqual([], self._calls)
614
lock_path = ld.transport.abspath(ld.path)
615
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
617
def test_LockDir_released_fail(self):
618
# the LockDir.lock_released hook does not fire on failure.
621
ld2 = self.get_lock()
623
ld2.force_break(ld2.peek())
624
LockDir.hooks.install_named_hook('lock_released',
625
self.record_hook, 'record_hook')
626
self.assertRaises(LockBroken, ld.unlock)
627
self.assertEqual([], self._calls)
629
def test_LockDir_broken_success(self):
630
# the LockDir.lock_broken hook fires when a lock is broken.
633
ld2 = self.get_lock()
634
result = ld.attempt_lock()
635
LockDir.hooks.install_named_hook('lock_broken',
636
self.record_hook, 'record_hook')
637
ld2.force_break(ld2.peek())
638
lock_path = ld.transport.abspath(ld.path)
639
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
641
def test_LockDir_broken_failure(self):
642
# the LockDir.lock_broken hook does not fires when a lock is already
646
ld2 = self.get_lock()
647
result = ld.attempt_lock()
648
holder_info = ld2.peek()
650
LockDir.hooks.install_named_hook('lock_broken',
651
self.record_hook, 'record_hook')
652
ld2.force_break(holder_info)
653
lock_path = ld.transport.abspath(ld.path)
654
self.assertEqual([], self._calls)
657
class TestLockHeldInfo(TestCase):
658
"""Can get information about the lock holder, and detect whether they're
662
info = LockHeldInfo.for_this_process(None)
663
self.assertContainsRe(repr(info), r"LockHeldInfo\(.*\)")
665
def test_unicode(self):
666
info = LockHeldInfo.for_this_process(None)
667
self.assertContainsRe(unicode(info),
668
r'held by .* on .* \(process #\d+\), acquired .* ago')
670
def test_is_locked_by_this_process(self):
671
info = LockHeldInfo.for_this_process(None)
672
self.assertTrue(info.is_locked_by_this_process())
674
def test_is_not_locked_by_this_process(self):
675
info = LockHeldInfo.for_this_process(None)
676
info.info_dict['pid'] = '123123123123123'
677
self.assertFalse(info.is_locked_by_this_process())
679
def test_lock_holder_live_process(self):
680
"""Detect that the holder (this process) is still running."""
681
info = LockHeldInfo.for_this_process(None)
682
self.assertFalse(info.is_lock_holder_known_dead())
684
def test_lock_holder_dead_process(self):
685
"""Detect that the holder (this process) is still running."""
686
self.overrideAttr(lockdir, 'get_host_name',
687
lambda: 'aproperhostname')
688
info = LockHeldInfo.for_this_process(None)
689
info.info_dict['pid'] = '123123123'
690
self.assertTrue(info.is_lock_holder_known_dead())
692
def test_lock_holder_other_machine(self):
693
"""The lock holder isn't here so we don't know if they're alive."""
694
info = LockHeldInfo.for_this_process(None)
695
info.info_dict['hostname'] = 'egg.example.com'
696
info.info_dict['pid'] = '123123123'
697
self.assertFalse(info.is_lock_holder_known_dead())
699
def test_lock_holder_other_user(self):
700
"""Only auto-break locks held by this user."""
701
info = LockHeldInfo.for_this_process(None)
702
info.info_dict['user'] = 'notme@example.com'
703
info.info_dict['pid'] = '123123123'
704
self.assertFalse(info.is_lock_holder_known_dead())
706
def test_no_good_hostname(self):
707
"""Correctly handle ambiguous hostnames.
709
If the lock's recorded with just 'localhost' we can't really trust
710
it's the same 'localhost'. (There are quite a few of them. :-)
711
So even if the process is known not to be alive, we can't say that's
714
self.overrideAttr(lockdir, 'get_host_name',
716
info = LockHeldInfo.for_this_process(None)
717
info.info_dict['pid'] = '123123123'
718
self.assertFalse(info.is_lock_holder_known_dead())
721
class TestStaleLockDir(TestCaseWithTransport):
722
"""Can automatically break stale locks.
724
:see: https://bugs.launchpad.net/bzr/+bug/220464
727
def test_auto_break_stale_lock(self):
728
"""Locks safely known to be stale are just cleaned up.
730
This generates a warning but no other user interaction.
732
self.overrideAttr(lockdir, 'get_host_name',
733
lambda: 'aproperhostname')
734
# This is off by default at present; see the discussion in the bug.
735
# If you change the default, don't forget to update the docs.
736
config.GlobalConfig().set_user_option('locks.steal_dead', True)
737
# Create a lock pretending to come from a different nonexistent
738
# process on the same machine.
739
l1 = LockDir(self.get_transport(), 'a',
740
extra_holder_info={'pid': '12312313'})
741
token_1 = l1.attempt_lock()
742
l2 = LockDir(self.get_transport(), 'a')
743
token_2 = l2.attempt_lock()
744
# l1 will notice its lock was stolen.
745
self.assertRaises(errors.LockBroken,
749
def test_auto_break_stale_lock_configured_off(self):
750
"""Automatic breaking can be turned off"""
751
l1 = LockDir(self.get_transport(), 'a',
752
extra_holder_info={'pid': '12312313'})
753
token_1 = l1.attempt_lock()
754
self.addCleanup(l1.unlock)
755
l2 = LockDir(self.get_transport(), 'a')
756
# This fails now, because dead lock breaking is off by default.
757
self.assertRaises(LockContention,
759
# and it's in fact not broken