~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: Martin Pool
  • Date: 2007-06-18 06:54:24 UTC
  • mto: This revision was merged to the branch mainline in revision 2551.
  • Revision ID: mbp@sourcefrog.net-20070618065424-awsn4t4tv2bi4okt
Remove duplicated commit use case documentation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
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
12
12
#
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
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Tests for LockDir"""
18
18
 
 
19
from cStringIO import StringIO
19
20
import os
 
21
from threading import Thread, Lock
20
22
import time
21
23
 
22
24
import bzrlib
23
25
from bzrlib import (
24
26
    config,
25
27
    errors,
26
 
    lock,
27
 
    lockdir,
28
28
    osutils,
29
29
    tests,
30
 
    transport,
31
30
    )
32
31
from bzrlib.errors import (
33
 
    LockBreakMismatch,
34
 
    LockBroken,
35
 
    LockContention,
36
 
    LockFailed,
37
 
    LockNotHeld,
38
 
    )
39
 
from bzrlib.lockdir import (
40
 
    LockDir,
41
 
    LockHeldInfo,
42
 
    )
43
 
from bzrlib.tests import (
44
 
    features,
45
 
    TestCase,
46
 
    TestCaseWithTransport,
47
 
    )
 
32
        LockBreakMismatch,
 
33
        LockContention, LockError, UnlockableTransport,
 
34
        LockNotHeld, LockBroken
 
35
        )
 
36
from bzrlib.lockdir import LockDir
 
37
from bzrlib.tests import TestCaseWithTransport
 
38
from bzrlib.trace import note
 
39
 
 
40
# These tests sometimes use threads to test the behaviour of lock files with
 
41
# concurrent actors.  This is not a typical (or necessarily supported) use;
 
42
# they're really meant for guarding between processes.
48
43
 
49
44
# These tests are run on the default transport provided by the test framework
50
45
# (typically a local disk transport).  That can be changed by the --transport
52
47
# implementation are tested separately.  (The main requirement is just that
53
48
# they don't allow overwriting nonempty directories.)
54
49
 
55
 
 
56
50
class TestLockDir(TestCaseWithTransport):
57
51
    """Test LockDir operations"""
58
52
 
110
104
        """Fail to create lock on readonly transport"""
111
105
        t = self.get_readonly_transport()
112
106
        lf = LockDir(t, 'test_lock')
113
 
        self.assertRaises(LockFailed, lf.create)
 
107
        self.assertRaises(UnlockableTransport, lf.create)
114
108
 
115
109
    def test_12_lock_readonly_transport(self):
116
110
        """Fail to lock on readonly transport"""
117
111
        lf = LockDir(self.get_transport(), 'test_lock')
118
112
        lf.create()
119
113
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
120
 
        self.assertRaises(LockFailed, lf.attempt_lock)
 
114
        self.assertRaises(UnlockableTransport, lf.attempt_lock)
121
115
 
122
116
    def test_20_lock_contested(self):
123
117
        """Contention to get a lock"""
127
121
        lf1.attempt_lock()
128
122
        lf2 = LockDir(t, 'test_lock')
129
123
        try:
130
 
            # locking is between LockDir instances; aliases within
 
124
            # locking is between LockDir instances; aliases within 
131
125
            # a single process are not detected
132
126
            lf2.attempt_lock()
133
127
            self.fail('Failed to detect lock collision')
143
137
        lf1 = LockDir(t, 'test_lock')
144
138
        lf1.create()
145
139
        lf1.attempt_lock()
146
 
        self.addCleanup(lf1.unlock)
147
140
        # lock is held, should get some info on it
148
141
        info1 = lf1.peek()
149
 
        self.assertEqual(set(info1.info_dict.keys()),
150
 
            set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
 
142
        self.assertEqual(set(info1.keys()),
 
143
                         set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
151
144
        # should get the same info if we look at it through a different
152
145
        # instance
153
146
        info2 = LockDir(t, 'test_lock').peek()
163
156
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
164
157
        self.assertEqual(lf2.peek(), None)
165
158
        lf1.attempt_lock()
166
 
        self.addCleanup(lf1.unlock)
167
159
        info2 = lf2.peek()
168
160
        self.assertTrue(info2)
169
 
        self.assertEqual(info2.get('nonce'), lf1.nonce)
 
161
        self.assertEqual(info2['nonce'], lf1.nonce)
170
162
 
171
163
    def test_30_lock_wait_fail(self):
172
164
        """Wait on a lock, then fail
173
 
 
 
165
        
174
166
        We ask to wait up to 400ms; this should fail within at most one
