~bzr-pqm/bzr/bzr.dev

1553.5.12 by Martin Pool
New LockDir locking mechanism
1
# Copyright (C) 2006 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1553.5.12 by Martin Pool
New LockDir locking mechanism
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1553.5.12 by Martin Pool
New LockDir locking mechanism
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1553.5.12 by Martin Pool
New LockDir locking mechanism
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tests for LockDir"""
18
1687.1.5 by Robert Collins
Add break_lock utility function to LockDir.
19
from cStringIO import StringIO
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
20
from threading import Thread, Lock
1553.5.12 by Martin Pool
New LockDir locking mechanism
21
import time
22
1687.1.5 by Robert Collins
Add break_lock utility function to LockDir.
23
import bzrlib
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
24
from bzrlib import (
25
    osutils,
26
    )
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
27
from bzrlib.errors import (
28
        LockBreakMismatch,
29
        LockContention, LockError, UnlockableTransport,
1553.5.23 by Martin Pool
Start LockDir.confirm method and LockBroken exception
30
        LockNotHeld, LockBroken
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
31
        )
1957.1.2 by John Arbash Meinel
Switch the default from instantly aborting, to waiting as long as 1 minute (down from 5 minutes)
32
from bzrlib.lockdir import LockDir, _DEFAULT_TIMEOUT_SECONDS
1553.5.33 by Martin Pool
LockDir review comment fixes
33
from bzrlib.tests import TestCaseWithTransport
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
34
from bzrlib.trace import note
1553.5.12 by Martin Pool
New LockDir locking mechanism
35
36
# These tests sometimes use threads to test the behaviour of lock files with
37
# concurrent actors.  This is not a typical (or necessarily supported) use;
38
# they're really meant for guarding between processes.
39
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
40
# These tests are run on the default transport provided by the test framework
41
# (typically a local disk transport).  That can be changed by the --transport
42
# option to bzr selftest.  The required properties of the transport
43
# implementation are tested separately.  (The main requirement is just that
44
# they don't allow overwriting nonempty directories.)
45
1553.5.12 by Martin Pool
New LockDir locking mechanism
46
class TestLockDir(TestCaseWithTransport):
47
    """Test LockDir operations"""
48
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
49
    def logging_report_function(self, fmt, *args):
50
        self._logged_reports.append((fmt, args))
51
52
    def setup_log_reporter(self, lock_dir):
53
        self._logged_reports = []
54
        lock_dir._report_function = self.logging_report_function
55
1553.5.12 by Martin Pool
New LockDir locking mechanism
56
    def test_00_lock_creation(self):
57
        """Creation of lock file on a transport"""
58
        t = self.get_transport()
59
        lf = LockDir(t, 'test_lock')
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
60
        self.assertFalse(lf.is_held)
1553.5.12 by Martin Pool
New LockDir locking mechanism
61
62
    def test_01_lock_repr(self):
63
        """Lock string representation"""
64
        lf = LockDir(self.get_transport(), 'test_lock')
65
        r = repr(lf)
66
        self.assertContainsRe(r, r'^LockDir\(.*/test_lock\)$')
67
68
    def test_02_unlocked_peek(self):
69
        lf = LockDir(self.get_transport(), 'test_lock')
70
        self.assertEqual(lf.peek(), None)
71
1687.1.3 by Robert Collins
Make LockDir.unlock check the lock is still intact.
72
    def get_lock(self):
73
        return LockDir(self.get_transport(), 'test_lock')
74
75
    def test_unlock_after_break_raises(self):
76
        ld = self.get_lock()
77
        ld2 = self.get_lock()
78
        ld.create()
79
        ld.attempt_lock()
80
        ld2.force_break(ld2.peek())
81
        self.assertRaises(LockBroken, ld.unlock)
82
1553.5.12 by Martin Pool
New LockDir locking mechanism
83
    def test_03_readonly_peek(self):
84
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
85
        self.assertEqual(lf.peek(), None)
86
87
    def test_10_lock_uncontested(self):
88
        """Acquire and release a lock"""
89
        t = self.get_transport()
90
        lf = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
91
        lf.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
92
        lf.attempt_lock()
93
        try:
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
94
            self.assertTrue(lf.is_held)
1553.5.12 by Martin Pool
New LockDir locking mechanism
95
        finally:
96
            lf.unlock()
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
97
            self.assertFalse(lf.is_held)
1553.5.12 by Martin Pool
New LockDir locking mechanism
98
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
99
    def test_11_create_readonly_transport(self):
100
        """Fail to create lock on readonly transport"""
101
        t = self.get_readonly_transport()
102
        lf = LockDir(t, 'test_lock')
103
        self.assertRaises(UnlockableTransport, lf.create)
104
105
    def test_12_lock_readonly_transport(self):
1553.5.12 by Martin Pool
New LockDir locking mechanism
106
        """Fail to lock on readonly transport"""
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
107
        lf = LockDir(self.get_transport(), 'test_lock')
108
        lf.create()
109
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
1553.5.12 by Martin Pool
New LockDir locking mechanism
110
        self.assertRaises(UnlockableTransport, lf.attempt_lock)
111
112
    def test_20_lock_contested(self):
113
        """Contention to get a lock"""
114
        t = self.get_transport()
115
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
116
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
117
        lf1.attempt_lock()
118
        lf2 = LockDir(t, 'test_lock')
119
        try:
120
            # locking is between LockDir instances; aliases within 
121
            # a single process are not detected
122
            lf2.attempt_lock()
123
            self.fail('Failed to detect lock collision')
124
        except LockContention, e:
125
            self.assertEqual(e.lock, lf2)
126
            self.assertContainsRe(str(e),
127
                    r'^Could not acquire.*test_lock.*$')
128
        lf1.unlock()
129
130
    def test_20_lock_peek(self):
131
        """Peek at the state of a lock"""
132
        t = self.get_transport()
133
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
134
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
135
        lf1.attempt_lock()
136
        # lock is held, should get some info on it
137
        info1 = lf1.peek()
138
        self.assertEqual(set(info1.keys()),
139
                         set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
140
        # should get the same info if we look at it through a different
141
        # instance
142
        info2 = LockDir(t, 'test_lock').peek()
143
        self.assertEqual(info1, info2)
144
        # locks which are never used should be not-held
145
        self.assertEqual(LockDir(t, 'other_lock').peek(), None)
146
147
    def test_21_peek_readonly(self):
148
        """Peek over a readonly transport"""
149
        t = self.get_transport()
150
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
151
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
152
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
153
        self.assertEqual(lf2.peek(), None)
154
        lf1.attempt_lock()
155
        info2 = lf2.peek()
156
        self.assertTrue(info2)
157
        self.assertEqual(info2['nonce'], lf1.nonce)
158
159
    def test_30_lock_wait_fail(self):
160
        """Wait on a lock, then fail
