split plugin documentation into "using" and "writing plugins",
referencing each other. Also add tryfirst/trylast examples. --HG-- branch : more_plugin
This commit is contained in:
		
							parent
							
								
									d2a5c7f99b
								
							
						
					
					
						commit
						ea50ef1588
					
				|  | @ -33,6 +33,9 @@ | ||||||
|   now deprecated use of ``pytest.mark`` which is meant to  |   now deprecated use of ``pytest.mark`` which is meant to  | ||||||
|   contain markers for test functions only.   |   contain markers for test functions only.   | ||||||
| 
 | 
 | ||||||
|  | - write/refine docs for "writing plugins" which now have their | ||||||
|  |   own page and are separate from the "using/installing plugins`` page. | ||||||
|  | 
 | ||||||
|   |   | ||||||
| 2.7.1.dev (compared to 2.7.0) | 2.7.1.dev (compared to 2.7.0) | ||||||
| ----------------------------- | ----------------------------- | ||||||
|  |  | ||||||
|  | @ -56,6 +56,7 @@ pytest: helps you write better programs | ||||||
|  - all collection, reporting, running aspects are delegated to hook functions |  - all collection, reporting, running aspects are delegated to hook functions | ||||||
|  - customizations can be per-directory, per-project or per PyPI released plugin |  - customizations can be per-directory, per-project or per PyPI released plugin | ||||||
|  - it is easy to add command line options or customize existing behaviour |  - it is easy to add command line options or customize existing behaviour | ||||||
|  |  - :ref:`easy to write your own plugins <writing-plugins>` | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html | .. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html | ||||||
|  |  | ||||||
|  | @ -1,64 +1,14 @@ | ||||||
| .. _plugins: |  | ||||||
| 
 |  | ||||||
| Working with plugins and conftest files |  | ||||||
| ======================================= |  | ||||||
| 
 |  | ||||||
| ``pytest`` implements all aspects of configuration, collection, running and reporting by calling `well specified hooks`_.  Virtually any Python module can be registered as a plugin.  It can implement any number of hook functions (usually two or three) which all have a ``pytest_`` prefix, making hook functions easy to distinguish and find.  There are three basic location types: |  | ||||||
| 
 |  | ||||||
| * `builtin plugins`_: loaded from pytest's internal ``_pytest`` directory. |  | ||||||
| * `external plugins`_: modules discovered through `setuptools entry points`_ |  | ||||||
| * `conftest.py plugins`_: modules auto-discovered in test directories |  | ||||||
| 
 |  | ||||||
| .. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/ |  | ||||||
| .. _`conftest.py plugins`: |  | ||||||
| .. _`conftest.py`: |  | ||||||
| .. _`localplugin`: |  | ||||||
| .. _`conftest`: |  | ||||||
| 
 |  | ||||||
| conftest.py: local per-directory plugins |  | ||||||
| ---------------------------------------- |  | ||||||
| 
 |  | ||||||
| local ``conftest.py`` plugins contain directory-specific hook |  | ||||||
| implementations.  Session and test running activities will |  | ||||||
| invoke all hooks defined in ``conftest.py`` files closer to the |  | ||||||
| root of the filesystem.  Example: Assume the following layout |  | ||||||
| and content of files:: |  | ||||||
| 
 |  | ||||||
|     a/conftest.py: |  | ||||||
|         def pytest_runtest_setup(item): |  | ||||||
|             # called for running each test in 'a' directory |  | ||||||
|             print ("setting up", item) |  | ||||||
| 
 |  | ||||||
|     a/test_sub.py: |  | ||||||
|         def test_sub(): |  | ||||||
|             pass |  | ||||||
| 
 |  | ||||||
|     test_flat.py: |  | ||||||
|         def test_flat(): |  | ||||||
|             pass |  | ||||||
| 
 |  | ||||||
| Here is how you might run it:: |  | ||||||
| 
 |  | ||||||
|      py.test test_flat.py   # will not show "setting up" |  | ||||||
|      py.test a/test_sub.py  # will show "setting up" |  | ||||||
| 
 |  | ||||||
| .. Note:: |  | ||||||
|     If you have ``conftest.py`` files which do not reside in a |  | ||||||
|     python package directory (i.e. one containing an ``__init__.py``) then |  | ||||||
|     "import conftest" can be ambiguous because there might be other |  | ||||||
|     ``conftest.py`` files as well on your PYTHONPATH or ``sys.path``. |  | ||||||
|     It is thus good practise for projects to either put ``conftest.py`` |  | ||||||
|     under a package scope or to never import anything from a |  | ||||||
|     conftest.py file. |  | ||||||
| 
 |  | ||||||
| .. _`external plugins`: | .. _`external plugins`: | ||||||
| .. _`extplugins`: | .. _`extplugins`: | ||||||
|  | .. _`using plugins`: | ||||||
| 
 | 
 | ||||||
| Installing External Plugins / Searching | Installing and Using plugins | ||||||
| --------------------------------------- | ============================ | ||||||
| 
 | 
 | ||||||
