[Pytest-commit] commit/pytest: 17 new changesets

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Sat Apr 25 09:08:35 CEST 2015


17 new commits in pytest:

https://bitbucket.org/pytest-dev/pytest/commits/f5541d71ac9c/
Changeset:   f5541d71ac9c
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 08:04:13+00:00
Summary:     remove redundant py check as our setup.py excludes py <=1.4 already
Affected #:  1 file

diff -r 2361a9322d2fd01a0addeab80fc1bd9a15e60a08 -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -7,8 +7,6 @@
 import py
 # don't import pytest to avoid circular imports
 
-assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
-    "%s is too old, remove or upgrade 'py'" % (py.__version__))
 
 py3 = sys.version_info > (3,0)
 


https://bitbucket.org/pytest-dev/pytest/commits/21aefbeaf96f/
Changeset:   21aefbeaf96f
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 11:31:46+00:00
Summary:     minimize HookRelay to become a pure container, refactor initialization and
tests of plugin management to be a bit better split between pytest
and pytest-independent bits
Affected #:  7 files

diff -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c -r 21aefbeaf96fb994bce816d3884707100d0b74da _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -87,9 +87,17 @@
         pluginmanager.ensure_shutdown()
         raise
 
+def exclude_pytest_names(name):
+    return not name.startswith(name) or name == "pytest_plugins" or \
+           name.startswith("pytest_funcarg__")
+
 class PytestPluginManager(PluginManager):
-    def __init__(self, hookspecs=[hookspec]):
-        super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
+    def __init__(self):
+        super(PytestPluginManager, self).__init__(prefix="pytest_",
+                                                  excludefunc=exclude_pytest_names)
+        self._warnings = []
+        self._plugin_distinfo = []
+        self.addhooks(hookspec)
         self.register(self)
         if os.environ.get('PYTEST_DEBUG'):
             err = sys.stderr
@@ -100,6 +108,14 @@
                 pass
             self.set_tracing(err.write)
 
+    def getplugin(self, name):
+        if name is None:
+            return name
+        plugin = super(PytestPluginManager, self).getplugin(name)
+        if plugin is None:
+            plugin = super(PytestPluginManager, self).getplugin("_pytest." + name)
+        return plugin
+
     def pytest_configure(self, config):
         config.addinivalue_line("markers",
             "tryfirst: mark a hook implementation function such that the "
@@ -110,6 +126,89 @@
         for warning in self._warnings:
             config.warn(code="I1", message=warning)
 
+    #
+    # API for bootstrapping plugin loading
+    #
+    #
+    def _envlist(self, varname):
+        val = os.environ.get(varname, None)
+        if val is not None:
+            return val.split(',')
+        return ()
+
+    def consider_env(self):
+        for spec in self._envlist("PYTEST_PLUGINS"):
+            self.import_plugin(spec)
+
+    def consider_setuptools_entrypoints(self):
+        try:
+            from pkg_resources import iter_entry_points, DistributionNotFound
+        except ImportError:
+            return # XXX issue a warning
+        for ep in iter_entry_points('pytest11'):
+            name = ep.name
+            if name.startswith("pytest_"):
+                name = name[7:]
+            if ep.name in self._name2plugin or name in self._name2plugin:
+                continue
+            try:
+                plugin = ep.load()
+            except DistributionNotFound:
+                continue
+            self._plugin_distinfo.append((ep.dist, plugin))
+            self.register(plugin, name=name)
+
+    def consider_preparse(self, args):
+        for opt1,opt2 in zip(args, args[1:]):
+            if opt1 == "-p":
+                self.consider_pluginarg(opt2)
+
+    def consider_pluginarg(self, arg):
+        if arg.startswith("no:"):
+            name = arg[3:]
+            plugin = self.getplugin(name)
+            if plugin is not None:
+                self.unregister(plugin)
+            self._name2plugin[name] = -1
+        else:
+            if self.getplugin(arg) is None:
+                self.import_plugin(arg)
+
+    def consider_conftest(self, conftestmodule):
+        if self.register(conftestmodule, name=conftestmodule.__file__,
+                         conftest=True):
+            self.consider_module(conftestmodule)
+
+    def consider_module(self, mod):
+        attr = getattr(mod, "pytest_plugins", ())
+        if attr:
+            if not isinstance(attr, (list, tuple)):
+                attr = (attr,)
+            for spec in attr:
+                self.import_plugin(spec)
+
+    def import_plugin(self, modname):
+        assert isinstance(modname, str)
+        if self.getplugin(modname) is not None:
+            return
+        try:
+            mod = importplugin(modname)
+        except KeyboardInterrupt:
+            raise
+        except ImportError:
+            if modname.startswith("pytest_"):
+                return self.import_plugin(modname[7:])
+            raise
+        except:
+            e = sys.exc_info()[1]
+            import pytest
+            if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
+                raise
+            self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
+        else:
+            self.register(mod, modname)
+            self.consider_module(mod)
+
 
 class Parser:
     """ Parser for command line arguments and ini-file values.  """
@@ -933,3 +1032,15 @@
             #if obj != pytest:
             #    pytest.__all__.append(name)
             setattr(pytest, name, value)
+
+
+def importplugin(importspec):
+    name = importspec
+    try:
+        mod = "_pytest." + name
+        __import__(mod)
+        return sys.modules[mod]
+    except ImportError:
+        __import__(importspec)
+        return sys.modules[importspec]
+

diff -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c -r 21aefbeaf96fb994bce816d3884707100d0b74da _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -1,12 +1,10 @@
 """
-pytest PluginManager, basic initialization and tracing.
+PluginManager, basic initialization and tracing.
 """
 import os
 import sys
 import inspect
 import py
-# don't import pytest to avoid circular imports
-
 
 py3 = sys.version_info > (3,0)
 
@@ -137,16 +135,16 @@
 
 
 class PluginManager(object):
-    def __init__(self, hookspecs=None, prefix="pytest_"):
+    def __init__(self, prefix, excludefunc=None):
+        self._prefix = prefix
+        self._excludefunc = excludefunc
         self._name2plugin = {}
         self._plugins = []
         self._conftestplugins = []
         self._plugin2hookcallers = {}
-        self._warnings = []
         self.trace = TagTracer().get("pluginmanage")
-        self._plugin_distinfo = []
         self._shutdown = []
-        self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix)
+        self.hook = HookRelay(pm=self)
 
     def set_tracing(self, writer):
         self.trace.root.setwriter(writer)
@@ -174,6 +172,39 @@
         assert not hasattr(self, "_registercallback")
         self._registercallback = callback
 
+    def make_hook_caller(self, name, plugins):
+        caller = getattr(self.hook, name)
+        methods = self.listattr(name, plugins=plugins)
+        if methods:
+            return HookCaller(self.hook, caller.name, caller.firstresult,
+                              argnames=caller.argnames, methods=methods)
+        return caller
+
+    def _scan_plugin(self, plugin):
+        def fail(msg, *args):
+            name = getattr(plugin, '__name__', plugin)
+            raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
+
+        for name in dir(plugin):
+            if name[0] == "_" or not name.startswith(self._prefix):
+                continue
+            hook = getattr(self.hook, name, None)
+            method = getattr(plugin, name)
+            if hook is None:
+                if self._excludefunc is not None and self._excludefunc(name):
+                    continue
+                if getattr(method, 'optionalhook', False):
+                    continue
+                fail("found unknown hook: %r", name)
+            for arg in varnames(method):
+                if arg not in hook.argnames:
+                    fail("argument %r not available\n"
+                         "actual definition: %s\n"
+                         "available hookargs: %s",
+                         arg, formatdef(method),
+                           ", ".join(hook.argnames))
+            yield hook
+
     def register(self, plugin, name=None, prepend=False, conftest=False):
         if self._name2plugin.get(name, None) == -1:
             return
@@ -185,7 +216,7 @@
         reg = getattr(self, "_registercallback", None)
         if reg is not None:
             reg(plugin, name)  # may call addhooks
-        hookcallers = list(self.hook._scan_plugin(plugin))
+        hookcallers = list(self._scan_plugin(plugin))
         self._plugin2hookcallers[plugin] = hookcallers
         self._name2plugin[name] = plugin
         if conftest:
@@ -227,108 +258,29 @@
             return True
         return plugin in self._plugins or plugin in self._conftestplugins
 
-    def addhooks(self, spec, prefix="pytest_"):
-        self.hook._addhooks(spec, prefix=prefix)
+    def addhooks(self, module_or_class):
+        isclass = int(inspect.isclass(module_or_class))
+        names = []
+        for name in dir(module_or_class):
+            if name.startswith(self._prefix):
+                method = module_or_class.__dict__[name]
+                firstresult = getattr(method, 'firstresult', False)
+                hc = HookCaller(self.hook, name, firstresult=firstresult,
+                                argnames=varnames(method, startindex=isclass))
+                setattr(self.hook, name, hc)
+                names.append(name)
+        if not names:
+            raise ValueError("did not find new %r hooks in %r"
+                             %(self._prefix, module_or_class))
 
     def getplugins(self):
         return self._plugins + self._conftestplugins
 
-    def skipifmissing(self, name):
-        if not self.hasplugin(name):
-            import pytest
-            pytest.skip("plugin %r is missing" % name)
-
     def hasplugin(self, name):
         return bool(self.getplugin(name))
 
     def getplugin(self, name):
-        if name is None:
-            return None
-        try:
-            return self._name2plugin[name]
-        except KeyError:
-            return self._name2plugin.get("_pytest." + name, None)
-
-    # API for bootstrapping
-    #
-    def _envlist(self, varname):
-        val = os.environ.get(varname, None)
-        if val is not None:
-            return val.split(',')
-        return ()
-
-    def consider_env(self):
-        for spec in self._envlist("PYTEST_PLUGINS"):
-            self.import_plugin(spec)
-
-    def consider_setuptools_entrypoints(self):
-        try:
-            from pkg_resources import iter_entry_points, DistributionNotFound
-        except ImportError:
-            return # XXX issue a warning
-        for ep in iter_entry_points('pytest11'):
-            name = ep.name
-            if name.startswith("pytest_"):
-                name = name[7:]
-            if ep.name in self._name2plugin or name in self._name2plugin:
-                continue
-            try:
-                plugin = ep.load()
-            except DistributionNotFound:
-                continue
-            self._plugin_distinfo.append((ep.dist, plugin))
-            self.register(plugin, name=name)
-
-    def consider_preparse(self, args):
-        for opt1,opt2 in zip(args, args[1:]):
-            if opt1 == "-p":
-                self.consider_pluginarg(opt2)
-
-    def consider_pluginarg(self, arg):
-        if arg.startswith("no:"):
-            name = arg[3:]
-            plugin = self.getplugin(name)
-            if plugin is not None:
-                self.unregister(plugin)
-            self._name2plugin[name] = -1
-        else:
-            if self.getplugin(arg) is None:
-                self.import_plugin(arg)
-
-    def consider_conftest(self, conftestmodule):
-        if self.register(conftestmodule, name=conftestmodule.__file__,
-                         conftest=True):
-            self.consider_module(conftestmodule)
-
-    def consider_module(self, mod):
-        attr = getattr(mod, "pytest_plugins", ())
-        if attr:
-            if not isinstance(attr, (list, tuple)):
-                attr = (attr,)
-            for spec in attr:
-                self.import_plugin(spec)
-
-    def import_plugin(self, modname):
-        assert isinstance(modname, str)
-        if self.getplugin(modname) is not None:
-            return
-        try:
-            mod = importplugin(modname)
-        except KeyboardInterrupt:
-            raise
-        except ImportError:
-            if modname.startswith("pytest_"):
-                return self.import_plugin(modname[7:])
-            raise
-        except:
-            e = sys.exc_info()[1]
-            import pytest
-            if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
-                raise
-            self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
-        else:
-            self.register(mod, modname)
-            self.consider_module(mod)
+        return self._name2plugin.get(name)
 
     def listattr(self, attrname, plugins=None):
         if plugins is None:
@@ -358,16 +310,6 @@
                 kwargs=kwargs, firstresult=True).execute()
 
 
-def importplugin(importspec):
-    name = importspec
-    try:
-        mod = "_pytest." + name
-        __import__(mod)
-        return sys.modules[mod]
-    except ImportError:
-        __import__(importspec)
-        return sys.modules[importspec]
-
 class MultiCall:
     """ execute a call into multiple python functions/methods. """
 
@@ -439,60 +381,9 @@
 
 
 class HookRelay:
-    def __init__(self, hookspecs, pm, prefix="pytest_"):
-        if not isinstance(hookspecs, list):
-            hookspecs = [hookspecs]
+    def __init__(self, pm):
         self._pm = pm
         self.trace = pm.trace.root.get("hook")
-        self.prefix = prefix
-        for hookspec in hookspecs:
-            self._addhooks(hookspec, prefix)
-
-    def _addhooks(self, hookspec, prefix):
-        added = False
-        isclass = int(inspect.isclass(hookspec))
-        for name, method in vars(hookspec).items():
-            if name.startswith(prefix):
-                firstresult = getattr(method, 'firstresult', False)
-                hc = HookCaller(self, name, firstresult=firstresult,
-                                argnames=varnames(method, startindex=isclass))
-                setattr(self, name, hc)
-                added = True
-                #print ("setting new hook", name)
-        if not added:
-            raise ValueError("did not find new %r hooks in %r" %(
-                prefix, hookspec,))
-
-    def _getcaller(self, name, plugins):
-        caller = getattr(self, name)
-        methods = self._pm.listattr(name, plugins=plugins)
-        if methods:
-            return caller.new_cached_caller(methods)
-        return caller
-
-    def _scan_plugin(self, plugin):
-        def fail(msg, *args):
-            name = getattr(plugin, '__name__', plugin)
-            raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
-
-        for name in dir(plugin):
-            if not name.startswith(self.prefix):
-                continue
-            hook = getattr(self, name, None)
-            method = getattr(plugin, name)
-            if hook is None:
-                is_optional = getattr(method, 'optionalhook', False)
-                if not isgenerichook(name) and not is_optional:
-                    fail("found unknown hook: %r", name)
-                continue
-            for arg in varnames(method):
-                if arg not in hook.argnames:
-                    fail("argument %r not available\n"
-                         "actual definition: %s\n"
-                         "available hookargs: %s",
-                         arg, formatdef(method),
-                           ", ".join(hook.argnames))
-            yield hook
 
 
 class HookCaller:
@@ -505,10 +396,6 @@
         assert "self" not in argnames  # sanity check
         self.methods = methods
 
-    def new_cached_caller(self, methods):
-        return HookCaller(self.hookrelay, self.name, self.firstresult,
-                          argnames=self.argnames, methods=methods)
-
     def __repr__(self):
         return "<HookCaller %r>" %(self.name,)
 
@@ -529,13 +416,9 @@
 class PluginValidationError(Exception):
     """ plugin failed validation. """
 
-def isgenerichook(name):
-    return name == "pytest_plugins" or \
-           name.startswith("pytest_funcarg__")
 
 def formatdef(func):
     return "%s%s" % (
         func.__name__,
         inspect.formatargspec(*inspect.getargspec(func))
     )
-

