[Pytest-commit] commit/pytest: hpk42: Merged in hpk42/pytest-patches/plugin_no_pytest (pull request #278)

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


1 new commit in pytest:

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

Refactor pluginmanagement
Affected #:  20 files

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

--

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


More information about the pytest-commit mailing list