344
330
# do this without IO redirection to ensure it doesn't prompt.
345
331
self.assertRaises(AssertionError, ld1.break_lock)
346
332
orig_factory = bzrlib.ui.ui_factory
347
# silent ui - no need for stdout
348
bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
349
bzrlib.ui.ui_factory.stdin = StringIO("y\n")
333
bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
352
336
self.assertRaises(LockBroken, ld1.unlock)
354
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