diff -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c -r 21aefbeaf96fb994bce816d3884707100d0b74da _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -6,7 +6,7 @@
 
 def pytest_addhooks(pluginmanager):
     """called at plugin load time to allow adding new hooks via a call to
-    pluginmanager.registerhooks(module)."""
+    pluginmanager.addhooks(module_or_class, prefix)."""
 
 
 def pytest_namespace():

diff -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c -r 21aefbeaf96fb994bce816d3884707100d0b74da _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -160,7 +160,7 @@
 
     def __getattr__(self, name):
         plugins = self.config._getmatchingplugins(self.fspath)
-        x = self.config.hook._getcaller(name, plugins)
+        x = self.config.pluginmanager.make_hook_caller(name, plugins)
         self.__dict__[name] = x
         return x
 

diff -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c -r 21aefbeaf96fb994bce816d3884707100d0b74da testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -1,236 +1,62 @@
 import pytest, py, os
 from _pytest.core import * # noqa
-from _pytest.config import get_plugin_manager
+from _pytest.config import get_plugin_manager, importplugin
 
 
-class TestBootstrapping:
-    def test_consider_env_fails_to_import(self, monkeypatch):
-        pluginmanager = PluginManager()
-        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
-        pytest.raises(ImportError, lambda: pluginmanager.consider_env())
+ at pytest.fixture
+def pm():
+    return PluginManager("he")
 
-    def test_preparse_args(self):
-        pluginmanager = PluginManager()
-        pytest.raises(ImportError, lambda:
-            pluginmanager.consider_preparse(["xyz", "-p", "hello123"]))
+ at pytest.fixture
+def pytestpm():
+    return PytestPluginManager()
 
-    def test_plugin_prevent_register(self):
-        pluginmanager = PluginManager()
-        pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
-        l1 = pluginmanager.getplugins()
-        pluginmanager.register(42, name="abc")
-        l2 = pluginmanager.getplugins()
-        assert len(l2) == len(l1)
 
-    def test_plugin_prevent_register_unregistered_alredy_registered(self):
-        pluginmanager = PluginManager()
-        pluginmanager.register(42, name="abc")
-        l1 = pluginmanager.getplugins()
-        assert 42 in l1
-        pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
-        l2 = pluginmanager.getplugins()
-        assert 42 not in l2
+class TestPluginManager:
+    def test_plugin_double_register(self, pm):
+        pm.register(42, name="abc")
+        with pytest.raises(ValueError):
+            pm.register(42, name="abc")
 
-    def test_plugin_double_register(self):
-        pm = PluginManager()
-        pm.register(42, name="abc")
-        pytest.raises(ValueError, lambda: pm.register(42, name="abc"))
-
-    def test_plugin_skip(self, testdir, monkeypatch):
-        p = testdir.makepyfile(skipping1="""
-            import pytest
-            pytest.skip("hello")
-        """)
-        p.copy(p.dirpath("skipping2.py"))
-        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
-        result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "WI1*skipped plugin*skipping1*hello*",
-            "WI1*skipped plugin*skipping2*hello*",
-        ])
-
-    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
-        pluginmanager = PluginManager()
-        testdir.syspathinsert()
-        testdir.makepyfile(xy123="#")
-        monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
-        l1 = len(pluginmanager.getplugins())
-        pluginmanager.consider_env()
-        l2 = len(pluginmanager.getplugins())
-        assert l2 == l1 + 1
-        assert pluginmanager.getplugin('xy123')
-        pluginmanager.consider_env()
-        l3 = len(pluginmanager.getplugins())
-        assert l2 == l3
-
-    def test_consider_setuptools_instantiation(self, monkeypatch):
-        pkg_resources = pytest.importorskip("pkg_resources")
-        def my_iter(name):
-            assert name == "pytest11"
-            class EntryPoint:
-                name = "pytest_mytestplugin"
-                dist = None
-                def load(self):
-                    class PseudoPlugin:
-                        x = 42
-                    return PseudoPlugin()
-            return iter([EntryPoint()])
-
-        monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
-        pluginmanager = PluginManager()
-        pluginmanager.consider_setuptools_entrypoints()
-        plugin = pluginmanager.getplugin("mytestplugin")
-        assert plugin.x == 42
-
-    def test_consider_setuptools_not_installed(self, monkeypatch):
-        monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
-            py.std.types.ModuleType("pkg_resources"))
-        pluginmanager = PluginManager()
-        pluginmanager.consider_setuptools_entrypoints()
-        # ok, we did not explode
-
-    def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
-        testdir.makepyfile(pytest_x500="#")
-        p = testdir.makepyfile("""
-            import pytest
-            def test_hello(pytestconfig):
-                plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
-                assert plugin is not None
-        """)
-        monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
-        result = testdir.runpytest(p)
-        assert result.ret == 0
-        result.stdout.fnmatch_lines(["*1 passed in*"])
-
-    def test_import_plugin_importname(self, testdir):
-        pluginmanager = PluginManager()
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwx.y")')
-
-        testdir.syspathinsert()
-        pluginname = "pytest_hello"
-        testdir.makepyfile(**{pluginname: ""})
-        pluginmanager.import_plugin("pytest_hello")
-        len1 = len(pluginmanager.getplugins())
-        pluginmanager.import_plugin("pytest_hello")
-        len2 = len(pluginmanager.getplugins())
-        assert len1 == len2
-        plugin1 = pluginmanager.getplugin("pytest_hello")
-        assert plugin1.__name__.endswith('pytest_hello')
-        plugin2 = pluginmanager.getplugin("pytest_hello")
-        assert plugin2 is plugin1
-
-    def test_import_plugin_dotted_name(self, testdir):
-        pluginmanager = PluginManager()
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwex.y")')
-
-        testdir.syspathinsert()
-        testdir.mkpydir("pkg").join("plug.py").write("x=3")
-        pluginname = "pkg.plug"
-        pluginmanager.import_plugin(pluginname)
-        mod = pluginmanager.getplugin("pkg.plug")
-        assert mod.x == 3
-
-    def test_consider_module(self, testdir):
-        pluginmanager = PluginManager()
-        testdir.syspathinsert()
-        testdir.makepyfile(pytest_p1="#")
-        testdir.makepyfile(pytest_p2="#")
-        mod = py.std.types.ModuleType("temp")
-        mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
-        pluginmanager.consider_module(mod)
-        assert pluginmanager.getplugin("pytest_p1").__name__ == "pytest_p1"
-        assert pluginmanager.getplugin("pytest_p2").__name__ == "pytest_p2"
-
-    def test_consider_module_import_module(self, testdir):
-        mod = py.std.types.ModuleType("x")
-        mod.pytest_plugins = "pytest_a"
-        aplugin = testdir.makepyfile(pytest_a="#")
-        pluginmanager = get_plugin_manager()
-        reprec = testdir.make_hook_recorder(pluginmanager)
-        #syspath.prepend(aplugin.dirpath())
-        py.std.sys.path.insert(0, str(aplugin.dirpath()))
-        pluginmanager.consider_module(mod)
-        call = reprec.getcall(pluginmanager.hook.pytest_plugin_registered.name)
-        assert call.plugin.__name__ == "pytest_a"
-
-        # check that it is not registered twice
-        pluginmanager.consider_module(mod)
-        l = reprec.getcalls("pytest_plugin_registered")
-        assert len(l) == 1
-
-    def test_config_sets_conftesthandle_onimport(self, testdir):
-        config = testdir.parseconfig([])
-        assert config._conftest._onimport == config._onimportconftest
-
-    def test_consider_conftest_deps(self, testdir):
-        mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
-        pp = PluginManager()
-        pytest.raises(ImportError, lambda: pp.consider_conftest(mod))
-
-    def test_pm(self):
-        pp = PluginManager()
+    def test_pm(self, pm):
         class A: pass
         a1, a2 = A(), A()
-        pp.register(a1)
-        assert pp.isregistered(a1)
-        pp.register(a2, "hello")
-        assert pp.isregistered(a2)
-        l = pp.getplugins()
+        pm.register(a1)
+        assert pm.isregistered(a1)
+        pm.register(a2, "hello")
+        assert pm.isregistered(a2)
+        l = pm.getplugins()
         assert a1 in l
         assert a2 in l
-        assert pp.getplugin('hello') == a2
-        pp.unregister(a1)
-        assert not pp.isregistered(a1)
+        assert pm.getplugin('hello') == a2
+        pm.unregister(a1)
+        assert not pm.isregistered(a1)
 
-    def test_pm_ordering(self):
-        pp = PluginManager()
+    def test_pm_ordering(self, pm):
         class A: pass
         a1, a2 = A(), A()
-        pp.register(a1)
-        pp.register(a2, "hello")
-        l = pp.getplugins()
+        pm.register(a1)
+        pm.register(a2, "hello")
+        l = pm.getplugins()
         assert l.index(a1) < l.index(a2)
         a3 = A()
-        pp.register(a3, prepend=True)
-        l = pp.getplugins()
+        pm.register(a3, prepend=True)
+        l = pm.getplugins()
         assert l.index(a3) == 0
 
-    def test_register_imported_modules(self):
-        pp = PluginManager()
-        mod = py.std.types.ModuleType("x.y.pytest_hello")
-        pp.register(mod)
-        assert pp.isregistered(mod)
-        l = pp.getplugins()
-        assert mod in l
-        pytest.raises(ValueError, "pp.register(mod)")
-        pytest.raises(ValueError, lambda: pp.register(mod))
-        #assert not pp.isregistered(mod2)
-        assert pp.getplugins() == l
-
-    def test_canonical_import(self, monkeypatch):
-        mod = py.std.types.ModuleType("pytest_xyz")
-        monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
-        pp = PluginManager()
-        pp.import_plugin('pytest_xyz')
-        assert pp.getplugin('pytest_xyz') == mod
-        assert pp.isregistered(mod)
-
     def test_register_mismatch_method(self):
-        pp = get_plugin_manager()
+        pm = get_plugin_manager()
         class hello:
             def pytest_gurgel(self):
                 pass
-        pytest.raises(Exception, lambda: pp.register(hello()))
+        pytest.raises(Exception, lambda: pm.register(hello()))
 
     def test_register_mismatch_arg(self):
-        pp = get_plugin_manager()
+        pm = get_plugin_manager()
         class hello:
             def pytest_configure(self, asd):
                 pass
-        pytest.raises(Exception, lambda: pp.register(hello()))
+        pytest.raises(Exception, lambda: pm.register(hello()))
 
     def test_register(self):
         pm = get_plugin_manager()
@@ -250,7 +76,7 @@
         assert pm.getplugins()[-1:] == [my2]
 
     def test_listattr(self):
-        plugins = PluginManager()
+        plugins = PluginManager("xyz")
         class api1:
             x = 41
         class api2:
@@ -263,27 +89,6 @@
         l = list(plugins.listattr('x'))
         assert l == [41, 42, 43]
 
-    def test_hook_tracing(self):
-        pm = get_plugin_manager()
-        saveindent = []
-        class api1:
-            x = 41
-            def pytest_plugin_registered(self, plugin):
-                saveindent.append(pm.trace.root.indent)
-                raise ValueError(42)
-        l = []
-        pm.set_tracing(l.append)
-        indent = pm.trace.root.indent
-        p = api1()
-        pm.register(p)
-
-        assert pm.trace.root.indent == indent
-        assert len(l) == 2
-        assert 'pytest_plugin_registered' in l[0]
-        assert 'finish' in l[1]
-        pytest.raises(ValueError, lambda: pm.register(api1()))
-        assert pm.trace.root.indent == indent
-        assert saveindent[0] > indent
 
 class TestPytestPluginInteractions:
 
@@ -372,10 +177,33 @@
         config.pluginmanager.register(A())
         assert len(l) == 2
 
+    def test_hook_tracing(self):
+        pytestpm = get_plugin_manager()  # fully initialized with plugins
+        saveindent = []
+        class api1:
+            x = 41
+            def pytest_plugin_registered(self, plugin):
+                saveindent.append(pytestpm.trace.root.indent)
+                raise ValueError(42)
+        l = []
+        pytestpm.set_tracing(l.append)
+        indent = pytestpm.trace.root.indent
+        p = api1()
+        pytestpm.register(p)
+
+        assert pytestpm.trace.root.indent == indent
+        assert len(l) == 2
+        assert 'pytest_plugin_registered' in l[0]
+        assert 'finish' in l[1]
+        with pytest.raises(ValueError):
+            pytestpm.register(api1())
+        assert pytestpm.trace.root.indent == indent
+        assert saveindent[0] > indent
+
     # lower level API
 
     def test_listattr(self):
-        pluginmanager = PluginManager()
+        pluginmanager = PluginManager("xyz")
         class My2:
             x = 42
         pluginmanager.register(My2())
@@ -395,7 +223,7 @@
             def m(self):
                 return 19
 
-        pluginmanager = PluginManager()
+        pluginmanager = PluginManager("xyz")
         p1 = P1()
         p2 = P2()
         p3 = P3()
@@ -572,7 +400,7 @@
             def m(self):
                 return 19
 
-        pluginmanager = PluginManager()
+        pluginmanager = PluginManager("xyz")
         p1 = P1()
         p2 = P2()
         p3 = P3()
@@ -624,11 +452,12 @@
 
 
 class TestHookRelay:
-    def test_happypath(self):
+    def test_hapmypath(self):
         class Api:
             def hello(self, arg):
                 "api hook 1"
-        pm = PluginManager([Api], prefix="he")
+        pm = PluginManager("he")
+        pm.addhooks(Api)
         hook = pm.hook
         assert hasattr(hook, 'hello')
         assert repr(hook.hello).find("hello") != -1
@@ -647,7 +476,8 @@
         class Api:
             def hello(self, arg):
                 "api hook 1"
-        pm = PluginManager(Api, prefix="he")
+        pm = PluginManager("he")
+        pm.addhooks(Api)
         class Plugin:
             def hello(self, argwrong):
                 return arg + 1
@@ -656,19 +486,20 @@
         assert "argwrong" in str(exc.value)
 
     def test_only_kwargs(self):
-        pm = PluginManager()
+        pm = PluginManager("he")
         class Api:
             def hello(self, arg):
                 "api hook 1"
-        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
-        pytest.raises(TypeError, lambda: mcm.hello(3))
+        pm.addhooks(Api)
+        pytest.raises(TypeError, lambda: pm.hook.hello(3))
 
     def test_firstresult_definition(self):
         class Api:
             def hello(self, arg):
                 "api hook 1"
             hello.firstresult = True
-        pm = PluginManager([Api], "he")
+        pm = PluginManager("he")
+        pm.addhooks(Api)
         class Plugin:
             def hello(self, arg):
                 return arg + 1
@@ -779,7 +610,7 @@
     assert "aaaa" in str(excinfo.value)
 
 class TestWrapMethod:
-    def test_basic_happypath(self):
+    def test_basic_hapmypath(self):
         class A:
             def f(self):
                 return "A.f"
@@ -880,3 +711,182 @@
         with pytest.raises(ValueError):
             A().error()
         assert l == [1]
+
+
+### to be shifted to own test file
+from _pytest.config import PytestPluginManager
+
+class TestPytestPluginManager:
+    def test_register_imported_modules(self):
+        pm = PytestPluginManager()
+        mod = py.std.types.ModuleType("x.y.pytest_hello")
+        pm.register(mod)
+        assert pm.isregistered(mod)
+        l = pm.getplugins()
+        assert mod in l
+        pytest.raises(ValueError, "pm.register(mod)")
+        pytest.raises(ValueError, lambda: pm.register(mod))
+        #assert not pm.isregistered(mod2)
+        assert pm.getplugins() == l
+
+    def test_canonical_import(self, monkeypatch):
+        mod = py.std.types.ModuleType("pytest_xyz")
+        monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
+        pm = PytestPluginManager()
+        pm.import_plugin('pytest_xyz')
+        assert pm.getplugin('pytest_xyz') == mod
+        assert pm.isregistered(mod)
+
+    def test_consider_module(self, testdir, pytestpm):
+        testdir.syspathinsert()
+        testdir.makepyfile(pytest_p1="#")
+        testdir.makepyfile(pytest_p2="#")
+        mod = py.std.types.ModuleType("temp")
+        mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
+        pytestpm.consider_module(mod)
+        assert pytestpm.getplugin("pytest_p1").__name__ == "pytest_p1"
+        assert pytestpm.getplugin("pytest_p2").__name__ == "pytest_p2"
+
+    def test_consider_module_import_module(self, testdir):
+        pytestpm = get_plugin_manager()
+        mod = py.std.types.ModuleType("x")
+        mod.pytest_plugins = "pytest_a"
+        aplugin = testdir.makepyfile(pytest_a="#")
+        reprec = testdir.make_hook_recorder(pytestpm)
+        #syspath.prepend(aplugin.dirpath())
+        py.std.sys.path.insert(0, str(aplugin.dirpath()))
+        pytestpm.consider_module(mod)
+        call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
+        assert call.plugin.__name__ == "pytest_a"
+
+        # check that it is not registered twice
+        pytestpm.consider_module(mod)
+        l = reprec.getcalls("pytest_plugin_registered")
+        assert len(l) == 1
+
+    def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
+        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
+        with pytest.raises(ImportError):
+            pytestpm.consider_env()
+
+    def test_plugin_skip(self, testdir, monkeypatch):
+        p = testdir.makepyfile(skipping1="""
+            import pytest
+            pytest.skip("hello")
+        """)
+        p.copy(p.dirpath("skipping2.py"))
+        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
+        result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "WI1*skipped plugin*skipping1*hello*",
+            "WI1*skipped plugin*skipping2*hello*",
+        ])
+
+    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
+        testdir.syspathinsert()
+        testdir.makepyfile(xy123="#")
+        monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
+        l1 = len(pytestpm.getplugins())
+        pytestpm.consider_env()
+        l2 = len(pytestpm.getplugins())
+        assert l2 == l1 + 1
+        assert pytestpm.getplugin('xy123')
+        pytestpm.consider_env()
+        l3 = len(pytestpm.getplugins())
+        assert l2 == l3
+
+    def test_consider_setuptools_instantiation(self, monkeypatch, pytestpm):
+        pkg_resources = pytest.importorskip("pkg_resources")
+        def my_iter(name):
+            assert name == "pytest11"
+            class EntryPoint:
+                name = "pytest_mytestplugin"
+                dist = None
+                def load(self):
+                    class PseudoPlugin:
+                        x = 42
+                    return PseudoPlugin()
+            return iter([EntryPoint()])
+
+        monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+        pytestpm.consider_setuptools_entrypoints()
+        plugin = pytestpm.getplugin("mytestplugin")
+        assert plugin.x == 42
+
+    def test_consider_setuptools_not_installed(self, monkeypatch, pytestpm):
+        monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
+            py.std.types.ModuleType("pkg_resources"))
+        pytestpm.consider_setuptools_entrypoints()
+        # ok, we did not explode
+
+    def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
+        testdir.makepyfile(pytest_x500="#")
+        p = testdir.makepyfile("""
+            import pytest
+            def test_hello(pytestconfig):
+                plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
+                assert plugin is not None
+        """)
+        monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
+        result = testdir.runpytest(p)
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["*1 passed in*"])
+
+    def test_import_plugin_importname(self, testdir, pytestpm):
+        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
+        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
+
+        testdir.syspathinsert()
+        pluginname = "pytest_hello"
+        testdir.makepyfile(**{pluginname: ""})
+        pytestpm.import_plugin("pytest_hello")
+        len1 = len(pytestpm.getplugins())
+        pytestpm.import_plugin("pytest_hello")
+        len2 = len(pytestpm.getplugins())
+        assert len1 == len2
+        plugin1 = pytestpm.getplugin("pytest_hello")
+        assert plugin1.__name__.endswith('pytest_hello')
+        plugin2 = pytestpm.getplugin("pytest_hello")
+        assert plugin2 is plugin1
+
+    def test_import_plugin_dotted_name(self, testdir, pytestpm):
+        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
+        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
+
+        testdir.syspathinsert()
+        testdir.mkpydir("pkg").join("plug.py").write("x=3")
+        pluginname = "pkg.plug"
+        pytestpm.import_plugin(pluginname)
+        mod = pytestpm.getplugin("pkg.plug")
+        assert mod.x == 3
+
+    def test_config_sets_conftesthandle_onimport(self, testdir):
+        config = testdir.parseconfig([])
+        assert config._conftest._onimport == config._onimportconftest
+
+    def test_consider_conftest_deps(self, testdir, pytestpm):
+        mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
+        with pytest.raises(ImportError):
+            pytestpm.consider_conftest(mod)
+
+
+class TestPytestPluginManagerBootstrapming:
+    def test_preparse_args(self, pytestpm):
+        pytest.raises(ImportError, lambda:
+            pytestpm.consider_preparse(["xyz", "-p", "hello123"]))
+
+    def test_plugin_prevent_register(self, pytestpm):
+        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
+        l1 = pytestpm.getplugins()
+        pytestpm.register(42, name="abc")
+        l2 = pytestpm.getplugins()
+        assert len(l2) == len(l1)
+
+    def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
+        pytestpm.register(42, name="abc")
+        l1 = pytestpm.getplugins()
+        assert 42 in l1
+        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
+        l2 = pytestpm.getplugins()
+        assert 42 not in l2

diff -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c -r 21aefbeaf96fb994bce816d3884707100d0b74da testing/test_pytester.py
--- a/testing/test_pytester.py
+++ b/testing/test_pytester.py
@@ -1,7 +1,7 @@
 import pytest
 import os
 from _pytest.pytester import HookRecorder
-from _pytest.core import PluginManager
+from _pytest.config import PytestPluginManager
 from _pytest.main import EXIT_OK, EXIT_TESTSFAILED
 
 
@@ -93,8 +93,8 @@
 
 @pytest.mark.parametrize("holder", make_holder())
 def test_hookrecorder_basic(holder):
-    pm = PluginManager()
-    pm.hook._addhooks(holder, "pytest_")
+    pm = PytestPluginManager()
+    pm.addhooks(holder)
     rec = HookRecorder(pm)
     pm.hook.pytest_xyz(arg=123)
     call = rec.popcall("pytest_xyz")

diff -r f5541d71ac9cdb7897ccdb993f9f2896715a6f3c -r 21aefbeaf96fb994bce816d3884707100d0b74da testing/test_terminal.py
--- a/testing/test_terminal.py
+++ b/testing/test_terminal.py
@@ -457,7 +457,9 @@
         ])
         assert result.ret == 1
 
-        pytestconfig.pluginmanager.skipifmissing("xdist")
+        if not pytestconfig.pluginmanager.hasplugin("xdist"):
+            pytest.skip("xdist plugin not installed")
+
         result = testdir.runpytest(p1, '-v', '-n 1')
         result.stdout.fnmatch_lines([
             "*FAIL*test_verbose_reporting.py::test_fail*",


https://bitbucket.org/pytest-dev/pytest/commits/d9e0c52761ab/
Changeset:   d9e0c52761ab
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 11:31:46+00:00
Summary:     avoid prepend to register api as it's redundant wrt to hooks
Affected #:  3 files

diff -r 21aefbeaf96fb994bce816d3884707100d0b74da -r d9e0c52761aba632a3e48daed1688f1fe103f1ad _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -205,7 +205,7 @@
                            ", ".join(hook.argnames))
             yield hook
 
-    def register(self, plugin, name=None, prepend=False, conftest=False):
+    def register(self, plugin, name=None, conftest=False):
         if self._name2plugin.get(name, None) == -1:
             return
         name = name or getattr(plugin, '__name__', str(id(plugin)))
@@ -222,10 +222,7 @@
         if conftest:
             self._conftestplugins.append(plugin)
         else:
-            if not prepend:
-                self._plugins.append(plugin)
-            else:
-                self._plugins.insert(0, plugin)
+            self._plugins.append(plugin)
         # finally make sure that the methods of the new plugin take part
         for hookcaller in hookcallers:
             hookcaller.scan_methods()

diff -r 21aefbeaf96fb994bce816d3884707100d0b74da -r d9e0c52761aba632a3e48daed1688f1fe103f1ad _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -510,7 +510,7 @@
     def __init__(self, config):
         FSCollector.__init__(self, config.rootdir, parent=None,
                              config=config, session=self)
-        self.config.pluginmanager.register(self, name="session", prepend=True)
+        self.config.pluginmanager.register(self, name="session")
         self._testsfailed = 0
         self.shouldstop = False
         self.trace = config.trace.root.get("collection")
@@ -521,10 +521,12 @@
     def _makeid(self):
         return ""
 
+    @pytest.mark.tryfirst
     def pytest_collectstart(self):
         if self.shouldstop:
             raise self.Interrupted(self.shouldstop)
 
+    @pytest.mark.tryfirst
     def pytest_runtest_logreport(self, report):
         if report.failed and not hasattr(report, 'wasxfail'):
             self._testsfailed += 1

diff -r 21aefbeaf96fb994bce816d3884707100d0b74da -r d9e0c52761aba632a3e48daed1688f1fe103f1ad testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -32,18 +32,6 @@
         pm.unregister(a1)
         assert not pm.isregistered(a1)
 
-    def test_pm_ordering(self, pm):
-        class A: pass
-        a1, a2 = A(), A()
-        pm.register(a1)
-        pm.register(a2, "hello")
-        l = pm.getplugins()
-        assert l.index(a1) < l.index(a2)
-        a3 = A()
-        pm.register(a3, prepend=True)
-        l = pm.getplugins()
-        assert l.index(a3) == 0
-
     def test_register_mismatch_method(self):
         pm = get_plugin_manager()
         class hello:


https://bitbucket.org/pytest-dev/pytest/commits/158322cbc5cc/
Changeset:   158322cbc5cc
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 11:33:01+00:00
Summary:     move bookkeeping of conftest plugins in core pluginmanager to PytestPluginManager
Affected #:  2 files

diff -r d9e0c52761aba632a3e48daed1688f1fe103f1ad -r 158322cbc5cc6b9a9864ab851201f8d71a43d328 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -97,6 +97,7 @@
                                                   excludefunc=exclude_pytest_names)
         self._warnings = []
         self._plugin_distinfo = []
+        self._globalplugins = []
         self.addhooks(hookspec)
         self.register(self)
         if os.environ.get('PYTEST_DEBUG'):
@@ -108,6 +109,19 @@
                 pass
             self.set_tracing(err.write)
 
+    def register(self, plugin, name=None, conftest=False):
+        ret = super(PytestPluginManager, self).register(plugin, name)
+        if ret and not conftest:
+            self._globalplugins.append(plugin)
+        return ret
+
+    def unregister(self, plugin):
+        super(PytestPluginManager, self).unregister(plugin)
+        try:
+            self._globalplugins.remove(plugin)
+        except ValueError:
+            pass
+
     def getplugin(self, name):
         if name is None:
             return name
@@ -787,7 +801,7 @@
                 setattr(self.option, opt.dest, opt.default)
 
     def _getmatchingplugins(self, fspath):
-        return self.pluginmanager._plugins + \
+        return self.pluginmanager._globalplugins + \
                self._conftest.getconftestmodules(fspath)
 
     def pytest_load_initial_conftests(self, early_config):

diff -r d9e0c52761aba632a3e48daed1688f1fe103f1ad -r 158322cbc5cc6b9a9864ab851201f8d71a43d328 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -140,7 +140,6 @@
         self._excludefunc = excludefunc
         self._name2plugin = {}
         self._plugins = []
-        self._conftestplugins = []
         self._plugin2hookcallers = {}
         self.trace = TagTracer().get("pluginmanage")
         self._shutdown = []
@@ -205,7 +204,7 @@
                            ", ".join(hook.argnames))
             yield hook
 
-    def register(self, plugin, name=None, conftest=False):
+    def register(self, plugin, name=None):
         if self._name2plugin.get(name, None) == -1:
             return
         name = name or getattr(plugin, '__name__', str(id(plugin)))
@@ -219,20 +218,14 @@
         hookcallers = list(self._scan_plugin(plugin))
         self._plugin2hookcallers[plugin] = hookcallers
         self._name2plugin[name] = plugin
-        if conftest:
-            self._conftestplugins.append(plugin)
-        else:
-            self._plugins.append(plugin)
+        self._plugins.append(plugin)
         # finally make sure that the methods of the new plugin take part
         for hookcaller in hookcallers:
             hookcaller.scan_methods()
         return True
 
     def unregister(self, plugin):
-        try:
-            self._plugins.remove(plugin)
-        except KeyError:
-            self._conftestplugins.remove(plugin)
+        self._plugins.remove(plugin)
         for name, value in list(self._name2plugin.items()):
             if value == plugin:
                 del self._name2plugin[name]
@@ -247,13 +240,13 @@
         while self._shutdown:
             func = self._shutdown.pop()
             func()
-        self._plugins = self._conftestplugins = []
+        self._plugins = []
         self._name2plugin.clear()
 
     def isregistered(self, plugin, name=None):
         if self.getplugin(name) is not None:
             return True
-        return plugin in self._plugins or plugin in self._conftestplugins
+        return plugin in self._plugins
 
     def addhooks(self, module_or_class):
         isclass = int(inspect.isclass(module_or_class))
@@ -271,7 +264,7 @@
                              %(self._prefix, module_or_class))
 
     def getplugins(self):
-        return self._plugins + self._conftestplugins
+        return self._plugins
 
     def hasplugin(self, name):
         return bool(self.getplugin(name))
@@ -281,7 +274,7 @@
 
     def listattr(self, attrname, plugins=None):
         if plugins is None:
-            plugins = self._plugins + self._conftestplugins
+            plugins = self._plugins
         l = []
         last = []
         wrappers = []


https://bitbucket.org/pytest-dev/pytest/commits/885fb42ad526/
Changeset:   885fb42ad526
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 11:37:42+00:00
Summary:     simplify exception capturing
Affected #:  1 file

diff -r 158322cbc5cc6b9a9864ab851201f8d71a43d328 -r 885fb42ad526081e7c7a85f1c161e692f72a78ba _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -207,14 +207,11 @@
             return
         try:
             mod = importplugin(modname)
-        except KeyboardInterrupt:
-            raise
         except ImportError:
             if modname.startswith("pytest_"):
                 return self.import_plugin(modname[7:])
             raise
-        except:
-            e = sys.exc_info()[1]
+        except Exception as e:
             import pytest
             if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
                 raise


https://bitbucket.org/pytest-dev/pytest/commits/9595f9a127fc/
Changeset:   9595f9a127fc
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 11:44:37+00:00
Summary:     avoid undocumented special casing of "pytest_" prefix
Affected #:  4 files

diff -r 885fb42ad526081e7c7a85f1c161e692f72a78ba -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -208,8 +208,6 @@
         try:
             mod = importplugin(modname)
         except ImportError:
-            if modname.startswith("pytest_"):
-                return self.import_plugin(modname[7:])
             raise
         except Exception as e:
             import pytest

diff -r 885fb42ad526081e7c7a85f1c161e692f72a78ba -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 testing/test_pytester.py
--- a/testing/test_pytester.py
+++ b/testing/test_pytester.py
@@ -64,7 +64,7 @@
 
 def test_testdir_runs_with_plugin(testdir):
     testdir.makepyfile("""
