~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: John Arbash Meinel
  • Date: 2009-10-21 03:49:03 UTC
  • mto: This revision was merged to the branch mainline in revision 4761.
  • Revision ID: john@arbash-meinel.com-20091021034903-0ut5l1rxnlrdjdi4
Update _static_tuple_py.py with the same concatenation behavior
And add some tests that it works like we want.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
"""Tests for LockDir"""
 
18
 
 
19
from cStringIO import StringIO
 
20
import os
 
21
from threading import Thread, Lock
 
22
import time
 
23
 
 
24
import bzrlib
 
25
from bzrlib import (
 
26
    config,
 
27
    errors,
 
28
    lock,
 
29
    osutils,
 
30
    tests,
 
31
    transport,
 
32
    )
 
33
from bzrlib.errors import (
 
34
    LockBreakMismatch,
 
35
    LockBroken,
 
36
    LockContention,
 
37
    LockError,
 
38
    LockFailed,
 
39
    LockNotHeld,
 
40
    )
 
41
from bzrlib.lockdir import LockDir
 
42
from bzrlib.tests import TestCaseWithTransport
 
43
from bzrlib.trace import note
 
44
 
 
45
# These tests sometimes use threads to test the behaviour of lock files with
 
46
# concurrent actors.  This is not a typical (or necessarily supported) use;
 
47
# they're really meant for guarding between processes.
 
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
 
 
55
class TestLockDir(TestCaseWithTransport):
 
56
    """Test LockDir operations"""
 
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
 
 
65
    def test_00_lock_creation(self):
 
66
        """Creation of lock file on a transport"""
 
67
        t = self.get_transport()
 
68
        lf = LockDir(t, 'test_lock')
 
69
        self.assertFalse(lf.is_held)
 
70
 
 
71
    def test_01_lock_repr(self):
 
72
        """Lock string representation"""
 
73
        lf = LockDir(self.get_transport(), 'test_lock')
 
74
        r = repr(lf)
 
75
        self.assertContainsRe(r, r'^LockDir\(.*/test_lock\)$')
 
76
 
 
77
    def test_02_unlocked_peek(self):
 
78
        lf = LockDir(self.get_transport(), 'test_lock')
 
79
        self.assertEqual(lf.peek(), None)
 
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
 
 
92
    def test_03_readonly_peek(self):
 
93
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
 
94
        self.assertEqual(lf.peek(), None)
 
95
 
 
96
    def test_10_lock_uncontested(self):
 
97
        """Acquire and release a lock"""
 
98
        t = self.get_transport()
 
99
        lf = LockDir(t, 'test_lock')
 
100
        lf.create()
 
101
        lf.attempt_lock()
 
102
        try:
 
103
            self.assertTrue(lf.is_held)
 
104
        finally:
 
105
            lf.unlock()
 
106
            self.assertFalse(lf.is_held)
 
107
 
 
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):
 
115
        """Fail to lock on readonly transport"""
 
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)
 
120
 
 
121
    def test_20_lock_contested(self):
 
122
        """Contention to get a lock"""
 
123
        t = self.get_transport()
 
124
        lf1 = LockDir(t, 'test_lock')
 
125
        lf1.create()
 
126
        lf1.attempt_lock()
 
127
        lf2 = LockDir(t, 'test_lock')
 
128
        try:
 
129
            # locking is between LockDir instances; aliases within
 
130
            # a single process are not detected
 
131
            lf2.attempt_lock()
 
132
            self.fail('Failed to detect lock collision')
 
133
        except LockContention, e:
 
134
            self.assertEqual(e.lock, lf2)
 
135
            self.assertContainsRe(str(e),
 
136
                    r'^Could not acquire.*test_lock.*$')
 
137
        lf1.unlock()
 
138
 
 
139
    def test_20_lock_peek(self):
 
140
        """Peek at the state of a lock"""
 
141
        t = self.get_transport()
 
142
        lf1 = LockDir(t, 'test_lock')
 
143
        lf1.create()
 
144
        lf1.attempt_lock()
 