161
        
162
        We ask to wait up to 400ms; this should fail within at most one
163
        second.  (Longer times are more realistic but we don't want the test
164
        suite to take too long, and this should do for now.)
165
        """
166
        t = self.get_transport()
167
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
168
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
169
        lf2 = LockDir(t, 'test_lock')
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
170
        self.setup_log_reporter(lf2)
1553.5.12 by Martin Pool
New LockDir locking mechanism
171
        lf1.attempt_lock()
172
        try:
173
            before = time.time()
174
            self.assertRaises(LockContention, lf2.wait_lock,
175
                              timeout=0.4, poll=0.1)
176
            after = time.time()
1704.2.1 by Martin Pool
Fix time-dependency in LockDir tests -- allow more margin for error in time to detect lock contention
177
            # it should only take about 0.4 seconds, but we allow more time in
178
            # case the machine is heavily loaded
179
            self.assertTrue(after - before <= 8.0, 
180
                    "took %f seconds to detect lock contention" % (after - before))
1553.5.12 by Martin Pool
New LockDir locking mechanism
181
        finally:
182
            lf1.unlock()
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
183
        lock_base = lf2.transport.abspath(lf2.path)
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
184
        self.assertEqual(1, len(self._logged_reports))
1957.1.9 by John Arbash Meinel
Change default timeouts, and report differently the first failure
185
        self.assertEqual('%s %s\n'
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
186
                         '%s\n%s\n'
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
187
                         'Will continue to try until %s\n',
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
188
                         self._logged_reports[0][0])
189
        args = self._logged_reports[0][1]
1957.1.9 by John Arbash Meinel
Change default timeouts, and report differently the first failure
190
        self.assertEqual('Unable to obtain', args[0])
191
        self.assertEqual('lock %s' % (lock_base,), args[1])
192
        self.assertStartsWith(args[2], 'held by ')
193
        self.assertStartsWith(args[3], 'locked ')
194
        self.assertEndsWith(args[3], ' ago')
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
195
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
1553.5.12 by Martin Pool
New LockDir locking mechanism
196
197
    def test_31_lock_wait_easy(self):
198
        """Succeed when waiting on a lock with no contention.