-        pytest_plugins = "pytest_pytester"
+        pytest_plugins = "pytester"
         def test_hello(testdir):
             assert 1
     """)

diff -r 885fb42ad526081e7c7a85f1c161e692f72a78ba -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 testing/test_recwarn.py
--- a/testing/test_recwarn.py
+++ b/testing/test_recwarn.py
@@ -22,7 +22,6 @@
 
 def test_recwarn_functional(testdir):
     reprec = testdir.inline_runsource("""
-        pytest_plugins = 'pytest_recwarn',
         import warnings
         oldwarn = warnings.showwarning
         def test_method(recwarn):

diff -r 885fb42ad526081e7c7a85f1c161e692f72a78ba -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 testing/test_unittest.py
--- a/testing/test_unittest.py
+++ b/testing/test_unittest.py
@@ -3,7 +3,6 @@
 def test_simple_unittest(testdir):
     testpath = testdir.makepyfile("""
         import unittest
-        pytest_plugins = "pytest_unittest"
         class MyTestCase(unittest.TestCase):
             def testpassing(self):
                 self.assertEquals('foo', 'foo')
@@ -17,7 +16,6 @@
 def test_runTest_method(testdir):
     testdir.makepyfile("""
         import unittest
-        pytest_plugins = "pytest_unittest"
         class MyTestCaseWithRunTest(unittest.TestCase):
             def runTest(self):
                 self.assertEquals('foo', 'foo')


https://bitbucket.org/pytest-dev/pytest/commits/582c7e123d81/
Changeset:   582c7e123d81
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 12:15:42+00:00
Summary:     merge conftest management into PytestPluginManager
Affected #:  5 files

diff -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 -r 582c7e123d8101de97d377a389ce71baddbe6451 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -98,6 +98,12 @@
         self._warnings = []
         self._plugin_distinfo = []
         self._globalplugins = []
+
+        # state related to local conftest plugins
+        self._path2confmods = {}
+        self._conftestpath2mod = {}
+        self._confcutdir = None
+
         self.addhooks(hookspec)
         self.register(self)
         if os.environ.get('PYTEST_DEBUG'):
@@ -141,6 +147,89 @@
             config.warn(code="I1", message=warning)
 
     #
+    # internal API for local conftest plugin handling
+    #
+    def _set_initial_conftests(self, namespace):
+        """ load initial conftest files given a preparsed "namespace".
+            As conftest files may add their own command line options
+            which have arguments ('--my-opt somepath') we might get some
+            false positives.  All builtin and 3rd party plugins will have
+            been loaded, however, so common options will not confuse our logic
+            here.
+        """
+        current = py.path.local()
+        self._confcutdir = current.join(namespace.confcutdir, abs=True) \
+                                if namespace.confcutdir else None
+        testpaths = namespace.file_or_dir
+        foundanchor = False
+        for path in testpaths:
+            path = str(path)
+            # remove node-id syntax
+            i = path.find("::")
+            if i != -1:
+                path = path[:i]
+            anchor = current.join(path, abs=1)
+            if exists(anchor): # we found some file object
+                self._try_load_conftest(anchor)
+                foundanchor = True
+        if not foundanchor:
+            self._try_load_conftest(current)
+
+    def _try_load_conftest(self, anchor):
+        self._getconftestmodules(anchor)
+        # let's also consider test* subdirs
+        if anchor.check(dir=1):
+            for x in anchor.listdir("test*"):
+                if x.check(dir=1):
+                    self._getconftestmodules(x)
+
+    def _getconftestmodules(self, path):
+        try:
+            return self._path2confmods[path]
+        except KeyError:
+            clist = []
+            for parent in path.parts():
+                if self._confcutdir and self._confcutdir.relto(parent):
+                    continue
+                conftestpath = parent.join("conftest.py")
+                if conftestpath.check(file=1):
+                    mod = self._importconftest(conftestpath)
+                    clist.append(mod)
+            self._path2confmods[path] = clist
+            return clist
+
+    def _rget_with_confmod(self, name, path):
+        modules = self._getconftestmodules(path)
+        for mod in reversed(modules):
+            try:
+                return mod, getattr(mod, name)
+            except AttributeError:
+                continue
+        raise KeyError(name)
+
+    def _importconftest(self, conftestpath):
+        try:
+            return self._conftestpath2mod[conftestpath]
+        except KeyError:
+            pkgpath = conftestpath.pypkgpath()
+            if pkgpath is None:
+                _ensure_removed_sysmodule(conftestpath.purebasename)
+            try:
+                mod = conftestpath.pyimport()
+            except Exception:
+                raise ConftestImportFailure(conftestpath, sys.exc_info())
+            self._conftestpath2mod[conftestpath] = mod
+            dirpath = conftestpath.dirpath()
+            if dirpath in self._path2confmods:
+                for path, mods in self._path2confmods.items():
+                    if path and path.relto(dirpath) or path == dirpath:
+                        assert mod not in mods
+                        mods.append(mod)
+            self.trace("loaded conftestmodule %r" %(mod))
+            self.consider_conftest(mod)
+            return mod
+
+    #
     # API for bootstrapping plugin loading
     #
     #
@@ -572,96 +661,6 @@
         return action._formatted_action_invocation
 
 
-class Conftest(object):
-    """ the single place for accessing values and interacting
-        towards conftest modules from pytest objects.
-    """
-    def __init__(self, onimport=None):
-        self._path2confmods = {}
-        self._onimport = onimport
-        self._conftestpath2mod = {}
-        self._confcutdir = None
-
-    def setinitial(self, namespace):
-        """ load initial conftest files given a preparsed "namespace".
-            As conftest files may add their own command line options
-            which have arguments ('--my-opt somepath') we might get some
-            false positives.  All builtin and 3rd party plugins will have
-            been loaded, however, so common options will not confuse our logic
-            here.
-        """
-        current = py.path.local()
-        self._confcutdir = current.join(namespace.confcutdir, abs=True) \
-                                if namespace.confcutdir else None
-        testpaths = namespace.file_or_dir
-        foundanchor = False
-        for path in testpaths:
-            path = str(path)
-            # remove node-id syntax
-            i = path.find("::")
-            if i != -1:
-                path = path[:i]
-            anchor = current.join(path, abs=1)
-            if exists(anchor): # we found some file object
-                self._try_load_conftest(anchor)
-                foundanchor = True
-        if not foundanchor:
-            self._try_load_conftest(current)
-
-    def _try_load_conftest(self, anchor):
-        self.getconftestmodules(anchor)
-        # let's also consider test* subdirs
-        if anchor.check(dir=1):
-            for x in anchor.listdir("test*"):
-                if x.check(dir=1):
-                    self.getconftestmodules(x)
-
-    def getconftestmodules(self, path):
-        try:
-            return self._path2confmods[path]
-        except KeyError:
-            clist = []
-            for parent in path.parts():
-                if self._confcutdir and self._confcutdir.relto(parent):
-                    continue
-                conftestpath = parent.join("conftest.py")
-                if conftestpath.check(file=1):
-                    mod = self.importconftest(conftestpath)
-                    clist.append(mod)
-            self._path2confmods[path] = clist
-            return clist
-
-    def rget_with_confmod(self, name, path):
-        modules = self.getconftestmodules(path)
-        for mod in reversed(modules):
-            try:
-                return mod, getattr(mod, name)
-            except AttributeError:
-                continue
-        raise KeyError(name)
-
-    def importconftest(self, conftestpath):
-        try:
-            return self._conftestpath2mod[conftestpath]
-        except KeyError:
-            pkgpath = conftestpath.pypkgpath()
-            if pkgpath is None:
-                _ensure_removed_sysmodule(conftestpath.purebasename)
-            try:
-                mod = conftestpath.pyimport()
-            except Exception:
-                raise ConftestImportFailure(conftestpath, sys.exc_info())
-            self._conftestpath2mod[conftestpath] = mod
-            dirpath = conftestpath.dirpath()
-            if dirpath in self._path2confmods:
-                for path, mods in self._path2confmods.items():
-                    if path and path.relto(dirpath) or path == dirpath:
-                        assert mod not in mods
-                        mods.append(mod)
-            if self._onimport:
-                self._onimport(mod)
-            return mod
-
 
 def _ensure_removed_sysmodule(modname):
     try:
@@ -697,7 +696,6 @@
         #: a pluginmanager instance
         self.pluginmanager = pluginmanager
         self.trace = self.pluginmanager.trace.root.get("config")
-        self._conftest = Conftest(onimport=self._onimportconftest)
         self.hook = self.pluginmanager.hook
         self._inicache = {}
         self._opt2dest = {}
@@ -783,10 +781,6 @@
             config.pluginmanager.consider_pluginarg(x)
         return config
 
-    def _onimportconftest(self, conftestmodule):
-        self.trace("loaded conftestmodule %r" %(conftestmodule,))
-        self.pluginmanager.consider_conftest(conftestmodule)
-
     def _processopt(self, opt):
         for name in opt._short_opts + opt._long_opts:
             self._opt2dest[name] = opt.dest
@@ -797,10 +791,10 @@
 
     def _getmatchingplugins(self, fspath):
         return self.pluginmanager._globalplugins + \
-               self._conftest.getconftestmodules(fspath)
+               self.pluginmanager._getconftestmodules(fspath)
 
     def pytest_load_initial_conftests(self, early_config):
-        self._conftest.setinitial(early_config.known_args_namespace)
+        self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
     pytest_load_initial_conftests.trylast = True
 
     def _initini(self, args):
@@ -907,7 +901,7 @@
 
     def _getconftest_pathlist(self, name, path):
         try:
-            mod, relroots = self._conftest.rget_with_confmod(name, path)
+            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
         except KeyError:
             return None
         modpath = py.path.local(mod.__file__).dirpath()

diff -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 -r 582c7e123d8101de97d377a389ce71baddbe6451 _pytest/doctest.py
--- a/_pytest/doctest.py
+++ b/_pytest/doctest.py
@@ -132,7 +132,7 @@
     def collect(self):
         import doctest
         if self.fspath.basename == "conftest.py":
-            module = self.config._conftest.importconftest(self.fspath)
+            module = self.config._conftest._importconftest(self.fspath)
         else:
             try:
                 module = self.fspath.pyimport()

diff -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 -r 582c7e123d8101de97d377a389ce71baddbe6451 testing/python/fixture.py
--- a/testing/python/fixture.py
+++ b/testing/python/fixture.py
@@ -1487,7 +1487,7 @@
         reprec = testdir.inline_run("-v","-s")
         reprec.assertoutcome(passed=8)
         config = reprec.getcalls("pytest_unconfigure")[0].config
-        l = config._conftest.getconftestmodules(p)[0].l
+        l = config.pluginmanager._getconftestmodules(p)[0].l
         assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
 
     def test_scope_ordering(self, testdir):

diff -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 -r 582c7e123d8101de97d377a389ce71baddbe6451 testing/test_conftest.py
--- a/testing/test_conftest.py
+++ b/testing/test_conftest.py
@@ -1,7 +1,6 @@
 from textwrap import dedent
 import py, pytest
-from _pytest.config import Conftest
-
+from _pytest.config import PytestPluginManager
 
 
 @pytest.fixture(scope="module", params=["global", "inpackage"])
@@ -16,7 +15,7 @@
     return tmpdir
 
 def ConftestWithSetinitial(path):
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [path])
     return conftest
 
@@ -25,51 +24,41 @@
         def __init__(self):
             self.file_or_dir = args
             self.confcutdir = str(confcutdir)
-    conftest.setinitial(Namespace())
+    conftest._set_initial_conftests(Namespace())
 
 class TestConftestValueAccessGlobal:
     def test_basic_init(self, basedir):
-        conftest = Conftest()
+        conftest = PytestPluginManager()
         p = basedir.join("adir")
-        assert conftest.rget_with_confmod("a", p)[1] == 1
-
-    def test_onimport(self, basedir):
-        l = []
-        conftest = Conftest(onimport=l.append)
-        adir = basedir.join("adir")
-        conftest_setinitial(conftest, [adir], confcutdir=basedir)
-        assert len(l) == 1
-        assert conftest.rget_with_confmod("a", adir)[1] == 1
-        assert conftest.rget_with_confmod("b", adir.join("b"))[1] == 2
-        assert len(l) == 2
+        assert conftest._rget_with_confmod("a", p)[1] == 1
 
     def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
-        conftest = Conftest()
+        conftest = PytestPluginManager()
         len(conftest._path2confmods)
-        conftest.getconftestmodules(basedir)
+        conftest._getconftestmodules(basedir)
         snap1 = len(conftest._path2confmods)
         #assert len(conftest._path2confmods) == snap1 + 1
-        conftest.getconftestmodules(basedir.join('adir'))
+        conftest._getconftestmodules(basedir.join('adir'))
         assert len(conftest._path2confmods) == snap1 + 1
-        conftest.getconftestmodules(basedir.join('b'))
+        conftest._getconftestmodules(basedir.join('b'))
         assert len(conftest._path2confmods) == snap1 + 2
 
     def test_value_access_not_existing(self, basedir):
         conftest = ConftestWithSetinitial(basedir)
         with pytest.raises(KeyError):
-            conftest.rget_with_confmod('a', basedir)
+            conftest._rget_with_confmod('a', basedir)
 
     def test_value_access_by_path(self, basedir):
         conftest = ConftestWithSetinitial(basedir)
         adir = basedir.join("adir")
-        assert conftest.rget_with_confmod("a", adir)[1] == 1
-        assert conftest.rget_with_confmod("a", adir.join("b"))[1] == 1.5
+        assert conftest._rget_with_confmod("a", adir)[1] == 1
+        assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
 
     def test_value_access_with_confmod(self, basedir):
         startdir = basedir.join("adir", "b")
         startdir.ensure("xx", dir=True)
         conftest = ConftestWithSetinitial(startdir)
-        mod, value = conftest.rget_with_confmod("a", startdir)
+        mod, value = conftest._rget_with_confmod("a", startdir)
         assert  value == 1.5
         path = py.path.local(mod.__file__)
         assert path.dirpath() == basedir.join("adir", "b")
@@ -85,9 +74,9 @@
 def test_doubledash_considered(testdir):
     conf = testdir.mkdir("--option")
     conf.join("conftest.py").ensure()
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [conf.basename, conf.basename])
-    l = conftest.getconftestmodules(conf)
+    l = conftest._getconftestmodules(conf)
     assert len(l) == 1
 
 def test_issue151_load_all_conftests(testdir):
@@ -96,7 +85,7 @@
         p = testdir.mkdir(name)
         p.ensure("conftest.py")
 
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, names)
     d = list(conftest._conftestpath2mod.values())
     assert len(d) == len(names)
@@ -105,15 +94,15 @@
     testdir.makeconftest("x=3")
     p = testdir.makepyfile("""
         import py, pytest
-        from _pytest.config import Conftest
-        conf = Conftest()
-        mod = conf.importconftest(py.path.local("conftest.py"))
+        from _pytest.config import PytestPluginManager
+        conf = PytestPluginManager()
+        mod = conf._importconftest(py.path.local("conftest.py"))
         assert mod.x == 3
         import conftest
         assert conftest is mod, (conftest, mod)
         subconf = py.path.local().ensure("sub", "conftest.py")
         subconf.write("y=4")
-        mod2 = conf.importconftest(subconf)
+        mod2 = conf._importconftest(subconf)
         assert mod != mod2
         assert mod2.y == 4
         import conftest
@@ -125,27 +114,27 @@
 def test_conftestcutdir(testdir):
     conf = testdir.makeconftest("")
     p = testdir.mkdir("x")
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
-    l = conftest.getconftestmodules(p)
+    l = conftest._getconftestmodules(p)
     assert len(l) == 0
-    l = conftest.getconftestmodules(conf.dirpath())
+    l = conftest._getconftestmodules(conf.dirpath())
     assert len(l) == 0
     assert conf not in conftest._conftestpath2mod
     # but we can still import a conftest directly
-    conftest.importconftest(conf)
-    l = conftest.getconftestmodules(conf.dirpath())
+    conftest._importconftest(conf)
+    l = conftest._getconftestmodules(conf.dirpath())
     assert l[0].__file__.startswith(str(conf))
     # and all sub paths get updated properly
-    l = conftest.getconftestmodules(p)
+    l = conftest._getconftestmodules(p)
     assert len(l) == 1
     assert l[0].__file__.startswith(str(conf))
 
 def test_conftestcutdir_inplace_considered(testdir):
     conf = testdir.makeconftest("")
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
-    l = conftest.getconftestmodules(conf.dirpath())
+    l = conftest._getconftestmodules(conf.dirpath())
     assert len(l) == 1
     assert l[0].__file__.startswith(str(conf))
 
@@ -153,7 +142,7 @@
 def test_setinitial_conftest_subdirs(testdir, name):
     sub = testdir.mkdir(name)
     subconftest = sub.ensure("conftest.py")
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
     if name not in ('whatever', '.dotdir'):
         assert  subconftest in conftest._conftestpath2mod
@@ -199,9 +188,9 @@
     ct2.write("")
     def impct(p):
         return p
-    conftest = Conftest()
-    monkeypatch.setattr(conftest, 'importconftest', impct)
-    assert conftest.getconftestmodules(sub) == [ct1, ct2]
+    conftest = PytestPluginManager()
+    monkeypatch.setattr(conftest, '_importconftest', impct)
+    assert conftest._getconftestmodules(sub) == [ct1, ct2]
 
 
 def test_fixture_dependency(testdir, monkeypatch):

diff -r 9595f9a127fc87b3b4bb06b054fce9e5a2f21857 -r 582c7e123d8101de97d377a389ce71baddbe6451 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -94,7 +94,7 @@
                 return xyz + 1
         """)
         config = get_plugin_manager().config