| Installing a plugin happens through any usual Python installation | This section talks about installing and using third party plugins. | ||||||
| tool, for example:: | For writing your own plugins, please refer to :ref:`writing-plugins`. | ||||||
|  | 
 | ||||||
|  | Installing a third party plugin can be easily done with ``pip``:: | ||||||
| 
 | 
 | ||||||
|     pip install pytest-NAME |     pip install pytest-NAME | ||||||
|     pip uninstall pytest-NAME |     pip uninstall pytest-NAME | ||||||
|  | @ -120,118 +70,20 @@ You may also discover more plugins through a `pytest- pypi.python.org search`_. | ||||||
| .. _`pytest- pypi.python.org search`: http://pypi.python.org/pypi?%3Aaction=search&term=pytest-&submit=search | .. _`pytest- pypi.python.org search`: http://pypi.python.org/pypi?%3Aaction=search&term=pytest-&submit=search | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Writing a plugin by looking at examples |  | ||||||
| --------------------------------------- |  | ||||||
| 
 |  | ||||||
| .. _`setuptools`: http://pypi.python.org/pypi/setuptools |  | ||||||
| 
 |  | ||||||
| If you want to write a plugin, there are many real-life examples |  | ||||||
| you can copy from: |  | ||||||
| 
 |  | ||||||
| * a custom collection example plugin: :ref:`yaml plugin` |  | ||||||
| * around 20 `builtin plugins`_ which provide pytest's own functionality |  | ||||||
| * many `external plugins`_ providing additional features |  | ||||||
| 
 |  | ||||||
| All of these plugins implement the documented `well specified hooks`_ |  | ||||||
| to extend and add functionality. |  | ||||||
| 
 |  | ||||||
| You can also :ref:`contribute your plugin to pytest-dev<submitplugin>` |  | ||||||
| once it has some happy users other than yourself. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| .. _`setuptools entry points`: |  | ||||||
| 
 |  | ||||||
| Making your plugin installable by others |  | ||||||
| ---------------------------------------- |  | ||||||
| 
 |  | ||||||
| If you want to make your plugin externally available, you |  | ||||||
| may define a so-called entry point for your distribution so |  | ||||||
| that ``pytest`` finds your plugin module.  Entry points are |  | ||||||
| a feature that is provided by `setuptools`_. pytest looks up |  | ||||||
| the ``pytest11`` entrypoint to discover its |  | ||||||
| plugins and you can thus make your plugin available by defining |  | ||||||
| it in your setuptools-invocation: |  | ||||||
| 
 |  | ||||||
| .. sourcecode:: python |  | ||||||
| 
 |  | ||||||
|     # sample ./setup.py file |  | ||||||
|     from setuptools import setup |  | ||||||
| 
 |  | ||||||