175
167
        second.  (Longer times are more realistic but we don't want the test
176
168
        suite to take too long, and this should do for now.)
188
180
            after = time.time()
189
181
            # it should only take about 0.4 seconds, but we allow more time in
190
182
            # case the machine is heavily loaded
191
 
            self.assertTrue(after - before <= 8.0,
192
 
                "took %f seconds to detect lock contention" % (after - before))
 
183
            self.assertTrue(after - before <= 8.0, 
 
184
                    "took %f seconds to detect lock contention" % (after - before))
193
185
        finally:
194
186
            lf1.unlock()
 
187
        lock_base = lf2.transport.abspath(lf2.path)
195
188
        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.')
 
189
        self.assertEqual('%s %s\n'
 
190
                         '%s\n%s\n'
 
191
                         'Will continue to try until %s\n',
 
192
                         self._logged_reports[0][0])
 
193
        args = self._logged_reports[0][1]
 
194
        self.assertEqual('Unable to obtain', args[0])
 
195
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
196
        self.assertStartsWith(args[2], 'held by ')
 
197
        self.assertStartsWith(args[3], 'locked ')
 
198
        self.assertEndsWith(args[3], ' ago')
 
199
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
202
200
 
203
201
    def test_31_lock_wait_easy(self):
204
202
        """Succeed when waiting on a lock with no contention.
216
214
            lf1.unlock()
217
215
        self.assertEqual([], self._logged_reports)
218
216
 
 
217
    def test_32_lock_wait_succeed(self):
 
218
        """Succeed when trying to acquire a lock that gets released
 
219
 
 
220
        One thread holds on a lock and then releases it; another 
 
221
        tries to lock it.
 
222
        """
 
223
        t = self.get_transport()
 
224
        lf1 = LockDir(t, 'test_lock')
 
225
        lf1.create()
 
226
        lf1.attempt_lock()
 
227
 
 
228
        def wait_and_unlock():
 
229
            time.sleep(0.1)
 
230
            lf1.unlock()
 
231
        unlocker = Thread(target=wait_and_unlock)
 
232
        unlocker.start()
 
233
        try:
 
234
            lf2 = LockDir(t, 'test_lock')
 
235
            self.setup_log_reporter(lf2)
 
236
            before = time.time()
 
237
            # wait and then lock
 
238
            lf2.wait_lock(timeout=0.4, poll=0.1)
 
239
            after = time.time()
 
240
            self.assertTrue(after - before <= 1.0)
 
241
        finally:
 
242
            unlocker.join()
 
243
 
 
244
        # There should be only 1 report, even though it should have to
 
245
        # wait for a while
 
246
        lock_base = lf2.transport.abspath(lf2.path)
 
247
        self.assertEqual(1, len(self._logged_reports))
 
248
        self.assertEqual('%s %s\n'
 
249
                         '%s\n%s\n'
 
250
                         'Will continue to try until %s\n',
 
251
                         self._logged_reports[0][0])
 
252
        args = self._logged_reports[0][1]
 
253
        self.assertEqual('Unable to obtain', args[0])
 
254
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
255
        self.assertStartsWith(args[2], 'held by ')
 
256
        self.assertStartsWith(args[3], 'locked ')
 
257
        self.assertEndsWith(args[3], ' ago')
 
258
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
259
 
 
260
    def test_33_wait(self):
 
261
        """Succeed when waiting on a lock that gets released
 
262
 
 
263
        The difference from test_32_lock_wait_succeed is that the second 
 
264
        caller does not actually acquire the lock, but just waits for it
 
265
        to be released.  This is done over a readonly transport.
 