-        config._conftest.importconftest(conf)
+        config.pluginmanager._importconftest(conf)
         print(config.pluginmanager.getplugins())
         res = config.hook.pytest_myhook(xyz=10)
         assert res == [11]
@@ -143,7 +143,7 @@
                 parser.addoption('--test123', action="store_true",
                     default=True)
         """)
-        config._conftest.importconftest(p)
+        config.pluginmanager._importconftest(p)
         assert config.option.test123
 
     def test_configure(self, testdir):
@@ -849,10 +849,6 @@
         mod = pytestpm.getplugin("pkg.plug")
         assert mod.x == 3
 
-    def test_config_sets_conftesthandle_onimport(self, testdir):
-        config = testdir.parseconfig([])
-        assert config._conftest._onimport == config._onimportconftest
-
     def test_consider_conftest_deps(self, testdir, pytestpm):
         mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
         with pytest.raises(ImportError):


https://bitbucket.org/pytest-dev/pytest/commits/02581ca33317/
Changeset:   02581ca33317
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 12:54:28+00:00
Summary:     slight cleanup of plugin register() functionality
Affected #:  2 files

diff -r 582c7e123d8101de97d377a389ce71baddbe6451 -r 02581ca33317258e8412fe4187299b6c74837d8a _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -121,6 +121,12 @@
             self._globalplugins.append(plugin)
         return ret
 
+    def _do_register(self, plugin, name):
+        # called from core PluginManager class
+        if hasattr(self, "config"):
+            self.config._register_plugin(plugin, name)
+        return super(PytestPluginManager, self)._do_register(plugin, name)
+
     def unregister(self, plugin):
         super(PytestPluginManager, self).unregister(plugin)
         try:
@@ -701,7 +707,6 @@
         self._opt2dest = {}
         self._cleanup = []
         self.pluginmanager.register(self, "pytestconfig")
-        self.pluginmanager.set_register_callback(self._register_plugin)
         self._configured = False
 
     def _register_plugin(self, plugin, name):

diff -r 582c7e123d8101de97d377a389ce71baddbe6451 -r 02581ca33317258e8412fe4187299b6c74837d8a _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -167,10 +167,6 @@
         # backward compatibility
         config.do_configure()
 
-    def set_register_callback(self, callback):
-        assert not hasattr(self, "_registercallback")
-        self._registercallback = callback
-
     def make_hook_caller(self, name, plugins):
         caller = getattr(self.hook, name)
         methods = self.listattr(name, plugins=plugins)
@@ -204,22 +200,26 @@
                            ", ".join(hook.argnames))
             yield hook
 
+    def _get_canonical_name(self, plugin):
+        return getattr(plugin, "__name__", None) or str(id(plugin))
+
     def register(self, plugin, name=None):
+        name = name or self._get_canonical_name(plugin)
         if self._name2plugin.get(name, None) == -1:
             return
-        name = name or getattr(plugin, '__name__', str(id(plugin)))
-        if self.isregistered(plugin, name):
+        if self.hasplugin(name):
             raise ValueError("Plugin already registered: %s=%s\n%s" %(
                               name, plugin, self._name2plugin))
         #self.trace("registering", name, plugin)
-        reg = getattr(self, "_registercallback", None)
-        if reg is not None:
-            reg(plugin, name)  # may call addhooks
+        # allow subclasses to intercept here by calling a helper
+        return self._do_register(plugin, name)
+
+    def _do_register(self, plugin, name):
         hookcallers = list(self._scan_plugin(plugin))
         self._plugin2hookcallers[plugin] = hookcallers
         self._name2plugin[name] = plugin
         self._plugins.append(plugin)
-        # finally make sure that the methods of the new plugin take part
+        # rescan all methods for the hookcallers we found
         for hookcaller in hookcallers:
             hookcaller.scan_methods()
         return True
@@ -243,11 +243,6 @@
         self._plugins = []
         self._name2plugin.clear()
 
-    def isregistered(self, plugin, name=None):
-        if self.getplugin(name) is not None:
-            return True
-        return plugin in self._plugins
-
     def addhooks(self, module_or_class):
         isclass = int(inspect.isclass(module_or_class))
         names = []
@@ -266,8 +261,12 @@
     def getplugins(self):
         return self._plugins
 
+    def isregistered(self, plugin):
+        return self.hasplugin(self._get_canonical_name(plugin)) or \
+               plugin in self._plugins
+
     def hasplugin(self, name):
-        return bool(self.getplugin(name))
+        return name in self._name2plugin
 
     def getplugin(self, name):
         return self._name2plugin.get(name)


https://bitbucket.org/pytest-dev/pytest/commits/300a44d73410/
Changeset:   300a44d73410
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 14:33:20+00:00
Summary:     remove shutdown logic from PluginManager and add a add_cleanup() API
for the already existing cleanup logic of the config object.
This simplifies lifecycle management as we don't keep two
layers of shutdown functions and also simplifies the pluginmanager
interface.

also add some docstrings.
Affected #:  12 files

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,15 @@
   from ``inline_run()`` to allow temporary modules to be reloaded.
   Thanks Eduardo Schettino.
 
+- internally refactor pluginmanager API and code so that there
+  is a clear distinction between a pytest-agnostic rather simple 
+  pluginmanager and the PytestPluginManager which adds a lot of
+  behaviour, among it handling of the local conftest files.
+  In terms of documented methods this is a backward compatible
+  change but it might still break 3rd party plugins which relied on 
+  details like especially the pluginmanager.add_shutdown() API.
+  Thanks Holger Krekel.
+ 
 2.7.1.dev (compared to 2.7.0)
 -----------------------------
 

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/assertion/__init__.py
--- a/_pytest/assertion/__init__.py
+++ b/_pytest/assertion/__init__.py
@@ -70,12 +70,11 @@
     config._assertstate = AssertionState(config, mode)
     config._assertstate.hook = hook
     config._assertstate.trace("configured with mode set to %r" % (mode,))
-
-
-def pytest_unconfigure(config):
-    hook = config._assertstate.hook
-    if hook is not None and hook in sys.meta_path:
-        sys.meta_path.remove(hook)
+    def undo():
+        hook = config._assertstate.hook
+        if hook is not None and hook in sys.meta_path:
+            sys.meta_path.remove(hook)
+    config.add_cleanup(undo)
 
 
 def pytest_collection(session):

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -37,13 +37,13 @@
     pluginmanager.register(capman, "capturemanager")
 
     # make sure that capturemanager is properly reset at final shutdown
-    pluginmanager.add_shutdown(capman.reset_capturings)
+    early_config.add_cleanup(capman.reset_capturings)
 
     # make sure logging does not raise exceptions at the end
     def silence_logging_at_shutdown():
         if "logging" in sys.modules:
             sys.modules["logging"].raiseExceptions = False
-    pluginmanager.add_shutdown(silence_logging_at_shutdown)
+    early_config.add_cleanup(silence_logging_at_shutdown)
 
     # finally trigger conftest loading but while capturing (issue93)
     capman.init_capturings()

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -77,20 +77,17 @@
             raise ValueError("not a string or argument list: %r" % (args,))
         args = shlex.split(args)
     pluginmanager = get_plugin_manager()
-    try:
-        if plugins:
-            for plugin in plugins:
-                pluginmanager.register(plugin)
-        return pluginmanager.hook.pytest_cmdline_parse(
-                pluginmanager=pluginmanager, args=args)
-    except Exception:
-        pluginmanager.ensure_shutdown()
-        raise
+    if plugins:
+        for plugin in plugins:
+            pluginmanager.register(plugin)
+    return pluginmanager.hook.pytest_cmdline_parse(
+            pluginmanager=pluginmanager, args=args)
 
 def exclude_pytest_names(name):
     return not name.startswith(name) or name == "pytest_plugins" or \
            name.startswith("pytest_funcarg__")
 
+
 class PytestPluginManager(PluginManager):
     def __init__(self):
         super(PytestPluginManager, self).__init__(prefix="pytest_",
@@ -723,16 +720,23 @@
         if self._configured:
             call_plugin(plugin, "pytest_configure", {'config': self})
 
-    def do_configure(self):
+    def add_cleanup(self, func):
+        """ Add a function to be called when the config object gets out of
+        use (usually coninciding with pytest_unconfigure)."""
+        self._cleanup.append(func)
+
+    def _do_configure(self):
         assert not self._configured
         self._configured = True
         self.hook.pytest_configure(config=self)
 
-    def do_unconfigure(self):
-        assert self._configured
-        self._configured = False
-        self.hook.pytest_unconfigure(config=self)
-        self.pluginmanager.ensure_shutdown()
+    def _ensure_unconfigure(self):
+        if self._configured:
+            self._configured = False
+            self.hook.pytest_unconfigure(config=self)
+        while self._cleanup:
+            fin = self._cleanup.pop()
+            fin()
 
     def warn(self, code, message):
         """ generate a warning for this test session. """
@@ -747,11 +751,6 @@
         self.parse(args)
         return self
 
-    def pytest_unconfigure(config):
-        while config._cleanup:
-            fin = config._cleanup.pop()
-            fin()
-
     def notify_exception(self, excinfo, option=None):
         if option and option.fulltrace:
             style = "long"

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -135,6 +135,21 @@
 
 
 class PluginManager(object):
+    """ Core Pluginmanager class which manages registration
+    of plugin objects and 1:N hook calling.
+
+    You can register new hooks by calling ``addhooks(module_or_class)``.
+    You can register plugin objects (which contain hooks) by calling
+    ``register(plugin)``.  The Pluginmanager is initialized with a
+    prefix that is searched for in the names of the dict of registered
+    plugin objects.  An optional excludefunc allows to blacklist names which
+    are not considered as hooks despite a matching prefix.
+
+    For debugging purposes you can call ``set_tracing(writer)``
+    which will subsequently send debug information to the specified
+    write function.
+    """
+
     def __init__(self, prefix, excludefunc=None):
         self._prefix = prefix
         self._excludefunc = excludefunc
@@ -142,10 +157,11 @@
         self._plugins = []
         self._plugin2hookcallers = {}
         self.trace = TagTracer().get("pluginmanage")
-        self._shutdown = []
         self.hook = HookRelay(pm=self)
 
     def set_tracing(self, writer):
+        """ turn on tracing to the given writer method and
+        return an undo function. """
         self.trace.root.setwriter(writer)
         # reconfigure HookCalling to perform tracing
         assert not hasattr(self, "_wrapping")
@@ -160,12 +176,7 @@
                 trace("finish", self.name, "-->", box.result)
             trace.root.indent -= 1
 
-        undo = add_method_wrapper(HookCaller, _docall)
-        self.add_shutdown(undo)
-
-    def do_configure(self, config):
-        # backward compatibility
-        config.do_configure()
+        return add_method_wrapper(HookCaller, _docall)
 
     def make_hook_caller(self, name, plugins):
         caller = getattr(self.hook, name)
@@ -233,16 +244,6 @@
         for hookcaller in hookcallers:
             hookcaller.scan_methods()
 
-    def add_shutdown(self, func):
-        self._shutdown.append(func)
-
-    def ensure_shutdown(self):
-        while self._shutdown:
-            func = self._shutdown.pop()
-            func()
-        self._plugins = []
-        self._name2plugin.clear()
-
     def addhooks(self, module_or_class):
         isclass = int(inspect.isclass(module_or_class))
         names = []

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -28,24 +28,20 @@
     config = outcome.get_result()
     if config.option.debug:
         path = os.path.abspath("pytestdebug.log")
-        f = open(path, 'w')
-        config._debugfile = f
-        f.write("versions pytest-%s, py-%s, "
+        debugfile = open(path, 'w')
+        debugfile.write("versions pytest-%s, py-%s, "
                 "python-%s\ncwd=%s\nargs=%s\n\n" %(
             pytest.__version__, py.__version__,
             ".".join(map(str, sys.version_info)),
             os.getcwd(), config._origargs))
-        config.pluginmanager.set_tracing(f.write)
+        config.pluginmanager.set_tracing(debugfile.write)
         sys.stderr.write("writing pytestdebug information to %s\n" % path)
-
- at pytest.mark.trylast
-def pytest_unconfigure(config):
-    if hasattr(config, '_debugfile'):
-        config._debugfile.close()
-        sys.stderr.write("wrote pytestdebug information to %s\n" %
-            config._debugfile.name)
-        config.trace.root.setwriter(None)
-
+        def unset_tracing():
+            debugfile.close()
+            sys.stderr.write("wrote pytestdebug information to %s\n" %
+                             debugfile.name)
+            config.trace.root.setwriter(None)
+        config.add_cleanup(unset_tracing)
 
 def pytest_cmdline_main(config):
     if config.option.version:
@@ -58,9 +54,9 @@
                 sys.stderr.write(line + "\n")
         return 0
     elif config.option.help:
-        config.do_configure()
+        config._do_configure()
         showhelp(config)
-        config.do_unconfigure()
+        config._ensure_unconfigure()
         return 0
 
 def showhelp(config):

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -77,7 +77,7 @@
     initstate = 0
     try:
         try:
-            config.do_configure()
+            config._do_configure()
             initstate = 1
             config.hook.pytest_sessionstart(session=session)
             initstate = 2
@@ -107,9 +107,7 @@
             config.hook.pytest_sessionfinish(
                 session=session,
                 exitstatus=session.exitstatus)
-        if initstate >= 1:
-            config.do_unconfigure()
-        config.pluginmanager.ensure_shutdown()
+        config._ensure_unconfigure()
     return session.exitstatus
 
 def pytest_cmdline_main(config):

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -44,14 +44,14 @@
 
 def pytest_cmdline_main(config):
     if config.option.markers:
-        config.do_configure()
+        config._do_configure()
         tw = py.io.TerminalWriter()
         for line in config.getini("markers"):
             name, rest = line.split(":", 1)
             tw.write("@pytest.mark.%s:" % name, bold=True)
             tw.line(rest)
             tw.line()
-        config.do_unconfigure()
+        config._ensure_unconfigure()
         return 0
 pytest_cmdline_main.tryfirst = True
 

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 _pytest/pytester.py
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -65,7 +65,8 @@
             self.calls.append(ParsedCall(hookcaller.name, kwargs))
             yield
         self._undo_wrapping = add_method_wrapper(HookCaller, _docall)
-        pluginmanager.add_shutdown(self._undo_wrapping)
+        #if hasattr(pluginmanager, "config"):
+        #    pluginmanager.add_shutdown(self._undo_wrapping)
 
     def finish_recording(self):
         self._undo_wrapping()
@@ -571,12 +572,7 @@
         # we don't know what the test will do with this half-setup config
         # object and thus we make sure it gets unconfigured properly in any
         # case (otherwise capturing could still be active, for example)
-        def ensure_unconfigure():
-            if hasattr(config.pluginmanager, "_config"):
-                config.pluginmanager.do_unconfigure(config)
-            config.pluginmanager.ensure_shutdown()
-
-        self.request.addfinalizer(ensure_unconfigure)
+        self.request.addfinalizer(config._ensure_unconfigure)
         return config
 
     def parseconfigure(self, *args):
@@ -588,8 +584,8 @@
 
         """
         config = self.parseconfig(*args)
-        config.do_configure()
-        self.request.addfinalizer(config.do_unconfigure)
+        config._do_configure()
+        self.request.addfinalizer(config._ensure_unconfigure)
         return config
 
     def getitem(self,  source, funcname="test_func"):

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 testing/conftest.py
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -66,6 +66,7 @@
         error.append(error[0])
         raise AssertionError("\n".join(error))
 
+ at pytest.mark.trylast
 def pytest_runtest_teardown(item, __multicall__):
     item.config._basedir.chdir()
     if hasattr(item.config, '_openfiles'):

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -155,13 +155,13 @@
 
         config.pluginmanager.register(A())
         assert len(l) == 0
-        config.do_configure()
+        config._do_configure()
         assert len(l) == 1
         config.pluginmanager.register(A())  # leads to a configured() plugin
         assert len(l) == 2
         assert l[0] != l[1]
 
-        config.do_unconfigure()
+        config._ensure_unconfigure()
         config.pluginmanager.register(A())
         assert len(l) == 2
 

diff -r 02581ca33317258e8412fe4187299b6c74837d8a -r 300a44d73410ff26328f49953240c64e78189913 testing/test_session.py
--- a/testing/test_session.py
+++ b/testing/test_session.py
@@ -214,8 +214,8 @@
 def test_plugin_already_exists(testdir):
     config = testdir.parseconfig("-p", "terminal")
     assert config.option.plugins == ['terminal']
-    config.do_configure()
-    config.do_unconfigure()
+    config._do_configure()
+    config._ensure_unconfigure()
 
 def test_exclude(testdir):
     hellodir = testdir.mkdir("hello")


https://bitbucket.org/pytest-dev/pytest/commits/55a53e8c99c6/
Changeset:   55a53e8c99c6
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 14:34:42+00:00
Summary:     shuffle PluginManager method order to first have the public API
and then the internal.
Affected #:  0 files



https://bitbucket.org/pytest-dev/pytest/commits/98fdb4919f0b/
Changeset:   98fdb4919f0b
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-22 14:42:41+00:00
Summary:     reshuffle pluginmanager methods and add some docstrings.
Affected #:  1 file

diff -r 55a53e8c99c6b4023341f1ee9185b4b6cdde8457 -r 98fdb4919f0b358f7b73c3db86029d80dca7e148 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -1,7 +1,6 @@
 """
 PluginManager, basic initialization and tracing.
 """
-import os
 import sys
 import inspect
 import py
@@ -186,6 +185,108 @@
                               argnames=caller.argnames, methods=methods)
         return caller
 
+    def register(self, plugin, name=None):
+        """ Register a plugin with the given name and ensure that all its
+        hook implementations are integrated.  If the name is not specified
+        we use the ``__name__`` attribute of the plugin object or, if that
+        doesn't exist, the id of the plugin.  This method will raise a
+        ValueError if the eventual name is already registered. """
+        name = name or self._get_canonical_name(plugin)
+        if self._name2plugin.get(name, None) == -1:
+            return
+        if self.hasplugin(name):
+            raise ValueError("Plugin already registered: %s=%s\n%s" %(
+                              name, plugin, self._name2plugin))
+        #self.trace("registering", name, plugin)
+        # allow subclasses to intercept here by calling a helper
+        return self._do_register(plugin, name)
+
+    def _do_register(self, plugin, name):
+        hookcallers = list(self._scan_plugin(plugin))
+        self._plugin2hookcallers[plugin] = hookcallers
+        self._name2plugin[name] = plugin
+        self._plugins.append(plugin)
+        # rescan all methods for the hookcallers we found
+        for hookcaller in hookcallers:
+            hookcaller.scan_methods()
+        return True
+
+    def unregister(self, plugin):
+        """ unregister the plugin object and all its contained hook implementations
+        from internal data structures. """
+        self._plugins.remove(plugin)
+        for name, value in list(self._name2plugin.items()):
+            if value == plugin:
+                del self._name2plugin[name]
+        hookcallers = self._plugin2hookcallers.pop(plugin)
+        for hookcaller in hookcallers:
+            hookcaller.scan_methods()
+
+    def addhooks(self, module_or_class):
+        """ add new hook definitions from the given module_or_class using
+        the prefix/excludefunc with which the PluginManager was initialized. """
+        isclass = int(inspect.isclass(module_or_class))
+        names = []
+        for name in dir(module_or_class):
+            if name.startswith(self._prefix):
+                method = module_or_class.__dict__[name]
+                firstresult = getattr(method, 'firstresult', False)
+                hc = HookCaller(self.hook, name, firstresult=firstresult,
+                                argnames=varnames(method, startindex=isclass))
+                setattr(self.hook, name, hc)
+                names.append(name)
+        if not names:
+            raise ValueError("did not find new %r hooks in %r"
+                             %(self._prefix, module_or_class))
+
+    def getplugins(self):
+        """ return the complete list of registered plugins. NOTE that
+        you will get the internal list and need to make a copy if you
+        modify the list."""
+        return self._plugins
+
+    def isregistered(self, plugin):
+        """ Return True if the plugin is already registered under its
+        canonical name. """
+        return self.hasplugin(self._get_canonical_name(plugin)) or \
+               plugin in self._plugins
+
+    def hasplugin(self, name):
+        """ Return True if there is a registered with the given name. """
+        return name in self._name2plugin
+
+    def getplugin(self, name):
+        """ Return a plugin or None for the given name. """
+        return self._name2plugin.get(name)
+
+    def listattr(self, attrname, plugins=None):
+        if plugins is None:
+            plugins = self._plugins
+        l = []
+        last = []
+        wrappers = []
+        for plugin in plugins:
+            try:
+                meth = getattr(plugin, attrname)
+            except AttributeError:
+                continue
+            if hasattr(meth, 'hookwrapper'):
+                wrappers.append(meth)
+            elif hasattr(meth, 'tryfirst'):
+                last.append(meth)
+            elif hasattr(meth, 'trylast'):
+                l.insert(0, meth)
+            else:
+                l.append(meth)
+        l.extend(last)
+        l.extend(wrappers)
+        return l
+
+    def call_plugin(self, plugin, methname, kwargs):
+        return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
+                kwargs=kwargs, firstresult=True).execute()
+
+
     def _scan_plugin(self, plugin):
         def fail(msg, *args):
             name = getattr(plugin, '__name__', plugin)
