/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-03-06 12:28:18 UTC
  • mto: (2321.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: robertc@robertcollins.net-20070306122818-xk0lc3l01ecl6vbc
Get merge_nested finally working: change nested tree iterators to take file_ids, and ensure the right branch is connected to in the merge logic. May not be suitable for shared repositories yet.

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
 
    )
38
 
from bzrlib.lockdir import LockDir
39
 
from bzrlib.tests import (
40
 
    features,
41
 
    TestCaseWithTransport,
42
 
    )
 
32
        LockBreakMismatch,
 
33
        LockContention, LockError, UnlockableTransport,
 
34
        LockNotHeld, LockBroken
 
35
        )
 
36
from bzrlib.lockdir import LockDir, _DEFAULT_TIMEOUT_SECONDS
 
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
        t = self.get_transport()
 
290
        lf1 = LockDir(t, 'test_lock')
 
291
        lf1.create()
 
292
        lf1.attempt_lock()
 
293
 
 
294
        def wait_and_unlock():
 
295
            time.sleep(0.1)
 
296
            lf1.unlock()
 
297
        unlocker = Thread(target=wait_and_unlock)
 
298
        unlocker.start()
 
299
        try:
 
300
            lf2 = LockDir(t, 'test_lock')
 
301
            self.setup_log_reporter(lf2)
 
302
            before = time.time()
 
303
            # wait and then lock
 
304
            lf2.lock_write()
 
305
            after = time.time()
 
306
        finally:
 
307
            unlocker.join()
 
308
 
 
309
        # There should be only 1 report, even though it should have to
 
310
        # wait for a while
 
311
        lock_base = lf2.transport.abspath(lf2.path)
 
312
        self.assertEqual(1, len(self._logged_reports))
 
313
        self.assertEqual('%s %s\n'
 
314
                         '%s\n%s\n'
 
315
                         'Will continue to try until %s\n',
 
316
                         self._logged_reports[0][0])
 
317
        args = self._logged_reports[0][1]
 
318
        self.assertEqual('Unable to obtain', args[0])
 
319
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
320
        self.assertStartsWith(args[2], 'held by ')
 
321
        self.assertStartsWith(args[3], 'locked ')
 
322
        self.assertEndsWith(args[3], ' ago')
 
323
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
324
 
 
325
    def test_35_wait_lock_changing(self):
 
326
        """LockDir.wait_lock() will report if the lock changes underneath.
 
327
        
 
328
        This is the stages we want to happen:
 
329
 
 
330
        0) Synchronization locks are created and locked.
 
331
        1) Lock1 obtains the lockdir, and releases the 'check' lock.
 
332
        2) Lock2 grabs the 'check' lock, and checks the lockdir.
 
333
           It sees the lockdir is already acquired, reports the fact, 
 
334
           and unsets the 'checked' lock.
 
335
        3) Thread1 blocks on acquiring the 'checked' lock, and then tells
 
336
           Lock1 to release and acquire the lockdir. This resets the 'check'
 
337
           lock.
 
338
        4) Lock2 acquires the 'check' lock, and checks again. It notices
 
339
           that the holder of the lock has changed, and so reports a new 
 
340
           lock holder.
 
341
        5) Thread1 blocks on the 'checked' lock, this time, it completely
 
342
           unlocks the lockdir, allowing Lock2 to acquire the lock.
 
343
        """
 
344
 
 
345
        wait_to_check_lock = Lock()
 
346
        wait_until_checked_lock = Lock()
 
347
 
 
348
        wait_to_check_lock.acquire()
 
349
        wait_until_checked_lock.acquire()
 
350
        note('locked check and checked locks')
 
351
 
 
352
        class LockDir1(LockDir):
 
353
            """Use the synchronization points for the first lock."""
 
354
 
 
355
            def attempt_lock(self):
 
356
                # Once we have acquired the lock, it is okay for
 
357
                # the other lock to check it
 
358
                try:
 
359
                    return super(LockDir1, self).attempt_lock()
 
