~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: Robert Collins
  • Date: 2009-09-07 03:08:30 UTC
  • mto: This revision was merged to the branch mainline in revision 4690.
  • Revision ID: robertc@robertcollins.net-20090907030830-rf59kt28d550eauj
Milestones language tightning, internal consistency.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for LockDir"""
18
18
 
19
 
from threading import Thread
 
19
from cStringIO import StringIO
 
20
import os
 
21
from threading import Thread, Lock
20
22
import time
21
23
 
 
24
import bzrlib
 
25
from bzrlib import (
 
26
    config,
 
27
    errors,
 
28
    lock,
 
29
    osutils,
 
30
    tests,
 
31
    transport,
 
32
    )
22
33
from bzrlib.errors import (
23
 
        LockBreakMismatch,
24
 
        LockContention, LockError, UnlockableTransport,
25
 
        LockNotHeld, LockBroken
26
 
        )
 
34
    LockBreakMismatch,
 
35
    LockBroken,
 
36
    LockContention,
 
37
    LockError,
 
38
    LockFailed,
 
39
    LockNotHeld,
 
40
    )
27
41
from bzrlib.lockdir import LockDir
28
42
from bzrlib.tests import TestCaseWithTransport
 
43
from bzrlib.trace import note
29
44
 
30
45
# These tests sometimes use threads to test the behaviour of lock files with
31
46
# concurrent actors.  This is not a typical (or necessarily supported) use;
32
47
# they're really meant for guarding between processes.
33
48
 
 
49
# These tests are run on the default transport provided by the test framework
 
50
# (typically a local disk transport).  That can be changed by the --transport
 
51
# option to bzr selftest.  The required properties of the transport
 
52
# implementation are tested separately.  (The main requirement is just that
 
53
# they don't allow overwriting nonempty directories.)
 
54
 
34
55
class TestLockDir(TestCaseWithTransport):
35
56
    """Test LockDir operations"""
36
57
 
 
58
    def logging_report_function(self, fmt, *args):
 
59
        self._logged_reports.append((fmt, args))
 
60
 
 
61
    def setup_log_reporter(self, lock_dir):
 
62
        self._logged_reports = []
 
63
        lock_dir._report_function = self.logging_report_function
 
64
 
37
65
    def test_00_lock_creation(self):
38
66
        """Creation of lock file on a transport"""
39
67
        t = self.get_transport()
50
78
        lf = LockDir(self.get_transport(), 'test_lock')
51
79
        self.assertEqual(lf.peek(), None)
52
80
 
 
81
    def get_lock(self):
 
82
        return LockDir(self.get_transport(), 'test_lock')
 
83
 
 
84
    def test_unlock_after_break_raises(self):
 
85
        ld = self.get_lock()
 
86
        ld2 = self.get_lock()
 
87
        ld.create()
 
88
        ld.attempt_lock()
 
89
        ld2.force_break(ld2.peek())
 
90
        self.assertRaises(LockBroken, ld.unlock)
 
91
 
53
92
    def test_03_readonly_peek(self):
54
93
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
55
94
        self.assertEqual(lf.peek(), None)
58
97
        """Acquire and release a lock"""
59
98
        t = self.get_transport()
60
99
        lf = LockDir(t, 'test_lock')
 
100
        lf.create()
61
101
        lf.attempt_lock()
62
102
        try:
63
103
            self.assertTrue(lf.is_held)
65
105
            lf.unlock()
66
106
            self.assertFalse(lf.is_held)
67
107
 
68
 
    def test_11_lock_readonly_transport(self):
 
108
    def test_11_create_readonly_transport(self):
 
109
        """Fail to create lock on readonly transport"""
 
110
        t = self.get_readonly_transport()
 
111
        lf = LockDir(t, 'test_lock')
 
112
        self.assertRaises(LockFailed, lf.create)
 
113
 
 
114
    def test_12_lock_readonly_transport(self):
69
115
        """Fail to lock on readonly transport"""
70
 
        t = self.get_readonly_transport()
71
 
        lf = LockDir(t, 'test_lock')
72
 
        self.assertRaises(UnlockableTransport, lf.attempt_lock)
 
116
        lf = LockDir(self.get_transport(), 'test_lock')
 
117
        lf.create()
 
118
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
 
119
        self.assertRaises(LockFailed, lf.attempt_lock)
73
120
 
74
121
    def test_20_lock_contested(self):
75
122
        """Contention to get a lock"""
76
123
        t = self.get_transport()
77
124
        lf1 = LockDir(t, 'test_lock')
 
125
        lf1.create()
78
126
        lf1.attempt_lock()
79
127
        lf2 = LockDir(t, 'test_lock')
80
128
        try:
81
 
            # locking is between LockDir instances; aliases within 
 
129
            # locking is between LockDir instances; aliases within
82
130
            # a single process are not detected
83
131
            lf2.attempt_lock()
84
132
            self.fail('Failed to detect lock collision')
92
140
        """Peek at the state of a lock"""
93
141
        t = self.get_transport()
94
142
        lf1 = LockDir(t, 'test_lock')
 
143
        lf1.create()
95
144
        lf1.attempt_lock()
 
145
        self.addCleanup(lf1.unlock)
96
146
        # lock is held, should get some info on it
97
147
        info1 = lf1.peek()
98
148
        self.assertEqual(set(info1.keys()),
108
158
        """Peek over a readonly transport"""
109
159
        t = self.get_transport()
110
160
        lf1 = LockDir(t, 'test_lock')
 
161
        lf1.create()
111
162
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
112
163
        self.assertEqual(lf2.peek(), None)
113
164
        lf1.attempt_lock()
 
165
        self.addCleanup(lf1.unlock)
114
166
        info2 = lf2.peek()
115
167
        self.assertTrue(info2)
116
168
        self.assertEqual(info2['nonce'], lf1.nonce)
117
169
 
118
170
    def test_30_lock_wait_fail(self):
119
171
        """Wait on a lock, then fail
