1
1
# Copyright (C) 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Tests for LockDir"""
19
from threading import Thread
19
from cStringIO import StringIO
20
from threading import Thread, Lock
22
28
from bzrlib.errors import (
24
30
LockContention, LockError, UnlockableTransport,
25
31
LockNotHeld, LockBroken
27
from bzrlib.lockdir import LockDir
33
from bzrlib.lockdir import LockDir, _DEFAULT_TIMEOUT_SECONDS
28
34
from bzrlib.tests import TestCaseWithTransport
35
from bzrlib.trace import note
30
37
# These tests sometimes use threads to test the behaviour of lock files with
31
38
# concurrent actors. This is not a typical (or necessarily supported) use;
143
168
lf1 = LockDir(t, 'test_lock')
145
170
lf2 = LockDir(t, 'test_lock')
171
self.setup_log_reporter(lf2)
146
172
lf1.attempt_lock()
148
174
before = time.time()
149
175
self.assertRaises(LockContention, lf2.wait_lock,
150
176
timeout=0.4, poll=0.1)
151
177
after = time.time()
152
self.assertTrue(after - before <= 1.0)
178
# it should only take about 0.4 seconds, but we allow more time in
179
# case the machine is heavily loaded
180
self.assertTrue(after - before <= 8.0,
181
"took %f seconds to detect lock contention" % (after - before))
184
lock_base = lf2.transport.abspath(lf2.path)
185
self.assertEqual(1, len(self._logged_reports))
186
self.assertEqual('%s %s\n'
188
'Will continue to try until %s\n',
189
self._logged_reports[0][0])
190
args = self._logged_reports[0][1]
191
self.assertEqual('Unable to obtain', args[0])
192
self.assertEqual('lock %s' % (lock_base,), args[1])
193
self.assertStartsWith(args[2], 'held by ')
194
self.assertStartsWith(args[3], 'locked ')
195
self.assertEndsWith(args[3], ' ago')
196
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
156
198
def test_31_lock_wait_easy(self):
157
199
"""Succeed when waiting on a lock with no contention.
284
def test_34_lock_write_waits(self):
285
"""LockDir.lock_write() will wait for the lock."""
286
t = self.get_transport()
287
lf1 = LockDir(t, 'test_lock')
291
def wait_and_unlock():
294
unlocker = Thread(target=wait_and_unlock)
297
lf2 = LockDir(t, 'test_lock')
298
self.setup_log_reporter(lf2)
306
# There should be only 1 report, even though it should have to
308
lock_base = lf2.transport.abspath(lf2.path)
309
self.assertEqual(1, len(self._logged_reports))
310
self.assertEqual('%s %s\n'
312
'Will continue to try until %s\n',
313
self._logged_reports[0][0])
314
args = self._logged_reports[0][1]
315
self.assertEqual('Unable to obtain', args[0])
316
self.assertEqual('lock %s' % (lock_base,), args[1])
317
self.assertStartsWith(args[2], 'held by ')
318
self.assertStartsWith(args[3], 'locked ')
319
self.assertEndsWith(args[3], ' ago')
320
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
322
def test_35_wait_lock_changing(self):
323
"""LockDir.wait_lock() will report if the lock changes underneath.
325
This is the stages we want to happen:
327
0) Synchronization locks are created and locked.
328
1) Lock1 obtains the lockdir, and releases the 'check' lock.
329
2) Lock2 grabs the 'check' lock, and checks the lockdir.
330
It sees the lockdir is already acquired, reports the fact,
331
and unsets the 'checked' lock.
332
3) Thread1 blocks on acquiring the 'checked' lock, and then tells
333
Lock1 to release and acquire the lockdir. This resets the 'check'
335
4) Lock2 acquires the 'check' lock, and checks again. It notices
336
that the holder of the lock has changed, and so reports a new
338
5) Thread1 blocks on the 'checked' lock, this time, it completely
339
unlocks the lockdir, allowing Lock2 to acquire the lock.
342
wait_to_check_lock = Lock()
343
wait_until_checked_lock = Lock()
345
wait_to_check_lock.acquire()
346
wait_until_checked_lock.acquire()
347
note('locked check and checked locks')
349
class LockDir1(LockDir):
350
"""Use the synchronization points for the first lock."""
352
def attempt_lock(self):
353
# Once we have acquired the lock, it is okay for
354
# the other lock to check it
356
return super(LockDir1, self).attempt_lock()
358
note('lock1: releasing check lock')
359
wait_to_check_lock.release()
361
class LockDir2(LockDir):
362
"""Use the synchronization points for the second lock."""
364
def attempt_lock(self):
365
note('lock2: waiting for check lock')
366
wait_to_check_lock.acquire()
367
note('lock2: acquired check lock')
369
return super(LockDir2, self).attempt_lock()
371
note('lock2: releasing checked lock')
372
wait_until_checked_lock.release()
374
t = self.get_transport()
375
lf1 = LockDir1(t, 'test_lock')
378
lf2 = LockDir2(t, 'test_lock')
379
self.setup_log_reporter(lf2)
381
def wait_and_switch():
383
# Block until lock2 has had a chance to check
384
note('lock1: waiting 1 for checked lock')
385
wait_until_checked_lock.acquire()
386
note('lock1: acquired for checked lock')
387
note('lock1: released lockdir')
389
note('lock1: acquiring lockdir')
390
# Create a new nonce, so the lock looks different.
391
lf1.nonce = osutils.rand_chars(20)
393
note('lock1: acquired lockdir')
395
# Block until lock2 has peeked again
396
note('lock1: waiting 2 for checked lock')
397
wait_until_checked_lock.acquire()
398
note('lock1: acquired for checked lock')
399
# Now unlock, and let lock 2 grab the lock
401
wait_to_check_lock.release()
403
unlocker = Thread(target=wait_and_switch)
406
# Wait and play against the other thread
407
lf2.wait_lock(timeout=1.0, poll=0.01)
412
# There should be 2 reports, because the lock changed
413
lock_base = lf2.transport.abspath(lf2.path)
414
self.assertEqual(2, len(self._logged_reports))
416
self.assertEqual('%s %s\n'
418
'Will continue to try until %s\n',
419
self._logged_reports[0][0])
420
args = self._logged_reports[0][1]
421
self.assertEqual('Unable to obtain', args[0])
422
self.assertEqual('lock %s' % (lock_base,), args[1])
423
self.assertStartsWith(args[2], 'held by ')
424
self.assertStartsWith(args[3], 'locked ')
425
self.assertEndsWith(args[3], ' ago')
426
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
428
self.assertEqual('%s %s\n'
430
'Will continue to try until %s\n',
431
self._logged_reports[1][0])
432
args = self._logged_reports[1][1]
433
self.assertEqual('Lock owner changed for', args[0])
434
self.assertEqual('lock %s' % (lock_base,), args[1])
435
self.assertStartsWith(args[2], 'held by ')
436
self.assertStartsWith(args[3], 'locked ')
437
self.assertEndsWith(args[3], ' ago')
438
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
223
440
def test_40_confirm_easy(self):
224
441
"""Confirm a lock that's already held"""
225
442
t = self.get_transport()
318
535
self.assertTrue(t.has('test_lock/held/info'))
320
537
self.assertFalse(t.has('test_lock/held/info'))
539
def test_break_lock(self):
540
# the ui based break_lock routine should Just Work (tm)
541
ld1 = self.get_lock()
542
ld2 = self.get_lock()
545
# do this without IO redirection to ensure it doesn't prompt.
546
self.assertRaises(AssertionError, ld1.break_lock)
547
orig_factory = bzrlib.ui.ui_factory
548
# silent ui - no need for stdout
549
bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
550
bzrlib.ui.ui_factory.stdin = StringIO("y\n")
553
self.assertRaises(LockBroken, ld1.unlock)
555
bzrlib.ui.ui_factory = orig_factory
557
def test_create_missing_base_directory(self):
558
"""If LockDir.path doesn't exist, it can be created
560
Some people manually remove the entire lock/ directory trying
561
to unlock a stuck repository/branch/etc. Rather than failing
562
after that, just create the lock directory when needed.
564
t = self.get_transport()
565
lf1 = LockDir(t, 'test_lock')
568
self.failUnless(t.has('test_lock'))
571
self.failIf(t.has('test_lock'))
573
# This will create 'test_lock' if it needs to
575
self.failUnless(t.has('test_lock'))
576
self.failUnless(t.has('test_lock/held/info'))
579
self.failIf(t.has('test_lock/held/info'))
581
def test__format_lock_info(self):
582
ld1 = self.get_lock()
586
info_list = ld1._format_lock_info(ld1.peek())
589
self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
591
self.assertContainsRe(info_list[1],
592
r'^held by .* on host .* \[process #\d*\]$')
593
self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')
595
def test_lock_without_email(self):
596
global_config = config.GlobalConfig()
597
# Intentionally has no email address
598
global_config.set_user_option('email', 'User Identity')
599
ld1 = self.get_lock()