introduce plugin discovery through setuptools "pytest11" entrypoints

and refine execnet dependency handling.  Prepare 1.1 release

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-11-23 17:20:36 +01:00
parent 0e03ae1ee8
commit ed03eef81b
7 changed files with 128 additions and 17 deletions

View File

@ -1,7 +1,10 @@
Changes between 1.1.1 and 1.1.0
=====================================
- fix py.test dist-testing to work with execnet >= 1.0.0b4 (required)
- introduce automatic lookup of 'pytest11' entrypoints
via setuptools' pkg_resources.iter_entry_points
- fix py.test dist-testing to work with execnet >= 1.0.0b4
- re-introduce py.test.cmdline.main() for better backward compatibility

View File

@ -88,6 +88,16 @@ class VirtualEnv(object):
] + list(args),
**kw)
def pytest_getouterr(self, *args):
self.ensure()
args = [self._cmd("python"), self._cmd("py.test")] + list(args)
popen = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, err = popen.communicate()
return out
def setup_develop(self):
self.ensure()
return self.pcall("python", "setup.py", "develop")
def easy_install(self, *packages, **kw):
args = []
@ -110,4 +120,25 @@ def test_make_sdist_and_run_it(py_setup, venv):
ch = gw.remote_exec("import py ; channel.send(py.__version__)")
version = ch.receive()
assert version == py.__version__
ch = gw.remote_exec("import py ; channel.send(py.__version__)")
def test_plugin_setuptools_entry_point_integration(py_setup, venv, tmpdir):
sdist = py_setup.make_sdist(venv.path)
venv.easy_install(str(sdist))
# create a sample plugin
basedir = tmpdir.mkdir("testplugin")
basedir.join("setup.py").write("""if 1:
from setuptools import setup
setup(name="testplugin",
entry_points = {'pytest11': ['testplugin=tp1']},
py_modules = ['tp1'],
)
""")
basedir.join("tp1.py").write(py.code.Source("""
def pytest_addoption(parser):
parser.addoption("--testpluginopt", action="store_true")
"""))
basedir.chdir()
print ("created sample plugin in %s" %basedir)
venv.setup_develop()
out = venv.pytest_getouterr("-h")
assert "testpluginopt" in out

View File

@ -1,10 +1,10 @@
py.test/pylib 1.1.1: bugfix release, improved 1.0.x backward compat
py.test/pylib 1.1.1: bugfix release, setuptools plugin registration
--------------------------------------------------------------------------------
This is a compatibility fixing release of pylib/py.test to work
better with previous 1.0.x code bases. It also contains fixes
and changes to work with `execnet>=1.0.0b4`_ which is now required
(but is not installed automatically, issue "easy_install -U execnet").
better with previous 1.0.x test code bases. It also contains fixes
and changes to work with `execnet>=1.0.0b4`_. 1.1.1 also introduces
a new mechanism for registering plugins via setuptools.
Last but not least, documentation has been improved.
What is pylib/py.test?
@ -17,7 +17,7 @@ existing common Python test suites without modification. Moreover,
it offers some unique features not found in other
testing tools. See http://pytest.org for more info.
The pylib contains a localpath and svnpath implementation
The pylib also contains a localpath and svnpath implementation
and some developer-oriented command line tools. See
http://pylib.org for more info.
@ -31,7 +31,10 @@ holger (http://twitter.com/hpk42)
Changes between 1.1.1 and 1.1.0
=====================================
- fix py.test dist-testing to work with execnet >= 1.0.0b4 (required)
- introduce automatic lookup of 'pytest11' entrypoints
via setuptools' pkg_resources.iter_entry_points
- fix py.test dist-testing to work with execnet >= 1.0.0b4
- re-introduce py.test.cmdline.main() for better backward compatibility

View File

@ -125,6 +125,8 @@ Plugin discovery at tool startup
py.test loads plugin modules at tool startup in the following way:
* by loading all plugins registered through `setuptools entry points`_.
* by reading the ``PYTEST_PLUGINS`` environment variable
and importing the comma-separated list of named plugins.
@ -132,17 +134,13 @@ py.test loads plugin modules at tool startup in the following way:
and loading the specified plugin before actual command line parsing.
* by loading all `conftest.py plugin`_ files as inferred by the command line
invocation
invocation (test files and all of its parent directories).
Note that ``conftest.py`` files from sub directories are loaded
during test collection and not at tool startup.
* by recursively loading all plugins specified by the
``pytest_plugins`` variable in a ``conftest.py`` file
Note that at tool startup only ``conftest.py`` files in
the directory of the specified test modules (or the current dir if None)
or any of the parent directories are found. There is no try to
pre-scan all subdirectories to find ``conftest.py`` files or test
modules.
Specifying plugins in a test module or plugin
-----------------------------------------------
@ -160,8 +158,8 @@ must be lowercase.
.. _`conftest.py plugin`:
.. _`conftestplugin`:
conftest.py as anonymous per-project plugins
--------------------------------------------------
Writing per-project plugins (conftest.py)
------------------------------------------------------
The purpose of ``conftest.py`` files is to allow `project-specific
test configuration`_. They thus make for a good place to implement
@ -181,6 +179,55 @@ by defining the following hook in a ``conftest.py`` file:
if config.getvalue("runall"):
collect_ignore[:] = []
.. _`setuptools entry points`:
Writing setuptools-registered plugins
------------------------------------------------------
.. _`Distribute`: http://pypi.python.org/pypi/distribute
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
If you want to make your plugin publically available, you
can use `setuptools`_ or `Distribute`_ which both allow
to register an entry point. ``py.test`` will register
all objects with the ``pytest11`` entry point.
To make your plugin available you may insert the following
lines in your setuptools/distribute-based setup-invocation:
.. sourcecode:: python
# sample ./setup.py file
from setuptools import setup
setup(
name="myproject",
packages = ['myproject']
# the following makes a plugin available to py.test
entry_points = {
'pytest11': [
'name_of_plugin = myproject.pluginmodule',
]
},
)
If a package is installed with this setup, py.test will load
``myproject.pluginmodule`` under the ``name_of_plugin`` name
and use it as a plugin.
Accessing another plugin by name
--------------------------------------------
If a plugin wants to collaborate with code from
another plugin it can obtain a reference through
the plugin manager like this:
.. sourcecode:: python
plugin = config.pluginmanager.getplugin("name_of_plugin")
If you want to look at the names of existing plugins, use
the ``--traceconfig`` option.
.. _`well specified hooks`:
.. _`implement hooks`:

View File

@ -74,6 +74,7 @@ class Config(object):
def _preparse(self, args):
self._conftest.setinitial(args)
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_env()
self.pluginmanager.do_addoption(self._parser)

View File

@ -77,6 +77,14 @@ class PluginManager(object):
for spec in self._envlist("PYTEST_PLUGINS"):
self.import_plugin(spec)
def consider_setuptools_entrypoints(self):
from pkg_resources import iter_entry_points
for ep in iter_entry_points('pytest11'):
if ep.name in self._name2plugin:
continue
plugin = ep.load()
self.register(plugin, name=ep.name)
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":

View File

@ -42,6 +42,24 @@ class TestBootstrapping:
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
def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
x500 = testdir.makepyfile(pytest_x500="#")
p = testdir.makepyfile("""