266
        """
 
267
        t = self.get_transport()
 
268
        lf1 = LockDir(t, 'test_lock')
 
269
        lf1.create()
 
270
        lf1.attempt_lock()
 
271
 
 
272
        def wait_and_unlock():
 
273
            time.sleep(0.1)
 
274
            lf1.unlock()
 
275
        unlocker = Thread(target=wait_and_unlock)
 
276
        unlocker.start()
 
277
        try:
 
278
            lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
 
279
            before = time.time()
 
280
            # wait but don't lock
 
281
            lf2.wait(timeout=0.4, poll=0.1)
 
282
            after = time.time()
 
283
            self.assertTrue(after - before <= 1.0)
 
284
        finally:
 
285
            unlocker.join()
 
286
 
 
287
    def test_34_lock_write_waits(self):
 
288
        """LockDir.lock_write() will wait for the lock.""" 
 
289
        # the test suite sets the default to 0 to make deadlocks fail fast.
 
290
        # change it for this test, as we want to try a manual deadlock.
 
291
        bzrlib.lockdir._DEFAULT_TIMEOUT_SECONDS = 300
 
292
        t = self.get_transport()
 
293
        lf1 = LockDir(t, 'test_lock')
 
294
        lf1.create()
 
295
        lf1.attempt_lock()
 
296
 
 
297
        def wait_and_unlock():
 
298
            time.sleep(0.1)
 
299
            lf1.unlock()
 
300
        unlocker = Thread(target=wait_and_unlock)
 
301
        unlocker.start()
 
302
        try:
 
303
            lf2 = LockDir(t, 'test_lock')
 
304
            self.setup_log_reporter(lf2)
 
305
            before = time.time()
 
306
            # wait and then lock
 
307
            lf2.lock_write()
 
308
            after = time.time()
 
309
        finally:
 
310
            unlocker.join()
 
311
 
 
312
        # There should be only 1 report, even though it should have to
 
313
        # wait for a while
 
314
        lock_base = lf2.transport.abspath(lf2.path)
 
315
        self.assertEqual(1, len(self._logged_reports))
 
316
        self.assertEqual('%s %s\n'
 
317
                         '%s\n%s\n'
 
318
                         'Will continue to try until %s\n',
 
319
                         self._logged_reports[0][0])
 
320
        args = self._logged_reports[0][1]
 
321
        self.assertEqual('Unable to obtain', args[0])
 
322
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
323
        self.assertStartsWith(args[2], 'held by ')
 
324
        self.assertStartsWith(args[3], 'locked ')
 
325
        self.assertEndsWith(args[3], ' ago')
 
326
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
327
 
 
328
    def test_35_wait_lock_changing(self):
 
329
        """LockDir.wait_lock() will report if the lock changes underneath.
 
330
        
 
331
        This is the stages we want to happen:
 
332
 
 
333
        0) Synchronization locks are created and locked.
 
334
        1) Lock1 obtains the lockdir, and releases the 'check' lock.
 
335
        2) Lock2 grabs the 'check' lock, and checks the lockdir.
 
336
           It sees the lockdir is already acquired, reports the fact, 
 
337
           and unsets the 'checked' lock.
 
338
        3) Thread1 blocks on acquiring the 'checked' lock, and then tells
 
339
           Lock1 to release and acquire the lockdir. This resets the 'check'
 
340
           lock.
 
341
        4) Lock2 acquires the 'check' lock, and checks again. It notices
 
342
           that the holder of the lock has changed, and so reports a new 
 
343
           lock holder.
 
344
        5) Thread1 blocks on the 'checked' lock, this time, it completely
 
345
           unlocks the lockdir, allowing Lock2 to acquire the lock.
 
