4 new commits in pytest:
https://bitbucket.org/hpk42/pytest/commits/2d3ddee7ba00/
Changeset: 2d3ddee7ba00
User: hpk42
Date: 2013-09-30 10:19:06
Summary: localize some argcomplete-related functionality
Affected #: 1 file
diff -r 1fe40df1127840c4aaf946c8dc955c2d7d6e1912 -r
2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -2,15 +2,7 @@
import py
import sys, os
-from _pytest.core import PluginManager
import pytest
-from _pytest._argcomplete import try_argcomplete, filescompleter
-
-# enable after some grace period for plugin writers
-TYPE_WARN = False
-if TYPE_WARN:
- import warnings
-
def pytest_cmdline_parse(pluginmanager, args):
config = Config(pluginmanager)
@@ -82,6 +74,7 @@
self._anonymous.addoption(*opts, **attrs)
def parse(self, args):
+ from _pytest._argcomplete import try_argcomplete, filescompleter
self.optparser = optparser = MyOptionParser(self)
groups = self._groups + [self._anonymous]
for group in groups:
@@ -142,6 +135,8 @@
'int': int,
'string': str,
}
+ # enable after some grace period for plugin writers
+ TYPE_WARN = False
def __init__(self, *names, **attrs):
"""store parms in private vars for use in add_argument"""
@@ -149,11 +144,11 @@
self._short_opts = []
self._long_opts = []
self.dest = attrs.get('dest')
- if TYPE_WARN:
+ if self.TYPE_WARN:
try:
help = attrs['help']
if '%default' in help:
- warnings.warn(
+ py.std.warnings.warn(
'py.test now uses argparse. "%default" should be'
' changed to "%(default)s" ',
FutureWarning,
@@ -168,8 +163,8 @@
# this might raise a keyerror as well, don't want to catch that
if isinstance(typ, str):
if typ == 'choice':
- if TYPE_WARN:
- warnings.warn(
+ if self.TYPE_WARN:
+ py.std.warnings.warn(
'type argument to addoption() is a string %r.'
' For parsearg this is optional and when supplied '
' should be a type.'
@@ -180,8 +175,8 @@
# the type of the first element
attrs['type'] = type(attrs['choices'][0])
else:
- if TYPE_WARN:
- warnings.warn(
+ if self.TYPE_WARN:
+ py.std.warnings.warn(
'type argument to addoption() is a string %r.'
' For parsearg this should be a type.'
' (options: %s)' % (typ, names),
https://bitbucket.org/hpk42/pytest/commits/1a7f73bd9982/
Changeset: 1a7f73bd9982
User: hpk42
Date: 2013-09-30 13:14:14
Summary: shift pytest_configure/unconfigure/addoption/namespace hook
calling to config object.
The _pytest.config module itself is no longer a plugin but the actual
config instance is plugin-registered as ``pytestconfig``.
This allows to put most pytest specific logic to _pytest.config instead
of in the core pluginmanager.
Affected #: 8 files
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -2,20 +2,6 @@
import py
import sys, os
-import pytest
-
-def pytest_cmdline_parse(pluginmanager, args):
- config = Config(pluginmanager)
- config.parse(args)
- return config
-
-def pytest_unconfigure(config):
- while 1:
- try:
- fin = config._cleanup.pop()
- except IndexError:
- break
- fin()
class Parser:
""" Parser for command line arguments and ini-file values. """
@@ -507,13 +493,46 @@
self._inicache = {}
self._opt2dest = {}
self._cleanup = []
+ self.pluginmanager.register(self, "pytestconfig")
+ self._configured = False
+
+ def pytest_plugin_registered(self, plugin):
+ call_plugin = self.pluginmanager.call_plugin
+ dic = call_plugin(plugin, "pytest_namespace", {}) or {}
+ if dic:
+ import pytest
+ setns(pytest, dic)
+ call_plugin(plugin, "pytest_addoption", {'parser': self._parser})
+ if self._configured:
+ call_plugin(plugin, "pytest_configure", {'config': self})
+
+ 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 pytest_cmdline_parse(self, pluginmanager, args):
+ assert self == pluginmanager.config, (self, pluginmanager.config)
+ self.parse(args)
+ return self
+
+ def pytest_unconfigure(config):
+ while config._cleanup:
+ fin = config._cleanup.pop()
+ fin()
@classmethod
def fromdictargs(cls, option_dict, args):
""" constructor useable for subprocesses. """
from _pytest.core import get_plugin_manager
pluginmanager = get_plugin_manager()
- config = cls(pluginmanager)
+ config = pluginmanager.config
# XXX slightly crude way to initialize capturing
import _pytest.capture
_pytest.capture.pytest_cmdline_parse(config.pluginmanager, args)
@@ -572,11 +591,11 @@
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
self._setinitialconftest(args)
- self.pluginmanager.do_addoption(self._parser)
if addopts:
self.hook.pytest_cmdline_preparse(config=self, args=args)
def _checkversion(self):
+ import pytest
minver = self.inicfg.get('minversion', None)
if minver:
ver = minver.split(".")
@@ -723,3 +742,23 @@
return iniconfig['pytest']
return {}
+
+def setns(obj, dic):
+ import pytest
+ for name, value in dic.items():
+ if isinstance(value, dict):
+ mod = getattr(obj, name, None)
+ if mod is None:
+ modname = "pytest.%s" % name
+ mod = py.std.types.ModuleType(modname)
+ sys.modules[modname] = mod
+ mod.__all__ = []
+ setattr(obj, name, mod)
+ obj.__all__.append(name)
+ setns(mod, value)
+ else:
+ setattr(obj, name, value)
+ obj.__all__.append(name)
+ #if obj != pytest:
+ # pytest.__all__.append(name)
+ setattr(pytest, name, value)
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -1,17 +1,17 @@
"""
pytest PluginManager, basic initialization and tracing.
-(c) Holger Krekel 2004-2010
"""
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 = (
- "config mark main terminal runner python pdb unittest capture skipping "
+ "mark main terminal runner python pdb unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
"junitxml resultlog doctest").split()
@@ -91,6 +91,7 @@
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)
@@ -100,7 +101,8 @@
return
name = name or getattr(plugin, '__name__', str(id(plugin)))
if self.isregistered(plugin, name):
- raise ValueError("Plugin already registered: %s=%s" %(name,
plugin))
+ raise ValueError("Plugin already registered: %s=%s\n%s" %(
+ name, plugin, self._name2plugin))
#self.trace("registering", name, plugin)
self._name2plugin[name] = plugin
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
@@ -220,7 +222,6 @@
if self.getplugin(modname) is not None:
return
try:
- #self.trace("importing", modname)
mod = importplugin(modname)
except KeyboardInterrupt:
raise
@@ -247,59 +248,12 @@
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
- def pytest_plugin_registered(self, plugin):
- import pytest
- dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
- if dic:
- self._setns(pytest, dic)
- if hasattr(self, '_config'):
- self.call_plugin(plugin, "pytest_addoption",
- {'parser': self._config._parser})
- self.call_plugin(plugin, "pytest_configure",
- {'config': self._config})
-
- def _setns(self, obj, dic):
- import pytest
- for name, value in dic.items():
- if isinstance(value, dict):
- mod = getattr(obj, name, None)
- if mod is None:
- modname = "pytest.%s" % name
- mod = py.std.types.ModuleType(modname)
- sys.modules[modname] = mod
- mod.__all__ = []
- setattr(obj, name, mod)
- obj.__all__.append(name)
- self._setns(mod, value)
- else:
- setattr(obj, name, value)
- obj.__all__.append(name)
- #if obj != pytest:
- # pytest.__all__.append(name)
- setattr(pytest, name, value)
-
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 do_addoption(self, parser):
- mname = "pytest_addoption"
- methods = reversed(self.listattr(mname))
- MultiCall(methods, {'parser': parser}).execute()
-
- def do_configure(self, config):
- assert not hasattr(self, '_config')
- self._config = config
- config.hook.pytest_configure(config=self._config)
-
- def do_unconfigure(self, config):
- config = self._config
- del self._config
- config.hook.pytest_unconfigure(config=config)
- config.pluginmanager.ensure_shutdown()
-
def notify_exception(self, excinfo, option=None):
if option and option.fulltrace:
style = "long"
@@ -350,6 +304,7 @@
name = importspec
try:
mod = "_pytest." + name
+ #print >>sys.stderr, "tryimport", mod
__import__(mod)
return sys.modules[mod]
except ImportError:
@@ -358,6 +313,7 @@
# raise
pass #
try:
+ #print >>sys.stderr, "tryimport", importspec
__import__(importspec)
except ImportError:
raise ImportError(importspec)
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -54,9 +54,9 @@
sys.stderr.write(line + "\n")
return 0
elif config.option.help:
- config.pluginmanager.do_configure(config)
+ config.do_configure()
showhelp(config)
- config.pluginmanager.do_unconfigure(config)
+ config.do_unconfigure()
return 0
def showhelp(config):
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -76,7 +76,7 @@
initstate = 0
try:
try:
- config.pluginmanager.do_configure(config)
+ config.do_configure()
initstate = 1
config.hook.pytest_sessionstart(session=session)
initstate = 2
@@ -105,7 +105,7 @@
session=session,
exitstatus=session.exitstatus)
if initstate >= 1:
- config.pluginmanager.do_unconfigure(config)
+ config.do_unconfigure()
config.pluginmanager.ensure_shutdown()
return session.exitstatus
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -1,5 +1,5 @@
""" generic mechanism for marking and selecting python functions. """
-import pytest, py
+import py
def pytest_namespace():
@@ -39,14 +39,14 @@
def pytest_cmdline_main(config):
if config.option.markers:
- config.pluginmanager.do_configure(config)
+ 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.pluginmanager.do_unconfigure(config)
+ config.do_unconfigure()
return 0
pytest_cmdline_main.tryfirst = True
@@ -129,6 +129,7 @@
mapped_names = set()
# Add the names of the current item and any parent items
+ import pytest
for item in colitem.listchain():
if not isinstance(item, pytest.Instance):
mapped_names.add(item.name)
@@ -145,6 +146,7 @@
def pytest_configure(config):
+ import pytest
if config.option.strict:
pytest.mark._config = config
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/pytester.py
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -390,9 +390,9 @@
def parseconfigure(self, *args):
config = self.parseconfig(*args)
- config.pluginmanager.do_configure(config)
+ config.do_configure()
self.request.addfinalizer(lambda:
- config.pluginmanager.do_unconfigure(config))
+ config.do_unconfigure())
return config
def getitem(self, source, funcname="test_func"):
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -261,13 +261,13 @@
assert pm.getplugins()
my2 = MyPlugin()
pm.register(my2)
- assert pm.getplugins()[1:] == [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()[1:] == [my2]
+ assert pm.getplugins()[2:] == [my2]
def test_listattr(self):
plugins = PluginManager()
@@ -319,7 +319,7 @@
def pytest_myhook(xyz):
return xyz + 1
""")
- config = testdir.Config(PluginManager(load=True))
+ config = PluginManager(load=True).config
config._conftest.importconftest(conf)
print(config.pluginmanager.getplugins())
res = config.hook.pytest_myhook(xyz=10)
@@ -383,13 +383,13 @@
config.pluginmanager.register(A())
assert len(l) == 0
- config.pluginmanager.do_configure(config=config)
+ 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.pluginmanager.do_unconfigure(config=config)
+ config.do_unconfigure()
config.pluginmanager.register(A())
assert len(l) == 2
diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r
1a7f73bd9982815bc9de2c3944171d69aa2658a0 testing/test_session.py
--- a/testing/test_session.py
+++ b/testing/test_session.py
@@ -208,13 +208,14 @@
testdir.parseconfig("-p", "nqweotexistent")
""")
#pytest.raises(ImportError,
- # "config.pluginmanager.do_configure(config)"
+ # "config.do_configure(config)"
#)
def test_plugin_already_exists(testdir):
config = testdir.parseconfig("-p", "terminal")
assert config.option.plugins == ['terminal']
- config.pluginmanager.do_configure(config)
+ config.do_configure()
+ config.do_unconfigure()
def test_exclude(testdir):
hellodir = testdir.mkdir("hello")
https://bitbucket.org/hpk42/pytest/commits/0371f8e21864/
Changeset: 0371f8e21864
User: hpk42
Date: 2013-09-30 13:14:14
Summary: 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.
Affected #: 9 files
diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d _pytest/config.py
--- 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 @@
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 @@
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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -4,17 +4,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -91,7 +91,7 @@
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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d _pytest/pytester.py
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -375,8 +375,8 @@
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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -342,6 +342,7 @@
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 @@
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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d pytest.py
--- a/pytest.py
+++ b/pytest.py
@@ -8,9 +8,11 @@
# 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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d testing/test_config.py
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -320,3 +320,18 @@
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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d testing/test_core.py
--- 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 @@
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 @@
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 @@
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 @@
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 @@
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 -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r
0371f8e218643f9c780189ade8f213016dea974d testing/test_pytester.py
--- a/testing/test_pytester.py
+++ b/testing/test_pytester.py
@@ -104,7 +104,7 @@
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):
https://bitbucket.org/hpk42/pytest/commits/cd10ce8eb344/
Changeset: cd10ce8eb344
User: hpk42
Date: 2013-09-30 13:14:16
Summary: fix issue358 -- introduce new pytest_load_initial_conftests hook
and make capturing initialization use it, relying on a new (somewhat internal)
parser.parse_known_args() method.
This also addresses issue359 -- plugins like pytest-django could implement a
pytest_load_initial_conftests hook like the capture plugin.
Affected #: 7 files
diff -r 0371f8e218643f9c780189ade8f213016dea974d -r
cd10ce8eb3440922c538b67256cbf92b09a29b6f CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -75,6 +75,9 @@
- fix issue 308 - allow to mark/xfail/skip individual parameter sets
when parametrizing. Thanks Brianna Laugher.
+- call new experimental pytest_load_initial_conftests hook to allow
+ 3rd party plugins to do something before a conftest is loaded.
+
Bug fixes:
- pytest now uses argparse instead of optparse (thanks Anthon) which
diff -r 0371f8e218643f9c780189ade8f213016dea974d -r
cd10ce8eb3440922c538b67256cbf92b09a29b6f _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -1,6 +1,7 @@
""" per-test stdout/stderr capturing mechanisms, ``capsys`` and ``capfd``
function arguments. """
import pytest, py
+import sys
import os
def pytest_addoption(parser):
@@ -12,23 +13,34 @@
help="shortcut for --capture=no.")
@pytest.mark.tryfirst
-def pytest_cmdline_parse(pluginmanager, args):
- # we want to perform capturing already for plugin/conftest loading
- if '-s' in args or "--capture=no" in args:
- method = "no"
- elif hasattr(os, 'dup') and '--capture=sys' not in args:
+def pytest_load_initial_conftests(early_config, parser, args, __multicall__):
+ ns = parser.parse_known_args(args)
+ method = ns.capture
+ if not method:
method = "fd"
- else:
+ if method == "fd" and not hasattr(os, "dup"):
method = "sys"
capman = CaptureManager(method)
- pluginmanager.register(capman, "capturemanager")
+ early_config.pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
def teardown():
try:
capman.reset_capturings()
except ValueError:
pass
- pluginmanager.add_shutdown(teardown)
+ early_config.pluginmanager.add_shutdown(teardown)
+
+ # finally trigger conftest loading but while capturing (issue93)
+ capman.resumecapture()
+ try:
+ try:
+ return __multicall__.execute()
+ finally:
+ out, err = capman.suspendcapture()
+ except:
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+ raise
def addouterr(rep, outerr):
for secname, content in zip(["out", "err"], outerr):
@@ -89,7 +101,6 @@
for name, cap in self._method2capture.items():
cap.reset()
-
def resumecapture_item(self, item):
method = self._getmethod(item.config, item.fspath)
if not hasattr(item, 'outerr'):
diff -r 0371f8e218643f9c780189ade8f213016dea974d -r
cd10ce8eb3440922c538b67256cbf92b09a29b6f _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -141,8 +141,14 @@
self._anonymous.addoption(*opts, **attrs)
def parse(self, args):
- from _pytest._argcomplete import try_argcomplete, filescompleter
- self.optparser = optparser = MyOptionParser(self)
+ from _pytest._argcomplete import try_argcomplete
+ self.optparser = self._getparser()
+ try_argcomplete(self.optparser)
+ return self.optparser.parse_args([str(x) for x in args])
+
+ def _getparser(self):
+ from _pytest._argcomplete import filescompleter
+ optparser = MyOptionParser(self)
groups = self._groups + [self._anonymous]
for group in groups:
if group.options:
@@ -155,8 +161,7 @@
# bash like autocompletion for dirs (appending '/')
optparser.add_argument(FILE_OR_DIR, nargs='*'
).completer=filescompleter
- try_argcomplete(self.optparser)
- return self.optparser.parse_args([str(x) for x in args])
+ return optparser
def parse_setoption(self, args, option):
parsedoption = self.parse(args)
@@ -164,6 +169,11 @@
setattr(option, name, value)
return getattr(parsedoption, FILE_OR_DIR)
+ def parse_known_args(self, args):
+ optparser = self._getparser()
+ args = [str(x) for x in args]
+ return optparser.parse_known_args(args)[0]
+
def addini(self, name, help, type=None, default=None):
""" register an ini-file option.
@@ -635,9 +645,6 @@
""" constructor useable for subprocesses. """
pluginmanager = get_plugin_manager()
config = pluginmanager.config
- # XXX slightly crude way to initialize capturing
- import _pytest.capture
- _pytest.capture.pytest_cmdline_parse(config.pluginmanager, args)
config._preparse(args, addopts=False)
config.option.__dict__.update(option_dict)
for x in config.option.plugins:
@@ -663,21 +670,9 @@
plugins += self._conftest.getconftestmodules(fspath)
return plugins
- def _setinitialconftest(self, args):
- # capture output during conftest init (#issue93)
- # XXX introduce load_conftest hook to avoid needing to know
- # about capturing plugin here
- capman = self.pluginmanager.getplugin("capturemanager")
- capman.resumecapture()
- try:
- try:
- self._conftest.setinitial(args)
- finally:
- out, err = capman.suspendcapture() # logging might have got it
- except:
- sys.stdout.write(out)
- sys.stderr.write(err)
- raise
+ def pytest_load_initial_conftests(self, parser, args):
+ self._conftest.setinitial(args)
+ pytest_load_initial_conftests.trylast = True
def _initini(self, args):
self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"])
@@ -692,9 +687,8 @@
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
- self._setinitialconftest(args)
- if addopts:
- self.hook.pytest_cmdline_preparse(config=self, args=args)
+ self.hook.pytest_load_initial_conftests(early_config=self,
+ args=args, parser=self._parser)
def _checkversion(self):
import pytest
@@ -715,6 +709,8 @@
"can only parse cmdline args at most once per Config object")
self._origargs = args
self._preparse(args)
+ # XXX deprecated hook:
+ self.hook.pytest_cmdline_preparse(config=self, args=args)
self._parser.hints.extend(self.pluginmanager._hints)
args = self._parser.parse_setoption(args, self.option)
if not args:
diff -r 0371f8e218643f9c780189ade8f213016dea974d -r
cd10ce8eb3440922c538b67256cbf92b09a29b6f _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -20,7 +20,7 @@
pytest_cmdline_parse.firstresult = True
def pytest_cmdline_preparse(config, args):
- """modify command line arguments before option parsing. """
+ """(deprecated) modify command line arguments before option parsing. """
def pytest_addoption(parser):
"""register argparse-style options and ini-style config values.
@@ -52,6 +52,10 @@
implementation will invoke the configure hooks and runtest_mainloop. """
pytest_cmdline_main.firstresult = True
+def pytest_load_initial_conftests(args, early_config, parser):
+ """ implements loading initial conftests.
+ """
+
def pytest_configure(config):
""" called after command line options have been parsed
and all plugins and initial conftest files been loaded.
diff -r 0371f8e218643f9c780189ade8f213016dea974d -r
cd10ce8eb3440922c538b67256cbf92b09a29b6f testing/test_capture.py
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -484,3 +484,13 @@
result = testdir.runpytest()
assert result.ret == 0
assert 'hello19' not in result.stdout.str()
+
+def test_capture_early_option_parsing(testdir):
+ testdir.makeconftest("""
+ def pytest_runtest_setup():
+ print ("hello19")
+ """)
+ testdir.makepyfile("def test_func(): pass")
+ result = testdir.runpytest("-vs")
+ assert result.ret == 0
+ assert 'hello19' in result.stdout.str()
diff -r 0371f8e218643f9c780189ade8f213016dea974d -r
cd10ce8eb3440922c538b67256cbf92b09a29b6f testing/test_config.py
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -335,3 +335,18 @@
out, err = capfd.readouterr()
assert not err
+
+def test_load_initial_conftest_last_ordering(testdir):
+ from _pytest.config import get_plugin_manager
+ pm = get_plugin_manager()
+ class My:
+ def pytest_load_initial_conftests(self):
+ pass
+ m = My()
+ pm.register(m)
+ l = pm.listattr("pytest_load_initial_conftests")
+ assert l[-1].__module__ == "_pytest.capture"
+ assert l[-2] == m.pytest_load_initial_conftests
+ assert l[-3].__module__ == "_pytest.config"
+
+
diff -r 0371f8e218643f9c780189ade8f213016dea974d -r
cd10ce8eb3440922c538b67256cbf92b09a29b6f testing/test_parseopt.py
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -101,6 +101,12 @@
args = parser.parse([py.path.local()])
assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local()
+ def test_parse_known_args(self, parser):
+ args = parser.parse_known_args([py.path.local()])
+ parser.addoption("--hello", action="store_true")
+ ns = parser.parse_known_args(["x", "--y", "--hello", "this"])
+ assert ns.hello
+
def test_parse_will_set_default(self, parser):
parser.addoption("--hello", dest="hello", default="x", action="store")
option = parser.parse([])
Repository URL: https://bitbucket.org/hpk42/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.
_______________________________________________
pytest-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pytest-commit