1
# Copyright (c) 2005 Divmod, Inc.
2
# Copyright (c) 2008 Twisted Matrix Laboratories.
3
# See LICENSE for details.
6
Tests for L{twisted.python.lockfile}.
11
from twisted.trial import unittest
12
from twisted.python import lockfile
13
from twisted.python.runtime import platform
16
if platform.isWindows():
18
from win32api import OpenProcess
21
skipKill = ("On windows, lockfile.kill is not implemented in the "
22
"absence of win32api and/or pywintypes.")
24
class UtilTests(unittest.TestCase):
26
Tests for the helper functions used to implement L{FilesystemLock}.
28
def test_symlinkEEXIST(self):
30
L{lockfile.symlink} raises L{OSError} with C{errno} set to L{EEXIST}
31
when an attempt is made to create a symlink which already exists.
34
lockfile.symlink('foo', name)
35
exc = self.assertRaises(OSError, lockfile.symlink, 'foo', name)
36
self.assertEqual(exc.errno, errno.EEXIST)
39
def test_symlinkEIOWindows(self):
41
L{lockfile.symlink} raises L{OSError} with C{errno} set to L{EIO} when
42
the underlying L{rename} call fails with L{EIO}.
44
Renaming a file on Windows may fail if the target of the rename is in
45
the process of being deleted (directory deletion appears not to be
49
def fakeRename(src, dst):
50
raise IOError(errno.EIO, None)
51
self.patch(lockfile, 'rename', fakeRename)
52
exc = self.assertRaises(IOError, lockfile.symlink, name, "foo")
53
self.assertEqual(exc.errno, errno.EIO)
54
if not platform.isWindows():
55
test_symlinkEIOWindows.skip = (
56
"special rename EIO handling only necessary and correct on "
60
def test_readlinkENOENT(self):
62
L{lockfile.readlink} raises L{OSError} with C{errno} set to L{ENOENT}
63
when an attempt is made to read a symlink which does not exist.
66
exc = self.assertRaises(OSError, lockfile.readlink, name)
67
self.assertEqual(exc.errno, errno.ENOENT)
70
def test_readlinkEACCESWindows(self):
72
L{lockfile.readlink} raises L{OSError} with C{errno} set to L{EACCES}
73
on Windows when the underlying file open attempt fails with C{EACCES}.
75
Opening a file on Windows may fail if the path is inside a directory
76
which is in the process of being deleted (directory deletion appears
80
def fakeOpen(path, mode):
81
raise IOError(errno.EACCES, None)
82
self.patch(lockfile, '_open', fakeOpen)
83
exc = self.assertRaises(IOError, lockfile.readlink, name)
84
self.assertEqual(exc.errno, errno.EACCES)
85
if not platform.isWindows():
86
test_readlinkEACCESWindows.skip = (
87
"special readlink EACCES handling only necessary and correct on "
93
L{lockfile.kill} returns without error if passed the PID of a
94
process which exists and signal C{0}.
96
lockfile.kill(os.getpid(), 0)
97
test_kill.skip = skipKill
100
def test_killESRCH(self):
102
L{lockfile.kill} raises L{OSError} with errno of L{ESRCH} if
103
passed a PID which does not correspond to any process.
105
# Hopefully there is no process with PID 2 ** 31 - 1
106
exc = self.assertRaises(OSError, lockfile.kill, 2 ** 31 - 1, 0)
107
self.assertEqual(exc.errno, errno.ESRCH)
108
test_killESRCH.skip = skipKill
111
def test_noKillCall(self):
113
Verify that when L{lockfile.kill} does end up as None (e.g. on Windows
114
without pywin32), it doesn't end up being called and raising a
117
self.patch(lockfile, "kill", None)
118
fl = lockfile.FilesystemLock(self.mktemp())
120
self.assertFalse(fl.lock())
124
class LockingTestCase(unittest.TestCase):
125
def _symlinkErrorTest(self, errno):
126
def fakeSymlink(source, dest):
127
raise OSError(errno, None)
128
self.patch(lockfile, 'symlink', fakeSymlink)
130
lockf = self.mktemp()
131
lock = lockfile.FilesystemLock(lockf)
132
exc = self.assertRaises(OSError, lock.lock)
133
self.assertEqual(exc.errno, errno)
136
def test_symlinkError(self):
138
An exception raised by C{symlink} other than C{EEXIST} is passed up to
139
the caller of L{FilesystemLock.lock}.
141
self._symlinkErrorTest(errno.ENOSYS)
144
def test_symlinkErrorPOSIX(self):
146
An L{OSError} raised by C{symlink} on a POSIX platform with an errno of
147
C{EACCES} or C{EIO} is passed to the caller of L{FilesystemLock.lock}.
149
On POSIX, unlike on Windows, these are unexpected errors which cannot
150
be handled by L{FilesystemLock}.
152
self._symlinkErrorTest(errno.EACCES)
153
self._symlinkErrorTest(errno.EIO)
154
if platform.isWindows():
155
test_symlinkErrorPOSIX.skip = (
156
"POSIX-specific error propagation not expected on Windows.")
159
def test_cleanlyAcquire(self):
161
If the lock has never been held, it can be acquired and the C{clean}
162
and C{locked} attributes are set to C{True}.
164
lockf = self.mktemp()
165
lock = lockfile.FilesystemLock(lockf)
166
self.assertTrue(lock.lock())
167
self.assertTrue(lock.clean)
168
self.assertTrue(lock.locked)
171
def test_cleanlyRelease(self):
173
If a lock is released cleanly, it can be re-acquired and the C{clean}
174
and C{locked} attributes are set to C{True}.
176
lockf = self.mktemp()
177
lock = lockfile.FilesystemLock(lockf)
178
self.assertTrue(lock.lock())
180
self.assertFalse(lock.locked)
182
lock = lockfile.FilesystemLock(lockf)
183
self.assertTrue(lock.lock())
184
self.assertTrue(lock.clean)
185
self.assertTrue(lock.locked)
188
def test_cannotLockLocked(self):
190
If a lock is currently locked, it cannot be locked again.
192
lockf = self.mktemp()
193
firstLock = lockfile.FilesystemLock(lockf)
194
self.assertTrue(firstLock.lock())
196
secondLock = lockfile.FilesystemLock(lockf)
197
self.assertFalse(secondLock.lock())
198
self.assertFalse(secondLock.locked)
201
def test_uncleanlyAcquire(self):
203
If a lock was held by a process which no longer exists, it can be
204
acquired, the C{clean} attribute is set to C{False}, and the
205
C{locked} attribute is set to C{True}.
209
def fakeKill(pid, signal):
211
raise OSError(errno.EPERM, None)
213
raise OSError(errno.ESRCH, None)
215
lockf = self.mktemp()
216
self.patch(lockfile, 'kill', fakeKill)
217
lockfile.symlink(str(owner), lockf)
219
lock = lockfile.FilesystemLock(lockf)
220
self.assertTrue(lock.lock())
221
self.assertFalse(lock.clean)
222
self.assertTrue(lock.locked)
224
self.assertEqual(lockfile.readlink(lockf), str(os.getpid()))
227
def test_lockReleasedBeforeCheck(self):
229
If the lock is initially held but then released before it can be
230
examined to determine if the process which held it still exists, it is
231
acquired and the C{clean} and C{locked} attributes are set to C{True}.
233
def fakeReadlink(name):
234
# Pretend to be another process releasing the lock.
235
lockfile.rmlink(lockf)
236
# Fall back to the real implementation of readlink.
237
readlinkPatch.restore()
238
return lockfile.readlink(name)
239
readlinkPatch = self.patch(lockfile, 'readlink', fakeReadlink)
241
def fakeKill(pid, signal):
243
raise OSError(errno.EPERM, None)
245
raise OSError(errno.ESRCH, None)
246
self.patch(lockfile, 'kill', fakeKill)
248
lockf = self.mktemp()
249
lock = lockfile.FilesystemLock(lockf)
250
lockfile.symlink(str(43125), lockf)
251
self.assertTrue(lock.lock())
252
self.assertTrue(lock.clean)
253
self.assertTrue(lock.locked)
256
def test_lockReleasedDuringAcquireSymlink(self):
258
If the lock is released while an attempt is made to acquire
259
it, the lock attempt fails and C{FilesystemLock.lock} returns
260
C{False}. This can happen on Windows when L{lockfile.symlink}
261
fails with L{IOError} of C{EIO} because another process is in
262
the middle of a call to L{os.rmdir} (implemented in terms of
263
RemoveDirectory) which is not atomic.
265
def fakeSymlink(src, dst):
266
# While another process id doing os.rmdir which the Windows
267
# implementation of rmlink does, a rename call will fail with EIO.
268
raise OSError(errno.EIO, None)
270
self.patch(lockfile, 'symlink', fakeSymlink)
272
lockf = self.mktemp()
273
lock = lockfile.FilesystemLock(lockf)
274
self.assertFalse(lock.lock())
275
self.assertFalse(lock.locked)
276
if not platform.isWindows():
277
test_lockReleasedDuringAcquireSymlink.skip = (
278
"special rename EIO handling only necessary and correct on "
282
def test_lockReleasedDuringAcquireReadlink(self):
284
If the lock is initially held but is released while an attempt
285
is made to acquire it, the lock attempt fails and
286
L{FilesystemLock.lock} returns C{False}.
288
def fakeReadlink(name):
289
# While another process is doing os.rmdir which the
290
# Windows implementation of rmlink does, a readlink call
291
# will fail with EACCES.
292
raise IOError(errno.EACCES, None)
293
readlinkPatch = self.patch(lockfile, 'readlink', fakeReadlink)
295
lockf = self.mktemp()
296
lock = lockfile.FilesystemLock(lockf)
297
lockfile.symlink(str(43125), lockf)
298
self.assertFalse(lock.lock())
299
self.assertFalse(lock.locked)
300
if not platform.isWindows():
301
test_lockReleasedDuringAcquireReadlink.skip = (
302
"special readlink EACCES handling only necessary and correct on "
306
def _readlinkErrorTest(self, exceptionType, errno):
307
def fakeReadlink(name):
308
raise exceptionType(errno, None)
309
self.patch(lockfile, 'readlink', fakeReadlink)
311
lockf = self.mktemp()
313
# Make it appear locked so it has to use readlink
314
lockfile.symlink(str(43125), lockf)
316
lock = lockfile.FilesystemLock(lockf)
317
exc = self.assertRaises(exceptionType, lock.lock)
318
self.assertEqual(exc.errno, errno)
319
self.assertFalse(lock.locked)
322
def test_readlinkError(self):
324
An exception raised by C{readlink} other than C{ENOENT} is passed up to
325
the caller of L{FilesystemLock.lock}.
327
self._readlinkErrorTest(OSError, errno.ENOSYS)
328
self._readlinkErrorTest(IOError, errno.ENOSYS)
331
def test_readlinkErrorPOSIX(self):
333
Any L{IOError} raised by C{readlink} on a POSIX platform passed to the
334
caller of L{FilesystemLock.lock}.
336
On POSIX, unlike on Windows, these are unexpected errors which cannot
337
be handled by L{FilesystemLock}.
339
self._readlinkErrorTest(IOError, errno.ENOSYS)
340
self._readlinkErrorTest(IOError, errno.EACCES)
341
if platform.isWindows():
342
test_readlinkErrorPOSIX.skip = (
343
"POSIX-specific error propagation not expected on Windows.")
346
def test_lockCleanedUpConcurrently(self):
348
If a second process cleans up the lock after a first one checks the
349
lock and finds that no process is holding it, the first process does
350
not fail when it tries to clean up the lock.
352
def fakeRmlink(name):
353
rmlinkPatch.restore()
354
# Pretend to be another process cleaning up the lock.
355
lockfile.rmlink(lockf)
356
# Fall back to the real implementation of rmlink.
357
return lockfile.rmlink(name)
358
rmlinkPatch = self.patch(lockfile, 'rmlink', fakeRmlink)
360
def fakeKill(pid, signal):
362
raise OSError(errno.EPERM, None)
364
raise OSError(errno.ESRCH, None)
365
self.patch(lockfile, 'kill', fakeKill)
367
lockf = self.mktemp()
368
lock = lockfile.FilesystemLock(lockf)
369
lockfile.symlink(str(43125), lockf)
370
self.assertTrue(lock.lock())
371
self.assertTrue(lock.clean)
372
self.assertTrue(lock.locked)
375
def test_rmlinkError(self):
377
An exception raised by L{rmlink} other than C{ENOENT} is passed up
378
to the caller of L{FilesystemLock.lock}.
380
def fakeRmlink(name):
381
raise OSError(errno.ENOSYS, None)
382
self.patch(lockfile, 'rmlink', fakeRmlink)
384
def fakeKill(pid, signal):
386
raise OSError(errno.EPERM, None)
388
raise OSError(errno.ESRCH, None)
389
self.patch(lockfile, 'kill', fakeKill)
391
lockf = self.mktemp()
393
# Make it appear locked so it has to use readlink
394
lockfile.symlink(str(43125), lockf)
396
lock = lockfile.FilesystemLock(lockf)
397
exc = self.assertRaises(OSError, lock.lock)
398
self.assertEqual(exc.errno, errno.ENOSYS)
399
self.assertFalse(lock.locked)
402
def test_killError(self):
404
If L{kill} raises an exception other than L{OSError} with errno set to
405
C{ESRCH}, the exception is passed up to the caller of
406
L{FilesystemLock.lock}.
408
def fakeKill(pid, signal):
409
raise OSError(errno.EPERM, None)
410
self.patch(lockfile, 'kill', fakeKill)
412
lockf = self.mktemp()
414
# Make it appear locked so it has to use readlink
415
lockfile.symlink(str(43125), lockf)
417
lock = lockfile.FilesystemLock(lockf)
418
exc = self.assertRaises(OSError, lock.lock)
419
self.assertEqual(exc.errno, errno.EPERM)
420
self.assertFalse(lock.locked)
423
def test_unlockOther(self):
425
L{FilesystemLock.unlock} raises L{ValueError} if called for a lock
426
which is held by a different process.
428
lockf = self.mktemp()
429
lockfile.symlink(str(os.getpid() + 1), lockf)
430
lock = lockfile.FilesystemLock(lockf)
431
self.assertRaises(ValueError, lock.unlock)
434
def test_isLocked(self):
436
L{isLocked} returns C{True} if the named lock is currently locked,
439
lockf = self.mktemp()
440
self.assertFalse(lockfile.isLocked(lockf))
441
lock = lockfile.FilesystemLock(lockf)
442
self.assertTrue(lock.lock())
443
self.assertTrue(lockfile.isLocked(lockf))
445
self.assertFalse(lockfile.isLocked(lockf))