summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorholger krekel <holger@merlinux.eu>2013-09-30 13:14:14 +0200
committerholger krekel <holger@merlinux.eu>2013-09-30 13:14:14 +0200
commit4b709037abf86e59f4227a8aad4ba8e1c64a0634 (patch)
tree82f215890995267d70c36933f267e8d3c6c4055e
parentd946299b0a3e42e447c92b6074f196077b9b7a91 (diff)
downloadpytest-4b709037abf86e59f4227a8aad4ba8e1c64a0634.tar.gz
some more separation of core pluginmanager from pytest specific functionality.
Idea is to have the PluginManager be re-useable from other projects at some point.
-rw-r--r--_pytest/config.py106
-rw-r--r--_pytest/core.py110
-rw-r--r--_pytest/main.py2
-rw-r--r--_pytest/pytester.py4
-rw-r--r--_pytest/terminal.py6
-rw-r--r--pytest.py14
-rw-r--r--testing/test_config.py15
-rw-r--r--testing/test_core.py32
-rw-r--r--testing/test_pytester.py2
9 files changed, 157 insertions, 134 deletions
diff --git a/_pytest/config.py b/_pytest/config.py
index f52a635bc..4aab1fae6 100644
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -2,6 +2,87 @@
import py
import sys, os
+from _pytest import hookspec # the extension point definitions
+from _pytest.core import PluginManager
+
+# pytest startup
+
+def main(args=None, plugins=None):
+ """ return exit code, after performing an in-process test run.
+
+ :arg args: list of command line arguments.
+
+ :arg plugins: list of plugin objects to be auto-registered during
+ initialization.
+ """
+ config = _prepareconfig(args, plugins)
+ exitstatus = config.hook.pytest_cmdline_main(config=config)
+ return exitstatus
+
+class cmdline: # compatibility namespace
+ main = staticmethod(main)
+
+class UsageError(Exception):
+ """ error in py.test usage or invocation"""
+
+_preinit = []
+
+default_plugins = (
+ "mark main terminal runner python pdb unittest capture skipping "
+ "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
+ "junitxml resultlog doctest").split()
+
+def _preloadplugins():
+ assert not _preinit
+ _preinit.append(get_plugin_manager())
+
+def get_plugin_manager():
+ if _preinit:
+ return _preinit.pop(0)
+ # subsequent calls to main will create a fresh instance
+ pluginmanager = PytestPluginManager()
+ pluginmanager.config = config = Config(pluginmanager) # XXX attr needed?
+ for spec in default_plugins:
+ pluginmanager.import_plugin(spec)
+ return pluginmanager
+
+def _prepareconfig(args=None, plugins=None):
+ if args is None:
+ args = sys.argv[1:]
+ elif isinstance(args, py.path.local):
+ args = [str(args)]
+ elif not isinstance(args, (tuple, list)):
+ if not isinstance(args, str):
+ raise ValueError("not a string or argument list: %r" % (args,))
+ args = py.std.shlex.split(args)
+ pluginmanager = get_plugin_manager()
+ if plugins:
+ for plugin in plugins:
+ pluginmanager.register(plugin)
+ return pluginmanager.hook.pytest_cmdline_parse(
+ pluginmanager=pluginmanager, args=args)
+
+class PytestPluginManager(PluginManager):
+ def __init__(self, hookspecs=[hookspec]):
+ super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
+ self.register(self)
+ if os.environ.get('PYTEST_DEBUG'):
+ err = sys.stderr
+ encoding = getattr(err, 'encoding', 'utf8')
+ try:
+ err = py.io.dupfile(err, encoding=encoding)
+ except Exception:
+ pass
+ self.trace.root.setwriter(err.write)
+
+ def pytest_configure(self, config):
+ config.addinivalue_line("markers",
+ "tryfirst: mark a hook implementation function such that the "
+ "plugin machinery will try to call it first/as early as possible.")
+ config.addinivalue_line("markers",
+ "trylast: mark a hook implementation function such that the "
+ "plugin machinery will try to call it last/as late as possible.")
+
class Parser:
""" Parser for command line arguments and ini-file values. """
@@ -494,10 +575,15 @@ class Config(object):
self._opt2dest = {}
self._cleanup = []
self.pluginmanager.register(self, "pytestconfig")
+ self.pluginmanager.set_register_callback(self._register_plugin)
self._configured = False
- def pytest_plugin_registered(self, plugin):
+ def _register_plugin(self, plugin, name):
call_plugin = self.pluginmanager.call_plugin
+ call_plugin(plugin, "pytest_addhooks",
+ {'pluginmanager': self.pluginmanager})
+ self.hook.pytest_plugin_registered(plugin=plugin,
+ manager=self.pluginmanager)
dic = call_plugin(plugin, "pytest_namespace", {}) or {}
if dic:
import pytest
@@ -527,10 +613,26 @@ class Config(object):
fin = config._cleanup.pop()
fin()
+ def notify_exception(self, excinfo, option=None):
+ if option and option.fulltrace:
+ style = "long"
+ else:
+ style = "native"
+ excrepr = excinfo.getrepr(funcargs=True,
+ showlocals=getattr(option, 'showlocals', False),
+ style=style,
+ )
+ res = self.hook.pytest_internalerror(excrepr=excrepr,
+ excinfo=excinfo)
+ if not py.builtin.any(res):
+ for line in str(excrepr).split("\n"):
+ sys.stderr.write("INTERNALERROR> %s\n" %line)
+ sys.stderr.flush()
+
+
@classmethod
def fromdictargs(cls, option_dict, args):
""" constructor useable for subprocesses. """
- from _pytest.core import get_plugin_manager
pluginmanager = get_plugin_manager()
config = pluginmanager.config
# XXX slightly crude way to initialize capturing
diff --git a/_pytest/core.py b/_pytest/core.py
index fae32228d..61a8228d3 100644
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -4,17 +4,10 @@ pytest PluginManager, basic initialization and tracing.
import sys, os
import inspect
import py
-from _pytest import hookspec # the extension point definitions
-from _pytest.config import Config
assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
"%s is too old, remove or upgrade 'py'" % (py.__version__))
-default_plugins = (
- "mark main terminal runner python pdb unittest capture skipping "
- "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
- "junitxml resultlog doctest").split()
-
class TagTracer:
def __init__(self):
self._tag2proc = {}
@@ -73,7 +66,7 @@ class TagTracerSub:
return self.__class__(self.root, self.tags + (name,))
class PluginManager(object):
- def __init__(self, load=False):
+ def __init__(self, hookspecs=None):
self._name2plugin = {}
self._listattrcache = {}
self._plugins = []
@@ -81,20 +74,11 @@ class PluginManager(object):
self.trace = TagTracer().get("pluginmanage")
self._plugin_distinfo = []
self._shutdown = []
- if os.environ.get('PYTEST_DEBUG'):
- err = sys.stderr
- encoding = getattr(err, 'encoding', 'utf8')
- try:
- err = py.io.dupfile(err, encoding=encoding)
- except Exception:
- pass
- self.trace.root.setwriter(err.write)
- self.hook = HookRelay([hookspec], pm=self)
- self.register(self)
- self.config = Config(self) # XXX unclear if the attr is needed
- if load:
- for spec in default_plugins:
- self.import_plugin(spec)
+ self.hook = HookRelay(hookspecs or [], pm=self)
+
+ def set_register_callback(self, callback):
+ assert not hasattr(self, "_registercallback")
+ self._registercallback = callback
def register(self, plugin, name=None, prepend=False):
if self._name2plugin.get(name, None) == -1:
@@ -105,8 +89,9 @@ class PluginManager(object):
name, plugin, self._name2plugin))
#self.trace("registering", name, plugin)
self._name2plugin[name] = plugin
- self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
- self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
+ reg = getattr(self, "_registercallback", None)
+ if reg is not None:
+ reg(plugin, name)
if not prepend:
self._plugins.append(plugin)
else:
@@ -139,8 +124,8 @@ class PluginManager(object):
if plugin == val:
return True
- def addhooks(self, spec):
- self.hook._addhooks(spec, prefix="pytest_")
+ def addhooks(self, spec, prefix="pytest_"):
+ self.hook._addhooks(spec, prefix=prefix)
def getplugins(self):
return list(self._plugins)
@@ -240,36 +225,6 @@ class PluginManager(object):
self.register(mod, modname)
self.consider_module(mod)
- def pytest_configure(self, config):
- config.addinivalue_line("markers",
- "tryfirst: mark a hook implementation function such that the "
- "plugin machinery will try to call it first/as early as possible.")
- config.addinivalue_line("markers",
- "trylast: mark a hook implementation function such that the "
- "plugin machinery will try to call it last/as late as possible.")
-
- def pytest_terminal_summary(self, terminalreporter):
- tw = terminalreporter._tw
- if terminalreporter.config.option.traceconfig:
- for hint in self._hints:
- tw.line("hint: %s" % hint)
-
- def notify_exception(self, excinfo, option=None):
- if option and option.fulltrace:
- style = "long"
- else:
- style = "native"
- excrepr = excinfo.getrepr(funcargs=True,
- showlocals=getattr(option, 'showlocals', False),
- style=style,
- )
- res = self.hook.pytest_internalerror(excrepr=excrepr,
- excinfo=excinfo)
- if not py.builtin.any(res):
- for line in str(excrepr).split("\n"):
- sys.stderr.write("INTERNALERROR> %s\n" %line)
- sys.stderr.flush()
-
def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins
@@ -424,46 +379,3 @@ class HookCaller:
self.trace.root.indent -= 1
return res
-_preinit = []
-
-def _preloadplugins():
- assert not _preinit
- _preinit.append(PluginManager(load=True))
-
-def get_plugin_manager():
- if _preinit:
- return _preinit.pop(0)
- else: # subsequent calls to main will create a fresh instance
- return PluginManager(load=True)
-
-def _prepareconfig(args=None, plugins=None):
- if args is None:
- args = sys.argv[1:]
- elif isinstance(args, py.path.local):
- args = [str(args)]
- elif not isinstance(args, (tuple, list)):
- if not isinstance(args, str):
- raise ValueError("not a string or argument list: %r" % (args,))
- args = py.std.shlex.split(args)
- pluginmanager = get_plugin_manager()
- if plugins:
- for plugin in plugins:
- pluginmanager.register(plugin)
- return pluginmanager.hook.pytest_cmdline_parse(
- pluginmanager=pluginmanager, args=args)
-
-def main(args=None, plugins=None):
- """ return exit code, after performing an in-process test run.
-
- :arg args: list of command line arguments.
-
- :arg plugins: list of plugin objects to be auto-registered during
- initialization.
- """
- config = _prepareconfig(args, plugins)
- exitstatus = config.hook.pytest_cmdline_main(config=config)
- return exitstatus
-
-class UsageError(Exception):
- """ error in py.test usage or invocation"""
-
diff --git a/_pytest/main.py b/_pytest/main.py
index 39c7dba3f..16d89fef5 100644
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -91,7 +91,7 @@ def wrap_session(config, doit):
session.exitstatus = EXIT_INTERRUPTED
except:
excinfo = py.code.ExceptionInfo()
- config.pluginmanager.notify_exception(excinfo, config.option)
+ config.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
diff --git a/_pytest/pytester.py b/_pytest/pytester.py
index 3d6ff2933..dc9236089 100644
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -375,8 +375,8 @@ class TmpTestdir:
break
else:
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
- import _pytest.core
- config = _pytest.core._prepareconfig(args, self.plugins)
+ import _pytest.config
+ config = _pytest.config._prepareconfig(args, self.plugins)
# 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)
diff --git a/_pytest/terminal.py b/_pytest/terminal.py
index 82ec17885..e55390a23 100644
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -342,6 +342,7 @@ class TerminalReporter:
if exitstatus in (0, 1, 2, 4):
self.summary_errors()
self.summary_failures()
+ self.summary_hints()
self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2:
self._report_keyboardinterrupt()
@@ -407,6 +408,11 @@ class TerminalReporter:
l.append(x)
return l
+ def summary_hints(self):
+ if self.config.option.traceconfig:
+ for hint in self.config.pluginmanager._hints:
+ self._tw.line("hint: %s" % hint)
+
def summary_failures(self):
if self.config.option.tbstyle != "no":
reports = self.getreports('failed')
diff --git a/pytest.py b/pytest.py
index 9897780b2..6c25c6195 100644
--- a/pytest.py
+++ b/pytest.py
@@ -8,9 +8,11 @@ if __name__ == '__main__': # if run as a script or by 'python -m pytest'
# we trigger the below "else" condition by the following import
import pytest
raise SystemExit(pytest.main())
-else:
- # we are simply imported
- from _pytest.core import main, UsageError, _preloadplugins
- from _pytest import core as cmdline
- from _pytest import __version__
- _preloadplugins() # to populate pytest.* namespace so help(pytest) works
+
+# else we are imported
+
+from _pytest.config import main, UsageError, _preloadplugins, cmdline
+from _pytest import __version__
+
+_preloadplugins() # to populate pytest.* namespace so help(pytest) works
+
diff --git a/testing/test_config.py b/testing/test_config.py
index 912ebbe83..2c8bb13af 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -320,3 +320,18 @@ def test_cmdline_processargs_simple(testdir):
def test_toolongargs_issue224(testdir):
result = testdir.runpytest("-m", "hello" * 500)
assert result.ret == 0
+
+def test_notify_exception(testdir, capfd):
+ config = testdir.parseconfig()
+ excinfo = pytest.raises(ValueError, "raise ValueError(1)")
+ config.notify_exception(excinfo)
+ out, err = capfd.readouterr()
+ assert "ValueError" in err
+ class A:
+ def pytest_internalerror(self, excrepr):
+ return True
+ config.pluginmanager.register(A())
+ config.notify_exception(excinfo)
+ out, err = capfd.readouterr()
+ assert not err
+
diff --git a/testing/test_core.py b/testing/test_core.py
index 3948b4695..a347e19a3 100644
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -1,6 +1,7 @@
import pytest, py, os
from _pytest.core import PluginManager
from _pytest.core import MultiCall, HookRelay, varnames
+from _pytest.config import get_plugin_manager
class TestBootstrapping:
@@ -149,7 +150,7 @@ class TestBootstrapping:
mod = py.std.types.ModuleType("x")
mod.pytest_plugins = "pytest_a"
aplugin = testdir.makepyfile(pytest_a="#")
- pluginmanager = PluginManager()
+ pluginmanager = get_plugin_manager()
reprec = testdir.getreportrecorder(pluginmanager)
#syspath.prepend(aplugin.dirpath())
py.std.sys.path.insert(0, str(aplugin.dirpath()))
@@ -224,36 +225,21 @@ class TestBootstrapping:
assert pp.isregistered(mod)
def test_register_mismatch_method(self):
- pp = PluginManager(load=True)
+ pp = get_plugin_manager()
class hello:
def pytest_gurgel(self):
pass
pytest.raises(Exception, "pp.register(hello())")
def test_register_mismatch_arg(self):
- pp = PluginManager(load=True)
+ pp = get_plugin_manager()
class hello:
def pytest_configure(self, asd):
pass
excinfo = pytest.raises(Exception, "pp.register(hello())")
-
- def test_notify_exception(self, capfd):
- pp = PluginManager()
- excinfo = pytest.raises(ValueError, "raise ValueError(1)")
- pp.notify_exception(excinfo)
- out, err = capfd.readouterr()
- assert "ValueError" in err
- class A:
- def pytest_internalerror(self, excrepr):
- return True
- pp.register(A())
- pp.notify_exception(excinfo)
- out, err = capfd.readouterr()
- assert not err
-
def test_register(self):
- pm = PluginManager(load=False)
+ pm = get_plugin_manager()
class MyPlugin:
pass
my = MyPlugin()
@@ -261,13 +247,13 @@ class TestBootstrapping:
assert pm.getplugins()
my2 = MyPlugin()
pm.register(my2)
- assert pm.getplugins()[2:] == [my, my2]
+ assert pm.getplugins()[-2:] == [my, my2]
assert pm.isregistered(my)
assert pm.isregistered(my2)
pm.unregister(my)
assert not pm.isregistered(my)
- assert pm.getplugins()[2:] == [my2]
+ assert pm.getplugins()[-1:] == [my2]
def test_listattr(self):
plugins = PluginManager()
@@ -284,7 +270,7 @@ class TestBootstrapping:
assert l == [41, 42, 43]
def test_hook_tracing(self):
- pm = PluginManager()
+ pm = get_plugin_manager()
saveindent = []
class api1:
x = 41
@@ -319,7 +305,7 @@ class TestPytestPluginInteractions:
def pytest_myhook(xyz):
return xyz + 1
""")
- config = PluginManager(load=True).config
+ config = get_plugin_manager().config
config._conftest.importconftest(conf)
print(config.pluginmanager.getplugins())
res = config.hook.pytest_myhook(xyz=10)
diff --git a/testing/test_pytester.py b/testing/test_pytester.py
index 64600f686..ff745062f 100644
--- a/testing/test_pytester.py
+++ b/testing/test_pytester.py
@@ -104,7 +104,7 @@ def test_functional(testdir, linecomp):
def test_func(_pytest):
class ApiClass:
def pytest_xyz(self, arg): "x"
- hook = HookRelay([ApiClass], PluginManager(load=False))
+ hook = HookRelay([ApiClass], PluginManager())
rec = _pytest.gethookrecorder(hook)
class Plugin:
def pytest_xyz(self, arg):