~bzr-pqm/bzr/bzr.dev

1553.5.12 by Martin Pool
New LockDir locking mechanism
1
# Copyright (C) 2006 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., 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
1553.5.12 by Martin Pool
New LockDir locking mechanism
20
from threading import Thread
21
import time
22
1687.1.5 by Robert Collins
Add break_lock utility function to LockDir.
23
import bzrlib
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
24
from bzrlib.errors import (
25
        LockBreakMismatch,
26
        LockContention, LockError, UnlockableTransport,
1553.5.23 by Martin Pool
Start LockDir.confirm method and LockBroken exception
27
        LockNotHeld, LockBroken
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
28
        )
1553.5.12 by Martin Pool
New LockDir locking mechanism
29
from bzrlib.lockdir import LockDir
1553.5.33 by Martin Pool
LockDir review comment fixes
30
from bzrlib.tests import TestCaseWithTransport
1553.5.12 by Martin Pool
New LockDir locking mechanism
31
32
# These tests sometimes use threads to test the behaviour of lock files with
33
# concurrent actors.  This is not a typical (or necessarily supported) use;
34
# they're really meant for guarding between processes.
35
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
36
# These tests are run on the default transport provided by the test framework
37
# (typically a local disk transport).  That can be changed by the --transport
38
# option to bzr selftest.  The required properties of the transport
39
# implementation are tested separately.  (The main requirement is just that
40
# they don't allow overwriting nonempty directories.)
41
1553.5.12 by Martin Pool
New LockDir locking mechanism
42
class TestLockDir(TestCaseWithTransport):
43
    """Test LockDir operations"""
44
45
    def test_00_lock_creation(self):
46
        """Creation of lock file on a transport"""
47
        t = self.get_transport()
48
        lf = LockDir(t, 'test_lock')
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
49
        self.assertFalse(lf.is_held)
1553.5.12 by Martin Pool
New LockDir locking mechanism
50
51
    def test_01_lock_repr(self):
52
        """Lock string representation"""
53
        lf = LockDir(self.get_transport(), 'test_lock')
54
        r = repr(lf)
55
        self.assertContainsRe(r, r'^LockDir\(.*/test_lock\)$')
56
57
    def test_02_unlocked_peek(self):
58
        lf = LockDir(self.get_transport(), 'test_lock')
59
        self.assertEqual(lf.peek(), None)
60
1687.1.3 by Robert Collins
Make LockDir.unlock check the lock is still intact.
61
    def get_lock(self):
62
        return LockDir(self.get_transport(), 'test_lock')
63
64
    def test_unlock_after_break_raises(self):
65
        ld = self.get_lock()
66
        ld2 = self.get_lock()
67
        ld.create()
68
        ld.attempt_lock()
69
        ld2.force_break(ld2.peek())
70
        self.assertRaises(LockBroken, ld.unlock)
71
1553.5.12 by Martin Pool
New LockDir locking mechanism
72
    def test_03_readonly_peek(self):
73
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
74
        self.assertEqual(lf.peek(), None)
75
76
    def test_10_lock_uncontested(self):
77
        """Acquire and release a lock"""
78
        t = self.get_transport()
79
        lf = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
80
        lf.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
81
        lf.attempt_lock()
82
        try:
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
83
            self.assertTrue(lf.is_held)
1553.5.12 by Martin Pool
New LockDir locking mechanism
84
        finally:
85
            lf.unlock()
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
86
            self.assertFalse(lf.is_held)
1553.5.12 by Martin Pool
New LockDir locking mechanism
87
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
88
    def test_11_create_readonly_transport(self):
89
        """Fail to create lock on readonly transport"""
90
        t = self.get_readonly_transport()
91
        lf = LockDir(t, 'test_lock')
92
        self.assertRaises(UnlockableTransport, lf.create)
93
94
    def test_12_lock_readonly_transport(self):
1553.5.12 by Martin Pool
New LockDir locking mechanism
95
        """Fail to lock on readonly transport"""
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
96
        lf = LockDir(self.get_transport(), 'test_lock')
97
        lf.create()
98
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
1553.5.12 by Martin Pool
New LockDir locking mechanism
99
        self.assertRaises(UnlockableTransport, lf.attempt_lock)
100
101
    def test_20_lock_contested(self):
102
        """Contention to get a lock"""
103
        t = self.get_transport()
104
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
105
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
106
        lf1.attempt_lock()
107
        lf2 = LockDir(t, 'test_lock')
108
        try:
109
            # locking is between LockDir instances; aliases within 
110
            # a single process are not detected
111
            lf2.attempt_lock()
112
            self.fail('Failed to detect lock collision')
113
        except LockContention, e:
114
            self.assertEqual(e.lock, lf2)
115
            self.assertContainsRe(str(e),
116
                    r'^Could not acquire.*test_lock.*$')
