/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

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
28
    osutils,
28
29
    tests,
29
 
    transport,
30
30
    )
31
31
from bzrlib.errors import (
32
 
    LockBreakMismatch,
33
 
    LockBroken,
34
 
    LockContention,
35
 
    LockFailed,
36
 
    LockNotHeld,
37
 
    )
 
32
        LockBreakMismatch,
 
33
        LockContention, LockError, UnlockableTransport,
 
34
        LockNotHeld, LockBroken
 
35
        )
38
36
from bzrlib.lockdir import LockDir
39
 
from bzrlib.tests import (
40
 
    features,
41
 
    TestCaseWithTransport,
42
 
    )
 
37
from bzrlib.tests import TestCaseWithTransport
43
38
from bzrlib.trace import note
44
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.
 
43
 
45
44
# These tests are run on the default transport provided by the test framework
46
45
# (typically a local disk transport).  That can be changed by the --transport
47
46
# option to bzr selftest.  The required properties of the transport
105
104
        """Fail to create lock on readonly transport"""
106
105
        t = self.get_readonly_transport()
107
106
        lf = LockDir(t, 'test_lock')
108
 
        self.assertRaises(LockFailed, lf.create)
 
107
        self.assertRaises(UnlockableTransport, lf.create)
109
108
 
110
109
    def test_12_lock_readonly_transport(self):
111
110
        """Fail to lock on readonly transport"""
112
111
        lf = LockDir(self.get_transport(), 'test_lock')
113
112
        lf.create()
114
113
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
115
 
        self.assertRaises(LockFailed, lf.attempt_lock)
 
114
        self.assertRaises(UnlockableTransport, lf.attempt_lock)
116
115
 
117
116
    def test_20_lock_contested(self):
118
117
        """Contention to get a lock"""
122
121
        lf1.attempt_lock()
123
122
        lf2 = LockDir(t, 'test_lock')
124
123
        try:
125
 
            # locking is between LockDir instances; aliases within
 
124
            # locking is between LockDir instances; aliases within 
126
125
            # a single process are not detected
127
126
            lf2.attempt_lock()
128
127
            self.fail('Failed to detect lock collision')
138
137
        lf1 = LockDir(t, 'test_lock')
139
138
        lf1.create()
140
139
        lf1.attempt_lock()
141
 
        self.addCleanup(lf1.unlock)
142
140
        # lock is held, should get some info on it
143
141
        info1 = lf1.peek()
144
142
        self.assertEqual(set(info1.keys()),
158
156
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
159
157
        self.assertEqual(lf2.peek(), None)
160
158
        lf1.attempt_lock()
161
 
        self.addCleanup(lf1.unlock)
162
159
        info2 = lf2.peek()
163
160
        self.assertTrue(info2)
164
161
        self.assertEqual(info2['nonce'], lf1.nonce)
165
162
 
166
163
    def test_30_lock_wait_fail(self):
167
164
        """Wait on a lock, then fail
168
 
 
 
165
        
169
166
        We ask to wait up to 400ms; this should fail within at most one
170
167
        second.  (Longer times are more realistic but we don't want the test
171
168
        suite to take too long, and this should do for now.)
183
180
            after = time.time()
184
181
            # it should only take about 0.4 seconds, but we allow more time in
185
182
            # case the machine is heavily loaded
186
 
            self.assertTrue(after - before <= 8.0,
 
183
            self.assertTrue(after - before <= 8.0, 
187
184
                    "took %f seconds to detect lock contention" % (after - before))
188
185
        finally:
189
186
            lf1.unlock()
 
187
        lock_base = lf2.transport.abspath(lf2.path)
190
188
        self.assertEqual(1, len(self._logged_reports))
191
 
        self.assertEqual(self._logged_reports[0][0],
192
 
            '%s lock %s held by %s\n'
193
 
            'at %s [process #%s], acquired %s.\n'
194
 
            'Will continue to try until %s, unless '
195
 
            'you press Ctrl-C.\n'
196
 
            'See "bzr help break-lock" for more.')
197
 
        start, lock_url, user, hostname, pid, time_ago, deadline_str = \
198
 
            self._logged_reports[0][1]
199
 
        self.assertEqual(start, u'Unable to obtain')
200
 
        self.assertEqual(user, u'jrandom@example.com')
201
 
        # skip hostname
202
 
        self.assertContainsRe(pid, r'\d+')
203
 
        self.assertContainsRe(time_ago, r'.* ago')
204
 
        self.assertContainsRe(deadline_str, r'\d{2}:\d{2}:\d{2}')
 
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')
205
200
 
206
201
    def test_31_lock_wait_easy(self):
207
202
        """Succeed when waiting on a lock with no contention.
219
214
            lf1.unlock()
220
215
        self.assertEqual([], self._logged_reports)
221
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
 
222
446
    def test_40_confirm_easy(self):
223
447
        """Confirm a lock that's already held"""
224
448
        t = self.get_transport()
225
449
        lf1 = LockDir(t, 'test_lock')
226
450
        lf1.create()
227
451
        lf1.attempt_lock()
228
 
        self.addCleanup(lf1.unlock)
229
452
        lf1.confirm()
230
453
 
231
454
    def test_41_confirm_not_held(self):
243
466
        lf1.attempt_lock()
244
467
        t.move('test_lock', 'lock_gone_now')
245
468
        self.assertRaises(LockBroken, lf1.confirm)
246
 
        # Clean up
247
 
        t.move('lock_gone_now', 'test_lock')
248
 
        lf1.unlock()
249
469
 
250
470
    def test_43_break(self):
251
471
        """Break a lock whose caller has forgotten it"""
262
482
        lf2.force_break(holder_info)
263
483
        # now we should be able to take it
264
484
        lf2.attempt_lock()
265
 
        self.addCleanup(lf2.unlock)
266
485
        lf2.confirm()
267
486
 
268
487
    def test_44_break_already_released(self):
280
499
        lf2.force_break(holder_info)
281
500
        # now we should be able to take it
282
501
        lf2.attempt_lock()
283
 
        self.addCleanup(lf2.unlock)
284
502
        lf2.confirm()
285
503
 
286
504
    def test_45_break_mismatch(self):
312
530
        """Check the on-disk representation of LockDirs is as expected.
313
531
 
314
532
        There should always be a top-level directory named by the lock.
315
 
        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 
316
534
        containing an info file.
317
535
        """
318
536
        t = self.get_transport()
333
551
        # do this without IO redirection to ensure it doesn't prompt.
334
552
        self.assertRaises(AssertionError, ld1.break_lock)
335
553
        orig_factory = bzrlib.ui.ui_factory
336
 
        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")
337
557
        try:
338
558
            ld2.break_lock()
339
559
            self.assertRaises(LockBroken, ld1.unlock)
340
560
        finally:
341
561
            bzrlib.ui.ui_factory = orig_factory
342
562
 
343
 
    def test_break_lock_corrupt_info(self):
344
 
        """break_lock works even if the info file is corrupt (and tells the UI
345
 
        that it is corrupt).
346
 
        """
347
 
        ld = self.get_lock()
348
 
        ld2 = self.get_lock()
349
 
        ld.create()
350
 
        ld.lock_write()
351
 
        ld.transport.put_bytes_non_atomic('test_lock/held/info', '\0')
352
 
        class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
353
 
            def __init__(self):
354
 
                self.prompts = []
355
 
            def get_boolean(self, prompt):
356
 
                self.prompts.append(('boolean', prompt))
357
 
                return True
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
 
        class LoggingUIFactory(bzrlib.ui.SilentUIFactory):
376
 
            def __init__(self):
377
 
                self.prompts = []
378
 
            def get_boolean(self, prompt):
379
 
                self.prompts.append(('boolean', prompt))
380
 
                return True
381
 
        ui = LoggingUIFactory()
382
 
        orig_factory = bzrlib.ui.ui_factory
383
 
        bzrlib.ui.ui_factory = ui
384
 
        try:
385
 
            ld2.break_lock()
386
 
            self.assertRaises(LockBroken, ld.unlock)
387
 
            self.assertLength(0, ui.prompts)
388
 
        finally:
389
 
            bzrlib.ui.ui_factory = orig_factory
390
 
        # Suppress warnings due to ld not being unlocked
391
 
        # XXX: if lock_broken hook was invoked in this case, this hack would
392
 
        # not be necessary.  - Andrew Bennetts, 2010-09-06.
393
 
        del self._lock_actions[:]
394
 
 
395
563
    def test_create_missing_base_directory(self):
396
564
        """If LockDir.path doesn't exist, it can be created
397
565
 
403
571
        lf1 = LockDir(t, 'test_lock')
404
572
 
405
573
        lf1.create()
406
 
        self.assertTrue(t.has('test_lock'))
 
574
        self.failUnless(t.has('test_lock'))
407
575
 
408
576
        t.rmdir('test_lock')
409
 
        self.assertFalse(t.has('test_lock'))
 
577
        self.failIf(t.has('test_lock'))
410
578
 
411
579
        # This will create 'test_lock' if it needs to
412
580
        lf1.lock_write()
413
 
        self.assertTrue(t.has('test_lock'))
414
 
        self.assertTrue(t.has('test_lock/held/info'))
 
581
        self.failUnless(t.has('test_lock'))
 
582
        self.failUnless(t.has('test_lock/held/info'))
415
583
 
416
584
        lf1.unlock()
417
 
        self.assertFalse(t.has('test_lock/held/info'))
 
585
        self.failIf(t.has('test_lock/held/info'))
418
586
 
419
587
    def test__format_lock_info(self):
420
588
        ld1 = self.get_lock()
424
592
            info_list = ld1._format_lock_info(ld1.peek())
425
593
        finally:
426
594
            ld1.unlock()
427
 
        self.assertEqual(info_list[0], u'jrandom@example.com')
428
 
        # info_list[1] is hostname. we skip this.
429
 
        self.assertContainsRe(info_list[2], '^\d+$') # pid
430
 
        self.assertContainsRe(info_list[3], r'^\d+ seconds? ago$') # time_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$')
431
600
 
432
601
    def test_lock_without_email(self):
433
602
        global_config = config.GlobalConfig()
439
608
        ld1.unlock()
440
609
 
441
610
    def test_lock_permission(self):
442
 
        self.requireFeature(features.not_running_as_root)
443
611
        if not osutils.supports_posix_readonly():
444
612
            raise tests.TestSkipped('Cannot induce a permission failure')
445
613
        ld1 = self.get_lock()
446
614
        lock_path = ld1.transport.local_abspath('test_lock')
447
615
        os.mkdir(lock_path)
448
616
        osutils.make_readonly(lock_path)
449
 
        self.assertRaises(errors.LockFailed, ld1.attempt_lock)
450
 
 
451
 
    def test_lock_by_token(self):
452
 
        ld1 = self.get_lock()
453
 
        token = ld1.lock_write()
454
 
        self.addCleanup(ld1.unlock)
455
 
        self.assertNotEqual(None, token)
456
 
        ld2 = self.get_lock()
457
 
        t2 = ld2.lock_write(token)
458
 
        self.addCleanup(ld2.unlock)
459
 
        self.assertEqual(token, t2)
460
 
 
461
 
    def test_lock_with_buggy_rename(self):
462
 
        # test that lock acquisition handles servers which pretend they
463
 
        # renamed correctly but that actually fail
464
 
        t = transport.get_transport('brokenrename+' + self.get_url())
465
 
        ld1 = LockDir(t, 'test_lock')
466
 
        ld1.create()
467
 
        ld1.attempt_lock()
468
 
        ld2 = LockDir(t, 'test_lock')
469
 
        # we should fail to lock
470
 
        e = self.assertRaises(errors.LockContention, ld2.attempt_lock)
471
 
        # now the original caller should succeed in unlocking
472
 
        ld1.unlock()
473
 
        # and there should be nothing left over
474
 
        self.assertEquals([], t.list_dir('test_lock'))
475
 
 
476
 
    def test_failed_lock_leaves_no_trash(self):
477
 
        # if we fail to acquire the lock, we don't leave pending directories
478
 
        # behind -- https://bugs.launchpad.net/bzr/+bug/109169
479
 
        ld1 = self.get_lock()
480
 
        ld2 = self.get_lock()
481
 
        # should be nothing before we start
482
 
        ld1.create()
483
 
        t = self.get_transport().clone('test_lock')
484
 
        def check_dir(a):
485
 
            self.assertEquals(a, t.list_dir('.'))
486
 
        check_dir([])
487
 
        # when held, that's all we see
488
 
        ld1.attempt_lock()
489
 
        self.addCleanup(ld1.unlock)
490
 
        check_dir(['held'])
491
 
        # second guy should fail
492
 
        self.assertRaises(errors.LockContention, ld2.attempt_lock)
493
 
        # no kibble
494
 
        check_dir(['held'])
495
 
 
496
 
    def test_no_lockdir_info(self):
497
 
        """We can cope with empty info files."""
498
 
        # This seems like a fairly common failure case - see
499
 
        # <https://bugs.launchpad.net/bzr/+bug/185103> and all its dupes.
500
 
        # Processes are often interrupted after opening the file
501
 
        # before the actual contents are committed.
502
 
        t = self.get_transport()
503
 
        t.mkdir('test_lock')
504
 
        t.mkdir('test_lock/held')
505
 
        t.put_bytes('test_lock/held/info', '')
506
 
        lf = LockDir(t, 'test_lock')
507
 
        info = lf.peek()
508
 
        formatted_info = lf._format_lock_info(info)
509
 
        self.assertEquals(
510
 
            ['<unknown>', '<unknown>', '<unknown>', '(unknown)'],
511
 
            formatted_info)
512
 
 
513
 
    def test_corrupt_lockdir_info(self):
514
 
        """We can cope with corrupt (and thus unparseable) info files."""
515
 
        # This seems like a fairly common failure case too - see
516
 
        # <https://bugs.launchpad.net/bzr/+bug/619872> for instance.
517
 
        # In particular some systems tend to fill recently created files with
518
 
        # nul bytes after recovering from a system crash.
519
 
        t = self.get_transport()
520
 
        t.mkdir('test_lock')
521
 
        t.mkdir('test_lock/held')
522
 
        t.put_bytes('test_lock/held/info', '\0')
523
 
        lf = LockDir(t, 'test_lock')
524
 
        self.assertRaises(errors.LockCorrupt, lf.peek)
525
 
        # Currently attempt_lock gives LockContention, but LockCorrupt would be
526
 
        # a reasonable result too.
527
 
        self.assertRaises(
528
 
            (errors.LockCorrupt, errors.LockContention), lf.attempt_lock)
529
 
        self.assertRaises(errors.LockCorrupt, lf.validate_token, 'fake token')
530
 
 
531
 
    def test_missing_lockdir_info(self):
532
 
        """We can cope with absent info files."""
533
 
        t = self.get_transport()
534
 
        t.mkdir('test_lock')
535
 
        t.mkdir('test_lock/held')
536
 
        lf = LockDir(t, 'test_lock')
537
 
        # In this case we expect the 'not held' result from peek, because peek
538
 
        # cannot be expected to notice that there is a 'held' directory with no
539
 
        # 'info' file.
540
 
        self.assertEqual(None, lf.peek())
541
 
        # And lock/unlock may work or give LockContention (but not any other
542
 
        # error).
543
 
        try:
544
 
            lf.attempt_lock()
545
 
        except LockContention:
546
 
            # LockContention is ok, and expected on Windows
547
 
            pass
548
 
        else:
549
 
            # no error is ok, and expected on POSIX (because POSIX allows
550
 
            # os.rename over an empty directory).
551
 
            lf.unlock()
552
 
        # Currently raises TokenMismatch, but LockCorrupt would be reasonable
553
 
        # too.
554
 
        self.assertRaises(
555
 
            (errors.TokenMismatch, errors.LockCorrupt),
556
 
            lf.validate_token, 'fake token')
557
 
 
558
 
 
559
 
class TestLockDirHooks(TestCaseWithTransport):
560
 
 
561
 
    def setUp(self):
562
 
        super(TestLockDirHooks, self).setUp()
563
 
        self._calls = []
564
 
 
565
 
    def get_lock(self):
566
 
        return LockDir(self.get_transport(), 'test_lock')
567
 
 
568
 
    def record_hook(self, result):
569
 
        self._calls.append(result)
570
 
 
571
 
    def test_LockDir_acquired_success(self):
572
 
        # the LockDir.lock_acquired hook fires when a lock is acquired.
573
 
        LockDir.hooks.install_named_hook('lock_acquired',
574
 
                                         self.record_hook, 'record_hook')
575
 
        ld = self.get_lock()
576
 
        ld.create()
577
 
        self.assertEqual([], self._calls)
578
 
        result = ld.attempt_lock()
579
 
        lock_path = ld.transport.abspath(ld.path)
580
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
581
 
        ld.unlock()
582
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
583
 
 
584
 
    def test_LockDir_acquired_fail(self):
585
 
        # the LockDir.lock_acquired hook does not fire on failure.
586
 
        ld = self.get_lock()
587
 
        ld.create()
588
 
        ld2 = self.get_lock()
589
 
        ld2.attempt_lock()
590
 
        # install a lock hook now, when the disk lock is locked
591
 
        LockDir.hooks.install_named_hook('lock_acquired',
592
 
                                         self.record_hook, 'record_hook')
593
 
        self.assertRaises(errors.LockContention, ld.attempt_lock)
594
 
        self.assertEqual([], self._calls)
595
 
        ld2.unlock()
596
 
        self.assertEqual([], self._calls)
597
 
 
598
 
    def test_LockDir_released_success(self):
599
 
        # the LockDir.lock_released hook fires when a lock is acquired.
600
 
        LockDir.hooks.install_named_hook('lock_released',
601
 
                                         self.record_hook, 'record_hook')
602
 
        ld = self.get_lock()
603
 
        ld.create()
604
 
        self.assertEqual([], self._calls)
605
 
        result = ld.attempt_lock()
606
 
        self.assertEqual([], self._calls)
607
 
        ld.unlock()
608
 
        lock_path = ld.transport.abspath(ld.path)
609
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
610
 
 
611
 
    def test_LockDir_released_fail(self):
612
 
        # the LockDir.lock_released hook does not fire on failure.
613
 
        ld = self.get_lock()
614
 
        ld.create()
615
 
        ld2 = self.get_lock()
616
 
        ld.attempt_lock()
617
 
        ld2.force_break(ld2.peek())
618
 
        LockDir.hooks.install_named_hook('lock_released',
619
 
                                         self.record_hook, 'record_hook')
620
 
        self.assertRaises(LockBroken, ld.unlock)
621
 
        self.assertEqual([], self._calls)
622
 
 
623
 
    def test_LockDir_broken_success(self):
624
 
        # the LockDir.lock_broken hook fires when a lock is broken.
625
 
        ld = self.get_lock()
626
 
        ld.create()
627
 
        ld2 = self.get_lock()
628
 
        result = ld.attempt_lock()
629
 
        LockDir.hooks.install_named_hook('lock_broken',
630
 
                                         self.record_hook, 'record_hook')
631
 
        ld2.force_break(ld2.peek())
632
 
        lock_path = ld.transport.abspath(ld.path)
633
 
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
634
 
 
635
 
    def test_LockDir_broken_failure(self):
636
 
        # the LockDir.lock_broken hook does not fires when a lock is already
637
 
        # released.
638
 
        ld = self.get_lock()
639
 
        ld.create()
640
 
        ld2 = self.get_lock()
641
 
        result = ld.attempt_lock()
642
 
        holder_info = ld2.peek()
643
 
        ld.unlock()
644
 
        LockDir.hooks.install_named_hook('lock_broken',
645
 
                                         self.record_hook, 'record_hook')
646
 
        ld2.force_break(holder_info)
647
 
        lock_path = ld.transport.abspath(ld.path)
648
 
        self.assertEqual([], self._calls)
 
617
        self.assertRaises(errors.PermissionDenied, ld1.attempt_lock)