~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-02-22 07:59:56 UTC
  • mfrom: (1553.5.33 bzr.mbp.locks)
  • Revision ID: pqm@pqm.ubuntu.com-20060222075956-fb281c427e571da6
add LockDir and related fixes

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 threading import Thread
 
20
import time
 
21
 
 
22
from bzrlib.errors import (
 
23
        LockBreakMismatch,
 
24
        LockContention, LockError, UnlockableTransport,
 
25
        LockNotHeld, LockBroken
 
26
        )
 
27
from bzrlib.lockdir import LockDir
 
28
from bzrlib.tests import TestCaseWithTransport
 
29
 
 
30
# These tests sometimes use threads to test the behaviour of lock files with
 
31
# concurrent actors.  This is not a typical (or necessarily supported) use;
 
32
# they're really meant for guarding between processes.
 
33
 
 
34
class TestLockDir(TestCaseWithTransport):
 
35
    """Test LockDir operations"""
 
36
 
 
37
    def test_00_lock_creation(self):
 
38
        """Creation of lock file on a transport"""
 
39
        t = self.get_transport()
 
40
        lf = LockDir(t, 'test_lock')
 
41
        self.assertFalse(lf.is_held)
 
42
 
 
43
    def test_01_lock_repr(self):
 
44
        """Lock string representation"""
 
45
        lf = LockDir(self.get_transport(), 'test_lock')
 
46
        r = repr(lf)
 
47
        self.assertContainsRe(r, r'^LockDir\(.*/test_lock\)$')
 
48
 
 
49
    def test_02_unlocked_peek(self):
 
50
        lf = LockDir(self.get_transport(), 'test_lock')
 
51
        self.assertEqual(lf.peek(), None)
 
52
 
 
53
    def test_03_readonly_peek(self):
 
54
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
 
55
        self.assertEqual(lf.peek(), None)
 
56
 
 
57
    def test_10_lock_uncontested(self):
 
58
        """Acquire and release a lock"""
 
59
        t = self.get_transport()
 
60
        lf = LockDir(t, 'test_lock')
 
61
        lf.attempt_lock()
 
62
        try:
 
63
            self.assertTrue(lf.is_held)
 
64
        finally:
 
65
            lf.unlock()
 
66
            self.assertFalse(lf.is_held)
 
67
 
 
68
    def test_11_lock_readonly_transport(self):
 
69
        """Fail to lock on readonly transport"""
 
70
        t = self.get_readonly_transport()
 
71
        lf = LockDir(t, 'test_lock')
 
72
        self.assertRaises(UnlockableTransport, lf.attempt_lock)
 
73
 
 
74
    def test_20_lock_contested(self):
 
75
        """Contention to get a lock"""
 
76
        t = self.get_transport()
 
77
        lf1 = LockDir(t, 'test_lock')
 
78
        lf1.attempt_lock()
 
79
        lf2 = LockDir(t, 'test_lock')
 
80
        try:
 
81
            # locking is between LockDir instances; aliases within 
 
82
            # a single process are not detected
 
83
            lf2.attempt_lock()
 
84
            self.fail('Failed to detect lock collision')
 
85
        except LockContention, e:
 
86
            self.assertEqual(e.lock, lf2)
 
87
            self.assertContainsRe(str(e),
 
88
                    r'^Could not acquire.*test_lock.*$')
 
89
        lf1.unlock()
 
90
 
 
91
    def test_20_lock_peek(self):
 
92
        """Peek at the state of a lock"""
 
93
        t = self.get_transport()
 
94
        lf1 = LockDir(t, 'test_lock')
 
95
        lf1.attempt_lock()
 
96
        # lock is held, should get some info on it
 
97
        info1 = lf1.peek()
 
98
        self.assertEqual(set(info1.keys()),
 
99
                         set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
 
100
        # should get the same info if we look at it through a different
 
101
        # instance
 
102
        info2 = LockDir(t, 'test_lock').peek()
 
103
        self.assertEqual(info1, info2)
 
104
        # locks which are never used should be not-held
 
105
        self.assertEqual(LockDir(t, 'other_lock').peek(), None)
 
106
 
 
107
    def test_21_peek_readonly(self):
 
108
        """Peek over a readonly transport"""
 
109
        t = self.get_transport()
 
110
        lf1 = LockDir(t, 'test_lock')
 
111
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
 
112
        self.assertEqual(lf2.peek(), None)
 
113
        lf1.attempt_lock()
 
114
        info2 = lf2.peek()
 
115
        self.assertTrue(info2)
 
116
        self.assertEqual(info2['nonce'], lf1.nonce)
 
117
 
 
118
    def test_30_lock_wait_fail(self):
 
119
        """Wait on a lock, then fail
 
120
        
 
121
        We ask to wait up to 400ms; this should fail within at most one
 
122
        second.  (Longer times are more realistic but we don't want the test
 
123
        suite to take too long, and this should do for now.)
 
124
        """
 
125
        t = self.get_transport()
 
126
        lf1 = LockDir(t, 'test_lock')
 
127
        lf2 = LockDir(t, 'test_lock')
 
128
        lf1.attempt_lock()
 
129
        try:
 
130
            before = time.time()
 
131
            self.assertRaises(LockContention, lf2.wait_lock,
 
132
                              timeout=0.4, poll=0.1)
 
133
            after = time.time()
 
134
            self.assertTrue(after - before <= 1.0)
 
135
        finally:
 
136
            lf1.unlock()
 
137
 
 
138
    def test_31_lock_wait_easy(self):
 
139
        """Succeed when waiting on a lock with no contention.
 
140
        """
 
141
        t = self.get_transport()
 
142
        lf2 = LockDir(t, 'test_lock')
 
143
        try:
 
144
            before = time.time()
 
145
            lf2.wait_lock(timeout=0.4, poll=0.1)
 
146
            after = time.time()
 
147
            self.assertTrue(after - before <= 1.0)
 
148
        finally:
 
149
            lf2.unlock()
 
150
 
 
151
    def test_32_lock_wait_succeed(self):
 
152
        """Succeed when trying to acquire a lock that gets released
 
153
 
 
154
        One thread holds on a lock and then releases it; another tries to lock it.
 
155
        """
 
156
        t = self.get_transport()
 
157
        lf1 = LockDir(t, 'test_lock')
 
158
        lf1.attempt_lock()
 
159
 
 
160
        def wait_and_unlock():
 
161
            time.sleep(0.1)
 
162
            lf1.unlock()
 
163
        unlocker = Thread(target=wait_and_unlock)
 
164
        unlocker.start()
 
165
        try:
 
166
            lf2 = LockDir(t, 'test_lock')
 
167
            before = time.time()
 
168
            # wait and then lock
 
169
            lf2.wait_lock(timeout=0.4, poll=0.1)
 
170
            after = time.time()
 
171
            self.assertTrue(after - before <= 1.0)
 
172
        finally:
 
173
            unlocker.join()
 
174
 
 
175
    def test_33_wait(self):
 
176
        """Succeed when waiting on a lock that gets released
 
177
 
 
178
        The difference from test_32_lock_wait_succeed is that the second 
 
179
        caller does not actually acquire the lock, but just waits for it
 
180
        to be released.  This is done over a readonly transport.
 
181
        """
 
182
        t = self.get_transport()
 
183
        lf1 = LockDir(t, 'test_lock')
 
184
        lf1.attempt_lock()
 
185
 
 
186
        def wait_and_unlock():
 
187
            time.sleep(0.1)
 
188
            lf1.unlock()
 
189
        unlocker = Thread(target=wait_and_unlock)
 
190
        unlocker.start()
 
191
        try:
 
192
            lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
 
193
            before = time.time()
 
194
            # wait but don't lock
 
195
            lf2.wait(timeout=0.4, poll=0.1)
 
196
            after = time.time()
 
197
            self.assertTrue(after - before <= 1.0)
 
198
        finally:
 
199
            unlocker.join()
 
200
 
 
201
    def test_40_confirm_easy(self):
 
202
        """Confirm a lock that's already held"""
 
203
        t = self.get_transport()
 
204
        lf1 = LockDir(t, 'test_lock')
 
205
        lf1.attempt_lock()
 
206
        lf1.confirm()
 
207
 
 
208
    def test_41_confirm_not_held(self):
 
209
        """Confirm a lock that's already held"""
 
210
        t = self.get_transport()
 
211
        lf1 = LockDir(t, 'test_lock')
 
212
        self.assertRaises(LockNotHeld, lf1.confirm)
 
213
 
 
214
    def test_42_confirm_broken_manually(self):
 
215
        """Confirm a lock broken by hand"""
 
216
        t = self.get_transport()
 
217
        lf1 = LockDir(t, 'test_lock')
 
218
        lf1.attempt_lock()
 
219
        t.move('test_lock', 'lock_gone_now')
 
220
        self.assertRaises(LockBroken, lf1.confirm)
 
221
 
 
222
    def test_43_break(self):
 
223
        """Break a lock whose caller has forgotten it"""
 
224
        t = self.get_transport()
 
225
        lf1 = LockDir(t, 'test_lock')
 
226
        lf1.attempt_lock()
 
227
        # we incorrectly discard the lock object without unlocking it
 
228
        del lf1
 
229
        # someone else sees it's still locked
 
230
        lf2 = LockDir(t, 'test_lock')
 
231
        holder_info = lf2.peek()
 
232
        self.assertTrue(holder_info)
 
233
        lf2.force_break(holder_info)
 
234
        # now we should be able to take it
 
235
        lf2.attempt_lock()
 
236
        lf2.confirm()
 
237
 
 
238
    def test_44_break_already_released(self):
 
239
        """Lock break races with regular release"""
 
240
        t = self.get_transport()
 
241
        lf1 = LockDir(t, 'test_lock')
 
242
        lf1.attempt_lock()
 
243
        # someone else sees it's still locked
 
244
        lf2 = LockDir(t, 'test_lock')
 
245
        holder_info = lf2.peek()
 
246
        # in the interim the lock is released
 
247
        lf1.unlock()
 
248
        # break should succeed
 
249
        lf2.force_break(holder_info)
 
250
        # now we should be able to take it
 
251
        lf2.attempt_lock()
 
252
        lf2.confirm()
 
253
 
 
254
    def test_45_break_mismatch(self):
 
255
        """Lock break races with someone else acquiring it"""
 
256
        t = self.get_transport()
 
257
        lf1 = LockDir(t, 'test_lock')
 
258
        lf1.attempt_lock()
 
259
        # someone else sees it's still locked
 
260
        lf2 = LockDir(t, 'test_lock')
 
261
        holder_info = lf2.peek()
 
262
        # in the interim the lock is released
 
263
        lf1.unlock()
 
264
        lf3 = LockDir(t, 'test_lock')
 
265
        lf3.attempt_lock()
 
266
        # break should now *fail*
 
267
        self.assertRaises(LockBreakMismatch, lf2.force_break,
 
268
                          holder_info)
 
269
        lf3.unlock()