introduce plugin discovery through setuptools "pytest11" entrypoints
and refine execnet dependency handling. Prepare 1.1 release --HG-- branch : trunk
This commit is contained in:
		
							parent
							
								
									0e03ae1ee8
								
							
						
					
					
						commit
						ed03eef81b
					
				|  | @ -1,7 +1,10 @@ | ||||||
| Changes between 1.1.1 and 1.1.0 | 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  | - re-introduce py.test.cmdline.main() for better backward compatibility  | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -88,6 +88,16 @@ class VirtualEnv(object): | ||||||
|             ] + list(args), |             ] + list(args), | ||||||
|             **kw) |             **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): |     def easy_install(self, *packages, **kw): | ||||||
|         args = [] |         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__)") |     ch = gw.remote_exec("import py ; channel.send(py.__version__)") | ||||||
|     version = ch.receive() |     version = ch.receive() | ||||||
|     assert version == py.__version__ |     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 | ||||||
|  |  | ||||||
|  | @ -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 | This is a compatibility fixing release of pylib/py.test to work | ||||||
| better with previous 1.0.x code bases.  It also contains fixes | better with previous 1.0.x test code bases.  It also contains fixes | ||||||
| and changes to work with `execnet>=1.0.0b4`_ which is now required | and changes to work with `execnet>=1.0.0b4`_.  1.1.1 also introduces | ||||||
| (but is not installed automatically, issue "easy_install -U execnet"). | a new mechanism for registering plugins via setuptools.  | ||||||
| Last but not least, documentation has been improved. | Last but not least, documentation has been improved. | ||||||
| 
 | 
 | ||||||
| What is pylib/py.test?  | 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 | it offers some unique features not found in other | ||||||
| testing tools.  See http://pytest.org for more info. | 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 | and some developer-oriented command line tools. See | ||||||
| http://pylib.org for more info. | http://pylib.org for more info. | ||||||
| 
 | 
 | ||||||
|  | @ -31,7 +31,10 @@ holger (http://twitter.com/hpk42) | ||||||
| Changes between 1.1.1 and 1.1.0 | 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  | - re-introduce py.test.cmdline.main() for better backward compatibility  | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -125,6 +125,8 @@ Plugin discovery at tool startup | ||||||
| 
 | 
 | ||||||
| py.test loads plugin modules at tool startup in the following way: | 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  | * by reading the ``PYTEST_PLUGINS`` environment variable  | ||||||
|   and importing the comma-separated list of named plugins.  |   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.  |   and loading the specified plugin before actual command line parsing.  | ||||||
| 
 | 
 | ||||||
| * by loading all `conftest.py plugin`_ files as inferred by the command line | * 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  | * by recursively loading all plugins specified by the  | ||||||
|   ``pytest_plugins`` variable in a ``conftest.py`` file  |   ``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 | Specifying plugins in a test module or plugin | ||||||
| ----------------------------------------------- | ----------------------------------------------- | ||||||
| 
 | 
 | ||||||
|  | @ -160,8 +158,8 @@ must be lowercase. | ||||||
| .. _`conftest.py plugin`: | .. _`conftest.py plugin`: | ||||||
| .. _`conftestplugin`: | .. _`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  | The purpose of ``conftest.py`` files is to allow `project-specific  | ||||||
| test configuration`_.  They thus make for a good place to implement  | 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"): |         if config.getvalue("runall"): | ||||||
|             collect_ignore[:] = [] |             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`:  | .. _`well specified hooks`:  | ||||||
| .. _`implement hooks`:  | .. _`implement hooks`:  | ||||||
|  |  | ||||||
|  | @ -74,6 +74,7 @@ class Config(object): | ||||||
| 
 | 
 | ||||||
|     def _preparse(self, args): |     def _preparse(self, args): | ||||||
|         self._conftest.setinitial(args)  |         self._conftest.setinitial(args)  | ||||||
|  |         self.pluginmanager.consider_setuptools_entrypoints() | ||||||
|         self.pluginmanager.consider_preparse(args) |         self.pluginmanager.consider_preparse(args) | ||||||
|         self.pluginmanager.consider_env() |         self.pluginmanager.consider_env() | ||||||
|         self.pluginmanager.do_addoption(self._parser) |         self.pluginmanager.do_addoption(self._parser) | ||||||
|  |  | ||||||
|  | @ -77,6 +77,14 @@ class PluginManager(object): | ||||||
|         for spec in self._envlist("PYTEST_PLUGINS"): |         for spec in self._envlist("PYTEST_PLUGINS"): | ||||||
|             self.import_plugin(spec) |             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): |     def consider_preparse(self, args): | ||||||
|         for opt1,opt2 in zip(args, args[1:]): |         for opt1,opt2 in zip(args, args[1:]): | ||||||
|             if opt1 == "-p":  |             if opt1 == "-p":  | ||||||
|  |  | ||||||
|  | @ -42,6 +42,24 @@ class TestBootstrapping: | ||||||
|         l3 = len(pluginmanager.getplugins()) |         l3 = len(pluginmanager.getplugins()) | ||||||
|         assert l2 == l3 |         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): |     def test_pluginmanager_ENV_startup(self, testdir, monkeypatch): | ||||||
|         x500 = testdir.makepyfile(pytest_x500="#") |         x500 = testdir.makepyfile(pytest_x500="#") | ||||||
|         p = testdir.makepyfile(""" |         p = testdir.makepyfile(""" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue