~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: Ian Clatworthy
  • Date: 2007-12-11 02:07:30 UTC
  • mto: (3119.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3120.
  • Revision ID: ian.clatworthy@internode.on.net-20071211020730-sdj4kj794dw0628e
make help topics more discoverable

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 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
30
    transport,
32
33
    LockBreakMismatch,
33
34
    LockBroken,
34
35
    LockContention,
 
36
    LockError,
35
37
    LockFailed,
36
38
    LockNotHeld,
37
39
    )
38
40
from bzrlib.lockdir import LockDir
39
 
from bzrlib.tests import (
40
 
    features,
41
 
    TestCaseWithTransport,
42
 
    )
 
41
from bzrlib.tests import TestCaseWithTransport
43
42
from bzrlib.trace import note
44
43
 
 
44
# These tests sometimes use threads to test the behaviour of lock files with
 
45
# concurrent actors.  This is not a typical (or necessarily supported) use;
 
46
# they're really meant for guarding between processes.
 
47
 
45
48
# These tests are run on the default transport provided by the test framework
46
49
# (typically a local disk transport).  That can be changed by the --transport
47
50
# option to bzr selftest.  The required properties of the transport
122
125
        lf1.attempt_lock()
123
126
        lf2 = LockDir(t, 'test_lock')
124
127
        try:
125
 
            # locking is between LockDir instances; aliases within
 
128
            # locking is between LockDir instances; aliases within 
126
129
            # a single process are not detected
127
130
            lf2.attempt_lock()
128
131
            self.fail('Failed to detect lock collision')
138
141
        lf1 = LockDir(t, 'test_lock')
139
142
        lf1.create()
140
143
        lf1.attempt_lock()
141
 
        self.addCleanup(lf1.unlock)
142
144
        # lock is held, should get some info on it
143
145
        info1 = lf1.peek()