199
        """
200
        t = self.get_transport()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
201
        lf1 = LockDir(t, 'test_lock')
202
        lf1.create()
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
203
        self.setup_log_reporter(lf1)
1553.5.12 by Martin Pool
New LockDir locking mechanism
204
        try:
205
            before = time.time()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
206
            lf1.wait_lock(timeout=0.4, poll=0.1)
1553.5.12 by Martin Pool
New LockDir locking mechanism
207
            after = time.time()
208
            self.assertTrue(after - before <= 1.0)
209
        finally:
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
210
            lf1.unlock()
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
211
        self.assertEqual([], self._logged_reports)
1553.5.12 by Martin Pool
New LockDir locking mechanism
212
213
    def test_32_lock_wait_succeed(self):
214
        """Succeed when trying to acquire a lock that gets released
215
1553.5.77 by Martin Pool
doc
216
        One thread holds on a lock and then releases it; another 
217
        tries to lock it.
1553.5.12 by Martin Pool
New LockDir locking mechanism
218
        """
219
        t = self.get_transport()
220
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
221
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
222
        lf1.attempt_lock()
223
224
        def wait_and_unlock():
225
            time.sleep(0.1)
226
            lf1.unlock()
227
        unlocker = Thread(target=wait_and_unlock)
228
        unlocker.start()
229
        try:
230
            lf2 = LockDir(t, 'test_lock')
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
231
            self.setup_log_reporter(lf2)
1553.5.12 by Martin Pool
New LockDir locking mechanism
232
            before = time.time()
233
            # wait and then lock
234
            lf2.wait_lock(timeout=0.4, poll=0.1)
235
            after = time.time()
236
            self.assertTrue(after - before <= 1.0)
237
        finally:
238
            unlocker.join()
239
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
240
        # There should be only 1 report, even though it should have to
241
        # wait for a while
242
        lock_base = lf2.transport.abspath(lf2.path)
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
243
        self.assertEqual(1, len(self._logged_reports))
1957.1.9 by John Arbash Meinel
Change default timeouts, and report differently the first failure
244
        self.assertEqual('%s %s\n'
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
245
                         '%s\n%s\n'
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
246
                         'Will continue to try until %s\n',
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
247
                         self._logged_reports[0][0])
248
        args = self._logged_reports[0][1]
1957.1.9 by John Arbash Meinel
Change default timeouts, and report differently the first failure
249
        self.assertEqual('Unable to obtain', args[0])
250
        self.assertEqual('lock %s' % (lock_base,), args[1])
251
        self.assertStartsWith(args[2], 'held by ')
252
        self.assertStartsWith(args[3], 'locked ')
253
        self.assertEndsWith(args[3], ' ago')
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
254
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
1957.1.1 by John Arbash Meinel
Report to the user when we are spinning on a lock
255
1553.5.12 by Martin Pool
New LockDir locking mechanism
256
    def test_33_wait(self):
257
        """Succeed when waiting on a lock that gets released
258
259
        The difference from test_32_lock_wait_succeed is that the second 
260
        caller does not actually acquire the lock, but just waits for it
261
        to be released.  This is done over a readonly transport.
262
        """
263
        t = self.get_transport()
264
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
265
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
266
        lf1.attempt_lock()
267
268
        def wait_and_unlock():
269
            time.sleep(0.1)
270
            lf1.unlock()
271
        unlocker = Thread(target=wait_and_unlock)
272
        unlocker.start()
273
        try:
274
            lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
275
            before = time.time()
276
            # wait but don't lock
277
            lf2.wait(timeout=0.4, poll=0.1)
278
            after = time.time()
279
            self.assertTrue(after - before <= 1.0)
280
        finally:
281
            unlocker.join()
1553.5.20 by Martin Pool
Start adding LockDir.confirm() method
282
1957.1.2 by John Arbash Meinel
Switch the default from instantly aborting, to waiting as long as 1 minute (down from 5 minutes)
283
    def test_34_lock_write_waits(self):
284
        """LockDir.lock_write() will wait for the lock.""" 
285
        t = self.get_transport()
286
        lf1 = LockDir(t, 'test_lock')
287
        lf1.create()
288
        lf1.attempt_lock()
289
290
        def wait_and_unlock():
291
            time.sleep(0.1)
292
            lf1.unlock()
293
        unlocker = Thread(target=wait_and_unlock)
294
        unlocker.start()
295
        try:
296
            lf2 = LockDir(t, 'test_lock')
297
            self.setup_log_reporter(lf2)
298
            before = time.time()
299
            # wait and then lock
300
            lf2.lock_write()
301
            after = time.time()
302
        finally:
303
            unlocker.join()
304
305
        # There should be only 1 report, even though it should have to
306
        # wait for a while
307
        lock_base = lf2.transport.abspath(lf2.path)
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
308
        self.assertEqual(1, len(self._logged_reports))
1957.1.9 by John Arbash Meinel
Change default timeouts, and report differently the first failure
309
        self.assertEqual('%s %s\n'
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
310
                         '%s\n%s\n'
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
311
                         'Will continue to try until %s\n',
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
312
                         self._logged_reports[0][0])
313
        args = self._logged_reports[0][1]
1957.1.9 by John Arbash Meinel
Change default timeouts, and report differently the first failure
314
        self.assertEqual('Unable to obtain', args[0])
315
        self.assertEqual('lock %s' % (lock_base,), args[1])
316
        self.assertStartsWith(args[2], 'held by ')
317
        self.assertStartsWith(args[3], 'locked ')
318
        self.assertEndsWith(args[3], ' ago')
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
319
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
320
321
    def test_35_wait_lock_changing(self):
322
        """LockDir.wait_lock() will report if the lock changes underneath.
