318
322
self.assertTrue(t.has('test_lock/held/info'))
320
324
self.assertFalse(t.has('test_lock/held/info'))
326
def test_break_lock(self):
327
# the ui based break_lock routine should Just Work (tm)
328
ld1 = self.get_lock()
329
ld2 = self.get_lock()
332
# do this without IO redirection to ensure it doesn't prompt.
333
self.assertRaises(AssertionError, ld1.break_lock)
334
orig_factory = bzrlib.ui.ui_factory
335
bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
338
self.assertRaises(LockBroken, ld1.unlock)
340
bzrlib.ui.ui_factory = orig_factory
342
def test_break_lock_corrupt_info(self):
343
"""break_lock works even if the info file is corrupt (and tells the UI
347
ld2 = self.get_lock()
350
ld.transport.put_bytes_non_atomic('test_lock/held/info', '\0')
352
class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
356
def get_boolean(self, prompt):
357
self.prompts.append(('boolean', prompt))
360
ui = LoggingUIFactory()
361
self.overrideAttr(bzrlib.ui, 'ui_factory', ui)
363
self.assertLength(1, ui.prompts)
364
self.assertEqual('boolean', ui.prompts[0][0])
365
self.assertStartsWith(ui.prompts[0][1], 'Break (corrupt LockDir')
366
self.assertRaises(LockBroken, ld.unlock)
368
def test_break_lock_missing_info(self):
369
"""break_lock works even if the info file is missing (and tells the UI
373
ld2 = self.get_lock()
376
ld.transport.delete('test_lock/held/info')
378
class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
382
def get_boolean(self, prompt):
383
self.prompts.append(('boolean', prompt))
386
ui = LoggingUIFactory()
387
orig_factory = bzrlib.ui.ui_factory
388
bzrlib.ui.ui_factory = ui
391
self.assertRaises(LockBroken, ld.unlock)
392
self.assertLength(0, ui.prompts)
394
bzrlib.ui.ui_factory = orig_factory
395
# Suppress warnings due to ld not being unlocked
396
# XXX: if lock_broken hook was invoked in this case, this hack would
397
# not be necessary. - Andrew Bennetts, 2010-09-06.
398
del self._lock_actions[:]
400
def test_create_missing_base_directory(self):
401
"""If LockDir.path doesn't exist, it can be created
403
Some people manually remove the entire lock/ directory trying
404
to unlock a stuck repository/branch/etc. Rather than failing
405
after that, just create the lock directory when needed.
407
t = self.get_transport()
408
lf1 = LockDir(t, 'test_lock')
411
self.assertTrue(t.has('test_lock'))
414
self.assertFalse(t.has('test_lock'))
416
# This will create 'test_lock' if it needs to
418
self.assertTrue(t.has('test_lock'))
419
self.assertTrue(t.has('test_lock/held/info'))
422
self.assertFalse(t.has('test_lock/held/info'))
424
def test_display_form(self):
425
ld1 = self.get_lock()
429
info_list = ld1.peek().to_readable_dict()
432
self.assertEqual(info_list['user'], u'jrandom@example.com')
433
self.assertContainsRe(info_list['pid'], '^\d+$')
434
self.assertContainsRe(info_list['time_ago'], r'^\d+ seconds? ago$')
436
def test_lock_without_email(self):
437
global_config = config.GlobalConfig()
438
# Intentionally has no email address
439
global_config.set_user_option('email', 'User Identity')
440
ld1 = self.get_lock()
445
def test_lock_permission(self):
446
self.requireFeature(features.not_running_as_root)
447
if not osutils.supports_posix_readonly():
448
raise tests.TestSkipped('Cannot induce a permission failure')
449
ld1 = self.get_lock()
450
lock_path = ld1.transport.local_abspath('test_lock')
452
osutils.make_readonly(lock_path)
453
self.assertRaises(errors.LockFailed, ld1.attempt_lock)
455
def test_lock_by_token(self):
456
ld1 = self.get_lock()
457
token = ld1.lock_write()
458
self.addCleanup(ld1.unlock)
459
self.assertNotEqual(None, token)
460
ld2 = self.get_lock()
461
t2 = ld2.lock_write(token)
462
self.addCleanup(ld2.unlock)
463
self.assertEqual(token, t2)
465
def test_lock_with_buggy_rename(self):
466
# test that lock acquisition handles servers which pretend they
467
# renamed correctly but that actually fail
468
t = transport.get_transport('brokenrename+' + self.get_url())
469
ld1 = LockDir(t, 'test_lock')
472
ld2 = LockDir(t, 'test_lock')
473
# we should fail to lock
474
e = self.assertRaises(errors.LockContention, ld2.attempt_lock)
475
# now the original caller should succeed in unlocking
477
# and there should be nothing left over
478
self.assertEquals([], t.list_dir('test_lock'))
480
def test_failed_lock_leaves_no_trash(self):
481
# if we fail to acquire the lock, we don't leave pending directories
482
# behind -- https://bugs.launchpad.net/bzr/+bug/109169
483
ld1 = self.get_lock()
484
ld2 = self.get_lock()
485
# should be nothing before we start
487
t = self.get_transport().clone('test_lock')
490
self.assertEquals(a, t.list_dir('.'))
493
# when held, that's all we see
495
self.addCleanup(ld1.unlock)
497
# second guy should fail
498
self.assertRaises(errors.LockContention, ld2.attempt_lock)
502
def test_no_lockdir_info(self):
503
"""We can cope with empty info files."""
504
# This seems like a fairly common failure case - see
505
# <https://bugs.launchpad.net/bzr/+bug/185103> and all its dupes.
506
# Processes are often interrupted after opening the file
507
# before the actual contents are committed.
508
t = self.get_transport()
510
t.mkdir('test_lock/held')
511
t.put_bytes('test_lock/held/info', '')
512
lf = LockDir(t, 'test_lock')
514
formatted_info = info.to_readable_dict()
516
dict(user='<unknown>', hostname='<unknown>', pid='<unknown>',
517
time_ago='(unknown)'),
520
def test_corrupt_lockdir_info(self):
521
"""We can cope with corrupt (and thus unparseable) info files."""
522
# This seems like a fairly common failure case too - see
523
# <https://bugs.launchpad.net/bzr/+bug/619872> for instance.
524
# In particular some systems tend to fill recently created files with
525
# nul bytes after recovering from a system crash.
526
t = self.get_transport()
528
t.mkdir('test_lock/held')
529
t.put_bytes('test_lock/held/info', '\0')
530
lf = LockDir(t, 'test_lock')
531
self.assertRaises(errors.LockCorrupt, lf.peek)
532
# Currently attempt_lock gives LockContention, but LockCorrupt would be
533
# a reasonable result too.
535
(errors.LockCorrupt, errors.LockContention), lf.attempt_lock)
536
self.assertRaises(errors.LockCorrupt, lf.validate_token, 'fake token')
538
def test_missing_lockdir_info(self):
539
"""We can cope with absent info files."""
540
t = self.get_transport()
542
t.mkdir('test_lock/held')
543
lf = LockDir(t, 'test_lock')
544
# In this case we expect the 'not held' result from peek, because peek
545
# cannot be expected to notice that there is a 'held' directory with no
547
self.assertEqual(None, lf.peek())
548
# And lock/unlock may work or give LockContention (but not any other
552
except LockContention:
553
# LockContention is ok, and expected on Windows
556
# no error is ok, and expected on POSIX (because POSIX allows
557
# os.rename over an empty directory).
559
# Currently raises TokenMismatch, but LockCorrupt would be reasonable
562
(errors.TokenMismatch, errors.LockCorrupt),
563
lf.validate_token, 'fake token')
566
class TestLockDirHooks(TestCaseWithTransport):
569
super(TestLockDirHooks, self).setUp()
573
return LockDir(self.get_transport(), 'test_lock')
575
def record_hook(self, result):
576
self._calls.append(result)
578
def test_LockDir_acquired_success(self):
579
# the LockDir.lock_acquired hook fires when a lock is acquired.
580
LockDir.hooks.install_named_hook('lock_acquired',
581
self.record_hook, 'record_hook')
584
self.assertEqual([], self._calls)
585
result = ld.attempt_lock()
586
lock_path = ld.transport.abspath(ld.path)
587
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
589
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
591
def test_LockDir_acquired_fail(self):
592
# the LockDir.lock_acquired hook does not fire on failure.
595
ld2 = self.get_lock()
597
# install a lock hook now, when the disk lock is locked
598
LockDir.hooks.install_named_hook('lock_acquired',
599
self.record_hook, 'record_hook')
600
self.assertRaises(errors.LockContention, ld.attempt_lock)
601
self.assertEqual([], self._calls)
603
self.assertEqual([], self._calls)
605
def test_LockDir_released_success(self):
606
# the LockDir.lock_released hook fires when a lock is acquired.
607
LockDir.hooks.install_named_hook('lock_released',
608
self.record_hook, 'record_hook')
611
self.assertEqual([], self._calls)
612
result = ld.attempt_lock()
613
self.assertEqual([], self._calls)
615
lock_path = ld.transport.abspath(ld.path)
616
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
618
def test_LockDir_released_fail(self):
619
# the LockDir.lock_released hook does not fire on failure.
622
ld2 = self.get_lock()
624
ld2.force_break(ld2.peek())
625
LockDir.hooks.install_named_hook('lock_released',
626
self.record_hook, 'record_hook')
627
self.assertRaises(LockBroken, ld.unlock)
628
self.assertEqual([], self._calls)
630
def test_LockDir_broken_success(self):
631
# the LockDir.lock_broken hook fires when a lock is broken.
634
ld2 = self.get_lock()
635
result = ld.attempt_lock()
636
LockDir.hooks.install_named_hook('lock_broken',
637
self.record_hook, 'record_hook')
638
ld2.force_break(ld2.peek())
639
lock_path = ld.transport.abspath(ld.path)
640
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
642
def test_LockDir_broken_failure(self):
643
# the LockDir.lock_broken hook does not fires when a lock is already
647
ld2 = self.get_lock()
648
result = ld.attempt_lock()
649
holder_info = ld2.peek()
651
LockDir.hooks.install_named_hook('lock_broken',
652
self.record_hook, 'record_hook')
653
ld2.force_break(holder_info)
654
lock_path = ld.transport.abspath(ld.path)
655
self.assertEqual([], self._calls)
658
class TestLockHeldInfo(TestCase):
659
"""Can get information about the lock holder, and detect whether they're
663
info = LockHeldInfo.for_this_process(None)
664
self.assertContainsRe(repr(info), r"LockHeldInfo\(.*\)")
666
def test_unicode(self):
667
info = LockHeldInfo.for_this_process(None)
668
self.assertContainsRe(unicode(info),
669
r'held by .* on .* \(process #\d+\), acquired .* ago')
671
def test_is_locked_by_this_process(self):
672
info = LockHeldInfo.for_this_process(None)
673
self.assertTrue(info.is_locked_by_this_process())
675
def test_is_not_locked_by_this_process(self):
676
info = LockHeldInfo.for_this_process(None)
677
info.info_dict['pid'] = '123123123123123'
678
self.assertFalse(info.is_locked_by_this_process())
680
def test_lock_holder_live_process(self):
681
"""Detect that the holder (this process) is still running."""
682
info = LockHeldInfo.for_this_process(None)
683
self.assertFalse(info.is_lock_holder_known_dead())
685
def test_lock_holder_dead_process(self):
686
"""Detect that the holder (this process) is still running."""
687
info = LockHeldInfo.for_this_process(None)
688
info.info_dict['pid'] = '123123123'
689
if sys.platform == 'win32':
691
'live lock holder detection not implemented yet on win32')
692
self.assertTrue(info.is_lock_holder_known_dead())
694
def test_lock_holder_other_machine(self):
695
"""The lock holder isn't here so we don't know if they're alive."""
696
info = LockHeldInfo.for_this_process(None)
697
info.info_dict['hostname'] = 'egg.example.com'
698
info.info_dict['pid'] = '123123123'
699
self.assertFalse(info.is_lock_holder_known_dead())
701
def test_lock_holder_other_user(self):
702
"""Only auto-break locks held by this user."""
703
info = LockHeldInfo.for_this_process(None)
704
info.info_dict['user'] = 'notme@example.com'
705
info.info_dict['pid'] = '123123123'
706
self.assertFalse(info.is_lock_holder_known_dead())
708
def test_no_good_hostname(self):
709
"""Correctly handle ambiguous hostnames.
711
If the lock's recorded with just 'localhost' we can't really trust
712
it's the same 'localhost'. (There are quite a few of them. :-)
713
So even if the process is known not to be alive, we can't say that's
716
self.overrideAttr(lockdir, 'get_host_name',
718
info = LockHeldInfo.for_this_process(None)
719
info.info_dict['pid'] = '123123123'
720
self.assertFalse(info.is_lock_holder_known_dead())
723
class TestStaleLockDir(TestCaseWithTransport):
724
"""Can automatically break stale locks.
726
:see: https://bugs.launchpad.net/bzr/+bug/220464
729
def test_auto_break_stale_lock(self):
730
"""Locks safely known to be stale are just cleaned up.
732
This generates a warning but no other user interaction.
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