introduce historic hook spec which will memorize calls to a hook
in order to call them on later registered plugins --HG-- branch : more_plugin
This commit is contained in:
parent
ea50ef1588
commit
a63585dcab
|
@ -122,8 +122,8 @@ class PytestPluginManager(PluginManager):
|
|||
if ret:
|
||||
if not conftest:
|
||||
self._globalplugins.append(plugin)
|
||||
if hasattr(self, "config"):
|
||||
self.config._register_plugin(plugin, name)
|
||||
self.hook.pytest_plugin_registered(plugin=plugin,
|
||||
manager=self)
|
||||
return ret
|
||||
|
||||
def unregister(self, plugin):
|
||||
|
@ -704,19 +704,11 @@ class Config(object):
|
|||
self._cleanup = []
|
||||
self.pluginmanager.register(self, "pytestconfig")
|
||||
self._configured = False
|
||||
|
||||
def _register_plugin(self, plugin, name):
|
||||
call_plugin = self.pluginmanager.call_plugin
|
||||
call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self.pluginmanager})
|
||||
self.hook.pytest_plugin_registered(plugin=plugin,
|
||||
manager=self.pluginmanager)
|
||||
dic = call_plugin(plugin, "pytest_namespace", {}) or {}
|
||||
if dic:
|
||||
def do_setns(dic):
|
||||
import pytest
|
||||
setns(pytest, dic)
|
||||
call_plugin(plugin, "pytest_addoption", {'parser': self._parser})
|
||||
if self._configured:
|
||||
call_plugin(plugin, "pytest_configure", {'config': self})
|
||||
self.hook.pytest_namespace.call_historic({}, proc=do_setns)
|
||||
self.hook.pytest_addoption.call_historic(dict(parser=self._parser))
|
||||
|
||||
def add_cleanup(self, func):
|
||||
""" Add a function to be called when the config object gets out of
|
||||
|
@ -726,12 +718,13 @@ class Config(object):
|
|||
def _do_configure(self):
|
||||
assert not self._configured
|
||||
self._configured = True
|
||||
self.hook.pytest_configure(config=self)
|
||||
self.hook.pytest_configure.call_historic(dict(config=self))
|
||||
|
||||
def _ensure_unconfigure(self):
|
||||
if self._configured:
|
||||
self._configured = False
|
||||
self.hook.pytest_unconfigure(config=self)
|
||||
self.hook.pytest_configure._call_history = []
|
||||
while self._cleanup:
|
||||
fin = self._cleanup.pop()
|
||||
fin()
|
||||
|
@ -847,6 +840,7 @@ class Config(object):
|
|||
assert not hasattr(self, 'args'), (
|
||||
"can only parse cmdline args at most once per Config object")
|
||||
self._origargs = args
|
||||
self.hook.pytest_addhooks.call_historic(dict(pluginmanager=self.pluginmanager))
|
||||
self._preparse(args)
|
||||
# XXX deprecated hook:
|
||||
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
||||
|
|
|
@ -7,16 +7,23 @@ import py
|
|||
|
||||
py3 = sys.version_info > (3,0)
|
||||
|
||||
def hookspec_opts(firstresult=False):
|
||||
def hookspec_opts(firstresult=False, historic=False):
|
||||
""" returns a decorator which will define a function as a hook specfication.
|
||||
|
||||
If firstresult is True the 1:N hook call (N being the number of registered
|
||||
hook implementation functions) will stop at I<=N when the I'th function
|
||||
returns a non-None result.
|
||||
|
||||
If historic is True calls to a hook will be memorized and replayed
|
||||
on later registered plugins.
|
||||
"""
|
||||
def setattr_hookspec_opts(func):
|
||||
if historic and firstresult:
|
||||
raise ValueError("cannot have a historic firstresult hook")
|
||||
if firstresult:
|
||||
func.firstresult = firstresult
|
||||
if historic:
|
||||
func.historic = historic
|
||||
return func
|
||||
return setattr_hookspec_opts
|
||||
|
||||
|
@ -226,6 +233,7 @@ class PluginManager(object):
|
|||
|
||||
def make_hook_caller(self, name, plugins):
|
||||
caller = getattr(self.hook, name)
|
||||
assert not caller.historic
|
||||
hc = HookCaller(caller.name, plugins, firstresult=caller.firstresult,
|
||||
argnames=caller.argnames)
|
||||
for plugin in hc.plugins:
|
||||
|
@ -272,15 +280,18 @@ class PluginManager(object):
|
|||
if name.startswith(self._prefix):
|
||||
specfunc = module_or_class.__dict__[name]
|
||||
firstresult = getattr(specfunc, 'firstresult', False)
|
||||
historic = getattr(specfunc, 'historic', False)
|
||||
hc = getattr(self.hook, name, None)
|
||||
argnames = varnames(specfunc, startindex=isclass)
|
||||
if hc is None:
|
||||
hc = HookCaller(name, [], firstresult=firstresult,
|
||||
historic=historic,
|
||||
argnames=argnames)
|
||||
setattr(self.hook, name, hc)
|
||||
else:
|
||||
# plugins registered this hook without knowing the spec
|
||||
hc.setspec(firstresult=firstresult, argnames=argnames)
|
||||
hc.setspec(firstresult=firstresult, argnames=argnames,
|
||||
historic=historic)
|
||||
for plugin in hc.plugins:
|
||||
self._verify_hook(hc, specfunc, plugin)
|
||||
hc.add_method(getattr(plugin, name))
|
||||
|
@ -309,11 +320,6 @@ class PluginManager(object):
|
|||
""" Return a plugin or None for the given name. """
|
||||
return self._name2plugin.get(name)
|
||||
|
||||
def call_plugin(self, plugin, methname, kwargs):
|
||||
meth = getattr(plugin, methname, None)
|
||||
if meth is not None:
|
||||
return MultiCall(methods=[meth], kwargs=kwargs, firstresult=True).execute()
|
||||
|
||||
def _scan_plugin(self, plugin):
|
||||
hookcallers = []
|
||||
for name in dir(plugin):
|
||||
|
@ -334,6 +340,7 @@ class PluginManager(object):
|
|||
self._verify_hook(hook, method, plugin)
|
||||
hook.plugins.append(plugin)
|
||||
hook.add_method(method)
|
||||
hook._apply_history(method)
|
||||
hookcallers.append(hook)
|
||||
return hookcallers
|
||||
|
||||
|
@ -441,25 +448,30 @@ class HookRelay:
|
|||
|
||||
|
||||
class HookCaller:
|
||||
def __init__(self, name, plugins, argnames=None, firstresult=None):
|
||||
def __init__(self, name, plugins, argnames=None, firstresult=None,
|
||||
historic=False):
|
||||
self.name = name
|
||||
self.plugins = plugins
|
||||
if argnames is not None:
|
||||
argnames = ["__multicall__"] + list(argnames)
|
||||
self.historic = historic
|
||||
self.argnames = argnames
|
||||
self.firstresult = firstresult
|
||||
self.wrappers = []
|
||||
self.nonwrappers = []
|
||||
if self.historic:
|
||||
self._call_history = []
|
||||
|
||||
@property
|
||||
def pre(self):
|
||||
return self.argnames is None
|
||||
|
||||
def setspec(self, argnames, firstresult):
|
||||
def setspec(self, argnames, firstresult, historic):
|
||||
assert self.pre
|
||||
assert "self" not in argnames # sanity check
|
||||
self.argnames = ["__multicall__"] + list(argnames)
|
||||
self.firstresult = firstresult
|
||||
self.historic = historic
|
||||
|
||||
def remove_plugin(self, plugin):
|
||||
self.plugins.remove(plugin)
|
||||
|
@ -472,6 +484,7 @@ class HookCaller:
|
|||
def add_method(self, meth):
|
||||
assert not self.pre
|
||||
if hasattr(meth, 'hookwrapper'):
|
||||
assert not self.historic
|
||||
self.wrappers.append(meth)
|
||||
elif hasattr(meth, 'trylast'):
|
||||
self.nonwrappers.insert(0, meth)
|
||||
|
@ -493,16 +506,27 @@ class HookCaller:
|
|||
return "<HookCaller %r>" %(self.name,)
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
assert not self.historic
|
||||
return self._docall(self.nonwrappers + self.wrappers, kwargs)
|
||||
|
||||
def callextra(self, methods, **kwargs):
|
||||
assert not self.historic
|
||||
return self._docall(self.nonwrappers + methods + self.wrappers,
|
||||
kwargs)
|
||||
|
||||
def _docall(self, methods, kwargs):
|
||||
assert not self.pre, self.name
|
||||
return MultiCall(methods, kwargs,
|
||||
firstresult=self.firstresult).execute()
|
||||
return MultiCall(methods, kwargs, firstresult=self.firstresult).execute()
|
||||
|
||||
def call_historic(self, kwargs, proc=None):
|
||||
self._call_history.append((kwargs, proc))
|
||||
self._docall(self.nonwrappers + self.wrappers, kwargs)
|
||||
|
||||
def _apply_history(self, meth):
|
||||
if hasattr(self, "_call_history"):
|
||||
for kwargs, proc in self._call_history:
|
||||
res = MultiCall([meth], kwargs, firstresult=True).execute()
|
||||
if proc is not None:
|
||||
proc(res)
|
||||
|
||||
|
||||
class PluginValidationError(Exception):
|
||||
|
|
|
@ -3,27 +3,23 @@
|
|||
from _pytest.core import hookspec_opts
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Initialization
|
||||
# Initialization hooks called for every plugin
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@hookspec_opts(historic=True)
|
||||
def pytest_addhooks(pluginmanager):
|
||||
"""called at plugin load time to allow adding new hooks via a call to
|
||||
"""called at plugin registration time to allow adding new hooks via a call to
|
||||
pluginmanager.addhooks(module_or_class, prefix)."""
|
||||
|
||||
|
||||
@hookspec_opts(historic=True)
|
||||
def pytest_namespace():
|
||||
"""return dict of name->object to be made globally available in
|
||||
the pytest namespace. This hook is called before command line options
|
||||
are parsed.
|
||||
the pytest namespace. This hook is called at plugin registration
|
||||
time.
|
||||
"""
|
||||
|
||||
@hookspec_opts(firstresult=True)
|
||||
def pytest_cmdline_parse(pluginmanager, args):
|
||||
"""return initialized config object, parsing the specified args. """
|
||||
|
||||
def pytest_cmdline_preparse(config, args):
|
||||
"""(deprecated) modify command line arguments before option parsing. """
|
||||
|
||||
@hookspec_opts(historic=True)
|
||||
def pytest_addoption(parser):
|
||||
"""register argparse-style options and ini-style config values.
|
||||
|
||||
|
@ -49,6 +45,26 @@ def pytest_addoption(parser):
|
|||
via (deprecated) ``pytest.config``.
|
||||
"""
|
||||
|
||||
@hookspec_opts(historic=True)
|
||||
def pytest_configure(config):
|
||||
""" called after command line options have been parsed
|
||||
and all plugins and initial conftest files been loaded.
|
||||
This hook is called for every plugin.
|
||||
"""
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Bootstrapping hooks called for plugins registered early enough:
|
||||
# internal and 3rd party plugins as well as directly
|
||||
# discoverable conftest.py local plugins.
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@hookspec_opts(firstresult=True)
|
||||
def pytest_cmdline_parse(pluginmanager, args):
|
||||
"""return initialized config object, parsing the specified args. """
|
||||
|
||||
def pytest_cmdline_preparse(config, args):
|
||||
"""(deprecated) modify command line arguments before option parsing. """
|
||||
|
||||
@hookspec_opts(firstresult=True)
|
||||
def pytest_cmdline_main(config):
|
||||
""" called for performing the main command line action. The default
|
||||
|
@ -58,18 +74,6 @@ def pytest_load_initial_conftests(args, early_config, parser):
|
|||
""" implements the loading of initial conftest files ahead
|
||||
of command line option parsing. """
|
||||
|
||||
def pytest_configure(config):
|
||||
""" called after command line options have been parsed
|
||||
and all plugins and initial conftest files been loaded.
|
||||
"""
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
""" called before test process is exited. """
|
||||
|
||||
@hookspec_opts(firstresult=True)
|
||||
def pytest_runtestloop(session):
|
||||
""" called for performing the main runtest loop
|
||||
(after collection finished). """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# collection hooks
|
||||
|
@ -144,6 +148,12 @@ def pytest_generate_tests(metafunc):
|
|||
# -------------------------------------------------------------------------
|
||||
# generic runtest related hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@hookspec_opts(firstresult=True)
|
||||
def pytest_runtestloop(session):
|
||||
""" called for performing the main runtest loop
|
||||
(after collection finished). """
|
||||
|
||||
def pytest_itemstart(item, node):
|
||||
""" (deprecated, use pytest_runtest_logstart). """
|
||||
|
||||
|
@ -201,6 +211,9 @@ def pytest_sessionstart(session):
|
|||
def pytest_sessionfinish(session, exitstatus):
|
||||
""" whole test run finishes. """
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
""" called before test process is exited. """
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# hooks for customising the assert methods
|
||||
|
|
|
@ -77,6 +77,61 @@ class TestPluginManager:
|
|||
#assert not pm._unverified_hooks
|
||||
assert pm.hook.he_method1(arg=1) == [2]
|
||||
|
||||
def test_register_unknown_hooks(self, pm):
|
||||
class Plugin1:
|
||||
def he_method1(self, arg):
|
||||
return arg + 1
|
||||
|
||||
pm.register(Plugin1())
|
||||
class Hooks:
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
pm.addhooks(Hooks)
|
||||
#assert not pm._unverified_hooks
|
||||
assert pm.hook.he_method1(arg=1) == [2]
|
||||
|
||||
def test_register_historic(self, pm):
|
||||
class Hooks:
|
||||
@hookspec_opts(historic=True)
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
pm.addhooks(Hooks)
|
||||
|
||||
pm.hook.he_method1.call_historic(kwargs=dict(arg=1))
|
||||
l = []
|
||||
class Plugin:
|
||||
def he_method1(self, arg):
|
||||
l.append(arg)
|
||||
|
||||
pm.register(Plugin())
|
||||
assert l == [1]
|
||||
|
||||
class Plugin2:
|
||||
def he_method1(self, arg):
|
||||
l.append(arg*10)
|
||||
pm.register(Plugin2())
|
||||
assert l == [1, 10]
|
||||
pm.hook.he_method1.call_historic(dict(arg=12))
|
||||
assert l == [1, 10, 120, 12]
|
||||
|
||||
def test_with_result_memorized(self, pm):
|
||||
class Hooks:
|
||||
@hookspec_opts(historic=True)
|
||||
def he_method1(self, arg):
|
||||
pass
|
||||
pm.addhooks(Hooks)
|
||||
|
||||
he_method1 = pm.hook.he_method1
|
||||
he_method1.call_historic(proc=lambda res: l.append(res), kwargs=dict(arg=1))
|
||||
l = []
|
||||
class Plugin:
|
||||
def he_method1(self, arg):
|
||||
return arg * 10
|
||||
|
||||
pm.register(Plugin())
|
||||
|
||||
assert l == [10]
|
||||
|
||||
|
||||
class TestAddMethodOrdering:
|
||||
@pytest.fixture
|
||||
|
@ -256,8 +311,10 @@ class TestPytestPluginInteractions:
|
|||
return xyz + 1
|
||||
""")
|
||||
config = get_plugin_manager().config
|
||||
pm = config.pluginmanager
|
||||
pm.hook.pytest_addhooks.call_historic(dict(pluginmanager=config.pluginmanager))
|
||||
config.pluginmanager._importconftest(conf)
|
||||
print(config.pluginmanager.getplugins())
|
||||
#print(config.pluginmanager.getplugins())
|
||||
res = config.hook.pytest_myhook(xyz=10)
|
||||
assert res == [11]
|
||||
|
||||
|
|
Loading…
Reference in New Issue