120
 
        
 
172
 
121
173
        We ask to wait up to 400ms; this should fail within at most one
122
174
        second.  (Longer times are more realistic but we don't want the test
123
175
        suite to take too long, and this should do for now.)
124
176
        """
125
177
        t = self.get_transport()
126
178
        lf1 = LockDir(t, 'test_lock')
 
179
        lf1.create()
127
180
        lf2 = LockDir(t, 'test_lock')
 
181
        self.setup_log_reporter(lf2)
128
182
        lf1.attempt_lock()
129
183
        try:
130
184
            before = time.time()
131
185
            self.assertRaises(LockContention, lf2.wait_lock,
132
186
                              timeout=0.4, poll=0.1)
133
187
            after = time.time()
134
 
            self.assertTrue(after - before <= 1.0)
 
188
            # it should only take about 0.4 seconds, but we allow more time in
 
189
            # case the machine is heavily loaded
 
190
            self.assertTrue(after - before <= 8.0,
 
191
                    "took %f seconds to detect lock contention" % (after - before))
135
192
        finally:
136
193
            lf1.unlock()
 
194
        lock_base = lf2.transport.abspath(lf2.path)
 
195
        self.assertEqual(1, len(self._logged_reports))
 
196
        lock_url = lf2.transport.abspath(lf2.path)
 
197
        self.assertEqual('%s %s\n'
 
198
                         '%s\n%s\n'
 
199
                         'Will continue to try until %s, unless '
 
200
                         'you press Ctrl-C\n'
 
201
                         'If you\'re sure that it\'s not being '
 
202
                         'modified, use bzr break-lock %s',
 
203
                         self._logged_reports[0][0])
 
204
        args = self._logged_reports[0][1]
 
205
        self.assertEqual('Unable to obtain', args[0])
 
206
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
207
        self.assertStartsWith(args[2], 'held by ')
 
208
        self.assertStartsWith(args[3], 'locked ')
 
209
        self.assertEndsWith(args[3], ' ago')
 
210
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
137
211
 
138
212
    def test_31_lock_wait_easy(self):
139
213
        """Succeed when waiting on a lock with no contention.
