remove overhead for tracing of hook calls and remove some old unused code

This commit is contained in:
holger krekel 2014-10-02 15:25:42 +02:00
parent c7c4f62f77
commit 69ff29bf44
5 changed files with 37 additions and 55 deletions

View File

@ -98,7 +98,7 @@ class PytestPluginManager(PluginManager):
err = py.io.dupfile(err, encoding=encoding) err = py.io.dupfile(err, encoding=encoding)
except Exception: except Exception:
pass pass
self.trace.root.setwriter(err.write) self.set_tracing(err.write)
def pytest_configure(self, config): def pytest_configure(self, config):
config.addinivalue_line("markers", config.addinivalue_line("markers",

View File

@ -70,7 +70,6 @@ class TagTracerSub:
class PluginManager(object): class PluginManager(object):
def __init__(self, hookspecs=None, prefix="pytest_"): def __init__(self, hookspecs=None, prefix="pytest_"):
self._name2plugin = {} self._name2plugin = {}
self._listattrcache = {}
self._plugins = [] self._plugins = []
self._conftestplugins = [] self._conftestplugins = []
self._warnings = [] self._warnings = []
@ -79,6 +78,26 @@ class PluginManager(object):
self._shutdown = [] self._shutdown = []
self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix) self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix)
def set_tracing(self, writer):
self.trace.root.setwriter(writer)
# we reconfigure HookCalling to perform tracing
# and we avoid doing the "do we need to trace" check dynamically
# for speed reasons
assert HookCaller._docall.__name__ == "_docall"
real_docall = HookCaller._docall
def docall_tracing(self, methods, kwargs):
trace = self.hookrelay.trace
trace.root.indent += 1
trace(self.name, kwargs)
try:
res = real_docall(self, methods, kwargs)
if res:
trace("finish", self.name, "-->", res)
finally:
trace.root.indent -= 1
return res
HookCaller._docall = docall_tracing
def do_configure(self, config): def do_configure(self, config):
# backward compatibility # backward compatibility
config.do_configure() config.do_configure()
@ -129,7 +148,6 @@ class PluginManager(object):
func() func()
self._plugins = self._conftestplugins = [] self._plugins = self._conftestplugins = []
self._name2plugin.clear() self._name2plugin.clear()
self._listattrcache.clear()
def isregistered(self, plugin, name=None): def isregistered(self, plugin, name=None):
if self.getplugin(name) is not None: if self.getplugin(name) is not None:
@ -261,7 +279,6 @@ class PluginManager(object):
l.append(meth) l.append(meth)
l.extend(last) l.extend(last)
l.extend(wrappers) l.extend(wrappers)
#self._listattrcache[key] = list(l)
return l return l
def call_plugin(self, plugin, methname, kwargs): def call_plugin(self, plugin, methname, kwargs):
@ -336,7 +353,7 @@ class MultiCall:
"wrapper contain more than one yield") "wrapper contain more than one yield")
def varnames(func): def varnames(func, startindex=None):
""" return argument name tuple for a function, method, class or callable. """ return argument name tuple for a function, method, class or callable.
In case of a class, its "__init__" method is considered. In case of a class, its "__init__" method is considered.
@ -353,14 +370,16 @@ def varnames(func):
func = func.__init__ func = func.__init__
except AttributeError: except AttributeError:
return () return ()
ismethod = True startindex = 1
else: else:
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) if startindex is None:
startindex = int(inspect.ismethod(func))
rawcode = py.code.getrawcode(func) rawcode = py.code.getrawcode(func)
try: try:
x = rawcode.co_varnames[ismethod:rawcode.co_argcount] x = rawcode.co_varnames[startindex:rawcode.co_argcount]
except AttributeError: except AttributeError:
x = () x = ()
else: else:
@ -388,12 +407,12 @@ class HookRelay:
def _addhooks(self, hookspec, prefix): def _addhooks(self, hookspec, prefix):
self._hookspecs.append(hookspec) self._hookspecs.append(hookspec)
added = False added = False
for name in dir(hookspec): isclass = int(inspect.isclass(hookspec))
for name, method in vars(hookspec).items():
if name.startswith(prefix): if name.startswith(prefix):
method = getattr(hookspec, name)
firstresult = getattr(method, 'firstresult', False) firstresult = getattr(method, 'firstresult', False)
hc = HookCaller(self, name, firstresult=firstresult, hc = HookCaller(self, name, firstresult=firstresult,
argnames=varnames(method)) argnames=varnames(method, startindex=isclass))
setattr(self, name, hc) setattr(self, name, hc)
added = True added = True
#print ("setting new hook", name) #print ("setting new hook", name)
@ -438,11 +457,10 @@ class HookCaller:
self.hookrelay = hookrelay self.hookrelay = hookrelay
self.name = name self.name = name
self.firstresult = firstresult self.firstresult = firstresult
self.trace = self.hookrelay.trace
self.methods = methods self.methods = methods
self.argnames = ["__multicall__"] self.argnames = ["__multicall__"]
self.argnames.extend(argnames) self.argnames.extend(argnames)
assert "self" not in argnames assert "self" not in argnames # prevent oversights
def new_cached_caller(self, methods): def new_cached_caller(self, methods):
return HookCaller(self.hookrelay, self.name, self.firstresult, return HookCaller(self.hookrelay, self.name, self.firstresult,
@ -461,22 +479,11 @@ class HookCaller:
return self._docall(methods, kwargs) return self._docall(methods, kwargs)
def callextra(self, methods, **kwargs): def callextra(self, methods, **kwargs):
#if self.methods is None:
# self.reload_methods()
return self._docall(self.methods + methods, kwargs) return self._docall(self.methods + methods, kwargs)
def _docall(self, methods, kwargs): def _docall(self, methods, kwargs):
self.trace(self.name, kwargs) return MultiCall(methods, kwargs,
self.trace.root.indent += 1 firstresult=self.firstresult).execute()
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
try:
res = mc.execute()
if res:
self.trace("finish", self.name, "-->", res)
finally:
self.trace.root.indent -= 1
return res
class PluginValidationError(Exception): class PluginValidationError(Exception):
@ -486,13 +493,6 @@ def isgenerichook(name):
return name == "pytest_plugins" or \ return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__") name.startswith("pytest_funcarg__")
def collectattr(obj):
methods = {}
for apiname in dir(obj):
if apiname.startswith("pytest_"):
methods[apiname] = getattr(obj, apiname)
return methods
def formatdef(func): def formatdef(func):
return "%s%s" % ( return "%s%s" % (
func.__name__, func.__name__,

View File

@ -1,8 +1,7 @@
""" version info, help messages, tracing configuration. """ """ version info, help messages, tracing configuration. """
import py import py
import pytest import pytest
import os, inspect, sys import os, sys
from _pytest.core import varnames
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup('debugconfig') group = parser.getgroup('debugconfig')
@ -32,7 +31,7 @@ def pytest_cmdline_parse(__multicall__):
f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %( f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)), pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs)) os.getcwd(), config._origargs))
config.trace.root.setwriter(f.write) config.pluginmanager.set_tracing(f.write)
sys.stderr.write("writing pytestdebug information to %s\n" % path) sys.stderr.write("writing pytestdebug information to %s\n" % path)
return config return config

