Skip to content

Commit 3de715e

Browse files
committed
refine internal management of plugins and conftest files
1 parent 9e549a1 commit 3de715e

File tree

4 files changed

+85
-39
lines changed

4 files changed

+85
-39
lines changed

_pytest/config.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -682,11 +682,8 @@ def _processopt(self, opt):
682682
setattr(self.option, opt.dest, opt.default)
683683

684684
def _getmatchingplugins(self, fspath):
685-
allconftests = self._conftest._conftestpath2mod.values()
686-
plugins = [x for x in self.pluginmanager.getplugins()
687-
if x not in allconftests]
688-
plugins += self._conftest.getconftestmodules(fspath)
689-
return plugins
685+
return self.pluginmanager._plugins + \
686+
self._conftest.getconftestmodules(fspath)
690687

691688
def pytest_load_initial_conftests(self, early_config):
692689
self._conftest.setinitial(early_config.known_args_namespace)

_pytest/core.py

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def __init__(self, hookspecs=None):
7272
self._name2plugin = {}
7373
self._listattrcache = {}
7474
self._plugins = []
75+
self._conftestplugins = []
7576
self._warnings = []
7677
self.trace = TagTracer().get("pluginmanage")
7778
self._plugin_distinfo = []
@@ -86,7 +87,7 @@ def set_register_callback(self, callback):
8687
assert not hasattr(self, "_registercallback")
8788
self._registercallback = callback
8889

89-
def register(self, plugin, name=None, prepend=False):
90+
def register(self, plugin, name=None, prepend=False, conftest=False):
9091
if self._name2plugin.get(name, None) == -1:
9192
return
9293
name = name or getattr(plugin, '__name__', str(id(plugin)))
@@ -98,16 +99,22 @@ def register(self, plugin, name=None, prepend=False):
9899
reg = getattr(self, "_registercallback", None)
99100
if reg is not None:
100101
reg(plugin, name)
101-
if not prepend:
102-
self._plugins.append(plugin)
102+
if conftest:
103+
self._conftestplugins.append(plugin)
103104
else:
104-
self._plugins.insert(0, plugin)
105+
if not prepend:
106+
self._plugins.append(plugin)
107+
else:
108+
self._plugins.insert(0, plugin)
105109
return True
106110

107111
def unregister(self, plugin=None, name=None):
108112
if plugin is None:
109113
plugin = self.getplugin(name=name)
110-
self._plugins.remove(plugin)
114+
try:
115+
self._plugins.remove(plugin)
116+
except KeyError:
117+
self._conftestplugins.remove(plugin)
111118
for name, value in list(self._name2plugin.items()):
112119
if value == plugin:
113120
del self._name2plugin[name]
@@ -119,7 +126,7 @@ def ensure_shutdown(self):
119126
while self._shutdown:
120127
func = self._shutdown.pop()
121128
func()
122-
self._plugins = []
129+
self._plugins = self._conftestplugins = []
123130
self._name2plugin.clear()
124131
self._listattrcache.clear()
125132

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

136143
def getplugins(self):
137-
return list(self._plugins)
144+
return self._plugins + self._conftestplugins
138145

139146
def skipifmissing(self, name):
140147
if not self.hasplugin(name):
@@ -198,7 +205,8 @@ def consider_pluginarg(self, arg):
198205
self.import_plugin(arg)
199206

200207
def consider_conftest(self, conftestmodule):
201-
if self.register(conftestmodule, name=conftestmodule.__file__):
208+
if self.register(conftestmodule, name=conftestmodule.__file__,
209+
conftest=True):
202210
self.consider_module(conftestmodule)
203211

204212
def consider_module(self, mod):
@@ -233,12 +241,7 @@ def import_plugin(self, modname):
233241

234242
def listattr(self, attrname, plugins=None):
235243
if plugins is None:
236-
plugins = self._plugins
237-
key = (attrname,) + tuple(plugins)
238-
try:
239-
return list(self._listattrcache[key])
240-
except KeyError:
241-
pass
244+
plugins = self._plugins + self._conftestplugins
242245
l = []
243246
last = []
244247
wrappers = []
@@ -257,7 +260,7 @@ def listattr(self, attrname, plugins=None):
257260
l.append(meth)
258261
l.extend(last)
259262
l.extend(wrappers)
260-
self._listattrcache[key] = list(l)
263+
#self._listattrcache[key] = list(l)
261264
return l
262265

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