140
214
        """
141
215
        t = self.get_transport()
142
 
        lf2 = LockDir(t, 'test_lock')
 
216
        lf1 = LockDir(t, 'test_lock')
 
217
        lf1.create()
 
218
        self.setup_log_reporter(lf1)
143
219
        try:
144
220
            before = time.time()
145
 
            lf2.wait_lock(timeout=0.4, poll=0.1)
 
221
            lf1.wait_lock(timeout=0.4, poll=0.1)
146
222
            after = time.time()
147
223
            self.assertTrue(after - before <= 1.0)
148
224
        finally:
149
 
            lf2.unlock()
 
225
            lf1.unlock()
 
226
        self.assertEqual([], self._logged_reports)
150
227
 
151
228
    def test_32_lock_wait_succeed(self):
152
229
        """Succeed when trying to acquire a lock that gets released
153
230
 
154
 
        One thread holds on a lock and then releases it; another tries to lock it.
 
231
        One thread holds on a lock and then releases it; another
 
232
        tries to lock it.
155
233
        """
 
234
        # This test sometimes fails like this:
 
235
        # Traceback (most recent call last):
 
236
 
 
237
        #   File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/
 
238
        # test_lockdir.py", line 247, in test_32_lock_wait_succeed
 
239
        #     self.assertEqual(1, len(self._logged_reports))
 
240
        # AssertionError: not equal:
 
241
        # a = 1
 
242
        # b = 0
 
243
        raise tests.TestSkipped("Test fails intermittently")
156
244
        t = self.get_transport()
157
245
        lf1 = LockDir(t, 'test_lock')
 
246
        lf1.create()
158
247
        lf1.attempt_lock()
159
248
 
160
249
        def wait_and_unlock():
164
253
        unlocker.start()
165
254
        try:
166
255
            lf2 = LockDir(t, 'test_lock')
 
256
            self.setup_log_reporter(lf2)
167
257
            before = time.time()
168
258
            # wait and then lock
169
259
            lf2.wait_lock(timeout=0.4, poll=0.1)
172
262
        finally:
173
263
            unlocker.join()
174
264
 
175
 
    def test_33_wait(self):
176
 
        """Succeed when waiting on a lock that gets released
 
265
        # There should be only 1 report, even though it should have to
 
266
        # wait for a while
 
267
        lock_base = lf2.transport.abspath(lf2.path)
 
268
        self.assertEqual(1, len(self._logged_reports))
 
269
        self.assertEqual('%s %s\n'
 
270
                         '%s\n%s\n'
 
271
                         'Will continue to try until %s\n',
 
272
                         self._logged_reports[0][0])
 
273
        args = self._logged_reports[0][1]
 
274
        self.assertEqual('Unable to obtain', args[0])
 
275
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
276
        self.assertStartsWith(args[2], 'held by ')
 
277
        self.assertStartsWith(args[3], 'locked ')
 
278
        self.assertEndsWith(args[3], ' ago')
 
279
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
177
280
 
178
 
        The difference from test_32_lock_wait_succeed is that the second 
179
 
        caller does not actually acquire the lock, but just waits for it
180
 
        to be released.  This is done over a readonly transport.
