Skip to content

Commit

Permalink
refine internal management of plugins and conftest files
Browse files Browse the repository at this point in the history
  • Loading branch information
hpk42 committed Oct 1, 2014
1 parent 9e549a1 commit 3de715e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 39 deletions.
7 changes: 2 additions & 5 deletions _pytest/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,11 +682,8 @@ def _processopt(self, opt):
setattr(self.option, opt.dest, opt.default)

def _getmatchingplugins(self, fspath):
allconftests = self._conftest._conftestpath2mod.values()
plugins = [x for x in self.pluginmanager.getplugins()
if x not in allconftests]
plugins += self._conftest.getconftestmodules(fspath)
return plugins
return self.pluginmanager._plugins + \
self._conftest.getconftestmodules(fspath)

def pytest_load_initial_conftests(self, early_config):
self._conftest.setinitial(early_config.known_args_namespace)
Expand Down
60 changes: 41 additions & 19 deletions _pytest/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def __init__(self, hookspecs=None):
self._name2plugin = {}
self._listattrcache = {}
self._plugins = []
self._conftestplugins = []
self._warnings = []
self.trace = TagTracer().get("pluginmanage")
self._plugin_distinfo = []
Expand All @@ -86,7 +87,7 @@ def set_register_callback(self, callback):
assert not hasattr(self, "_registercallback")
self._registercallback = callback

def register(self, plugin, name=None, prepend=False):
def register(self, plugin, name=None, prepend=False, conftest=False):
if self._name2plugin.get(name, None) == -1:
return
name = name or getattr(plugin, '__name__', str(id(plugin)))
Expand All @@ -98,16 +99,22 @@ def register(self, plugin, name=None, prepend=False):
reg = getattr(self, "_registercallback", None)
if reg is not None:
reg(plugin, name)
if not prepend:
self._plugins.append(plugin)
if conftest:
self._conftestplugins.append(plugin)
else:
self._plugins.insert(0, plugin)
if not prepend:
self._plugins.append(plugin)
else:
self._plugins.insert(0, plugin)
return True

def unregister(self, plugin=None, name=None):
if plugin is None:
plugin = self.getplugin(name=name)
self._plugins.remove(plugin)
try:
self._plugins.remove(plugin)
except KeyError:
self._conftestplugins.remove(plugin)
for name, value in list(self._name2plugin.items()):
if value == plugin:
del self._name2plugin[name]
Expand All @@ -119,7 +126,7 @@ def ensure_shutdown(self):
while self._shutdown:
func = self._shutdown.pop()
func()
self._plugins = []
self._plugins = self._conftestplugins = []
self._name2plugin.clear()
self._listattrcache.clear()

Expand All @@ -134,7 +141,7 @@ def addhooks(self, spec, prefix="pytest_"):
self.hook._addhooks(spec, prefix=prefix)

def getplugins(self):
return list(self._plugins)
return self._plugins + self._conftestplugins

def skipifmissing(self, name):
if not self.hasplugin(name):
Expand Down Expand Up @@ -198,7 +205,8 @@ def consider_pluginarg(self, arg):
self.import_plugin(arg)

def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
if self.register(conftestmodule, name=conftestmodule.__file__,
conftest=True):
self.consider_module(conftestmodule)

def consider_module(self, mod):
Expand Down Expand Up @@ -233,12 +241,7 @@ def import_plugin(self, modname):

def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins
key = (attrname,) + tuple(plugins)
try:
return list(self._listattrcache[key])
except KeyError:
pass
plugins = self._plugins + self._conftestplugins
l = []
last = []
wrappers = []
Expand All @@ -257,7 +260,7 @@ def listattr(self, attrname, plugins=None):
l.append(meth)
l.extend(last)
l.extend(wrappers)
self._listattrcache[key] = list(l)
#self._listattrcache[key] = list(l)
return l

def call_plugin(self, plugin, methname, kwargs):
Expand Down Expand Up @@ -397,6 +400,29 @@ def _addhooks(self, hookspecs, prefix):
raise ValueError("did not find new %r hooks in %r" %(
prefix, hookspecs,))