346
        """
 
347
 
 
348
        wait_to_check_lock = Lock()
 
349
        wait_until_checked_lock = Lock()
 
350
 
 
351
        wait_to_check_lock.acquire()
 
352
        wait_until_checked_lock.acquire()
 
353
        note('locked check and checked locks')
 
354
 
 
355
        class LockDir1(LockDir):
 
356
            """Use the synchronization points for the first lock."""
 
357
 
 
358
            def attempt_lock(self):
 
359
                # Once we have acquired the lock, it is okay for
 
360
                # the other lock to check it
 
361
                try:
 
362
                    return super(LockDir1, self).attempt_lock()
 
363
                finally:
 
364
                    note('lock1: releasing check lock')
 
365
                    wait_to_check_lock.release()
 
366
 
 
367
        class LockDir2(LockDir):
 
368
            """Use the synchronization points for the second lock."""
 
369
 
 
370
            def attempt_lock(self):
 
371
                note('lock2: waiting for check lock')
 
372
                wait_to_check_lock.acquire()
 
373
                note('lock2: acquired check lock')
 
374
                try:
 
375
                    return super(LockDir2, self).attempt_lock()
 
376
                finally:
 
377
                    note('lock2: releasing checked lock')
 
378
                    wait_until_checked_lock.release()
 
379
 
 
380
        t = self.get_transport()
 
381
        lf1 = LockDir1(t, 'test_lock')
 
382
        lf1.create()
 
383
 
 
384
        lf2 = LockDir2(t, 'test_lock')
 
385
        self.setup_log_reporter(lf2)
 
386
 
 
387
        def wait_and_switch():
 
388
            lf1.attempt_lock()
 
389
            # Block until lock2 has had a chance to check
 
390
            note('lock1: waiting 1 for checked lock')
 
391
            wait_until_checked_lock.acquire()
 
392
            note('lock1: acquired for checked lock')
 
393
            note('lock1: released lockdir')
 
394
            lf1.unlock()
 
395
            note('lock1: acquiring lockdir')
 
396
            # Create a new nonce, so the lock looks different.
 
397
            lf1.nonce = osutils.rand_chars(20)
 
398
            lf1.lock_write()
 
399
            note('lock1: acquired lockdir')
 
400
 
 
401
            # Block until lock2 has peeked again
 
402
            note('lock1: waiting 2 for checked lock')
 
403
            wait_until_checked_lock.acquire()
 
404
            note('lock1: acquired for checked lock')
 
405
            # Now unlock, and let lock 2 grab the lock
 
406
            lf1.unlock()
 
407
            wait_to_check_lock.release()
 
408
 
 
409
        unlocker = Thread(target=wait_and_switch)
 
410
        unlocker.start()
 
411
        try:
 
412
            # Wait and play against the other thread
 
413
            lf2.wait_lock(timeout=1.0, poll=0.01)
 
414
        finally:
 
415
            unlocker.join()
 
416
        lf2.unlock()
 
417
 
 
418
        # There should be 2 reports, because the lock changed
 
419
        lock_base = lf2.transport.abspath(lf2.path)
 
420
        self.assertEqual(2, len(self._logged_reports))
 
421
 
 
422
        self.assertEqual('%s %s\n'
 
423
                         '%s\n%s\n'
 
424
                         'Will continue to try until %s\n',
 
425
                         self._logged_reports[0][0])
 
426
        args = self._logged_reports[0][1]
 
427
        self.assertEqual('Unable to obtain', args[0])
 
428
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
429
        self.assertStartsWith(args[2], 'held by ')
 
430
        self.assertStartsWith(args[3], 'locked ')
 
431
        self.assertEndsWith(args[3], ' ago')
 
432
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
433
 
 
434
        self.assertEqual('%s %s\n'
 
435
                         '%s\n%s\n'
 
436
                         'Will continue to try until %s\n',
 
437
                         self._logged_reports[1][0])
 
438
        args = self._logged_reports[1][1]
 
439
        self.assertEqual('Lock owner changed for', args[0])
 
440
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
441
        self.assertStartsWith(args[2], 'held by ')
 
442
        self.assertStartsWith(args[3], 'locked ')
 
443
        self.assertEndsWith(args[3], ' ago')
 
444
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
445
 
219
446
    def test_40_confirm_easy(self):
220
447
        """Confirm a lock that's already held"""
221
448
        t = self.get_transport()
222
449
        lf1 = LockDir(t, 'test_lock')
223
450
        lf1.create()
224
451
        lf1.attempt_lock()
225
 
        self.addCleanup(lf1.unlock)
226
452
        lf1.confirm()
227
453
 
228
454
    def test_41_confirm_not_held(self):
240
466
        lf1.attempt_lock()
241
467
        t.move('test_lock', 'lock_gone_now')
242
468
        self.assertRaises(LockBroken, lf1.confirm)
243
 
        # Clean up
244
 
        t.move('lock_gone_now', 'test_lock')
245
 
        lf1.unlock()
246
469
 
247
470
    def test_43_break(self):
248
471
        """Break a lock whose caller has forgotten it"""
259
482
        lf2.force_break(holder_info)
260
483
        # now we should be able to take it
261
484
        lf2.attempt_lock()
262
 
        self.addCleanup(lf2.unlock)
263
485
        lf2.confirm()
264
486
 
265
487
    def test_44_break_already_released(self):
277
499
        lf2.force_break(holder_info)
278
500
        # now we should be able to take it
279
501
        lf2.attempt_lock()
280
 
        self.addCleanup(lf2.unlock)
281
502
        lf2.confirm()
282
503
 
283
504
    def test_45_break_mismatch(self):
309
530
        """Check the on-disk representation of LockDirs is as expected.
310
531
 
311
532
        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
 
533
        When the lock is held, there should be a lockname/held directory 
313
534
        containing an info file.
314
535
        """
315
536
        t = self.get_transport()
330
551
        # do this without IO redirection to ensure it doesn't prompt.
331
552
        self.assertRaises(AssertionError, ld1.break_lock)
332
553
        orig_factory = bzrlib.ui.ui_factory
333
 
        bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
 
554
        # silent ui - no need for stdout
 
555
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
 
556
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
334
557
        try:
335
558
            ld2.break_lock()
336
559
            self.assertRaises(LockBroken, ld1.unlock)
337
560
        finally:
338
561
            bzrlib.ui.ui_factory = orig_factory
339
562
 
340
 
    def test_break_lock_corrupt_info(self):
341
 
        """break_lock works even if the info file is corrupt (and tells the UI
342
 
        that it is corrupt).
343
 
        """
