~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: Robert Collins
  • Date: 2005-10-06 22:15:52 UTC
  • mfrom: (1185.13.2)
  • mto: This revision was merged to the branch mainline in revision 1420.
  • Revision ID: robertc@robertcollins.net-20051006221552-9b15c96fa504e0ad
mergeĀ fromĀ upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
19
 
from cStringIO import StringIO
20
 
from threading import Thread
21
 
import time
22
 
 
23
 
import bzrlib
24
 
from bzrlib.errors import (
25
 
        LockBreakMismatch,
26
 
        LockContention, LockError, UnlockableTransport,
27
 
        LockNotHeld, LockBroken
28
 
        )
29
 
from bzrlib.lockdir import LockDir
30
 
from bzrlib.tests import TestCaseWithTransport
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
 
 
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
 
 
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')
49
 
        self.assertFalse(lf.is_held)
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
 
 
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
 
 
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')
80
 
        lf.create()
81
 
        lf.attempt_lock()
82
 
        try:
83
 
            self.assertTrue(lf.is_held)
84
 
        finally:
85
 
            lf.unlock()
86
 
            self.assertFalse(lf.is_held)
87
 
 
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):
95
 
        """Fail to lock on readonly transport"""
96
 
        lf = LockDir(self.get_transport(), 'test_lock')
97
 
        lf.create()
98
 
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
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')
105
 
        lf1.create()
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')
123
 
        lf1.create()
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')
140
 
        lf1.create()
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')
157
 
        lf1.create()
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()
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))
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()
176
 
        lf1 = LockDir(t, 'test_lock')
177
 
        lf1.create()
178
 
        try:
179
 
            before = time.time()
180
 
            lf1.wait_lock(timeout=0.4, poll=0.1)
181
 
            after = time.time()
182
 
            self.assertTrue(after - before <= 1.0)
183
 
        finally:
184
 
            lf1.unlock()
185
 
 
186
 
    def test_32_lock_wait_succeed(self):
187
 
        """Succeed when trying to acquire a lock that gets released
188
 
 
189
 
        One thread holds on a lock and then releases it; another 
190
 
        tries to lock it.
191
 
        """
192
 
        t = self.get_transport()
193
 
        lf1 = LockDir(t, 'test_lock')
194
 
        lf1.create()
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')
221
 
        lf1.create()
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()
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')
243
 
        lf1.create()
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')
251
 
        lf1.create()
252
 
        self.assertRaises(LockNotHeld, lf1.confirm)
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')
258
 
        lf1.create()
259
 
        lf1.attempt_lock()
260
 
        t.move('test_lock', 'lock_gone_now')
261
 
        self.assertRaises(LockBroken, lf1.confirm)
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')
267
 
        lf1.create()
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')
273
 
        holder_info = lf2.peek()
274
 
        self.assertTrue(holder_info)
275
 
        lf2.force_break(holder_info)
276
 
        # now we should be able to take it
277
 
        lf2.attempt_lock()
278
 
        lf2.confirm()
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')
284
 
        lf1.create()
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
292
 
        lf2.force_break(holder_info)
293
 
        # now we should be able to take it
294
 
        lf2.attempt_lock()
295
 
        lf2.confirm()
296
 
 
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')
301
 
        lf1.create()
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()
314
 
 
315
 
    def test_46_fake_read_lock(self):
316
 
        t = self.get_transport()
317
 
        lf1 = LockDir(t, 'test_lock')
318
 
        lf1.create()
319
 
        lf1.lock_read()
320
 
        lf1.unlock()
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'))
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()
344
 
        # do this without IO redirection to ensure it doesn't prompt.
345
 
        self.assertRaises(AssertionError, ld1.break_lock)
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
355
 
 
356
 
    def test_create_missing_base_directory(self):
357
 
        """If LockDir.path doesn't exist, it can be created
358
 
 
359
 
        Some people manually remove the entire lock/ directory trying
360
 
        to unlock a stuck repository/branch/etc. Rather than failing
361
 
        after that, just create the lock directory when needed.
362
 
        """
363
 
        t = self.get_transport()
364
 
        lf1 = LockDir(t, 'test_lock')
365
 
 
366
 
        lf1.create()
367
 
        self.failUnless(t.has('test_lock'))
368
 
 
369
 
        t.rmdir('test_lock')
370
 
        self.failIf(t.has('test_lock'))
371
 
 
372
 
        # This will create 'test_lock' if it needs to
373
 
        lf1.lock_write()
374
 
        self.failUnless(t.has('test_lock'))
375
 
        self.failUnless(t.has('test_lock/held/info'))
376
 
 
377
 
        lf1.unlock()
378
 
        self.failIf(t.has('test_lock/held/info'))