def _getcaller(self, name, plugins):
caller = getattr(self, name)
methods = self._pm.listattr(name, plugins=plugins)
return CachedHookCaller(caller, methods)


class CachedHookCaller:
def __init__(self, hookmethod, methods):
self.hookmethod = hookmethod
self.methods = methods

def __call__(self, **kwargs):
return self.hookmethod._docall(self.methods, kwargs)

def callextra(self, methods, **kwargs):
# XXX in theory we should respect "tryfirst/trylast" if set
# on the added methods but we currently only use it for
# pytest_generate_tests and it doesn't make sense there i'd think
all = self.methods
if methods:
all = all + methods
return self.hookmethod._docall(all, kwargs)


class HookCaller:
def __init__(self, hookrelay, name, firstresult):
Expand All @@ -412,10 +438,6 @@ def __call__(self, **kwargs):
methods = self.hookrelay._pm.listattr(self.name)
return self._docall(methods, kwargs)

def pcall(self, plugins, **kwargs):
methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
return self._docall(methods, kwargs)

def _docall(self, methods, kwargs):
self.trace(self.name, kwargs)
self.trace.root.indent += 1
Expand Down
43 changes: 34 additions & 9 deletions _pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,39 @@ def pytest_ignore_collect(path, config):
ignore_paths.extend([py.path.local(x) for x in excludeopt])
return path in ignore_paths

class HookProxy(object):
class FSHookProxy(object):
def __init__(self, fspath, config):
self.fspath = fspath
self.config = config

def __getattr__(self, name):
config = object.__getattribute__(self, "config")
hookmethod = getattr(config.hook, name)

def call_matching_hooks(**kwargs):
plugins = self.config._getmatchingplugins(self.fspath)
return hookmethod.pcall(plugins, **kwargs)
return call_matching_hooks
plugins = self.config._getmatchingplugins(self.fspath)
x = self.config.hook._getcaller(name, plugins)
self.__dict__[name] = x
return x

hookmethod = getattr(self.config.hook, name)
methods = self.config.pluginmanager.listattr(name, plugins=plugins)
self.__dict__[name] = x = HookCaller(hookmethod, methods)
return x


class HookCaller:
def __init__(self, hookmethod, methods):
self.hookmethod = hookmethod
self.methods = methods

def __call__(self, **kwargs):
return self.hookmethod._docall(self.methods, kwargs)

def callextra(self, methods, **kwargs):
# XXX in theory we should respect "tryfirst/trylast" if set
# on the added methods but we currently only use it for
# pytest_generate_tests and it doesn't make sense there i'd think
all = self.methods
if methods:
all = all + methods
return self.hookmethod._docall(all, kwargs)

def compatproperty(name):
def fget(self):
Expand Down Expand Up @@ -520,6 +540,7 @@ def __init__(self, config):
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local()
self._fs2hookproxy = {}

def pytest_collectstart(self):
if self.shouldstop:
Expand All @@ -538,7 +559,11 @@ def isinitpath(self, path):
return path in self._initialpaths

def gethookproxy(self, fspath):
return HookProxy(fspath, self.config)
try:
return self._fs2hookproxy[fspath]
except KeyError:
self._fs2hookproxy[fspath] = x = FSHookProxy(fspath, self.config)
return x

def perform_collect(self, args=None, genitems=True):
hook = self.config.hook
Expand Down
14 changes: 8 additions & 6 deletions _pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,14 @@ def _genfunctions(self, name, funcobj):
fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
metafunc = Metafunc(funcobj, fixtureinfo, self.config,
cls=cls, module=module)
gentesthook = self.config.hook.pytest_generate_tests
extra = [module]
if cls is not None:
extra.append(cls())
plugins = self.getplugins() + extra
gentesthook.pcall(plugins, metafunc=metafunc)
try:
methods = [module.pytest_generate_tests]
except AttributeError:
methods = []
if hasattr(cls, "pytest_generate_tests"):
methods.append(cls().pytest_generate_tests)
self.ihook.pytest_generate_tests.callextra(methods, metafunc=metafunc)

Function = self._getcustomclass("Function")
if not metafunc._calls:
yield Function(name, parent=self)
Expand Down

0 comments on commit 3de715e

Please sign in to comment.