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
pytest-commit@python.org
https://mail.python.org/mailman/listinfo/pytest-commit

Reply via email to