360
                finally:
 
361
                    note('lock1: releasing check lock')
 
362
                    wait_to_check_lock.release()
 
363
 
 
364
        class LockDir2(LockDir):
 
365
            """Use the synchronization points for the second lock."""
 
366
 
 
367
            def attempt_lock(self):
 
368
                note('lock2: waiting for check lock')
 
369
                wait_to_check_lock.acquire()
 
370
                note('lock2: acquired check lock')
 
371
                try:
 
372
                    return super(LockDir2, self).attempt_lock()
 
373
                finally:
 
374
                    note('lock2: releasing checked lock')
 
375
                    wait_until_checked_lock.release()
 
376
 
 
377
        t = self.get_transport()
 
378
        lf1 = LockDir1(t, 'test_lock')
 
379
        lf1.create()
 
380
 
 
381
        lf2 = LockDir2(t, 'test_lock')
 
382
        self.setup_log_reporter(lf2)
 
383
 
 
384
        def wait_and_switch():
 
385
            lf1.attempt_lock()
 
386
            # Block until lock2 has had a chance to check
 
387
            note('lock1: waiting 1 for checked lock')
 
388
            wait_until_checked_lock.acquire()
 
389
            note('lock1: acquired for checked lock')
 
390
            note('lock1: released lockdir')
 
391
            lf1.unlock()
 
392
            note('lock1: acquiring lockdir')
 
393
            # Create a new nonce, so the lock looks different.
 
394
            lf1.nonce = osutils.rand_chars(20)
 
395
            lf1.lock_write()
 
396
            note('lock1: acquired lockdir')
 
397
 
 
398
            # Block until lock2 has peeked again
 
399
            note('lock1: waiting 2 for checked lock')
 
400
            wait_until_checked_lock.acquire()
 
401
            note('lock1: acquired for checked lock')
 
402
            # Now unlock, and let lock 2 grab the lock
 
403
            lf1.unlock()
 
404
            wait_to_check_lock.release()
 
405
 
 
406
        unlocker = Thread(target=wait_and_switch)
 
407
        unlocker.start()
 
408
        try:
 
409
            # Wait and play against the other thread
 
410
            lf2.wait_lock(timeout=1.0, poll=0.01)
 
411
        finally:
 
412
            unlocker.join()
 
413
        lf2.unlock()
 
414
 
 
415
        # There should be 2 reports, because the lock changed
 
416
        lock_base = lf2.transport.abspath(lf2.path)
 
417
        self.assertEqual(2, len(self._logged_reports))
 
418
 
 
419
        self.assertEqual('%s %s\n'
 
420
                         '%s\n%s\n'
 
421
                         'Will continue to try until %s\n',
 
422
                         self._logged_reports[0][0])
 
423
        args = self._logged_reports[0][1]
 
424
        self.assertEqual('Unable to obtain', args[0])
 
425
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
426
        self.assertStartsWith(args[2], 'held by ')
 
427
        self.assertStartsWith(args[3], 'locked ')
 
428
        self.assertEndsWith(args[3], ' ago')
 
429
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
430
 
 
431
        self.assertEqual('%s %s\n'
 
432
                         '%s\n%s\n'
 
433
                         'Will continue to try until %s\n',
 
434
                         self._logged_reports[1][0])
 
435
        args = self._logged_reports[1][1]
 
436
        self.assertEqual('Lock owner changed for', args[0])
 
437
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
438
        self.assertStartsWith(args[2], 'held by ')
 
439
        self.assertStartsWith(args[3], 'locked ')
 
440
        self.assertEndsWith(args[3], ' ago')
 
441
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
442
 
222
443
    def test_40_confirm_easy(self):
223
444
        """Confirm a lock that's already held"""
224
445
        t = self.get_transport()
225
446
        lf1 = LockDir(t, 'test_lock')
226
447
        lf1.create()
227
448
        lf1.attempt_lock()
228
 
        self.addCleanup(lf1.unlock)
229
449
        lf1.confirm()
230
450
 