117
        lf1.unlock()
118
119
    def test_20_lock_peek(self):
120
        """Peek at the state of a lock"""
121
        t = self.get_transport()
122
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
123
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
124
        lf1.attempt_lock()
125
        # lock is held, should get some info on it
126
        info1 = lf1.peek()
127
        self.assertEqual(set(info1.keys()),
128
                         set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
129
        # should get the same info if we look at it through a different
130
        # instance
131
        info2 = LockDir(t, 'test_lock').peek()
132
        self.assertEqual(info1, info2)
133
        # locks which are never used should be not-held
134
        self.assertEqual(LockDir(t, 'other_lock').peek(), None)
135
136
    def test_21_peek_readonly(self):
137
        """Peek over a readonly transport"""
138
        t = self.get_transport()
139
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
140
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
141
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
142
        self.assertEqual(lf2.peek(), None)
143
        lf1.attempt_lock()
144
        info2 = lf2.peek()
145
        self.assertTrue(info2)
146
        self.assertEqual(info2['nonce'], lf1.nonce)
147
148
    def test_30_lock_wait_fail(self):
149
        """Wait on a lock, then fail
150
        
151
        We ask to wait up to 400ms; this should fail within at most one
152
        second.  (Longer times are more realistic but we don't want the test
153
        suite to take too long, and this should do for now.)
154
        """
155
        t = self.get_transport()
156
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
157
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
158
        lf2 = LockDir(t, 'test_lock')
159
        lf1.attempt_lock()
160
        try:
161
            before = time.time()
162
            self.assertRaises(LockContention, lf2.wait_lock,
163
                              timeout=0.4, poll=0.1)
164
            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
165
            # it should only take about 0.4 seconds, but we allow more time in
166
            # case the machine is heavily loaded
167
            self.assertTrue(after - before <= 8.0, 
168
                    "took %f seconds to detect lock contention" % (after - before))
1553.5.12 by Martin Pool
New LockDir locking mechanism
169
        finally:
170
            lf1.unlock()
171
172
    def test_31_lock_wait_easy(self):
173
        """Succeed when waiting on a lock with no contention.
174
        """
175
        t = self.get_transport()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
176
        lf1 = LockDir(t, 'test_lock')
177
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
178
        try:
179
            before = time.time()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
180
            lf1.wait_lock(timeout=0.4, poll=0.1)
1553.5.12 by Martin Pool
New LockDir locking mechanism
181
            after = time.time()
182
            self.assertTrue(after - before <= 1.0)
183
        finally:
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
184
            lf1.unlock()
1553.5.12 by Martin Pool
New LockDir locking mechanism
185
186
    def test_32_lock_wait_succeed(self):
187
        """Succeed when trying to acquire a lock that gets released
188
1553.5.77 by Martin Pool
doc
189
        One thread holds on a lock and then releases it; another 
190
        tries to lock it.
1553.5.12 by Martin Pool
New LockDir locking mechanism
191
        """
192
        t = self.get_transport()
193
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
194
        lf1.create()
1553.5.12 by Martin Pool
New LockDir locking mechanism
195
        lf1.attempt_lock()
196
197
        def wait_and_unlock():
198
            time.sleep(0.1)
199
            lf1.unlock()
200
        unlocker = Thread(target=wait_and_unlock)
201
        unlocker.start()
202
        try:
203
            lf2 = LockDir(t, 'test_lock')
204
            before = time.time()
205
            # wait and then lock
206
            lf2.wait_lock(timeout=0.4, poll=0.1)
207
            after = time.time()
208
            self.assertTrue(after - before <= 1.0)
209
        finally:
210
            unlocker.join()
211
212
    def test_33_wait(self):
213
        """Succeed when waiting on a lock that gets released
214
215
        The difference from test_32_lock_wait_succeed is that the second 
216
        caller does not actually acquire the lock, but just waits for it
217
        to be released.  This is done over a readonly transport.
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(self.get_readonly_transport(), 'test_lock')
231
            before = time.time()
232
            # wait but don't lock
233
            lf2.wait(timeout=0.4, poll=0.1)
234
            after = time.time()
235
            self.assertTrue(after - before <= 1.0)
236
        finally:
237
            unlocker.join()
1553.5.20 by Martin Pool
Start adding LockDir.confirm() method
238
239
    def test_40_confirm_easy(self):
240
        """Confirm a lock that's already held"""
241
        t = self.get_transport()
242
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
243
        lf1.create()
1553.5.20 by Martin Pool
Start adding LockDir.confirm() method
244
        lf1.attempt_lock()
245
        lf1.confirm()
246
247
    def test_41_confirm_not_held(self):
248
        """Confirm a lock that's already held"""
249
        t = self.get_transport()
250
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
251
        lf1.create()
1553.5.20 by Martin Pool
Start adding LockDir.confirm() method
252
        self.assertRaises(LockNotHeld, lf1.confirm)
1553.5.23 by Martin Pool
Start LockDir.confirm method and LockBroken exception
253
254
    def test_42_confirm_broken_manually(self):
255
        """Confirm a lock broken by hand"""
256
        t = self.get_transport()
257
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
258
        lf1.create()
1553.5.23 by Martin Pool
Start LockDir.confirm method and LockBroken exception
259
        lf1.attempt_lock()
260
        t.move('test_lock', 'lock_gone_now')
261
        self.assertRaises(LockBroken, lf1.confirm)
1553.5.25 by Martin Pool
New LockDir.force_break and simple test case
262
263
    def test_43_break(self):
264
        """Break a lock whose caller has forgotten it"""
265
        t = self.get_transport()
266
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
267
        lf1.create()
1553.5.25 by Martin Pool
New LockDir.force_break and simple test case
268
        lf1.attempt_lock()
269
        # we incorrectly discard the lock object without unlocking it
270
        del lf1
271
        # someone else sees it's still locked
272
        lf2 = LockDir(t, 'test_lock')
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
273
        holder_info = lf2.peek()
274
        self.assertTrue(holder_info)
275
        lf2.force_break(holder_info)
1553.5.25 by Martin Pool
New LockDir.force_break and simple test case
276
        # now we should be able to take it
277
        lf2.attempt_lock()
278
        lf2.confirm()
1553.5.26 by Martin Pool
Breaking an already-released lock should just succeed
279
280
    def test_44_break_already_released(self):
281
        """Lock break races with regular release"""
282
        t = self.get_transport()
283
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
284
        lf1.create()
1553.5.26 by Martin Pool
Breaking an already-released lock should just succeed
285
        lf1.attempt_lock()
286
        # someone else sees it's still locked
287
        lf2 = LockDir(t, 'test_lock')
288
        holder_info = lf2.peek()
289
        # in the interim the lock is released
290
        lf1.unlock()
291
        # break should succeed
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
292
        lf2.force_break(holder_info)
1553.5.26 by Martin Pool
Breaking an already-released lock should just succeed
293
        # now we should be able to take it
294
        lf2.attempt_lock()
295
        lf2.confirm()
296
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
297
    def test_45_break_mismatch(self):
298
        """Lock break races with someone else acquiring it"""
299
        t = self.get_transport()
300
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
301
        lf1.create()
1553.5.27 by Martin Pool
Confirm that only the intended holder of a lock was broken.
302
        lf1.attempt_lock()
303
        # someone else sees it's still locked
304
        lf2 = LockDir(t, 'test_lock')
305
        holder_info = lf2.peek()
306
        # in the interim the lock is released
307
        lf1.unlock()
308
        lf3 = LockDir(t, 'test_lock')
309
        lf3.attempt_lock()
310
        # break should now *fail*
311
        self.assertRaises(LockBreakMismatch, lf2.force_break,
312
                          holder_info)
313
        lf3.unlock()
1553.5.54 by Martin Pool
Add LockDir.read_lock fake method
314
315
    def test_46_fake_read_lock(self):
316
        t = self.get_transport()
317
        lf1 = LockDir(t, 'test_lock')
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
318
        lf1.create()
1553.5.54 by Martin Pool
Add LockDir.read_lock fake method
319
        lf1.lock_read()
320
        lf1.unlock()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
321
322
    def test_50_lockdir_representation(self):
323
        """Check the on-disk representation of LockDirs is as expected.
324
325
        There should always be a top-level directory named by the lock.
326
        When the lock is held, there should be a lockname/held directory 
327
        containing an info file.
328
        """
329
        t = self.get_transport()
330
        lf1 = LockDir(t, 'test_lock')
331
        lf1.create()
332
        self.assertTrue(t.has('test_lock'))
333
        lf1.lock_write()
334
        self.assertTrue(t.has('test_lock/held/info'))
335
        lf1.unlock()
336
        self.assertFalse(t.has('test_lock/held/info'))
1687.1.5 by Robert Collins
Add break_lock utility function to LockDir.
337
338
    def test_break_lock(self):
339
        # the ui based break_lock routine should Just Work (tm)
340
        ld1 = self.get_lock()
341
        ld2 = self.get_lock()
342
        ld1.create()
343
        ld1.lock_write()
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
344
        # do this without IO redirection to ensure it doesn't prompt.
345
        self.assertRaises(AssertionError, ld1.break_lock)
1687.1.5 by Robert Collins
Add break_lock utility function to LockDir.
346
        orig_factory = bzrlib.ui.ui_factory
347
        # silent ui - no need for stdout
348
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
349
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
350
        try:
351
            ld2.break_lock()
352
            self.assertRaises(LockBroken, ld1.unlock)
353
        finally:
354
            bzrlib.ui.ui_factory = orig_factory