test-lock.py
296 lines
| 9.6 KiB
| text/x-python
|
PythonLexer
/ tests / test-lock.py
|
r26289 | from __future__ import absolute_import | ||
|
r26386 | import copy | ||
|
r32089 | import errno | ||
|
r26289 | import os | ||
import silenttestrunner | ||||
import tempfile | ||||
|
r26386 | import types | ||
|
r26289 | import unittest | ||
from mercurial import ( | ||||
|
r26498 | error, | ||
|
r26289 | lock, | ||
|
r31249 | vfs as vfsmod, | ||
|
r26289 | ) | ||
testlockname = 'testlock' | ||||
|
r26386 | # work around http://bugs.python.org/issue1515 | ||
if types.MethodType not in copy._deepcopy_dispatch: | ||||
def _deepcopy_method(x, memo): | ||||
return type(x)(x.im_func, copy.deepcopy(x.im_self, memo), x.im_class) | ||||
copy._deepcopy_dispatch[types.MethodType] = _deepcopy_method | ||||
|
r26384 | class lockwrapper(lock.lock): | ||
def __init__(self, pidoffset, *args, **kwargs): | ||||
# lock.lock.__init__() calls lock(), so the pidoffset assignment needs | ||||
# to be earlier | ||||
self._pidoffset = pidoffset | ||||
super(lockwrapper, self).__init__(*args, **kwargs) | ||||
def _getpid(self): | ||||
|
r28027 | return super(lockwrapper, self)._getpid() + self._pidoffset | ||
|
r26384 | |||
|
r26289 | class teststate(object): | ||
|
r26385 | def __init__(self, testcase, dir, pidoffset=0): | ||
|
r26289 | self._testcase = testcase | ||
|
r26321 | self._acquirecalled = False | ||
|
r26289 | self._releasecalled = False | ||
self._postreleasecalled = False | ||||
|
r31249 | self.vfs = vfsmod.vfs(dir, audit=False) | ||
|
r26385 | self._pidoffset = pidoffset | ||
|
r26289 | |||
def makelock(self, *args, **kwargs): | ||||
|
r26385 | l = lockwrapper(self._pidoffset, self.vfs, testlockname, | ||
releasefn=self.releasefn, acquirefn=self.acquirefn, | ||||
*args, **kwargs) | ||||
|
r26289 | l.postrelease.append(self.postreleasefn) | ||
return l | ||||
|
r26321 | def acquirefn(self): | ||
self._acquirecalled = True | ||||
|
r26289 | def releasefn(self): | ||
self._releasecalled = True | ||||
def postreleasefn(self): | ||||
self._postreleasecalled = True | ||||
|
r26321 | def assertacquirecalled(self, called): | ||
self._testcase.assertEqual( | ||||
self._acquirecalled, called, | ||||
'expected acquire to be %s but was actually %s' % ( | ||||
self._tocalled(called), | ||||
self._tocalled(self._acquirecalled), | ||||
)) | ||||
def resetacquirefn(self): | ||||
self._acquirecalled = False | ||||
|
r26289 | def assertreleasecalled(self, called): | ||
self._testcase.assertEqual( | ||||
self._releasecalled, called, | ||||
'expected release to be %s but was actually %s' % ( | ||||
self._tocalled(called), | ||||
self._tocalled(self._releasecalled), | ||||
)) | ||||
def assertpostreleasecalled(self, called): | ||||
self._testcase.assertEqual( | ||||
self._postreleasecalled, called, | ||||
'expected postrelease to be %s but was actually %s' % ( | ||||
self._tocalled(called), | ||||
self._tocalled(self._postreleasecalled), | ||||
)) | ||||
def assertlockexists(self, exists): | ||||
actual = self.vfs.lexists(testlockname) | ||||
self._testcase.assertEqual( | ||||
actual, exists, | ||||
'expected lock to %s but actually did %s' % ( | ||||
self._toexists(exists), | ||||
self._toexists(actual), | ||||
)) | ||||
def _tocalled(self, called): | ||||
if called: | ||||
return 'called' | ||||
else: | ||||
return 'not called' | ||||
def _toexists(self, exists): | ||||
if exists: | ||||
|
r26381 | return 'exist' | ||
|
r26289 | else: | ||
|
r26381 | return 'not exist' | ||
|
r26289 | |||
class testlock(unittest.TestCase): | ||||
def testlock(self): | ||||
|
r26382 | state = teststate(self, tempfile.mkdtemp(dir=os.getcwd())) | ||
|
r26289 | lock = state.makelock() | ||
|
r26321 | state.assertacquirecalled(True) | ||
|
r26289 | lock.release() | ||
state.assertreleasecalled(True) | ||||
state.assertpostreleasecalled(True) | ||||
state.assertlockexists(False) | ||||
def testrecursivelock(self): | ||||
|
r26382 | state = teststate(self, tempfile.mkdtemp(dir=os.getcwd())) | ||
|
r26289 | lock = state.makelock() | ||
|
r26321 | state.assertacquirecalled(True) | ||
state.resetacquirefn() | ||||
|
r26289 | lock.lock() | ||
|
r26321 | # recursive lock should not call acquirefn again | ||
state.assertacquirecalled(False) | ||||
|
r26289 | lock.release() # brings lock refcount down from 2 to 1 | ||
state.assertreleasecalled(False) | ||||
state.assertpostreleasecalled(False) | ||||
state.assertlockexists(True) | ||||
lock.release() # releases the lock | ||||
state.assertreleasecalled(True) | ||||
state.assertpostreleasecalled(True) | ||||
state.assertlockexists(False) | ||||
def testlockfork(self): | ||||
|
r26382 | state = teststate(self, tempfile.mkdtemp(dir=os.getcwd())) | ||
|
r26289 | lock = state.makelock() | ||
|
r26321 | state.assertacquirecalled(True) | ||
|
r26386 | |||
|
r26289 | # fake a fork | ||
|
r26386 | forklock = copy.deepcopy(lock) | ||
forklock._pidoffset = 1 | ||||
forklock.release() | ||||
|
r26289 | state.assertreleasecalled(False) | ||
state.assertpostreleasecalled(False) | ||||
state.assertlockexists(True) | ||||
# release the actual lock | ||||
lock.release() | ||||
state.assertreleasecalled(True) | ||||
state.assertpostreleasecalled(True) | ||||
state.assertlockexists(False) | ||||
|
r26387 | def testinheritlock(self): | ||
d = tempfile.mkdtemp(dir=os.getcwd()) | ||||
parentstate = teststate(self, d) | ||||
parentlock = parentstate.makelock() | ||||
parentstate.assertacquirecalled(True) | ||||
# set up lock inheritance | ||||
|
r26473 | with parentlock.inherit() as lockname: | ||
parentstate.assertreleasecalled(True) | ||||
parentstate.assertpostreleasecalled(False) | ||||
parentstate.assertlockexists(True) | ||||
|
r26387 | |||
|
r26473 | childstate = teststate(self, d, pidoffset=1) | ||
childlock = childstate.makelock(parentlock=lockname) | ||||
childstate.assertacquirecalled(True) | ||||
|
r26387 | |||
|
r26473 | childlock.release() | ||
childstate.assertreleasecalled(True) | ||||
|
r26474 | childstate.assertpostreleasecalled(False) | ||
|
r26473 | childstate.assertlockexists(True) | ||
parentstate.resetacquirefn() | ||||
|
r26387 | parentstate.assertacquirecalled(True) | ||
parentlock.release() | ||||
parentstate.assertreleasecalled(True) | ||||
parentstate.assertpostreleasecalled(True) | ||||
parentstate.assertlockexists(False) | ||||
def testmultilock(self): | ||||
d = tempfile.mkdtemp(dir=os.getcwd()) | ||||
state0 = teststate(self, d) | ||||
lock0 = state0.makelock() | ||||
state0.assertacquirecalled(True) | ||||
|
r26473 | with lock0.inherit() as lock0name: | ||
state0.assertreleasecalled(True) | ||||
state0.assertpostreleasecalled(False) | ||||
state0.assertlockexists(True) | ||||
|
r26387 | |||
|
r26473 | state1 = teststate(self, d, pidoffset=1) | ||
lock1 = state1.makelock(parentlock=lock0name) | ||||
state1.assertacquirecalled(True) | ||||
|
r26387 | |||
|
r26473 | # from within lock1, acquire another lock | ||
with lock1.inherit() as lock1name: | ||||
# since the file on disk is lock0's this should have the same | ||||
# name | ||||
self.assertEqual(lock0name, lock1name) | ||||
|
r26387 | |||
|
r26473 | state2 = teststate(self, d, pidoffset=2) | ||
lock2 = state2.makelock(parentlock=lock1name) | ||||
state2.assertacquirecalled(True) | ||||
|
r26387 | |||
|
r26473 | lock2.release() | ||
state2.assertreleasecalled(True) | ||||
|
r26474 | state2.assertpostreleasecalled(False) | ||
|
r26473 | state2.assertlockexists(True) | ||
|
r26387 | |||
|
r26473 | state1.resetacquirefn() | ||
state1.assertacquirecalled(True) | ||||
|
r26387 | |||
|
r26473 | lock1.release() | ||
state1.assertreleasecalled(True) | ||||
|
r26474 | state1.assertpostreleasecalled(False) | ||
|
r26473 | state1.assertlockexists(True) | ||
|
r26387 | |||
lock0.release() | ||||
def testinheritlockfork(self): | ||||
d = tempfile.mkdtemp(dir=os.getcwd()) | ||||
parentstate = teststate(self, d) | ||||
parentlock = parentstate.makelock() | ||||
parentstate.assertacquirecalled(True) | ||||
# set up lock inheritance | ||||
|
r26473 | with parentlock.inherit() as lockname: | ||
childstate = teststate(self, d, pidoffset=1) | ||||
childlock = childstate.makelock(parentlock=lockname) | ||||
childstate.assertacquirecalled(True) | ||||
|
r26387 | |||
|
r26473 | # fork the child lock | ||
forkchildlock = copy.deepcopy(childlock) | ||||
forkchildlock._pidoffset += 1 | ||||
forkchildlock.release() | ||||
childstate.assertreleasecalled(False) | ||||
childstate.assertpostreleasecalled(False) | ||||
childstate.assertlockexists(True) | ||||
|
r26387 | |||
|
r26473 | # release the child lock | ||
childlock.release() | ||||
childstate.assertreleasecalled(True) | ||||
|
r26474 | childstate.assertpostreleasecalled(False) | ||
|
r26473 | childstate.assertlockexists(True) | ||
|
r26387 | |||
parentlock.release() | ||||
|
r26498 | def testinheritcheck(self): | ||
d = tempfile.mkdtemp(dir=os.getcwd()) | ||||
state = teststate(self, d) | ||||
def check(): | ||||
raise error.LockInheritanceContractViolation('check failed') | ||||
lock = state.makelock(inheritchecker=check) | ||||
state.assertacquirecalled(True) | ||||
|
r32279 | with self.assertRaises(error.LockInheritanceContractViolation): | ||
|
r26499 | with lock.inherit(): | ||
|
r26498 | pass | ||
lock.release() | ||||
|
r32089 | def testfrequentlockunlock(self): | ||
"""This tests whether lock acquisition fails as expected, even if | ||||
(1) lock can't be acquired (makelock fails by EEXIST), and | ||||
(2) locker info can't be read in (readlock fails by ENOENT) while | ||||
retrying 5 times. | ||||
""" | ||||
d = tempfile.mkdtemp(dir=os.getcwd()) | ||||
state = teststate(self, d) | ||||
def emulatefrequentlock(*args): | ||||
raise OSError(errno.EEXIST, "File exists") | ||||
def emulatefrequentunlock(*args): | ||||
raise OSError(errno.ENOENT, "No such file or directory") | ||||
state.vfs.makelock = emulatefrequentlock | ||||
state.vfs.readlock = emulatefrequentunlock | ||||
try: | ||||
state.makelock(timeout=0) | ||||
self.fail("unexpected lock acquisition") | ||||
except error.LockHeld as why: | ||||
self.assertTrue(why.errno == errno.ETIMEDOUT) | ||||
self.assertTrue(why.locker == "") | ||||
state.assertlockexists(False) | ||||
|
r26289 | if __name__ == '__main__': | ||
silenttestrunner.main(__name__) | ||||