403+
def _getcaller(self, name, plugins):
404+
caller = getattr(self, name)
405+
methods = self._pm.listattr(name, plugins=plugins)
406+
return CachedHookCaller(caller, methods)
407+
408+
409+
class CachedHookCaller:
410+
def __init__(self, hookmethod, methods):
411+
self.hookmethod = hookmethod
412+
self.methods = methods
413+
414+
def __call__(self, **kwargs):
415+
return self.hookmethod._docall(self.methods, kwargs)
416+
417+
def callextra(self, methods, **kwargs):
418+
# XXX in theory we should respect "tryfirst/trylast" if set
419+
# on the added methods but we currently only use it for
420+
# pytest_generate_tests and it doesn't make sense there i'd think
421+
all = self.methods
422+
if methods:
423+
all = all + methods
424+
return self.hookmethod._docall(all, kwargs)
425+
400426

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

415-
def pcall(self, plugins, **kwargs):
416-
methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
417-
return self._docall(methods, kwargs)
418-
419441
def _docall(self, methods, kwargs):
420442
self.trace(self.name, kwargs)
421443
self.trace.root.indent += 1

_pytest/main.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,39 @@ def pytest_ignore_collect(path, config):
153153
ignore_paths.extend([py.path.local(x) for x in excludeopt])
154154
return path in ignore_paths
155155

156-
class HookProxy(object):
156+
class FSHookProxy(object):
157157
def __init__(self, fspath, config):
158158
self.fspath = fspath
159159
self.config = config
160160

161161
def __getattr__(self, name):
162-
config = object.__getattribute__(self, "config")
163-
hookmethod = getattr(config.hook, name)
164-
165-
def call_matching_hooks(**kwargs):
166-
plugins = self.config._getmatchingplugins(self.fspath)
167-
return hookmethod.pcall(plugins, **kwargs)
168-
return call_matching_hooks
162+
plugins = self.config._getmatchingplugins(self.fspath)
163+
x = self.config.hook._getcaller(name, plugins)
164+
self.__dict__[name] = x
165+
return x
166+
167+
hookmethod = getattr(self.config.hook, name)
168+
methods = self.config.pluginmanager.listattr(name, plugins=plugins)
169+
self.__dict__[name] = x = HookCaller(hookmethod, methods)
170+
return x
171+
172+
173+
class HookCaller:
174+
def __init__(self, hookmethod, methods):
175+
self.hookmethod = hookmethod
176+
self.methods = methods
177+
178+
def __call__(self, **kwargs):
179+
return self.hookmethod._docall(self.methods, kwargs)
180+
181+
def callextra(self, methods, **kwargs):
182+
# XXX in theory we should respect "tryfirst/trylast" if set
183+
# on the added methods but we currently only use it for
184+
# pytest_generate_tests and it doesn't make sense there i'd think
185+
all = self.methods
186+
if methods:
187+
all = all + methods
188+
return self.hookmethod._docall(all, kwargs)
169189

170190
def compatproperty(name):
171191
def fget(self):
@@ -520,6 +540,7 @@ def __init__(self, config):
520540
self.trace = config.trace.root.get("collection")
521541
self._norecursepatterns = config.getini("norecursedirs")
522542
self.startdir = py.path.local()
543+
self._fs2hookproxy = {}
523544

524545
def pytest_collectstart(self):
525546
if self.shouldstop:
@@ -538,7 +559,11 @@ def isinitpath(self, path):
538559
return path in self._initialpaths
539560

540561
def gethookproxy(self, fspath):
541-
return HookProxy(fspath, self.config)
562+
try:
563+
return self._fs2hookproxy[fspath]
564+
except KeyError:
565+
self._fs2hookproxy[fspath] = x = FSHookProxy(fspath, self.config)
566+
return x
542567

543568
def perform_collect(self, args=None, genitems=True):
544569
hook = self.config.hook

_pytest/python.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,14 @@ def _genfunctions(self, name, funcobj):
353353
fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
354354
metafunc = Metafunc(funcobj, fixtureinfo, self.config,
355355
cls=cls, module=module)
356-
gentesthook = self.config.hook.pytest_generate_tests
357-
extra = [module]
358-
if cls is not None:
359-
extra.append(cls())
360-
plugins = self.getplugins() + extra
361-
gentesthook.pcall(plugins, metafunc=metafunc)
356+
try:
357+
methods = [module.pytest_generate_tests]
358+
except AttributeError:
359+
methods = []
360+
if hasattr(cls, "pytest_generate_tests"):
361+
methods.append(cls().pytest_generate_tests)
362+
self.ihook.pytest_generate_tests.callextra(methods, metafunc=metafunc)
363+
362364
Function = self._getcustomclass("Function")
363365
if not metafunc._calls:
364366
yield Function(name, parent=self)

0 commit comments

Comments
 (0)