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