144
146
        self.assertEqual(set(info1.keys()),
158
160
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
159
161
        self.assertEqual(lf2.peek(), None)
160
162
        lf1.attempt_lock()
161
 
        self.addCleanup(lf1.unlock)
162
163
        info2 = lf2.peek()
163
164
        self.assertTrue(info2)
164
165
        self.assertEqual(info2['nonce'], lf1.nonce)
165
166
 
166
167
    def test_30_lock_wait_fail(self):
167
168
        """Wait on a lock, then fail
168
 
 
 
169
        
169
170
        We ask to wait up to 400ms; this should fail within at most one
170
171
        second.  (Longer times are more realistic but we don't want the test
171
172
        suite to take too long, and this should do for now.)
183
184
            after = time.time()
184
185
            # it should only take about 0.4 seconds, but we allow more time in
185
186
            # case the machine is heavily loaded
186
 
            self.assertTrue(after - before <= 8.0,
 
187
            self.assertTrue(after - before <= 8.0, 
187
188
                    "took %f seconds to detect lock contention" % (after - before))
188
189
        finally:
189
190
            lf1.unlock()
 
191
        lock_base = lf2.transport.abspath(lf2.path)
190
192
        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}')
 
193
        self.assertEqual('%s %s\n'
 
194
                         '%s\n%s\n'
 
195
                         'Will continue to try until %s\n',
 
196
                         self._logged_reports[0][0])
 
197
        args = self._logged_reports[0][1]
 
198
        self.assertEqual('Unable to obtain', args[0])
 
199
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
200
        self.assertStartsWith(args[2], 'held by ')
 
201
        self.assertStartsWith(args[3], 'locked ')
 
202
        self.assertEndsWith(args[3], ' ago')
 
203
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
205
204
 
206
205
    def test_31_lock_wait_easy(self):
207
206
        """Succeed when waiting on a lock with no contention.
219
218
            lf1.unlock()
220
219
        self.assertEqual([], self._logged_reports)
221
220
 
 
221
    def test_32_lock_wait_succeed(self):
 
222
        """Succeed when trying to acquire a lock that gets released
 
223
 
 
224
        One thread holds on a lock and then releases it; another 
 
225
        tries to lock it.
 
226
        """
 
227
        # This test sometimes fails like this:
 
228
        # Traceback (most recent call last):
 
229
 
 
230
        #   File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/
 
231
        # test_lockdir.py", line 247, in test_32_lock_wait_succeed
 
232
        #     self.assertEqual(1, len(self._logged_reports))
 
233
        # AssertionError: not equal:
 
234
        # a = 1
 
235
        # b = 0
 
236
        raise tests.TestSkipped("Test fails intermittently")
 
237
        t = self.get_transport()
 
238
        lf1 = LockDir(t, 'test_lock')
 
239
        lf1.create()
 
240
        lf1.attempt_lock()
 
241
 
 
242
        def wait_and_unlock():
 
243
            time.sleep(0.1)
 
244
            lf1.unlock()
 
245
        unlocker = Thread(target=wait_and_unlock)
 
246
        unlocker.start()
 
247
        try:
 
248
            lf2 = LockDir(t, 'test_lock')
 
249
            self.setup_log_reporter(lf2)
 
250
            before = time.time()
 
251
            # wait and then lock
 
252
            lf2.wait_lock(timeout=0.4, poll=0.1)
 
253
            after = time.time()
 
254
            self.assertTrue(after - before <= 1.0)
 
255
        finally:
 
256
            unlocker.join()
 
257
 
 
258
        # There should be only 1 report, even though it should have to
 
259
        # wait for a while
 
260
        lock_base = lf2.transport.abspath(lf2.path)
 
261
        self.assertEqual(1, len(self._logged_reports))
 
262
        self.assertEqual('%s %s\n'
 
263
                         '%s\n%s\n'
 
264
                         'Will continue to try until %s\n',
 
265
                         self._logged_reports[0][0])
 
266
        args = self._logged_reports[0][1]
 
267
        self.assertEqual('Unable to obtain', args[0])
 
268
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
269
        self.assertStartsWith(args[2], 'held by ')
 
270
        self.assertStartsWith(args[3], 'locked ')
 
271
        self.assertEndsWith(args[3], ' ago')
 
272
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
273
 
 
274
    def test_34_lock_write_waits(self):
 
275
        """LockDir.lock_write() will wait for the lock.""" 
 
276
        # the test suite sets the default to 0 to make deadlocks fail fast.
 
277
        # change it for this test, as we want to try a manual deadlock.
 
278
        raise tests.TestSkipped('Timing-sensitive test')
 
279
        bzrlib.lockdir._DEFAULT_TIMEOUT_SECONDS = 300
 
280
        t = self.get_transport()
 
281
        lf1 = LockDir(t, 'test_lock')
 
282
        lf1.create()
 
283
        lf1.attempt_lock()
 
284
 
 
285
        def wait_and_unlock():
 
286
            time.sleep(0.1)
 
287
            lf1.unlock()
 
288
        unlocker = Thread(target=wait_and_unlock)
 
289
        unlocker.start()
 
290
        try:
 
291
            lf2 = LockDir(t, 'test_lock')
 
292
            self.setup_log_reporter(lf2)
 
293
            before = time.time()
 
294
            # wait and then lock
 
295
            lf2.lock_write()
 
296
            after = time.time()
 
297
        finally:
 
298
            unlocker.join()
 
299
 
 
300
        # There should be only 1 report, even though it should have to
 
301
        # wait for a while
 
302
        lock_base = lf2.transport.abspath(lf2.path)
 
303
        self.assertEqual(1, len(self._logged_reports))
 
304
        self.assertEqual('%s %s\n'
 
305
                         '%s\n%s\n'
 
306
                         'Will continue to try until %s\n',
 
307
                         self._logged_reports[0][0])
 
308
        args = self._logged_reports[0][1]
 
309
        self.assertEqual('Unable to obtain', args[0])
 
310
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
311
        self.assertStartsWith(args[2], 'held by ')
 
312
        self.assertStartsWith(args[3], 'locked ')
 
313
        self.assertEndsWith(args[3], ' ago')
 
314
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
315
 
 
316
    def test_35_wait_lock_changing(self):
 
317
        """LockDir.wait_lock() will report if the lock changes underneath.
 
318
        
 
319
        This is the stages we want to happen:
 
320
 
 
321
        0) Synchronization locks are created and locked.
 
322
        1) Lock1 obtains the lockdir, and releases the 'check' lock.
 
323
        2) Lock2 grabs the 'check' lock, and checks the lockdir.
 
324
           It sees the lockdir is already acquired, reports the fact, 
 
325
           and unsets the 'checked' lock.
 
326
        3) Thread1 blocks on acquiring the 'checked' lock, and then tells
 
327
           Lock1 to release and acquire the lockdir. This resets the 'check'
 
328
           lock.
 
329
        4) Lock2 acquires the 'check' lock, and checks again. It notices
 
330
           that the holder of the lock has changed, and so reports a new 
 
331
           lock holder.
 
332
        5) Thread1 blocks on the 'checked' lock, this time, it completely
 
333
           unlocks the lockdir, allowing Lock2 to acquire the lock.
 
334
        """
 
335
 
 
336
        wait_to_check_lock = Lock()
 
337
        wait_until_checked_lock = Lock()
 
338
 
 
339
        wait_to_check_lock.acquire()
 
340
        wait_until_checked_lock.acquire()
 
341
        note('locked check and checked locks')
 
342
 
 
343
        class LockDir1(LockDir):
 
344
            """Use the synchronization points for the first lock."""
 
345
 
 
346
            def attempt_lock(self):
 
347
                # Once we have acquired the lock, it is okay for
 
348
                # the other lock to check it
 
349
                try:
 
350
                    return super(LockDir1, self).attempt_lock()
 
351
                finally:
 
352
                    note('lock1: releasing check lock')
 
353
                    wait_to_check_lock.release()
 
354
 
 
355
        class LockDir2(LockDir):
 
356
            """Use the synchronization points for the second lock."""
 
357
 
 
358
            def attempt_lock(self):
 
359
                note('lock2: waiting for check lock')
 
360
                wait_to_check_lock.acquire()
 
361
                note('lock2: acquired check lock')
 
362
                try:
 
363
                    return super(LockDir2, self).attempt_lock()
 
364
                finally:
 
365
                    note('lock2: releasing checked lock')
 
366
                    wait_until_checked_lock.release()
 
367
 
 
368
        t = self.get_transport()
 
369
        lf1 = LockDir1(t, 'test_lock')
 
370
        lf1.create()
 
371
 
 
372
        lf2 = LockDir2(t, 'test_lock')
 
373
        self.setup_log_reporter(lf2)
 
374
 
 
375
        def wait_and_switch():
 
376
            lf1.attempt_lock()
 
377
            # Block until lock2 has had a chance to check
 
378
            note('lock1: waiting 1 for checked lock')
 
379
            wait_until_checked_lock.acquire()
 
380
            note('lock1: acquired for checked lock')
 
381
            note('lock1: released lockdir')
 
382
            lf1.unlock()
 
383
            note('lock1: acquiring lockdir')
 
384
            # Create a new nonce, so the lock looks different.
 
385
            lf1.nonce = osutils.rand_chars(20)
 
386
            lf1.lock_write()
 
387
            note('lock1: acquired lockdir')
 
388
 
 
389
            # Block until lock2 has peeked again
 
390
            note('lock1: waiting 2 for checked lock')
 
391
            wait_until_checked_lock.acquire()
 
392
            note('lock1: acquired for checked lock')
 
393
            # Now unlock, and let lock 2 grab the lock
 
394
            lf1.unlock()
 
395
            wait_to_check_lock.release()
 
396
 
 
397
        unlocker = Thread(target=wait_and_switch)
 
398
        unlocker.start()
 
399
        try:
 
400
            # Wait and play against the other thread
 
401
            lf2.wait_lock(timeout=20.0, poll=0.01)
 
402
        finally:
 
403
            unlocker.join()
 
404
        lf2.unlock()
 
405
 
 
406
        # There should be 2 reports, because the lock changed
 
407
        lock_base = lf2.transport.abspath(lf2.path)
 
408
        self.assertEqual(2, len(self._logged_reports))
 
409
 
 
410
        self.assertEqual('%s %s\n'
 
411
                         '%s\n%s\n'
 
412
                         'Will continue to try until %s\n',
 
413
                         self._logged_reports[0][0])
 
414
        args = self._logged_reports[0][1]
 
415
        self.assertEqual('Unable to obtain', args[0])
 
416
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
417
        self.assertStartsWith(args[2], 'held by ')
 
418
        self.assertStartsWith(args[3], 'locked ')
 
419
        self.assertEndsWith(args[3], ' ago')
 
420
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
421
 
 
422
        self.assertEqual('%s %s\n'
 
423
                         '%s\n%s\n'
 
424
                         'Will continue to try until %s\n',
 
425
                         self._logged_reports[1][0])
 
426
        args = self._logged_reports[1][1]
 
427
        self.assertEqual('Lock owner changed for', 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
 
222
434
    def test_40_confirm_easy(self):
223
435
        """Confirm a lock that's already held"""
224
436
        t = self.get_transport()
225
437
        lf1 = LockDir(t, 'test_lock')
226
438
        lf1.create()
227
439
        lf1.attempt_lock()
228
 
        self.addCleanup(lf1.unlock)
229
440
        lf1.confirm()
230
441
 
231
442
    def test_41_confirm_not_held(self):
243
454
        lf1.attempt_lock()
244
455
        t.move('test_lock', 'lock_gone_now')
245
456
        self.assertRaises(LockBroken, lf1.confirm)
246
 
        # Clean up
247
 
        t.move('lock_gone_now', 'test_lock')
248
 
        lf1.unlock()
249
457
 
250
458
    def test_43_break(self):
251
459
        """Break a lock whose caller has forgotten it"""
262
470
        lf2.force_break(holder_info)
263
471
        # now we should be able to take it
264
472
        lf2.attempt_lock()
265
 
        self.addCleanup(lf2.unlock)
266
473
        lf2.confirm()
267
474
 
268
475
    def test_44_break_already_released(self):
280
487
        lf2.force_break(holder_info)
281
488
        # now we should be able to take it
282
489
        lf2.attempt_lock()
283
 
        self.addCleanup(lf2.unlock)
284
490
        lf2.confirm()
285
491
 
286
492
    def test_45_break_mismatch(self):
312
518
        """Check the on-disk representation of LockDirs is as expected.
313
519
 
314
520
        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
 
521
        When the lock is held, there should be a lockname/held directory 
316
522
        containing an info file.
317
523
        """
318
524
        t = self.get_transport()
333
539
        # do this without IO redirection to ensure it doesn't prompt.
334
540
        self.assertRaises(AssertionError, ld1.break_lock)
335
541
        orig_factory = bzrlib.ui.ui_factory
336
 
        bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
 
542
        # silent ui - no need for stdout
 
543
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
 
544
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
337
545
        try:
338
546
            ld2.break_lock()
339
547
            self.assertRaises(LockBroken, ld1.unlock)
340
548
        finally:
341
549
            bzrlib.ui.ui_factory = orig_factory
342
550
 
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
551
    def test_create_missing_base_directory(self):
396
552
        """If LockDir.path doesn't exist, it can be created
397
553
 
403
559
        lf1 = LockDir(t, 'test_lock')
404
560
 
405
561
        lf1.create()
406
 
        self.assertTrue(t.has('test_lock'))
 
562
        self.failUnless(t.has('test_lock'))
407
563
 
408
564
        t.rmdir('test_lock')
409
 
        self.assertFalse(t.has('test_lock'))
 
565
        self.failIf(t.has('test_lock'))
410
566
 
411
567
        # This will create 'test_lock' if it needs to
412
568
        lf1.lock_write()
413
 
        self.assertTrue(t.has('test_lock'))
414
 
        self.assertTrue(t.has('test_lock/held/info'))
 
569
        self.failUnless(t.has('test_lock'))
 
570
        self.failUnless(t.has('test_lock/held/info'))
415
571
 
416
572
        lf1.unlock()
417
 
        self.assertFalse(t.has('test_lock/held/info'))
 
573
        self.failIf(t.has('test_lock/held/info'))
418
574
 
419
575
    def test__format_lock_info(self):
420
576
        ld1 = self.get_lock()
424
580
            info_list = ld1._format_lock_info(ld1.peek())
425
581
        finally:
426
582
            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
 
583
        self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
 
584
                         info_list[0])
 
585
        self.assertContainsRe(info_list[1],
 
586
                              r'^held by .* on host .* \[process #\d*\]$')
 
587
        self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')
431
588
 
432
589
    def test_lock_without_email(self):
433
590
        global_config = config.GlobalConfig()
439
596
        ld1.unlock()
440
597
 
441
598
    def test_lock_permission(self):
442
 
        self.requireFeature(features.not_running_as_root)
443
599
        if not osutils.supports_posix_readonly():
444
600
            raise tests.TestSkipped('Cannot induce a permission failure')
445
601
        ld1 = self.get_lock()
451
607
    def test_lock_by_token(self):
452
608
        ld1 = self.get_lock()
453
609
        token = ld1.lock_write()
454
 
        self.addCleanup(ld1.unlock)
455
610
        self.assertNotEqual(None, token)
456
611
        ld2 = self.get_lock()
457
612
        t2 = ld2.lock_write(token)
458
 
        self.addCleanup(ld2.unlock)
459
613
        self.assertEqual(token, t2)
460
614
 
461
615
    def test_lock_with_buggy_rename(self):
486
640
        check_dir([])
487
641
        # when held, that's all we see
488
642
        ld1.attempt_lock()
489
 
        self.addCleanup(ld1.unlock)
490
643
        check_dir(['held'])
491
644
        # second guy should fail
492
645
        self.assertRaises(errors.LockContention, ld2.attempt_lock)
493
646
        # no kibble
494
647
        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)