181
 
        """
 
281
    def test_34_lock_write_waits(self):
 
282
        """LockDir.lock_write() will wait for the lock."""
 
283
        # the test suite sets the default to 0 to make deadlocks fail fast.
 
284
        # change it for this test, as we want to try a manual deadlock.
 
285
        raise tests.TestSkipped('Timing-sensitive test')
 
286
        bzrlib.lockdir._DEFAULT_TIMEOUT_SECONDS = 300
182
287
        t = self.get_transport()
183
288
        lf1 = LockDir(t, 'test_lock')
 
289
        lf1.create()
184
290
        lf1.attempt_lock()
185
291
 
186
292
        def wait_and_unlock():
189
295
        unlocker = Thread(target=wait_and_unlock)
190
296
        unlocker.start()
191
297
        try:
192
 
            lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
 
298
            lf2 = LockDir(t, 'test_lock')
 
299
            self.setup_log_reporter(lf2)
193
300
            before = time.time()
194
 
            # wait but don't lock
195
 
            lf2.wait(timeout=0.4, poll=0.1)
 
301
            # wait and then lock
 
302
            lf2.lock_write()
196
303
            after = time.time()
197
 
            self.assertTrue(after - before <= 1.0)
198
 
        finally:
199
 
            unlocker.join()
 
304
        finally:
 
305
            unlocker.join()
 
306
 
 
307
        # There should be only 1 report, even though it should have to
 
308
        # wait for a while
 
309
        lock_base = lf2.transport.abspath(lf2.path)
 
310
        self.assertEqual(1, len(self._logged_reports))
 
311
        self.assertEqual('%s %s\n'
 
312
                         '%s\n%s\n'
 
313
                         'Will continue to try until %s\n',
 
314
                         self._logged_reports[0][0])
 
315
        args = self._logged_reports[0][1]
 
316
        self.assertEqual('Unable to obtain', args[0])
 
317
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
318
        self.assertStartsWith(args[2], 'held by ')
 
319
        self.assertStartsWith(args[3], 'locked ')
 
320
        self.assertEndsWith(args[3], ' ago')
 
321
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
322
 
 
323
    def test_35_wait_lock_changing(self):
 
324
        """LockDir.wait_lock() will report if the lock changes underneath.
 
325
 
 
326
        This is the stages we want to happen:
 
327
 
 
328
        0) Synchronization locks are created and locked.
 
329
        1) Lock1 obtains the lockdir, and releases the 'check' lock.
 
330
        2) Lock2 grabs the 'check' lock, and checks the lockdir.
 
331
           It sees the lockdir is already acquired, reports the fact,
 
332
           and unsets the 'checked' lock.
 
333
        3) Thread1 blocks on acquiring the 'checked' lock, and then tells
 
334
           Lock1 to release and acquire the lockdir. This resets the 'check'
 
335
           lock.
 
336
        4) Lock2 acquires the 'check' lock, and checks again. It notices
 
337
           that the holder of the lock has changed, and so reports a new
 
338
           lock holder.
 
339
        5) Thread1 blocks on the 'checked' lock, this time, it completely
 
340
           unlocks the lockdir, allowing Lock2 to acquire the lock.
 