View File

@ -274,7 +274,7 @@ class TestBootstrapping:
saveindent.append(pm.trace.root.indent) saveindent.append(pm.trace.root.indent)
raise ValueError(42) raise ValueError(42)
l = [] l = []
pm.trace.root.setwriter(l.append) pm.set_tracing(l.append)
indent = pm.trace.root.indent indent = pm.trace.root.indent
p = api1() p = api1()
pm.register(p) pm.register(p)
@ -405,11 +405,7 @@ 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))
methods = pluginmanager.listattr('m') methods = pluginmanager.listattr('m')
assert methods == [p2.m, p1.m, p3.m] assert methods == [p2.m, p1.m, p3.m]

View File

@ -1,5 +1,4 @@
import py, pytest import pytest
from _pytest.core import collectattr
def test_version(testdir, pytestconfig): def test_version(testdir, pytestconfig):
result = testdir.runpytest("--version") result = testdir.runpytest("--version")
@ -25,18 +24,6 @@ def test_help(testdir):
*to see*fixtures*py.test --fixtures* *to see*fixtures*py.test --fixtures*
""") """)
def test_collectattr():
class A:
def pytest_hello(self):
pass
class B(A):
def pytest_world(self):
pass
methods = py.builtin.sorted(collectattr(B))
assert list(methods) == ['pytest_hello', 'pytest_world']
methods = py.builtin.sorted(collectattr(B()))
assert list(methods) == ['pytest_hello', 'pytest_world']
def test_hookvalidation_unknown(testdir): def test_hookvalidation_unknown(testdir):
testdir.makeconftest(""" testdir.makeconftest("""
def pytest_hello(xyz): def pytest_hello(xyz):