[py-svn] pytest commit 09ceba1e4812: implement and document new invocation mechanisms, see doc/usage.txt

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Sat Nov 6 09:57:01 CET 2010


# HG changeset patch -- Bitbucket.org
# Project pytest
# URL http://bitbucket.org/hpk42/pytest/overview
# User holger krekel <holger at merlinux.eu>
# Date 1288996651 -3600
# Node ID 09ceba1e4812bd91aecb71f2e31d78d663957f60
# Parent  22ce60ec51feb6e4464ba3142c5c008322d74557
implement and document new invocation mechanisms, see doc/usage.txt
also rename pytest._core to pytest.main for convenience.

--- /dev/null
+++ b/pytest/__main__.py
@@ -0,0 +1,4 @@
+import pytest
+
+if __name__ == '__main__':
+    raise SystemExit(pytest.main())

--- a/doc/test/plugin/cov.txt
+++ b/doc/test/plugin/cov.txt
@@ -35,7 +35,7 @@ However easy_install does not provide an
 
 .. IMPORTANT::
 
-    Ensure that you manually delete the init_cov_core.pth file in your site-packages directory.
+    Ensure that you manually delete the init_covmain.pth file in your site-packages directory.
 
     This file starts coverage collection of subprocesses if appropriate during site initialisation
     at python startup.

--- a/pytest/__init__.py
+++ b/pytest/__init__.py
@@ -9,8 +9,6 @@ __version__ = '2.0.0.dev18'
 
 __all__ = ['config', 'cmdline']
 
-from pytest import _core as cmdline
+from pytest import main as cmdline
 UsageError = cmdline.UsageError
-
-def __main__():
-    raise SystemExit(cmdline.main())
+main = cmdline.main

--- a/doc/index.txt
+++ b/doc/index.txt
@@ -3,10 +3,14 @@ py.test: no-boilerplate testing with Pyt
 
 .. todolist::
 
-   
+.. note::
+    version 2.0 introduces ``pytest`` as the main Python import name
+    but for historic reasons ``py.test`` remains fully valid and
+    represents the same package.
+
 Welcome to ``py.test`` documentation:
 
-.. toctree:: 
+.. toctree::
    :maxdepth: 2
 
    overview
@@ -27,4 +31,3 @@ Indices and tables
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-

--- a/doc/cmdline.txt
+++ /dev/null
@@ -1,111 +0,0 @@
-
-.. _cmdline:
-
-Using the interactive command line
-===============================================
-
-Getting help on version, option names, environment vars
------------------------------------------------------------
-
-::
-
-    py.test --version   # shows where pytest was imported from
-    py.test --funcargs  # show available builtin function arguments
-    py.test -h | --help # show help on command line and config file options
-
-
-Stopping after the first (or N) failures
----------------------------------------------------
-
-To stop the testing process after the first (N) failures::
-
-    py.test -x            # stop after first failure
-    py.test -maxfail=2    # stop after two failures
-
-Modifying Python traceback printing
-----------------------------------------------
-
-Examples for modifying traceback printing::
-
-    py.test --showlocals # show local variables in tracebacks
-    py.test -l           # show local variables (shortcut)
-
-    py.test --tb=long    # the default informative traceback formatting
-    py.test --tb=native  # the Python standard library formatting
-    py.test --tb=short   # a shorter traceback format
-    py.test --tb=line    # only one line per failure
-
-Dropping to PDB (Python Debugger) on failures
-----------------------------------------------
-
-.. _PDB: http://docs.python.org/library/pdb.html
-
-Python comes with a builtin Python debugger called PDB_.  ``py.test`` 
-allows to drop into the PDB prompt via a command line option::
-
-    py.test --pdb
-
-This will invoke the Python debugger on every failure.  Often you might
-only want to do this for the first failing test to understand a certain
-failure situation::
-
-    py.test -x --pdb   # drop to PDB on first failure, then end test session
-    py.test --pdb --maxfail=3  # drop to PDB for the first three failures
-
-
-Setting a breakpoint / aka ``set_trace()``
-----------------------------------------------------
-
-If you want to set a breakpoint and enter the ``pdb.set_trace()`` you
-can use a helper::
-
-    def test_function():
-        ...
-        py.test.set_trace()    # invoke PDB debugger and tracing
-
-.. versionadded: 2.0.0
-
-In previous versions you could only enter PDB tracing if
-you :ref:`disable capturing`.
-
-creating JUnitXML format files
-----------------------------------------------------
-
-To create result files which can be read by Hudson_ or other Continous
-integration servers, use this invocation::
-
-    py.test --junitxml=path
-
-to create an XML file at ``path``.
-
-creating resultlog format files
-----------------------------------------------------
-
-To create plain-text machine-readable result files you can issue::
-
-    py.test --resultlog=path
-
-and look at the content at the ``path`` location.  Such files are used e.g.
-by the `PyPy-test`_ web page to show test results over several revisions.
-
-.. _`PyPy-test`: http://codespeak.net:8099/summary
-
-
-send test report to pocoo pastebin service
------------------------------------------------------
-
-**Creating a URL for each test failure**::
-
-    py.test --pastebin=failed
-
-This will submit test run information to a remote Paste service and
-provide a URL for each failure.  You may select tests as usual or add
-for example ``-x`` if you only want to send one particular failure.
-
-**Creating a URL for a whole test session log**::
-
-    py.test --pastebin=all
-
-Currently only pasting to the http://paste.pocoo.org service is implemented.
-
-.. include:: links.inc