344
 
        ld = self.get_lock()
345
 
        ld2 = self.get_lock()
346
 
        ld.create()
347
 
        ld.lock_write()
348
 
        ld.transport.put_bytes_non_atomic('test_lock/held/info', '\0')
349
 
 
350
 
        class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
351
 
            def __init__(self):
352
 
                self.prompts = []
353
 
 
354
 
            def get_boolean(self, prompt):
355
 
                self.prompts.append(('boolean', prompt))
356
 
                return True
357
 
 
358
 
        ui = LoggingUIFactory()
359
 
        self.overrideAttr(bzrlib.ui, 'ui_factory', ui)
360
 
        ld2.break_lock()
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)
365
 
 
366
 
    def test_break_lock_missing_info(self):
367
 
        """break_lock works even if the info file is missing (and tells the UI
368
 
        that it is corrupt).
369
 
        """
370
 
        ld = self.get_lock()
371
 
        ld2 = self.get_lock()
372
 
        ld.create()
373
 
        ld.lock_write()
374
 
        ld.transport.delete('test_lock/held/info')
375
 
 
376
 
        class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
377
 
            def __init__(self):
378
 
                self.prompts = []
379
 
 
380
 
            def get_boolean(self, prompt):
381
 
                self.prompts.append(('boolean', prompt))
382
 
                return True
383
 
 
384
 
        ui = LoggingUIFactory()
385
 
        orig_factory = bzrlib.ui.ui_factory
386
 
        bzrlib.ui.ui_factory = ui
387
 
        try:
388
 
            ld2.break_lock()
389
 
            self.assertRaises(LockBroken, ld.unlock)
390
 
            self.assertLength(0, ui.prompts)
391
 
        finally:
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[:]
397
 
 
398
563
    def test_create_missing_base_directory(self):
