half the overhead for calling a test function by introducing some caching
This commit is contained in:
parent
5470cadbff
commit
f2670651b3
|
@ -1,6 +1,8 @@
|
||||||
Changes between 2.0.1 and 2.0.2
|
Changes between 2.0.1 and 2.0.2
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
- tackle issue32 - half the overhead for running test functions
|
||||||
|
|
||||||
- fix issue30 - extended xfail/skipif handling and improved reporting.
|
- fix issue30 - extended xfail/skipif handling and improved reporting.
|
||||||
If you have a syntax error in your skip/xfail
|
If you have a syntax error in your skip/xfail
|
||||||
expressions you now get nice error reports.
|
expressions you now get nice error reports.
|
||||||
|
|
|
@ -60,6 +60,7 @@ class TagTracerSub:
|
||||||
class PluginManager(object):
|
class PluginManager(object):
|
||||||
def __init__(self, load=False):
|
def __init__(self, load=False):
|
||||||
self._name2plugin = {}
|
self._name2plugin = {}
|
||||||
|
self._listattrcache = {}
|
||||||
self._plugins = []
|
self._plugins = []
|
||||||
self._hints = []
|
self._hints = []
|
||||||
self.trace = TagTracer().get("pluginmanage")
|
self.trace = TagTracer().get("pluginmanage")
|
||||||
|
@ -272,6 +273,11 @@ class PluginManager(object):
|
||||||
def listattr(self, attrname, plugins=None):
|
def listattr(self, attrname, plugins=None):
|
||||||
if plugins is None:
|
if plugins is None:
|
||||||
plugins = self._plugins
|
plugins = self._plugins
|
||||||
|
key = (attrname,) + tuple(plugins)
|
||||||
|
try:
|
||||||
|
return list(self._listattrcache[key])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
l = []
|
l = []
|
||||||
last = []
|
last = []
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
|
@ -286,6 +292,7 @@ class PluginManager(object):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
l.extend(last)
|
l.extend(last)
|
||||||
|
self._listattrcache[key] = list(l)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def call_plugin(self, plugin, methname, kwargs):
|
def call_plugin(self, plugin, methname, kwargs):
|
||||||
|
@ -340,14 +347,20 @@ class MultiCall:
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def varnames(func):
|
def varnames(func):
|
||||||
|
try:
|
||||||
|
return func._varnames
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
if not inspect.isfunction(func) and not inspect.ismethod(func):
|
if not inspect.isfunction(func) and not inspect.ismethod(func):
|
||||||
func = getattr(func, '__call__', func)
|
func = getattr(func, '__call__', func)
|
||||||
ismethod = inspect.ismethod(func)
|
ismethod = inspect.ismethod(func)
|
||||||
rawcode = py.code.getrawcode(func)
|
rawcode = py.code.getrawcode(func)
|
||||||
try:
|
try:
|
||||||
return rawcode.co_varnames[ismethod:rawcode.co_argcount]
|
x = rawcode.co_varnames[ismethod:rawcode.co_argcount]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return ()
|
x = ()
|
||||||
|
py.builtin._getfuncdict(func)['_varnames'] = x
|
||||||
|
return x
|
||||||
|
|
||||||
class HookRelay:
|
class HookRelay:
|
||||||
def __init__(self, hookspecs, pm, prefix="pytest_"):
|
def __init__(self, hookspecs, pm, prefix="pytest_"):
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
import inspect, sys
|
import inspect, sys
|
||||||
|
from _pytest.core import varnames
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup('debugconfig')
|
group = parser.getgroup('debugconfig')
|
||||||
|
@ -135,12 +136,11 @@ def pytest_plugin_registered(manager, plugin):
|
||||||
fail = True
|
fail = True
|
||||||
else:
|
else:
|
||||||
#print "checking", method
|
#print "checking", method
|
||||||
method_args = getargs(method)
|
method_args = list(varnames(method))
|
||||||
#print "method_args", method_args
|
|
||||||
if '__multicall__' in method_args:
|
if '__multicall__' in method_args:
|
||||||
method_args.remove('__multicall__')
|
method_args.remove('__multicall__')
|
||||||
hook = hooks[name]
|
hook = hooks[name]
|
||||||
hookargs = getargs(hook)
|
hookargs = varnames(hook)
|
||||||
for arg in method_args:
|
for arg in method_args:
|
||||||
if arg not in hookargs:
|
if arg not in hookargs:
|
||||||
Print("argument %r not available" %(arg, ))
|
Print("argument %r not available" %(arg, ))
|
||||||
|
@ -162,11 +162,6 @@ def isgenerichook(name):
|
||||||
return name == "pytest_plugins" or \
|
return name == "pytest_plugins" or \
|
||||||
name.startswith("pytest_funcarg__")
|
name.startswith("pytest_funcarg__")
|
||||||
|
|
||||||
def getargs(func):
|
|
||||||
args = inspect.getargs(py.code.getrawcode(func))[0]
|
|
||||||
startindex = inspect.ismethod(func) and 1 or 0
|
|
||||||
return args[startindex:]
|
|
||||||
|
|
||||||
def collectattr(obj):
|
def collectattr(obj):
|
||||||
methods = {}
|
methods = {}
|
||||||
for apiname in dir(obj):
|
for apiname in dir(obj):
|
||||||
|
|
|
@ -326,7 +326,13 @@ class Item(Node):
|
||||||
return self._location
|
return self._location
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
location = self.reportinfo()
|
location = self.reportinfo()
|
||||||
fspath = self.session.fspath.bestrelpath(location[0])
|
# bestrelpath is a quite slow function
|
||||||
|
cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
|
||||||
|
try:
|
||||||
|
fspath = cache[location[0]]
|
||||||
|
except KeyError:
|
||||||
|
fspath = self.session.fspath.bestrelpath(location[0])
|
||||||
|
cache[location[0]] = fspath
|
||||||
location = (fspath, location[1], str(location[2]))
|
location = (fspath, location[1], str(location[2]))
|
||||||
self._location = location
|
self._location = location
|
||||||
return location
|
return location
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
unit and functional testing with Python.
|
unit and functional testing with Python.
|
||||||
"""
|
"""
|
||||||
__version__ = '2.0.2.dev6'
|
__version__ = '2.0.2.dev7'
|
||||||
__all__ = ['main']
|
__all__ = ['main']
|
||||||
|
|
||||||
from _pytest.core import main, UsageError, _preloadplugins
|
from _pytest.core import main, UsageError, _preloadplugins
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -22,7 +22,7 @@ def main():
|
||||||
name='pytest',
|
name='pytest',
|
||||||
description='py.test: simple powerful testing with Python',
|
description='py.test: simple powerful testing with Python',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version='2.0.2.dev6',
|
version='2.0.2.dev7',
|
||||||
url='http://pytest.org',
|
url='http://pytest.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
|
|
@ -433,6 +433,9 @@ class TestPytestPluginInteractions:
|
||||||
pluginmanager.register(p3)
|
pluginmanager.register(p3)
|
||||||
methods = pluginmanager.listattr('m')
|
methods = pluginmanager.listattr('m')
|
||||||
assert methods == [p2.m, p3.m, p1.m]
|
assert methods == [p2.m, p3.m, p1.m]
|
||||||
|
# listattr keeps a cache and deleting
|
||||||
|
# a function attribute requires clearing it
|
||||||
|
pluginmanager._listattrcache.clear()
|
||||||
del P1.m.__dict__['tryfirst']
|
del P1.m.__dict__['tryfirst']
|
||||||
|
|
||||||
pytest.mark.trylast(getattr(P2.m, 'im_func', P2.m))
|
pytest.mark.trylast(getattr(P2.m, 'im_func', P2.m))
|
||||||
|
|
Loading…
Reference in New Issue