Merged in hpk42/pytest-patches/pluggy1 (pull request #290)
integrate pluggy as external plugin manager
This commit is contained in:
commit
b93abfb3d1
|
@ -27,8 +27,8 @@
|
||||||
details like especially the pluginmanager.add_shutdown() API.
|
details like especially the pluginmanager.add_shutdown() API.
|
||||||
Thanks Holger Krekel.
|
Thanks Holger Krekel.
|
||||||
|
|
||||||
- pluginmanagement: introduce ``pytest.hookimpl_opts`` and
|
- pluginmanagement: introduce ``pytest.hookimpl`` and
|
||||||
``pytest.hookspec_opts`` decorators for setting impl/spec
|
``pytest.hookspec`` decorators for setting impl/spec
|
||||||
specific parameters. This substitutes the previous
|
specific parameters. This substitutes the previous
|
||||||
now deprecated use of ``pytest.mark`` which is meant to
|
now deprecated use of ``pytest.mark`` which is meant to
|
||||||
contain markers for test functions only.
|
contain markers for test functions only.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
#
|
#
|
||||||
__version__ = '2.8.0.dev2'
|
__version__ = '2.8.0.dev3'
|
||||||
|
|
|
@ -29,7 +29,7 @@ def pytest_addoption(parser):
|
||||||
help="shortcut for --capture=no.")
|
help="shortcut for --capture=no.")
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_load_initial_conftests(early_config, parser, args):
|
def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
ns = early_config.known_args_namespace
|
ns = early_config.known_args_namespace
|
||||||
pluginmanager = early_config.pluginmanager
|
pluginmanager = early_config.pluginmanager
|
||||||
|
@ -101,7 +101,7 @@ class CaptureManager:
|
||||||
if capfuncarg is not None:
|
if capfuncarg is not None:
|
||||||
capfuncarg.close()
|
capfuncarg.close()
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_make_collect_report(self, collector):
|
def pytest_make_collect_report(self, collector):
|
||||||
if isinstance(collector, pytest.File):
|
if isinstance(collector, pytest.File):
|
||||||
self.resumecapture()
|
self.resumecapture()
|
||||||
|
@ -115,13 +115,13 @@ class CaptureManager:
|
||||||
else:
|
else:
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_setup(self, item):
|
def pytest_runtest_setup(self, item):
|
||||||
self.resumecapture()
|
self.resumecapture()
|
||||||
yield
|
yield
|
||||||
self.suspendcapture_item(item, "setup")
|
self.suspendcapture_item(item, "setup")
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_call(self, item):
|
def pytest_runtest_call(self, item):
|
||||||
self.resumecapture()
|
self.resumecapture()
|
||||||
self.activate_funcargs(item)
|
self.activate_funcargs(item)
|
||||||
|
@ -129,17 +129,17 @@ class CaptureManager:
|
||||||
#self.deactivate_funcargs() called from suspendcapture()
|
#self.deactivate_funcargs() called from suspendcapture()
|
||||||
self.suspendcapture_item(item, "call")
|
self.suspendcapture_item(item, "call")
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_teardown(self, item):
|
def pytest_runtest_teardown(self, item):
|
||||||
self.resumecapture()
|
self.resumecapture()
|
||||||
yield
|
yield
|
||||||
self.suspendcapture_item(item, "teardown")
|
self.suspendcapture_item(item, "teardown")
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_keyboard_interrupt(self, excinfo):
|
def pytest_keyboard_interrupt(self, excinfo):
|
||||||
self.reset_capturings()
|
self.reset_capturings()
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_internalerror(self, excinfo):
|
def pytest_internalerror(self, excinfo):
|
||||||
self.reset_capturings()
|
self.reset_capturings()
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,11 @@ import warnings
|
||||||
import py
|
import py
|
||||||
# DON't import pytest here because it causes import cycle troubles
|
# DON't import pytest here because it causes import cycle troubles
|
||||||
import sys, os
|
import sys, os
|
||||||
from _pytest import hookspec # the extension point definitions
|
import _pytest.hookspec # the extension point definitions
|
||||||
from _pytest.core import PluginManager, hookimpl_opts, varnames
|
from pluggy import PluginManager, HookimplMarker, HookspecMarker
|
||||||
|
|
||||||
|
hookimpl = HookimplMarker("pytest")
|
||||||
|
hookspec = HookspecMarker("pytest")
|
||||||
|
|
||||||
# pytest startup
|
# pytest startup
|
||||||
#
|
#
|
||||||
|
@ -106,10 +109,10 @@ def exclude_pytest_names(name):
|
||||||
name.startswith("pytest_funcarg__")
|
name.startswith("pytest_funcarg__")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PytestPluginManager(PluginManager):
|
class PytestPluginManager(PluginManager):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(PytestPluginManager, self).__init__(prefix="pytest_",
|
super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
|
||||||
excludefunc=exclude_pytest_names)
|
|
||||||
self._warnings = []
|
self._warnings = []
|
||||||
self._conftest_plugins = set()
|
self._conftest_plugins = set()
|
||||||
|
|
||||||
|
@ -118,7 +121,7 @@ class PytestPluginManager(PluginManager):
|
||||||
self._conftestpath2mod = {}
|
self._conftestpath2mod = {}
|
||||||
self._confcutdir = None
|
self._confcutdir = None
|
||||||
|
|
||||||
self.addhooks(hookspec)
|
self.add_hookspecs(_pytest.hookspec)
|
||||||
self.register(self)
|
self.register(self)
|
||||||
if os.environ.get('PYTEST_DEBUG'):
|
if os.environ.get('PYTEST_DEBUG'):
|
||||||
err = sys.stderr
|
err = sys.stderr
|
||||||
|
@ -130,12 +133,39 @@ class PytestPluginManager(PluginManager):
|
||||||
self.trace.root.setwriter(err.write)
|
self.trace.root.setwriter(err.write)
|
||||||
self.enable_tracing()
|
self.enable_tracing()
|
||||||
|
|
||||||
|
def addhooks(self, module_or_class):
|
||||||
|
warning = dict(code="I2",
|
||||||
|
fslocation=py.code.getfslineno(sys._getframe(1)),
|
||||||
|
message="use pluginmanager.add_hookspecs instead of "
|
||||||
|
"deprecated addhooks() method.")
|
||||||
|
self._warnings.append(warning)
|
||||||
|
return self.add_hookspecs(module_or_class)
|
||||||
|
|
||||||
def _verify_hook(self, hook, plugin):
|
def parse_hookimpl_opts(self, plugin, name):
|
||||||
super(PytestPluginManager, self)._verify_hook(hook, plugin)
|
if exclude_pytest_names(name):
|
||||||
method = getattr(plugin, hook.name)
|
return None
|
||||||
if "__multicall__" in varnames(method):
|
|
||||||
fslineno = py.code.getfslineno(method)
|
method = getattr(plugin, name)
|
||||||
|
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
|
||||||
|
if opts is not None:
|
||||||
|
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
|
||||||
|
opts.setdefault(name, hasattr(method, name))
|
||||||
|
return opts
|
||||||
|
|
||||||
|
def parse_hookspec_opts(self, module_or_class, name):
|
||||||
|
opts = super(PytestPluginManager, self).parse_hookspec_opts(
|
||||||
|
module_or_class, name)
|
||||||
|
if opts is None:
|
||||||
|
method = getattr(module_or_class, name)
|
||||||
|
if name.startswith("pytest_"):
|
||||||
|
opts = {"firstresult": hasattr(method, "firstresult"),
|
||||||
|
"historic": hasattr(method, "historic")}
|
||||||
|
return opts
|
||||||
|
|
||||||
|
def _verify_hook(self, hook, hookmethod):
|
||||||
|
super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
|
||||||
|
if "__multicall__" in hookmethod.argnames:
|
||||||
|
fslineno = py.code.getfslineno(hookmethod.function)
|
||||||
warning = dict(code="I1",
|
warning = dict(code="I1",
|
||||||
fslocation=fslineno,
|
fslocation=fslineno,
|
||||||
message="%r hook uses deprecated __multicall__ "
|
message="%r hook uses deprecated __multicall__ "
|
||||||
|
@ -154,7 +184,7 @@ class PytestPluginManager(PluginManager):
|
||||||
return self.get_plugin(name)
|
return self.get_plugin(name)
|
||||||
|
|
||||||
def pytest_configure(self, config):
|
def pytest_configure(self, config):
|
||||||
# XXX now that the pluginmanager exposes hookimpl_opts(tryfirst...)
|
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
|
||||||
# we should remove tryfirst/trylast as markers
|
# we should remove tryfirst/trylast as markers
|
||||||
config.addinivalue_line("markers",
|
config.addinivalue_line("markers",
|
||||||
"tryfirst: mark a hook implementation function such that the "
|
"tryfirst: mark a hook implementation function such that the "
|
||||||
|
@ -797,7 +827,7 @@ class Config(object):
|
||||||
if not hasattr(self.option, opt.dest):
|
if not hasattr(self.option, opt.dest):
|
||||||
setattr(self.option, opt.dest, opt.default)
|
setattr(self.option, opt.dest, opt.default)
|
||||||
|
|
||||||
@hookimpl_opts(trylast=True)
|
@hookimpl(trylast=True)
|
||||||
def pytest_load_initial_conftests(self, early_config):
|
def pytest_load_initial_conftests(self, early_config):
|
||||||
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
|
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
|
||||||
|
|
||||||
|
|
590
_pytest/core.py
590
_pytest/core.py
|
@ -1,590 +0,0 @@
|
||||||
"""
|
|
||||||
PluginManager, basic initialization and tracing.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from inspect import isfunction, ismethod, isclass, formatargspec, getargspec
|
|
||||||
import py
|
|
||||||
|
|
||||||
py3 = sys.version_info > (3,0)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def hookimpl_opts(hookwrapper=False, optionalhook=False,
|
|
||||||
tryfirst=False, trylast=False):
|
|
||||||
""" Return a decorator which marks a function as a hook implementation.
|
|
||||||
|
|
||||||
If optionalhook is True a missing matching hook specification will not result
|
|
||||||
in an error (by default it is an error if no matching spec is found).
|
|
||||||
|
|
||||||
If tryfirst is True this hook implementation will run as early as possible
|
|
||||||
in the chain of N hook implementations for a specfication.
|
|
||||||
|
|
||||||
If trylast is True this hook implementation will run as late as possible
|
|
||||||
in the chain of N hook implementations.
|
|
||||||
|
|
||||||
If hookwrapper is True the hook implementations needs to execute exactly
|
|
||||||
one "yield". The code before the yield is run early before any non-hookwrapper
|
|
||||||
function is run. The code after the yield is run after all non-hookwrapper
|
|
||||||
function have run. The yield receives an ``CallOutcome`` object representing
|
|
||||||
the exception or result outcome of the inner calls (including other hookwrapper
|
|
||||||
calls).
|
|
||||||
"""
|
|
||||||
def setattr_hookimpl_opts(func):
|
|
||||||
if hookwrapper:
|
|
||||||
func.hookwrapper = True
|
|
||||||
if optionalhook:
|
|
||||||
func.optionalhook = True
|
|
||||||
if tryfirst:
|
|
||||||
func.tryfirst = True
|
|
||||||
if trylast:
|
|
||||||
func.trylast = True
|
|
||||||
return func
|
|
||||||
return setattr_hookimpl_opts
|
|
||||||
|
|
||||||
|
|
||||||
class TagTracer:
|
|
||||||
def __init__(self):
|
|
||||||
self._tag2proc = {}
|
|
||||||
self.writer = None
|
|
||||||
self.indent = 0
|
|
||||||
|
|
||||||
def get(self, name):
|
|
||||||
return TagTracerSub(self, (name,))
|
|
||||||
|
|
||||||
def format_message(self, tags, args):
|
|
||||||
if isinstance(args[-1], dict):
|
|
||||||
extra = args[-1]
|
|
||||||
args = args[:-1]
|
|
||||||
else:
|
|
||||||
extra = {}
|
|
||||||
|
|
||||||
content = " ".join(map(str, args))
|
|
||||||
indent = " " * self.indent
|
|
||||||
|
|
||||||
lines = [
|
|
||||||
"%s%s [%s]\n" %(indent, content, ":".join(tags))
|
|
||||||
]
|
|
||||||
|
|
||||||
for name, value in extra.items():
|
|
||||||
lines.append("%s %s: %s\n" % (indent, name, value))
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def processmessage(self, tags, args):
|
|
||||||
if self.writer is not None and args:
|
|
||||||
lines = self.format_message(tags, args)
|
|
||||||
self.writer(''.join(lines))
|
|
||||||
try:
|
|
||||||
self._tag2proc[tags](tags, args)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setwriter(self, writer):
|
|
||||||
self.writer = writer
|
|
||||||
|
|
||||||
def setprocessor(self, tags, processor):
|
|
||||||
if isinstance(tags, str):
|
|
||||||
tags = tuple(tags.split(":"))
|
|
||||||
else:
|
|
||||||
assert isinstance(tags, tuple)
|
|
||||||
self._tag2proc[tags] = processor
|
|
||||||
|
|
||||||
|
|
||||||
class TagTracerSub:
|
|
||||||
def __init__(self, root, tags):
|
|
||||||
self.root = root
|
|
||||||
self.tags = tags
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
self.root.processmessage(self.tags, args)
|
|
||||||
|
|
||||||
def setmyprocessor(self, processor):
|
|
||||||
self.root.setprocessor(self.tags, processor)
|
|
||||||
|
|
||||||
def get(self, name):
|
|
||||||
return self.__class__(self.root, self.tags + (name,))
|
|
||||||
|
|
||||||
|
|
||||||
def raise_wrapfail(wrap_controller, msg):
|
|
||||||
co = wrap_controller.gi_code
|
|
||||||
raise RuntimeError("wrap_controller at %r %s:%d %s" %
|
|
||||||
(co.co_name, co.co_filename, co.co_firstlineno, msg))
|
|
||||||
|
|
||||||
|
|
||||||
def wrapped_call(wrap_controller, func):
|
|
||||||
""" Wrap calling to a function with a generator which needs to yield
|
|
||||||
exactly once. The yield point will trigger calling the wrapped function
|
|
||||||
and return its CallOutcome to the yield point. The generator then needs
|
|
||||||
to finish (raise StopIteration) in order for the wrapped call to complete.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
next(wrap_controller) # first yield
|
|
||||||
except StopIteration:
|
|
||||||
raise_wrapfail(wrap_controller, "did not yield")
|
|
||||||
call_outcome = CallOutcome(func)
|
|
||||||
try:
|
|
||||||
wrap_controller.send(call_outcome)
|
|
||||||
raise_wrapfail(wrap_controller, "has second yield")
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
return call_outcome.get_result()
|
|
||||||
|
|
||||||
|
|
||||||
class CallOutcome:
|
|
||||||
""" Outcome of a function call, either an exception or a proper result.
|
|
||||||
Calling the ``get_result`` method will return the result or reraise
|
|
||||||
the exception raised when the function was called. """
|
|
||||||
excinfo = None
|
|
||||||
def __init__(self, func):
|
|
||||||
try:
|
|
||||||
self.result = func()
|
|
||||||
except BaseException:
|
|
||||||
self.excinfo = sys.exc_info()
|
|
||||||
|
|
||||||
def force_result(self, result):
|
|
||||||
self.result = result
|
|
||||||
self.excinfo = None
|
|
||||||
|
|
||||||
def get_result(self):
|
|
||||||
if self.excinfo is None:
|
|
||||||
return self.result
|
|
||||||
else:
|
|
||||||
ex = self.excinfo
|
|
||||||
if py3:
|
|
||||||
raise ex[1].with_traceback(ex[2])
|
|
||||||
py.builtin._reraise(*ex)
|
|
||||||
|
|
||||||
|
|
||||||
class TracedHookExecution:
|
|
||||||
def __init__(self, pluginmanager, before, after):
|
|
||||||
self.pluginmanager = pluginmanager
|
|
||||||
self.before = before
|
|
||||||
self.after = after
|
|
||||||
self.oldcall = pluginmanager._inner_hookexec
|
|
||||||
assert not isinstance(self.oldcall, TracedHookExecution)
|
|
||||||
self.pluginmanager._inner_hookexec = self
|
|
||||||
|
|
||||||
def __call__(self, hook, methods, kwargs):
|
|
||||||
self.before(hook, methods, kwargs)
|
|
||||||
outcome = CallOutcome(lambda: self.oldcall(hook, methods, kwargs))
|
|
||||||
self.after(outcome, hook, methods, kwargs)
|
|
||||||
return outcome.get_result()
|
|
||||||
|
|
||||||
def undo(self):
|
|
||||||
self.pluginmanager._inner_hookexec = self.oldcall
|
|
||||||
|
|
||||||
|
|
||||||
class PluginManager(object):
|
|
||||||
""" Core Pluginmanager class which manages registration
|
|
||||||
of plugin objects and 1:N hook calling.
|
|
||||||
|
|
||||||
You can register new hooks by calling ``addhooks(module_or_class)``.
|
|
||||||
You can register plugin objects (which contain hooks) by calling
|
|
||||||
``register(plugin)``. The Pluginmanager is initialized with a
|
|
||||||
prefix that is searched for in the names of the dict of registered
|
|
||||||
plugin objects. An optional excludefunc allows to blacklist names which
|
|
||||||
are not considered as hooks despite a matching prefix.
|
|
||||||
|
|
||||||
For debugging purposes you can call ``enable_tracing()``
|
|
||||||
which will subsequently send debug information to the trace helper.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, prefix, excludefunc=None):
|
|
||||||
self._prefix = prefix
|
|
||||||
self._excludefunc = excludefunc
|
|
||||||
self._name2plugin = {}
|
|
||||||
self._plugin2hookcallers = {}
|
|
||||||
self._plugin_distinfo = []
|
|
||||||
self.trace = TagTracer().get("pluginmanage")
|
|
||||||
self.hook = HookRelay(self.trace.root.get("hook"))
|
|
||||||
self._inner_hookexec = lambda hook, methods, kwargs: \
|
|
||||||
MultiCall(methods, kwargs, hook.firstresult).execute()
|
|
||||||
|
|
||||||
def _hookexec(self, hook, methods, kwargs):
|
|
||||||
# called from all hookcaller instances.
|
|
||||||
# enable_tracing will set its own wrapping function at self._inner_hookexec
|
|
||||||
return self._inner_hookexec(hook, methods, kwargs)
|
|
||||||
|
|
||||||
def enable_tracing(self):
|
|
||||||
""" enable tracing of hook calls and return an undo function. """
|
|
||||||
hooktrace = self.hook._trace
|
|
||||||
|
|
||||||
def before(hook, methods, kwargs):
|
|
||||||
hooktrace.root.indent += 1
|
|
||||||
hooktrace(hook.name, kwargs)
|
|
||||||
|
|
||||||
def after(outcome, hook, methods, kwargs):
|
|
||||||
if outcome.excinfo is None:
|
|
||||||
hooktrace("finish", hook.name, "-->", outcome.result)
|
|
||||||
hooktrace.root.indent -= 1
|
|
||||||
|
|
||||||
return TracedHookExecution(self, before, after).undo
|
|
||||||
|
|
||||||
def subset_hook_caller(self, name, remove_plugins):
|
|
||||||
""" Return a new HookCaller instance for the named method
|
|
||||||
which manages calls to all registered plugins except the
|
|
||||||
ones from remove_plugins. """
|
|
||||||
orig = getattr(self.hook, name)
|
|
||||||
plugins_to_remove = [plugin for plugin in remove_plugins
|
|
||||||
if hasattr(plugin, name)]
|
|
||||||
if plugins_to_remove:
|
|
||||||
hc = HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class)
|
|
||||||
for plugin in orig._plugins:
|
|
||||||
if plugin not in plugins_to_remove:
|
|
||||||
hc._add_plugin(plugin)
|
|
||||||
# we also keep track of this hook caller so it
|
|
||||||
# gets properly removed on plugin unregistration
|
|
||||||
self._plugin2hookcallers.setdefault(plugin, []).append(hc)
|
|
||||||
return hc
|
|
||||||
return orig
|
|
||||||
|
|
||||||
def register(self, plugin, name=None):
|
|
||||||
""" Register a plugin and return its canonical name or None if the name
|
|
||||||
is blocked from registering. Raise a ValueError if the plugin is already
|
|
||||||
registered. """
|
|
||||||
plugin_name = name or self.get_canonical_name(plugin)
|
|
||||||
|
|
||||||
if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers:
|
|
||||||
if self._name2plugin.get(plugin_name, -1) is None:
|
|
||||||
return # blocked plugin, return None to indicate no registration
|
|
||||||
raise ValueError("Plugin already registered: %s=%s\n%s" %(
|
|
||||||
plugin_name, plugin, self._name2plugin))
|
|
||||||
|
|
||||||
self._name2plugin[plugin_name] = plugin
|
|
||||||
|
|
||||||
# register prefix-matching hook specs of the plugin
|
|
||||||
self._plugin2hookcallers[plugin] = hookcallers = []
|
|
||||||
for name in dir(plugin):
|
|
||||||
if name.startswith(self._prefix):
|
|
||||||
hook = getattr(self.hook, name, None)
|
|
||||||
if hook is None:
|
|
||||||
if self._excludefunc is not None and self._excludefunc(name):
|
|
||||||
continue
|
|
||||||
hook = HookCaller(name, self._hookexec)
|
|
||||||
setattr(self.hook, name, hook)
|
|
||||||
elif hook.has_spec():
|
|
||||||
self._verify_hook(hook, plugin)
|
|
||||||
hook._maybe_apply_history(getattr(plugin, name))
|
|
||||||
hookcallers.append(hook)
|
|
||||||
hook._add_plugin(plugin)
|
|
||||||
return plugin_name
|
|
||||||
|
|
||||||
def unregister(self, plugin=None, name=None):
|
|
||||||
""" unregister a plugin object and all its contained hook implementations
|
|
||||||
from internal data structures. """
|
|
||||||
if name is None:
|
|
||||||
assert plugin is not None, "one of name or plugin needs to be specified"
|
|
||||||
name = self.get_name(plugin)
|
|
||||||
|
|
||||||
if plugin is None:
|
|
||||||
plugin = self.get_plugin(name)
|
|
||||||
|
|
||||||
# if self._name2plugin[name] == None registration was blocked: ignore
|
|
||||||
if self._name2plugin.get(name):
|
|
||||||
del self._name2plugin[name]
|
|
||||||
|
|
||||||
for hookcaller in self._plugin2hookcallers.pop(plugin, []):
|
|
||||||
hookcaller._remove_plugin(plugin)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
|
|
||||||
def set_blocked(self, name):
|
|
||||||
""" block registrations of the given name, unregister if already registered. """
|
|
||||||
self.unregister(name=name)
|
|
||||||
self._name2plugin[name] = None
|
|
||||||
|
|
||||||
def addhooks(self, module_or_class):
|
|
||||||
""" add new hook definitions from the given module_or_class using
|
|
||||||
the prefix/excludefunc with which the PluginManager was initialized. """
|
|
||||||
names = []
|
|
||||||
for name in dir(module_or_class):
|
|
||||||
if name.startswith(self._prefix):
|
|
||||||
hc = getattr(self.hook, name, None)
|
|
||||||
if hc is None:
|
|
||||||
hc = HookCaller(name, self._hookexec, module_or_class)
|
|
||||||
setattr(self.hook, name, hc)
|
|
||||||
else:
|
|
||||||
# plugins registered this hook without knowing the spec
|
|
||||||
hc.set_specification(module_or_class)
|
|
||||||
for plugin in hc._plugins:
|
|
||||||
self._verify_hook(hc, plugin)
|
|
||||||
names.append(name)
|
|
||||||
|
|
||||||
if not names:
|
|
||||||
raise ValueError("did not find new %r hooks in %r"
|
|
||||||
%(self._prefix, module_or_class))
|
|
||||||
|
|
||||||
def get_plugins(self):
|
|
||||||
""" return the set of registered plugins. """
|
|
||||||
return set(self._plugin2hookcallers)
|
|
||||||
|
|
||||||
def is_registered(self, plugin):
|
|
||||||
""" Return True if the plugin is already registered. """
|
|
||||||
return plugin in self._plugin2hookcallers
|
|
||||||
|
|
||||||
def get_canonical_name(self, plugin):
|
|
||||||
""" Return canonical name for a plugin object. Note that a plugin
|
|
||||||
may be registered under a different name which was specified
|
|
||||||
by the caller of register(plugin, name). To obtain the name
|
|
||||||
of an registered plugin use ``get_name(plugin)`` instead."""
|
|
||||||
return getattr(plugin, "__name__", None) or str(id(plugin))
|
|
||||||
|
|
||||||
def get_plugin(self, name):
|
|
||||||
""" Return a plugin or None for the given name. """
|
|
||||||
return self._name2plugin.get(name)
|
|
||||||
|
|
||||||
def get_name(self, plugin):
|
|
||||||
""" Return name for registered plugin or None if not registered. """
|
|
||||||
for name, val in self._name2plugin.items():
|
|
||||||
if plugin == val:
|
|
||||||
return name
|
|
||||||
|
|
||||||
def _verify_hook(self, hook, plugin):
|
|
||||||
method = getattr(plugin, hook.name)
|
|
||||||
pluginname = self.get_name(plugin)
|
|
||||||
|
|
||||||
if hook.is_historic() and hasattr(method, "hookwrapper"):
|
|
||||||
raise PluginValidationError(
|
|
||||||
"Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %(
|
|
||||||
pluginname, hook.name))
|
|
||||||
|
|
||||||
for arg in varnames(method):
|
|
||||||
if arg not in hook.argnames:
|
|
||||||
raise PluginValidationError(
|
|
||||||
"Plugin %r\nhook %r\nargument %r not available\n"
|
|
||||||
"plugin definition: %s\n"
|
|
||||||
"available hookargs: %s" %(
|
|
||||||
pluginname, hook.name, arg, formatdef(method),
|
|
||||||
", ".join(hook.argnames)))
|
|
||||||
|
|
||||||
def check_pending(self):
|
|
||||||
""" Verify that all hooks which have not been verified against
|
|
||||||
a hook specification are optional, otherwise raise PluginValidationError"""
|
|
||||||
for name in self.hook.__dict__:
|
|
||||||
if name.startswith(self._prefix):
|
|
||||||
hook = getattr(self.hook, name)
|
|
||||||
if not hook.has_spec():
|
|
||||||
for plugin in hook._plugins:
|
|
||||||
method = getattr(plugin, hook.name)
|
|
||||||
if not getattr(method, "optionalhook", False):
|
|
||||||
raise PluginValidationError(
|
|
||||||
"unknown hook %r in plugin %r" %(name, plugin))
|
|
||||||
|
|
||||||
def load_setuptools_entrypoints(self, entrypoint_name):
|
|
||||||
""" Load modules from querying the specified setuptools entrypoint name.
|
|
||||||
Return the number of loaded plugins. """
|
|
||||||
from pkg_resources import iter_entry_points, DistributionNotFound
|
|
||||||
for ep in iter_entry_points(entrypoint_name):
|
|
||||||
# is the plugin registered or blocked?
|
|
||||||
if self.get_plugin(ep.name) or ep.name in self._name2plugin:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
plugin = ep.load()
|
|
||||||
except DistributionNotFound:
|
|
||||||
continue
|
|
||||||
self.register(plugin, name=ep.name)
|
|
||||||
self._plugin_distinfo.append((ep.dist, plugin))
|
|
||||||
return len(self._plugin_distinfo)
|
|
||||||
|
|
||||||
|
|
||||||
class MultiCall:
|
|
||||||
""" execute a call into multiple python functions/methods. """
|
|
||||||
|
|
||||||
# XXX note that the __multicall__ argument is supported only
|
|
||||||
# for pytest compatibility reasons. It was never officially
|
|
||||||
# supported there and is explicitely deprecated since 2.8
|
|
||||||
# so we can remove it soon, allowing to avoid the below recursion
|
|
||||||
# in execute() and simplify/speed up the execute loop.
|
|
||||||
|
|
||||||
def __init__(self, methods, kwargs, firstresult=False):
|
|
||||||
self.methods = methods
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.kwargs["__multicall__"] = self
|
|
||||||
self.firstresult = firstresult
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
all_kwargs = self.kwargs
|
|
||||||
self.results = results = []
|
|
||||||
firstresult = self.firstresult
|
|
||||||
|
|
||||||
while self.methods:
|
|
||||||
method = self.methods.pop()
|
|
||||||
args = [all_kwargs[argname] for argname in varnames(method)]
|
|
||||||
if hasattr(method, "hookwrapper"):
|
|
||||||
return wrapped_call(method(*args), self.execute)
|
|
||||||
res = method(*args)
|
|
||||||
if res is not None:
|
|
||||||
if firstresult:
|
|
||||||
return res
|
|
||||||
results.append(res)
|
|
||||||
|
|
||||||
if not firstresult:
|
|
||||||
return results
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
status = "%d meths" % (len(self.methods),)
|
|
||||||
if hasattr(self, "results"):
|
|
||||||
status = ("%d results, " % len(self.results)) + status
|
|
||||||
return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def varnames(func, startindex=None):
|
|
||||||
""" return argument name tuple for a function, method, class or callable.
|
|
||||||
|
|
||||||
In case of a class, its "__init__" method is considered.
|
|
||||||
For methods the "self" parameter is not included unless you are passing
|
|
||||||
an unbound method with Python3 (which has no supports for unbound methods)
|
|
||||||
"""
|
|
||||||
cache = getattr(func, "__dict__", {})
|
|
||||||
try:
|
|
||||||
return cache["_varnames"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if isclass(func):
|
|
||||||
try:
|
|
||||||
func = func.__init__
|
|
||||||
except AttributeError:
|
|
||||||
return ()
|
|
||||||
startindex = 1
|
|
||||||
else:
|
|
||||||
if not isfunction(func) and not ismethod(func):
|
|
||||||
func = getattr(func, '__call__', func)
|
|
||||||
if startindex is None:
|
|
||||||
startindex = int(ismethod(func))
|
|
||||||
|
|
||||||
rawcode = py.code.getrawcode(func)
|
|
||||||
try:
|
|
||||||
x = rawcode.co_varnames[startindex:rawcode.co_argcount]
|
|
||||||
except AttributeError:
|
|
||||||
x = ()
|
|
||||||
else:
|
|
||||||
defaults = func.__defaults__
|
|
||||||
if defaults:
|
|
||||||
x = x[:-len(defaults)]
|
|
||||||
try:
|
|
||||||
cache["_varnames"] = x
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
return x
|
|
||||||
|
|
||||||
|
|
||||||
class HookRelay:
|
|
||||||
def __init__(self, trace):
|
|
||||||
self._trace = trace
|
|
||||||
|
|
||||||
|
|
||||||
class HookCaller(object):
|
|
||||||
def __init__(self, name, hook_execute, specmodule_or_class=None):
|
|
||||||
self.name = name
|
|
||||||
self._plugins = []
|
|
||||||
self._wrappers = []
|
|
||||||
self._nonwrappers = []
|
|
||||||
self._hookexec = hook_execute
|
|
||||||
if specmodule_or_class is not None:
|
|
||||||
self.set_specification(specmodule_or_class)
|
|
||||||
|
|
||||||
def has_spec(self):
|
|
||||||
return hasattr(self, "_specmodule_or_class")
|
|
||||||
|
|
||||||
def set_specification(self, specmodule_or_class):
|
|
||||||
assert not self.has_spec()
|
|
||||||
self._specmodule_or_class = specmodule_or_class
|
|
||||||
specfunc = getattr(specmodule_or_class, self.name)
|
|
||||||
argnames = varnames(specfunc, startindex=isclass(specmodule_or_class))
|
|
||||||
assert "self" not in argnames # sanity check
|
|
||||||
self.argnames = ["__multicall__"] + list(argnames)
|
|
||||||
self.firstresult = getattr(specfunc, 'firstresult', False)
|
|
||||||
if hasattr(specfunc, "historic"):
|
|
||||||
self._call_history = []
|
|
||||||
|
|
||||||
def is_historic(self):
|
|
||||||
return hasattr(self, "_call_history")
|
|
||||||
|
|
||||||
def _remove_plugin(self, plugin):
|
|
||||||
self._plugins.remove(plugin)
|
|
||||||
meth = getattr(plugin, self.name)
|
|
||||||
try:
|
|
||||||
self._nonwrappers.remove(meth)
|
|
||||||
except ValueError:
|
|
||||||
self._wrappers.remove(meth)
|
|
||||||
|
|
||||||
def _add_plugin(self, plugin):
|
|
||||||
self._plugins.append(plugin)
|
|
||||||
self._add_method(getattr(plugin, self.name))
|
|
||||||
|
|
||||||
def _add_method(self, meth):
|
|
||||||
if hasattr(meth, 'hookwrapper'):
|
|
||||||
methods = self._wrappers
|
|
||||||
else:
|
|
||||||
methods = self._nonwrappers
|
|
||||||
|
|
||||||
if hasattr(meth, 'trylast'):
|
|
||||||
methods.insert(0, meth)
|
|
||||||
elif hasattr(meth, 'tryfirst'):
|
|
||||||
methods.append(meth)
|
|
||||||
else:
|
|
||||||
# find last non-tryfirst method
|
|
||||||
i = len(methods) - 1
|
|
||||||
while i >= 0 and hasattr(methods[i], "tryfirst"):
|
|
||||||
i -= 1
|
|
||||||
methods.insert(i + 1, meth)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<HookCaller %r>" %(self.name,)
|
|
||||||
|
|
||||||
def __call__(self, **kwargs):
|
|
||||||
assert not self.is_historic()
|
|
||||||
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
|
|
||||||
|
|
||||||
def call_historic(self, proc=None, kwargs=None):
|
|
||||||
self._call_history.append((kwargs or {}, proc))
|
|
||||||
# historizing hooks don't return results
|
|
||||||
self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
|
|
||||||
|
|
||||||
def call_extra(self, methods, kwargs):
|
|
||||||
""" Call the hook with some additional temporarily participating
|
|
||||||
methods using the specified kwargs as call parameters. """
|
|
||||||
old = list(self._nonwrappers), list(self._wrappers)
|
|
||||||
for method in methods:
|
|
||||||
self._add_method(method)
|
|
||||||
try:
|
|
||||||
return self(**kwargs)
|
|
||||||
finally:
|
|
||||||
self._nonwrappers, self._wrappers = old
|
|
||||||
|
|
||||||
def _maybe_apply_history(self, method):
|
|
||||||
if self.is_historic():
|
|
||||||
for kwargs, proc in self._call_history:
|
|
||||||
res = self._hookexec(self, [method], kwargs)
|
|
||||||
if res and proc is not None:
|
|
||||||
proc(res[0])
|
|
||||||
|
|
||||||
|
|
||||||
class PluginValidationError(Exception):
|
|
||||||
""" plugin failed validation. """
|
|
||||||
|
|
||||||
|
|
||||||
def formatdef(func):
|
|
||||||
return "%s%s" % (
|
|
||||||
func.__name__,
|
|
||||||
formatargspec(*getargspec(func))
|
|
||||||
)
|
|
|
@ -4,7 +4,6 @@ import sys
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,6 +32,9 @@ def pkg_to_mapping(name):
|
||||||
for pyfile in toplevel.visit('*.py'):
|
for pyfile in toplevel.visit('*.py'):
|
||||||
pkg = pkgname(name, toplevel, pyfile)
|
pkg = pkgname(name, toplevel, pyfile)
|
||||||
name2src[pkg] = pyfile.read()
|
name2src[pkg] = pyfile.read()
|
||||||
|
# with wheels py source code might be not be installed
|
||||||
|
# and the resulting genscript is useless, just bail out.
|
||||||
|
assert name2src, "no source code found for %r at %r" %(name, toplevel)
|
||||||
return name2src
|
return name2src
|
||||||
|
|
||||||
def compress_mapping(mapping):
|
def compress_mapping(mapping):
|
||||||
|
@ -69,7 +71,7 @@ def pytest_cmdline_main(config):
|
||||||
genscript = config.getvalue("genscript")
|
genscript = config.getvalue("genscript")
|
||||||
if genscript:
|
if genscript:
|
||||||
tw = py.io.TerminalWriter()
|
tw = py.io.TerminalWriter()
|
||||||
deps = ['py', '_pytest', 'pytest']
|
deps = ['py', 'pluggy', '_pytest', 'pytest']
|
||||||
if sys.version_info < (2,7):
|
if sys.version_info < (2,7):
|
||||||
deps.append("argparse")
|
deps.append("argparse")
|
||||||
tw.line("generated script will run on python2.6-python3.3++")
|
tw.line("generated script will run on python2.6-python3.3++")
|
||||||
|
|
|
@ -22,7 +22,7 @@ def pytest_addoption(parser):
|
||||||
help="store internal tracing debug information in 'pytestdebug.log'.")
|
help="store internal tracing debug information in 'pytestdebug.log'.")
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_cmdline_parse():
|
def pytest_cmdline_parse():
|
||||||
outcome = yield
|
outcome = yield
|
||||||
config = outcome.get_result()
|
config = outcome.get_result()
|
||||||
|
@ -96,10 +96,10 @@ conftest_options = [
|
||||||
|
|
||||||
def getpluginversioninfo(config):
|
def getpluginversioninfo(config):
|
||||||
lines = []
|
lines = []
|
||||||
plugininfo = config.pluginmanager._plugin_distinfo
|
plugininfo = config.pluginmanager.list_plugin_distinfo()
|
||||||
if plugininfo:
|
if plugininfo:
|
||||||
lines.append("setuptools registered plugins:")
|
lines.append("setuptools registered plugins:")
|
||||||
for dist, plugin in plugininfo:
|
for plugin, dist in plugininfo:
|
||||||
loc = getattr(plugin, '__file__', repr(plugin))
|
loc = getattr(plugin, '__file__', repr(plugin))
|
||||||
content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
|
content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
|
||||||
lines.append(" " + content)
|
lines.append(" " + content)
|
||||||
|
@ -117,7 +117,7 @@ def pytest_report_header(config):
|
||||||
|
|
||||||
if config.option.traceconfig:
|
if config.option.traceconfig:
|
||||||
lines.append("active plugins:")
|
lines.append("active plugins:")
|
||||||
items = config.pluginmanager._name2plugin.items()
|
items = config.pluginmanager.list_name_plugin()
|
||||||
for name, plugin in items:
|
for name, plugin in items:
|
||||||
if hasattr(plugin, '__file__'):
|
if hasattr(plugin, '__file__'):
|
||||||
r = plugin.__file__
|
r = plugin.__file__
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
||||||
|
|
||||||
from _pytest.core import hookspec_opts
|
from pluggy import HookspecMarker
|
||||||
|
|
||||||
|
hookspec = HookspecMarker("pytest")
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Initialization hooks called for every plugin
|
# Initialization hooks called for every plugin
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
@hookspec_opts(historic=True)
|
@hookspec(historic=True)
|
||||||
def pytest_addhooks(pluginmanager):
|
def pytest_addhooks(pluginmanager):
|
||||||
"""called at plugin registration 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)."""
|
pluginmanager.add_hookspecs(module_or_class, prefix)."""
|
||||||
|
|
||||||
|
|
||||||
@hookspec_opts(historic=True)
|
@hookspec(historic=True)
|
||||||
def pytest_namespace():
|
def pytest_namespace():
|
||||||
"""return dict of name->object to be made globally available in
|
"""return dict of name->object to be made globally available in
|
||||||
the pytest namespace. This hook is called at plugin registration
|
the pytest namespace. This hook is called at plugin registration
|
||||||
time.
|
time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hookspec_opts(historic=True)
|
@hookspec(historic=True)
|
||||||
def pytest_plugin_registered(plugin, manager):
|
def pytest_plugin_registered(plugin, manager):
|
||||||
""" a new pytest plugin got registered. """
|
""" a new pytest plugin got registered. """
|
||||||
|
|
||||||
|
|
||||||
@hookspec_opts(historic=True)
|
@hookspec(historic=True)
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
"""register argparse-style options and ini-style config values.
|
"""register argparse-style options and ini-style config values.
|
||||||
|
|
||||||
|
@ -50,7 +52,7 @@ def pytest_addoption(parser):
|
||||||
via (deprecated) ``pytest.config``.
|
via (deprecated) ``pytest.config``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hookspec_opts(historic=True)
|
@hookspec(historic=True)
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
""" called after command line options have been parsed
|
""" called after command line options have been parsed
|
||||||
and all plugins and initial conftest files been loaded.
|
and all plugins and initial conftest files been loaded.
|
||||||
|
@ -63,14 +65,14 @@ def pytest_configure(config):
|
||||||
# discoverable conftest.py local plugins.
|
# discoverable conftest.py local plugins.
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_cmdline_parse(pluginmanager, args):
|
def pytest_cmdline_parse(pluginmanager, args):
|
||||||
"""return initialized config object, parsing the specified args. """
|
"""return initialized config object, parsing the specified args. """
|
||||||
|
|
||||||
def pytest_cmdline_preparse(config, args):
|
def pytest_cmdline_preparse(config, args):
|
||||||
"""(deprecated) modify command line arguments before option parsing. """
|
"""(deprecated) modify command line arguments before option parsing. """
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
""" called for performing the main command line action. The default
|
""" called for performing the main command line action. The default
|
||||||
implementation will invoke the configure hooks and runtest_mainloop. """
|
implementation will invoke the configure hooks and runtest_mainloop. """
|
||||||
|
@ -84,7 +86,7 @@ def pytest_load_initial_conftests(args, early_config, parser):
|
||||||
# collection hooks
|
# collection hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_collection(session):
|
def pytest_collection(session):
|
||||||
""" perform the collection protocol for the given session. """
|
""" perform the collection protocol for the given session. """
|
||||||
|
|
||||||
|
@ -95,14 +97,14 @@ def pytest_collection_modifyitems(session, config, items):
|
||||||
def pytest_collection_finish(session):
|
def pytest_collection_finish(session):
|
||||||
""" called after collection has been performed and modified. """
|
""" called after collection has been performed and modified. """
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_ignore_collect(path, config):
|
def pytest_ignore_collect(path, config):
|
||||||
""" return True to prevent considering this path for collection.
|
""" return True to prevent considering this path for collection.
|
||||||
This hook is consulted for all files and directories prior to calling
|
This hook is consulted for all files and directories prior to calling
|
||||||
more specific hooks.
|
more specific hooks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_collect_directory(path, parent):
|
def pytest_collect_directory(path, parent):
|
||||||
""" called before traversing a directory for collection files. """
|
""" called before traversing a directory for collection files. """
|
||||||
|
|
||||||
|
@ -123,7 +125,7 @@ def pytest_collectreport(report):
|
||||||
def pytest_deselected(items):
|
def pytest_deselected(items):
|
||||||
""" called for test items deselected by keyword. """
|
""" called for test items deselected by keyword. """
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_make_collect_report(collector):
|
def pytest_make_collect_report(collector):
|
||||||
""" perform ``collector.collect()`` and return a CollectReport. """
|
""" perform ``collector.collect()`` and return a CollectReport. """
|
||||||
|
|
||||||
|
@ -131,7 +133,7 @@ def pytest_make_collect_report(collector):
|
||||||
# Python test function related hooks
|
# Python test function related hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_pycollect_makemodule(path, parent):
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
""" return a Module collector or None for the given path.
|
""" return a Module collector or None for the given path.
|
||||||
This hook will be called for each matching test module path.
|
This hook will be called for each matching test module path.
|
||||||
|
@ -139,11 +141,11 @@ def pytest_pycollect_makemodule(path, parent):
|
||||||
create test modules for files that do not match as a test module.
|
create test modules for files that do not match as a test module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
""" return custom item/collector for a python object in a module, or None. """
|
""" return custom item/collector for a python object in a module, or None. """
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_pyfunc_call(pyfuncitem):
|
def pytest_pyfunc_call(pyfuncitem):
|
||||||
""" call underlying test function. """
|
""" call underlying test function. """
|
||||||
|
|
||||||
|
@ -154,7 +156,7 @@ def pytest_generate_tests(metafunc):
|
||||||
# generic runtest related hooks
|
# generic runtest related hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_runtestloop(session):
|
def pytest_runtestloop(session):
|
||||||
""" called for performing the main runtest loop
|
""" called for performing the main runtest loop
|
||||||
(after collection finished). """
|
(after collection finished). """
|
||||||
|
@ -162,7 +164,7 @@ def pytest_runtestloop(session):
|
||||||
def pytest_itemstart(item, node):
|
def pytest_itemstart(item, node):
|
||||||
""" (deprecated, use pytest_runtest_logstart). """
|
""" (deprecated, use pytest_runtest_logstart). """
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_runtest_protocol(item, nextitem):
|
def pytest_runtest_protocol(item, nextitem):
|
||||||
""" implements the runtest_setup/call/teardown protocol for
|
""" implements the runtest_setup/call/teardown protocol for
|
||||||
the given test item, including capturing exceptions and calling
|
the given test item, including capturing exceptions and calling
|
||||||
|
@ -195,7 +197,7 @@ def pytest_runtest_teardown(item, nextitem):
|
||||||
so that nextitem only needs to call setup-functions.
|
so that nextitem only needs to call setup-functions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
""" return a :py:class:`_pytest.runner.TestReport` object
|
""" return a :py:class:`_pytest.runner.TestReport` object
|
||||||
for the given :py:class:`pytest.Item` and
|
for the given :py:class:`pytest.Item` and
|
||||||
|
@ -240,7 +242,7 @@ def pytest_assertrepr_compare(config, op, left, right):
|
||||||
def pytest_report_header(config, startdir):
|
def pytest_report_header(config, startdir):
|
||||||
""" return a string to be displayed as header info for terminal reporting."""
|
""" return a string to be displayed as header info for terminal reporting."""
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_report_teststatus(report):
|
def pytest_report_teststatus(report):
|
||||||
""" return result-category, shortletter and verbose word for reporting."""
|
""" return result-category, shortletter and verbose word for reporting."""
|
||||||
|
|
||||||
|
@ -256,7 +258,7 @@ def pytest_logwarning(message, code, nodeid, fslocation):
|
||||||
# doctest hooks
|
# doctest hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
@hookspec_opts(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_doctest_prepare_content(content):
|
def pytest_doctest_prepare_content(content):
|
||||||
""" return processed content for a given doctest"""
|
""" return processed content for a given doctest"""
|
||||||
|
|
||||||
|
|
|
@ -501,23 +501,23 @@ class Session(FSCollector):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
FSCollector.__init__(self, config.rootdir, parent=None,
|
FSCollector.__init__(self, config.rootdir, parent=None,
|
||||||
config=config, session=self)
|
config=config, session=self)
|
||||||
self.config.pluginmanager.register(self, name="session")
|
self._fs2hookproxy = {}
|
||||||
self._testsfailed = 0
|
self._testsfailed = 0
|
||||||
self.shouldstop = False
|
self.shouldstop = False
|
||||||
self.trace = config.trace.root.get("collection")
|
self.trace = config.trace.root.get("collection")
|
||||||
self._norecursepatterns = config.getini("norecursedirs")
|
self._norecursepatterns = config.getini("norecursedirs")
|
||||||
self.startdir = py.path.local()
|
self.startdir = py.path.local()
|
||||||
self._fs2hookproxy = {}
|
self.config.pluginmanager.register(self, name="session")
|
||||||
|
|
||||||
def _makeid(self):
|
def _makeid(self):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_collectstart(self):
|
def pytest_collectstart(self):
|
||||||
if self.shouldstop:
|
if self.shouldstop:
|
||||||
raise self.Interrupted(self.shouldstop)
|
raise self.Interrupted(self.shouldstop)
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_runtest_logreport(self, report):
|
def pytest_runtest_logreport(self, report):
|
||||||
if report.failed and not hasattr(report, 'wasxfail'):
|
if report.failed and not hasattr(report, 'wasxfail'):
|
||||||
self._testsfailed += 1
|
self._testsfailed += 1
|
||||||
|
|
|
@ -24,7 +24,7 @@ def pytest_runtest_makereport(item, call):
|
||||||
call.excinfo = call2.excinfo
|
call.excinfo = call2.excinfo
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl_opts(trylast=True)
|
@pytest.hookimpl(trylast=True)
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
if is_potential_nosetest(item):
|
if is_potential_nosetest(item):
|
||||||
if isinstance(item.parent, pytest.Generator):
|
if isinstance(item.parent, pytest.Generator):
|
||||||
|
|
|
@ -11,7 +11,7 @@ def pytest_addoption(parser):
|
||||||
choices=['failed', 'all'],
|
choices=['failed', 'all'],
|
||||||
help="send failed|all info to bpaste.net pastebin service.")
|
help="send failed|all info to bpaste.net pastebin service.")
|
||||||
|
|
||||||
@pytest.hookimpl_opts(trylast=True)
|
@pytest.hookimpl(trylast=True)
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
if config.option.pastebin == "all":
|
if config.option.pastebin == "all":
|
||||||
tr = config.pluginmanager.getplugin('terminalreporter')
|
tr = config.pluginmanager.getplugin('terminalreporter')
|
||||||
|
|
|
@ -13,7 +13,7 @@ import subprocess
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
from py.builtin import print_
|
from py.builtin import print_
|
||||||
from _pytest.core import TracedHookExecution
|
from pluggy import _TracedHookExecution
|
||||||
|
|
||||||
from _pytest.main import Session, EXIT_OK
|
from _pytest.main import Session, EXIT_OK
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ class LsofFdLeakChecker(object):
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_item(self, item):
|
def pytest_runtest_item(self, item):
|
||||||
lines1 = self.get_open_files()
|
lines1 = self.get_open_files()
|
||||||
yield
|
yield
|
||||||
|
@ -198,7 +198,7 @@ class HookRecorder:
|
||||||
self.calls.append(ParsedCall(hook.name, kwargs))
|
self.calls.append(ParsedCall(hook.name, kwargs))
|
||||||
def after(outcome, hook, method, kwargs):
|
def after(outcome, hook, method, kwargs):
|
||||||
pass
|
pass
|
||||||
executor = TracedHookExecution(pluginmanager, before, after)
|
executor = _TracedHookExecution(pluginmanager, before, after)
|
||||||
self._undo_wrapping = executor.undo
|
self._undo_wrapping = executor.undo
|
||||||
|
|
||||||
def finish_recording(self):
|
def finish_recording(self):
|
||||||
|
@ -712,8 +712,20 @@ class Testdir:
|
||||||
option "--runpytest" and return a :py:class:`RunResult`.
|
option "--runpytest" and return a :py:class:`RunResult`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
args = self._ensure_basetemp(args)
|
||||||
return self._runpytest_method(*args, **kwargs)
|
return self._runpytest_method(*args, **kwargs)
|
||||||
|
|
||||||
|
def _ensure_basetemp(self, args):
|
||||||
|
args = [str(x) for x in args]
|
||||||
|
for x in args:
|
||||||
|
if str(x).startswith('--basetemp'):
|
||||||
|
#print ("basedtemp exists: %s" %(args,))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
|
||||||
|
#print ("added basetemp: %s" %(args,))
|
||||||
|
return args
|
||||||
|
|
||||||
def parseconfig(self, *args):
|
def parseconfig(self, *args):
|
||||||
"""Return a new py.test Config instance from given commandline args.
|
"""Return a new py.test Config instance from given commandline args.
|
||||||
|
|
||||||
|
@ -726,12 +738,8 @@ class Testdir:
|
||||||
modules which will be registered with the PluginManager.
|
modules which will be registered with the PluginManager.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args = [str(x) for x in args]
|
args = self._ensure_basetemp(args)
|
||||||
for x in args:
|
|
||||||
if str(x).startswith('--basetemp'):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
|
|
||||||
import _pytest.config
|
import _pytest.config
|
||||||
config = _pytest.config._prepareconfig(args, self.plugins)
|
config = _pytest.config._prepareconfig(args, self.plugins)
|
||||||
# we don't know what the test will do with this half-setup config
|
# we don't know what the test will do with this half-setup config
|
||||||
|
|
|
@ -8,7 +8,11 @@ from _pytest.mark import MarkDecorator, MarkerError
|
||||||
from py._code.code import TerminalRepr
|
from py._code.code import TerminalRepr
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
cutdir = py.path.local(_pytest.__file__).dirpath()
|
import pluggy
|
||||||
|
|
||||||
|
cutdir2 = py.path.local(_pytest.__file__).dirpath()
|
||||||
|
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||||
|
|
||||||
|
|
||||||
NoneType = type(None)
|
NoneType = type(None)
|
||||||
NOTSET = object()
|
NOTSET = object()
|
||||||
|
@ -18,6 +22,11 @@ callable = py.builtin.callable
|
||||||
# used to work around a python2 exception info leak
|
# used to work around a python2 exception info leak
|
||||||
exc_clear = getattr(sys, 'exc_clear', lambda: None)
|
exc_clear = getattr(sys, 'exc_clear', lambda: None)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_traceback(entry):
|
||||||
|
return entry.path != cutdir1 and not entry.path.relto(cutdir2)
|
||||||
|
|
||||||
|
|
||||||
def getfslineno(obj):
|
def getfslineno(obj):
|
||||||
# xxx let decorators etc specify a sane ordering
|
# xxx let decorators etc specify a sane ordering
|
||||||
while hasattr(obj, "__wrapped__"):
|
while hasattr(obj, "__wrapped__"):
|
||||||
|
@ -172,7 +181,7 @@ def pytest_configure(config):
|
||||||
def pytest_sessionstart(session):
|
def pytest_sessionstart(session):
|
||||||
session._fixturemanager = FixtureManager(session)
|
session._fixturemanager = FixtureManager(session)
|
||||||
|
|
||||||
@pytest.hookimpl_opts(trylast=True)
|
@pytest.hookimpl(trylast=True)
|
||||||
def pytest_namespace():
|
def pytest_namespace():
|
||||||
raises.Exception = pytest.fail.Exception
|
raises.Exception = pytest.fail.Exception
|
||||||
return {
|
return {
|
||||||
|
@ -191,7 +200,7 @@ def pytestconfig(request):
|
||||||
return request.config
|
return request.config
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl_opts(trylast=True)
|
@pytest.hookimpl(trylast=True)
|
||||||
def pytest_pyfunc_call(pyfuncitem):
|
def pytest_pyfunc_call(pyfuncitem):
|
||||||
testfunction = pyfuncitem.obj
|
testfunction = pyfuncitem.obj
|
||||||
if pyfuncitem._isyieldedfunction():
|
if pyfuncitem._isyieldedfunction():
|
||||||
|
@ -219,7 +228,7 @@ def pytest_collect_file(path, parent):
|
||||||
def pytest_pycollect_makemodule(path, parent):
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
return Module(path, parent)
|
return Module(path, parent)
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
outcome = yield
|
outcome = yield
|
||||||
res = outcome.get_result()
|
res = outcome.get_result()
|
||||||
|
@ -604,7 +613,11 @@ class FunctionMixin(PyobjMixin):
|
||||||
if ntraceback == traceback:
|
if ntraceback == traceback:
|
||||||
ntraceback = ntraceback.cut(path=path)
|
ntraceback = ntraceback.cut(path=path)
|
||||||
if ntraceback == traceback:
|
if ntraceback == traceback:
|
||||||
ntraceback = ntraceback.cut(excludepath=cutdir)
|
#ntraceback = ntraceback.cut(excludepath=cutdir2)
|
||||||
|
ntraceback = ntraceback.filter(filter_traceback)
|
||||||
|
if not ntraceback:
|
||||||
|
ntraceback = traceback
|
||||||
|
|
||||||
excinfo.traceback = ntraceback.filter()
|
excinfo.traceback = ntraceback.filter()
|
||||||
# issue364: mark all but first and last frames to
|
# issue364: mark all but first and last frames to
|
||||||
# only show a single-line message for each frame
|
# only show a single-line message for each frame
|
||||||
|
|
|
@ -133,7 +133,7 @@ class MarkEvaluator:
|
||||||
return expl
|
return expl
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
evalskip = MarkEvaluator(item, 'skipif')
|
evalskip = MarkEvaluator(item, 'skipif')
|
||||||
if evalskip.istrue():
|
if evalskip.istrue():
|
||||||
|
@ -151,7 +151,7 @@ def check_xfail_no_run(item):
|
||||||
if not evalxfail.get('run', True):
|
if not evalxfail.get('run', True):
|
||||||
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
|
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
outcome = yield
|
outcome = yield
|
||||||
rep = outcome.get_result()
|
rep = outcome.get_result()
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
This is a good source for looking at the various reporting hooks.
|
This is a good source for looking at the various reporting hooks.
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
import pluggy
|
||||||
import py
|
import py
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
@ -267,7 +268,7 @@ class TerminalReporter:
|
||||||
def pytest_collection_modifyitems(self):
|
def pytest_collection_modifyitems(self):
|
||||||
self.report_collect(True)
|
self.report_collect(True)
|
||||||
|
|
||||||
@pytest.hookimpl_opts(trylast=True)
|
@pytest.hookimpl(trylast=True)
|
||||||
def pytest_sessionstart(self, session):
|
def pytest_sessionstart(self, session):
|
||||||
self._sessionstarttime = time.time()
|
self._sessionstarttime = time.time()
|
||||||
if not self.showheader:
|
if not self.showheader:
|
||||||
|
@ -278,7 +279,8 @@ class TerminalReporter:
|
||||||
if hasattr(sys, 'pypy_version_info'):
|
if hasattr(sys, 'pypy_version_info'):
|
||||||
verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
|
verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
|
||||||
msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
|
msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
|
||||||
msg += " -- py-%s -- pytest-%s" % (py.__version__, pytest.__version__)
|
msg += ", pytest-%s, py-%s, pluggy-%s" % (
|
||||||
|
pytest.__version__, py.__version__, pluggy.__version__)
|
||||||
if self.verbosity > 0 or self.config.option.debug or \
|
if self.verbosity > 0 or self.config.option.debug or \
|
||||||
getattr(self.config.option, 'pastebin', None):
|
getattr(self.config.option, 'pastebin', None):
|
||||||
msg += " -- " + str(sys.executable)
|
msg += " -- " + str(sys.executable)
|
||||||
|
@ -294,10 +296,11 @@ class TerminalReporter:
|
||||||
if config.inifile:
|
if config.inifile:
|
||||||
inifile = config.rootdir.bestrelpath(config.inifile)
|
inifile = config.rootdir.bestrelpath(config.inifile)
|
||||||
lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
|
lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
|
||||||
plugininfo = config.pluginmanager._plugin_distinfo
|
|
||||||
|
plugininfo = config.pluginmanager.list_plugin_distinfo()
|
||||||
if plugininfo:
|
if plugininfo:
|
||||||
l = []
|
l = []
|
||||||
for dist, plugin in plugininfo:
|
for plugin, dist in plugininfo:
|
||||||
name = dist.project_name
|
name = dist.project_name
|
||||||
if name.startswith("pytest-"):
|
if name.startswith("pytest-"):
|
||||||
name = name[7:]
|
name = name[7:]
|
||||||
|
@ -352,7 +355,7 @@ class TerminalReporter:
|
||||||
indent = (len(stack) - 1) * " "
|
indent = (len(stack) - 1) * " "
|
||||||
self._tw.line("%s%s" % (indent, col))
|
self._tw.line("%s%s" % (indent, col))
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_sessionfinish(self, exitstatus):
|
def pytest_sessionfinish(self, exitstatus):
|
||||||
outcome = yield
|
outcome = yield
|
||||||
outcome.get_result()
|
outcome.get_result()
|
||||||
|
|
|
@ -140,7 +140,7 @@ class TestCaseFunction(pytest.Function):
|
||||||
if traceback:
|
if traceback:
|
||||||
excinfo.traceback = traceback
|
excinfo.traceback = traceback
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
if isinstance(item, TestCaseFunction):
|
if isinstance(item, TestCaseFunction):
|
||||||
if item._excinfo:
|
if item._excinfo:
|
||||||
|
@ -152,7 +152,7 @@ def pytest_runtest_makereport(item, call):
|
||||||
|
|
||||||
# twisted trial support
|
# twisted trial support
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item):
|
||||||
if isinstance(item, TestCaseFunction) and \
|
if isinstance(item, TestCaseFunction) and \
|
||||||
'twisted.trial.unittest' in sys.modules:
|
'twisted.trial.unittest' in sys.modules:
|
||||||
|
|
|
@ -201,9 +201,9 @@ You can ask which markers exist for your test suite - the list includes our just
|
||||||
|
|
||||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
|
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
@pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
||||||
|
|
||||||
@pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
@pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
||||||
|
|
||||||
|
|
||||||
For an example on how to add and work with markers from a plugin, see
|
For an example on how to add and work with markers from a plugin, see
|
||||||
|
@ -375,9 +375,9 @@ The ``--markers`` option always gives you a list of available markers::
|
||||||
|
|
||||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
|
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
@pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
||||||
|
|
||||||
@pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
@pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
||||||
|
|
||||||
|
|
||||||
Reading markers which were set from multiple places
|
Reading markers which were set from multiple places
|
||||||
|
|
|
@ -534,7 +534,7 @@ case we just write some informations out to a ``failures`` file::
|
||||||
import pytest
|
import pytest
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_runtest_makereport(item, call, __multicall__):
|
def pytest_runtest_makereport(item, call, __multicall__):
|
||||||
# execute all other hooks to obtain the report object
|
# execute all other hooks to obtain the report object
|
||||||
rep = __multicall__.execute()
|
rep = __multicall__.execute()
|
||||||
|
@ -607,7 +607,7 @@ here is a little example implemented via a local plugin::
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@pytest.hookimpl_opts(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_runtest_makereport(item, call, __multicall__):
|
def pytest_runtest_makereport(item, call, __multicall__):
|
||||||
# execute all other hooks to obtain the report object
|
# execute all other hooks to obtain the report object
|
||||||
rep = __multicall__.execute()
|
rep = __multicall__.execute()
|
||||||
|
|
|
@ -169,6 +169,14 @@ You can tell people to download the script and then e.g. run it like this::
|
||||||
|
|
||||||
python runtests.py
|
python runtests.py
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You must have pytest and its dependencies installed as an sdist, not
|
||||||
|
as wheels because genscript need the source code for generating a
|
||||||
|
standalone script.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Integrating with distutils / ``python setup.py test``
|
Integrating with distutils / ``python setup.py test``
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|
|
@ -292,7 +292,7 @@ Here is an example definition of a hook wrapper::
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_pyfunc_call(pyfuncitem):
|
def pytest_pyfunc_call(pyfuncitem):
|
||||||
# do whatever you want before the next hook executes
|
# do whatever you want before the next hook executes
|
||||||
|
|
||||||
|
@ -305,8 +305,7 @@ Here is an example definition of a hook wrapper::
|
||||||
Note that hook wrappers don't return results themselves, they merely
|
Note that hook wrappers don't return results themselves, they merely
|
||||||
perform tracing or other side effects around the actual hook implementations.
|
perform tracing or other side effects around the actual hook implementations.
|
||||||
If the result of the underlying hook is a mutable object, they may modify
|
If the result of the underlying hook is a mutable object, they may modify
|
||||||
that result, however.
|
that result but it's probably better to avoid it.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Hook function ordering / call example
|
Hook function ordering / call example
|
||||||
|
@ -338,16 +337,24 @@ after others, i.e. the position in the ``N``-sized list of functions::
|
||||||
Here is the order of execution:
|
Here is the order of execution:
|
||||||
|
|
||||||
1. Plugin3's pytest_collection_modifyitems called until the yield point
|
1. Plugin3's pytest_collection_modifyitems called until the yield point
|
||||||
2. Plugin1's pytest_collection_modifyitems is called
|
because it is a hook wrapper.
|
||||||
3. Plugin2's pytest_collection_modifyitems is called
|
|
||||||
4. Plugin3's pytest_collection_modifyitems called for executing after the yield
|
2. Plugin1's pytest_collection_modifyitems is called because it is marked
|
||||||
The yield receives a :py:class:`CallOutcome` instance which encapsulates
|
with ``tryfirst=True``.
|
||||||
the result from calling the non-wrappers. Wrappers cannot modify the result.
|
|
||||||
|
3. Plugin2's pytest_collection_modifyitems is called because it is marked
|
||||||
|
with ``trylast=True`` (but even without this mark it would come after
|
||||||
|
Plugin1).
|
||||||
|
|
||||||
|
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
|
||||||
|
point. The yield receives a :py:class:`CallOutcome` instance which encapsulates
|
||||||
|
the result from calling the non-wrappers. Wrappers shall not modify the result.
|
||||||
|
|
||||||
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
|
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
|
||||||
``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
|
``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
|
||||||
among each other.
|
among each other.
|
||||||
|
|
||||||
|
|
||||||
Declaring new hooks
|
Declaring new hooks
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
@ -368,11 +375,11 @@ For an example, see `newhooks.py`_ from :ref:`xdist`.
|
||||||
.. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default
|
.. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default
|
||||||
|
|
||||||
|
|
||||||
Using hooks from 3rd party plugins
|
Optionally using hooks from 3rd party plugins
|
||||||
-------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
Using new hooks from plugins as explained above might be a little tricky
|
Using new hooks from plugins as explained above might be a little tricky
|
||||||
because the standard :ref:`validation mechanism <validation>`:
|
because of the standard :ref:`validation mechanism <validation>`:
|
||||||
if you depend on a plugin that is not installed, validation will fail and
|
if you depend on a plugin that is not installed, validation will fail and
|
||||||
the error message will not make much sense to your users.
|
the error message will not make much sense to your users.
|
||||||
|
|
||||||
|
@ -395,7 +402,6 @@ declaring the hook functions directly in your plugin module, for example::
|
||||||
This has the added benefit of allowing you to conditionally install hooks
|
This has the added benefit of allowing you to conditionally install hooks
|
||||||
depending on which plugins are installed.
|
depending on which plugins are installed.
|
||||||
|
|
||||||
|
|
||||||
.. _`well specified hooks`:
|
.. _`well specified hooks`:
|
||||||
|
|
||||||
.. currentmodule:: _pytest.hookspec
|
.. currentmodule:: _pytest.hookspec
|
||||||
|
|
|
@ -11,8 +11,10 @@ if __name__ == '__main__': # if run as a script or by 'python -m pytest'
|
||||||
|
|
||||||
# else we are imported
|
# else we are imported
|
||||||
|
|
||||||
from _pytest.config import main, UsageError, _preloadplugins, cmdline
|
from _pytest.config import (
|
||||||
from _pytest.core import hookspec_opts, hookimpl_opts
|
main, UsageError, _preloadplugins, cmdline,
|
||||||
|
hookspec, hookimpl
|
||||||
|
)
|
||||||
from _pytest import __version__
|
from _pytest import __version__
|
||||||
|
|
||||||
_preloadplugins() # to populate pytest.* namespace so help(pytest) works
|
_preloadplugins() # to populate pytest.* namespace so help(pytest) works
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -48,7 +48,7 @@ def has_environment_marker_support():
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
install_requires = ['py>=1.4.27.dev2']
|
install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0']
|
||||||
extras_require = {}
|
extras_require = {}
|
||||||
if has_environment_marker_support():
|
if has_environment_marker_support():
|
||||||
extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse']
|
extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse']
|
||||||
|
|
|
@ -559,7 +559,7 @@ class TestConftestCustomization:
|
||||||
b = testdir.mkdir("a").mkdir("b")
|
b = testdir.mkdir("a").mkdir("b")
|
||||||
b.join("conftest.py").write(py.code.Source("""
|
b.join("conftest.py").write(py.code.Source("""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_pycollect_makeitem():
|
def pytest_pycollect_makeitem():
|
||||||
outcome = yield
|
outcome = yield
|
||||||
if outcome.excinfo is None:
|
if outcome.excinfo is None:
|
||||||
|
|
|
@ -357,9 +357,9 @@ def test_load_initial_conftest_last_ordering(testdir):
|
||||||
pm.register(m)
|
pm.register(m)
|
||||||
hc = pm.hook.pytest_load_initial_conftests
|
hc = pm.hook.pytest_load_initial_conftests
|
||||||
l = hc._nonwrappers + hc._wrappers
|
l = hc._nonwrappers + hc._wrappers
|
||||||
assert l[-1].__module__ == "_pytest.capture"
|
assert l[-1].function.__module__ == "_pytest.capture"
|
||||||
assert l[-2] == m.pytest_load_initial_conftests
|
assert l[-2].function == m.pytest_load_initial_conftests
|
||||||
assert l[-3].__module__ == "_pytest.config"
|
assert l[-3].function.__module__ == "_pytest.config"
|
||||||
|
|
||||||
class TestWarning:
|
class TestWarning:
|
||||||
def test_warn_config(self, testdir):
|
def test_warn_config(self, testdir):
|
||||||
|
|
1050
testing/test_core.py
1050
testing/test_core.py
File diff suppressed because it is too large
Load Diff
|
@ -7,7 +7,7 @@ def test_version(testdir, pytestconfig):
|
||||||
result.stderr.fnmatch_lines([
|
result.stderr.fnmatch_lines([
|
||||||
'*pytest*%s*imported from*' % (pytest.__version__, )
|
'*pytest*%s*imported from*' % (pytest.__version__, )
|
||||||
])
|
])
|
||||||
if pytestconfig.pluginmanager._plugin_distinfo:
|
if pytestconfig.pluginmanager.list_plugin_distinfo():
|
||||||
result.stderr.fnmatch_lines([
|
result.stderr.fnmatch_lines([
|
||||||
"*setuptools registered plugins:",
|
"*setuptools registered plugins:",
|
||||||
"*at*",
|
"*at*",
|
||||||
|
@ -38,7 +38,7 @@ def test_hookvalidation_unknown(testdir):
|
||||||
def test_hookvalidation_optional(testdir):
|
def test_hookvalidation_optional(testdir):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl_opts(optionalhook=True)
|
@pytest.hookimpl(optionalhook=True)
|
||||||
def pytest_hello(xyz):
|
def pytest_hello(xyz):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
|
|
|
@ -510,7 +510,7 @@ class TestKeywordSelection:
|
||||||
""")
|
""")
|
||||||
testdir.makepyfile(conftest="""
|
testdir.makepyfile(conftest="""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl_opts(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_pycollect_makeitem(name):
|
def pytest_pycollect_makeitem(name):
|
||||||
outcome = yield
|
outcome = yield
|
||||||
if name == "TestClass":
|
if name == "TestClass":
|
||||||
|
|
|
@ -0,0 +1,311 @@
|
||||||
|
import pytest
|
||||||
|
import py
|
||||||
|
import os
|
||||||
|
|
||||||
|
from _pytest.config import get_config, PytestPluginManager
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pytestpm():
|
||||||
|
return PytestPluginManager()
|
||||||
|
|
||||||
|
class TestPytestPluginInteractions:
|
||||||
|
def test_addhooks_conftestplugin(self, testdir):
|
||||||
|
testdir.makepyfile(newhooks="""
|
||||||
|
def pytest_myhook(xyz):
|
||||||
|
"new hook"
|
||||||
|
""")
|
||||||
|
conf = testdir.makeconftest("""
|
||||||
|
import sys ; sys.path.insert(0, '.')
|
||||||
|
import newhooks
|
||||||
|
def pytest_addhooks(pluginmanager):
|
||||||
|
pluginmanager.addhooks(newhooks)
|
||||||
|
def pytest_myhook(xyz):
|
||||||
|
return xyz + 1
|
||||||
|
""")
|
||||||
|
config = get_config()
|
||||||
|
pm = config.pluginmanager
|
||||||
|
pm.hook.pytest_addhooks.call_historic(
|
||||||
|
kwargs=dict(pluginmanager=config.pluginmanager))
|
||||||
|
config.pluginmanager._importconftest(conf)
|
||||||
|
#print(config.pluginmanager.get_plugins())
|
||||||
|
res = config.hook.pytest_myhook(xyz=10)
|
||||||
|
assert res == [11]
|
||||||
|
|
||||||
|
def test_addhooks_nohooks(self, testdir):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import sys
|
||||||
|
def pytest_addhooks(pluginmanager):
|
||||||
|
pluginmanager.addhooks(sys)
|
||||||
|
""")
|
||||||
|
res = testdir.runpytest()
|
||||||
|
assert res.ret != 0
|
||||||
|
res.stderr.fnmatch_lines([
|
||||||
|
"*did not find*sys*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_namespace_early_from_import(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
from pytest import Item
|
||||||
|
from pytest import Item as Item2
|
||||||
|
assert Item is Item2
|
||||||
|
""")
|
||||||
|
result = testdir.runpython(p)
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_do_ext_namespace(self, testdir):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
def pytest_namespace():
|
||||||
|
return {'hello': 'world'}
|
||||||
|
""")
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
from pytest import hello
|
||||||
|
import pytest
|
||||||
|
def test_hello():
|
||||||
|
assert hello == "world"
|
||||||
|
assert 'hello' in pytest.__all__
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run(p)
|
||||||
|
reprec.assertoutcome(passed=1)
|
||||||
|
|
||||||
|
def test_do_option_postinitialize(self, testdir):
|
||||||
|
config = testdir.parseconfigure()
|
||||||
|
assert not hasattr(config.option, 'test123')
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption('--test123', action="store_true",
|
||||||
|
default=True)
|
||||||
|
""")
|
||||||
|
config.pluginmanager._importconftest(p)
|
||||||
|
assert config.option.test123
|
||||||
|
|
||||||
|
def test_configure(self, testdir):
|
||||||
|
config = testdir.parseconfig()
|
||||||
|
l = []
|
||||||
|
class A:
|
||||||
|
def pytest_configure(self, config):
|
||||||
|
l.append(self)
|
||||||
|
|
||||||
|
config.pluginmanager.register(A())
|
||||||
|
assert len(l) == 0
|
||||||
|
config._do_configure()
|
||||||
|
assert len(l) == 1
|
||||||
|
config.pluginmanager.register(A()) # leads to a configured() plugin
|
||||||
|
assert len(l) == 2
|
||||||
|
assert l[0] != l[1]
|
||||||
|
|
||||||
|
config._ensure_unconfigure()
|
||||||
|
config.pluginmanager.register(A())
|
||||||
|
assert len(l) == 2
|
||||||
|
|
||||||
|
def test_hook_tracing(self):
|
||||||
|
pytestpm = get_config().pluginmanager # fully initialized with plugins
|
||||||
|
saveindent = []
|
||||||
|
class api1:
|
||||||
|
def pytest_plugin_registered(self):
|
||||||
|
saveindent.append(pytestpm.trace.root.indent)
|
||||||
|
class api2:
|
||||||
|
def pytest_plugin_registered(self):
|
||||||
|
saveindent.append(pytestpm.trace.root.indent)
|
||||||
|
raise ValueError()
|
||||||
|
l = []
|
||||||
|
pytestpm.trace.root.setwriter(l.append)
|
||||||
|
undo = pytestpm.enable_tracing()
|
||||||
|
try:
|
||||||
|
indent = pytestpm.trace.root.indent
|
||||||
|
p = api1()
|
||||||
|
pytestpm.register(p)
|
||||||
|
assert pytestpm.trace.root.indent == indent
|
||||||
|
assert len(l) >= 2
|
||||||
|
assert 'pytest_plugin_registered' in l[0]
|
||||||
|
assert 'finish' in l[1]
|
||||||
|
|
||||||
|
l[:] = []
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
pytestpm.register(api2())
|
||||||
|
assert pytestpm.trace.root.indent == indent
|
||||||
|
assert saveindent[0] > indent
|
||||||
|
finally:
|
||||||
|
undo()
|
||||||
|
|
||||||
|
def test_warn_on_deprecated_multicall(self, pytestpm):
|
||||||
|
class Plugin:
|
||||||
|
def pytest_configure(self, __multicall__):
|
||||||
|
pass
|
||||||
|
|
||||||
|
before = list(pytestpm._warnings)
|
||||||
|
pytestpm.register(Plugin())
|
||||||
|
assert len(pytestpm._warnings) == len(before) + 1
|
||||||
|
assert "deprecated" in pytestpm._warnings[-1]["message"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_namespace_has_default_and_env_plugins(testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
pytest.mark
|
||||||
|
""")
|
||||||
|
result = testdir.runpython(p)
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_default_markers(testdir):
|
||||||
|
result = testdir.runpytest("--markers")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*tryfirst*first*",
|
||||||
|
"*trylast*last*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_importplugin_issue375(testdir, pytestpm):
|
||||||
|
testdir.syspathinsert(testdir.tmpdir)
|
||||||
|
testdir.makepyfile(qwe="import aaaa")
|
||||||
|
with pytest.raises(ImportError) as excinfo:
|
||||||
|
pytestpm.import_plugin("qwe")
|
||||||
|
assert "qwe" not in str(excinfo.value)
|
||||||
|
assert "aaaa" in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPytestPluginManager:
|
||||||
|
def test_register_imported_modules(self):
|
||||||
|
pm = PytestPluginManager()
|
||||||
|
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
||||||
|
pm.register(mod)
|
||||||
|
assert pm.is_registered(mod)
|
||||||
|
l = pm.get_plugins()
|
||||||
|
assert mod in l
|
||||||
|
pytest.raises(ValueError, "pm.register(mod)")
|
||||||
|
pytest.raises(ValueError, lambda: pm.register(mod))
|
||||||
|
#assert not pm.is_registered(mod2)
|
||||||
|
assert pm.get_plugins() == l
|
||||||
|
|
||||||
|
def test_canonical_import(self, monkeypatch):
|
||||||
|
mod = py.std.types.ModuleType("pytest_xyz")
|
||||||
|
monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
|
||||||
|
pm = PytestPluginManager()
|
||||||
|
pm.import_plugin('pytest_xyz')
|
||||||
|
assert pm.get_plugin('pytest_xyz') == mod
|
||||||
|
assert pm.is_registered(mod)
|
||||||
|
|
||||||
|
def test_consider_module(self, testdir, pytestpm):
|
||||||
|
testdir.syspathinsert()
|
||||||
|
testdir.makepyfile(pytest_p1="#")
|
||||||
|
testdir.makepyfile(pytest_p2="#")
|
||||||
|
mod = py.std.types.ModuleType("temp")
|
||||||
|
mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
|
||||||
|
pytestpm.consider_module(mod)
|
||||||
|
assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
|
||||||
|
assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
|
||||||
|
|
||||||
|
def test_consider_module_import_module(self, testdir):
|
||||||
|
pytestpm = get_config().pluginmanager
|
||||||
|
mod = py.std.types.ModuleType("x")
|
||||||
|
mod.pytest_plugins = "pytest_a"
|
||||||
|
aplugin = testdir.makepyfile(pytest_a="#")
|
||||||
|
reprec = testdir.make_hook_recorder(pytestpm)
|
||||||
|
#syspath.prepend(aplugin.dirpath())
|
||||||
|
py.std.sys.path.insert(0, str(aplugin.dirpath()))
|
||||||
|
pytestpm.consider_module(mod)
|
||||||
|
call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
|
||||||
|
assert call.plugin.__name__ == "pytest_a"
|
||||||
|
|
||||||
|
# check that it is not registered twice
|
||||||
|
pytestpm.consider_module(mod)
|
||||||
|
l = reprec.getcalls("pytest_plugin_registered")
|
||||||
|
assert len(l) == 1
|
||||||
|
|
||||||
|
def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
|
||||||
|
monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
|
||||||
|
with pytest.raises(ImportError):
|
||||||
|
pytestpm.consider_env()
|
||||||
|
|
||||||
|
def test_plugin_skip(self, testdir, monkeypatch):
|
||||||
|
p = testdir.makepyfile(skipping1="""
|
||||||
|
import pytest
|
||||||
|
pytest.skip("hello")
|
||||||
|
""")
|
||||||
|
p.copy(p.dirpath("skipping2.py"))
|
||||||
|
monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
|
||||||
|
result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"WI1*skipped plugin*skipping1*hello*",
|
||||||
|
"WI1*skipped plugin*skipping2*hello*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
|
||||||
|
testdir.syspathinsert()
|
||||||
|
testdir.makepyfile(xy123="#")
|
||||||
|
monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
|
||||||
|
l1 = len(pytestpm.get_plugins())
|
||||||
|
pytestpm.consider_env()
|
||||||
|
l2 = len(pytestpm.get_plugins())
|
||||||
|
assert l2 == l1 + 1
|
||||||
|
assert pytestpm.get_plugin('xy123')
|
||||||
|
pytestpm.consider_env()
|
||||||
|
l3 = len(pytestpm.get_plugins())
|
||||||
|
assert l2 == l3
|
||||||
|
|
||||||
|
def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
|
||||||
|
testdir.makepyfile(pytest_x500="#")
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
def test_hello(pytestconfig):
|
||||||
|
plugin = pytestconfig.pluginmanager.get_plugin('pytest_x500')
|
||||||
|
assert plugin is not None
|
||||||
|
""")
|
||||||
|
monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
|
||||||
|
result = testdir.runpytest(p, syspathinsert=True)
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||||
|
|
||||||
|
def test_import_plugin_importname(self, testdir, pytestpm):
|
||||||
|
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
|
||||||
|
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
|
||||||
|
|
||||||
|
testdir.syspathinsert()
|
||||||
|
pluginname = "pytest_hello"
|
||||||
|
testdir.makepyfile(**{pluginname: ""})
|
||||||
|
pytestpm.import_plugin("pytest_hello")
|
||||||
|
len1 = len(pytestpm.get_plugins())
|
||||||
|
pytestpm.import_plugin("pytest_hello")
|
||||||
|
len2 = len(pytestpm.get_plugins())
|
||||||
|
assert len1 == len2
|
||||||
|
plugin1 = pytestpm.get_plugin("pytest_hello")
|
||||||
|
assert plugin1.__name__.endswith('pytest_hello')
|
||||||
|
plugin2 = pytestpm.get_plugin("pytest_hello")
|
||||||
|
assert plugin2 is plugin1
|
||||||
|
|
||||||
|
def test_import_plugin_dotted_name(self, testdir, pytestpm):
|
||||||
|
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
|
||||||
|
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
|
||||||
|
|
||||||
|
testdir.syspathinsert()
|
||||||
|
testdir.mkpydir("pkg").join("plug.py").write("x=3")
|
||||||
|
pluginname = "pkg.plug"
|
||||||
|
pytestpm.import_plugin(pluginname)
|
||||||
|
mod = pytestpm.get_plugin("pkg.plug")
|
||||||
|
assert mod.x == 3
|
||||||
|
|
||||||
|
def test_consider_conftest_deps(self, testdir, pytestpm):
|
||||||
|
mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
|
||||||
|
with pytest.raises(ImportError):
|
||||||
|
pytestpm.consider_conftest(mod)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPytestPluginManagerBootstrapming:
|
||||||
|
def test_preparse_args(self, pytestpm):
|
||||||
|
pytest.raises(ImportError, lambda:
|
||||||
|
pytestpm.consider_preparse(["xyz", "-p", "hello123"]))
|
||||||
|
|
||||||
|
def test_plugin_prevent_register(self, pytestpm):
|
||||||
|
pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
|
||||||
|
l1 = pytestpm.get_plugins()
|
||||||
|
pytestpm.register(42, name="abc")
|
||||||
|
l2 = pytestpm.get_plugins()
|
||||||
|
assert len(l2) == len(l1)
|
||||||
|
assert 42 not in l2
|
||||||
|
|
||||||
|
def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
|
||||||
|
pytestpm.register(42, name="abc")
|
||||||
|
l1 = pytestpm.get_plugins()
|
||||||
|
assert 42 in l1
|
||||||
|
pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
|
||||||
|
l2 = pytestpm.get_plugins()
|
||||||
|
assert 42 not in l2
|
|
@ -1,7 +1,9 @@
|
||||||
"""
|
"""
|
||||||
terminal reporting of the full testing process.
|
terminal reporting of the full testing process.
|
||||||
"""
|
"""
|
||||||
import pytest, py
|
import pytest
|
||||||
|
import py
|
||||||
|
import pluggy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt
|
from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt
|
||||||
|
@ -408,13 +410,13 @@ class TestTerminalFunctional:
|
||||||
verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
|
verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*===== test session starts ====*",
|
"*===== test session starts ====*",
|
||||||
"platform %s -- Python %s* -- py-%s -- pytest-%s" % (
|
"platform %s -- Python %s*pytest-%s*py-%s*pluggy-%s" % (
|
||||||
py.std.sys.platform, verinfo,
|
py.std.sys.platform, verinfo,
|
||||||
py.__version__, pytest.__version__),
|
pytest.__version__, py.__version__, pluggy.__version__),
|
||||||
"*test_header_trailer_info.py .",
|
"*test_header_trailer_info.py .",
|
||||||
"=* 1 passed*in *.[0-9][0-9] seconds *=",
|
"=* 1 passed*in *.[0-9][0-9] seconds *=",
|
||||||
])
|
])
|
||||||
if pytest.config.pluginmanager._plugin_distinfo:
|
if pytest.config.pluginmanager.list_plugin_distinfo():
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"plugins: *",
|
"plugins: *",
|
||||||
])
|
])
|
||||||
|
|
Loading…
Reference in New Issue