399
564
        """If LockDir.path doesn't exist, it can be created
400
565
 
406
571
        lf1 = LockDir(t, 'test_lock')
407
572
 
408
573
        lf1.create()
409
 
        self.assertTrue(t.has('test_lock'))
 
574
        self.failUnless(t.has('test_lock'))
410
575
 
411
576
        t.rmdir('test_lock')
412
 
        self.assertFalse(t.has('test_lock'))
 
577
        self.failIf(t.has('test_lock'))
413
578
 
414
579
        # This will create 'test_lock' if it needs to
415
580
        lf1.lock_write()
416
 
        self.assertTrue(t.has('test_lock'))
417
 
        self.assertTrue(t.has('test_lock/held/info'))
 
581
        self.failUnless(t.has('test_lock'))
 
582
        self.failUnless(t.has('test_lock/held/info'))
418
583
 
419
584
        lf1.unlock()
420
 
        self.assertFalse(t.has('test_lock/held/info'))
 
585
        self.failIf(t.has('test_lock/held/info'))
421
586
 
422
 
    def test_display_form(self):
 
587
    def test__format_lock_info(self):
423
588
        ld1 = self.get_lock()
424
589
        ld1.create()
425
590
        ld1.lock_write()
426
591
        try:
427
 
            info_list = ld1.peek().to_readable_dict()
 
592
            info_list = ld1._format_lock_info(ld1.peek())
428
593
        finally:
429
594
            ld1.unlock()
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$')
 
595
        self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
 
596
                         info_list[0])
 
597
        self.assertContainsRe(info_list[1],
 
598
                              r'^held by .* on host .* \[process #\d*\]$')
 
599
        self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')
433
600
 
434
601
    def test_lock_without_email(self):
435
602
        global_config = config.GlobalConfig()
441
608
        ld1.unlock()
442
609
 
443
610
    def test_lock_permission(self):
444
 
        self.requireFeature(features.not_running_as_root)
445
611
        if not osutils.supports_posix_readonly():
446
612
            raise tests.TestSkipped('Cannot induce a permission failure')
447
613
        ld1 = self.get_lock()
448
614
        lock_path = ld1.transport.local_abspath('test_lock')
449
615
        os.mkdir(lock_path)
450
616
        osutils.make_readonly(lock_path)
451
 
        self.assertRaises(errors.LockFailed, ld1.attempt_lock)
452
 
 
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)
462
 
 
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('brokenrename+' + self.get_url())
467
 
        ld1 = LockDir(t, 'test_lock')
468
 
        ld1.create()
469
 
        ld1.attempt_lock()
470
 
        ld2 = LockDir(t, 'test_lock')
471
 
        # we should fail to lock
472
 
        e = self.assertRaises(errors.LockContention, ld2.attempt_lock)
473
 
        # now the original caller should succeed in unlocking
474
 
        ld1.unlock()
475
 
        # and there should be nothing left over
476
 
        self.assertEquals([], t.list_dir('test_lock'))
477
 
 
478
 
    def test_failed_lock_leaves_no_trash(self):
479
 
        # if we fail to acquire the lock, we don't leave pending directories
480
 
        # behind -- https://bugs.launchpad.net/bzr/+bug/109169
481
 
        ld1 = self.get_lock()
482
 
        ld2 = self.get_lock()
483
 
        # should be nothing before we start
484
 
        ld1.create()
485
 
        t = self.get_transport().clone('test_lock')
486
 
 
487
 
        def check_dir(a):
488
 
            self.assertEquals(a, t.list_dir('.'))
489
 
 
490
 
        check_dir([])
491
 
        # when held, that's all we see
492
 
        ld1.attempt_lock()
493
 
        self.addCleanup(ld1.unlock)
494
 
        check_dir(['held'])
495
 
        # second guy should fail
496
 
        self.assertRaises(errors.LockContention, ld2.attempt_lock)
497
 
        # no kibble
498
 
        check_dir(['held'])
499
 
 
500
 
    def test_no_lockdir_info(self):
501
 
        """We can cope with empty info files."""
502
 
        # This seems like a fairly common failure case - see
503
 
        # <https://bugs.launchpad.net/bzr/+bug/185103> and all its dupes.
504
 
        # Processes are often interrupted after opening the file
505
 
        # before the actual contents are committed.
506
 
        t = self.get_transport()
507
 
        t.mkdir('test_lock')
508
 
        t.mkdir('test_lock/held')
509
 
        t.put_bytes('test_lock/held/info', '')
510
 
        lf = LockDir(t, 'test_lock')
511
 
        info = lf.peek()
512
 
        formatted_info = info.to_readable_dict()
513
 
        self.assertEquals(
514
 
            dict(user='<unknown>', hostname='<unknown>', pid='<unknown>',
515
 
                time_ago='(unknown)'),
516
 
            formatted_info)
517
 
 
518
 
    def test_corrupt_lockdir_info(self):
519
 
        """We can cope with corrupt (and thus unparseable) info files."""
520
 
        # This seems like a fairly common failure case too - see
521
 
        # <https://bugs.launchpad.net/bzr/+bug/619872> for instance.
522
 
        # In particular some systems tend to fill recently created files with
523
 
        # nul bytes after recovering from a system crash.
524
 
        t = self.get_transport()
525
 
        t.mkdir('test_lock')
526
 
        t.mkdir('test_lock/held')
527
 
        t.put_bytes('test_lock/held/info', '\0')
528
 
        lf = LockDir(t, 'test_lock')
529
 
        self.assertRaises(errors.LockCorrupt, lf.peek)
530
 
        # Currently attempt_lock gives LockContention, but LockCorrupt would be
531
 
        # a reasonable result too.
532
 
        self.assertRaises(
533
 
            (errors.LockCorrupt, errors.LockContention), lf.attempt_lock)
534
 
        self.assertRaises(errors.LockCorrupt, lf.validate_token, 'fake token')
535
 
 
536
 
    def test_missing_lockdir_info(self):
537
 
        """We can cope with absent info files."""
538
 
        t = self.get_transport()
539
 
        t.mkdir('test_lock')
540
 
        t.mkdir('test_lock/held')
541
 
        lf = LockDir(t, 'test_lock')
542
 
        # In this case we expect the 'not held' result from peek, because peek
543
 
        # cannot be expected to notice that there is a 'held' directory with no
544
 
        # 'info' file.
545
 
        self.assertEqual(None, lf.peek())
546
 
        # And lock/unlock may work or give LockContention (but not any other
547
 
        # error).
548
 
        try:
549
 
            lf.attempt_lock()
550
 
        except LockContention:
551
 
            # LockContention is ok, and expected on Windows
552
 
            pass
553
 
        else:
554
 
            # no error is ok, and expected on POSIX (because POSIX allows
555
 
            # os.rename over an empty directory).
556
 
            lf.unlock()
557
 
        # Currently raises TokenMismatch, but LockCorrupt would be reasonable
558
 
        # too.
559
 
        self.assertRaises(
560
 
            (errors.TokenMismatch, errors.LockCorrupt),
561
 
            lf.validate_token, 'fake token')
562
 
 
563
 
 
564
 
class TestLockDirHooks(TestCaseWithTransport):
565
 
 
566
 
    def setUp(self):
567
 
        super(TestLockDirHooks, self).setUp()
568
 
        self._calls = []
569
 
 
570
 
    def get_lock(self):
571
 
        return LockDir(self.get_transport(), 'test_lock')
572
 
 
573
 
    def record_hook(self, result):
574
 
        self._calls.append(result)
575
 
 
576
 
    def test_LockDir_acquired_success(self):
577
 
        # the LockDir.lock_acquired hook fires when a lock is acquired.
578
 
        LockDir.hooks.install_named_hook('lock_acquired',
579
 
                                         self.record_hook, 'record_hook')
580
 
        ld = self.get_lock()
581
 
        ld.create()
582
 
        self.assertEqual([], self._calls)
583
 
        result = ld.attempt_lock()
584
 
        lock_path = ld.transport.abspath(ld.path)
585
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
586
 
        ld.unlock()
587
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
588
 
 
589
 
    def test_LockDir_acquired_fail(self):
590
 
        # the LockDir.lock_acquired hook does not fire on failure.
591
 
        ld = self.get_lock()
592
 
        ld.create()
593
 
        ld2 = self.get_lock()
594
 
        ld2.attempt_lock()
595
 
        # install a lock hook now, when the disk lock is locked
596
 
        LockDir.hooks.install_named_hook('lock_acquired',
597
 
                                         self.record_hook, 'record_hook')
598
 
        self.assertRaises(errors.LockContention, ld.attempt_lock)
599
 
        self.assertEqual([], self._calls)
600
 
        ld2.unlock()
601
 
        self.assertEqual([], self._calls)
602
 
 
603
 
    def test_LockDir_released_success(self):
604
 
        # the LockDir.lock_released hook fires when a lock is acquired.
605
 
        LockDir.hooks.install_named_hook('lock_released',
606
 
                                         self.record_hook, 'record_hook')
607
 
        ld = self.get_lock()
608
 
        ld.create()
609
 
        self.assertEqual([], self._calls)
610
 
        result = ld.attempt_lock()
611
 
        self.assertEqual([], self._calls)
612
 
        ld.unlock()
613
 
        lock_path = ld.transport.abspath(ld.path)
614
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
615
 
 
616
 
    def test_LockDir_released_fail(self):
617
 
        # the LockDir.lock_released hook does not fire on failure.
618
 
        ld = self.get_lock()
619
 
        ld.create()
620
 
        ld2 = self.get_lock()
621
 
        ld.attempt_lock()
622
 
        ld2.force_break(ld2.peek())
623
 
        LockDir.hooks.install_named_hook('lock_released',
624
 
                                         self.record_hook, 'record_hook')
625
 
        self.assertRaises(LockBroken, ld.unlock)
626
 
        self.assertEqual([], self._calls)
627
 
 
628
 
    def test_LockDir_broken_success(self):
629
 
        # the LockDir.lock_broken hook fires when a lock is broken.
630
 
        ld = self.get_lock()
631
 
        ld.create()
632
 
        ld2 = self.get_lock()
633
 
        result = ld.attempt_lock()
634
 
        LockDir.hooks.install_named_hook('lock_broken',
635
 
                                         self.record_hook, 'record_hook')
636
 
        ld2.force_break(ld2.peek())
637
 
        lock_path = ld.transport.abspath(ld.path)
638
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
639
 
 
640
 
    def test_LockDir_broken_failure(self):
641
 
        # the LockDir.lock_broken hook does not fires when a lock is already
642
 
        # released.
643
 
        ld = self.get_lock()
644
 
        ld.create()
645
 
        ld2 = self.get_lock()
646
 
        result = ld.attempt_lock()
647
 
        holder_info = ld2.peek()
648
 
        ld.unlock()
649
 
        LockDir.hooks.install_named_hook('lock_broken',
650
 
                                         self.record_hook, 'record_hook')
651
 
        ld2.force_break(holder_info)
652
 
        lock_path = ld.transport.abspath(ld.path)
653
 
        self.assertEqual([], self._calls)
654
 
 
655
 
 
656
 
class TestLockHeldInfo(TestCase):
657
 
    """Can get information about the lock holder, and detect whether they're