|     setup( |  | ||||||
|         name="myproject", |  | ||||||
|         packages = ['myproject'] |  | ||||||
| 
 |  | ||||||
|         # the following makes a plugin available to pytest |  | ||||||
|         entry_points = { |  | ||||||
|             'pytest11': [ |  | ||||||
|                 'name_of_plugin = myproject.pluginmodule', |  | ||||||
|             ] |  | ||||||
|         }, |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| If a package is installed this way, ``pytest`` will load |  | ||||||
| ``myproject.pluginmodule`` as a plugin which can define |  | ||||||
| `well specified hooks`_. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| .. _`pluginorder`: |  | ||||||
| 
 |  | ||||||
| Plugin discovery order at tool startup |  | ||||||
| -------------------------------------- |  | ||||||
| 
 |  | ||||||
| ``pytest`` loads plugin modules at tool startup in the following way: |  | ||||||
| 
 |  | ||||||
| * by loading all builtin plugins |  | ||||||
| 
 |  | ||||||
| * by loading all plugins registered through `setuptools entry points`_. |  | ||||||
| 
 |  | ||||||
| * by pre-scanning the command line for the ``-p name`` option |  | ||||||
|   and loading the specified plugin before actual command line parsing. |  | ||||||
| 
 |  | ||||||
| * by loading all :file:`conftest.py` files as inferred by the command line |  | ||||||
|   invocation: |  | ||||||
| 
 |  | ||||||
|   - if no test paths are specified use current dir as a test path |  | ||||||
|   - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative |  | ||||||
|     to the directory part of the first test path. |  | ||||||
| 
 |  | ||||||
|   Note that pytest does not find ``conftest.py`` files in deeper nested |  | ||||||
|   sub directories at tool startup.  It is usually a good idea to keep |  | ||||||
|   your conftest.py file in the top level test or project root directory. |  | ||||||
| 
 |  | ||||||
| * by recursively loading all plugins specified by the |  | ||||||
|   ``pytest_plugins`` variable in ``conftest.py`` files |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Requiring/Loading plugins in a test module or conftest file | Requiring/Loading plugins in a test module or conftest file | ||||||
| ----------------------------------------------------------- | ----------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| You can require plugins in a test module or a conftest file like this:: | You can require plugins in a test module or a conftest file like this:: | ||||||
| 
 | 
 | ||||||
|     pytest_plugins = "name1", "name2", |     pytest_plugins = "myapp.testsupport.myplugin", | ||||||
| 
 | 
 | ||||||
| When the test module or conftest plugin is loaded the specified plugins | When the test module or conftest plugin is loaded the specified plugins | ||||||
| will be loaded as well.  You can also use dotted path like this:: | will be loaded as well. | ||||||
| 
 | 
 | ||||||
|     pytest_plugins = "myapp.testsupport.myplugin" |     pytest_plugins = "myapp.testsupport.myplugin" | ||||||
| 
 | 
 | ||||||
| which will import the specified module as a ``pytest`` plugin. | which will import the specified module as a ``pytest`` 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. |  | ||||||
| 
 |  | ||||||
| .. _`findpluginname`: | .. _`findpluginname`: | ||||||
| 
 | 
 | ||||||
| Finding out which plugins are active | Finding out which plugins are active | ||||||
|  | @ -293,223 +145,3 @@ in the `pytest repository <http://bitbucket.org/pytest-dev/pytest/>`_. | ||||||
|     _pytest.tmpdir |     _pytest.tmpdir | ||||||
|     _pytest.unittest |     _pytest.unittest | ||||||
| 
 | 
 | ||||||
| .. _`well specified hooks`: |  | ||||||
| 
 |  | ||||||
| pytest hook reference |  | ||||||
| ===================== |  | ||||||
| 
 |  | ||||||
| Hook specification and validation |  | ||||||
| --------------------------------- |  | ||||||
| 
 |  | ||||||
| ``pytest`` calls hook functions to implement initialization, running, |  | ||||||
| test execution and reporting.  When ``pytest`` loads a plugin it validates |  | ||||||
| that each hook function conforms to its respective hook specification. |  | ||||||
| Each hook function name and its argument names need to match a hook |  | ||||||
| specification.  However, a hook function may accept *fewer* parameters |  | ||||||
| by simply not specifying them.  If you mistype argument names or the |  | ||||||
| hook name itself you get an error showing the available arguments. |  | ||||||
| 
 |  | ||||||
| Initialization, command line and configuration hooks |  | ||||||
| ---------------------------------------------------- |  | ||||||
| 
 |  | ||||||
| .. currentmodule:: _pytest.hookspec |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_load_initial_conftests |  | ||||||
| .. autofunction:: pytest_cmdline_preparse |  | ||||||
| .. autofunction:: pytest_cmdline_parse |  | ||||||
| .. autofunction:: pytest_namespace |  | ||||||
| .. autofunction:: pytest_addoption |  | ||||||
| .. autofunction:: pytest_cmdline_main |  | ||||||
| .. autofunction:: pytest_configure |  | ||||||
| .. autofunction:: pytest_unconfigure |  | ||||||
| 
 |  | ||||||
| Generic "runtest" hooks |  | ||||||
| ----------------------- |  | ||||||
| 
 |  | ||||||
| All runtest related hooks receive a :py:class:`pytest.Item` object. |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_runtest_protocol |  | ||||||
| .. autofunction:: pytest_runtest_setup |  | ||||||
| .. autofunction:: pytest_runtest_call |  | ||||||
| .. autofunction:: pytest_runtest_teardown |  | ||||||
| .. autofunction:: pytest_runtest_makereport |  | ||||||
| 
 |  | ||||||
| For deeper understanding you may look at the default implementation of |  | ||||||
| these hooks in :py:mod:`_pytest.runner` and maybe also |  | ||||||
| in :py:mod:`_pytest.pdb` which interacts with :py:mod:`_pytest.capture` |  | ||||||
| and its input/output capturing in order to immediately drop |  | ||||||
| into interactive debugging when a test failure occurs. |  | ||||||
| 
 |  | ||||||
| The :py:mod:`_pytest.terminal` reported specifically uses |  | ||||||
| the reporting hook to print information about a test run. |  | ||||||
| 
 |  | ||||||
| Collection hooks |  | ||||||
| ---------------- |  | ||||||
| 
 |  | ||||||
| ``pytest`` calls the following hooks for collecting files and directories: |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_ignore_collect |  | ||||||
| .. autofunction:: pytest_collect_directory |  | ||||||
| .. autofunction:: pytest_collect_file |  | ||||||
| 
 |  | ||||||
| For influencing the collection of objects in Python modules |  | ||||||
| you can use the following hook: |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_pycollect_makeitem |  | ||||||
| .. autofunction:: pytest_generate_tests |  | ||||||
| 
 |  | ||||||
| After collection is complete, you can modify the order of |  | ||||||
| items, delete or otherwise amend the test items: |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_collection_modifyitems |  | ||||||
| 
 |  | ||||||
| Reporting hooks |  | ||||||
| --------------- |  | ||||||
| 
 |  | ||||||
| Session related reporting hooks: |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_collectstart |  | ||||||
| .. autofunction:: pytest_itemcollected |  | ||||||
| .. autofunction:: pytest_collectreport |  | ||||||
| .. autofunction:: pytest_deselected |  | ||||||
| 
 |  | ||||||
| And here is the central hook for reporting about |  | ||||||
| test execution: |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_runtest_logreport |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Debugging/Interaction hooks |  | ||||||
| --------------------------- |  | ||||||
| 
 |  | ||||||
| There are few hooks which can be used for special |  | ||||||
| reporting or interaction with exceptions: |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_internalerror |  | ||||||
| .. autofunction:: pytest_keyboard_interrupt |  | ||||||
| .. autofunction:: pytest_exception_interact |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Declaring new hooks |  | ||||||
| ------------------------ |  | ||||||
| 
 |  | ||||||
| Plugins and ``conftest.py`` files may declare new hooks that can then be |  | ||||||
| implemented by other plugins in order to alter behaviour or interact with |  | ||||||
| the new plugin: |  | ||||||
| 
 |  | ||||||
| .. autofunction:: pytest_addhooks |  | ||||||
| 
 |  | ||||||
| Hooks are usually declared as do-nothing functions that contain only |  | ||||||
| documentation describing when the hook will be called and what return values |  | ||||||
| are expected. |  | ||||||
| 
 |  | ||||||
| For an example, see `newhooks.py`_ from :ref:`xdist`. |  | ||||||
| 
 |  | ||||||
| .. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Using hooks from 3rd party plugins |  | ||||||
| ------------------------------------- |  | ||||||
| 
 |  | ||||||
| Using new hooks from plugins as explained above might be a little tricky |  | ||||||
| because the standard `Hook specification and validation`_ mechanism: |  | ||||||
| if you depend on a plugin that is not installed, |  | ||||||
| validation will fail and the error message will not make much sense to your users. |  | ||||||
| 
 |  | ||||||
| One approach is to defer the hook implementation to a new plugin instead of |  | ||||||
| declaring the hook functions directly in your plugin module, for example:: |  | ||||||
| 
 |  | ||||||
|     # contents of myplugin.py |  | ||||||
| 
 |  | ||||||
|     class DeferPlugin(object): |  | ||||||
|         """Simple plugin to defer pytest-xdist hook functions.""" |  | ||||||
| 
 |  | ||||||
|         def pytest_testnodedown(self, node, error): |  | ||||||
|             """standard xdist hook function. |  | ||||||
|             """ |  | ||||||
| 
 |  | ||||||
|     def pytest_configure(config): |  | ||||||
|         if config.pluginmanager.hasplugin('xdist'): |  | ||||||
|             config.pluginmanager.register(DeferPlugin()) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| This has the added benefit of allowing you to conditionally install hooks |  | ||||||
| depending on which plugins are installed. |  | ||||||
| 
 |  | ||||||
| hookwrapper: executing around other hooks |  | ||||||
| ------------------------------------------------- |  | ||||||
| 
 |  | ||||||
| .. currentmodule:: _pytest.core |  | ||||||
| 
 |  | ||||||
| .. versionadded:: 2.7 (experimental) |  | ||||||
| 
 |  | ||||||
| pytest plugins can implement hook wrappers which which wrap the execution |  | ||||||
| of other hook implementations.  A hook wrapper is a generator function |  | ||||||
| which yields exactly once. When pytest invokes hooks it first executes |  | ||||||
| hook wrappers and passes the same arguments as to the regular hooks. |  | ||||||
| 
 |  | ||||||
| At the yield point of the hook wrapper pytest will execute the next hook |  | ||||||
| implementations and return their result to the yield point in the form of |  | ||||||
| a :py:class:`CallOutcome` instance which encapsulates a result or |  | ||||||
| exception info.  The yield point itself will thus typically not raise |  | ||||||
| exceptions (unless there are bugs). |  | ||||||
| 
 |  | ||||||
| Here is an example definition of a hook wrapper:: |  | ||||||
| 
 |  | ||||||
|     import pytest |  | ||||||
| 
 |  | ||||||
|     @pytest.hookimpl_opts(hookwrapper=True) |  | ||||||
|     def pytest_pyfunc_call(pyfuncitem): |  | ||||||
|         # do whatever you want before the next hook executes |  | ||||||
|         outcome = yield |  | ||||||
|         # outcome.excinfo may be None or a (cls, val, tb) tuple |  | ||||||
|         res = outcome.get_result()  # will raise if outcome was exception |  | ||||||
|         # postprocess result |  | ||||||
| 
 |  | ||||||
| Note that hook wrappers don't return results themselves, they merely |  | ||||||
| perform tracing or other side effects around the actual hook implementations. |  | ||||||
| If the result of the underlying hook is a mutable object, they may modify |  | ||||||
| that result, however. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Reference of objects involved in hooks |  | ||||||
| ====================================== |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.config.Config() |  | ||||||
|     :members: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.config.Parser() |  | ||||||
|     :members: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.main.Node() |  | ||||||
|     :members: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.main.Collector() |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.main.Item() |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.python.Module() |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.python.Class() |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.python.Function() |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.runner.CallInfo() |  | ||||||
|     :members: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.runner.TestReport() |  | ||||||
|     :members: |  | ||||||
| 
 |  | ||||||
| .. autoclass:: _pytest.core.CallOutcome() |  | ||||||
|     :members: |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,469 @@ | ||||||
|  | .. _plugins: | ||||||
|  | .. _`writing-plugins`: | ||||||
|  | 
 | ||||||
|  | Writing plugins | ||||||
|  | =============== | ||||||
|  | 
 | ||||||
|  | It is easy to implement `local conftest plugins`_ for your own project | ||||||
|  | or `pip-installable plugins`_ that can be used throughout many projects, | ||||||
|  | including third party projects.  Please refer to :ref:`using plugins` if you | ||||||
|  | only want to use but not write plugins. | ||||||
|  | 
 | ||||||
|  | A plugin contains one or multiple hook functions. :ref:`Writing hooks <writinghooks>` | ||||||
|  | explains the basics and details of how you can write a hook function yourself. | ||||||
|  | ``pytest`` implements all aspects of configuration, collection, running and | ||||||
|  | reporting by calling `well specified hooks`_ of the following plugins: | ||||||
|  | 
 | ||||||
|  | * :ref:`builtin plugins`: loaded from pytest's internal ``_pytest`` directory. | ||||||
|  | 
 | ||||||
|  | * :ref:`external plugins <extplugin>`: modules discovered through | ||||||
|  |   `setuptools entry points`_ | ||||||
|  | 
 | ||||||
|  | * `conftest.py plugins`_: modules auto-discovered in test directories | ||||||
|  | 
 | ||||||
|  | In principle, each hook call is a ``1:N`` Python function call where ``N`` is the | ||||||
|  | number of registered implementation functions for a given specification. | ||||||
|  | All specifications and implementations following the ``pytest_`` prefix | ||||||
|  | naming convention, making them easy to distinguish and find. | ||||||
|  | 
 | ||||||
|  | .. _`pluginorder`: | ||||||
|  | 
 | ||||||
|  | Plugin discovery order at tool startup | ||||||
|  | -------------------------------------- | ||||||
|  | 
 | ||||||
|  | ``pytest`` loads plugin modules at tool startup in the following way: | ||||||
|  | 
 | ||||||
|  | * by loading all builtin plugins | ||||||
|  | 
 | ||||||
|  | * by loading all plugins registered through `setuptools entry points`_. | ||||||
|  | 
 | ||||||
|  | * by pre-scanning the command line for the ``-p name`` option | ||||||
|  |   and loading the specified plugin before actual command line parsing. | ||||||
|  | 
 | ||||||
|  | * by loading all :file:`conftest.py` files as inferred by the command line | ||||||
|  |   invocation: | ||||||
|  | 
 | ||||||
|  |   - if no test paths are specified use current dir as a test path | ||||||
|  |   - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative | ||||||
|  |     to the directory part of the first test path. | ||||||
|  | 
 | ||||||
|  |   Note that pytest does not find ``conftest.py`` files in deeper nested | ||||||
|  |   sub directories at tool startup.  It is usually a good idea to keep | ||||||
|  |   your conftest.py file in the top level test or project root directory. | ||||||
|  | 
 | ||||||
|  | * by recursively loading all plugins specified by the | ||||||
|  |   ``pytest_plugins`` variable in ``conftest.py`` files | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/ | ||||||
|  | .. _`conftest.py plugins`: | ||||||
|  | .. _`conftest.py`: | ||||||
|  | .. _`localplugin`: | ||||||
|  | .. _`conftest`: | ||||||
|  | .. _`local conftest plugins`: | ||||||
|  | 
 | ||||||
|  | conftest.py: local per-directory plugins | ||||||
|  | ---------------------------------------- | ||||||
|  | 
 | ||||||
|  | Local ``conftest.py`` plugins contain directory-specific hook | ||||||
|  | implementations.  Hook Session and test running activities will | ||||||
|  | invoke all hooks defined in ``conftest.py`` files closer to the | ||||||
|  | root of the filesystem.  Example of implementing the | ||||||
|  | ``pytest_runtest_setup`` hook so that is called for tests in the ``a`` | ||||||
|  | sub directory but not for other directories:: | ||||||
|  | 
 | ||||||
|  |     a/conftest.py: | ||||||
|  |         def pytest_runtest_setup(item): | ||||||
|  |             # called for running each test in 'a' directory | ||||||
|  |             print ("setting up", item) | ||||||
|  | 
 | ||||||
|  |     a/test_sub.py: | ||||||
|  |         def test_sub(): | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  |     test_flat.py: | ||||||
|  |         def test_flat(): | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  | Here is how you might run it:: | ||||||
|  | 
 | ||||||
|  |      py.test test_flat.py   # will not show "setting up" | ||||||
|  |      py.test a/test_sub.py  # will show "setting up" | ||||||
|  | 
 | ||||||
|  | .. Note:: | ||||||
|  |     If you have ``conftest.py`` files which do not reside in a | ||||||
|  |     python package directory (i.e. one containing an ``__init__.py``) then | ||||||
|  |     "import conftest" can be ambiguous because there might be other | ||||||
|  |     ``conftest.py`` files as well on your PYTHONPATH or ``sys.path``. | ||||||
|  |     It is thus good practise for projects to either put ``conftest.py`` | ||||||
|  |     under a package scope or to never import anything from a | ||||||
|  |     conftest.py file. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Writing a plugin by looking at examples | ||||||
|  | --------------------------------------- | ||||||
|  | 
 | ||||||
|  | .. _`setuptools`: http://pypi.python.org/pypi/setuptools | ||||||
|  | 
 | ||||||
|  | If you want to write a plugin, there are many real-life examples | ||||||
|  | you can copy from: | ||||||
|  | 
 | ||||||
|  | * a custom collection example plugin: :ref:`yaml plugin` | ||||||
|  | * around 20 doc:`builtin plugins` which provide pytest's own functionality | ||||||
|  | * many :doc:`external plugins` providing additional features | ||||||
|  | 
 | ||||||
|  | All of these plugins implement the documented `well specified hooks`_ | ||||||
|  | to extend and add functionality. | ||||||
|  | 
 | ||||||
|  | You can also :ref:`contribute your plugin to pytest-dev<submitplugin>` | ||||||
|  | once it has some happy users other than yourself. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .. _`setuptools entry points`: | ||||||
|  | .. _`pip-installable plugins`: | ||||||
|  | 
 | ||||||
|  | Making your plugin installable by others | ||||||
|  | ---------------------------------------- | ||||||
|  | 
 | ||||||
|  | If you want to make your plugin externally available, you | ||||||
|  | may define a so-called entry point for your distribution so | ||||||
|  | that ``pytest`` finds your plugin module.  Entry points are | ||||||
|  | a feature that is provided by `setuptools`_. pytest looks up | ||||||
|  | the ``pytest11`` entrypoint to discover its | ||||||
|  | plugins and you can thus make your plugin available by defining | ||||||
|  | it in your setuptools-invocation: | ||||||
|  | 
 | ||||||
|  | .. sourcecode:: python | ||||||
|  | 
 | ||||||
|  |     # sample ./setup.py file | ||||||
|  |     from setuptools import setup | ||||||
|  | 
 | ||||||
|  |     setup( | ||||||
|  |         name="myproject", | ||||||
|  |         packages = ['myproject'] | ||||||
|  | 
 | ||||||
|  |         # the following makes a plugin available to pytest | ||||||
|  |         entry_points = { | ||||||
|  |             'pytest11': [ | ||||||
|  |                 'name_of_plugin = myproject.pluginmodule', | ||||||
|  |             ] | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | If a package is installed this way, ``pytest`` will load | ||||||
|  | ``myproject.pluginmodule`` as a plugin which can define | ||||||
|  | `well specified hooks`_. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Requiring/Loading plugins in a test module or conftest file | ||||||
|  | ----------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | You can require plugins in a test module or a conftest file like this:: | ||||||
|  | 
 | ||||||
|  |     pytest_plugins = "name1", "name2", | ||||||
|  | 
 | ||||||
|  | When the test module or conftest plugin is loaded the specified plugins | ||||||
|  | will be loaded as well.  You can also use dotted path like this:: | ||||||
|  | 
 | ||||||
|  |     pytest_plugins = "myapp.testsupport.myplugin" | ||||||
|  | 
 | ||||||
|  | which will import the specified module as a ``pytest`` 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. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .. _`writinghooks`: | ||||||
|  | 
 | ||||||
|  | Writing hook functions | ||||||
|  | ====================== | ||||||
|  | 
 | ||||||
|  | .. _validation: | ||||||
|  | 
 | ||||||
|  | hook function validation and execution | ||||||
|  | -------------------------------------- | ||||||
|  | 
 | ||||||
|  | pytest calls hook functions from registered plugins for any | ||||||
|  | given hook specification.  Let's look at a typical hook function | ||||||
|  | for the ``pytest_collection_modifyitems(session, config, | ||||||
|  | items)`` hook which pytest calls after collection of all test items is | ||||||
|  | completed. | ||||||
|  | 
 | ||||||
|  | When we implement a ``pytest_collection_modifyitems`` function in our plugin | ||||||
|  | pytest will during registration verify that you use argument | ||||||
|  | names which match the specification and bail out if not. | ||||||
|  | 
 | ||||||
|  | Let's look at a possible implementation:: | ||||||
|  | 
 | ||||||
|  |     def pytest_collection_modifyitems(config, items): | ||||||
|  |         # called after collectin is completed | ||||||
|  |         # you can modify the ``items`` list | ||||||
|  | 
 | ||||||
|  | Here, ``pytest`` will pass in ``config`` (the pytest config object) | ||||||
|  | and ``items`` (the list of collected test items) but will not pass | ||||||
|  | in the ``session`` argument because we didn't list it in the function | ||||||
|  | signature.  This dynamic "pruning" of arguments allows ``pytest`` to | ||||||
|  | be "future-compatible": we can introduce new hook named parameters without | ||||||
|  | breaking the signatures of existing hook implementations.  It is one of | ||||||
|  | the reasons for the general long-lived compatibility of pytest plugins. | ||||||
|  | 
 | ||||||
|  | Hook function results | ||||||
|  | --------------------- | ||||||
|  | 
 | ||||||
|  | Most calls to ``pytest`` hooks result in a **list of results** which contains | ||||||
|  | all non-None results of the called hook functions. | ||||||
|  | 
 | ||||||
|  | Some hooks are specified so that the hook call only executes until the | ||||||
|  | first function returned a non-None value which is then also the | ||||||
|  | result of the overall hook call.  The remaining hook functions will | ||||||
|  | not be called in this case. | ||||||
|  | 
 | ||||||
|  | Note that hook functions other than ``pytest_runtest_*`` are not | ||||||
|  | allowed to raise exceptions.  Doing so will break the pytest run. | ||||||
|  | 
 | ||||||
|  | Hook function ordering | ||||||
|  | ---------------------- | ||||||
|  | 
 | ||||||
|  | For any given hook there may be more than one implementation and we thus | ||||||
|  | generally view ``hook`` execution as a ``1:N`` function call where ``N`` | ||||||
|  | is the number of registered functions.  There are ways to | ||||||
|  | influence if a hook implementation comes before or after others, i.e. | ||||||
|  | the position in the ``N``-sized list of functions:: | ||||||
|  | 
 | ||||||
|  |     @pytest.hookimpl_spec(tryfirst=True) | ||||||
|  |     def pytest_collection_modifyitems(items): | ||||||
|  |         # will execute as early as possible | ||||||
|  | 
 | ||||||
|  |     @pytest.hookimpl_spec(trylast=True) | ||||||
|  |     def pytest_collection_modifyitems(items): | ||||||
|  |         # will execute as late as possible | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | hookwrapper: executing around other hooks | ||||||
|  | ------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | .. currentmodule:: _pytest.core | ||||||
|  | 
 | ||||||
|  | .. versionadded:: 2.7 (experimental) | ||||||
|  | 
 | ||||||
|  | pytest plugins can implement hook wrappers which wrap the execution | ||||||
|  | of other hook implementations.  A hook wrapper is a generator function | ||||||
|  | which yields exactly once. When pytest invokes hooks it first executes | ||||||
|  | hook wrappers and passes the same arguments as to the regular hooks. | ||||||
|  | 
 | ||||||
|  | At the yield point of the hook wrapper pytest will execute the next hook | ||||||
|  | implementations and return their result to the yield point in the form of | ||||||
|  | a :py:class:`CallOutcome` instance which encapsulates a result or | ||||||
|  | exception info.  The yield point itself will thus typically not raise | ||||||
|  | exceptions (unless there are bugs). | ||||||
|  | 
 | ||||||
|  | Here is an example definition of a hook wrapper:: | ||||||
|  | 
 | ||||||
|  |     import pytest | ||||||
|  | 
 | ||||||
|  |     @pytest.hookimpl_opts(hookwrapper=True) | ||||||
|  |     def pytest_pyfunc_call(pyfuncitem): | ||||||
|  |         # do whatever you want before the next hook executes | ||||||
|  | 
 | ||||||
|  |         outcome = yield | ||||||
|  |         # outcome.excinfo may be None or a (cls, val, tb) tuple | ||||||
|  | 
 | ||||||
|  |         res = outcome.get_result()  # will raise if outcome was exception | ||||||
|  |         # postprocess result | ||||||
|  | 
 | ||||||
|  | Note that hook wrappers don't return results themselves, they merely | ||||||
|  | perform tracing or other side effects around the actual hook implementations. | ||||||
|  | If the result of the underlying hook is a mutable object, they may modify | ||||||
|  | that result, however. | ||||||
|  | 
 | ||||||
|  | Declaring new hooks | ||||||
|  | ------------------------ | ||||||
|  | 
 | ||||||
|  | .. currentmodule:: _pytest.hookspec | ||||||
|  | 
 | ||||||
|  | Plugins and ``conftest.py`` files may declare new hooks that can then be | ||||||
|  | implemented by other plugins in order to alter behaviour or interact with | ||||||
|  | the new plugin: | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_addhooks | ||||||
|  | 
 | ||||||
|  | Hooks are usually declared as do-nothing functions that contain only | ||||||
|  | documentation describing when the hook will be called and what return values | ||||||
|  | are expected. | ||||||
|  | 
 | ||||||
|  | For an example, see `newhooks.py`_ from :ref:`xdist`. | ||||||
|  | 
 | ||||||
|  | .. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Using hooks from 3rd party plugins | ||||||
|  | ------------------------------------- | ||||||
|  | 
 | ||||||
|  | Using new hooks from plugins as explained above might be a little tricky | ||||||
|  | because the standard :ref:`validation mechanism <validation>`: | ||||||
|  | if you depend on a plugin that is not installed, validation will fail and | ||||||
|  | the error message will not make much sense to your users. | ||||||
|  | 
 | ||||||
|  | One approach is to defer the hook implementation to a new plugin instead of | ||||||
|  | declaring the hook functions directly in your plugin module, for example:: | ||||||
|  | 
 | ||||||
|  |     # contents of myplugin.py | ||||||
|  | 
 | ||||||
|  |     class DeferPlugin(object): | ||||||
|  |         """Simple plugin to defer pytest-xdist hook functions.""" | ||||||
|  | 
 | ||||||
|  |         def pytest_testnodedown(self, node, error): | ||||||
|  |             """standard xdist hook function. | ||||||
|  |             """ | ||||||
|  | 
 | ||||||
|  |     def pytest_configure(config): | ||||||
|  |         if config.pluginmanager.hasplugin('xdist'): | ||||||
|  |             config.pluginmanager.register(DeferPlugin()) | ||||||
|  | 
 | ||||||
|  | This has the added benefit of allowing you to conditionally install hooks | ||||||
|  | depending on which plugins are installed. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .. _`well specified hooks`: | ||||||
|  | 
 | ||||||
|  | .. currentmodule:: _pytest.hookspec | ||||||
|  | 
 | ||||||
|  | pytest hook reference | ||||||
|  | ===================== | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Initialization, command line and configuration hooks | ||||||
|  | ---------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_load_initial_conftests | ||||||
|  | .. autofunction:: pytest_cmdline_preparse | ||||||
|  | .. autofunction:: pytest_cmdline_parse | ||||||
|  | .. autofunction:: pytest_namespace | ||||||
|  | .. autofunction:: pytest_addoption | ||||||
|  | .. autofunction:: pytest_cmdline_main | ||||||
|  | .. autofunction:: pytest_configure | ||||||
|  | .. autofunction:: pytest_unconfigure | ||||||
|  | 
 | ||||||
|  | Generic "runtest" hooks | ||||||
|  | ----------------------- | ||||||
|  | 
 | ||||||
|  | All runtest related hooks receive a :py:class:`pytest.Item` object. | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_runtest_protocol | ||||||
|  | .. autofunction:: pytest_runtest_setup | ||||||
|  | .. autofunction:: pytest_runtest_call | ||||||
|  | .. autofunction:: pytest_runtest_teardown | ||||||
|  | .. autofunction:: pytest_runtest_makereport | ||||||
|  | 
 | ||||||
|  | For deeper understanding you may look at the default implementation of | ||||||
|  | these hooks in :py:mod:`_pytest.runner` and maybe also | ||||||
|  | in :py:mod:`_pytest.pdb` which interacts with :py:mod:`_pytest.capture` | ||||||
|  | and its input/output capturing in order to immediately drop | ||||||
|  | into interactive debugging when a test failure occurs. | ||||||
|  | 
 | ||||||
|  | The :py:mod:`_pytest.terminal` reported specifically uses | ||||||
|  | the reporting hook to print information about a test run. | ||||||
|  | 
 | ||||||
|  | Collection hooks | ||||||
|  | ---------------- | ||||||
|  | 
 | ||||||
|  | ``pytest`` calls the following hooks for collecting files and directories: | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_ignore_collect | ||||||
|  | .. autofunction:: pytest_collect_directory | ||||||
|  | .. autofunction:: pytest_collect_file | ||||||
|  | 
 | ||||||
|  | For influencing the collection of objects in Python modules | ||||||
|  | you can use the following hook: | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_pycollect_makeitem | ||||||
|  | .. autofunction:: pytest_generate_tests | ||||||
|  | 
 | ||||||
|  | After collection is complete, you can modify the order of | ||||||
|  | items, delete or otherwise amend the test items: | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_collection_modifyitems | ||||||
|  | 
 | ||||||
|  | Reporting hooks | ||||||
|  | --------------- | ||||||
|  | 
 | ||||||
|  | Session related reporting hooks: | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_collectstart | ||||||
|  | .. autofunction:: pytest_itemcollected | ||||||
|  | .. autofunction:: pytest_collectreport | ||||||
|  | .. autofunction:: pytest_deselected | ||||||
|  | 
 | ||||||
|  | And here is the central hook for reporting about | ||||||
|  | test execution: | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_runtest_logreport | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Debugging/Interaction hooks | ||||||
|  | --------------------------- | ||||||
|  | 
 | ||||||
|  | There are few hooks which can be used for special | ||||||
|  | reporting or interaction with exceptions: | ||||||
|  | 
 | ||||||
|  | .. autofunction:: pytest_internalerror | ||||||
|  | .. autofunction:: pytest_keyboard_interrupt | ||||||
|  | .. autofunction:: pytest_exception_interact | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Reference of objects involved in hooks | ||||||
|  | ====================================== | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.config.Config() | ||||||
|  |     :members: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.config.Parser() | ||||||
|  |     :members: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.main.Node() | ||||||
|  |     :members: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.main.Collector() | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.main.Item() | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.python.Module() | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.python.Class() | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.python.Function() | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.runner.CallInfo() | ||||||
|  |     :members: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.runner.TestReport() | ||||||
|  |     :members: | ||||||
|  | 
 | ||||||
|  | .. autoclass:: _pytest.core.CallOutcome() | ||||||
|  |     :members: | ||||||
|  | 
 | ||||||
		Loading…
	
		Reference in New Issue