@@ -214,90 +315,6 @@
     def _get_canonical_name(self, plugin):
         return getattr(plugin, "__name__", None) or str(id(plugin))
 
-    def register(self, plugin, name=None):
-        name = name or self._get_canonical_name(plugin)
-        if self._name2plugin.get(name, None) == -1:
-            return
-        if self.hasplugin(name):
-            raise ValueError("Plugin already registered: %s=%s\n%s" %(
-                              name, plugin, self._name2plugin))
-        #self.trace("registering", name, plugin)
-        # allow subclasses to intercept here by calling a helper
-        return self._do_register(plugin, name)
-
-    def _do_register(self, plugin, name):
-        hookcallers = list(self._scan_plugin(plugin))
-        self._plugin2hookcallers[plugin] = hookcallers
-        self._name2plugin[name] = plugin
-        self._plugins.append(plugin)
-        # rescan all methods for the hookcallers we found
-        for hookcaller in hookcallers:
-            hookcaller.scan_methods()
-        return True
-
-    def unregister(self, plugin):
-        self._plugins.remove(plugin)
-        for name, value in list(self._name2plugin.items()):
-            if value == plugin:
-                del self._name2plugin[name]
-        hookcallers = self._plugin2hookcallers.pop(plugin)
-        for hookcaller in hookcallers:
-            hookcaller.scan_methods()
-
-    def addhooks(self, module_or_class):
-        isclass = int(inspect.isclass(module_or_class))
-        names = []
-        for name in dir(module_or_class):
-            if name.startswith(self._prefix):
-                method = module_or_class.__dict__[name]
-                firstresult = getattr(method, 'firstresult', False)
-                hc = HookCaller(self.hook, name, firstresult=firstresult,
-                                argnames=varnames(method, startindex=isclass))
-                setattr(self.hook, name, hc)
-                names.append(name)
-        if not names:
-            raise ValueError("did not find new %r hooks in %r"
-                             %(self._prefix, module_or_class))
-
-    def getplugins(self):
-        return self._plugins
-
-    def isregistered(self, plugin):
-        return self.hasplugin(self._get_canonical_name(plugin)) or \
-               plugin in self._plugins
-
-    def hasplugin(self, name):
-        return name in self._name2plugin
-
-    def getplugin(self, name):
-        return self._name2plugin.get(name)
-
-    def listattr(self, attrname, plugins=None):
-        if plugins is None:
-            plugins = self._plugins
-        l = []
-        last = []
-        wrappers = []
-        for plugin in plugins:
-            try:
-                meth = getattr(plugin, attrname)
-            except AttributeError:
-                continue
-            if hasattr(meth, 'hookwrapper'):
-                wrappers.append(meth)
-            elif hasattr(meth, 'tryfirst'):
-                last.append(meth)
-            elif hasattr(meth, 'trylast'):
-                l.insert(0, meth)
-            else:
-                l.append(meth)
-        l.extend(last)
-        l.extend(wrappers)
-        return l
-
-    def call_plugin(self, plugin, methname, kwargs):
-        return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
-                kwargs=kwargs, firstresult=True).execute()
 
 
 class MultiCall:


https://bitbucket.org/pytest-dev/pytest/commits/5dd0adc5efd0/
Changeset:   5dd0adc5efd0
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-23 10:35:15+00:00
Summary:     merge default
Affected #:  6 files