323
        
324
        This is the stages we want to happen:
325
326
        0) Synchronization locks are created and locked.
327
        1) Lock1 obtains the lockdir, and releases the 'check' lock.
328
        2) Lock2 grabs the 'check' lock, and checks the lockdir.
329
           It sees the lockdir is already acquired, reports the fact, 
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
330
           and unsets the 'checked' lock.
331
        3) Thread1 blocks on acquiring the 'checked' lock, and then tells
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
332
           Lock1 to release and acquire the lockdir. This resets the 'check'
333
           lock.
334
        4) Lock2 acquires the 'check' lock, and checks again. It notices
335
           that the holder of the lock has changed, and so reports a new 
336
           lock holder.
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
337
        5) Thread1 blocks on the 'checked' lock, this time, it completely
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
338
           unlocks the lockdir, allowing Lock2 to acquire the lock.
339
        """
340
341
        wait_to_check_lock = Lock()
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
342
        wait_until_checked_lock = Lock()
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
343
344
        wait_to_check_lock.acquire()
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
345
        wait_until_checked_lock.acquire()
346
        note('locked check and checked locks')
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
347
348
        class LockDir1(LockDir):
349
            """Use the synchronization points for the first lock."""
350
351
            def attempt_lock(self):
352
                # Once we have acquired the lock, it is okay for
353
                # the other lock to check it
354
                try:
355
                    return super(LockDir1, self).attempt_lock()
356
                finally:
357
                    note('lock1: releasing check lock')
358
                    wait_to_check_lock.release()
359
360
        class LockDir2(LockDir):
361
            """Use the synchronization points for the second lock."""
362
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
363
            def attempt_lock(self):
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
364
                note('lock2: waiting for check lock')
365
                wait_to_check_lock.acquire()
366
                note('lock2: acquired check lock')
367
                try:
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
368
                    return super(LockDir2, self).attempt_lock()
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
369
                finally:
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
370
                    note('lock2: releasing checked lock')
371
                    wait_until_checked_lock.release()
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
372
373
        t = self.get_transport()
374
        lf1 = LockDir1(t, 'test_lock')
375
        lf1.create()
376
377
        lf2 = LockDir2(t, 'test_lock')
378
        self.setup_log_reporter(lf2)
379
380
        def wait_and_switch():
381
            lf1.attempt_lock()
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
382
            # Block until lock2 has had a chance to check
383
            note('lock1: waiting 1 for checked lock')
384
            wait_until_checked_lock.acquire()
385
            note('lock1: acquired for checked lock')
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
386
            note('lock1: released lockdir')
387
            lf1.unlock()
388
            note('lock1: acquiring lockdir')
389
            # Create a new nonce, so the lock looks different.
390
            lf1.nonce = osutils.rand_chars(20)
391
            lf1.lock_write()
392
            note('lock1: acquired lockdir')
393
394
            # Block until lock2 has peeked again
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
395
            note('lock1: waiting 2 for checked lock')
396
            wait_until_checked_lock.acquire()
397
            note('lock1: acquired for checked lock')
398
            # Now unlock, and let lock 2 grab the lock
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
399
            lf1.unlock()
400
            wait_to_check_lock.release()
401
402
        unlocker = Thread(target=wait_and_switch)
403
        unlocker.start()
404
        try:
1957.1.11 by John Arbash Meinel
Switch from locking the peek() to locking attempt_lock(), which is much more stable
405
            # Wait and play against the other thread
406
            lf2.wait_lock(timeout=1.0, poll=0.01)
1957.1.7 by John Arbash Meinel
Add the ability to report if the lock changes from underneath you
407
        finally:
408
            unlocker.join()
409
        lf2.unlock()
410
411
        # There should be 2 reports, because the lock changed
412
        lock_base = lf2.transport.abspath(lf2.path)
413
        self.assertEqual(2, len(self._logged_reports))
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
414
1957.1.15 by John Arbash Meinel
Review feedback from Robert
415
        self.assertEqual('%s %s\n'
416
                         '%s\n%s\n'
417
                         'Will continue to try until %s\n',
418
                         self._logged_reports[0][0])
419
        args = self._logged_reports[0][1]
420
        self.assertEqual('Unable to obtain', args[0])
421
        self.assertEqual('lock %s' % (lock_base,), args[1])
422
        self.assertStartsWith(args[2], 'held by ')
423
        self.assertStartsWith(args[3], 'locked ')
424
        self.assertEndsWith(args[3], ' ago')
425
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
1957.1.13 by John Arbash Meinel
Change to reporting the time when we will stop trying to grab the lock
426
1957.1.15 by John Arbash Meinel
Review feedback from Robert
427
        self.assertEqual('%s %s\n'
428
                         '%s\n%s\n'
429
                         'Will continue to try until %s\n',
430
                         self._logged_reports[1][0])
431
        args = self._logged_reports[1][1]
432
        self.assertEqual('Lock owner changed for', args[0])
433
        self.assertEqual('lock %s' % (lock_base,), args[1])
434
        self.assertStartsWith(args[2], 'held by ')
435
        self.assertStartsWith(args[3], 'locked ')
436
        self.assertEndsWith(args[3], ' ago')
437
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
1957.1.2 by John Arbash Meinel
Switch the default from instantly aborting, to waiting as long as 1 minute (down from 5 minutes)
438
1553.5.20 by Martin Pool
Start adding LockDir.confirm() method
439
    def test_40_confirm_easy(self):
440
        """Confirm a lock that's already held"""
