324
318
self.assertTrue(t.has('test_lock/held/info'))
326
320
self.assertFalse(t.has('test_lock/held/info'))
328
def test_break_lock(self):
329
# the ui based break_lock routine should Just Work (tm)
330
ld1 = self.get_lock()
331
ld2 = self.get_lock()
334
# do this without IO redirection to ensure it doesn't prompt.
335
self.assertRaises(AssertionError, ld1.break_lock)
336
orig_factory = breezy.ui.ui_factory
337
breezy.ui.ui_factory = breezy.ui.CannedInputUIFactory([True])
340
self.assertRaises(LockBroken, ld1.unlock)
342
breezy.ui.ui_factory = orig_factory
344
def test_break_lock_corrupt_info(self):
345
"""break_lock works even if the info file is corrupt (and tells the UI
349
ld2 = self.get_lock()
352
ld.transport.put_bytes_non_atomic('test_lock/held/info', b'\0')
354
class LoggingUIFactory(breezy.ui.SilentUIFactory):
358
def get_boolean(self, prompt):
359
self.prompts.append(('boolean', prompt))
362
ui = LoggingUIFactory()
363
self.overrideAttr(breezy.ui, 'ui_factory', ui)
365
self.assertLength(1, ui.prompts)
366
self.assertEqual('boolean', ui.prompts[0][0])
367
self.assertStartsWith(ui.prompts[0][1], 'Break (corrupt LockDir')
368
self.assertRaises(LockBroken, ld.unlock)
370
def test_break_lock_missing_info(self):
371
"""break_lock works even if the info file is missing (and tells the UI
375
ld2 = self.get_lock()
378
ld.transport.delete('test_lock/held/info')
380
class LoggingUIFactory(breezy.ui.SilentUIFactory):
384
def get_boolean(self, prompt):
385
self.prompts.append(('boolean', prompt))
388
ui = LoggingUIFactory()
389
orig_factory = breezy.ui.ui_factory
390
breezy.ui.ui_factory = ui
393
self.assertRaises(LockBroken, ld.unlock)
394
self.assertLength(0, ui.prompts)
396
breezy.ui.ui_factory = orig_factory
397
# Suppress warnings due to ld not being unlocked
398
# XXX: if lock_broken hook was invoked in this case, this hack would
399
# not be necessary. - Andrew Bennetts, 2010-09-06.
400
del self._lock_actions[:]
402
def test_create_missing_base_directory(self):
403
"""If LockDir.path doesn't exist, it can be created
405
Some people manually remove the entire lock/ directory trying
406
to unlock a stuck repository/branch/etc. Rather than failing
407
after that, just create the lock directory when needed.
409
t = self.get_transport()
410
lf1 = LockDir(t, 'test_lock')
413
self.assertTrue(t.has('test_lock'))
416
self.assertFalse(t.has('test_lock'))
418
# This will create 'test_lock' if it needs to
420
self.assertTrue(t.has('test_lock'))
421
self.assertTrue(t.has('test_lock/held/info'))
424
self.assertFalse(t.has('test_lock/held/info'))
426
def test_display_form(self):
427
ld1 = self.get_lock()
431
info_list = ld1.peek().to_readable_dict()
434
self.assertEqual(info_list['user'], u'jrandom@example.com')
435
self.assertContainsRe(info_list['pid'], '^\\d+$')
436
self.assertContainsRe(info_list['time_ago'], '^\\d+ seconds? ago$')
438
def test_lock_without_email(self):
439
global_config = config.GlobalStack()
440
# Intentionally has no email address
441
global_config.set('email', 'User Identity')
442
ld1 = self.get_lock()
447
def test_lock_permission(self):
448
self.requireFeature(features.not_running_as_root)
449
if not osutils.supports_posix_readonly():
450
raise tests.TestSkipped('Cannot induce a permission failure')
451
ld1 = self.get_lock()
452
lock_path = ld1.transport.local_abspath('test_lock')
454
osutils.make_readonly(lock_path)
455
self.assertRaises(errors.LockFailed, ld1.attempt_lock)
457
def test_lock_by_token(self):
458
ld1 = self.get_lock()
459
token = ld1.lock_write()
460
self.addCleanup(ld1.unlock)
461
self.assertNotEqual(None, token)
462
ld2 = self.get_lock()
463
t2 = ld2.lock_write(token)
464
self.addCleanup(ld2.unlock)
465
self.assertEqual(token, t2)
467
def test_lock_with_buggy_rename(self):
468
# test that lock acquisition handles servers which pretend they
469
# renamed correctly but that actually fail
470
t = transport.get_transport_from_url(
471
'brokenrename+' + self.get_url())
472
ld1 = LockDir(t, 'test_lock')
475
ld2 = LockDir(t, 'test_lock')
476
# we should fail to lock
477
e = self.assertRaises(errors.LockContention, ld2.attempt_lock)
478
# now the original caller should succeed in unlocking
480
# and there should be nothing left over
481
self.assertEqual([], t.list_dir('test_lock'))
483
def test_failed_lock_leaves_no_trash(self):
484
# if we fail to acquire the lock, we don't leave pending directories
485
# behind -- https://bugs.launchpad.net/bzr/+bug/109169
486
ld1 = self.get_lock()
487
ld2 = self.get_lock()
488
# should be nothing before we start
490
t = self.get_transport().clone('test_lock')
493
self.assertEqual(a, t.list_dir('.'))
496
# when held, that's all we see
498
self.addCleanup(ld1.unlock)
500
# second guy should fail
501
self.assertRaises(errors.LockContention, ld2.attempt_lock)
505
def test_no_lockdir_info(self):
506
"""We can cope with empty info files."""
507
# This seems like a fairly common failure case - see
508
# <https://bugs.launchpad.net/bzr/+bug/185103> and all its dupes.
509
# Processes are often interrupted after opening the file
510
# before the actual contents are committed.
511
t = self.get_transport()
513
t.mkdir('test_lock/held')
514
t.put_bytes('test_lock/held/info', b'')
515
lf = LockDir(t, 'test_lock')
517
formatted_info = info.to_readable_dict()
519
dict(user='<unknown>', hostname='<unknown>', pid='<unknown>',
520
time_ago='(unknown)'),
523
def test_corrupt_lockdir_info(self):
524
"""We can cope with corrupt (and thus unparseable) info files."""
525
# This seems like a fairly common failure case too - see
526
# <https://bugs.launchpad.net/bzr/+bug/619872> for instance.
527
# In particular some systems tend to fill recently created files with
528
# nul bytes after recovering from a system crash.
529
t = self.get_transport()
531
t.mkdir('test_lock/held')
532
t.put_bytes('test_lock/held/info', b'\0')
533
lf = LockDir(t, 'test_lock')
534
self.assertRaises(errors.LockCorrupt, lf.peek)
535
# Currently attempt_lock gives LockContention, but LockCorrupt would be
536
# a reasonable result too.
538
(errors.LockCorrupt, errors.LockContention), lf.attempt_lock)
539
self.assertRaises(errors.LockCorrupt, lf.validate_token, 'fake token')
541
def test_missing_lockdir_info(self):
542
"""We can cope with absent info files."""
543
t = self.get_transport()
545
t.mkdir('test_lock/held')
546
lf = LockDir(t, 'test_lock')
547
# In this case we expect the 'not held' result from peek, because peek
548
# cannot be expected to notice that there is a 'held' directory with no
550
self.assertEqual(None, lf.peek())
551
# And lock/unlock may work or give LockContention (but not any other
555
except LockContention:
556
# LockContention is ok, and expected on Windows
559
# no error is ok, and expected on POSIX (because POSIX allows
560
# os.rename over an empty directory).
562
# Currently raises TokenMismatch, but LockCorrupt would be reasonable
565
(errors.TokenMismatch, errors.LockCorrupt),
566
lf.validate_token, 'fake token')
569
class TestLockDirHooks(TestCaseWithTransport):
572
super(TestLockDirHooks, self).setUp()
576
return LockDir(self.get_transport(), 'test_lock')
578
def record_hook(self, result):
579
self._calls.append(result)
581
def test_LockDir_acquired_success(self):
582
# the LockDir.lock_acquired hook fires when a lock is acquired.
583
LockDir.hooks.install_named_hook('lock_acquired',
584
self.record_hook, 'record_hook')
587
self.assertEqual([], self._calls)
588
result = ld.attempt_lock()
589
lock_path = ld.transport.abspath(ld.path)
590
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
592
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
594
def test_LockDir_acquired_fail(self):
595
# the LockDir.lock_acquired hook does not fire on failure.
598
ld2 = self.get_lock()
600
# install a lock hook now, when the disk lock is locked
601
LockDir.hooks.install_named_hook('lock_acquired',
602
self.record_hook, 'record_hook')
603
self.assertRaises(errors.LockContention, ld.attempt_lock)
604
self.assertEqual([], self._calls)
606
self.assertEqual([], self._calls)
608
def test_LockDir_released_success(self):
609
# the LockDir.lock_released hook fires when a lock is acquired.
610
LockDir.hooks.install_named_hook('lock_released',
611
self.record_hook, 'record_hook')
614
self.assertEqual([], self._calls)
615
result = ld.attempt_lock()
616
self.assertEqual([], self._calls)
618
lock_path = ld.transport.abspath(ld.path)
619
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
621
def test_LockDir_released_fail(self):
622
# the LockDir.lock_released hook does not fire on failure.
625
ld2 = self.get_lock()
627
ld2.force_break(ld2.peek())
628
LockDir.hooks.install_named_hook('lock_released',
629
self.record_hook, 'record_hook')
630
self.assertRaises(LockBroken, ld.unlock)
631
self.assertEqual([], self._calls)
633
def test_LockDir_broken_success(self):
634
# the LockDir.lock_broken hook fires when a lock is broken.
637
ld2 = self.get_lock()
638
result = ld.attempt_lock()
639
LockDir.hooks.install_named_hook('lock_broken',
640
self.record_hook, 'record_hook')
641
ld2.force_break(ld2.peek())
642
lock_path = ld.transport.abspath(ld.path)
643
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
645
def test_LockDir_broken_failure(self):
646
# the LockDir.lock_broken hook does not fires when a lock is already
650
ld2 = self.get_lock()
651
result = ld.attempt_lock()
652
holder_info = ld2.peek()
654
LockDir.hooks.install_named_hook('lock_broken',
655
self.record_hook, 'record_hook')
656
ld2.force_break(holder_info)
657
lock_path = ld.transport.abspath(ld.path)
658
self.assertEqual([], self._calls)
661
class TestLockHeldInfo(TestCaseInTempDir):
662
"""Can get information about the lock holder, and detect whether they're
666
info = LockHeldInfo.for_this_process(None)
667
self.assertContainsRe(repr(info), r"LockHeldInfo\(.*\)")
669
def test_unicode(self):
670
info = LockHeldInfo.for_this_process(None)
671
self.assertContainsRe(text_type(info),
672
r'held by .* on .* \(process #\d+\), acquired .* ago')
674
def test_is_locked_by_this_process(self):
675
info = LockHeldInfo.for_this_process(None)
676
self.assertTrue(info.is_locked_by_this_process())
678
def test_is_not_locked_by_this_process(self):
679
info = LockHeldInfo.for_this_process(None)
680
info.info_dict['pid'] = '123123123123123'
681
self.assertFalse(info.is_locked_by_this_process())
683
def test_lock_holder_live_process(self):
684
"""Detect that the holder (this process) is still running."""
685
info = LockHeldInfo.for_this_process(None)
686
self.assertFalse(info.is_lock_holder_known_dead())
688
def test_lock_holder_dead_process(self):
689
"""Detect that the holder (this process) is still running."""
690
self.overrideAttr(lockdir, 'get_host_name',
691
lambda: 'aproperhostname')
692
info = LockHeldInfo.for_this_process(None)
693
info.info_dict['pid'] = '123123123'
694
self.assertTrue(info.is_lock_holder_known_dead())
696
def test_lock_holder_other_machine(self):
697
"""The lock holder isn't here so we don't know if they're alive."""
698
info = LockHeldInfo.for_this_process(None)
699
info.info_dict['hostname'] = 'egg.example.com'
700
info.info_dict['pid'] = '123123123'
701
self.assertFalse(info.is_lock_holder_known_dead())
703
def test_lock_holder_other_user(self):
704
"""Only auto-break locks held by this user."""
705
info = LockHeldInfo.for_this_process(None)
706
info.info_dict['user'] = 'notme@example.com'
707
info.info_dict['pid'] = '123123123'
708
self.assertFalse(info.is_lock_holder_known_dead())
710
def test_no_good_hostname(self):
711
"""Correctly handle ambiguous hostnames.
713
If the lock's recorded with just 'localhost' we can't really trust
714
it's the same 'localhost'. (There are quite a few of them. :-)
715
So even if the process is known not to be alive, we can't say that's
718
self.overrideAttr(lockdir, 'get_host_name',
720
info = LockHeldInfo.for_this_process(None)
721
info.info_dict['pid'] = '123123123'
722
self.assertFalse(info.is_lock_holder_known_dead())
725
class TestStaleLockDir(TestCaseWithTransport):
726
"""Can automatically break stale locks.
728
:see: https://bugs.launchpad.net/bzr/+bug/220464
731
def test_auto_break_stale_lock(self):
732
"""Locks safely known to be stale are just cleaned up.
734
This generates a warning but no other user interaction.
736
self.overrideAttr(lockdir, 'get_host_name',
737
lambda: 'aproperhostname')
738
# Stealing dead locks is enabled by default.
739
# Create a lock pretending to come from a different nonexistent
740
# process on the same machine.
741
l1 = LockDir(self.get_transport(), 'a',
742
extra_holder_info={'pid': '12312313'})
743
token_1 = l1.attempt_lock()
744
l2 = LockDir(self.get_transport(), 'a')
745
token_2 = l2.attempt_lock()
746
# l1 will notice its lock was stolen.
747
self.assertRaises(errors.LockBroken,
751
def test_auto_break_stale_lock_configured_off(self):
752
"""Automatic breaking can be turned off"""
753
l1 = LockDir(self.get_transport(), 'a',
754
extra_holder_info={'pid': '12312313'})
755
# Stealing dead locks is enabled by default, so disable it.
756
config.GlobalStack().set('locks.steal_dead', False)
757
token_1 = l1.attempt_lock()
758
self.addCleanup(l1.unlock)
759
l2 = LockDir(self.get_transport(), 'a')
760
# This fails now, because dead lock breaking is disabled.
761
self.assertRaises(LockContention,
763
# and it's in fact not broken