341
        """
 
342
 
 
343
        raise tests.KnownFailure(
 
344
            "timing dependency in lock tests (#213182)")
 
345
 
 
346
        wait_to_check_lock = Lock()
 
347
        wait_until_checked_lock = Lock()
 
348
 
 
349
        wait_to_check_lock.acquire()
 
350
        wait_until_checked_lock.acquire()
 
351
        note('locked check and checked locks')
 
352
 
 
353
        class LockDir1(LockDir):
 
354
            """Use the synchronization points for the first lock."""
 
355
 
 
356
            def attempt_lock(self):
 
357
                # Once we have acquired the lock, it is okay for
 
358
                # the other lock to check it
 
359
                try:
 
360
                    return super(LockDir1, self).attempt_lock()
 
361
                finally:
 
362
                    note('lock1: releasing check lock')
 
363
                    wait_to_check_lock.release()
 
364
 
 
365
        class LockDir2(LockDir):
 
366
            """Use the synchronization points for the second lock."""
 
367
 
 
368
            def attempt_lock(self):
 
369
                note('lock2: waiting for check lock')
 
370
                wait_to_check_lock.acquire()
 
371
                note('lock2: acquired check lock')
 
372
                try:
 
373
                    return super(LockDir2, self).attempt_lock()
 
374
                finally:
 
375
                    note('lock2: releasing checked lock')
 
376
                    wait_until_checked_lock.release()
 
377
 
 
378
        t = self.get_transport()
 
379
        lf1 = LockDir1(t, 'test_lock')
 
380
        lf1.create()
 
381
 
 
382
        lf2 = LockDir2(t, 'test_lock')
 
383
        self.setup_log_reporter(lf2)
 
384
 
 
385
        def wait_and_switch():
 
386
            lf1.attempt_lock()
 
387
            # Block until lock2 has had a chance to check
 
388
            note('lock1: waiting 1 for checked lock')
 
389
            wait_until_checked_lock.acquire()
 
390
            note('lock1: acquired for checked lock')
 
391
            note('lock1: released lockdir')
 
392
            lf1.unlock()
 
393
            note('lock1: acquiring lockdir')
 
394
            # Create a new nonce, so the lock looks different.
 
395
            lf1.nonce = osutils.rand_chars(20)
 
396
            lf1.lock_write()
 
397
            note('lock1: acquired lockdir')
 
398
 
 
399
            # Block until lock2 has peeked again
 
400
            note('lock1: waiting 2 for checked lock')
 
401
            wait_until_checked_lock.acquire()
 
402
            note('lock1: acquired for checked lock')
 
403
            # Now unlock, and let lock 2 grab the lock
 
404
            lf1.unlock()
 
405
            wait_to_check_lock.release()
 
406
 
 
407
        unlocker = Thread(target=wait_and_switch)
 
408
        unlocker.start()
 
409
        try:
 
410
            # Wait and play against the other thread
 
411
            lf2.wait_lock(timeout=20.0, poll=0.01)
 
412
        finally:
 
413
            unlocker.join()
 
414
        lf2.unlock()
 
415
 
 
416
        # There should be 2 reports, because the lock changed
 
417
        lock_base = lf2.transport.abspath(lf2.path)
 
418
        self.assertEqual(2, len(self._logged_reports))
 
419
        lock_url = lf2.transport.abspath(lf2.path)
 
420
        self.assertEqual('%s %s\n'
 
421
                         '%s\n%s\n'
 
422
                         'Will continue to try until %s, unless '
 
423
                         'you press Ctrl-C\n'
 
424
                         'If you\'re sure that it\'s not being '
 
425
                         'modified, use bzr break-lock %s',
 
426
                         self._logged_reports[0][0])
 
427
        args = self._logged_reports[0][1]
 
428
        self.assertEqual('Unable to obtain', args[0])
 
429
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
430
        self.assertStartsWith(args[2], 'held by ')
 
431
        self.assertStartsWith(args[3], 'locked ')
 
432
        self.assertEndsWith(args[3], ' ago')
 
433
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
434
 
 
435
        self.assertEqual('%s %s\n'
 
436
                         '%s\n%s\n'
 
437
                         'Will continue to try until %s, unless '
 
438
                         'you press Ctrl-C\n'
 
439
                         'If you\'re sure that it\'s not being '
 
440
                         'modified, use bzr break-lock %s',
 
441
                         self._logged_reports[1][0])
 
442
        args = self._logged_reports[1][1]
 
443
        self.assertEqual('Lock owner changed for', args[0])
 
444
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
445
        self.assertStartsWith(args[2], 'held by ')
 
446
        self.assertStartsWith(args[3], 'locked ')
 
447
        self.assertEndsWith(args[3], ' ago')
 
448
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
200
449
 
201
450
    def test_40_confirm_easy(self):
202
451
        """Confirm a lock that's already held"""
203
452
        t = self.get_transport()
204
453
        lf1 = LockDir(t, 'test_lock')
 
454
        lf1.create()
205
455
        lf1.attempt_lock()
 
456
        self.addCleanup(lf1.unlock)
206
457
        lf1.confirm()
207
458
 
208
459
    def test_41_confirm_not_held(self):
209
460
        """Confirm a lock that's already held"""
210
461
        t = self.get_transport()
211
462
        lf1 = LockDir(t, 'test_lock')
 
463
        lf1.create()
212
464
        self.assertRaises(LockNotHeld, lf1.confirm)
213
465
 
214
466
    def test_42_confirm_broken_manually(self):
215
467
        """Confirm a lock broken by hand"""
216
468
        t = self.get_transport()
217
469
        lf1 = LockDir(t, 'test_lock')
 
470
        lf1.create()
218
471
        lf1.attempt_lock()
219
472
        t.move('test_lock', 'lock_gone_now')
220
473
        self.assertRaises(LockBroken, lf1.confirm)
 
474
        # Clean up
 
475
        t.move('lock_gone_now', 'test_lock')
 
