[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