231
451
    def test_41_confirm_not_held(self):
243
463
        lf1.attempt_lock()
244
464
        t.move('test_lock', 'lock_gone_now')
245
465
        self.assertRaises(LockBroken, lf1.confirm)
246
 
        # Clean up
247
 
        t.move('lock_gone_now', 'test_lock')
248
 
        lf1.unlock()
249
466
 
250
467
    def test_43_break(self):
251
468
        """Break a lock whose caller has forgotten it"""
262
479
        lf2.force_break(holder_info)
263
480
        # now we should be able to take it
264
481
        lf2.attempt_lock()
265
 
        self.addCleanup(lf2.unlock)
266
482
        lf2.confirm()
267
483
 
268
484
    def test_44_break_already_released(self):
280
496
        lf2.force_break(holder_info)
281
497
        # now we should be able to take it
282
498
        lf2.attempt_lock()
283
 
        self.addCleanup(lf2.unlock)
284
499
        lf2.confirm()
285
500
 
286
501
    def test_45_break_mismatch(self):
312
527
        """Check the on-disk representation of LockDirs is as expected.
313
528
 
314
529
        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
 
530
        When the lock is held, there should be a lockname/held directory 
316
531
        containing an info file.
317
532
        """
318
533
        t = self.get_transport()
333
548
        # do this without IO redirection to ensure it doesn't prompt.
334
549
        self.assertRaises(AssertionError, ld1.break_lock)
335
550
        orig_factory = bzrlib.ui.ui_factory
336
 
        bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
 
551
        # silent ui - no need for stdout
 
552
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
 
553
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
337
554
        try:
338
555
            ld2.break_lock()
339
556
            self.assertRaises(LockBroken, ld1.unlock)
340
557
        finally:
341
558
            bzrlib.ui.ui_factory = orig_factory
342
559
 
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
560
    def test_create_missing_base_directory(self):
396
561
        """If LockDir.path doesn't exist, it can be created
397
562
 
403
568
        lf1 = LockDir(t, 'test_lock')
404
569
 
405
570
        lf1.create()
406
 
        self.assertTrue(t.has('test_lock'))
 
571
        self.failUnless(t.has('test_lock'))
407
572
 
408
573
        t.rmdir('test_lock')
409
 
        self.assertFalse(t.has('test_lock'))
 
574
        self.failIf(t.has('test_lock'))
410
575
 
411
576
        # This will create 'test_lock' if it needs to
412
577
        lf1.lock_write()
413
 
        self.assertTrue(t.has('test_lock'))
414
 
        self.assertTrue(t.has('test_lock/held/info'))
 
578
        self.failUnless(t.has('test_lock'))
 
579
        self.failUnless(t.has('test_lock/held/info'))
415
580
 
416
581
        lf1.unlock()
417
 
        self.assertFalse(t.has('test_lock/held/info'))
 
582
        self.failIf(t.has('test_lock/held/info'))
418
583
 
419
584
    def test__format_lock_info(self):
420
585
        ld1 = self.get_lock()
424
589
            info_list = ld1._format_lock_info(ld1.peek())
425
590
        finally:
426
591
            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
 
592
        self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
 
593
                         info_list[0])
 
594
        self.assertContainsRe(info_list[1],
 
595
                              r'^held by .* on host .* \[process #\d*\]$')
 
596
        self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')
431
597
 
432
598
    def test_lock_without_email(self):
433
599
        global_config = config.GlobalConfig()
439
605
        ld1.unlock()
440
606
 
441
607
    def test_lock_permission(self):
442
 
        self.requireFeature(features.not_running_as_root)
443
608
        if not osutils.supports_posix_readonly():
444
609
            raise tests.TestSkipped('Cannot induce a permission failure')
445
610
        ld1 = self.get_lock()
446
611
        lock_path = ld1.transport.local_abspath('test_lock')
447
612
        os.mkdir(lock_path)
448
613
        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)
 
614
        self.assertRaises(errors.PermissionDenied, ld1.attempt_lock)