476
        lf1.unlock()
221
477
 
222
478
    def test_43_break(self):
223
479
        """Break a lock whose caller has forgotten it"""
224
480
        t = self.get_transport()
225
481
        lf1 = LockDir(t, 'test_lock')
 
482
        lf1.create()
226
483
        lf1.attempt_lock()
227
484
        # we incorrectly discard the lock object without unlocking it
228
485
        del lf1
233
490
        lf2.force_break(holder_info)
234
491
        # now we should be able to take it
235
492
        lf2.attempt_lock()
 
493
        self.addCleanup(lf2.unlock)
236
494
        lf2.confirm()
237
495
 
238
496
    def test_44_break_already_released(self):
239
497
        """Lock break races with regular release"""
240
498
        t = self.get_transport()
241
499
        lf1 = LockDir(t, 'test_lock')
 
500
        lf1.create()
242
501
        lf1.attempt_lock()
243
502
        # someone else sees it's still locked
244
503
        lf2 = LockDir(t, 'test_lock')
249
508
        lf2.force_break(holder_info)
250
509
        # now we should be able to take it
251
510
        lf2.attempt_lock()
 
511
        self.addCleanup(lf2.unlock)
252
512
        lf2.confirm()
253
513
 
254
514
    def test_45_break_mismatch(self):
255
515
        """Lock break races with someone else acquiring it"""
256
516
        t = self.get_transport()
257
517
        lf1 = LockDir(t, 'test_lock')
 
518
        lf1.create()
258
519
        lf1.attempt_lock()
259
520
        # someone else sees it's still locked
260
521
        lf2 = LockDir(t, 'test_lock')
267
528
        self.assertRaises(LockBreakMismatch, lf2.force_break,
268
529
                          holder_info)
269
530
        lf3.unlock()
 
531
 
 
532
    def test_46_fake_read_lock(self):
 
533
        t = self.get_transport()
 
534
        lf1 = LockDir(t, 'test_lock')
 
535
        lf1.create()
 
536
        lf1.lock_read()
 
537
        lf1.unlock()
 
538
 
 
539
    def test_50_lockdir_representation(self):
 
540
        """Check the on-disk representation of LockDirs is as expected.
 
541
 
 
542
        There should always be a top-level directory named by the lock.
 
543
        When the lock is held, there should be a lockname/held directory
 
544
        containing an info file.
 
545
        """
 
546
        t = self.get_transport()
 
547
        lf1 = LockDir(t, 'test_lock')
 
548
        lf1.create()
 
549
        self.assertTrue(t.has('test_lock'))
 
550
        lf1.lock_write()
 
551
        self.assertTrue(t.has('test_lock/held/info'))
 
552
        lf1.unlock()
 
553
        self.assertFalse(t.has('test_lock/held/info'))
 
554
 
 
555
    def test_break_lock(self):
 
556
        # the ui based break_lock routine should Just Work (tm)
 
557
        ld1 = self.get_lock()
 
558
        ld2 = self.get_lock()
 
559
        ld1.create()
 
560
        ld1.lock_write()
 
561
        # do this without IO redirection to ensure it doesn't prompt.
 
562
        self.assertRaises(AssertionError, ld1.break_lock)
 
563
        orig_factory = bzrlib.ui.ui_factory
 
564
        bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
 
565
        try:
 
566
            ld2.break_lock()
 
567
            self.assertRaises(LockBroken, ld1.unlock)
 
568
        finally:
 
569
            bzrlib.ui.ui_factory = orig_factory
 
570
 
 
571
    def test_create_missing_base_directory(self):
 
572
        """If LockDir.path doesn't exist, it can be created
 
573
 
 
574
        Some people manually remove the entire lock/ directory trying
 
575
        to unlock a stuck repository/branch/etc. Rather than failing
 
576
        after that, just create the lock directory when needed.
 
577
        """
 
578
        t = self.get_transport()
 
579
        lf1 = LockDir(t, 'test_lock')
 
580
 
 
581
        lf1.create()
 
582
        self.failUnless(t.has('test_lock'))
 