145
        self.addCleanup(lf1.unlock)
 
146
        # lock is held, should get some info on it
 
147
        info1 = lf1.peek()
 
148
        self.assertEqual(set(info1.keys()),
 
149
                         set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
 
150
        # should get the same info if we look at it through a different
 
151
        # instance
 
152
        info2 = LockDir(t, 'test_lock').peek()
 
153
        self.assertEqual(info1, info2)
 
154
        # locks which are never used should be not-held
 
155
        self.assertEqual(LockDir(t, 'other_lock').peek(), None)
 
156
 
 
157
    def test_21_peek_readonly(self):
 
158
        """Peek over a readonly transport"""
 
159
        t = self.get_transport()
 
160
        lf1 = LockDir(t, 'test_lock')
 
161
        lf1.create()
 
162
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
 
163
        self.assertEqual(lf2.peek(), None)
 
164
        lf1.attempt_lock()
 
165
        self.addCleanup(lf1.unlock)
 
166
        info2 = lf2.peek()
 
167
        self.assertTrue(info2)
 
168
        self.assertEqual(info2['nonce'], lf1.nonce)
 
169
 
 
170
    def test_30_lock_wait_fail(self):
 
171
        """Wait on a lock, then fail
 
172
 
 
173
        We ask to wait up to 400ms; this should fail within at most one
 
174
        second.  (Longer times are more realistic but we don't want the test
 
175
        suite to take too long, and this should do for now.)
 
176
        """
 
177
        t = self.get_transport()
 
178
        lf1 = LockDir(t, 'test_lock')
 
179
        lf1.create()
 
180
        lf2 = LockDir(t, 'test_lock')
 
181
        self.setup_log_reporter(lf2)
 
182
        lf1.attempt_lock()
 
183
        try:
 
184
            before = time.time()
 
185
            self.assertRaises(LockContention, lf2.wait_lock,
 
186
                              timeout=0.4, poll=0.1)
 
187
            after = time.time()
 
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))
 
192
        finally:
 
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')
 
211
 
 
212
    def test_31_lock_wait_easy(self):
 
213
        """Succeed when waiting on a lock with no contention.
 
214
        """
 
215
        t = self.get_transport()
 
216
        lf1 = LockDir(t, 'test_lock')
 
217
        lf1.create()
 
218
        self.setup_log_reporter(lf1)
 
219
        try:
 
220
            before = time.time()
 
221
            lf1.wait_lock(timeout=0.4, poll=0.1)
 
222
            after = time.time()
 
223
            self.assertTrue(after - before <= 1.0)
 
224
        finally:
 
225
            lf1.unlock()
 
226
        self.assertEqual([], self._logged_reports)
 
227
 
 
228
    def test_32_lock_wait_succeed(self):
 
229
        """Succeed when trying to acquire a lock that gets released
 
230
 
 
231
        One thread holds on a lock and then releases it; another
 
232
        tries to lock it.
 
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")
 
244
        t = self.get_transport()
 
245
        lf1 = LockDir(t, 'test_lock')
 
246
        lf1.create()
 
247
        lf1.attempt_lock()
 
248
 
 
249
        def wait_and_unlock():
 
250
            time.sleep(0.1)
 
251
            lf1.unlock()
 
252
        unlocker = Thread(target=wait_and_unlock)
 
253
        unlocker.start()
 
254
        try:
 
255
            lf2 = LockDir(t, 'test_lock')
 
256
            self.setup_log_reporter(lf2)
 
257
            before = time.time()
 
258
            # wait and then lock
 
259
            lf2.wait_lock(timeout=0.4, poll=0.1)
 
260
            after = time.time()
 
261
            self.assertTrue(after - before <= 1.0)
 
262
        finally:
 
263
            unlocker.join()
 
264
 
 
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')
 
280
 
 
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
 
287
        t = self.get_transport()
 
288
        lf1 = LockDir(t, 'test_lock')
 
289
        lf1.create()
 
290
        lf1.attempt_lock()
 
291
 
 
292
        def wait_and_unlock():
 
293
            time.sleep(0.1)
 
294
            lf1.unlock()
 
295
        unlocker = Thread(target=wait_and_unlock)
 
296
        unlocker.start()
 
297
        try:
 
298
            lf2 = LockDir(t, 'test_lock')
 
299
            self.setup_log_reporter(lf2)
 
300
            before = time.time()
 
301
            # wait and then lock
 
302
            lf2.lock_write()
 
303
            after = time.time()
 
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')
 
449
 
 
450
    def test_40_confirm_easy(self):
 
451
        """Confirm a lock that's already held"""
 