441
        t = self.get_transport()
442
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
443
        lf1.create()
1553.5.20 by Martin Pool
Start adding LockDir.confirm() method
444
        lf1.attempt_lock()
445
        lf1.confirm()
446
447
    def test_41_confirm_not_held(self):
448
        """Confirm a lock that's already held"""
449
        t = self.get_transport()
450
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
451
        lf1.create()
1553.5.20 by Martin Pool
Start adding LockDir.confirm() method
452
        self.assertRaises(LockNotHeld, lf1.confirm)
1553.5.23 by Martin Pool
Start LockDir.confirm method and LockBroken exception
453
454
    def test_42_confirm_broken_manually(self):
455
        """Confirm a lock broken by hand"""
456
        t = self.get_transport()
457
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
458
        lf1.create()
1553.5.23 by Martin Pool
Start LockDir.confirm method and LockBroken exception
459
        lf1.attempt_lock()
460
        t.move('test_lock', 'lock_gone_now')
461
        self.assertRaises(LockBroken, lf1.confirm)
1553.5.25 by Martin Pool
New LockDir.force_break and simple test case
462
463
    def test_43_break(self):
464
        """Break a lock whose caller has forgotten it"""
465
        t = self.get_transport()
466
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
467
        lf1.create()
1553.5.25 by Martin Pool
New LockDir.force_break and simple test case
468
        lf1.attempt_lock()
469
        # we incorrectly discard the lock object without unlocking it
470
        del lf1
471
        # someone else sees it's still locked
472
        lf2 = LockDir(t, 'test_lock')
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
473
        holder_info = lf2.peek()
474
        self.assertTrue(holder_info)
475
        lf2.force_break(holder_info)
1553.5.25 by Martin Pool
New LockDir.force_break and simple test case
476
        # now we should be able to take it
477
        lf2.attempt_lock()
478
        lf2.confirm()
1553.5.26 by Martin Pool
Breaking an already-released lock should just succeed
479
480
    def test_44_break_already_released(self):
481
        """Lock break races with regular release"""
482
        t = self.get_transport()
483
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
484
        lf1.create()
1553.5.26 by Martin Pool
Breaking an already-released lock should just succeed
485
        lf1.attempt_lock()
486
        # someone else sees it's still locked