583
 
 
584
        t.rmdir('test_lock')
 
585
        self.failIf(t.has('test_lock'))
 
586
 
 
587
        # This will create 'test_lock' if it needs to
 
588
        lf1.lock_write()
 
589
        self.failUnless(t.has('test_lock'))
 
590
        self.failUnless(t.has('test_lock/held/info'))
 
591
 
 
592
        lf1.unlock()
 
593
        self.failIf(t.has('test_lock/held/info'))
 
594
 
 
595
    def test__format_lock_info(self):
 
596
        ld1 = self.get_lock()
 
597
        ld1.create()
 
598
        ld1.lock_write()
 
599
        try:
 
600
            info_list = ld1._format_lock_info(ld1.peek())
 
601
        finally:
 
602
            ld1.unlock()
 
603
        self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
 
604
                         info_list[0])
 
605
        self.assertContainsRe(info_list[1],
 
606
                              r'^held by .* on host .* \[process #\d*\]$')
 
607
        self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')
 
608
 
 
609
    def test_lock_without_email(self):
 
610
        global_config = config.GlobalConfig()
 
611
        # Intentionally has no email address
 
612
        global_config.set_user_option('email', 'User Identity')
 
613
        ld1 = self.get_lock()
 
614
        ld1.create()
 
615
        ld1.lock_write()
 
616
        ld1.unlock()
 
617
 
 
618
    def test_lock_permission(self):
 
619
        if not osutils.supports_posix_readonly():
 
620
            raise tests.TestSkipped('Cannot induce a permission failure')
 
621
        ld1 = self.get_lock()
 
622
        lock_path = ld1.transport.local_abspath('test_lock')
 
623
        os.mkdir(lock_path)
 
624
        osutils.make_readonly(lock_path)
 
625
        self.assertRaises(errors.LockFailed, ld1.attempt_lock)
 
626
 
 
627
    def test_lock_by_token(self):
 
628
        ld1 = self.get_lock()
 
629
        token = ld1.lock_write()
 
630
        self.addCleanup(ld1.unlock)
 
631
        self.assertNotEqual(None, token)
 
632
        ld2 = self.get_lock()
 
633
        t2 = ld2.lock_write(token)
 
634
        self.addCleanup(ld2.unlock)
 
635
        self.assertEqual(token, t2)
 
636
 
 
637
    def test_lock_with_buggy_rename(self):
 
638
        # test that lock acquisition handles servers which pretend they
 
639
        # renamed correctly but that actually fail
 
640
        t = transport.get_transport('brokenrename+' + self.get_url())
 
641
        ld1 = LockDir(t, 'test_lock')
 
642
        ld1.create()
 
643
        ld1.attempt_lock()
 
644
        ld2 = LockDir(t, 'test_lock')
 
645
        # we should fail to lock
 
646
        e = self.assertRaises(errors.LockContention, ld2.attempt_lock)
 
647
        # now the original caller should succeed in unlocking
 
648
        ld1.unlock()
 
649
        # and there should be nothing left over
 
650
        self.assertEquals([], t.list_dir('test_lock'))
 
651
 
 
652
    def test_failed_lock_leaves_no_trash(self):
 
653
        # if we fail to acquire the lock, we don't leave pending directories
 
654
        # behind -- https://bugs.launchpad.net/bzr/+bug/109169
 
655
        ld1 = self.get_lock()
 
656
        ld2 = self.get_lock()
 
657
        # should be nothing before we start
 
658
        ld1.create()
 
659
        t = self.get_transport().clone('test_lock')
 
660
        def check_dir(a):
 
661
            self.assertEquals(a, t.list_dir('.'))
 
662
        check_dir([])
 
663
        # when held, that's all we see
 
664
        ld1.attempt_lock()
 
665
        self.addCleanup(ld1.unlock)
 
666
        check_dir(['held'])
 
667
        # second guy should fail
 
668
        self.assertRaises(errors.LockContention, ld2.attempt_lock)
 
669
        # no kibble
 
670
        check_dir(['held'])
 
671
 
 
672
 
 
673
class TestLockDirHooks(TestCaseWithTransport):
 