diff -r 98fdb4919f0b358f7b73c3db86029d80dca7e148 -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e .hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -25,6 +25,8 @@
 doc/*/_build
 build/
 dist/
+testing/cx_freeze/build
+testing/cx_freeze/cx_freeze_source
 *.egg-info
 issue/
 env/

diff -r 98fdb4919f0b358f7b73c3db86029d80dca7e148 -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -53,6 +53,8 @@
 - fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
   when tests raised SystemExit. Thanks Holger Krekel.
 
+- reintroduced _pytest fixture of the pytester plugin which is used
+  at least by pytest-xdist.
 
 2.7.0 (compared to 2.6.4)
 -----------------------------

diff -r 98fdb4919f0b358f7b73c3db86029d80dca7e148 -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e README.rst
--- a/README.rst
+++ b/README.rst
@@ -1,5 +1,3 @@
-.. image:: https://drone.io/bitbucket.org/pytest-dev/pytest/status.png
-   :target: https://drone.io/bitbucket.org/pytest-dev/pytest/latest
 .. image:: https://pypip.in/v/pytest/badge.png
    :target: https://pypi.python.org/pypi/pytest
 

diff -r 98fdb4919f0b358f7b73c3db86029d80dca7e148 -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e _pytest/pytester.py
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -15,6 +15,24 @@
 
 from _pytest.main import Session, EXIT_OK
 
+# used at least by pytest-xdist plugin
+ at pytest.fixture
+def _pytest(request):
+    """ Return a helper which offers a gethookrecorder(hook)
+    method which returns a HookRecorder instance which helps
+    to make assertions about called hooks.
+    """
+    return PytestArg(request)
+
+class PytestArg:
+    def __init__(self, request):
+        self.request = request
+
+    def gethookrecorder(self, hook):
+        hookrecorder = HookRecorder(hook._pm)
+        self.request.addfinalizer(hookrecorder.finish_recording)
+        return hookrecorder
+
 
 def get_public_names(l):
     """Only return names from iterator l without a leading underscore."""

diff -r 98fdb4919f0b358f7b73c3db86029d80dca7e148 -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e testing/cx_freeze/install_cx_freeze.py
--- /dev/null
+++ b/testing/cx_freeze/install_cx_freeze.py
@@ -0,0 +1,66 @@
+"""
+Installs cx_freeze from source, but first patching
+setup.py as described here:
+
+http://stackoverflow.com/questions/25107697/compiling-cx-freeze-under-ubuntu
+"""
+import glob
+import shutil
+import tarfile
+import os
+import sys
+import platform
+
+if __name__ == '__main__':
+    if 'ubuntu' not in platform.version().lower():
+
+        print('Not Ubuntu, installing using pip. (platform.version() is %r)' %
+              platform.version())
+        res = os.system('pip install cx_freeze')
+        if res != 0:
+            sys.exit(res)
+        sys.exit(0)
+
+    if os.path.isdir('cx_freeze_source'):
+        shutil.rmtree('cx_freeze_source')
+    os.mkdir('cx_freeze_source')
+
+    res = os.system('pip install --download cx_freeze_source --no-use-wheel '
+                    'cx_freeze')
+    if res != 0:
+        sys.exit(res)
+
+    packages = glob.glob('cx_freeze_source/*.tar.gz')
+    assert len(packages) == 1
+    tar_filename = packages[0]
+
+    tar_file = tarfile.open(tar_filename)
+    try:
+        tar_file.extractall(path='cx_freeze_source')
+    finally:
+        tar_file.close()
+
+    basename = os.path.basename(tar_filename).replace('.tar.gz', '')
+    setup_py_filename = 'cx_freeze_source/%s/setup.py' % basename
+    with open(setup_py_filename) as f:
+        lines = f.readlines()
+
+    line_to_patch = 'if not vars.get("Py_ENABLE_SHARED", 0):'
+    for index, line in enumerate(lines):
+        if line_to_patch in line:
+            indent = line[:line.index(line_to_patch)]
+            lines[index] = indent + 'if True:\n'
+            print('Patched line %d' % (index + 1))
+            break
+    else:
+        sys.exit('Could not find line in setup.py to patch!')
+
+    with open(setup_py_filename, 'w') as f:
+        f.writelines(lines)
+
+    os.chdir('cx_freeze_source/%s' % basename)
+    res = os.system('python setup.py install')
+    if res != 0:
+        sys.exit(res)
+
+    sys.exit(0)
\ No newline at end of file

diff -r 98fdb4919f0b358f7b73c3db86029d80dca7e148 -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -125,10 +125,10 @@
         -rfsxX --junitxml={envlogdir}/junit-{envname}2.xml []
 
 [testenv:py27-cxfreeze]
-deps=cx_freeze
 changedir=testing/cx_freeze
 basepython=python2.7
 commands=
+    {envpython} install_cx_freeze.py
     {envpython} runtests_setup.py build --build-exe build
     {envpython} tox_run.py
 


https://bitbucket.org/pytest-dev/pytest/commits/b66bb638a5ac/
Changeset:   b66bb638a5ac
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-23 10:39:11+00:00
Summary:     streamline and document handling of builtin module special casing.
Affected #:  2 files

diff -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e -r b66bb638a5ac20659f54feba2b4cfa2ff73411df _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -53,6 +53,10 @@
      "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
      "junitxml resultlog doctest").split()
 
+builtin_plugins = set(default_plugins)
+builtin_plugins.add("pytester")
+
+
 def _preloadplugins():
     assert not _preinit
     _preinit.append(get_plugin_manager())
@@ -131,14 +135,6 @@
         except ValueError:
             pass
 
-    def getplugin(self, name):
-        if name is None:
-            return name
-        plugin = super(PytestPluginManager, self).getplugin(name)
-        if plugin is None:
-            plugin = super(PytestPluginManager, self).getplugin("_pytest." + name)
-        return plugin
-
     def pytest_configure(self, config):
         config.addinivalue_line("markers",
             "tryfirst: mark a hook implementation function such that the "
@@ -294,11 +290,19 @@
                 self.import_plugin(spec)
 
     def import_plugin(self, modname):
+        # most often modname refers to builtin modules, e.g. "pytester",
+        # "terminal" or "capture".  Those plugins are registered under their
+        # basename for historic purposes but must be imported with the
+        # _pytest prefix.
         assert isinstance(modname, str)
         if self.getplugin(modname) is not None:
             return
+        if modname in builtin_plugins:
+            importspec = "_pytest." + modname
+        else:
+            importspec = modname
         try:
-            mod = importplugin(modname)
+            __import__(importspec)
         except ImportError:
             raise
         except Exception as e:
@@ -307,6 +311,7 @@
                 raise
             self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
         else:
+            mod = sys.modules[importspec]
             self.register(mod, modname)
             self.consider_module(mod)
 
@@ -1040,14 +1045,3 @@
             #    pytest.__all__.append(name)
             setattr(pytest, name, value)
 
-
-def importplugin(importspec):
-    name = importspec
-    try:
-        mod = "_pytest." + name
-        __import__(mod)
-        return sys.modules[mod]
-    except ImportError:
-        __import__(importspec)
-        return sys.modules[importspec]
-

diff -r 5dd0adc5efd0533b71fa68ce2465ed62a9aba49e -r b66bb638a5ac20659f54feba2b4cfa2ff73411df testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -1,6 +1,6 @@
 import pytest, py, os
 from _pytest.core import * # noqa
-from _pytest.config import get_plugin_manager, importplugin
+from _pytest.config import get_plugin_manager
 
 
 @pytest.fixture
@@ -590,10 +590,11 @@
         "*trylast*last*",
     ])
 
-def test_importplugin_issue375(testdir):
+def test_importplugin_issue375(testdir, pytestpm):
     testdir.syspathinsert(testdir.tmpdir)
     testdir.makepyfile(qwe="import aaaa")
-    excinfo = pytest.raises(ImportError, lambda: importplugin("qwe"))
+    with pytest.raises(ImportError) as excinfo:
+        pytestpm.import_plugin("qwe")
     assert "qwe" not in str(excinfo.value)
     assert "aaaa" in str(excinfo.value)
 


https://bitbucket.org/pytest-dev/pytest/commits/b26c40205235/
Changeset:   b26c40205235
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-23 11:15:34+00:00
Summary:     remove some redundancy when parsing import spec
Affected #:  1 file

diff -r b66bb638a5ac20659f54feba2b4cfa2ff73411df -r b26c402052354f344adee76a5a2209a39fbb3b1f _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -232,15 +232,6 @@
     # API for bootstrapping plugin loading
     #
     #
-    def _envlist(self, varname):
-        val = os.environ.get(varname, None)
-        if val is not None:
-            return val.split(',')
-        return ()
-
-    def consider_env(self):
-        for spec in self._envlist("PYTEST_PLUGINS"):
-            self.import_plugin(spec)
 
     def consider_setuptools_entrypoints(self):
         try:
@@ -281,13 +272,18 @@
                          conftest=True):
             self.consider_module(conftestmodule)
 
+    def consider_env(self):
+        self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
+
     def consider_module(self, mod):
-        attr = getattr(mod, "pytest_plugins", ())
-        if attr:
-            if not isinstance(attr, (list, tuple)):
-                attr = (attr,)
-            for spec in attr:
-                self.import_plugin(spec)
+        self._import_plugin_specs(getattr(mod, "pytest_plugins", None))
+
+    def _import_plugin_specs(self, spec):
+        if spec:
+            if isinstance(spec, str):
+                spec = spec.split(",")
+            for import_spec in spec:
+                self.import_plugin(import_spec)
 
     def import_plugin(self, modname):
         # most often modname refers to builtin modules, e.g. "pytester",


https://bitbucket.org/pytest-dev/pytest/commits/1ecf40e29f0f/
Changeset:   1ecf40e29f0f
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-24 11:02:49+00:00
Summary:     minimize HookCaller attributes: avoid passing in hookrelay to HookCallers
Affected #:  1 file

diff -r b26c402052354f344adee76a5a2209a39fbb3b1f -r 1ecf40e29f0f56d107192f3eea9b26b963e853e3 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -166,14 +166,15 @@
         assert not hasattr(self, "_wrapping")
         self._wrapping = True
 
+        hooktrace = self.hook.trace
+
         def _docall(self, methods, kwargs):
-            trace = self.hookrelay.trace
-            trace.root.indent += 1
-            trace(self.name, kwargs)
+            hooktrace.root.indent += 1
+            hooktrace(self.name, kwargs)
             box = yield
             if box.excinfo is None:
-                trace("finish", self.name, "-->", box.result)
-            trace.root.indent -= 1
+                hooktrace("finish", self.name, "-->", box.result)
+            hooktrace.root.indent -= 1
 
         return add_method_wrapper(HookCaller, _docall)
 
@@ -181,7 +182,7 @@
         caller = getattr(self.hook, name)
         methods = self.listattr(name, plugins=plugins)
         if methods:
-            return HookCaller(self.hook, caller.name, caller.firstresult,
+            return HookCaller(caller.name, caller.firstresult,
                               argnames=caller.argnames, methods=methods)
         return caller
 
@@ -208,7 +209,7 @@
         self._plugins.append(plugin)
         # rescan all methods for the hookcallers we found
         for hookcaller in hookcallers:
-            hookcaller.scan_methods()
+            self._scan_methods(hookcaller)
         return True
 
     def unregister(self, plugin):
@@ -220,7 +221,7 @@
                 del self._name2plugin[name]
         hookcallers = self._plugin2hookcallers.pop(plugin)
         for hookcaller in hookcallers:
-            hookcaller.scan_methods()
+            self._scan_methods(hookcaller)
 
     def addhooks(self, module_or_class):
         """ add new hook definitions from the given module_or_class using
@@ -231,7 +232,7 @@
             if name.startswith(self._prefix):
                 method = module_or_class.__dict__[name]
                 firstresult = getattr(method, 'firstresult', False)
-                hc = HookCaller(self.hook, name, firstresult=firstresult,
+                hc = HookCaller(name, firstresult=firstresult,
                                 argnames=varnames(method, startindex=isclass))
                 setattr(self.hook, name, hc)
                 names.append(name)
@@ -282,6 +283,9 @@
         l.extend(wrappers)
         return l
 
+    def _scan_methods(self, hookcaller):
+        hookcaller.methods = self.listattr(hookcaller.name)
+
     def call_plugin(self, plugin, methname, kwargs):
         return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
                 kwargs=kwargs, firstresult=True).execute()
@@ -394,8 +398,7 @@
 
 
 class HookCaller:
-    def __init__(self, hookrelay, name, firstresult, argnames, methods=()):
-        self.hookrelay = hookrelay
+    def __init__(self, name, firstresult, argnames, methods=()):
         self.name = name
         self.firstresult = firstresult
         self.argnames = ["__multicall__"]
@@ -406,9 +409,6 @@
     def __repr__(self):
         return "<HookCaller %r>" %(self.name,)
 
-    def scan_methods(self):
-        self.methods = self.hookrelay._pm.listattr(self.name)
-
     def __call__(self, **kwargs):
         return self._docall(self.methods, kwargs)
 


https://bitbucket.org/pytest-dev/pytest/commits/49c0ea47afbf/
Changeset:   49c0ea47afbf
Branch:      plugin_no_pytest
User:        hpk42
Date:        2015-04-24 12:09:57+00:00
Summary:     remove useless check
Affected #:  1 file

diff -r 1ecf40e29f0f56d107192f3eea9b26b963e853e3 -r 49c0ea47afbfd0a04b70fbf7de57531f53cd3f9e _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -181,10 +181,8 @@
     def make_hook_caller(self, name, plugins):
         caller = getattr(self.hook, name)
         methods = self.listattr(name, plugins=plugins)
-        if methods:
-            return HookCaller(caller.name, caller.firstresult,
-                              argnames=caller.argnames, methods=methods)
-        return caller
+        return HookCaller(caller.name, caller.firstresult,
+                          argnames=caller.argnames, methods=methods)
 
     def register(self, plugin, name=None):
         """ Register a plugin with the given name and ensure that all its


https://bitbucket.org/pytest-dev/pytest/commits/90f9b67b555f/
Changeset:   90f9b67b555f
User:        hpk42
Date:        2015-04-25 07:08:21+00:00
Summary:     Merged in hpk42/pytest-patches/plugin_no_pytest (pull request #278)

Refactor pluginmanagement
Affected #:  20 files

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,6 +18,15 @@
   from ``inline_run()`` to allow temporary modules to be reloaded.
   Thanks Eduardo Schettino.
 
+- internally refactor pluginmanager API and code so that there
+  is a clear distinction between a pytest-agnostic rather simple 
+  pluginmanager and the PytestPluginManager which adds a lot of
+  behaviour, among it handling of the local conftest files.
+  In terms of documented methods this is a backward compatible
+  change but it might still break 3rd party plugins which relied on 
+  details like especially the pluginmanager.add_shutdown() API.
+  Thanks Holger Krekel.
+ 
 2.7.1.dev (compared to 2.7.0)
 -----------------------------
 

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/assertion/__init__.py
--- a/_pytest/assertion/__init__.py
+++ b/_pytest/assertion/__init__.py
@@ -70,12 +70,11 @@
     config._assertstate = AssertionState(config, mode)
     config._assertstate.hook = hook
     config._assertstate.trace("configured with mode set to %r" % (mode,))
-
-
-def pytest_unconfigure(config):
-    hook = config._assertstate.hook
-    if hook is not None and hook in sys.meta_path:
-        sys.meta_path.remove(hook)
+    def undo():
+        hook = config._assertstate.hook
+        if hook is not None and hook in sys.meta_path:
+            sys.meta_path.remove(hook)
+    config.add_cleanup(undo)
 
 
 def pytest_collection(session):

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -37,13 +37,13 @@
     pluginmanager.register(capman, "capturemanager")
 
     # make sure that capturemanager is properly reset at final shutdown
-    pluginmanager.add_shutdown(capman.reset_capturings)
+    early_config.add_cleanup(capman.reset_capturings)
 
     # make sure logging does not raise exceptions at the end
     def silence_logging_at_shutdown():
         if "logging" in sys.modules:
             sys.modules["logging"].raiseExceptions = False
-    pluginmanager.add_shutdown(silence_logging_at_shutdown)
+    early_config.add_cleanup(silence_logging_at_shutdown)
 
     # finally trigger conftest loading but while capturing (issue93)
     capman.init_capturings()

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -53,6 +53,10 @@
      "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
      "junitxml resultlog doctest").split()
 
+builtin_plugins = set(default_plugins)
+builtin_plugins.add("pytester")
+
+
 def _preloadplugins():
     assert not _preinit
     _preinit.append(get_plugin_manager())
@@ -77,19 +81,31 @@
             raise ValueError("not a string or argument list: %r" % (args,))
         args = shlex.split(args)
     pluginmanager = get_plugin_manager()
-    try:
-        if plugins:
-            for plugin in plugins:
-                pluginmanager.register(plugin)
-        return pluginmanager.hook.pytest_cmdline_parse(
-                pluginmanager=pluginmanager, args=args)
-    except Exception:
-        pluginmanager.ensure_shutdown()
-        raise
+    if plugins:
+        for plugin in plugins:
+            pluginmanager.register(plugin)
+    return pluginmanager.hook.pytest_cmdline_parse(
+            pluginmanager=pluginmanager, args=args)
+
+def exclude_pytest_names(name):
+    return not name.startswith(name) or name == "pytest_plugins" or \
+           name.startswith("pytest_funcarg__")
+
 
 class PytestPluginManager(PluginManager):
-    def __init__(self, hookspecs=[hookspec]):
-        super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
+    def __init__(self):
+        super(PytestPluginManager, self).__init__(prefix="pytest_",
+                                                  excludefunc=exclude_pytest_names)
+        self._warnings = []
+        self._plugin_distinfo = []
+        self._globalplugins = []
+
+        # state related to local conftest plugins
+        self._path2confmods = {}
+        self._conftestpath2mod = {}
+        self._confcutdir = None
+
+        self.addhooks(hookspec)
         self.register(self)
         if os.environ.get('PYTEST_DEBUG'):
             err = sys.stderr
@@ -100,6 +116,25 @@
                 pass
             self.set_tracing(err.write)
 
+    def register(self, plugin, name=None, conftest=False):
+        ret = super(PytestPluginManager, self).register(plugin, name)
+        if ret and not conftest:
+            self._globalplugins.append(plugin)
+        return ret
+
+    def _do_register(self, plugin, name):
+        # called from core PluginManager class
+        if hasattr(self, "config"):
+            self.config._register_plugin(plugin, name)
+        return super(PytestPluginManager, self)._do_register(plugin, name)
+
+    def unregister(self, plugin):
+        super(PytestPluginManager, self).unregister(plugin)
+        try:
+            self._globalplugins.remove(plugin)
+        except ValueError:
+            pass
+
     def pytest_configure(self, config):
         config.addinivalue_line("markers",
             "tryfirst: mark a hook implementation function such that the "
@@ -110,6 +145,172 @@
         for warning in self._warnings:
             config.warn(code="I1", message=warning)
 
+    #
+    # internal API for local conftest plugin handling
+    #
+    def _set_initial_conftests(self, namespace):
+        """ load initial conftest files given a preparsed "namespace".
+            As conftest files may add their own command line options
+            which have arguments ('--my-opt somepath') we might get some
+            false positives.  All builtin and 3rd party plugins will have
+            been loaded, however, so common options will not confuse our logic
+            here.
+        """
+        current = py.path.local()
+        self._confcutdir = current.join(namespace.confcutdir, abs=True) \
+                                if namespace.confcutdir else None
+        testpaths = namespace.file_or_dir
+        foundanchor = False
+        for path in testpaths:
+            path = str(path)
+            # remove node-id syntax
+            i = path.find("::")
+            if i != -1:
+                path = path[:i]
+            anchor = current.join(path, abs=1)
+            if exists(anchor): # we found some file object
+                self._try_load_conftest(anchor)
+                foundanchor = True
+        if not foundanchor:
+            self._try_load_conftest(current)
+
+    def _try_load_conftest(self, anchor):
+        self._getconftestmodules(anchor)
+        # let's also consider test* subdirs
+        if anchor.check(dir=1):
+            for x in anchor.listdir("test*"):
+                if x.check(dir=1):
+                    self._getconftestmodules(x)
+
+    def _getconftestmodules(self, path):
+        try:
+            return self._path2confmods[path]
+        except KeyError:
+            clist = []
+            for parent in path.parts():
+                if self._confcutdir and self._confcutdir.relto(parent):
+                    continue
+                conftestpath = parent.join("conftest.py")
+                if conftestpath.check(file=1):
+                    mod = self._importconftest(conftestpath)
+                    clist.append(mod)
+            self._path2confmods[path] = clist
+            return clist
+
+    def _rget_with_confmod(self, name, path):
+        modules = self._getconftestmodules(path)
+        for mod in reversed(modules):
+            try:
+                return mod, getattr(mod, name)
+            except AttributeError:
+                continue
+        raise KeyError(name)
+
+    def _importconftest(self, conftestpath):
+        try:
+            return self._conftestpath2mod[conftestpath]
+        except KeyError:
+            pkgpath = conftestpath.pypkgpath()
+            if pkgpath is None:
+                _ensure_removed_sysmodule(conftestpath.purebasename)
+            try:
+                mod = conftestpath.pyimport()
+            except Exception:
+                raise ConftestImportFailure(conftestpath, sys.exc_info())
+            self._conftestpath2mod[conftestpath] = mod
+            dirpath = conftestpath.dirpath()
+            if dirpath in self._path2confmods:
+                for path, mods in self._path2confmods.items():
+                    if path and path.relto(dirpath) or path == dirpath:
+                        assert mod not in mods
+                        mods.append(mod)
+            self.trace("loaded conftestmodule %r" %(mod))
+            self.consider_conftest(mod)
+            return mod
+
+    #
+    # API for bootstrapping plugin loading
+    #
+    #
+
+    def consider_setuptools_entrypoints(self):
+        try:
+            from pkg_resources import iter_entry_points, DistributionNotFound
+        except ImportError:
+            return # XXX issue a warning
+        for ep in iter_entry_points('pytest11'):
+            name = ep.name
+            if name.startswith("pytest_"):
+                name = name[7:]
+            if ep.name in self._name2plugin or name in self._name2plugin:
+                continue
+            try:
+                plugin = ep.load()
+            except DistributionNotFound:
+                continue
+            self._plugin_distinfo.append((ep.dist, plugin))
+            self.register(plugin, name=name)
+
+    def consider_preparse(self, args):
+        for opt1,opt2 in zip(args, args[1:]):
+            if opt1 == "-p":
+                self.consider_pluginarg(opt2)
+
+    def consider_pluginarg(self, arg):
+        if arg.startswith("no:"):
+            name = arg[3:]
+            plugin = self.getplugin(name)
+            if plugin is not None:
+                self.unregister(plugin)
+            self._name2plugin[name] = -1
+        else:
+            if self.getplugin(arg) is None:
+                self.import_plugin(arg)
+
+    def consider_conftest(self, conftestmodule):
+        if self.register(conftestmodule, name=conftestmodule.__file__,
+                         conftest=True):
+            self.consider_module(conftestmodule)
+
+    def consider_env(self):
+        self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
+
+    def consider_module(self, mod):
+        self._import_plugin_specs(getattr(mod, "pytest_plugins", None))
+
+    def _import_plugin_specs(self, spec):
+        if spec:
+            if isinstance(spec, str):
+                spec = spec.split(",")
+            for import_spec in spec:
+                self.import_plugin(import_spec)
+
+    def import_plugin(self, modname):
+        # most often modname refers to builtin modules, e.g. "pytester",
+        # "terminal" or "capture".  Those plugins are registered under their
+        # basename for historic purposes but must be imported with the
+        # _pytest prefix.
+        assert isinstance(modname, str)
+        if self.getplugin(modname) is not None:
+            return
+        if modname in builtin_plugins:
+            importspec = "_pytest." + modname
+        else:
+            importspec = modname
+        try:
+            __import__(importspec)
+        except ImportError:
+            raise
+        except Exception as e:
+            import pytest
+            if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
+                raise
+            self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
+        else:
+            mod = sys.modules[importspec]
+            self.register(mod, modname)
+            self.consider_module(mod)
+
 
 class Parser:
     """ Parser for command line arguments and ini-file values.  """
@@ -464,96 +665,6 @@
         return action._formatted_action_invocation
 
 
-class Conftest(object):
-    """ the single place for accessing values and interacting
-        towards conftest modules from pytest objects.
-    """
-    def __init__(self, onimport=None):
-        self._path2confmods = {}
-        self._onimport = onimport
-        self._conftestpath2mod = {}
-        self._confcutdir = None
-
-    def setinitial(self, namespace):
-        """ load initial conftest files given a preparsed "namespace".
-            As conftest files may add their own command line options
-            which have arguments ('--my-opt somepath') we might get some
-            false positives.  All builtin and 3rd party plugins will have
-            been loaded, however, so common options will not confuse our logic
-            here.
-        """
-        current = py.path.local()
-        self._confcutdir = current.join(namespace.confcutdir, abs=True) \
-                                if namespace.confcutdir else None
-        testpaths = namespace.file_or_dir
-        foundanchor = False
-        for path in testpaths:
-            path = str(path)
-            # remove node-id syntax
-            i = path.find("::")
-            if i != -1:
-                path = path[:i]
-            anchor = current.join(path, abs=1)
-            if exists(anchor): # we found some file object
-                self._try_load_conftest(anchor)
-                foundanchor = True
-        if not foundanchor:
-            self._try_load_conftest(current)
-
-    def _try_load_conftest(self, anchor):
-        self.getconftestmodules(anchor)
-        # let's also consider test* subdirs
-        if anchor.check(dir=1):
-            for x in anchor.listdir("test*"):
-                if x.check(dir=1):
-                    self.getconftestmodules(x)
-
-    def getconftestmodules(self, path):
-        try:
-            return self._path2confmods[path]
-        except KeyError:
-            clist = []
-            for parent in path.parts():
-                if self._confcutdir and self._confcutdir.relto(parent):
-                    continue
-                conftestpath = parent.join("conftest.py")
-                if conftestpath.check(file=1):
-                    mod = self.importconftest(conftestpath)
-                    clist.append(mod)
-            self._path2confmods[path] = clist
-            return clist
-
-    def rget_with_confmod(self, name, path):
-        modules = self.getconftestmodules(path)
-        for mod in reversed(modules):
-            try:
-                return mod, getattr(mod, name)
-            except AttributeError:
-                continue
-        raise KeyError(name)
-
-    def importconftest(self, conftestpath):
-        try:
-            return self._conftestpath2mod[conftestpath]
-        except KeyError:
-            pkgpath = conftestpath.pypkgpath()
-            if pkgpath is None:
-                _ensure_removed_sysmodule(conftestpath.purebasename)
-            try:
-                mod = conftestpath.pyimport()
-            except Exception:
-                raise ConftestImportFailure(conftestpath, sys.exc_info())
-            self._conftestpath2mod[conftestpath] = mod
-            dirpath = conftestpath.dirpath()
-            if dirpath in self._path2confmods:
-                for path, mods in self._path2confmods.items():
-                    if path and path.relto(dirpath) or path == dirpath:
-                        assert mod not in mods
-                        mods.append(mod)
-            if self._onimport:
-                self._onimport(mod)
-            return mod
-
 
 def _ensure_removed_sysmodule(modname):
     try:
@@ -589,13 +700,11 @@
         #: a pluginmanager instance
         self.pluginmanager = pluginmanager
         self.trace = self.pluginmanager.trace.root.get("config")
-        self._conftest = Conftest(onimport=self._onimportconftest)
         self.hook = self.pluginmanager.hook
         self._inicache = {}
         self._opt2dest = {}
         self._cleanup = []
         self.pluginmanager.register(self, "pytestconfig")
-        self.pluginmanager.set_register_callback(self._register_plugin)
         self._configured = False
 
     def _register_plugin(self, plugin, name):
@@ -612,16 +721,23 @@
         if self._configured:
             call_plugin(plugin, "pytest_configure", {'config': self})
 
-    def do_configure(self):
+    def add_cleanup(self, func):
+        """ Add a function to be called when the config object gets out of
+        use (usually coninciding with pytest_unconfigure)."""
+        self._cleanup.append(func)
+
+    def _do_configure(self):
         assert not self._configured
         self._configured = True
         self.hook.pytest_configure(config=self)
 
-    def do_unconfigure(self):
-        assert self._configured
-        self._configured = False
-        self.hook.pytest_unconfigure(config=self)
-        self.pluginmanager.ensure_shutdown()
+    def _ensure_unconfigure(self):
+        if self._configured:
+            self._configured = False
+            self.hook.pytest_unconfigure(config=self)
+        while self._cleanup:
+            fin = self._cleanup.pop()
+            fin()
 
     def warn(self, code, message):
         """ generate a warning for this test session. """
@@ -636,11 +752,6 @@
         self.parse(args)
         return self
 
-    def pytest_unconfigure(config):
-        while config._cleanup:
-            fin = config._cleanup.pop()
-            fin()
-
     def notify_exception(self, excinfo, option=None):
         if option and option.fulltrace:
             style = "long"
@@ -675,10 +786,6 @@
             config.pluginmanager.consider_pluginarg(x)
         return config
 
-    def _onimportconftest(self, conftestmodule):
-        self.trace("loaded conftestmodule %r" %(conftestmodule,))
-        self.pluginmanager.consider_conftest(conftestmodule)
-
     def _processopt(self, opt):
         for name in opt._short_opts + opt._long_opts:
             self._opt2dest[name] = opt.dest
@@ -688,11 +795,11 @@
                 setattr(self.option, opt.dest, opt.default)
 
     def _getmatchingplugins(self, fspath):
-        return self.pluginmanager._plugins + \
-               self._conftest.getconftestmodules(fspath)
+        return self.pluginmanager._globalplugins + \
+               self.pluginmanager._getconftestmodules(fspath)
 
     def pytest_load_initial_conftests(self, early_config):
-        self._conftest.setinitial(early_config.known_args_namespace)
+        self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
     pytest_load_initial_conftests.trylast = True
 
     def _initini(self, args):
@@ -799,7 +906,7 @@
 
     def _getconftest_pathlist(self, name, path):
         try:
-            mod, relroots = self._conftest.rget_with_confmod(name, path)
+            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
         except KeyError:
             return None
         modpath = py.path.local(mod.__file__).dirpath()
@@ -933,3 +1040,4 @@
             #if obj != pytest:
             #    pytest.__all__.append(name)
             setattr(pytest, name, value)
+

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -1,14 +1,9 @@
 """
-pytest PluginManager, basic initialization and tracing.
+PluginManager, basic initialization and tracing.
 """
-import os
 import sys
 import inspect
 import py
-# don't import pytest to avoid circular imports
-
-assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
-    "%s is too old, remove or upgrade 'py'" % (py.__version__))
 
 py3 = sys.version_info > (3,0)
 
@@ -139,202 +134,133 @@
 
 
 class PluginManager(object):
-    def __init__(self, hookspecs=None, prefix="pytest_"):
+    """ Core Pluginmanager class which manages registration
+    of plugin objects and 1:N hook calling.
+
+    You can register new hooks by calling ``addhooks(module_or_class)``.
+    You can register plugin objects (which contain hooks) by calling
+    ``register(plugin)``.  The Pluginmanager is initialized with a
+    prefix that is searched for in the names of the dict of registered
+    plugin objects.  An optional excludefunc allows to blacklist names which
+    are not considered as hooks despite a matching prefix.
+
+    For debugging purposes you can call ``set_tracing(writer)``
+    which will subsequently send debug information to the specified
+    write function.
+    """
+
+    def __init__(self, prefix, excludefunc=None):
+        self._prefix = prefix
+        self._excludefunc = excludefunc
         self._name2plugin = {}
         self._plugins = []
-        self._conftestplugins = []
         self._plugin2hookcallers = {}
-        self._warnings = []
         self.trace = TagTracer().get("pluginmanage")
-        self._plugin_distinfo = []
-        self._shutdown = []
-        self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix)
+        self.hook = HookRelay(pm=self)
 
     def set_tracing(self, writer):
+        """ turn on tracing to the given writer method and
+        return an undo function. """
         self.trace.root.setwriter(writer)
         # reconfigure HookCalling to perform tracing
         assert not hasattr(self, "_wrapping")
         self._wrapping = True
 
+        hooktrace = self.hook.trace
+
         def _docall(self, methods, kwargs):
-            trace = self.hookrelay.trace
-            trace.root.indent += 1
-            trace(self.name, kwargs)
+            hooktrace.root.indent += 1
+            hooktrace(self.name, kwargs)
             box = yield
             if box.excinfo is None:
-                trace("finish", self.name, "-->", box.result)
-            trace.root.indent -= 1
+                hooktrace("finish", self.name, "-->", box.result)
+            hooktrace.root.indent -= 1
 
-        undo = add_method_wrapper(HookCaller, _docall)
-        self.add_shutdown(undo)
+        return add_method_wrapper(HookCaller, _docall)
 
-    def do_configure(self, config):
-        # backward compatibility
-        config.do_configure()
+    def make_hook_caller(self, name, plugins):
+        caller = getattr(self.hook, name)
+        methods = self.listattr(name, plugins=plugins)
+        return HookCaller(caller.name, caller.firstresult,
+                          argnames=caller.argnames, methods=methods)
 
-    def set_register_callback(self, callback):
-        assert not hasattr(self, "_registercallback")
-        self._registercallback = callback
-
-    def register(self, plugin, name=None, prepend=False, conftest=False):
+    def register(self, plugin, name=None):
+        """ Register a plugin with the given name and ensure that all its
+        hook implementations are integrated.  If the name is not specified
+        we use the ``__name__`` attribute of the plugin object or, if that
+        doesn't exist, the id of the plugin.  This method will raise a
+        ValueError if the eventual name is already registered. """
+        name = name or self._get_canonical_name(plugin)
         if self._name2plugin.get(name, None) == -1:
             return
-        name = name or getattr(plugin, '__name__', str(id(plugin)))
-        if self.isregistered(plugin, name):
+        if self.hasplugin(name):
             raise ValueError("Plugin already registered: %s=%s\n%s" %(
                               name, plugin, self._name2plugin))
         #self.trace("registering", name, plugin)
-        reg = getattr(self, "_registercallback", None)
-        if reg is not None:
-            reg(plugin, name)  # may call addhooks
-        hookcallers = list(self.hook._scan_plugin(plugin))
+        # allow subclasses to intercept here by calling a helper
+        return self._do_register(plugin, name)
+
+    def _do_register(self, plugin, name):
+        hookcallers = list(self._scan_plugin(plugin))
         self._plugin2hookcallers[plugin] = hookcallers
         self._name2plugin[name] = plugin
-        if conftest:
-            self._conftestplugins.append(plugin)
-        else:
-            if not prepend:
-                self._plugins.append(plugin)
-            else:
-                self._plugins.insert(0, plugin)
-        # finally make sure that the methods of the new plugin take part
+        self._plugins.append(plugin)
+        # rescan all methods for the hookcallers we found
         for hookcaller in hookcallers:
-            hookcaller.scan_methods()
+            self._scan_methods(hookcaller)
         return True
 
     def unregister(self, plugin):
-        try:
-            self._plugins.remove(plugin)
-        except KeyError:
-            self._conftestplugins.remove(plugin)
+        """ unregister the plugin object and all its contained hook implementations
+        from internal data structures. """
+        self._plugins.remove(plugin)
         for name, value in list(self._name2plugin.items()):
             if value == plugin:
                 del self._name2plugin[name]
         hookcallers = self._plugin2hookcallers.pop(plugin)
         for hookcaller in hookcallers:
-            hookcaller.scan_methods()
+            self._scan_methods(hookcaller)
 
-    def add_shutdown(self, func):
-        self._shutdown.append(func)
-
-    def ensure_shutdown(self):
-        while self._shutdown:
-            func = self._shutdown.pop()
-            func()
-        self._plugins = self._conftestplugins = []
-        self._name2plugin.clear()
-
-    def isregistered(self, plugin, name=None):
-        if self.getplugin(name) is not None:
-            return True
-        return plugin in self._plugins or plugin in self._conftestplugins
-
-    def addhooks(self, spec, prefix="pytest_"):
-        self.hook._addhooks(spec, prefix=prefix)
+    def addhooks(self, module_or_class):
+        """ add new hook definitions from the given module_or_class using
+        the prefix/excludefunc with which the PluginManager was initialized. """
+        isclass = int(inspect.isclass(module_or_class))
+        names = []
+        for name in dir(module_or_class):
+            if name.startswith(self._prefix):
+                method = module_or_class.__dict__[name]
+                firstresult = getattr(method, 'firstresult', False)
+                hc = HookCaller(name, firstresult=firstresult,
+                                argnames=varnames(method, startindex=isclass))
+                setattr(self.hook, name, hc)
+                names.append(name)
+        if not names:
+            raise ValueError("did not find new %r hooks in %r"
+                             %(self._prefix, module_or_class))
 
     def getplugins(self):
-        return self._plugins + self._conftestplugins
+        """ return the complete list of registered plugins. NOTE that
+        you will get the internal list and need to make a copy if you
+        modify the list."""
+        return self._plugins
 
-    def skipifmissing(self, name):
-        if not self.hasplugin(name):
-            import pytest
-            pytest.skip("plugin %r is missing" % name)
+    def isregistered(self, plugin):
+        """ Return True if the plugin is already registered under its
+        canonical name. """
+        return self.hasplugin(self._get_canonical_name(plugin)) or \
+               plugin in self._plugins
 
     def hasplugin(self, name):
-        return bool(self.getplugin(name))
+        """ Return True if there is a registered with the given name. """
+        return name in self._name2plugin
 
     def getplugin(self, name):
-        if name is None:
-            return None
-        try:
-            return self._name2plugin[name]
-        except KeyError:
-            return self._name2plugin.get("_pytest." + name, None)
-
-    # API for bootstrapping
-    #
-    def _envlist(self, varname):
-        val = os.environ.get(varname, None)
-        if val is not None:
-            return val.split(',')
-        return ()
-
-    def consider_env(self):
-        for spec in self._envlist("PYTEST_PLUGINS"):
-            self.import_plugin(spec)
-
-    def consider_setuptools_entrypoints(self):
-        try:
-            from pkg_resources import iter_entry_points, DistributionNotFound
-        except ImportError:
-            return # XXX issue a warning
-        for ep in iter_entry_points('pytest11'):
-            name = ep.name
-            if name.startswith("pytest_"):
-                name = name[7:]
-            if ep.name in self._name2plugin or name in self._name2plugin:
-                continue
-            try:
-                plugin = ep.load()
-            except DistributionNotFound:
-                continue
-            self._plugin_distinfo.append((ep.dist, plugin))
-            self.register(plugin, name=name)
-
-    def consider_preparse(self, args):
-        for opt1,opt2 in zip(args, args[1:]):
-            if opt1 == "-p":
-                self.consider_pluginarg(opt2)
-
-    def consider_pluginarg(self, arg):
-        if arg.startswith("no:"):
-            name = arg[3:]
-            plugin = self.getplugin(name)
-            if plugin is not None:
-                self.unregister(plugin)
-            self._name2plugin[name] = -1
-        else:
-            if self.getplugin(arg) is None:
-                self.import_plugin(arg)
-
-    def consider_conftest(self, conftestmodule):
-        if self.register(conftestmodule, name=conftestmodule.__file__,
-                         conftest=True):
-            self.consider_module(conftestmodule)
-
-    def consider_module(self, mod):
-        attr = getattr(mod, "pytest_plugins", ())
-        if attr:
-            if not isinstance(attr, (list, tuple)):
-                attr = (attr,)
-            for spec in attr:
-                self.import_plugin(spec)
-
-    def import_plugin(self, modname):
-        assert isinstance(modname, str)
-        if self.getplugin(modname) is not None:
-            return
-        try:
-            mod = importplugin(modname)
-        except KeyboardInterrupt:
-            raise
-        except ImportError:
-            if modname.startswith("pytest_"):
-                return self.import_plugin(modname[7:])
-            raise
-        except:
-            e = sys.exc_info()[1]
-            import pytest
-            if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
-                raise
-            self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
-        else:
-            self.register(mod, modname)
-            self.consider_module(mod)
+        """ Return a plugin or None for the given name. """
+        return self._name2plugin.get(name)
 
     def listattr(self, attrname, plugins=None):
         if plugins is None:
-            plugins = self._plugins + self._conftestplugins
+            plugins = self._plugins
         l = []
         last = []
         wrappers = []
@@ -355,20 +281,43 @@
         l.extend(wrappers)
         return l
 
+    def _scan_methods(self, hookcaller):
+        hookcaller.methods = self.listattr(hookcaller.name)
+
     def call_plugin(self, plugin, methname, kwargs):
         return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
                 kwargs=kwargs, firstresult=True).execute()
 
 
-def importplugin(importspec):
-    name = importspec
-    try:
-        mod = "_pytest." + name
-        __import__(mod)
-        return sys.modules[mod]
-    except ImportError:
-        __import__(importspec)
-        return sys.modules[importspec]
+    def _scan_plugin(self, plugin):
+        def fail(msg, *args):
+            name = getattr(plugin, '__name__', plugin)
+            raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
+
+        for name in dir(plugin):
+            if name[0] == "_" or not name.startswith(self._prefix):
+                continue
+            hook = getattr(self.hook, name, None)
+            method = getattr(plugin, name)
+            if hook is None:
+                if self._excludefunc is not None and self._excludefunc(name):
+                    continue
+                if getattr(method, 'optionalhook', False):
+                    continue
+                fail("found unknown hook: %r", name)
+            for arg in varnames(method):
+                if arg not in hook.argnames:
+                    fail("argument %r not available\n"
+                         "actual definition: %s\n"
+                         "available hookargs: %s",
+                         arg, formatdef(method),
+                           ", ".join(hook.argnames))
+            yield hook
+
+    def _get_canonical_name(self, plugin):
+        return getattr(plugin, "__name__", None) or str(id(plugin))
+
+
 
 class MultiCall:
     """ execute a call into multiple python functions/methods. """
@@ -441,65 +390,13 @@
 
 
 class HookRelay:
-    def __init__(self, hookspecs, pm, prefix="pytest_"):
-        if not isinstance(hookspecs, list):
-            hookspecs = [hookspecs]
+    def __init__(self, pm):
         self._pm = pm
         self.trace = pm.trace.root.get("hook")
-        self.prefix = prefix
-        for hookspec in hookspecs:
-            self._addhooks(hookspec, prefix)
-
-    def _addhooks(self, hookspec, prefix):
-        added = False
-        isclass = int(inspect.isclass(hookspec))
-        for name, method in vars(hookspec).items():
-            if name.startswith(prefix):
-                firstresult = getattr(method, 'firstresult', False)
-                hc = HookCaller(self, name, firstresult=firstresult,
-                                argnames=varnames(method, startindex=isclass))
-                setattr(self, name, hc)
-                added = True
-                #print ("setting new hook", name)
-        if not added:
-            raise ValueError("did not find new %r hooks in %r" %(
-                prefix, hookspec,))
-
-    def _getcaller(self, name, plugins):
-        caller = getattr(self, name)
-        methods = self._pm.listattr(name, plugins=plugins)
-        if methods:
-            return caller.new_cached_caller(methods)
-        return caller
-
-    def _scan_plugin(self, plugin):
-        def fail(msg, *args):
-            name = getattr(plugin, '__name__', plugin)
-            raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
-
-        for name in dir(plugin):
-            if not name.startswith(self.prefix):
-                continue
-            hook = getattr(self, name, None)
-            method = getattr(plugin, name)
-            if hook is None:
-                is_optional = getattr(method, 'optionalhook', False)
-                if not isgenerichook(name) and not is_optional:
-                    fail("found unknown hook: %r", name)
-                continue
-            for arg in varnames(method):
-                if arg not in hook.argnames:
-                    fail("argument %r not available\n"
-                         "actual definition: %s\n"
-                         "available hookargs: %s",
-                         arg, formatdef(method),
-                           ", ".join(hook.argnames))
-            yield hook
 
 
 class HookCaller:
-    def __init__(self, hookrelay, name, firstresult, argnames, methods=()):
-        self.hookrelay = hookrelay
+    def __init__(self, name, firstresult, argnames, methods=()):
         self.name = name
         self.firstresult = firstresult
         self.argnames = ["__multicall__"]
@@ -507,16 +404,9 @@
         assert "self" not in argnames  # sanity check
         self.methods = methods
 
-    def new_cached_caller(self, methods):
-        return HookCaller(self.hookrelay, self.name, self.firstresult,
-                          argnames=self.argnames, methods=methods)
-
     def __repr__(self):
         return "<HookCaller %r>" %(self.name,)
 
-    def scan_methods(self):
-        self.methods = self.hookrelay._pm.listattr(self.name)
-
     def __call__(self, **kwargs):
         return self._docall(self.methods, kwargs)
 
@@ -531,13 +421,9 @@
 class PluginValidationError(Exception):
     """ plugin failed validation. """
 
-def isgenerichook(name):
-    return name == "pytest_plugins" or \
-           name.startswith("pytest_funcarg__")
 
 def formatdef(func):
     return "%s%s" % (
         func.__name__,
         inspect.formatargspec(*inspect.getargspec(func))
     )
-

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/doctest.py
--- a/_pytest/doctest.py
+++ b/_pytest/doctest.py
@@ -132,7 +132,7 @@
     def collect(self):
         import doctest
         if self.fspath.basename == "conftest.py":
-            module = self.config._conftest.importconftest(self.fspath)
+            module = self.config._conftest._importconftest(self.fspath)
         else:
             try:
                 module = self.fspath.pyimport()

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -28,24 +28,20 @@
     config = outcome.get_result()
     if config.option.debug:
         path = os.path.abspath("pytestdebug.log")
-        f = open(path, 'w')
-        config._debugfile = f
-        f.write("versions pytest-%s, py-%s, "
+        debugfile = open(path, 'w')
+        debugfile.write("versions pytest-%s, py-%s, "
                 "python-%s\ncwd=%s\nargs=%s\n\n" %(
             pytest.__version__, py.__version__,
             ".".join(map(str, sys.version_info)),
             os.getcwd(), config._origargs))
-        config.pluginmanager.set_tracing(f.write)
+        config.pluginmanager.set_tracing(debugfile.write)
         sys.stderr.write("writing pytestdebug information to %s\n" % path)
-
- at pytest.mark.trylast
-def pytest_unconfigure(config):
-    if hasattr(config, '_debugfile'):
-        config._debugfile.close()
-        sys.stderr.write("wrote pytestdebug information to %s\n" %
-            config._debugfile.name)
-        config.trace.root.setwriter(None)
-
+        def unset_tracing():
+            debugfile.close()
+            sys.stderr.write("wrote pytestdebug information to %s\n" %
+                             debugfile.name)
+            config.trace.root.setwriter(None)
+        config.add_cleanup(unset_tracing)
 
 def pytest_cmdline_main(config):
     if config.option.version:
@@ -58,9 +54,9 @@
                 sys.stderr.write(line + "\n")
         return 0
     elif config.option.help:
-        config.do_configure()
+        config._do_configure()
         showhelp(config)
-        config.do_unconfigure()
+        config._ensure_unconfigure()
         return 0
 
 def showhelp(config):

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -6,7 +6,7 @@
 
 def pytest_addhooks(pluginmanager):
     """called at plugin load time to allow adding new hooks via a call to
-    pluginmanager.registerhooks(module)."""
+    pluginmanager.addhooks(module_or_class, prefix)."""
 
 
 def pytest_namespace():

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -77,7 +77,7 @@
     initstate = 0
     try:
         try:
-            config.do_configure()
+            config._do_configure()
             initstate = 1
             config.hook.pytest_sessionstart(session=session)
             initstate = 2
@@ -107,9 +107,7 @@
             config.hook.pytest_sessionfinish(
                 session=session,
                 exitstatus=session.exitstatus)
-        if initstate >= 1:
-            config.do_unconfigure()
-        config.pluginmanager.ensure_shutdown()
+        config._ensure_unconfigure()
     return session.exitstatus
 
 def pytest_cmdline_main(config):
@@ -160,7 +158,7 @@
 
     def __getattr__(self, name):
         plugins = self.config._getmatchingplugins(self.fspath)
-        x = self.config.hook._getcaller(name, plugins)
+        x = self.config.pluginmanager.make_hook_caller(name, plugins)
         self.__dict__[name] = x
         return x
 
@@ -510,7 +508,7 @@
     def __init__(self, config):
         FSCollector.__init__(self, config.rootdir, parent=None,
                              config=config, session=self)
-        self.config.pluginmanager.register(self, name="session", prepend=True)
+        self.config.pluginmanager.register(self, name="session")
         self._testsfailed = 0
         self.shouldstop = False
         self.trace = config.trace.root.get("collection")
@@ -521,10 +519,12 @@
     def _makeid(self):
         return ""
 
+    @pytest.mark.tryfirst
     def pytest_collectstart(self):
         if self.shouldstop:
             raise self.Interrupted(self.shouldstop)
 
+    @pytest.mark.tryfirst
     def pytest_runtest_logreport(self, report):
         if report.failed and not hasattr(report, 'wasxfail'):
             self._testsfailed += 1

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -44,14 +44,14 @@
 
 def pytest_cmdline_main(config):
     if config.option.markers:
-        config.do_configure()
+        config._do_configure()
         tw = py.io.TerminalWriter()
         for line in config.getini("markers"):
             name, rest = line.split(":", 1)
             tw.write("@pytest.mark.%s:" % name, bold=True)
             tw.line(rest)
             tw.line()
-        config.do_unconfigure()
+        config._ensure_unconfigure()
         return 0
 pytest_cmdline_main.tryfirst = True
 

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 _pytest/pytester.py
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -83,7 +83,8 @@
             self.calls.append(ParsedCall(hookcaller.name, kwargs))
             yield
         self._undo_wrapping = add_method_wrapper(HookCaller, _docall)
-        pluginmanager.add_shutdown(self._undo_wrapping)
+        #if hasattr(pluginmanager, "config"):
+        #    pluginmanager.add_shutdown(self._undo_wrapping)
 
     def finish_recording(self):
         self._undo_wrapping()
@@ -589,12 +590,7 @@
         # we don't know what the test will do with this half-setup config
         # object and thus we make sure it gets unconfigured properly in any
         # case (otherwise capturing could still be active, for example)
-        def ensure_unconfigure():
-            if hasattr(config.pluginmanager, "_config"):
-                config.pluginmanager.do_unconfigure(config)
-            config.pluginmanager.ensure_shutdown()
-
-        self.request.addfinalizer(ensure_unconfigure)
+        self.request.addfinalizer(config._ensure_unconfigure)
         return config
 
     def parseconfigure(self, *args):
@@ -606,8 +602,8 @@
 
         """
         config = self.parseconfig(*args)
-        config.do_configure()
-        self.request.addfinalizer(config.do_unconfigure)
+        config._do_configure()
+        self.request.addfinalizer(config._ensure_unconfigure)
         return config
 
     def getitem(self,  source, funcname="test_func"):

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/conftest.py
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -66,6 +66,7 @@
         error.append(error[0])
         raise AssertionError("\n".join(error))
 
+ at pytest.mark.trylast
 def pytest_runtest_teardown(item, __multicall__):
     item.config._basedir.chdir()
     if hasattr(item.config, '_openfiles'):

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/python/fixture.py
--- a/testing/python/fixture.py
+++ b/testing/python/fixture.py
@@ -1487,7 +1487,7 @@
         reprec = testdir.inline_run("-v","-s")
         reprec.assertoutcome(passed=8)
         config = reprec.getcalls("pytest_unconfigure")[0].config
-        l = config._conftest.getconftestmodules(p)[0].l
+        l = config.pluginmanager._getconftestmodules(p)[0].l
         assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
 
     def test_scope_ordering(self, testdir):

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/test_conftest.py
--- a/testing/test_conftest.py
+++ b/testing/test_conftest.py
@@ -1,7 +1,6 @@
 from textwrap import dedent
 import py, pytest
-from _pytest.config import Conftest
-
+from _pytest.config import PytestPluginManager
 
 
 @pytest.fixture(scope="module", params=["global", "inpackage"])
@@ -16,7 +15,7 @@
     return tmpdir
 
 def ConftestWithSetinitial(path):
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [path])
     return conftest
 
@@ -25,51 +24,41 @@
         def __init__(self):
             self.file_or_dir = args
             self.confcutdir = str(confcutdir)
-    conftest.setinitial(Namespace())
+    conftest._set_initial_conftests(Namespace())
 
 class TestConftestValueAccessGlobal:
     def test_basic_init(self, basedir):
-        conftest = Conftest()
+        conftest = PytestPluginManager()
         p = basedir.join("adir")
-        assert conftest.rget_with_confmod("a", p)[1] == 1
-
-    def test_onimport(self, basedir):
-        l = []
-        conftest = Conftest(onimport=l.append)
-        adir = basedir.join("adir")
-        conftest_setinitial(conftest, [adir], confcutdir=basedir)
-        assert len(l) == 1
-        assert conftest.rget_with_confmod("a", adir)[1] == 1
-        assert conftest.rget_with_confmod("b", adir.join("b"))[1] == 2
-        assert len(l) == 2
+        assert conftest._rget_with_confmod("a", p)[1] == 1
 
     def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
-        conftest = Conftest()
+        conftest = PytestPluginManager()
         len(conftest._path2confmods)
-        conftest.getconftestmodules(basedir)
+        conftest._getconftestmodules(basedir)
         snap1 = len(conftest._path2confmods)
         #assert len(conftest._path2confmods) == snap1 + 1
-        conftest.getconftestmodules(basedir.join('adir'))
+        conftest._getconftestmodules(basedir.join('adir'))
         assert len(conftest._path2confmods) == snap1 + 1
-        conftest.getconftestmodules(basedir.join('b'))
+        conftest._getconftestmodules(basedir.join('b'))
         assert len(conftest._path2confmods) == snap1 + 2
 
     def test_value_access_not_existing(self, basedir):
         conftest = ConftestWithSetinitial(basedir)
         with pytest.raises(KeyError):
-            conftest.rget_with_confmod('a', basedir)
+            conftest._rget_with_confmod('a', basedir)
 
     def test_value_access_by_path(self, basedir):
         conftest = ConftestWithSetinitial(basedir)
         adir = basedir.join("adir")
-        assert conftest.rget_with_confmod("a", adir)[1] == 1
-        assert conftest.rget_with_confmod("a", adir.join("b"))[1] == 1.5
+        assert conftest._rget_with_confmod("a", adir)[1] == 1
+        assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
 
     def test_value_access_with_confmod(self, basedir):
         startdir = basedir.join("adir", "b")
         startdir.ensure("xx", dir=True)
         conftest = ConftestWithSetinitial(startdir)
-        mod, value = conftest.rget_with_confmod("a", startdir)
+        mod, value = conftest._rget_with_confmod("a", startdir)
         assert  value == 1.5
         path = py.path.local(mod.__file__)
         assert path.dirpath() == basedir.join("adir", "b")
@@ -85,9 +74,9 @@
 def test_doubledash_considered(testdir):
     conf = testdir.mkdir("--option")
     conf.join("conftest.py").ensure()
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [conf.basename, conf.basename])
-    l = conftest.getconftestmodules(conf)
+    l = conftest._getconftestmodules(conf)
     assert len(l) == 1
 
 def test_issue151_load_all_conftests(testdir):
@@ -96,7 +85,7 @@
         p = testdir.mkdir(name)
         p.ensure("conftest.py")
 
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, names)
     d = list(conftest._conftestpath2mod.values())
     assert len(d) == len(names)
@@ -105,15 +94,15 @@
     testdir.makeconftest("x=3")
     p = testdir.makepyfile("""
         import py, pytest
-        from _pytest.config import Conftest
-        conf = Conftest()
-        mod = conf.importconftest(py.path.local("conftest.py"))
+        from _pytest.config import PytestPluginManager
+        conf = PytestPluginManager()
+        mod = conf._importconftest(py.path.local("conftest.py"))
         assert mod.x == 3
         import conftest
         assert conftest is mod, (conftest, mod)
         subconf = py.path.local().ensure("sub", "conftest.py")
         subconf.write("y=4")
-        mod2 = conf.importconftest(subconf)
+        mod2 = conf._importconftest(subconf)
         assert mod != mod2
         assert mod2.y == 4
         import conftest
@@ -125,27 +114,27 @@
 def test_conftestcutdir(testdir):
     conf = testdir.makeconftest("")
     p = testdir.mkdir("x")
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
-    l = conftest.getconftestmodules(p)
+    l = conftest._getconftestmodules(p)
     assert len(l) == 0
-    l = conftest.getconftestmodules(conf.dirpath())
+    l = conftest._getconftestmodules(conf.dirpath())
     assert len(l) == 0
     assert conf not in conftest._conftestpath2mod
     # but we can still import a conftest directly
-    conftest.importconftest(conf)
-    l = conftest.getconftestmodules(conf.dirpath())
+    conftest._importconftest(conf)
+    l = conftest._getconftestmodules(conf.dirpath())
     assert l[0].__file__.startswith(str(conf))
     # and all sub paths get updated properly
-    l = conftest.getconftestmodules(p)
+    l = conftest._getconftestmodules(p)
     assert len(l) == 1
     assert l[0].__file__.startswith(str(conf))
 
 def test_conftestcutdir_inplace_considered(testdir):
     conf = testdir.makeconftest("")
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
-    l = conftest.getconftestmodules(conf.dirpath())
+    l = conftest._getconftestmodules(conf.dirpath())
     assert len(l) == 1
     assert l[0].__file__.startswith(str(conf))
 
@@ -153,7 +142,7 @@
 def test_setinitial_conftest_subdirs(testdir, name):
     sub = testdir.mkdir(name)
     subconftest = sub.ensure("conftest.py")
-    conftest = Conftest()
+    conftest = PytestPluginManager()
     conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
     if name not in ('whatever', '.dotdir'):
         assert  subconftest in conftest._conftestpath2mod
@@ -199,9 +188,9 @@
     ct2.write("")
     def impct(p):
         return p
-    conftest = Conftest()
-    monkeypatch.setattr(conftest, 'importconftest', impct)
-    assert conftest.getconftestmodules(sub) == [ct1, ct2]
+    conftest = PytestPluginManager()
+    monkeypatch.setattr(conftest, '_importconftest', impct)
+    assert conftest._getconftestmodules(sub) == [ct1, ct2]
 
 
 def test_fixture_dependency(testdir, monkeypatch):

diff -r 34ec01b366b95afec4c4928b21e2020389a35bee -r 90f9b67b555f24f26798193206f58930f2ea1306 testing/test_core.py
--- a/testing/test_core.py
+++ b/testing/test_core.py
@@ -3,234 +3,48 @@
 from _pytest.config import get_plugin_manager
 
 
-class TestBootstrapping:
-    def test_consider_env_fails_to_import(self, monkeypatch):
-        pluginmanager = PluginManager()
-        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
-        pytest.raises(ImportError, lambda: pluginmanager.consider_env())
+ at pytest.fixture
+def pm():
+    return PluginManager("he")
 
-    def test_preparse_args(self):
-        pluginmanager = PluginManager()
-        pytest.raises(ImportError, lambda:
-            pluginmanager.consider_preparse(["xyz", "-p", "hello123"]))
+ at pytest.fixture
+def pytestpm():
+    return PytestPluginManager()
 
-    def test_plugin_prevent_register(self):
-        pluginmanager = PluginManager()
-        pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
-        l1 = pluginmanager.getplugins()
-        pluginmanager.register(42, name="abc")
-        l2 = pluginmanager.getplugins()
-        assert len(l2) == len(l1)
 
-    def test_plugin_prevent_register_unregistered_alredy_registered(self):
-        pluginmanager = PluginManager()
-        pluginmanager.register(42, name="abc")
-        l1 = pluginmanager.getplugins()
-        assert 42 in l1
-        pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
-        l2 = pluginmanager.getplugins()
-        assert 42 not in l2
+class TestPluginManager:
+    def test_plugin_double_register(self, pm):
+        pm.register(42, name="abc")
+        with pytest.raises(ValueError):
+            pm.register(42, name="abc")
 
-    def test_plugin_double_register(self):
-        pm = PluginManager()
-        pm.register(42, name="abc")
-        pytest.raises(ValueError, lambda: pm.register(42, name="abc"))
-
-    def test_plugin_skip(self, testdir, monkeypatch):
-        p = testdir.makepyfile(skipping1="""
-            import pytest
-            pytest.skip("hello")
-        """)
-        p.copy(p.dirpath("skipping2.py"))
-        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
-        result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "WI1*skipped plugin*skipping1*hello*",
-            "WI1*skipped plugin*skipping2*hello*",
-        ])
-
-    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
-        pluginmanager = PluginManager()
-        testdir.syspathinsert()
-        testdir.makepyfile(xy123="#")
-        monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
-        l1 = len(pluginmanager.getplugins())
-        pluginmanager.consider_env()
-        l2 = len(pluginmanager.getplugins())
-        assert l2 == l1 + 1
-        assert pluginmanager.getplugin('xy123')
-        pluginmanager.consider_env()
-        l3 = len(pluginmanager.getplugins())
-        assert l2 == l3
-
-    def test_consider_setuptools_instantiation(self, monkeypatch):
-        pkg_resources = pytest.importorskip("pkg_resources")
-        def my_iter(name):
-            assert name == "pytest11"
-            class EntryPoint:
-                name = "pytest_mytestplugin"
-                dist = None
-                def load(self):
-                    class PseudoPlugin:
-                        x = 42
-                    return PseudoPlugin()
-            return iter([EntryPoint()])
-
-        monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
-        pluginmanager = PluginManager()
-        pluginmanager.consider_setuptools_entrypoints()
-        plugin = pluginmanager.getplugin("mytestplugin")
-        assert plugin.x == 42
-
-    def test_consider_setuptools_not_installed(self, monkeypatch):
-        monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
-            py.std.types.ModuleType("pkg_resources"))
-        pluginmanager = PluginManager()
-        pluginmanager.consider_setuptools_entrypoints()
-        # ok, we did not explode
-
-    def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
-        testdir.makepyfile(pytest_x500="#")
-        p = testdir.makepyfile("""
-            import pytest
-            def test_hello(pytestconfig):
-                plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
-                assert plugin is not None
-        """)
-        monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
-        result = testdir.runpytest(p)
-        assert result.ret == 0
-        result.stdout.fnmatch_lines(["*1 passed in*"])
-
-    def test_import_plugin_importname(self, testdir):
-        pluginmanager = PluginManager()
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwx.y")')
-
-        testdir.syspathinsert()
-        pluginname = "pytest_hello"
-        testdir.makepyfile(**{pluginname: ""})
-        pluginmanager.import_plugin("pytest_hello")
-        len1 = len(pluginmanager.getplugins())
-        pluginmanager.import_plugin("pytest_hello")
-        len2 = len(pluginmanager.getplugins())
-        assert len1 == len2
-        plugin1 = pluginmanager.getplugin("pytest_hello")
-        assert plugin1.__name__.endswith('pytest_hello')
-        plugin2 = pluginmanager.getplugin("pytest_hello")
-        assert plugin2 is plugin1
-
-    def test_import_plugin_dotted_name(self, testdir):
-        pluginmanager = PluginManager()
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
-        pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwex.y")')
-
-        testdir.syspathinsert()
-        testdir.mkpydir("pkg").join("plug.py").write("x=3")
-        pluginname = "pkg.plug"
-        pluginmanager.import_plugin(pluginname)
-        mod = pluginmanager.getplugin("pkg.plug")
-        assert mod.x == 3
-
-    def test_consider_module(self, testdir):
-        pluginmanager = PluginManager()
-        testdir.syspathinsert()
-        testdir.makepyfile(pytest_p1="#")
-        testdir.makepyfile(pytest_p2="#")
-        mod = py.std.types.ModuleType("temp")
-        mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
-        pluginmanager.consider_module(mod)
-        assert pluginmanager.getplugin("pytest_p1").__name__ == "pytest_p1"
-        assert pluginmanager.getplugin("pytest_p2").__name__ == "pytest_p2"
-
-    def test_consider_module_import_module(self, testdir):
-        mod = py.std.types.ModuleType("x")
-        mod.pytest_plugins = "pytest_a"
-        aplugin = testdir.makepyfile(pytest_a="#")
-        pluginmanager = get_plugin_manager()
-        reprec = testdir.make_hook_recorder(pluginmanager)
-        #syspath.prepend(aplugin.dirpath())
-        py.std.sys.path.insert(0, str(aplugin.dirpath()))
-        pluginmanager.consider_module(mod)
-        call = reprec.getcall(pluginmanager.hook.pytest_plugin_registered.name)
-        assert call.plugin.__name__ == "pytest_a"
-
-        # check that it is not registered twice
-        pluginmanager.consider_module(mod)
-        l = reprec.getcalls("pytest_plugin_registered")
-        assert len(l) == 1
-
-    def test_config_sets_conftesthandle_onimport(self, testdir):
-        config = testdir.parseconfig([])
-        assert config._conftest._onimport == config._onimportconftest
-
-    def test_consider_conftest_deps(self, testdir):
-        mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
-        pp = PluginManager()
-        pytest.raises(ImportError, lambda: pp.consider_conftest(mod))
-
-    def test_pm(self):
-        pp = PluginManager()
+    def test_pm(self, pm):
         class A: pass
         a1, a2 = A(), A()
-        pp.register(a1)
-        assert pp.isregistered(a1)
-        pp.register(a2, "hello")
-        assert pp.isregistered(a2)
-        l = pp.getplugins()
+        pm.register(a1)
+        assert pm.isregistered(a1)
+        pm.register(a2, "hello")
+        assert pm.isregistered(a2)
+        l = pm.getplugins()
         assert a1 in l
         assert a2 in l
-        assert pp.getplugin('hello') == a2
-        pp.unregister(a1)
-        assert not pp.isregistered(a1)
-
-    def test_pm_ordering(self):
-        pp = PluginManager()
-        class A: pass
-        a1, a2 = A(), A()
-        pp.register(a1)
-        pp.register(a2, "hello")
-        l = pp.getplugins()
-        assert l.index(a1) < l.index(a2)
-        a3 = A()
-        pp.register(a3, prepend=True)
-        l = pp.getplugins()
-        assert l.index(a3) == 0
-
-    def test_register_imported_modules(self):
-        pp = PluginManager()
-        mod = py.std.types.ModuleType("x.y.pytest_hello")
-        pp.register(mod)
-        assert pp.isregistered(mod)
-        l = pp.getplugins()
-        assert mod in l
-        pytest.raises(ValueError, "pp.register(mod)")
-        pytest.raises(ValueError, lambda: pp.register(mod))
-        #assert not pp.isregistered(mod2)
-        assert pp.getplugins() == l
-
-    def test_canonical_import(self, monkeypatch):
-        mod = py.std.types.ModuleType("pytest_xyz")
-        monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
-        pp = PluginManager()
-        pp.import_plugin('pytest_xyz')
-        assert pp.getplugin('pytest_xyz') == mod
-        assert pp.isregistered(mod)
+        assert pm.getplugin('hello') == a2
+        pm.unregister(a1)
+        assert not pm.isregistered(a1)
 
     def test_register_mismatch_method(self):
-        pp = get_plugin_manager()
+        pm = get_plugin_manager()
         class hello:
             def pytest_gurgel(self):
                 pass
-        pytest.raises(Exception, lambda: pp.register(hello()))
+        pytest.raises(Exception, lambda: pm.register(hello()))
 
     def test_register_mismatch_arg(self):
-        pp = get_plugin_manager()
+        pm = get_plugin_manager()
         class hello:
             def pytest_configure(self, asd):
                 pass
-        pytest.raises(Exception, lambda: pp.register(hello()))
+        pytest.raises(Exception, lambda: pm.register(hello()))
 
     def test_register(self):
         pm = get_plugin_manager()
@@ -250,7 +64,7 @@
         assert pm.getplugins()[-1:] == [my2]
 
     def test_listattr(self):
-        plugins = PluginManager()
+        plugins = PluginManager("xyz")
         class api1:
             x = 41
         class api2:
@@ -263,27 +77,6 @@
         l = list(plugins.listattr('x'))
         assert l == [41, 42, 43]
 
-    def test_hook_tracing(self):
-        pm = get_plugin_manager()
-        saveindent = []
-        class api1:
-            x = 41
-            def pytest_plugin_registered(self, plugin):
-                saveindent.append(pm.trace.root.indent)
-                raise ValueError(42)
-        l = []
-        pm.set_tracing(l.append)
-        indent = pm.trace.root.indent
-        p = api1()
-        pm.register(p)
-
-        assert pm.trace.root.indent == indent
-        assert len(l) == 2
-        assert 'pytest_plugin_registered' in l[0]
-        assert 'finish' in l[1]
-        pytest.raises(ValueError, lambda: pm.register(api1()))
-        assert pm.trace.root.indent == indent
-        assert saveindent[0] > indent
 
 class TestPytestPluginInteractions:
 
@@ -301,7 +94,7 @@
                 return xyz + 1
         """)
         config = get_plugin_manager().config
-        config._conftest.importconftest(conf)
+        config.pluginmanager._importconftest(conf)
         print(config.pluginmanager.getplugins())
         res = config.hook.pytest_myhook(xyz=10)
         assert res == [11]
@@ -350,7 +143,7 @@
                 parser.addoption('--test123', action="store_true",
                     default=True)
         """)
-        config._conftest.importconftest(p)
+        config.pluginmanager._importconftest(p)
         assert config.option.test123
 
     def test_configure(self, testdir):
@@ -362,20 +155,43 @@
 
         config.pluginmanager.register(A())
         assert len(l) == 0
-        config.do_configure()
+        config._do_configure()
         assert len(l) == 1
         config.pluginmanager.register(A())  # leads to a configured() plugin
         assert len(l) == 2
         assert l[0] != l[1]
 
-        config.do_unconfigure()
+        config._ensure_unconfigure()
         config.pluginmanager.register(A())
         assert len(l) == 2
 
+    def test_hook_tracing(self):
+        pytestpm = get_plugin_manager()  # fully initialized with plugins
+        saveindent = []
+        class api1:
+            x = 41
+            def pytest_plugin_registered(self, plugin):
+                saveindent.append(pytestpm.trace.root.indent)
+                raise ValueError(42)
+        l = []
+        pytestpm.set_tracing(l.append)
+        indent = pytestpm.trace.root.indent
+        p = api1()
+        pytestpm.register(p)
+
+        assert pytestpm.trace.root.indent == indent
+        assert len(l) == 2
+        assert 'pytest_plugin_registered' in l[0]
+        assert 'finish' in l[1]
+        with pytest.raises(ValueError):
+            pytestpm.register(api1())
+        assert pytestpm.trace.root.indent == indent
+        assert saveindent[0] > indent
+
     # lower level API
 
     def test_listattr(self):
-        pluginmanager = PluginManager()
+        pluginmanager = PluginManager("xyz")
         class My2:
             x = 42
         pluginmanager.register(My2())
@@ -395,7 +211,7 @@
             def m(self):
                 return 19
 
-        pluginmanager = PluginManager()
+        pluginmanager = PluginManager("xyz")
         p1 = P1()
         p2 = P2()
         p3 = P3()
@@ -572,7 +388,7 @@
             def m(self):
                 return 19
 
-        pluginmanager = PluginManager()
+        pluginmanager = PluginManager("xyz")
         p1 = P1()
         p2 = P2()
         p3 = P3()
@@ -624,11 +440,12 @@
 
 
 class TestHookRelay:
-    def test_happypath(self):
+    def test_hapmypath(self):
         class Api:
             def hello(self, arg):
                 "api hook 1"
-        pm = PluginManager([Api], prefix="he")
+        pm = PluginManager("he")
+        pm.addhooks(Api)
         hook = pm.hook
         assert hasattr(hook, 'hello')
         assert repr(hook.hello).find("hello") != -1
@@ -647,7 +464,8 @@
         class Api:
             def hello(self, arg):
                 "api hook 1"
-        pm = PluginManager(Api, prefix="he")
+        pm = PluginManager("he")
+        pm.addhooks(Api)
         class Plugin:
             def hello(self, argwrong):
                 return arg + 1
@@ -656,19 +474,20 @@
         assert "argwrong" in str(exc.value)
 
     def test_only_kwargs(self):
-        pm = PluginManager()
+        pm = PluginManager("he")
         class Api:
             def hello(self, arg):
                 "api hook 1"
-        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
-        pytest.raises(TypeError, lambda: mcm.hello(3))
+        pm.addhooks(Api)
+        pytest.raises(TypeError, lambda: pm.hook.hello(3))
 
     def test_firstresult_definition(self):
         class Api:
             def hello(self, arg):
                 "api hook 1"
             hello.firstresult = True
-        pm = PluginManager([Api], "he")
+        pm = PluginManager("he")
+        pm.addhooks(Api)
         class Plugin:
             def hello(self, arg):
                 return arg + 1
@@ -771,15 +590,16 @@
         "*trylast*last*",
     ])
 
-def test_importplugin_issue375(testdir):
+def test_importplugin_issue375(testdir, pytestpm):
     testdir.syspathinsert(testdir.tmpdir)
     testdir.makepyfile(qwe="import aaaa")
-    excinfo = pytest.raises(ImportError, lambda: importplugin("qwe"))
+    with pytest.raises(ImportError) as excinfo:
+        pytestpm.import_plugin("qwe")
     assert "qwe" not in str(excinfo.value)
     assert "aaaa" in str(excinfo.value)
 
 class TestWrapMethod:
-    def test_basic_happypath(self):
+    def test_basic_hapmypath(self):
         class A:
             def f(self):
                 return "A.f"
@@ -880,3 +700,178 @@
         with pytest.raises(ValueError):
             A().error()
         assert l == [1]
+
+
+### to be shifted to own test file
+from _pytest.config import PytestPluginManager
+
+class TestPytestPluginManager:
+    def test_register_imported_modules(self):
+        pm = PytestPluginManager()
+        mod = py.std.types.ModuleType("x.y.pytest_hello")
+        pm.register(mod)
+        assert pm.isregistered(mod)
+        l = pm.getplugins()
+        assert mod in l
+        pytest.raises(ValueError, "pm.register(mod)")
+        pytest.raises(ValueError, lambda: pm.register(mod))
+        #assert not pm.isregistered(mod2)
+        assert pm.getplugins() == l
+
+    def test_canonical_import(self, monkeypatch):
+        mod = py.std.types.ModuleType("pytest_xyz")
+        monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
+        pm = PytestPluginManager()
+        pm.import_plugin('pytest_xyz')
+        assert pm.getplugin('pytest_xyz') == mod
+        assert pm.isregistered(mod)
+
+    def test_consider_module(self, testdir, pytestpm):
+        testdir.syspathinsert()
+        testdir.makepyfile(pytest_p1="#")
+        testdir.makepyfile(pytest_p2="#")
+        mod = py.std.types.ModuleType("temp")
+        mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
+        pytestpm.consider_module(mod)
+        assert pytestpm.getplugin("pytest_p1").__name__ == "pytest_p1"
+        assert pytestpm.getplugin("pytest_p2").__name__ == "pytest_p2"
+
+    def test_consider_module_import_module(self, testdir):
+        pytestpm = get_plugin_manager()
+        mod = py.std.types.ModuleType("x")
+        mod.pytest_plugins = "pytest_a"
+        aplugin = testdir.makepyfile(pytest_a="#")
+        reprec = testdir.make_hook_recorder(pytestpm)
+        #syspath.prepend(aplugin.dirpath())
+        py.std.sys.path.insert(0, str(aplugin.dirpath()))
+        pytestpm.consider_module(mod)
+        call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
+        assert call.plugin.__name__ == "pytest_a"
+
+        # check that it is not registered twice
+        pytestpm.consider_module(mod)
+        l = reprec.getcalls("pytest_plugin_registered")
+        assert len(l) == 1
+
+    def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
+        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
+        with pytest.raises(ImportError):
+            pytestpm.consider_env()
+
+    def test_plugin_skip(self, testdir, monkeypatch):
+        p = testdir.makepyfile(skipping1="""
+            import pytest
+            pytest.skip("hello")
+        """)
+        p.copy(p.dirpath("skipping2.py"))
+        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
+        result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "WI1*skipped plugin*skipping1*hello*",
+            "WI1*skipped plugin*skipping2*hello*",
+        ])
+
+    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
+        testdir.syspathinsert()
+        testdir.makepyfile(xy123="#")
+        monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
+        l1 = len(pytestpm.getplugins())
+        pytestpm.consider_env()
+        l2 = len(pytestpm.getplugins())
+        assert l2 == l1 + 1
+        assert pytestpm.getplugin('xy123')
+        pytestpm.consider_env()
+        l3 = len(pytestpm.getplugins())
+        assert l2 == l3
+
+    def test_consider_setuptools_instantiation(self, monkeypatch, pytestpm):
+        pkg_resources = pytest.importorskip("pkg_resources")
+        def my_iter(name):
+            assert name == "pytest11"
+            class EntryPoint:
+                name = "pytest_mytestplugin"
+                dist = None
+                def load(self):
+                    class PseudoPlugin:
+                        x = 42
+                    return PseudoPlugin()
+            return iter([EntryPoint()])
+
+        monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+        pytestpm.consider_setuptools_entrypoints()
+        plugin = pytestpm.getplugin("mytestplugin")
+        assert plugin.x == 42
+
+    def test_consider_setuptools_not_installed(self, monkeypatch, pytestpm):
+        monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
+            py.std.types.ModuleType("pkg_resources"))
+        pytestpm.consider_setuptools_entrypoints()
+        # ok, we did not explode
+
+    def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
+        testdir.makepyfile(pytest_x500="#")
+        p = testdir.makepyfile("""
+            import pytest
+            def test_hello(pytestconfig):
+                plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
+                assert plugin is not None
+        """)
+        monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
+        result = testdir.runpytest(p)
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["*1 passed in*"])
+
+    def test_import_plugin_importname(self, testdir, pytestpm):
+        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
+        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
+
+        testdir.syspathinsert()
+        pluginname = "pytest_hello"
+        testdir.makepyfile(**{pluginname: ""})
+        pytestpm.import_plugin("pytest_hello")
+        len1 = len(pytestpm.getplugins())
+        pytestpm.import_plugin("pytest_hello")
+        len2 = len(pytestpm.getplugins())
+        assert len1 == len2
+        plugin1 = pytestpm.getplugin("pytest_hello")
+        assert plugin1.__name__.endswith('pytest_hello')
+        plugin2 = pytestpm.getplugin("pytest_hello")
+        assert plugin2 is plugin1
+
+    def test_import_plugin_dotted_name(self, testdir, pytestpm):
+        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
+        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
+
+        testdir.syspathinsert()
+        testdir.mkpydir("pkg").join("plug.py").write("x=3")
+        pluginname = "pkg.plug"
+        pytestpm.import_plugin(pluginname)
+        mod = pytestpm.getplugin("pkg.plug")
+        assert mod.x == 3
+
+    def test_consider_conftest_deps(self, testdir, pytestpm):
+        mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
+        with pytest.raises(ImportError):
+            pytestpm.consider_conftest(mod)
+
+
+class TestPytestPluginManagerBootstrapming:
+    def test_preparse_args(self, pytestpm):
+        pytest.raises(ImportError, lambda:
+            pytestpm.consider_preparse(["xyz", "-p", "hello123"]))
+
+    def test_plugin_prevent_register(self, pytestpm):
+        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
+        l1 = pytestpm.getplugins()
+        pytestpm.register(42, name="abc")
+        l2 = pytestpm.getplugins()
+        assert len(l2) == len(l1)
+
+    def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
+        pytestpm.register(42, name="abc")
+        l1 = pytestpm.getplugins()
+        assert 42 in l1
+        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
+        l2 = pytestpm.getplugins()
+        assert 42 not in l2

This diff is so big that we needed to truncate the remainder.

Repository URL: https://bitbucket.org/pytest-dev/pytest/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.


More information about the pytest-commit mailing list