658
 
    still alive."""
659
 
 
660
 
    def test_repr(self):
661
 
        info = LockHeldInfo.for_this_process(None)
662
 
        self.assertContainsRe(repr(info), r"LockHeldInfo\(.*\)")
663
 
 
664
 
    def test_unicode(self):
665
 
        info = LockHeldInfo.for_this_process(None)
666
 
        self.assertContainsRe(unicode(info),
667
 
            r'held by .* on .* \(process #\d+\), acquired .* ago')
668
 
 
669
 
    def test_is_locked_by_this_process(self):
670
 
        info = LockHeldInfo.for_this_process(None)
671
 
        self.assertTrue(info.is_locked_by_this_process())
672
 
 
673
 
    def test_is_not_locked_by_this_process(self):
674
 
        info = LockHeldInfo.for_this_process(None)
675
 
        info.info_dict['pid'] = '123123123123123'
676
 
        self.assertFalse(info.is_locked_by_this_process())
677
 
 
678
 
    def test_lock_holder_live_process(self):
679
 
        """Detect that the holder (this process) is still running."""
680
 
        info = LockHeldInfo.for_this_process(None)
681
 
        self.assertFalse(info.is_lock_holder_known_dead())
682
 
 
683
 
    def test_lock_holder_dead_process(self):
684
 
        """Detect that the holder (this process) is still running."""
685
 
        self.overrideAttr(lockdir, 'get_host_name',
686
 
            lambda: 'aproperhostname')
687
 
        info = LockHeldInfo.for_this_process(None)
688
 
        info.info_dict['pid'] = '123123123'
689
 
        self.assertTrue(info.is_lock_holder_known_dead())
690
 
 
691
 
    def test_lock_holder_other_machine(self):
692
 
        """The lock holder isn't here so we don't know if they're alive."""