--- a/pytest/_core.py
+++ /dev/null
@@ -1,397 +0,0 @@
-import sys, os
-import inspect
-import py
-from pytest import hookspec
-
-assert py.__version__.split(".")[:2] >= ['2', '0'], ("installation problem: "
-    "%s is too old, remove or upgrade 'py'" % (py.__version__))
-
-default_plugins = (
- "config session terminal runner python pdb capture unittest mark skipping "
- "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
- "junitxml doctest").split()
-
-IMPORTPREFIX = "pytest_"
-
-class TagTracer:
-    def __init__(self):
-        self._tag2proc = {}
-        self.writer = None
-
-    def get(self, name):
-        return TagTracerSub(self, (name,))
-
-    def processmessage(self, tags, args):
-        if self.writer is not None:
-            prefix = ":".join(tags)
-            content = " ".join(map(str, args))
-            self.writer("[%s] %s\n" %(prefix, content))
-        try:
-            self._tag2proc[tags](tags, args)
-        except KeyError:
-            pass
-
-    def setwriter(self, writer):
-        self.writer = writer
-
-    def setprocessor(self, tags, processor):
-        if isinstance(tags, str):
-            tags = tuple(tags.split(":"))
-        else:
-            assert isinstance(tags, tuple)
-        self._tag2proc[tags] = processor
-
-class TagTracerSub:
-    def __init__(self, root, tags):
-        self.root = root
-        self.tags = tags
-    def __call__(self, *args):
-        self.root.processmessage(self.tags, args)
-    def setmyprocessor(self, processor):
-        self.root.setprocessor(self.tags, processor)
-    def get(self, name):
-        return self.__class__(self.root, self.tags + (name,))
-
-class PluginManager(object):
-    def __init__(self, load=False):
-        self._name2plugin = {}
-        self._plugins = []
-        self._hints = []
-        self.trace = TagTracer().get("pytest")
-        if os.environ.get('PYTEST_DEBUG'):
-            self.trace.root.setwriter(sys.stderr.write)
-        self.hook = HookRelay([hookspec], pm=self)
-        self.register(self)
-        if load:
-            for spec in default_plugins:
-                self.import_plugin(spec)
-
-    def _getpluginname(self, plugin, name):
-        if name is None:
-            if hasattr(plugin, '__name__'):
-                name = plugin.__name__.split(".")[-1]
-            else:
-                name = id(plugin)
-        return name
-
-    def register(self, plugin, name=None, prepend=False):
-        assert not self.isregistered(plugin), plugin
-        assert not self.isregistered(plugin), plugin
-        name = self._getpluginname(plugin, name)
-        if name in self._name2plugin:
-            return False
-        self._name2plugin[name] = plugin
-        self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
-        self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
-        self.trace("registered", plugin)
-        if not prepend:
-            self._plugins.append(plugin)
-        else:
-            self._plugins.insert(0, plugin)
-        return True
-
-    def unregister(self, plugin=None, name=None):
-        if plugin is None:
-            plugin = self.getplugin(name=name)
-        self._plugins.remove(plugin)
-        self.hook.pytest_plugin_unregistered(plugin=plugin)
-        for name, value in list(self._name2plugin.items()):
-            if value == plugin:
-                del self._name2plugin[name]
-
-    def isregistered(self, plugin, name=None):
-        if self._getpluginname(plugin, name) in self._name2plugin:
-            return True
-        for val in self._name2plugin.values():
-            if plugin == val:
-                return True
-
-    def addhooks(self, spec):
-        self.hook._addhooks(spec, prefix="pytest_")
-
-    def getplugins(self):
-        return list(self._plugins)
-
-    def skipifmissing(self, name):
-        if not self.hasplugin(name):
-            py.test.skip("plugin %r is missing" % name)
-
-    def hasplugin(self, name):
-        try:
-            self.getplugin(name)
-            return True
-        except KeyError:
-            return False
-
-    def getplugin(self, name):
-        try:
-            return self._name2plugin[name]
-        except KeyError:
-            impname = canonical_importname(name)
-            return self._name2plugin[impname]
-
-    # API for bootstrapping
-    #
-    def _envlist(self, varname):
-        val = py.std.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
-        except ImportError:
-            return # XXX issue a warning
-        for ep in iter_entry_points('pytest11'):
-            name = canonical_importname(ep.name)
-            if name in self._name2plugin:
-                continue
-            plugin = ep.load()
-            self.register(plugin, name=name)
-
-    def consider_preparse(self, args):
-        for opt1,opt2 in zip(args, args[1:]):
-            if opt1 == "-p":
-                self.import_plugin(opt2)
-
-    def consider_conftest(self, conftestmodule):
-        if self.register(conftestmodule, name=conftestmodule.__file__):
-            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, spec):
-        assert isinstance(spec, str)
-        modname = canonical_importname(spec)
-        if modname in self._name2plugin:
-            return
-        try:
-            mod = importplugin(modname)
-        except KeyboardInterrupt:
-            raise
-        except:
-            e = py.std.sys.exc_info()[1]
-            if not hasattr(py.test, 'skip'):
-                raise
-            elif not isinstance(e, py.test.skip.Exception):
-                raise
-            self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
-        else:
-            self.register(mod, modname)
-            self.consider_module(mod)
-
-    def pytest_plugin_registered(self, plugin):
-        dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
-        if dic:
-            self._setns(py.test, dic)
-        if hasattr(self, '_config'):
-            self.call_plugin(plugin, "pytest_addoption",
-                {'parser': self._config._parser})
-            self.call_plugin(plugin, "pytest_configure",
-                {'config': self._config})
-
-    def _setns(self, obj, dic):
-        for name, value in dic.items():
-            if isinstance(value, dict):
-                mod = getattr(obj, name, None)
-                if mod is None:
-                    mod = py.std.types.ModuleType(name)
-                    sys.modules['pytest.%s' % name] = mod
-                    sys.modules['py.test.%s' % name] = mod
-                    mod.__all__ = []
-                    setattr(obj, name, mod)
-                self._setns(mod, value)
-            else:
-                #print "setting", name, value, "on", obj
-                setattr(obj, name, value)
-                obj.__all__.append(name)
-
-    def pytest_terminal_summary(self, terminalreporter):
-        tw = terminalreporter._tw
-        if terminalreporter.config.option.traceconfig:
-            for hint in self._hints:
-                tw.line("hint: %s" % hint)
-
-    def do_configure(self, config):
-        assert not hasattr(self, '_config')
-        self._config = config
-        config.hook.pytest_configure(config=self._config)
-
-    def do_unconfigure(self, config):
-        config = self._config
-        del self._config
-        config.hook.pytest_unconfigure(config=config)
-        config.pluginmanager.unregister(self)
-
-    def notify_exception(self, excinfo):
-        excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
-        res = self.hook.pytest_internalerror(excrepr=excrepr)
-        if not py.builtin.any(res):
-            for line in str(excrepr).split("\n"):
-                sys.stderr.write("INTERNALERROR> %s\n" %line)
-                sys.stderr.flush()
-
-    def listattr(self, attrname, plugins=None):
-        if plugins is None:
-            plugins = self._plugins
-        l = []
-        for plugin in plugins:
-            try:
-                l.append(getattr(plugin, attrname))
-            except AttributeError:
-                continue
-        return l
-
-    def call_plugin(self, plugin, methname, kwargs):
-        return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
-                kwargs=kwargs, firstresult=True).execute()
-
-def canonical_importname(name):
-    if '.' in name:
-        return name
-    name = name.lower()
-    if not name.startswith(IMPORTPREFIX):
-        name = IMPORTPREFIX + name
-    return name
-
-def importplugin(importspec):
-    try:
-        return __import__(importspec, None, None, '__doc__')
-    except ImportError:
-        e = py.std.sys.exc_info()[1]
-        if str(e).find(importspec) == -1:
-            raise
-        name = importspec
-        try:
-            if name.startswith("pytest_"):
-                name = importspec[7:]
-            return __import__("pytest.plugin.%s" %(name), None, None, '__doc__')
-        except ImportError:
-            e = py.std.sys.exc_info()[1]
-            if str(e).find(name) == -1:
-                raise
-            # show the original exception, not the failing internal one
-            return __import__(importspec, None, None, '__doc__')
-
-
-class MultiCall:
-    """ execute a call into multiple python functions/methods. """
-    def __init__(self, methods, kwargs, firstresult=False):
-        self.methods = list(methods)
-        self.kwargs = kwargs
-        self.results = []
-        self.firstresult = firstresult
-
-    def __repr__(self):
-        status = "%d results, %d meths" % (len(self.results), len(self.methods))
-        return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
-
-    def execute(self):
-        while self.methods:
-            method = self.methods.pop()
-            kwargs = self.getkwargs(method)
-            res = method(**kwargs)
-            if res is not None:
-                self.results.append(res)
-                if self.firstresult:
-                    return res
-        if not self.firstresult:
-            return self.results
-
-    def getkwargs(self, method):
-        kwargs = {}
-        for argname in varnames(method):
-            try:
-                kwargs[argname] = self.kwargs[argname]
-            except KeyError:
-                if argname == "__multicall__":
-                    kwargs[argname] = self
-        return kwargs
-
-def varnames(func):
-    if not inspect.isfunction(func) and not inspect.ismethod(func):
-        func = getattr(func, '__call__', func)
-    ismethod = inspect.ismethod(func)
-    rawcode = py.code.getrawcode(func)
-    try:
-        return rawcode.co_varnames[ismethod:rawcode.co_argcount]
-    except AttributeError:
-        return ()
-
-class HookRelay:
-    def __init__(self, hookspecs, pm, prefix="pytest_"):
-        if not isinstance(hookspecs, list):
-            hookspecs = [hookspecs]
-        self._hookspecs = []
-        self._pm = pm
-        for hookspec in hookspecs:
-            self._addhooks(hookspec, prefix)
-
-    def _addhooks(self, hookspecs, prefix):
-        self._hookspecs.append(hookspecs)
-        added = False
-        for name, method in vars(hookspecs).items():
-            if name.startswith(prefix):
-                if not method.__doc__:
-                    raise ValueError("docstring required for hook %r, in %r"
-                        % (method, hookspecs))
-                firstresult = getattr(method, 'firstresult', False)
-                hc = HookCaller(self, name, firstresult=firstresult)
-                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, hookspecs,))
-
-
-class HookCaller:
-    def __init__(self, hookrelay, name, firstresult):
-        self.hookrelay = hookrelay
-        self.name = name
-        self.firstresult = firstresult
-
-    def __repr__(self):
-        return "<HookCaller %r>" %(self.name,)
-
-    def __call__(self, **kwargs):
-        methods = self.hookrelay._pm.listattr(self.name)
-        mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
-        return mc.execute()
-
-    def pcall(self, plugins, **kwargs):
-        methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
-        mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
-        return mc.execute()
-
-pluginmanager = PluginManager(load=True) # will trigger default plugin importing
-
-def main(args=None):
-    global pluginmanager
-    if args is None:
-        args = sys.argv[1:]
-    hook = pluginmanager.hook
-    try:
-        config = hook.pytest_cmdline_parse(
-                pluginmanager=pluginmanager, args=args)
-        exitstatus = hook.pytest_cmdline_main(config=config)
-    except UsageError:
-        e = sys.exc_info()[1]
-        sys.stderr.write("ERROR: %s\n" %(e.args[0],))
-        exitstatus = 3
-    pluginmanager = PluginManager(load=True)
-    return exitstatus
-
-class UsageError(Exception):
-    """ error in py.test usage or invocation"""

--- a/pytest/plugin/config.py
+++ b/pytest/plugin/config.py
@@ -1,7 +1,7 @@
 
 import py
 import sys, os
-from pytest._core import PluginManager
+from pytest.main import PluginManager
 import pytest
 
 

--- a/doc/goodpractises.txt
+++ b/doc/goodpractises.txt
@@ -76,16 +76,21 @@ You can always run your tests by pointin
     ...
 
 .. _`package name`:
- 
-.. note:: 
+
+.. note::
 
     Test modules are imported under their fully qualified name as follows:
 
-    * ``basedir`` = first upward directory not containing an ``__init__.py``
+    * find ``basedir`` -- this is the first "upward" directory not
+      containing an ``__init__.py``
 
-    * perform ``sys.path.insert(0, basedir)``.
+    * perform ``sys.path.insert(0, basedir)`` to make the fully
+      qualified test module path importable.
 
-    * ``import path.to.test_module``
+    * ``import path.to.test_module`` where the path is determined
+      by converting path separators into "." files.  This means
+      you must follow the convention of having directory and file
+      names map to the import names.
 
 .. _standalone:
 .. _`genscript method`:
@@ -94,7 +99,7 @@ Generating a py.test standalone Script
 -------------------------------------------
 
 If you are a maintainer or application developer and want others
-to easily run tests you can generate a completely standalone "py.test" 
+to easily run tests you can generate a completely standalone "py.test"
 script::
 
     py.test --genscript=runtests.py

--- a/setup.py
+++ b/setup.py
@@ -46,7 +46,7 @@ def main():
     )
 
 def cmdline_entrypoints(versioninfo, platform, basename):
-    target = 'pytest:__main__'
+    target = 'pytest:main'
     if platform.startswith('java'):
         points = {'py.test-jython': target}
     else:

--- a/testing/test_pluginmanager.py
+++ /dev/null
@@ -1,604 +0,0 @@
-import py, os
-from pytest._core import PluginManager, canonical_importname
-from pytest._core import MultiCall, HookRelay, varnames
-
-
-class TestBootstrapping:
-    def test_consider_env_fails_to_import(self, monkeypatch):
-        pluginmanager = PluginManager()
-        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
-        py.test.raises(ImportError, "pluginmanager.consider_env()")
-
-    def test_preparse_args(self):
-        pluginmanager = PluginManager()
-        py.test.raises(ImportError, """
-            pluginmanager.consider_preparse(["xyz", "-p", "hello123"])
-        """)
-
-    def test_plugin_skip(self, testdir, monkeypatch):
-        p = testdir.makepyfile(pytest_skipping1="""
-            import py
-            py.test.skip("hello")
-        """)
-        p.copy(p.dirpath("pytest_skipping2.py"))
-        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
-        result = testdir.runpytest("-p", "skipping1", "--traceconfig")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*hint*skipping2*hello*",
-            "*hint*skipping1*hello*",
-        ])
-
-    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
-        pluginmanager = PluginManager()
-        testdir.syspathinsert()
-        testdir.makepyfile(pytest_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('pytest_xy123')
-        pluginmanager.consider_env()
-        l3 = len(pluginmanager.getplugins())
-        assert l2 == l3
-
-    def test_consider_setuptools_instantiation(self, monkeypatch):
-        pkg_resources = py.test.importorskip("pkg_resources")
-        def my_iter(name):
-            assert name == "pytest11"
-            class EntryPoint:
-                name = "mytestplugin"
-                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
-        plugin2 = pluginmanager.getplugin("pytest_mytestplugin")
-        assert plugin2 == plugin
-
-    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):
-        x500 = testdir.makepyfile(pytest_x500="#")
-        p = testdir.makepyfile("""
-            import py
-            def test_hello(pytestconfig):
-                plugin = pytestconfig.pluginmanager.getplugin('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()
-        py.test.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
-        py.test.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwx.y")')
-
-        reset = testdir.syspathinsert()
-        pluginname = "pytest_hello"
-        testdir.makepyfile(**{pluginname: ""})
-        pluginmanager.import_plugin("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("hello")
-        assert plugin2 is plugin1
-
-    def test_import_plugin_dotted_name(self, testdir):
-        pluginmanager = PluginManager()
-        py.test.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
-        py.test.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwex.y")')
-
-        reset = 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_plug1="#")
-        testdir.makepyfile(pytest_plug2="#")
-        mod = py.std.types.ModuleType("temp")
-        mod.pytest_plugins = ["pytest_plug1", "pytest_plug2"]
-        pluginmanager.consider_module(mod)
-        assert pluginmanager.getplugin("plug1").__name__ == "pytest_plug1"
-        assert pluginmanager.getplugin("plug2").__name__ == "pytest_plug2"
-
-    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 = PluginManager()
-        reprec = testdir.getreportrecorder(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()
-        py.test.raises(ImportError, "pp.consider_conftest(mod)")
-
-    def test_pm(self):
-        pp = PluginManager()
-        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()
-        assert a1 in l
-        assert a2 in l
-        assert pp.getplugin('hello') == a2
-        pp.unregister(a1)
-        assert not pp.isregistered(a1)
-        pp.unregister(name="hello")
-        assert not pp.isregistered(a2)
-
-    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
-        py.test.raises(AssertionError, "pp.register(mod)")
-        mod2 = py.std.types.ModuleType("pytest_hello")
-        #pp.register(mod2) # double pm
-        py.test.raises(AssertionError, "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('xyz')
-        assert pp.getplugin('xyz') == mod
-        assert pp.getplugin('pytest_xyz') == mod
-        assert pp.isregistered(mod)
-
-    def test_register_mismatch_method(self):
-        pp = PluginManager(load=True)
-        class hello:
-            def pytest_gurgel(self):
-                pass
-        py.test.raises(Exception, "pp.register(hello())")
-
-    def test_register_mismatch_arg(self):
-        pp = PluginManager(load=True)
-        class hello:
-            def pytest_configure(self, asd):
-                pass
-        excinfo = py.test.raises(Exception, "pp.register(hello())")
-
-    def test_canonical_importname(self):
-        for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
-            impname = canonical_importname(name)
-
-    def test_notify_exception(self, capfd):
-        pp = PluginManager()
-        excinfo = py.test.raises(ValueError, "raise ValueError(1)")
-        pp.notify_exception(excinfo)
-        out, err = capfd.readouterr()
-        assert "ValueError" in err
-        class A:
-            def pytest_internalerror(self, excrepr):
-                return True
-        pp.register(A())
-        pp.notify_exception(excinfo)
-        out, err = capfd.readouterr()
-        assert not err
-
-    def test_register(self):
-        pm = PluginManager(load=False)
-        class MyPlugin:
-            pass
-        my = MyPlugin()
-        pm.register(my)
-        assert pm.getplugins()
-        my2 = MyPlugin()
-        pm.register(my2)
-        assert pm.getplugins()[1:] == [my, my2]
-
-        assert pm.isregistered(my)
-        assert pm.isregistered(my2)
-        pm.unregister(my)
-        assert not pm.isregistered(my)
-        assert pm.getplugins()[1:] == [my2]
-
-    def test_listattr(self):
-        plugins = PluginManager()
-        class api1:
-            x = 41
-        class api2:
-            x = 42
-        class api3:
-            x = 43
-        plugins.register(api1())
-        plugins.register(api2())
-        plugins.register(api3())
-        l = list(plugins.listattr('x'))
-        assert l == [41, 42, 43]
-
-    def test_register_trace(self):
-        pm = PluginManager()
-        class api1:
-            x = 41
-        l = []
-        pm.trace.setmyprocessor(lambda kw, args: l.append((kw, args)))
-        p = api1()
-        pm.register(p)
-        assert len(l) == 1
-        kw, args = l[0]
-        assert args[0] == "registered"
-        assert args[1] == p
-
-class TestPytestPluginInteractions:
-
-    def test_addhooks_conftestplugin(self, testdir):
-        newhooks = testdir.makepyfile(newhooks="""
-            def pytest_myhook(xyz):
-                "new hook"
-        """)
-        conf = testdir.makeconftest("""
-            import sys ; sys.path.insert(0, '.')
-            import newhooks
-            def pytest_addhooks(pluginmanager):
-                pluginmanager.addhooks(newhooks)
-            def pytest_myhook(xyz):
-                return xyz + 1
-        """)
-        config = testdir.Config()
-        config._conftest.importconftest(conf)
-        print(config.pluginmanager.getplugins())
-        res = config.hook.pytest_myhook(xyz=10)
-        assert res == [11]
-
-    def test_addhooks_docstring_error(self, testdir):
-        newhooks = testdir.makepyfile(newhooks="""
-            class A: # no pytest_ prefix
-                pass
-            def pytest_myhook(xyz):
-                pass
-        """)
-        conf = testdir.makeconftest("""
-            import sys ; sys.path.insert(0, '.')
-            import newhooks
-            def pytest_addhooks(pluginmanager):
-                pluginmanager.addhooks(newhooks)
-        """)
-        res = testdir.runpytest()
-        assert res.ret != 0
-        res.stderr.fnmatch_lines([
-            "*docstring*pytest_myhook*newhooks*"
-        ])
-
-    def test_addhooks_nohooks(self, testdir):
-        conf = testdir.makeconftest("""
-            import sys
-            def pytest_addhooks(pluginmanager):
-                pluginmanager.addhooks(sys)
-        """)
-        res = testdir.runpytest()
-        assert res.ret != 0
-        res.stderr.fnmatch_lines([
-            "*did not find*sys*"
-        ])
-
-    def test_do_option_conftestplugin(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_addoption(parser):
-                parser.addoption('--test123', action="store_true")
-        """)
-        config = testdir.Config()
-        config._conftest.importconftest(p)
-        print(config.pluginmanager.getplugins())
-        config.parse([])
-        assert not config.option.test123
-
-    def test_namespace_early_from_import(self, testdir):
-        p = testdir.makepyfile("""
-            from py.test.collect import Item
-            from pytest.collect import Item as Item2
-            assert Item is Item2
-        """)
-        result = testdir.runpython(p)
-        assert result.ret == 0
-
-    def test_do_ext_namespace(self, testdir):
-        testdir.makeconftest("""
-            def pytest_namespace():
-                return {'hello': 'world'}
-        """)
-        p = testdir.makepyfile("""
-            from py.test import hello
-            import py
-            def test_hello():
-                assert hello == "world"
-                assert 'hello' in py.test.__all__
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*1 passed*"
-        ])
-
-    def test_do_option_postinitialize(self, testdir):
-        config = testdir.Config()
-        config.parse([])
-        config.pluginmanager.do_configure(config=config)
-        assert not hasattr(config.option, 'test123')
-        p = testdir.makepyfile("""
-            def pytest_addoption(parser):
-                parser.addoption('--test123', action="store_true",
-                    default=True)
-        """)
-        config._conftest.importconftest(p)
-        assert config.option.test123
-
-    def test_configure(self, testdir):
-        config = testdir.parseconfig()
-        l = []
-        class A:
-            def pytest_configure(self, config):
-                l.append(self)
-
-        config.pluginmanager.register(A())
-        assert len(l) == 0
-        config.pluginmanager.do_configure(config=config)
-        assert len(l) == 1
-        config.pluginmanager.register(A())  # this should lead to a configured() plugin
-        assert len(l) == 2
-        assert l[0] != l[1]
-
-        config.pluginmanager.do_unconfigure(config=config)
-        config.pluginmanager.register(A())
-        assert len(l) == 2
-
-    # lower level API
-
-    def test_listattr(self):
-        pluginmanager = PluginManager()
-        class My2:
-            x = 42
-        pluginmanager.register(My2())
-        assert not pluginmanager.listattr("hello")
-        assert pluginmanager.listattr("x") == [42]
-
-def test_namespace_has_default_and_env_plugins(testdir):
-    p = testdir.makepyfile("""
-        import py
-        py.test.mark
-    """)
-    result = testdir.runpython(p)
-    assert result.ret == 0
-
-def test_varnames():
-    def f(x):
-        i = 3
-    class A:
-        def f(self, y):
-            pass
-    class B(object):
-        def __call__(self, z):
-            pass
-    assert varnames(f) == ("x",)
-    assert varnames(A().f) == ('y',)
-    assert varnames(B()) == ('z',)
-
-class TestMultiCall:
-    def test_uses_copy_of_methods(self):
-        l = [lambda: 42]
-        mc = MultiCall(l, {})
-        repr(mc)
-        l[:] = []
-        res = mc.execute()
-        return res == 42
-
-    def test_call_passing(self):
-        class P1:
-            def m(self, __multicall__, x):
-                assert len(__multicall__.results) == 1
-                assert not __multicall__.methods
-                return 17
-
-        class P2:
-            def m(self, __multicall__, x):
-                assert __multicall__.results == []
-                assert __multicall__.methods
-                return 23
-
-        p1 = P1()
-        p2 = P2()
-        multicall = MultiCall([p1.m, p2.m], {'x': 23})
-        assert "23" in repr(multicall)
-        reslist = multicall.execute()
-        assert len(reslist) == 2
-        # ensure reversed order
-        assert reslist == [23, 17]
-
-    def test_keyword_args(self):
-        def f(x):
-            return x + 1
-        class A:
-            def f(self, x, y):
-                return x + y
-        multicall = MultiCall([f, A().f], dict(x=23, y=24))
-        assert "'x': 23" in repr(multicall)
-        assert "'y': 24" in repr(multicall)
-        reslist = multicall.execute()
-        assert reslist == [24+23, 24]
-        assert "2 results" in repr(multicall)
-
-    def test_keyword_args_with_defaultargs(self):
-        def f(x, z=1):
-            return x + z
-        reslist = MultiCall([f], dict(x=23, y=24)).execute()
-        assert reslist == [24]
-        reslist = MultiCall([f], dict(x=23, z=2)).execute()
-        assert reslist == [25]
-
-    def test_tags_call_error(self):
-        multicall = MultiCall([lambda x: x], {})
-        py.test.raises(TypeError, "multicall.execute()")
-
-    def test_call_subexecute(self):
-        def m(__multicall__):
-            subresult = __multicall__.execute()
-            return subresult + 1
-
-        def n():
-            return 1
-
-        call = MultiCall([n, m], {}, firstresult=True)
-        res = call.execute()
-        assert res == 2
-
-    def test_call_none_is_no_result(self):
-        def m1():
-            return 1
-        def m2():
-            return None
-        res = MultiCall([m1, m2], {}, firstresult=True).execute()
-        assert res == 1
-        res = MultiCall([m1, m2], {}).execute()
-        assert res == [1]
-
-class TestHookRelay:
-    def test_happypath(self):
-        pm = PluginManager()
-        class Api:
-            def hello(self, arg):
-                "api hook 1"
-
-        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
-        assert hasattr(mcm, 'hello')
-        assert repr(mcm.hello).find("hello") != -1
-        class Plugin:
-            def hello(self, arg):
-                return arg + 1
-        pm.register(Plugin())
-        l = mcm.hello(arg=3)
-        assert l == [4]
-        assert not hasattr(mcm, 'world')
-
-    def test_only_kwargs(self):
-        pm = PluginManager()
-        class Api:
-            def hello(self, arg):
-                "api hook 1"
-        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
-        py.test.raises(TypeError, "mcm.hello(3)")
-
-    def test_firstresult_definition(self):
-        pm = PluginManager()
-        class Api:
-            def hello(self, arg):
-                "api hook 1"
-            hello.firstresult = True
-
-        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
-        class Plugin:
-            def hello(self, arg):
-                return arg + 1
-        pm.register(Plugin())
-        res = mcm.hello(arg=3)
-        assert res == 4
-
-class TestTracer:
-    def test_simple(self):
-        from pytest._core import TagTracer
-        rootlogger = TagTracer()
-        log = rootlogger.get("pytest")
-        log("hello")
-        l = []
-        rootlogger.setwriter(l.append)
-        log("world")
-        assert len(l) == 1
-        assert l[0] == "[pytest] world\n"
-        sublog = log.get("collection")
-        sublog("hello")
-        assert l[1] == "[pytest:collection] hello\n"
-
-    def test_setprocessor(self):
-        from pytest._core import TagTracer
-        rootlogger = TagTracer()
-        log = rootlogger.get("1")
-        log2 = log.get("2")
-        assert log2.tags  == tuple("12")
-        l = []
-        rootlogger.setprocessor(tuple("12"), lambda *args: l.append(args))
-        log("not seen")
-        log2("seen")
-        assert len(l) == 1
-        tags, args = l[0]
-        assert "1" in tags
-        assert "2" in tags
-        assert args == ("seen",)
-        l2 = []
-        rootlogger.setprocessor("1:2", lambda *args: l2.append(args))
-        log2("seen")
-        tags, args = l2[0]
-        assert args == ("seen",)
-        
-
-    def test_setmyprocessor(self):
-        from pytest._core import TagTracer
-        rootlogger = TagTracer()
-        log = rootlogger.get("1")
-        log2 = log.get("2")
-        l = []
-        log2.setmyprocessor(lambda *args: l.append(args))
-        log("not seen")
-        assert not l
-        log2(42)
-        assert len(l) == 1
-        tags, args = l[0]
-        assert "1" in tags
-        assert "2" in tags
-        assert args == (42,)

--- /dev/null
+++ b/testing/test_main.py
@@ -0,0 +1,604 @@
+import py, os
+from pytest.main import PluginManager, canonical_importname
+from pytest.main import MultiCall, HookRelay, varnames
+
+
+class TestBootstrapping:
+    def test_consider_env_fails_to_import(self, monkeypatch):
+        pluginmanager = PluginManager()
+        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
+        py.test.raises(ImportError, "pluginmanager.consider_env()")
+
+    def test_preparse_args(self):
+        pluginmanager = PluginManager()
+        py.test.raises(ImportError, """
+            pluginmanager.consider_preparse(["xyz", "-p", "hello123"])
+        """)
+
+    def test_plugin_skip(self, testdir, monkeypatch):
+        p = testdir.makepyfile(pytest_skipping1="""
+            import py
+            py.test.skip("hello")
+        """)
+        p.copy(p.dirpath("pytest_skipping2.py"))
+        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
+        result = testdir.runpytest("-p", "skipping1", "--traceconfig")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*hint*skipping2*hello*",
+            "*hint*skipping1*hello*",
+        ])
+
+    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
+        pluginmanager = PluginManager()
+        testdir.syspathinsert()
+        testdir.makepyfile(pytest_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('pytest_xy123')
+        pluginmanager.consider_env()
+        l3 = len(pluginmanager.getplugins())
+        assert l2 == l3
+
+    def test_consider_setuptools_instantiation(self, monkeypatch):
+        pkg_resources = py.test.importorskip("pkg_resources")
+        def my_iter(name):
+            assert name == "pytest11"
+            class EntryPoint:
+                name = "mytestplugin"
+                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
+        plugin2 = pluginmanager.getplugin("pytest_mytestplugin")
+        assert plugin2 == plugin
+
+    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):
+        x500 = testdir.makepyfile(pytest_x500="#")
+        p = testdir.makepyfile("""
+            import py
+            def test_hello(pytestconfig):
+                plugin = pytestconfig.pluginmanager.getplugin('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()
+        py.test.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
+        py.test.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwx.y")')
+
+        reset = testdir.syspathinsert()
+        pluginname = "pytest_hello"
+        testdir.makepyfile(**{pluginname: ""})
+        pluginmanager.import_plugin("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("hello")
+        assert plugin2 is plugin1
+
+    def test_import_plugin_dotted_name(self, testdir):
+        pluginmanager = PluginManager()
+        py.test.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
+        py.test.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwex.y")')
+
+        reset = 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_plug1="#")
+        testdir.makepyfile(pytest_plug2="#")
+        mod = py.std.types.ModuleType("temp")
+        mod.pytest_plugins = ["pytest_plug1", "pytest_plug2"]
+        pluginmanager.consider_module(mod)
+        assert pluginmanager.getplugin("plug1").__name__ == "pytest_plug1"
+        assert pluginmanager.getplugin("plug2").__name__ == "pytest_plug2"
+
+    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 = PluginManager()
+        reprec = testdir.getreportrecorder(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()
+        py.test.raises(ImportError, "pp.consider_conftest(mod)")
+
+    def test_pm(self):
+        pp = PluginManager()
+        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()
+        assert a1 in l
+        assert a2 in l
+        assert pp.getplugin('hello') == a2
+        pp.unregister(a1)
+        assert not pp.isregistered(a1)
+        pp.unregister(name="hello")
+        assert not pp.isregistered(a2)
+
+    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
+        py.test.raises(AssertionError, "pp.register(mod)")
+        mod2 = py.std.types.ModuleType("pytest_hello")
+        #pp.register(mod2) # double pm
+        py.test.raises(AssertionError, "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('xyz')
+        assert pp.getplugin('xyz') == mod
+        assert pp.getplugin('pytest_xyz') == mod
+        assert pp.isregistered(mod)
+
+    def test_register_mismatch_method(self):
+        pp = PluginManager(load=True)
+        class hello:
+            def pytest_gurgel(self):
+                pass
+        py.test.raises(Exception, "pp.register(hello())")
+
+    def test_register_mismatch_arg(self):
+        pp = PluginManager(load=True)
+        class hello:
+            def pytest_configure(self, asd):
+                pass
+        excinfo = py.test.raises(Exception, "pp.register(hello())")
+
+    def test_canonical_importname(self):
+        for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
+            impname = canonical_importname(name)
+
+    def test_notify_exception(self, capfd):
+        pp = PluginManager()
+        excinfo = py.test.raises(ValueError, "raise ValueError(1)")
+        pp.notify_exception(excinfo)
+        out, err = capfd.readouterr()
+        assert "ValueError" in err
+        class A:
+            def pytest_internalerror(self, excrepr):
+                return True
+        pp.register(A())
+        pp.notify_exception(excinfo)
+        out, err = capfd.readouterr()
+        assert not err
+
+    def test_register(self):
+        pm = PluginManager(load=False)
+        class MyPlugin:
+            pass
+        my = MyPlugin()
+        pm.register(my)
+        assert pm.getplugins()
+        my2 = MyPlugin()
+        pm.register(my2)
+        assert pm.getplugins()[1:] == [my, my2]
+
+        assert pm.isregistered(my)
+        assert pm.isregistered(my2)
+        pm.unregister(my)
+        assert not pm.isregistered(my)
+        assert pm.getplugins()[1:] == [my2]
+
+    def test_listattr(self):
+        plugins = PluginManager()
+        class api1:
+            x = 41
+        class api2:
+            x = 42
+        class api3:
+            x = 43
+        plugins.register(api1())
+        plugins.register(api2())
+        plugins.register(api3())
+        l = list(plugins.listattr('x'))
+        assert l == [41, 42, 43]
+
+    def test_register_trace(self):
+        pm = PluginManager()
+        class api1:
+            x = 41
+        l = []
+        pm.trace.setmyprocessor(lambda kw, args: l.append((kw, args)))
+        p = api1()
+        pm.register(p)
+        assert len(l) == 1
+        kw, args = l[0]
+        assert args[0] == "registered"
+        assert args[1] == p
+
+class TestPytestPluginInteractions:
+
+    def test_addhooks_conftestplugin(self, testdir):
+        newhooks = testdir.makepyfile(newhooks="""
+            def pytest_myhook(xyz):
+                "new hook"
+        """)
+        conf = testdir.makeconftest("""
+            import sys ; sys.path.insert(0, '.')
+            import newhooks
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(newhooks)
+            def pytest_myhook(xyz):
+                return xyz + 1
+        """)
+        config = testdir.Config()
+        config._conftest.importconftest(conf)
+        print(config.pluginmanager.getplugins())
+        res = config.hook.pytest_myhook(xyz=10)
+        assert res == [11]
+
+    def test_addhooks_docstring_error(self, testdir):
+        newhooks = testdir.makepyfile(newhooks="""
+            class A: # no pytest_ prefix
+                pass
+            def pytest_myhook(xyz):
+                pass
+        """)
+        conf = testdir.makeconftest("""
+            import sys ; sys.path.insert(0, '.')
+            import newhooks
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(newhooks)
+        """)
+        res = testdir.runpytest()
+        assert res.ret != 0
+        res.stderr.fnmatch_lines([
+            "*docstring*pytest_myhook*newhooks*"
+        ])
+
+    def test_addhooks_nohooks(self, testdir):
+        conf = testdir.makeconftest("""
+            import sys
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(sys)
+        """)
+        res = testdir.runpytest()
+        assert res.ret != 0
+        res.stderr.fnmatch_lines([
+            "*did not find*sys*"
+        ])
+
+    def test_do_option_conftestplugin(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_addoption(parser):
+                parser.addoption('--test123', action="store_true")
+        """)
+        config = testdir.Config()
+        config._conftest.importconftest(p)
+        print(config.pluginmanager.getplugins())
+        config.parse([])
+        assert not config.option.test123
+
+    def test_namespace_early_from_import(self, testdir):
+        p = testdir.makepyfile("""
+            from py.test.collect import Item
+            from pytest.collect import Item as Item2
+            assert Item is Item2
+        """)
+        result = testdir.runpython(p)
+        assert result.ret == 0
+
+    def test_do_ext_namespace(self, testdir):
+        testdir.makeconftest("""
+            def pytest_namespace():
+                return {'hello': 'world'}
+        """)
+        p = testdir.makepyfile("""
+            from py.test import hello
+            import py
+            def test_hello():
+                assert hello == "world"
+                assert 'hello' in py.test.__all__
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*1 passed*"
+        ])
+
+    def test_do_option_postinitialize(self, testdir):
+        config = testdir.Config()
+        config.parse([])
+        config.pluginmanager.do_configure(config=config)
+        assert not hasattr(config.option, 'test123')
+        p = testdir.makepyfile("""
+            def pytest_addoption(parser):
+                parser.addoption('--test123', action="store_true",
+                    default=True)
+        """)
+        config._conftest.importconftest(p)
+        assert config.option.test123
+
+    def test_configure(self, testdir):
+        config = testdir.parseconfig()
+        l = []
+        class A:
+            def pytest_configure(self, config):
+                l.append(self)
+
+        config.pluginmanager.register(A())
+        assert len(l) == 0
+        config.pluginmanager.do_configure(config=config)
+        assert len(l) == 1
+        config.pluginmanager.register(A())  # this should lead to a configured() plugin
+        assert len(l) == 2
+        assert l[0] != l[1]
+
+        config.pluginmanager.do_unconfigure(config=config)
+        config.pluginmanager.register(A())
+        assert len(l) == 2
+
+    # lower level API
+
+    def test_listattr(self):
+        pluginmanager = PluginManager()
+        class My2:
+            x = 42
+        pluginmanager.register(My2())
+        assert not pluginmanager.listattr("hello")
+        assert pluginmanager.listattr("x") == [42]
+
+def test_namespace_has_default_and_env_plugins(testdir):
+    p = testdir.makepyfile("""
+        import py
+        py.test.mark
+    """)
+    result = testdir.runpython(p)
+    assert result.ret == 0
+
+def test_varnames():
+    def f(x):
+        i = 3
+    class A:
+        def f(self, y):
+            pass
+    class B(object):
+        def __call__(self, z):
+            pass
+    assert varnames(f) == ("x",)
+    assert varnames(A().f) == ('y',)
+    assert varnames(B()) == ('z',)
+
+class TestMultiCall:
+    def test_uses_copy_of_methods(self):
+        l = [lambda: 42]
+        mc = MultiCall(l, {})
+        repr(mc)
+        l[:] = []
+        res = mc.execute()
+        return res == 42
+
+    def test_call_passing(self):
+        class P1:
+            def m(self, __multicall__, x):
+                assert len(__multicall__.results) == 1
+                assert not __multicall__.methods
+                return 17
+
+        class P2:
+            def m(self, __multicall__, x):
+                assert __multicall__.results == []
+                assert __multicall__.methods
+                return 23
+
+        p1 = P1()
+        p2 = P2()
+        multicall = MultiCall([p1.m, p2.m], {'x': 23})
+        assert "23" in repr(multicall)
+        reslist = multicall.execute()
+        assert len(reslist) == 2
+        # ensure reversed order
+        assert reslist == [23, 17]
+
+    def test_keyword_args(self):
+        def f(x):
+            return x + 1
+        class A:
+            def f(self, x, y):
+                return x + y
+        multicall = MultiCall([f, A().f], dict(x=23, y=24))
+        assert "'x': 23" in repr(multicall)
+        assert "'y': 24" in repr(multicall)
+        reslist = multicall.execute()
+        assert reslist == [24+23, 24]
+        assert "2 results" in repr(multicall)
+
+    def test_keyword_args_with_defaultargs(self):
+        def f(x, z=1):
+            return x + z
+        reslist = MultiCall([f], dict(x=23, y=24)).execute()
+        assert reslist == [24]
+        reslist = MultiCall([f], dict(x=23, z=2)).execute()
+        assert reslist == [25]
+
+    def test_tags_call_error(self):
+        multicall = MultiCall([lambda x: x], {})
+        py.test.raises(TypeError, "multicall.execute()")
+
+    def test_call_subexecute(self):
+        def m(__multicall__):
+            subresult = __multicall__.execute()
+            return subresult + 1
+
+        def n():
+            return 1
+
+        call = MultiCall([n, m], {}, firstresult=True)
+        res = call.execute()
+        assert res == 2
+
+    def test_call_none_is_no_result(self):
+        def m1():
+            return 1
+        def m2():
+            return None
+        res = MultiCall([m1, m2], {}, firstresult=True).execute()
+        assert res == 1
+        res = MultiCall([m1, m2], {}).execute()
+        assert res == [1]
+
+class TestHookRelay:
+    def test_happypath(self):
+        pm = PluginManager()
+        class Api:
+            def hello(self, arg):
+                "api hook 1"
+
+        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
+        assert hasattr(mcm, 'hello')
+        assert repr(mcm.hello).find("hello") != -1
+        class Plugin:
+            def hello(self, arg):
+                return arg + 1
+        pm.register(Plugin())
+        l = mcm.hello(arg=3)
+        assert l == [4]
+        assert not hasattr(mcm, 'world')
+
+    def test_only_kwargs(self):
+        pm = PluginManager()
+        class Api:
+            def hello(self, arg):
+                "api hook 1"
+        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
+        py.test.raises(TypeError, "mcm.hello(3)")
+
+    def test_firstresult_definition(self):
+        pm = PluginManager()
+        class Api:
+            def hello(self, arg):
+                "api hook 1"
+            hello.firstresult = True
+
+        mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
+        class Plugin:
+            def hello(self, arg):
+                return arg + 1
+        pm.register(Plugin())
+        res = mcm.hello(arg=3)
+        assert res == 4
+
+class TestTracer:
+    def test_simple(self):
+        from pytest.main import TagTracer
+        rootlogger = TagTracer()
+        log = rootlogger.get("pytest")
+        log("hello")
+        l = []
+        rootlogger.setwriter(l.append)
+        log("world")
+        assert len(l) == 1
+        assert l[0] == "[pytest] world\n"
+        sublog = log.get("collection")
+        sublog("hello")
+        assert l[1] == "[pytest:collection] hello\n"
+
+    def test_setprocessor(self):
+        from pytest.main import TagTracer
+        rootlogger = TagTracer()
+        log = rootlogger.get("1")
+        log2 = log.get("2")
+        assert log2.tags  == tuple("12")
+        l = []
+        rootlogger.setprocessor(tuple("12"), lambda *args: l.append(args))
+        log("not seen")
+        log2("seen")
+        assert len(l) == 1
+        tags, args = l[0]
+        assert "1" in tags
+        assert "2" in tags
+        assert args == ("seen",)
+        l2 = []
+        rootlogger.setprocessor("1:2", lambda *args: l2.append(args))
+        log2("seen")
+        tags, args = l2[0]
+        assert args == ("seen",)
+        
+
+    def test_setmyprocessor(self):
+        from pytest.main import TagTracer
+        rootlogger = TagTracer()
+        log = rootlogger.get("1")
+        log2 = log.get("2")
+        l = []
+        log2.setmyprocessor(lambda *args: l.append(args))
+        log("not seen")
+        assert not l
+        log2(42)
+        assert len(l) == 1
+        tags, args = l[0]
+        assert "1" in tags
+        assert "2" in tags
+        assert args == (42,)

--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,9 @@ Changes between 1.3.4 and 2.0.0dev0
 ----------------------------------------------
 
 - pytest-2.0 is now its own package and depends on pylib-2.0
+- new ability: python -m pytest / python -m pytest.main ability
+- new python invcation: pytest.main(args, plugins) to load
+  some custom plugins early.
 - try harder to run unittest test suites in a more compatible manner
   by deferring setup/teardown semantics to the unittest package. 
 - introduce a new way to set config options via ini-style files,

--- a/doc/customize.txt
+++ b/doc/customize.txt
@@ -58,8 +58,8 @@ builtin configuration file options
 .. confval:: norecursedirs
 
    Set the directory basename patterns to avoid when recursing
-   for test discovery.  The individual (fnmatch-style) patterns are 
-   applied to the basename of a directory to decide if to recurse into it.  
+   for test discovery.  The individual (fnmatch-style) patterns are
+   applied to the basename of a directory to decide if to recurse into it.
    Pattern matching characters::
 
         *       matches everything
@@ -68,7 +68,7 @@ builtin configuration file options
         [!seq]  matches any char not in seq
 
    Default patterns are ``.* _* CVS {args}``. Setting a ``norecurse``
-   replaces the default.  Here is a customizing example for avoiding 
+   replaces the default.  Here is a customizing example for avoiding
    a different set of directories::
 
     # content of setup.cfg

--- a/doc/apiref.txt
+++ b/doc/apiref.txt
@@ -4,6 +4,7 @@
 py.test reference documentation
 ================================================
 
+
 .. toctree::
    :maxdepth: 2
 

--- /dev/null
+++ b/doc/usage.txt
@@ -0,0 +1,165 @@
+
+.. _usage:
+
+Usage and Invocations
+==========================================
+
+
+.. _cmdline:
+
+Getting help on version, option names, environment vars
+-----------------------------------------------------------
+
+::
+
+    py.test --version   # shows where pytest was imported from
+    py.test --funcargs  # show available builtin function arguments
+    py.test -h | --help # show help on command line and config file options
+
+
+Stopping after the first (or N) failures
+---------------------------------------------------
+
+To stop the testing process after the first (N) failures::
+
+    py.test -x            # stop after first failure
+    py.test -maxfail=2    # stop after two failures
+
+calling pytest from Python code
+----------------------------------------------------
+
+.. versionadded: 2.0
+
+You can invoke ``py.test`` from Python code directly::
+
+    pytest.main()
+
+this acts as if you would call "py.test" from the command line.
+It will not raise ``SystemExit`` but return the exitcode instead.
+You can pass in options and arguments::
+
+    pytest.main(['x', 'mytestdir'])
+
+or pass in a string::
+
+    pytest.main("-x mytestdir")
+
+You can specify additional plugins to ``pytest.main``::
+
+    # content of myinvoke.py
+    import pytest
+    class MyPlugin:
+        def pytest_addoption(self, parser):
+            raise pytest.UsageError("hi from our plugin")
+
+    pytest.main(plugins=[MyPlugin()])
+
+Running it will exit quickly::
+
+    $ python myinvoke.py
+    ERROR: hi from our plugin
+
+calling pytest through ``python -m pytest``
+-----------------------------------------------------
+
+.. versionadded: 2.0
+
+You can invoke testing through the Python interpreter from the command line::
+
+    python -m pytest.main [...]
+
+Python2.7 and Python3 introduced specifying packages to "-m" so there
+you can also type::
+
+    python -m pytest [...]
+
+All of these invocations are equivalent to the ``py.test [...]`` command line invocation.
+
+
+Modifying Python traceback printing
+----------------------------------------------
+
+Examples for modifying traceback printing::
+
+    py.test --showlocals # show local variables in tracebacks
+    py.test -l           # show local variables (shortcut)
+
+    py.test --tb=long    # the default informative traceback formatting
+    py.test --tb=native  # the Python standard library formatting
+    py.test --tb=short   # a shorter traceback format
+    py.test --tb=line    # only one line per failure
+
+Dropping to PDB (Python Debugger) on failures
+----------------------------------------------
+
+.. _PDB: http://docs.python.org/library/pdb.html
+
+Python comes with a builtin Python debugger called PDB_.  ``py.test``
+allows to drop into the PDB prompt via a command line option::
+
+    py.test --pdb
+
+This will invoke the Python debugger on every failure.  Often you might
+only want to do this for the first failing test to understand a certain
+failure situation::
+
+    py.test -x --pdb   # drop to PDB on first failure, then end test session
+    py.test --pdb --maxfail=3  # drop to PDB for the first three failures
+
+
+Setting a breakpoint / aka ``set_trace()``
+----------------------------------------------------
+
+If you want to set a breakpoint and enter the ``pdb.set_trace()`` you
+can use a helper::
+
+    def test_function():
+        ...
+        py.test.set_trace()    # invoke PDB debugger and tracing
+
+.. versionadded: 2.0.0
+
+In previous versions you could only enter PDB tracing if
+you :ref:`disable capturing`.
+
+creating JUnitXML format files
+----------------------------------------------------
+
+To create result files which can be read by Hudson_ or other Continous
+integration servers, use this invocation::
+
+    py.test --junitxml=path
+
+to create an XML file at ``path``.
+
+creating resultlog format files
+----------------------------------------------------
+
+To create plain-text machine-readable result files you can issue::
+
+    py.test --resultlog=path
+
+and look at the content at the ``path`` location.  Such files are used e.g.
+by the `PyPy-test`_ web page to show test results over several revisions.
+
+.. _`PyPy-test`: http://codespeak.net:8099/summary
+
+
+send test report to pocoo pastebin service
+-----------------------------------------------------
+
+**Creating a URL for each test failure**::
+
+    py.test --pastebin=failed
+
+This will submit test run information to a remote Paste service and
+provide a URL for each failure.  You may select tests as usual or add
+for example ``-x`` if you only want to send one particular failure.
+
+**Creating a URL for a whole test session log**::
+
+    py.test --pastebin=all
+
+Currently only pasting to the http://paste.pocoo.org service is implemented.
+
+.. include:: links.inc

--- a/doc/overview.txt
+++ b/doc/overview.txt
@@ -7,7 +7,7 @@ Overview and Introduction
 
    features.txt
    getting-started.txt
-   cmdline.txt
+   usage.txt
    goodpractises.txt
    faq.txt
 

--- a/pytest/plugin/pytester.py
+++ b/pytest/plugin/pytester.py
@@ -2,7 +2,7 @@
 funcargs and support code for testing py.test's own functionality.
 """
 
-import py
+import py, pytest
 import sys, os
 import re
 import inspect
@@ -10,7 +10,7 @@ import time
 from fnmatch import fnmatch
 from pytest.plugin.session import Collection
 from py.builtin import print_
-from pytest._core import HookRelay
+from pytest.main import HookRelay
 
 def pytest_addoption(parser):
     group = parser.getgroup("pylib")
@@ -401,6 +401,10 @@ class TmpTestdir:
         #print "env", env
         return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
 
+    def pytestmain(self, *args, **kwargs):
+        ret = pytest.main(*args, **kwargs)
+        if ret == 2:
+            raise KeyboardInterrupt()
     def run(self, *cmdargs):
         return self._run(*cmdargs)
 

--- a/testing/acceptance_test.py
+++ b/testing/acceptance_test.py
@@ -1,4 +1,4 @@
-import sys, py
+import sys, py, pytest
 
 class TestGeneralUsage:
     def test_config_error(self, testdir):
@@ -82,36 +82,6 @@ class TestGeneralUsage:
         ])
 
 
-    def test_earlyinit(self, testdir):
-        p = testdir.makepyfile("""
-            import py
-            assert hasattr(py.test, 'mark')
-        """)
-        result = testdir.runpython(p)
-        assert result.ret == 0
-
-    def test_pydoc(self, testdir):
-        result = testdir.runpython_c("import py;help(py.test)")
-        assert result.ret == 0
-        s = result.stdout.str()
-        assert 'MarkGenerator' in s
-
-    def test_double_pytestcmdline(self, testdir):
-        p = testdir.makepyfile(run="""
-            import py
-            py.test.cmdline.main()
-            py.test.cmdline.main()
-        """)
-        testdir.makepyfile("""
-            def test_hello():
-                pass
-        """)
-        result = testdir.runpython(p)
-        result.stdout.fnmatch_lines([
-            "*1 passed*",
-            "*1 passed*",
-        ])
-
 
     @py.test.mark.xfail
     def test_early_skip(self, testdir):
@@ -225,19 +195,6 @@ class TestGeneralUsage:
             "*1 pass*",
         ])
 
-
-    @py.test.mark.skipif("sys.version_info < (2,5)")
-    def test_python_minus_m_invocation_ok(self, testdir):
-        p1 = testdir.makepyfile("def test_hello(): pass")
-        res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
-        assert res.ret == 0
-
-    @py.test.mark.skipif("sys.version_info < (2,5)")
-    def test_python_minus_m_invocation_fail(self, testdir):
-        p1 = testdir.makepyfile("def test_fail(): 0/0")
-        res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
-        assert res.ret == 1
-
     def test_skip_on_generated_funcarg_id(self, testdir):
         testdir.makeconftest("""
             import py
@@ -253,3 +210,83 @@ class TestGeneralUsage:
         res = testdir.runpytest(p)
         assert res.ret == 0
         res.stdout.fnmatch_lines(["*1 skipped*"])
+
+class TestInvocationVariants:
+    def test_earlyinit(self, testdir):
+        p = testdir.makepyfile("""
+            import py
+            assert hasattr(py.test, 'mark')
+        """)
+        result = testdir.runpython(p)
+        assert result.ret == 0
+
+    def test_pydoc(self, testdir):
+        result = testdir.runpython_c("import py;help(py.test)")
+        assert result.ret == 0
+        s = result.stdout.str()
+        assert 'MarkGenerator' in s
+
+    def test_double_pytestcmdline(self, testdir):
+        p = testdir.makepyfile(run="""
+            import py
+            py.test.cmdline.main()
+            py.test.cmdline.main()
+        """)
+        testdir.makepyfile("""
+            def test_hello():
+                pass
+        """)
+        result = testdir.runpython(p)
+        result.stdout.fnmatch_lines([
+            "*1 passed*",
+            "*1 passed*",
+        ])
+
+    @py.test.mark.skipif("sys.version_info < (2,5)")
+    def test_python_minus_m_invocation_ok(self, testdir):
+        p1 = testdir.makepyfile("def test_hello(): pass")
+        res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
+        assert res.ret == 0
+
+    @py.test.mark.skipif("sys.version_info < (2,5)")
+    def test_python_minus_m_invocation_fail(self, testdir):
+        p1 = testdir.makepyfile("def test_fail(): 0/0")
+        res = testdir.run(py.std.sys.executable, "-m", "py.test", str(p1))
+        assert res.ret == 1
+
+    def test_python_pytest_main(self, testdir):
+        p1 = testdir.makepyfile("def test_pass(): pass")
+        res = testdir.run(py.std.sys.executable, "-m", "pytest.main", str(p1))
+        assert res.ret == 0
+        res.stdout.fnmatch_lines(["*1 passed*"])
+
+    @py.test.mark.skipif("sys.version_info < (2,7)")
+    def test_python_pytest_package(self, testdir):
+        p1 = testdir.makepyfile("def test_pass(): pass")
+        res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
+        assert res.ret == 0
+        res.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_equivalence_pytest_pytest(self):
+        assert pytest.main == py.test.cmdline.main
+
+    def test_invoke_with_string(self, capsys):
+        retcode = pytest.main("-h")
+        assert not retcode
+        out, err = capsys.readouterr()
+        assert "--help" in out
+
+    def test_invoke_with_path(self, testdir, capsys):
+        retcode = testdir.pytestmain(testdir.tmpdir)
+        assert not retcode
+        out, err = capsys.readouterr()
+        
+    def test_invoke_plugin_api(self, capsys):
+        class MyPlugin:
+            def pytest_addoption(self, parser):
+                parser.addoption("--myopt")
+            
+        pytest.main(["-h"], plugins=[MyPlugin()])
+        out, err = capsys.readouterr()
+        assert "--myopt" in out
+

--- a/pytest/plugin/python.py
+++ b/pytest/plugin/python.py
@@ -461,7 +461,7 @@ def hasinit(obj):
 
 
 def getfuncargnames(function):
-    # XXX merge with _core.py's varnames
+    # XXX merge with main.py's varnames
     argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
     startindex = py.std.inspect.ismethod(function) and 1 or 0
     defaults = getattr(function, 'func_defaults',

--- a/doc/builtin.txt
+++ b/doc/builtin.txt
@@ -1,7 +1,19 @@
 
-pytest builtin helpers
+py.test builtin helpers
 ================================================
 
+builtin py.test.* helpers
+-----------------------------------------------------
+
+You can always use an interactive Python prompt and type::
+
+    import pytest
+    help(pytest)
+
+to get an overview on available globally available helpers.
+
+.. automodule:: pytest
+    :members:
 
 builtin function arguments
 -----------------------------------------------------
@@ -54,17 +66,3 @@ You can ask for available builtin or pro
         * ``pop(category=None)``: return last warning matching the category.
         * ``clear()``: clear list of warnings
 
-
-builtin py.test.* helpers
------------------------------------------------------
-
-You can always use an interactive Python prompt and type::
-
-    import pytest
-    help(pytest)
-
-to get an overview on available globally available helpers.
-
-.. automodule:: pytest
-    :members:
-

--- a/testing/plugin/test_pytester.py
+++ b/testing/plugin/test_pytester.py
@@ -1,7 +1,7 @@
 import py
 import os, sys
 from pytest.plugin.pytester import LineMatcher, LineComp, HookRecorder
-from pytest._core import PluginManager
+from pytest.main import PluginManager
 
 def test_reportrecorder(testdir):
     item = testdir.getitem("def test_func(): pass")
@@ -97,7 +97,7 @@ def test_hookrecorder_basic_no_args_hook
 def test_functional(testdir, linecomp):
     reprec = testdir.inline_runsource("""
         import py
-        from pytest._core import HookRelay, PluginManager
+        from pytest.main import HookRelay, PluginManager
         pytest_plugins="pytester"
         def test_func(_pytest):
             class ApiClass:

--- a/pytest/hookspec.py
+++ b/pytest/hookspec.py
@@ -1,4 +1,4 @@
-""" hook specifications for pytest plugins, invoked from _core.py and builtin plugins.  """
+""" hook specifications for pytest plugins, invoked from main.py and builtin plugins.  """
 
 # -------------------------------------------------------------------------
 # Initialization

--- a/doc/features.txt
+++ b/doc/features.txt
@@ -5,8 +5,8 @@ no-boilerplate testing with Python
 ----------------------------------
 
 - automatic, fully customizable Python test discovery
-- :pep:`8` consistent testing style
-- allows simple test functions
+- allows fully :pep:`8` compliant coding style
+- write simple test functions and freely group tests
 - ``assert`` statement for your assertions
 - powerful parametrization of test functions
 - rely on powerful traceback and assertion reporting
@@ -25,8 +25,8 @@ extensive plugin and customization syste
 mature command line testing tool
 --------------------------------------
 
+- powerful :ref:`usage` possibilities
 - used in many projects, ranging from 10 to 10K tests
-- autodiscovery of tests
 - simple well sorted command line options
 - runs on Unix, Windows from Python 2.4 up to Python 3.1 and 3.2
 - is itself tested extensively on a CI server

--- a/testing/plugin/conftest.py
+++ b/testing/plugin/conftest.py
@@ -2,7 +2,7 @@ import py
 
 import pytest.plugin
 plugindir = py.path.local(pytest.plugin.__file__).dirpath()
-from pytest._core import default_plugins
+from pytest.main import default_plugins
 
 def pytest_collect_file(path, parent):
     if path.basename.startswith("pytest_") and path.ext == ".py":

--- /dev/null
+++ b/pytest/main.py
@@ -0,0 +1,412 @@
+"""
+pytest PluginManager, basic initialization and tracing.
+All else is in pytest/plugin.
+(c) Holger Krekel 2004-2010
+"""
+import sys, os
+import inspect
+import py
+from pytest import hookspec # the extension point definitions
+
+assert py.__version__.split(".")[:2] >= ['2', '0'], ("installation problem: "
+    "%s is too old, remove or upgrade 'py'" % (py.__version__))
+
+default_plugins = (
+ "config session terminal runner python pdb capture unittest mark skipping "
+ "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
+ "junitxml doctest").split()
+
+IMPORTPREFIX = "pytest_"
+
+class TagTracer:
+    def __init__(self):
+        self._tag2proc = {}
+        self.writer = None
+
+    def get(self, name):
+        return TagTracerSub(self, (name,))
+
+    def processmessage(self, tags, args):
+        if self.writer is not None:
+            prefix = ":".join(tags)
+            content = " ".join(map(str, args))
+            self.writer("[%s] %s\n" %(prefix, content))
+        try:
+            self._tag2proc[tags](tags, args)
+        except KeyError:
+            pass
+
+    def setwriter(self, writer):
+        self.writer = writer
+
+    def setprocessor(self, tags, processor):
+        if isinstance(tags, str):
+            tags = tuple(tags.split(":"))
+        else:
+            assert isinstance(tags, tuple)
+        self._tag2proc[tags] = processor
+
+class TagTracerSub:
+    def __init__(self, root, tags):
+        self.root = root
+        self.tags = tags
+    def __call__(self, *args):
+        self.root.processmessage(self.tags, args)
+    def setmyprocessor(self, processor):
+        self.root.setprocessor(self.tags, processor)
+    def get(self, name):
+        return self.__class__(self.root, self.tags + (name,))
+
+class PluginManager(object):
+    def __init__(self, load=False):
+        self._name2plugin = {}
+        self._plugins = []
+        self._hints = []
+        self.trace = TagTracer().get("pytest")
+        if os.environ.get('PYTEST_DEBUG'):
+            self.trace.root.setwriter(sys.stderr.write)
+        self.hook = HookRelay([hookspec], pm=self)
+        self.register(self)
+        if load:
+            for spec in default_plugins:
+                self.import_plugin(spec)
+
+    def _getpluginname(self, plugin, name):
+        if name is None:
+            if hasattr(plugin, '__name__'):
+                name = plugin.__name__.split(".")[-1]
+            else:
+                name = id(plugin)
+        return name
+
+    def register(self, plugin, name=None, prepend=False):
+        assert not self.isregistered(plugin), plugin
+        assert not self.isregistered(plugin), plugin
+        name = self._getpluginname(plugin, name)
+        if name in self._name2plugin:
+            return False
+        self._name2plugin[name] = plugin
+        self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
+        self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
+        self.trace("registered", plugin)
+        if not prepend:
+            self._plugins.append(plugin)
+        else:
+            self._plugins.insert(0, plugin)
+        return True
+
+    def unregister(self, plugin=None, name=None):
+        if plugin is None:
+            plugin = self.getplugin(name=name)
+        self._plugins.remove(plugin)
+        self.hook.pytest_plugin_unregistered(plugin=plugin)
+        for name, value in list(self._name2plugin.items()):
+            if value == plugin:
+                del self._name2plugin[name]
+
+    def isregistered(self, plugin, name=None):
+        if self._getpluginname(plugin, name) in self._name2plugin:
+            return True
+        for val in self._name2plugin.values():
+            if plugin == val:
+                return True
+
+    def addhooks(self, spec):
+        self.hook._addhooks(spec, prefix="pytest_")
+
+    def getplugins(self):
+        return list(self._plugins)
+
+    def skipifmissing(self, name):
+        if not self.hasplugin(name):
+            py.test.skip("plugin %r is missing" % name)
+
+    def hasplugin(self, name):
+        try:
+            self.getplugin(name)
+            return True
+        except KeyError:
+            return False
+
+    def getplugin(self, name):
+        try:
+            return self._name2plugin[name]
+        except KeyError:
+            impname = canonical_importname(name)
+            return self._name2plugin[impname]
+
+    # API for bootstrapping
+    #
+    def _envlist(self, varname):
+        val = py.std.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
+        except ImportError:
+            return # XXX issue a warning
+        for ep in iter_entry_points('pytest11'):
+            name = canonical_importname(ep.name)
+            if name in self._name2plugin:
+                continue
+            plugin = ep.load()
+            self.register(plugin, name=name)
+
+    def consider_preparse(self, args):
+        for opt1,opt2 in zip(args, args[1:]):
+            if opt1 == "-p":
+                self.import_plugin(opt2)
+
+    def consider_conftest(self, conftestmodule):
+        if self.register(conftestmodule, name=conftestmodule.__file__):
+            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, spec):
+        assert isinstance(spec, str)
+        modname = canonical_importname(spec)
+        if modname in self._name2plugin:
+            return
+        try:
+            mod = importplugin(modname)
+        except KeyboardInterrupt:
+            raise
+        except:
+            e = py.std.sys.exc_info()[1]
+            if not hasattr(py.test, 'skip'):
+                raise
+            elif not isinstance(e, py.test.skip.Exception):
+                raise
+            self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
+        else:
+            self.register(mod, modname)
+            self.consider_module(mod)
+
+    def pytest_plugin_registered(self, plugin):
+        dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
+        if dic:
+            self._setns(py.test, dic)
+        if hasattr(self, '_config'):
+            self.call_plugin(plugin, "pytest_addoption",
+                {'parser': self._config._parser})
+            self.call_plugin(plugin, "pytest_configure",
+                {'config': self._config})
+
+    def _setns(self, obj, dic):
+        for name, value in dic.items():
+            if isinstance(value, dict):
+                mod = getattr(obj, name, None)
+                if mod is None:
+                    mod = py.std.types.ModuleType(name)
+                    sys.modules['pytest.%s' % name] = mod
+                    sys.modules['py.test.%s' % name] = mod
+                    mod.__all__ = []
+                    setattr(obj, name, mod)
+                self._setns(mod, value)
+            else:
+                #print "setting", name, value, "on", obj
+                setattr(obj, name, value)
+                obj.__all__.append(name)
+
+    def pytest_terminal_summary(self, terminalreporter):
+        tw = terminalreporter._tw
+        if terminalreporter.config.option.traceconfig:
+            for hint in self._hints:
+                tw.line("hint: %s" % hint)
+
+    def do_configure(self, config):
+        assert not hasattr(self, '_config')
+        self._config = config
+        config.hook.pytest_configure(config=self._config)
+
+    def do_unconfigure(self, config):
+        config = self._config
+        del self._config
+        config.hook.pytest_unconfigure(config=config)
+        config.pluginmanager.unregister(self)
+
+    def notify_exception(self, excinfo):
+        excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
+        res = self.hook.pytest_internalerror(excrepr=excrepr)
+        if not py.builtin.any(res):
+            for line in str(excrepr).split("\n"):
+                sys.stderr.write("INTERNALERROR> %s\n" %line)
+                sys.stderr.flush()
+
+    def listattr(self, attrname, plugins=None):
+        if plugins is None:
+            plugins = self._plugins
+        l = []
+        for plugin in plugins:
+            try:
+                l.append(getattr(plugin, attrname))
+            except AttributeError:
+                continue
+        return l
+
+    def call_plugin(self, plugin, methname, kwargs):
+        return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
+                kwargs=kwargs, firstresult=True).execute()
+
+def canonical_importname(name):
+    if '.' in name:
+        return name
+    name = name.lower()
+    if not name.startswith(IMPORTPREFIX):
+        name = IMPORTPREFIX + name
+    return name
+
+def importplugin(importspec):
+    try:
+        return __import__(importspec, None, None, '__doc__')
+    except ImportError:
+        e = py.std.sys.exc_info()[1]
+        if str(e).find(importspec) == -1:
+            raise
+        name = importspec
+        try:
+            if name.startswith("pytest_"):
+                name = importspec[7:]
+            return __import__("pytest.plugin.%s" %(name), None, None, '__doc__')
+        except ImportError:
+            e = py.std.sys.exc_info()[1]
+            if str(e).find(name) == -1:
+                raise
+            # show the original exception, not the failing internal one
+            return __import__(importspec, None, None, '__doc__')
+
+
+class MultiCall:
+    """ execute a call into multiple python functions/methods. """
+    def __init__(self, methods, kwargs, firstresult=False):
+        self.methods = list(methods)
+        self.kwargs = kwargs
+        self.results = []
+        self.firstresult = firstresult
+
+    def __repr__(self):
+        status = "%d results, %d meths" % (len(self.results), len(self.methods))
+        return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
+
+    def execute(self):
+        while self.methods:
+            method = self.methods.pop()
+            kwargs = self.getkwargs(method)
+            res = method(**kwargs)
+            if res is not None:
+                self.results.append(res)
+                if self.firstresult:
+                    return res
+        if not self.firstresult:
+            return self.results
+
+    def getkwargs(self, method):
+        kwargs = {}
+        for argname in varnames(method):
+            try:
+                kwargs[argname] = self.kwargs[argname]
+            except KeyError:
+                if argname == "__multicall__":
+                    kwargs[argname] = self
+        return kwargs
+
+def varnames(func):
+    if not inspect.isfunction(func) and not inspect.ismethod(func):
+        func = getattr(func, '__call__', func)
+    ismethod = inspect.ismethod(func)
+    rawcode = py.code.getrawcode(func)
+    try:
+        return rawcode.co_varnames[ismethod:rawcode.co_argcount]
+    except AttributeError:
+        return ()
+
+class HookRelay:
+    def __init__(self, hookspecs, pm, prefix="pytest_"):
+        if not isinstance(hookspecs, list):
+            hookspecs = [hookspecs]
+        self._hookspecs = []
+        self._pm = pm
+        for hookspec in hookspecs:
+            self._addhooks(hookspec, prefix)
+
+    def _addhooks(self, hookspecs, prefix):
+        self._hookspecs.append(hookspecs)
+        added = False
+        for name, method in vars(hookspecs).items():
+            if name.startswith(prefix):
+                if not method.__doc__:
+                    raise ValueError("docstring required for hook %r, in %r"
+                        % (method, hookspecs))
+                firstresult = getattr(method, 'firstresult', False)
+                hc = HookCaller(self, name, firstresult=firstresult)
+                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, hookspecs,))
+
+
+class HookCaller:
+    def __init__(self, hookrelay, name, firstresult):
+        self.hookrelay = hookrelay
+        self.name = name
+        self.firstresult = firstresult
+
+    def __repr__(self):
+        return "<HookCaller %r>" %(self.name,)
+
+    def __call__(self, **kwargs):
+        methods = self.hookrelay._pm.listattr(self.name)
+        mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
+        return mc.execute()
+
+    def pcall(self, plugins, **kwargs):
+        methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
+        mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
+        return mc.execute()
+
+_preinit = [PluginManager(load=True)] # triggers default plugin importing
+
+def main(args=None, plugins=None):
+    if args is None:
+        args = sys.argv[1:]
+    elif not isinstance(args, (tuple, list)):
+        args = py.std.shlex.split(str(args))
+    if _preinit:
+       _pluginmanager = _preinit.pop(0)
+    else: # subsequent calls to main will create a fresh instance
+        _pluginmanager = PluginManager(load=True)
+    hook = _pluginmanager.hook
+    try:
+        if plugins:
+            for plugin in plugins:
+                _pluginmanager.register(plugin)
+        config = hook.pytest_cmdline_parse(
+                pluginmanager=_pluginmanager, args=args)
+        exitstatus = hook.pytest_cmdline_main(config=config)
+    except UsageError:
+        e = sys.exc_info()[1]
+        sys.stderr.write("ERROR: %s\n" %(e.args[0],))
+        exitstatus = 3
+    return exitstatus
+
+class UsageError(Exception):
+    """ error in py.test usage or invocation"""
+
+if __name__ == '__main__':
+    raise SystemExit(main())



More information about the pytest-commit mailing list