674
 
 
675
    def setUp(self):
 
676
        super(TestLockDirHooks, self).setUp()
 
677
        self._calls = []
 
678
 
 
679
    def get_lock(self):
 
680
        return LockDir(self.get_transport(), 'test_lock')
 
681
 
 
682
    def record_hook(self, result):
 
683
        self._calls.append(result)
 
684
 
 
685
    def test_LockDir_acquired_success(self):
 
686
        # the LockDir.lock_acquired hook fires when a lock is acquired.
 
687
        LockDir.hooks.install_named_hook('lock_acquired',
 
688
                                         self.record_hook, 'record_hook')
 
689
        ld = self.get_lock()
 
690
        ld.create()
 
691
        self.assertEqual([], self._calls)
 
692
        result = ld.attempt_lock()
 
693
        lock_path = ld.transport.abspath(ld.path)
 
694
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
 
695
        ld.unlock()
 
696
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
 
697
 
 
698
    def test_LockDir_acquired_fail(self):
 
699
        # the LockDir.lock_acquired hook does not fire on failure.
 
700
        ld = self.get_lock()
 
701
        ld.create()
 
702
        ld2 = self.get_lock()
 
703
        ld2.attempt_lock()
 
704
        # install a lock hook now, when the disk lock is locked
 
705
        LockDir.hooks.install_named_hook('lock_acquired',
 
706
                                         self.record_hook, 'record_hook')
 
707
        self.assertRaises(errors.LockContention, ld.attempt_lock)
 
708
        self.assertEqual([], self._calls)
 
709
        ld2.unlock()
 
710
        self.assertEqual([], self._calls)
 
711
 
 
712
    def test_LockDir_released_success(self):
 
713
        # the LockDir.lock_released hook fires when a lock is acquired.
 
714
        LockDir.hooks.install_named_hook('lock_released',
 
715
                                         self.record_hook, 'record_hook')
 
716
        ld = self.get_lock()
 
717
        ld.create()
 
718
        self.assertEqual([], self._calls)
 
719
        result = ld.attempt_lock()
 
720
        self.assertEqual([], self._calls)
 
721
        ld.unlock()
 
722
        lock_path = ld.transport.abspath(ld.path)
 
723
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
 
724
 
 
725
    def test_LockDir_released_fail(self):
 
726
        # the LockDir.lock_released hook does not fire on failure.
 
727
        ld = self.get_lock()
 
728
        ld.create()
 
729
        ld2 = self.get_lock()
 
730
        ld.attempt_lock()
 
731
        ld2.force_break(ld2.peek())
 
732
        LockDir.hooks.install_named_hook('lock_released',
 
733
                                         self.record_hook, 'record_hook')
 
734
        self.assertRaises(LockBroken, ld.unlock)
 
735
        self.assertEqual([], self._calls)
 
736
 
 
737
    def test_LockDir_broken_success(self):
 
738
        # the LockDir.lock_broken hook fires when a lock is broken.
 
739
        ld = self.get_lock()
 
740
        ld.create()
 
741
        ld2 = self.get_lock()
 
742
        result = ld.attempt_lock()
 
743
        LockDir.hooks.install_named_hook('lock_broken',
 
744
                                         self.record_hook, 'record_hook')
 
745
        ld2.force_break(ld2.peek())
 
746
        lock_path = ld.transport.abspath(ld.path)
 
747
        self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
 
748
 
 
749
    def test_LockDir_broken_failure(self):
 
750
        # the LockDir.lock_broken hook does not fires when a lock is already
 
751
        # released.
 
752
        ld = self.get_lock()
 
753
        ld.create()
 
754
        ld2 = self.get_lock()
 
755
        result = ld.attempt_lock()
 
756
        holder_info = ld2.peek()
 
757
        ld.unlock()
 
758
        LockDir.hooks.install_named_hook('lock_broken',
 
759
                                         self.record_hook, 'record_hook')
 
760
        ld2.force_break(holder_info)
 
761
        lock_path = ld.transport.abspath(ld.path)
 
762
        self.assertEqual([], self._calls)