693
 
        info = LockHeldInfo.for_this_process(None)
694
 
        info.info_dict['hostname'] = 'egg.example.com'
695
 
        info.info_dict['pid'] = '123123123'
696
 
        self.assertFalse(info.is_lock_holder_known_dead())
697
 
 
698
 
    def test_lock_holder_other_user(self):
699
 
        """Only auto-break locks held by this user."""
700
 
        info = LockHeldInfo.for_this_process(None)
701
 
        info.info_dict['user'] = 'notme@example.com'
702
 
        info.info_dict['pid'] = '123123123'
703
 
        self.assertFalse(info.is_lock_holder_known_dead())
704
 
 
705
 
    def test_no_good_hostname(self):
706
 
        """Correctly handle ambiguous hostnames.
707
 
 
708
 
        If the lock's recorded with just 'localhost' we can't really trust
709
 
        it's the same 'localhost'.  (There are quite a few of them. :-)
710
 
        So even if the process is known not to be alive, we can't say that's
711
 
        known for sure.
712
 
        """
713
 
        self.overrideAttr(lockdir, 'get_host_name',
714
 
            lambda: 'localhost')
715
 
        info = LockHeldInfo.for_this_process(None)
716
 
        info.info_dict['pid'] = '123123123'
717
 
        self.assertFalse(info.is_lock_holder_known_dead())
718
 
 
719
 
 
720
 
class TestStaleLockDir(TestCaseWithTransport):
721
 
    """Can automatically break stale locks.
722
 
 
723
 
    :see: https://bugs.launchpad.net/bzr/+bug/220464
724
 
    """
725
 
 
726
 
    def test_auto_break_stale_lock(self):
727
 
        """Locks safely known to be stale are just cleaned up.
728
 
 
729
 
        This generates a warning but no other user interaction.
730
 
        """
731
 
        self.overrideAttr(lockdir, 'get_host_name',
732
 
            lambda: 'aproperhostname')
733
 
        # This is off by default at present; see the discussion in the bug.
734
 
        # If you change the default, don't forget to update the docs.
735
 
        config.GlobalConfig().set_user_option('locks.steal_dead', True)
736
 
        # Create a lock pretending to come from a different nonexistent
737
 
        # process on the same machine.
738
 
        l1 = LockDir(self.get_transport(), 'a',
739
 
            extra_holder_info={'pid': '12312313'})
740
 
        token_1 = l1.attempt_lock()
741
 
        l2 = LockDir(self.get_transport(), 'a')
742
 
        token_2 = l2.attempt_lock()
743
 
        # l1 will notice its lock was stolen.
744
 
        self.assertRaises(errors.LockBroken,
745
 
            l1.unlock)
746
 
        l2.unlock()
747
 
 
748
 
    def test_auto_break_stale_lock_configured_off(self):
749
 
        """Automatic breaking can be turned off"""
750
 
        l1 = LockDir(self.get_transport(), 'a',
751
 
            extra_holder_info={'pid': '12312313'})
752
 
        token_1 = l1.attempt_lock()
753
 
        self.addCleanup(l1.unlock)
754
 
        l2 = LockDir(self.get_transport(), 'a')
755
 
        # This fails now, because dead lock breaking is off by default.
756
 
        self.assertRaises(LockContention,
757
 
            l2.attempt_lock)
758
 
        # and it's in fact not broken
759
 
        l1.confirm()
 
617
        self.assertRaises(errors.PermissionDenied, ld1.attempt_lock)