452
        t = self.get_transport()
 
453
        lf1 = LockDir(t, 'test_lock')
 
454
        lf1.create()
 
455
        lf1.attempt_lock()
 
456
        self.addCleanup(lf1.unlock)
 
457
        lf1.confirm()
 
458
 
 
459
    def test_41_confirm_not_held(self):
 
460
        """Confirm a lock that's already held"""
 
461
        t = self.get_transport()
 
462
        lf1 = LockDir(t, 'test_lock')
 
463
        lf1.create()
 
464
        self.assertRaises(LockNotHeld, lf1.confirm)
 
465
 
 
466
    def test_42_confirm_broken_manually(self):
 
467
        """Confirm a lock broken by hand"""
 
468
        t = self.get_transport()
 
469
        lf1 = LockDir(t, 'test_lock')
 
470
        lf1.create()
 
471
        lf1.attempt_lock()
 
472
        t.move('test_lock', 'lock_gone_now')
 
473
        self.assertRaises(LockBroken, lf1.confirm)
 
474
        # Clean up
 
475
        t.move('lock_gone_now', 'test_lock')
 
476
        lf1.unlock()
 
477
 
 
478
    def test_43_break(self):
 
479
        """Break a lock whose caller has forgotten it"""
 
480
        t = self.get_transport()
 
481
        lf1 = LockDir(t, 'test_lock')
 
482
        lf1.create()
 
483
        lf1.attempt_lock()
 
484
        # we incorrectly discard the lock object without unlocking it
 
485
        del lf1
 
486
        # someone else sees it's still locked
 
487
        lf2 = LockDir(t, 'test_lock')
 
488
        holder_info = lf2.peek()
 
489
        self.assertTrue(holder_info)
 
490
        lf2.force_break(holder_info)
 
491
        # now we should be able to take it
 
492
        lf2.attempt_lock()
 
493
        self.addCleanup(lf2.unlock)
 
494
        lf2.confirm()
 
495
 
 
496
    def test_44_break_already_released(self):
 
497
        """Lock break races with regular release"""
 
498
        t = self.get_transport()
 
499
        lf1 = LockDir(t, 'test_lock')
 
500
        lf1.create()
 
501
        lf1.attempt_lock()
 
502
        # someone else sees it's still locked
 
503
        lf2 = LockDir(t, 'test_lock')
 
504
        holder_info = lf2.peek()
 
505
        # in the interim the lock is released
 
506
        lf1.unlock()
 
507
        # break should succeed
 
508
        lf2.force_break(holder_info)
 
509
        # now we should be able to take it
 
510
        lf2.attempt_lock()
 
511
        self.addCleanup(lf2.unlock)
 
512
        lf2.confirm()
 
513
 
 
514
    def test_45_break_mismatch(self):
 
515
        """Lock break races with someone else acquiring it"""
 
516
        t = self.get_transport()
 
517
        lf1 = LockDir(t, 'test_lock')
 
518
        lf1.create()
 
519
        lf1.attempt_lock()
 
520
        # someone else sees it's still locked
 
521
        lf2 = LockDir(t, 'test_lock')
 
522
        holder_info = lf2.peek()
 
523
        # in the interim the lock is released
 
524
        lf1.unlock()
 
525
        lf3 = LockDir(t, 'test_lock')
 
526
        lf3.attempt_lock()
 
527
        # break should now *fail*
 
528
        self.assertRaises(LockBreakMismatch, lf2.force_break,
 
529
                          holder_info)
 
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)