[Pytest-commit] commit/pytest: hpk42: Merged in hpk42/pytest-patches/plugin_no_pytest (pull request #278)
commits-noreply at bitbucket.org
commits-noreply at bitbucket.org
Sat Apr 25 09:08:35 CEST 2015
1 new commit in pytest:
https://bitbucket.org/pytest-dev/pytest/commits/90f9b67b555f/
Changeset: 90f9b67b555f
User: hpk42
Date: 2015-04-25 07:08:21+00:00
Summary: Merged in hpk42/pytest-patches/plugin_no_pytest (pull request #278)
Refactor pluginmanagement
Affected #: 20 files
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,15 @@
from ``inline_run()`` to allow temporary modules to be reloaded.
Thanks Eduardo Schettino.
+- internally refactor pluginmanager API and code so that there
+ is a clear distinction between a pytest-agnostic rather simple
+ pluginmanager and the PytestPluginManager which adds a lot of
+ behaviour, among it handling of the local conftest files.
+ In terms of documented methods this is a backward compatible
+ change but it might still break 3rd party plugins which relied on
+ details like especially the pluginmanager.add_shutdown() API.
+ Thanks Holger Krekel.
+
2.7.1.dev (compared to 2.7.0)
-----------------------------
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/assertion/__init__.py
--- a/_pytest/assertion/__init__.py
+++ b/_pytest/assertion/__init__.py
@@ -70,12 +70,11 @@
config._assertstate = AssertionState(config, mode)
config._assertstate.hook = hook
config._assertstate.trace("configured with mode set to %r" % (mode,))
-
-
-def pytest_unconfigure(config):
- hook = config._assertstate.hook
- if hook is not None and hook in sys.meta_path:
- sys.meta_path.remove(hook)
+ def undo():
+ hook = config._assertstate.hook
+ if hook is not None and hook in sys.meta_path:
+ sys.meta_path.remove(hook)
+ config.add_cleanup(undo)
def pytest_collection(session):
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -37,13 +37,13 @@
pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
- pluginmanager.add_shutdown(capman.reset_capturings)
+ early_config.add_cleanup(capman.reset_capturings)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
if "logging" in sys.modules:
sys.modules["logging"].raiseExceptions = False
- pluginmanager.add_shutdown(silence_logging_at_shutdown)
+ early_config.add_cleanup(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
capman.init_capturings()
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -53,6 +53,10 @@
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
"junitxml resultlog doctest").split()
+builtin_plugins = set(default_plugins)
+builtin_plugins.add("pytester")
+
+
def _preloadplugins():
assert not _preinit
_preinit.append(get_plugin_manager())
@@ -77,19 +81,31 @@
raise ValueError("not a string or argument list: %r" % (args,))
args = shlex.split(args)
pluginmanager = get_plugin_manager()
- try:
- if plugins:
- for plugin in plugins:
- pluginmanager.register(plugin)
- return pluginmanager.hook.pytest_cmdline_parse(
- pluginmanager=pluginmanager, args=args)
- except Exception:
- pluginmanager.ensure_shutdown()
- raise
+ if plugins:
+ for plugin in plugins:
+ pluginmanager.register(plugin)
+ return pluginmanager.hook.pytest_cmdline_parse(
+ pluginmanager=pluginmanager, args=args)
+
+def exclude_pytest_names(name):
+ return not name.startswith(name) or name == "pytest_plugins" or \
+ name.startswith("pytest_funcarg__")
+
class PytestPluginManager(PluginManager):
- def __init__(self, hookspecs=[hookspec]):
- super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
+ def __init__(self):
+ super(PytestPluginManager, self).__init__(prefix="pytest_",
+ excludefunc=exclude_pytest_names)
+ self._warnings = []
+ self._plugin_distinfo = []
+ self._globalplugins = []
+
+ # state related to local conftest plugins
+ self._path2confmods = {}
+ self._conftestpath2mod = {}
+ self._confcutdir = None
+
+ self.addhooks(hookspec)
self.register(self)
if os.environ.get('PYTEST_DEBUG'):
err = sys.stderr
@@ -100,6 +116,25 @@
pass
self.set_tracing(err.write)
+ def register(self, plugin, name=None, conftest=False):
+ ret = super(PytestPluginManager, self).register(plugin, name)
+ if ret and not conftest:
+ self._globalplugins.append(plugin)
+ return ret
+
+ def _do_register(self, plugin, name):
+ # called from core PluginManager class
+ if hasattr(self, "config"):
+ self.config._register_plugin(plugin, name)
+ return super(PytestPluginManager, self)._do_register(plugin, name)
+
+ def unregister(self, plugin):
+ super(PytestPluginManager, self).unregister(plugin)
+ try:
+ self._globalplugins.remove(plugin)
+ except ValueError:
+ pass
+
def pytest_configure(self, config):
config.addinivalue_line("markers",
"tryfirst: mark a hook implementation function such that the "
@@ -110,6 +145,172 @@
for warning in self._warnings:
config.warn(code="I1", message=warning)
+ #
+ # internal API for local conftest plugin handling
+ #
+ def _set_initial_conftests(self, namespace):
+ """ load initial conftest files given a preparsed "namespace".
+ As conftest files may add their own command line options
+ which have arguments ('--my-opt somepath') we might get some
+ false positives. All builtin and 3rd party plugins will have
+ been loaded, however, so common options will not confuse our logic
+ here.
+ """
+ current = py.path.local()
+ self._confcutdir = current.join(namespace.confcutdir, abs=True) \
+ if namespace.confcutdir else None
+ testpaths = namespace.file_or_dir
+ foundanchor = False
+ for path in testpaths:
+ path = str(path)
+ # remove node-id syntax
+ i = path.find("::")
+ if i != -1:
+ path = path[:i]
+ anchor = current.join(path, abs=1)
+ if exists(anchor): # we found some file object
+ self._try_load_conftest(anchor)
+ foundanchor = True
+ if not foundanchor:
+ self._try_load_conftest(current)
+
+ def _try_load_conftest(self, anchor):
+ self._getconftestmodules(anchor)
+ # let's also consider test* subdirs
+ if anchor.check(dir=1):
+ for x in anchor.listdir("test*"):
+ if x.check(dir=1):
+ self._getconftestmodules(x)
+
+ def _getconftestmodules(self, path):
+ try:
+ return self._path2confmods[path]
+ except KeyError:
+ clist = []
+ for parent in path.parts():
+ if self._confcutdir and self._confcutdir.relto(parent):
+ continue
+ conftestpath = parent.join("conftest.py")
+ if conftestpath.check(file=1):
+ mod = self._importconftest(conftestpath)
+ clist.append(mod)
+ self._path2confmods[path] = clist
+ return clist
+
+ def _rget_with_confmod(self, name, path):
+ modules = self._getconftestmodules(path)
+ for mod in reversed(modules):
+ try:
+ return mod, getattr(mod, name)
+ except AttributeError:
+ continue
+ raise KeyError(name)
+
+ def _importconftest(self, conftestpath):
+ try:
+ return self._conftestpath2mod[conftestpath]
+ except KeyError:
+ pkgpath = conftestpath.pypkgpath()
+ if pkgpath is None:
+ _ensure_removed_sysmodule(conftestpath.purebasename)
+ try:
+ mod = conftestpath.pyimport()
+ except Exception:
+ raise ConftestImportFailure(conftestpath, sys.exc_info())
+ self._conftestpath2mod[conftestpath] = mod
+ dirpath = conftestpath.dirpath()
+ if dirpath in self._path2confmods:
+ for path, mods in self._path2confmods.items():
+ if path and path.relto(dirpath) or path == dirpath:
+ assert mod not in mods
+ mods.append(mod)
+ self.trace("loaded conftestmodule %r" %(mod))
+ self.consider_conftest(mod)
+ return mod
+
+ #
+ # API for bootstrapping plugin loading
+ #
+ #
+
+ def consider_setuptools_entrypoints(self):
+ try:
+ from pkg_resources import iter_entry_points, DistributionNotFound
+ except ImportError:
+ return # XXX issue a warning
+ for ep in iter_entry_points('pytest11'):
+ name = ep.name
+ if name.startswith("pytest_"):
+ name = name[7:]
+ if ep.name in self._name2plugin or name in self._name2plugin:
+ continue
+ try:
+ plugin = ep.load()
+ except DistributionNotFound:
+ continue
+ self._plugin_distinfo.append((ep.dist, plugin))
+ self.register(plugin, name=name)
+
+ def consider_preparse(self, args):
+ for opt1,opt2 in zip(args, args[1:]):
+ if opt1 == "-p":
+ self.consider_pluginarg(opt2)
+
+ def consider_pluginarg(self, arg):
+ if arg.startswith("no:"):
+ name = arg[3:]
+ plugin = self.getplugin(name)
+ if plugin is not None:
+ self.unregister(plugin)
+ self._name2plugin[name] = -1
+ else:
+ if self.getplugin(arg) is None:
+ self.import_plugin(arg)
+
+ def consider_conftest(self, conftestmodule):
+ if self.register(conftestmodule, name=conftestmodule.__file__,
+ conftest=True):
+ self.consider_module(conftestmodule)
+
+ def consider_env(self):
+ self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
+
+ def consider_module(self, mod):
+ self._import_plugin_specs(getattr(mod, "pytest_plugins", None))
+
+ def _import_plugin_specs(self, spec):
+ if spec:
+ if isinstance(spec, str):
+ spec = spec.split(",")
+ for import_spec in spec:
+ self.import_plugin(import_spec)
+
+ def import_plugin(self, modname):
+ # most often modname refers to builtin modules, e.g. "pytester",
+ # "terminal" or "capture". Those plugins are registered under their
+ # basename for historic purposes but must be imported with the
+ # _pytest prefix.
+ assert isinstance(modname, str)
+ if self.getplugin(modname) is not None:
+ return
+ if modname in builtin_plugins:
+ importspec = "_pytest." + modname
+ else:
+ importspec = modname
+ try:
+ __import__(importspec)
+ except ImportError:
+ raise
+ except Exception as e:
+ import pytest
+ if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
+ raise
+ self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
+ else:
+ mod = sys.modules[importspec]
+ self.register(mod, modname)
+ self.consider_module(mod)
+
class Parser:
""" Parser for command line arguments and ini-file values. """
@@ -464,96 +665,6 @@
return action._formatted_action_invocation
-class Conftest(object):
- """ the single place for accessing values and interacting
- towards conftest modules from pytest objects.
- """
- def __init__(self, onimport=None):
- self._path2confmods = {}
- self._onimport = onimport
- self._conftestpath2mod = {}
- self._confcutdir = None
-
- def setinitial(self, namespace):
- """ load initial conftest files given a preparsed "namespace".
- As conftest files may add their own command line options
- which have arguments ('--my-opt somepath') we might get some
- false positives. All builtin and 3rd party plugins will have
- been loaded, however, so common options will not confuse our logic
- here.
- """
- current = py.path.local()
- self._confcutdir = current.join(namespace.confcutdir, abs=True) \
- if namespace.confcutdir else None
- testpaths = namespace.file_or_dir
- foundanchor = False
- for path in testpaths:
- path = str(path)
- # remove node-id syntax
- i = path.find("::")
- if i != -1:
- path = path[:i]
- anchor = current.join(path, abs=1)
- if exists(anchor): # we found some file object
- self._try_load_conftest(anchor)
- foundanchor = True
- if not foundanchor:
- self._try_load_conftest(current)
-
- def _try_load_conftest(self, anchor):
- self.getconftestmodules(anchor)
- # let's also consider test* subdirs
- if anchor.check(dir=1):
- for x in anchor.listdir("test*"):
- if x.check(dir=1):
- self.getconftestmodules(x)
-
- def getconftestmodules(self, path):
- try:
- return self._path2confmods[path]
- except KeyError:
- clist = []
- for parent in path.parts():
- if self._confcutdir and self._confcutdir.relto(parent):
- continue
- conftestpath = parent.join("conftest.py")
- if conftestpath.check(file=1):
- mod = self.importconftest(conftestpath)
- clist.append(mod)
- self._path2confmods[path] = clist
- return clist
-
- def rget_with_confmod(self, name, path):
- modules = self.getconftestmodules(path)
- for mod in reversed(modules):
- try:
- return mod, getattr(mod, name)
- except AttributeError:
- continue
- raise KeyError(name)
-
- def importconftest(self, conftestpath):
- try:
- return self._conftestpath2mod[conftestpath]
- except KeyError:
- pkgpath = conftestpath.pypkgpath()
- if pkgpath is None:
- _ensure_removed_sysmodule(conftestpath.purebasename)
- try:
- mod = conftestpath.pyimport()
- except Exception:
- raise ConftestImportFailure(conftestpath, sys.exc_info())
- self._conftestpath2mod[conftestpath] = mod
- dirpath = conftestpath.dirpath()
- if dirpath in self._path2confmods:
- for path, mods in self._path2confmods.items():
- if path and path.relto(dirpath) or path == dirpath:
- assert mod not in mods
- mods.append(mod)
- if self._onimport:
- self._onimport(mod)
- return mod
-
def _ensure_removed_sysmodule(modname):
try:
@@ -589,13 +700,11 @@
#: a pluginmanager instance
self.pluginmanager = pluginmanager
self.trace = self.pluginmanager.trace.root.get("config")
- self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook
self._inicache = {}
self._opt2dest = {}
self._cleanup = []
self.pluginmanager.register(self, "pytestconfig")
- self.pluginmanager.set_register_callback(self._register_plugin)
self._configured = False
def _register_plugin(self, plugin, name):
@@ -612,16 +721,23 @@
if self._configured:
call_plugin(plugin, "pytest_configure", {'config': self})
- def do_configure(self):
+ def add_cleanup(self, func):
+ """ Add a function to be called when the config object gets out of
+ use (usually coninciding with pytest_unconfigure)."""
+ self._cleanup.append(func)
+
+ def _do_configure(self):
assert not self._configured
self._configured = True
self.hook.pytest_configure(config=self)
- def do_unconfigure(self):
- assert self._configured
- self._configured = False
- self.hook.pytest_unconfigure(config=self)
- self.pluginmanager.ensure_shutdown()
+ def _ensure_unconfigure(self):
+ if self._configured:
+ self._configured = False
+ self.hook.pytest_unconfigure(config=self)
+ while self._cleanup:
+ fin = self._cleanup.pop()
+ fin()
def warn(self, code, message):
""" generate a warning for this test session. """
@@ -636,11 +752,6 @@
self.parse(args)
return self
- def pytest_unconfigure(config):
- while config._cleanup:
- fin = config._cleanup.pop()
- fin()
-
def notify_exception(self, excinfo, option=None):
if option and option.fulltrace:
style = "long"
@@ -675,10 +786,6 @@
config.pluginmanager.consider_pluginarg(x)
return config
- def _onimportconftest(self, conftestmodule):
- self.trace("loaded conftestmodule %r" %(conftestmodule,))
- self.pluginmanager.consider_conftest(conftestmodule)
-
def _processopt(self, opt):
for name in opt._short_opts + opt._long_opts:
self._opt2dest[name] = opt.dest
@@ -688,11 +795,11 @@
setattr(self.option, opt.dest, opt.default)
def _getmatchingplugins(self, fspath):
- return self.pluginmanager._plugins + \
- self._conftest.getconftestmodules(fspath)
+ return self.pluginmanager._globalplugins + \
+ self.pluginmanager._getconftestmodules(fspath)
def pytest_load_initial_conftests(self, early_config):
- self._conftest.setinitial(early_config.known_args_namespace)
+ self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
pytest_load_initial_conftests.trylast = True
def _initini(self, args):
@@ -799,7 +906,7 @@
def _getconftest_pathlist(self, name, path):
try:
- mod, relroots = self._conftest.rget_with_confmod(name, path)
+ mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
except KeyError:
return None
modpath = py.path.local(mod.__file__).dirpath()
@@ -933,3 +1040,4 @@
#if obj != pytest:
# pytest.__all__.append(name)
setattr(pytest, name, value)
+
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -1,14 +1,9 @@
"""
-pytest PluginManager, basic initialization and tracing.
+PluginManager, basic initialization and tracing.
"""
-import os
import sys
import inspect
import py
-# don't import pytest to avoid circular imports
-
-assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
- "%s is too old, remove or upgrade 'py'" % (py.__version__))
py3 = sys.version_info > (3,0)
@@ -139,202 +134,133 @@
class PluginManager(object):
- def __init__(self, hookspecs=None, prefix="pytest_"):
+ """ 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 ``set_tracing(writer)``
+ which will subsequently send debug information to the specified
+ write function.
+ """
+
+ def __init__(self, prefix, excludefunc=None):
+ self._prefix = prefix
+ self._excludefunc = excludefunc
self._name2plugin = {}
self._plugins = []
- self._conftestplugins = []
self._plugin2hookcallers = {}
- self._warnings = []
self.trace = TagTracer().get("pluginmanage")
- self._plugin_distinfo = []
- self._shutdown = []
- self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix)
+ self.hook = HookRelay(pm=self)
def set_tracing(self, writer):
+ """ turn on tracing to the given writer method and
+ return an undo function. """
self.trace.root.setwriter(writer)
# reconfigure HookCalling to perform tracing
assert not hasattr(self, "_wrapping")
self._wrapping = True
+ hooktrace = self.hook.trace
+
def _docall(self, methods, kwargs):
- trace = self.hookrelay.trace
- trace.root.indent += 1
- trace(self.name, kwargs)
+ hooktrace.root.indent += 1
+ hooktrace(self.name, kwargs)
box = yield
if box.excinfo is None:
- trace("finish", self.name, "-->", box.result)
- trace.root.indent -= 1
+ hooktrace("finish", self.name, "-->", box.result)
+ hooktrace.root.indent -= 1
- undo = add_method_wrapper(HookCaller, _docall)
- self.add_shutdown(undo)
+ return add_method_wrapper(HookCaller, _docall)
- def do_configure(self, config):
- # backward compatibility
- config.do_configure()
+ def make_hook_caller(self, name, plugins):
+ caller = getattr(self.hook, name)
+ methods = self.listattr(name, plugins=plugins)
+ return HookCaller(caller.name, caller.firstresult,
+ argnames=caller.argnames, methods=methods)
- def set_register_callback(self, callback):
- assert not hasattr(self, "_registercallback")
- self._registercallback = callback
-
- def register(self, plugin, name=None, prepend=False, conftest=False):
+ def register(self, plugin, name=None):
+ """ Register a plugin with the given name and ensure that all its
+ hook implementations are integrated. If the name is not specified
+ we use the ``__name__`` attribute of the plugin object or, if that
+ doesn't exist, the id of the plugin. This method will raise a
+ ValueError if the eventual name is already registered. """
+ name = name or self._get_canonical_name(plugin)
if self._name2plugin.get(name, None) == -1:
return
- name = name or getattr(plugin, '__name__', str(id(plugin)))
- if self.isregistered(plugin, name):
+ if self.hasplugin(name):
raise ValueError("Plugin already registered: %s=%s\n%s" %(
name, plugin, self._name2plugin))
#self.trace("registering", name, plugin)
- reg = getattr(self, "_registercallback", None)
- if reg is not None:
- reg(plugin, name) # may call addhooks
- hookcallers = list(self.hook._scan_plugin(plugin))
+ # allow subclasses to intercept here by calling a helper
+ return self._do_register(plugin, name)
+
+ def _do_register(self, plugin, name):
+ hookcallers = list(self._scan_plugin(plugin))
self._plugin2hookcallers[plugin] = hookcallers
self._name2plugin[name] = plugin
- if conftest:
- self._conftestplugins.append(plugin)
- else:
- if not prepend:
- self._plugins.append(plugin)
- else:
- self._plugins.insert(0, plugin)
- # finally make sure that the methods of the new plugin take part
+ self._plugins.append(plugin)
+ # rescan all methods for the hookcallers we found
for hookcaller in hookcallers:
- hookcaller.scan_methods()
+ self._scan_methods(hookcaller)
return True
def unregister(self, plugin):
- try:
- self._plugins.remove(plugin)
- except KeyError:
- self._conftestplugins.remove(plugin)
+ """ unregister the plugin object and all its contained hook implementations
+ from internal data structures. """
+ self._plugins.remove(plugin)
for name, value in list(self._name2plugin.items()):
if value == plugin:
del self._name2plugin[name]
hookcallers = self._plugin2hookcallers.pop(plugin)
for hookcaller in hookcallers:
- hookcaller.scan_methods()
+ self._scan_methods(hookcaller)
- def add_shutdown(self, func):
- self._shutdown.append(func)
-
- def ensure_shutdown(self):
- while self._shutdown:
- func = self._shutdown.pop()
- func()
- self._plugins = self._conftestplugins = []
- self._name2plugin.clear()
-
- def isregistered(self, plugin, name=None):
- if self.getplugin(name) is not None:
- return True
- return plugin in self._plugins or plugin in self._conftestplugins
-
- def addhooks(self, spec, prefix="pytest_"):
- self.hook._addhooks(spec, prefix=prefix)
+ 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. """
+ isclass = int(inspect.isclass(module_or_class))
+ names = []
+ for name in dir(module_or_class):
+ if name.startswith(self._prefix):
+ method = module_or_class.__dict__[name]
+ firstresult = getattr(method, 'firstresult', False)
+ hc = HookCaller(name, firstresult=firstresult,
+ argnames=varnames(method, startindex=isclass))
+ setattr(self.hook, name, hc)
+ names.append(name)
+ if not names:
+ raise ValueError("did not find new %r hooks in %r"
+ %(self._prefix, module_or_class))
def getplugins(self):
- return self._plugins + self._conftestplugins
+ """ return the complete list of registered plugins. NOTE that
+ you will get the internal list and need to make a copy if you
+ modify the list."""
+ return self._plugins
- def skipifmissing(self, name):
- if not self.hasplugin(name):
- import pytest
- pytest.skip("plugin %r is missing" % name)
+ def isregistered(self, plugin):
+ """ Return True if the plugin is already registered under its
+ canonical name. """
+ return self.hasplugin(self._get_canonical_name(plugin)) or \
+ plugin in self._plugins
def hasplugin(self, name):
- return bool(self.getplugin(name))
+ """ Return True if there is a registered with the given name. """
+ return name in self._name2plugin
def getplugin(self, name):
- if name is None:
- return None
- try:
- return self._name2plugin[name]
- except KeyError:
- return self._name2plugin.get("_pytest." + name, None)
-
- # API for bootstrapping
- #
- def _envlist(self, varname):
- val = os.environ.get(varname, None)
- if val is not None:
- return val.split(',')
- return ()
-
- def consider_env(self):
- for spec in self._envlist("PYTEST_PLUGINS"):
- self.import_plugin(spec)
-
- def consider_setuptools_entrypoints(self):
- try:
- from pkg_resources import iter_entry_points, DistributionNotFound
- except ImportError:
- return # XXX issue a warning
- for ep in iter_entry_points('pytest11'):
- name = ep.name
- if name.startswith("pytest_"):
- name = name[7:]
- if ep.name in self._name2plugin or name in self._name2plugin:
- continue
- try:
- plugin = ep.load()
- except DistributionNotFound:
- continue
- self._plugin_distinfo.append((ep.dist, plugin))
- self.register(plugin, name=name)
-
- def consider_preparse(self, args):
- for opt1,opt2 in zip(args, args[1:]):
- if opt1 == "-p":
- self.consider_pluginarg(opt2)
-
- def consider_pluginarg(self, arg):
- if arg.startswith("no:"):
- name = arg[3:]
- plugin = self.getplugin(name)
- if plugin is not None:
- self.unregister(plugin)
- self._name2plugin[name] = -1
- else:
- if self.getplugin(arg) is None:
- self.import_plugin(arg)
-
- def consider_conftest(self, conftestmodule):
- if self.register(conftestmodule, name=conftestmodule.__file__,
- conftest=True):
- self.consider_module(conftestmodule)
-
- def consider_module(self, mod):
- attr = getattr(mod, "pytest_plugins", ())
- if attr:
- if not isinstance(attr, (list, tuple)):
- attr = (attr,)
- for spec in attr:
- self.import_plugin(spec)
-
- def import_plugin(self, modname):
- assert isinstance(modname, str)
- if self.getplugin(modname) is not None:
- return
- try:
- mod = importplugin(modname)
- except KeyboardInterrupt:
- raise
- except ImportError:
- if modname.startswith("pytest_"):
- return self.import_plugin(modname[7:])
- raise
- except:
- e = sys.exc_info()[1]
- import pytest
- if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
- raise
- self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
- else:
- self.register(mod, modname)
- self.consider_module(mod)
+ """ Return a plugin or None for the given name. """
+ return self._name2plugin.get(name)
def listattr(self, attrname, plugins=None):
if plugins is None:
- plugins = self._plugins + self._conftestplugins
+ plugins = self._plugins
l = []
last = []
wrappers = []
@@ -355,20 +281,43 @@
l.extend(wrappers)
return l
+ def _scan_methods(self, hookcaller):
+ hookcaller.methods = self.listattr(hookcaller.name)
+
def call_plugin(self, plugin, methname, kwargs):
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
-def importplugin(importspec):
- name = importspec
- try:
- mod = "_pytest." + name
- __import__(mod)
- return sys.modules[mod]
- except ImportError:
- __import__(importspec)
- return sys.modules[importspec]
+ def _scan_plugin(self, plugin):
+ def fail(msg, *args):
+ name = getattr(plugin, '__name__', plugin)
+ raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
+
+ for name in dir(plugin):
+ if name[0] == "_" or not name.startswith(self._prefix):
+ continue
+ hook = getattr(self.hook, name, None)
+ method = getattr(plugin, name)
+ if hook is None:
+ if self._excludefunc is not None and self._excludefunc(name):
+ continue
+ if getattr(method, 'optionalhook', False):
+ continue
+ fail("found unknown hook: %r", name)
+ for arg in varnames(method):
+ if arg not in hook.argnames:
+ fail("argument %r not available\n"
+ "actual definition: %s\n"
+ "available hookargs: %s",
+ arg, formatdef(method),
+ ", ".join(hook.argnames))
+ yield hook
+
+ def _get_canonical_name(self, plugin):
+ return getattr(plugin, "__name__", None) or str(id(plugin))
+
+
class MultiCall:
""" execute a call into multiple python functions/methods. """
@@ -441,65 +390,13 @@
class HookRelay:
- def __init__(self, hookspecs, pm, prefix="pytest_"):
- if not isinstance(hookspecs, list):
- hookspecs = [hookspecs]
+ def __init__(self, pm):
self._pm = pm
self.trace = pm.trace.root.get("hook")
- self.prefix = prefix
- for hookspec in hookspecs:
- self._addhooks(hookspec, prefix)
-
- def _addhooks(self, hookspec, prefix):
- added = False
- isclass = int(inspect.isclass(hookspec))
- for name, method in vars(hookspec).items():
- if name.startswith(prefix):
- firstresult = getattr(method, 'firstresult', False)
- hc = HookCaller(self, name, firstresult=firstresult,
- argnames=varnames(method, startindex=isclass))
- setattr(self, name, hc)
- added = True
- #print ("setting new hook", name)
- if not added:
- raise ValueError("did not find new %r hooks in %r" %(
- prefix, hookspec,))
-
- def _getcaller(self, name, plugins):
- caller = getattr(self, name)
- methods = self._pm.listattr(name, plugins=plugins)
- if methods:
- return caller.new_cached_caller(methods)
- return caller
-
- def _scan_plugin(self, plugin):
- def fail(msg, *args):
- name = getattr(plugin, '__name__', plugin)
- raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
-
- for name in dir(plugin):
- if not name.startswith(self.prefix):
- continue
- hook = getattr(self, name, None)
- method = getattr(plugin, name)
- if hook is None:
- is_optional = getattr(method, 'optionalhook', False)
- if not isgenerichook(name) and not is_optional:
- fail("found unknown hook: %r", name)
- continue
- for arg in varnames(method):
- if arg not in hook.argnames:
- fail("argument %r not available\n"
- "actual definition: %s\n"
- "available hookargs: %s",
- arg, formatdef(method),
- ", ".join(hook.argnames))
- yield hook
class HookCaller:
- def __init__(self, hookrelay, name, firstresult, argnames, methods=()):
- self.hookrelay = hookrelay
+ def __init__(self, name, firstresult, argnames, methods=()):
self.name = name
self.firstresult = firstresult
self.argnames = ["__multicall__"]
@@ -507,16 +404,9 @@
assert "self" not in argnames # sanity check
self.methods = methods
- def new_cached_caller(self, methods):
- return HookCaller(self.hookrelay, self.name, self.firstresult,
- argnames=self.argnames, methods=methods)
-
def __repr__(self):
return "<HookCaller %r>" %(self.name,)
- def scan_methods(self):
- self.methods = self.hookrelay._pm.listattr(self.name)
-
def __call__(self, **kwargs):
return self._docall(self.methods, kwargs)
@@ -531,13 +421,9 @@
class PluginValidationError(Exception):
""" plugin failed validation. """
-def isgenerichook(name):
- return name == "pytest_plugins" or \
- name.startswith("pytest_funcarg__")
def formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)
-
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/doctest.py
--- a/_pytest/doctest.py
+++ b/_pytest/doctest.py
@@ -132,7 +132,7 @@
def collect(self):
import doctest
if self.fspath.basename == "conftest.py":
- module = self.config._conftest.importconftest(self.fspath)
+ module = self.config._conftest._importconftest(self.fspath)
else:
try:
module = self.fspath.pyimport()
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -28,24 +28,20 @@
config = outcome.get_result()
if config.option.debug:
path = os.path.abspath("pytestdebug.log")
- f = open(path, 'w')
- config._debugfile = f
- f.write("versions pytest-%s, py-%s, "
+ debugfile = open(path, 'w')
+ debugfile.write("versions pytest-%s, py-%s, "
"python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__,
".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs))
- config.pluginmanager.set_tracing(f.write)
+ config.pluginmanager.set_tracing(debugfile.write)
sys.stderr.write("writing pytestdebug information to %s\n" % path)
-
- at pytest.mark.trylast
-def pytest_unconfigure(config):
- if hasattr(config, '_debugfile'):
- config._debugfile.close()
- sys.stderr.write("wrote pytestdebug information to %s\n" %
- config._debugfile.name)
- config.trace.root.setwriter(None)
-
+ def unset_tracing():
+ debugfile.close()
+ sys.stderr.write("wrote pytestdebug information to %s\n" %
+ debugfile.name)
+ config.trace.root.setwriter(None)
+ config.add_cleanup(unset_tracing)
def pytest_cmdline_main(config):
if config.option.version:
@@ -58,9 +54,9 @@
sys.stderr.write(line + "\n")
return 0
elif config.option.help:
- config.do_configure()
+ config._do_configure()
showhelp(config)
- config.do_unconfigure()
+ config._ensure_unconfigure()
return 0
def showhelp(config):
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -6,7 +6,7 @@
def pytest_addhooks(pluginmanager):
"""called at plugin load time to allow adding new hooks via a call to
- pluginmanager.registerhooks(module)."""
+ pluginmanager.addhooks(module_or_class, prefix)."""
def pytest_namespace():
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -77,7 +77,7 @@
initstate = 0
try:
try:
- config.do_configure()
+ config._do_configure()
initstate = 1
config.hook.pytest_sessionstart(session=session)
initstate = 2
@@ -107,9 +107,7 @@
config.hook.pytest_sessionfinish(
session=session,
exitstatus=session.exitstatus)
- if initstate >= 1:
- config.do_unconfigure()
- config.pluginmanager.ensure_shutdown()
+ config._ensure_unconfigure()
return session.exitstatus
def pytest_cmdline_main(config):
@@ -160,7 +158,7 @@
def __getattr__(self, name):
plugins = self.config._getmatchingplugins(self.fspath)
- x = self.config.hook._getcaller(name, plugins)
+ x = self.config.pluginmanager.make_hook_caller(name, plugins)
self.__dict__[name] = x
return x
@@ -510,7 +508,7 @@
def __init__(self, config):
FSCollector.__init__(self, config.rootdir, parent=None,
config=config, session=self)
- self.config.pluginmanager.register(self, name="session", prepend=True)
+ self.config.pluginmanager.register(self, name="session")
self._testsfailed = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
@@ -521,10 +519,12 @@
def _makeid(self):
return ""
+ @pytest.mark.tryfirst
def pytest_collectstart(self):
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
+ @pytest.mark.tryfirst
def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'):
self._testsfailed += 1
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -44,14 +44,14 @@
def pytest_cmdline_main(config):
if config.option.markers:
- config.do_configure()
+ config._do_configure()
tw = py.io.TerminalWriter()
for line in config.getini("markers"):
name, rest = line.split(":", 1)
tw.write("@pytest.mark.%s:" % name, bold=True)
tw.line(rest)
tw.line()
- config.do_unconfigure()
+ config._ensure_unconfigure()
return 0
pytest_cmdline_main.tryfirst = True
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/pytester.py
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -83,7 +83,8 @@
self.calls.append(ParsedCall(hookcaller.name, kwargs))
yield
self._undo_wrapping = add_method_wrapper(HookCaller, _docall)
- pluginmanager.add_shutdown(self._undo_wrapping)
+ #if hasattr(pluginmanager, "config"):
+ # pluginmanager.add_shutdown(self._undo_wrapping)
def finish_recording(self):
self._undo_wrapping()
@@ -589,12 +590,7 @@
# we don't know what the test will do with this half-setup config
# object and thus we make sure it gets unconfigured properly in any
# case (otherwise capturing could still be active, for example)
- def ensure_unconfigure():
- if hasattr(config.pluginmanager, "_config"):
- config.pluginmanager.do_unconfigure(config)
- config.pluginmanager.ensure_shutdown()
-
- self.request.addfinalizer(ensure_unconfigure)
+ self.request.addfinalizer(config._ensure_unconfigure)
return config
def parseconfigure(self, *args):
@@ -606,8 +602,8 @@
"""
config = self.parseconfig(*args)
- config.do_configure()
- self.request.addfinalizer(config.do_unconfigure)
+ config._do_configure()
+ self.request.addfinalizer(config._ensure_unconfigure)
return config
def getitem(self, source, funcname="test_func"):
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/conftest.py
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -66,6 +66,7 @@
error.append(error[0])
raise AssertionError("\n".join(error))
+ at pytest.mark.trylast
def pytest_runtest_teardown(item, __multicall__):
item.config._basedir.chdir()
if hasattr(item.config, '_openfiles'):
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/python/fixture.py
--- a/testing/python/fixture.py
+++ b/testing/python/fixture.py
@@ -1487,7 +1487,7 @@
reprec = testdir.inline_run("-v","-s")
reprec.assertoutcome(passed=8)
config = reprec.getcalls("pytest_unconfigure")[0].config
- l = config._conftest.getconftestmodules(p)[0].l
+ l = config.pluginmanager._getconftestmodules(p)[0].l
assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
def test_scope_ordering(self, testdir):
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/test_conftest.py
--- a/testing/test_conftest.py
+++ b/testing/test_conftest.py
@@ -1,7 +1,6 @@
from textwrap import dedent
import py, pytest
-from _pytest.config import Conftest
-
+from _pytest.config import PytestPluginManager
@pytest.fixture(scope="module", params=["global", "inpackage"])
@@ -16,7 +15,7 @@
return tmpdir
def ConftestWithSetinitial(path):
- conftest = Conftest()
+ conftest = PytestPluginManager()
conftest_setinitial(conftest, [path])
return conftest
@@ -25,51 +24,41 @@
def __init__(self):
self.file_or_dir = args
self.confcutdir = str(confcutdir)
- conftest.setinitial(Namespace())
+ conftest._set_initial_conftests(Namespace())
class TestConftestValueAccessGlobal:
def test_basic_init(self, basedir):
- conftest = Conftest()
+ conftest = PytestPluginManager()
p = basedir.join("adir")
- assert conftest.rget_with_confmod("a", p)[1] == 1
-
- def test_onimport(self, basedir):
- l = []
- conftest = Conftest(onimport=l.append)
- adir = basedir.join("adir")
- conftest_setinitial(conftest, [adir], confcutdir=basedir)
- assert len(l) == 1
- assert conftest.rget_with_confmod("a", adir)[1] == 1
- assert conftest.rget_with_confmod("b", adir.join("b"))[1] == 2
- assert len(l) == 2
+ assert conftest._rget_with_confmod("a", p)[1] == 1
def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
- conftest = Conftest()
+ conftest = PytestPluginManager()
len(conftest._path2confmods)
- conftest.getconftestmodules(basedir)
+ conftest._getconftestmodules(basedir)
snap1 = len(conftest._path2confmods)
#assert len(conftest._path2confmods) == snap1 + 1
- conftest.getconftestmodules(basedir.join('adir'))
+ conftest._getconftestmodules(basedir.join('adir'))
assert len(conftest._path2confmods) == snap1 + 1
- conftest.getconftestmodules(basedir.join('b'))
+ conftest._getconftestmodules(basedir.join('b'))
assert len(conftest._path2confmods) == snap1 + 2
def test_value_access_not_existing(self, basedir):
conftest = ConftestWithSetinitial(basedir)
with pytest.raises(KeyError):
- conftest.rget_with_confmod('a', basedir)
+ conftest._rget_with_confmod('a', basedir)
def test_value_access_by_path(self, basedir):
conftest = ConftestWithSetinitial(basedir)
adir = basedir.join("adir")
- assert conftest.rget_with_confmod("a", adir)[1] == 1
- assert conftest.rget_with_confmod("a", adir.join("b"))[1] == 1.5
+ assert conftest._rget_with_confmod("a", adir)[1] == 1
+ assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
def test_value_access_with_confmod(self, basedir):
startdir = basedir.join("adir", "b")
startdir.ensure("xx", dir=True)
conftest = ConftestWithSetinitial(startdir)
- mod, value = conftest.rget_with_confmod("a", startdir)
+ mod, value = conftest._rget_with_confmod("a", startdir)
assert value == 1.5
path = py.path.local(mod.__file__)
assert path.dirpath() == basedir.join("adir", "b")
@@ -85,9 +74,9 @@
def test_doubledash_considered(testdir):
conf = testdir.mkdir("--option")
conf.join("conftest.py").ensure()
- conftest = Conftest()
+ conftest = PytestPluginManager()
conftest_setinitial(conftest, [conf.basename, conf.basename])
- l = conftest.getconftestmodules(conf)
+ l = conftest._getconftestmodules(conf)
assert len(l) == 1
def test_issue151_load_all_conftests(testdir):
@@ -96,7 +85,7 @@
p = testdir.mkdir(name)
p.ensure("conftest.py")
- conftest = Conftest()
+ conftest = PytestPluginManager()
conftest_setinitial(conftest, names)
d = list(conftest._conftestpath2mod.values())
assert len(d) == len(names)
@@ -105,15 +94,15 @@
testdir.makeconftest("x=3")
p = testdir.makepyfile("""
import py, pytest
- from _pytest.config import Conftest
- conf = Conftest()
- mod = conf.importconftest(py.path.local("conftest.py"))
+ from _pytest.config import PytestPluginManager
+ conf = PytestPluginManager()
+ mod = conf._importconftest(py.path.local("conftest.py"))
assert mod.x == 3
import conftest
assert conftest is mod, (conftest, mod)
subconf = py.path.local().ensure("sub", "conftest.py")
subconf.write("y=4")
- mod2 = conf.importconftest(subconf)
+ mod2 = conf._importconftest(subconf)
assert mod != mod2
assert mod2.y == 4
import conftest
@@ -125,27 +114,27 @@
def test_conftestcutdir(testdir):
conf = testdir.makeconftest("")
p = testdir.mkdir("x")
- conftest = Conftest()
+ conftest = PytestPluginManager()
conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
- l = conftest.getconftestmodules(p)
+ l = conftest._getconftestmodules(p)
assert len(l) == 0
- l = conftest.getconftestmodules(conf.dirpath())
+ l = conftest._getconftestmodules(conf.dirpath())
assert len(l) == 0
assert conf not in conftest._conftestpath2mod
# but we can still import a conftest directly
- conftest.importconftest(conf)
- l = conftest.getconftestmodules(conf.dirpath())
+ conftest._importconftest(conf)
+ l = conftest._getconftestmodules(conf.dirpath())
assert l[0].__file__.startswith(str(conf))
# and all sub paths get updated properly
- l = conftest.getconftestmodules(p)
+ l = conftest._getconftestmodules(p)
assert len(l) == 1
assert l[0].__file__.startswith(str(conf))
def test_conftestcutdir_inplace_considered(testdir):
conf = testdir.makeconftest("")
- conftest = Conftest()
+ conftest = PytestPluginManager()
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
- l = conftest.getconftestmodules(conf.dirpath())
+ l = conftest._getconftestmodules(conf.dirpath())
assert len(l) == 1
assert l[0].__file__.startswith(str(conf))
@@ -153,7 +142,7 @@
def test_setinitial_conftest_subdirs(testdir, name):
sub = testdir.mkdir(name)
subconftest = sub.ensure("conftest.py")
- conftest = Conftest()
+ conftest = PytestPluginManager()
conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
if name not in ('whatever', '.dotdir'):
assert subconftest in conftest._conftestpath2mod
@@ -199,9 +188,9 @@
ct2.write("")
def impct(p):
return p
- conftest = Conftest()
- monkeypatch.setattr(conftest, 'importconftest', impct)
- assert conftest.getconftestmodules(sub) == [ct1, ct2]
+ conftest = PytestPluginManager()
+ monkeypatch.setattr(conftest, '_importconftest', impct)
+ assert conftest._getconftestmodules(sub) == [ct1, ct2]
def test_fixture_dependency(testdir, monkeypatch):
diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -3,234 +3,48 @@
from _pytest.config import get_plugin_manager
-class TestBootstrapping:
- def test_consider_env_fails_to_import(self, monkeypatch):
- pluginmanager = PluginManager()
- monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
- pytest.raises(ImportError, lambda: pluginmanager.consider_env())
+ at pytest.fixture
+def pm():
+ return PluginManager("he")
- def test_preparse_args(self):
- pluginmanager = PluginManager()
- pytest.raises(ImportError, lambda:
- pluginmanager.consider_preparse(["xyz", "-p", "hello123"]))
+ at pytest.fixture
+def pytestpm():
+ return PytestPluginManager()
- def test_plugin_prevent_register(self):
- pluginmanager = PluginManager()
- pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
- l1 = pluginmanager.getplugins()
- pluginmanager.register(42, name="abc")
- l2 = pluginmanager.getplugins()
- assert len(l2) == len(l1)
- def test_plugin_prevent_register_unregistered_alredy_registered(self):
- pluginmanager = PluginManager()
- pluginmanager.register(42, name="abc")
- l1 = pluginmanager.getplugins()
- assert 42 in l1
- pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
- l2 = pluginmanager.getplugins()
- assert 42 not in l2
+class TestPluginManager:
+ def test_plugin_double_register(self, pm):
+ pm.register(42, name="abc")
+ with pytest.raises(ValueError):
+ pm.register(42, name="abc")
- def test_plugin_double_register(self):
- pm = PluginManager()
- pm.register(42, name="abc")
- pytest.raises(ValueError, lambda: pm.register(42, name="abc"))
-
- 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", "--traceconfig")
- 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):
- pluginmanager = PluginManager()
- testdir.syspathinsert()
- testdir.makepyfile(xy123="#")
- monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
- l1 = len(pluginmanager.getplugins())
- pluginmanager.consider_env()
- l2 = len(pluginmanager.getplugins())
- assert l2 == l1 + 1
- assert pluginmanager.getplugin('xy123')
- pluginmanager.consider_env()
- l3 = len(pluginmanager.getplugins())
- assert l2 == l3
-
- def test_consider_setuptools_instantiation(self, monkeypatch):
- pkg_resources = pytest.importorskip("pkg_resources")
- def my_iter(name):
- assert name == "pytest11"
- class EntryPoint:
- name = "pytest_mytestplugin"
- dist = None
- def load(self):
- class PseudoPlugin:
- x = 42
- return PseudoPlugin()
- return iter([EntryPoint()])
-
- monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
- pluginmanager = PluginManager()
- pluginmanager.consider_setuptools_entrypoints()
- plugin = pluginmanager.getplugin("mytestplugin")
- assert plugin.x == 42
-
- def test_consider_setuptools_not_installed(self, monkeypatch):
- monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
- py.std.types.ModuleType("pkg_resources"))
- pluginmanager = PluginManager()
- pluginmanager.consider_setuptools_entrypoints()
- # ok, we did not explode
-
- def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
- testdir.makepyfile(pytest_x500="#")
- p = testdir.makepyfile("""
- import pytest
- def test_hello(pytestconfig):
- plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
- assert plugin is not None
- """)
- monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
- result = testdir.runpytest(p)
- assert result.ret == 0
- result.stdout.fnmatch_lines(["*1 passed in*"])
-
- def test_import_plugin_importname(self, testdir):
- pluginmanager = PluginManager()
- pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
- pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwx.y")')
-
- testdir.syspathinsert()
- pluginname = "pytest_hello"
- testdir.makepyfile(**{pluginname: ""})
- pluginmanager.import_plugin("pytest_hello")
- len1 = len(pluginmanager.getplugins())
- pluginmanager.import_plugin("pytest_hello")
- len2 = len(pluginmanager.getplugins())
- assert len1 == len2
- plugin1 = pluginmanager.getplugin("pytest_hello")
- assert plugin1.__name__.endswith('pytest_hello')
- plugin2 = pluginmanager.getplugin("pytest_hello")
- assert plugin2 is plugin1
-
- def test_import_plugin_dotted_name(self, testdir):
- pluginmanager = PluginManager()
- pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
- pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwex.y")')
-
- testdir.syspathinsert()
- testdir.mkpydir("pkg").join("plug.py").write("x=3")
- pluginname = "pkg.plug"
- pluginmanager.import_plugin(pluginname)
- mod = pluginmanager.getplugin("pkg.plug")
- assert mod.x == 3
-
- def test_consider_module(self, testdir):
- pluginmanager = PluginManager()
- testdir.syspathinsert()
- testdir.makepyfile(pytest_p1="#")
- testdir.makepyfile(pytest_p2="#")
- mod = py.std.types.ModuleType("temp")
- mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
- pluginmanager.consider_module(mod)
- assert pluginmanager.getplugin("pytest_p1").__name__ == "pytest_p1"
- assert pluginmanager.getplugin("pytest_p2").__name__ == "pytest_p2"
-
- def test_consider_module_import_module(self, testdir):
- mod = py.std.types.ModuleType("x")
- mod.pytest_plugins = "pytest_a"
- aplugin = testdir.makepyfile(pytest_a="#")
- pluginmanager = get_plugin_manager()
- reprec = testdir.make_hook_recorder(pluginmanager)
- #syspath.prepend(aplugin.dirpath())
- py.std.sys.path.insert(0, str(aplugin.dirpath()))
- pluginmanager.consider_module(mod)
- call = reprec.getcall(pluginmanager.hook.pytest_plugin_registered.name)
- assert call.plugin.__name__ == "pytest_a"
-
- # check that it is not registered twice
- pluginmanager.consider_module(mod)
- l = reprec.getcalls("pytest_plugin_registered")
- assert len(l) == 1
-
- def test_config_sets_conftesthandle_onimport(self, testdir):
- config = testdir.parseconfig([])
- assert config._conftest._onimport == config._onimportconftest
-
- def test_consider_conftest_deps(self, testdir):
- mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
- pp = PluginManager()
- pytest.raises(ImportError, lambda: pp.consider_conftest(mod))
-
- def test_pm(self):
- pp = PluginManager()
+ def test_pm(self, pm):
class A: pass
a1, a2 = A(), A()
- pp.register(a1)
- assert pp.isregistered(a1)
- pp.register(a2, "hello")
- assert pp.isregistered(a2)
- l = pp.getplugins()
+ pm.register(a1)
+ assert pm.isregistered(a1)
+ pm.register(a2, "hello")
+ assert pm.isregistered(a2)
+ l = pm.getplugins()
assert a1 in l
assert a2 in l
- assert pp.getplugin('hello') == a2
- pp.unregister(a1)
- assert not pp.isregistered(a1)
-
- def test_pm_ordering(self):
- pp = PluginManager()
- class A: pass
- a1, a2 = A(), A()
- pp.register(a1)
- pp.register(a2, "hello")
- l = pp.getplugins()
- assert l.index(a1) < l.index(a2)
- a3 = A()
- pp.register(a3, prepend=True)
- l = pp.getplugins()
- assert l.index(a3) == 0
-
- def test_register_imported_modules(self):
- pp = PluginManager()
- mod = py.std.types.ModuleType("x.y.pytest_hello")
- pp.register(mod)
- assert pp.isregistered(mod)
- l = pp.getplugins()
- assert mod in l
- pytest.raises(ValueError, "pp.register(mod)")
- pytest.raises(ValueError, lambda: pp.register(mod))
- #assert not pp.isregistered(mod2)
- assert pp.getplugins() == l
-
- def test_canonical_import(self, monkeypatch):
- mod = py.std.types.ModuleType("pytest_xyz")
- monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
- pp = PluginManager()
- pp.import_plugin('pytest_xyz')
- assert pp.getplugin('pytest_xyz') == mod
- assert pp.isregistered(mod)
+ assert pm.getplugin('hello') == a2
+ pm.unregister(a1)
+ assert not pm.isregistered(a1)
def test_register_mismatch_method(self):
- pp = get_plugin_manager()
+ pm = get_plugin_manager()
class hello:
def pytest_gurgel(self):
pass
- pytest.raises(Exception, lambda: pp.register(hello()))
+ pytest.raises(Exception, lambda: pm.register(hello()))
def test_register_mismatch_arg(self):
- pp = get_plugin_manager()
+ pm = get_plugin_manager()
class hello:
def pytest_configure(self, asd):
pass
- pytest.raises(Exception, lambda: pp.register(hello()))
+ pytest.raises(Exception, lambda: pm.register(hello()))
def test_register(self):
pm = get_plugin_manager()
@@ -250,7 +64,7 @@
assert pm.getplugins()[-1:] == [my2]
def test_listattr(self):
- plugins = PluginManager()
+ plugins = PluginManager("xyz")
class api1:
x = 41
class api2:
@@ -263,27 +77,6 @@
l = list(plugins.listattr('x'))
assert l == [41, 42, 43]
- def test_hook_tracing(self):
- pm = get_plugin_manager()
- saveindent = []
- class api1:
- x = 41
- def pytest_plugin_registered(self, plugin):
- saveindent.append(pm.trace.root.indent)
- raise ValueError(42)
- l = []
- pm.set_tracing(l.append)
- indent = pm.trace.root.indent
- p = api1()
- pm.register(p)
-
- assert pm.trace.root.indent == indent
- assert len(l) == 2
- assert 'pytest_plugin_registered' in l[0]
- assert 'finish' in l[1]
- pytest.raises(ValueError, lambda: pm.register(api1()))
- assert pm.trace.root.indent == indent
- assert saveindent[0] > indent
class TestPytestPluginInteractions:
@@ -301,7 +94,7 @@
return xyz + 1
""")
config = get_plugin_manager().config
- config._conftest.importconftest(conf)
+ config.pluginmanager._importconftest(conf)
print(config.pluginmanager.getplugins())
res = config.hook.pytest_myhook(xyz=10)
assert res == [11]
@@ -350,7 +143,7 @@
parser.addoption('--test123', action="store_true",
default=True)
""")
- config._conftest.importconftest(p)
+ config.pluginmanager._importconftest(p)
assert config.option.test123
def test_configure(self, testdir):
@@ -362,20 +155,43 @@
config.pluginmanager.register(A())
assert len(l) == 0
- config.do_configure()
+ 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.do_unconfigure()
+ config._ensure_unconfigure()
config.pluginmanager.register(A())
assert len(l) == 2
+ def test_hook_tracing(self):
+ pytestpm = get_plugin_manager() # fully initialized with plugins
+ saveindent = []
+ class api1:
+ x = 41
+ def pytest_plugin_registered(self, plugin):
+ saveindent.append(pytestpm.trace.root.indent)
+ raise ValueError(42)
+ l = []
+ pytestpm.set_tracing(l.append)
+ 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]
+ with pytest.raises(ValueError):
+ pytestpm.register(api1())
+ assert pytestpm.trace.root.indent == indent
+ assert saveindent[0] > indent
+
# lower level API
def test_listattr(self):
- pluginmanager = PluginManager()
+ pluginmanager = PluginManager("xyz")
class My2:
x = 42
pluginmanager.register(My2())
@@ -395,7 +211,7 @@
def m(self):
return 19
- pluginmanager = PluginManager()
+ pluginmanager = PluginManager("xyz")
p1 = P1()
p2 = P2()
p3 = P3()
@@ -572,7 +388,7 @@
def m(self):
return 19
- pluginmanager = PluginManager()
+ pluginmanager = PluginManager("xyz")
p1 = P1()
p2 = P2()
p3 = P3()
@@ -624,11 +440,12 @@
class TestHookRelay:
- def test_happypath(self):
+ def test_hapmypath(self):
class Api:
def hello(self, arg):
"api hook 1"
- pm = PluginManager([Api], prefix="he")
+ pm = PluginManager("he")
+ pm.addhooks(Api)
hook = pm.hook
assert hasattr(hook, 'hello')
assert repr(hook.hello).find("hello") != -1
@@ -647,7 +464,8 @@
class Api:
def hello(self, arg):
"api hook 1"
- pm = PluginManager(Api, prefix="he")
+ pm = PluginManager("he")
+ pm.addhooks(Api)
class Plugin:
def hello(self, argwrong):
return arg + 1
@@ -656,19 +474,20 @@
assert "argwrong" in str(exc.value)
def test_only_kwargs(self):
- pm = PluginManager()
+ pm = PluginManager("he")
class Api:
def hello(self, arg):
"api hook 1"
- mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
- pytest.raises(TypeError, lambda: mcm.hello(3))
+ pm.addhooks(Api)
+ pytest.raises(TypeError, lambda: pm.hook.hello(3))
def test_firstresult_definition(self):
class Api:
def hello(self, arg):
"api hook 1"
hello.firstresult = True
- pm = PluginManager([Api], "he")
+ pm = PluginManager("he")
+ pm.addhooks(Api)
class Plugin:
def hello(self, arg):
return arg + 1
@@ -771,15 +590,16 @@
"*trylast*last*",
])
-def test_importplugin_issue375(testdir):
+def test_importplugin_issue375(testdir, pytestpm):
testdir.syspathinsert(testdir.tmpdir)
testdir.makepyfile(qwe="import aaaa")
- excinfo = pytest.raises(ImportError, lambda: importplugin("qwe"))
+ with pytest.raises(ImportError) as excinfo:
+ pytestpm.import_plugin("qwe")
assert "qwe" not in str(excinfo.value)
assert "aaaa" in str(excinfo.value)
class TestWrapMethod:
- def test_basic_happypath(self):
+ def test_basic_hapmypath(self):
class A:
def f(self):
return "A.f"
@@ -880,3 +700,178 @@
with pytest.raises(ValueError):
A().error()
assert l == [1]
+
+
+### to be shifted to own test file
+from _pytest.config import PytestPluginManager
+
+class TestPytestPluginManager:
+ def test_register_imported_modules(self):
+ pm = PytestPluginManager()
+ mod = py.std.types.ModuleType("x.y.pytest_hello")
+ pm.register(mod)
+ assert pm.isregistered(mod)
+ l = pm.getplugins()
+ assert mod in l
+ pytest.raises(ValueError, "pm.register(mod)")
+ pytest.raises(ValueError, lambda: pm.register(mod))
+ #assert not pm.isregistered(mod2)
+ assert pm.getplugins() == 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.getplugin('pytest_xyz') == mod
+ assert pm.isregistered(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.getplugin("pytest_p1").__name__ == "pytest_p1"
+ assert pytestpm.getplugin("pytest_p2").__name__ == "pytest_p2"
+
+ def test_consider_module_import_module(self, testdir):
+ pytestpm = get_plugin_manager()
+ 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", "--traceconfig")
+ 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.getplugins())
+ pytestpm.consider_env()
+ l2 = len(pytestpm.getplugins())
+ assert l2 == l1 + 1
+ assert pytestpm.getplugin('xy123')
+ pytestpm.consider_env()
+ l3 = len(pytestpm.getplugins())
+ assert l2 == l3
+
+ def test_consider_setuptools_instantiation(self, monkeypatch, pytestpm):
+ pkg_resources = pytest.importorskip("pkg_resources")
+ def my_iter(name):
+ assert name == "pytest11"
+ class EntryPoint:
+ name = "pytest_mytestplugin"
+ dist = None
+ def load(self):
+ class PseudoPlugin:
+ x = 42
+ return PseudoPlugin()
+ return iter([EntryPoint()])
+
+ monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+ pytestpm.consider_setuptools_entrypoints()
+ plugin = pytestpm.getplugin("mytestplugin")
+ assert plugin.x == 42
+
+ def test_consider_setuptools_not_installed(self, monkeypatch, pytestpm):
+ monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
+ py.std.types.ModuleType("pkg_resources"))
+ pytestpm.consider_setuptools_entrypoints()
+ # ok, we did not explode
+
+ def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
+ testdir.makepyfile(pytest_x500="#")
+ p = testdir.makepyfile("""
+ import pytest
+ def test_hello(pytestconfig):
+ plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
+ assert plugin is not None
+ """)
+ monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
+ result = testdir.runpytest(p)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines(["*1 passed in*"])
+
+ 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.getplugins())
+ pytestpm.import_plugin("pytest_hello")
+ len2 = len(pytestpm.getplugins())
+ assert len1 == len2
+ plugin1 = pytestpm.getplugin("pytest_hello")
+ assert plugin1.__name__.endswith('pytest_hello')
+ plugin2 = pytestpm.getplugin("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.getplugin("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.getplugins()
+ pytestpm.register(42, name="abc")
+ l2 = pytestpm.getplugins()
+ assert len(l2) == len(l1)
+
+ def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
+ pytestpm.register(42, name="abc")
+ l1 = pytestpm.getplugins()
+ assert 42 in l1
+ pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
+ l2 = pytestpm.getplugins()
+ assert 42 not in l2
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/pytest-dev/pytest/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
More information about the pytest-commit
mailing list