487
        lf2 = LockDir(t, 'test_lock')
488
        holder_info = lf2.peek()
489
        # in the interim the lock is released
490
        lf1.unlock()
491
        # break should succeed
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
492
        lf2.force_break(holder_info)
1553.5.26 by Martin Pool
Breaking an already-released lock should just succeed
493
        # now we should be able to take it
494
        lf2.attempt_lock()
495
        lf2.confirm()
496
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
497
    def test_45_break_mismatch(self):
498
        """Lock break races with someone else acquiring it"""
499
        t = self.get_transport()
500
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
501
        lf1.create()
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
502
        lf1.attempt_lock()
503
        # someone else sees it's still locked
504
        lf2 = LockDir(t, 'test_lock')
505
        holder_info = lf2.peek()
506
        # in the interim the lock is released
507
        lf1.unlock()
508
        lf3 = LockDir(t, 'test_lock')
509
        lf3.attempt_lock()
510
        # break should now *fail*
511
        self.assertRaises(LockBreakMismatch, lf2.force_break,
512
                          holder_info)
513
        lf3.unlock()
1553.5.54 by Martin Pool
Add LockDir.read_lock fake method
514
515
    def test_46_fake_read_lock(self):
516
        t = self.get_transport()
517
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
518
        lf1.create()
1553.5.54 by Martin Pool
Add LockDir.read_lock fake method
519
        lf1.lock_read()
520
        lf1.unlock()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
521
522
    def test_50_lockdir_representation(self):
523
        """Check the on-disk representation of LockDirs is as expected.
524
525
        There should always be a top-level directory named by the lock.
526
        When the lock is held, there should be a lockname/held directory 
527
        containing an info file.
528
        """
529
        t = self.get_transport()
530
        lf1 = LockDir(t, 'test_lock')
531
        lf1.create()
532
        self.assertTrue(t.has('test_lock'))
533
        lf1.lock_write()
534
        self.assertTrue(t.has('test_lock/held/info'))
535
        lf1.unlock()
536
        self.assertFalse(t.has('test_lock/held/info'))
1687.1.5 by Robert Collins
Add break_lock utility function to LockDir.
537
538
    def test_break_lock(self):
539
        # the ui based break_lock routine should Just Work (tm)
540
        ld1 = self.get_lock()
541
        ld2 = self.get_lock()
542
        ld1.create()
543
        ld1.lock_write()
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
544
        # do this without IO redirection to ensure it doesn't prompt.
545
        self.assertRaises(AssertionError, ld1.break_lock)
1687.1.5 by Robert Collins
Add break_lock utility function to LockDir.
546
        orig_factory = bzrlib.ui.ui_factory
547
        # silent ui - no need for stdout
548
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
549
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
550
        try:
551
            ld2.break_lock()
552
            self.assertRaises(LockBroken, ld1.unlock)
553
        finally:
554
            bzrlib.ui.ui_factory = orig_factory
1955.1.1 by John Arbash Meinel
LockDir can create the root directory if it fails to create a pending directory due to NoSuchFile.
555
556
    def test_create_missing_base_directory(self):
557
        """If LockDir.path doesn't exist, it can be created
558
559
        Some people manually remove the entire lock/ directory trying
560
        to unlock a stuck repository/branch/etc. Rather than failing
561
        after that, just create the lock directory when needed.
562
        """
563
        t = self.get_transport()
564
        lf1 = LockDir(t, 'test_lock')
565
566
        lf1.create()
567
        self.failUnless(t.has('test_lock'))
568
569
        t.rmdir('test_lock')
570
        self.failIf(t.has('test_lock'))
571
572
        # This will create 'test_lock' if it needs to
573
        lf1.lock_write()
574
        self.failUnless(t.has('test_lock'))
575
        self.failUnless(t.has('test_lock/held/info'))
576
577
        lf1.unlock()
578
        self.failIf(t.has('test_lock/held/info'))
1957.1.6 by John Arbash Meinel
[merge] bzr.dev 2009
579
1957.1.5 by John Arbash Meinel
Create a helper function for formatting lock information
580
    def test__format_lock_info(self):
581
        ld1 = self.get_lock()
582
        ld1.create()
583
        ld1.lock_write()
584
        try:
585
            info_list = ld1._format_lock_info(ld1.peek())
586
        finally:
587
            ld1.unlock()
588
        self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
589
                         info_list[0])
590
        self.assertContainsRe(info_list[1],
591
                              r'^held by .* on host .* \[process #\d*\]$')
592
        self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')