.. _`warnings`:

Warnings Capture
================

.. versionadded:: 3.1

Starting from version ``3.1``, pytest now automatically catches warnings during test execution
and displays them at the end of the session::

    # content of test_show_warnings.py
    import warnings

    def api_v1():
        warnings.warn(UserWarning("api v1, should use functions from v2"))
        return 1

    def test_one():
        assert api_v1() == 1

Running pytest now produces this output::

    $ pytest test_show_warnings.py
    ======= test session starts ========
    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
    rootdir: $REGENDOC_TMPDIR, inifile:
    collected 1 item
    
    test_show_warnings.py .
    
    ======= warnings summary ========
    test_show_warnings.py::test_one
      $REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
        warnings.warn(UserWarning("api v1, should use functions from v2"))
    
    -- Docs: http://doc.pytest.org/en/latest/warnings.html
    ======= 1 passed, 1 warnings in 0.12 seconds ========

Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``.

The ``-W`` flag can be passed to control which warnings will be displayed or even turn
them into errors::

    $ pytest -q test_show_warnings.py -W error::UserWarning
    F
    ======= FAILURES ========
    _______ test_one ________
    
        def test_one():
    >       assert api_v1() == 1
    
    test_show_warnings.py:8: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
        def api_v1():
    >       warnings.warn(UserWarning("api v1, should use functions from v2"))
    E       UserWarning: api v1, should use functions from v2
    
    test_show_warnings.py:4: UserWarning
    1 failed in 0.12 seconds

The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
For example, the configuration below will ignore all user warnings, but will transform
all other warnings into errors.

.. code-block:: ini

    [pytest]
    filterwarnings =
        error
        ignore::UserWarning


When a warning matches more than one option in the list, the action for the last matching option
is performed.

Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
documentation for other examples and advanced usage.

``@pytest.mark.filterwarnings``
-------------------------------

.. versionadded:: 3.2

You can use the ``@pytest.mark.filterwarnings`` to add warning filters to specific test items,
allowing you to have finer control of which warnings should be captured at test, class or
even module level:

.. code-block:: python

    import warnings

    def api_v1():
        warnings.warn(UserWarning("api v1, should use functions from v2"))
        return 1

    @pytest.mark.filterwarnings('ignore:api v1')
    def test_one():
        assert api_v1() == 1


Filters applied using a mark take precedence over filters passed on the command line or configured
by the ``filterwarnings`` ini option.

You may apply a filter to all tests of a class by using the ``filterwarnings`` mark as a class
decorator or to all tests in a module by setting the ``pytestmark`` variable:

.. code-block:: python

    # turns all warnings into errors for this module
    pytestmark = @pytest.mark.filterwarnings('error')


.. note::

    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are hidden by the standard library
    by default so you have to explicitly configure them to be displayed in your ``pytest.ini``:

    .. code-block:: ini

        [pytest]
        filterwarnings =
            once::DeprecationWarning
            once::PendingDeprecationWarning


*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
*plugin.*

.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W
.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings


Disabling warning capture
-------------------------

This feature is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:

    .. code-block:: ini

        [pytest]
        addopts = -p no:warnings

Or passing ``-p no:warnings`` in the command-line.

.. _`asserting warnings`:

.. _assertwarnings:

.. _`asserting warnings with the warns function`:

.. _warns:

Asserting warnings with the warns function
-----------------------------------------------

.. versionadded:: 2.8

You can check that code raises a particular warning using ``pytest.warns``,
which works in a similar manner to :ref:`raises <assertraises>`::

    import warnings
    import pytest

    def test_warning():
        with pytest.warns(UserWarning):
            warnings.warn("my warning", UserWarning)

The test will fail if the warning in question is not raised.

You can also call ``pytest.warns`` on a function or code string::

    pytest.warns(expected_warning, func, *args, **kwargs)
    pytest.warns(expected_warning, "func(*args, **kwargs)")

The function also returns a list of all raised warnings (as
``warnings.WarningMessage`` objects), which you can query for
additional information::

    with pytest.warns(RuntimeWarning) as record:
        warnings.warn("another warning", RuntimeWarning)

    # check that only one warning was raised
    assert len(record) == 1
    # check that the message matches
    assert record[0].message.args[0] == "another warning"

Alternatively, you can examine raised warnings in detail using the
:ref:`recwarn <recwarn>` fixture (see below).

.. note::
    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
    differently; see :ref:`ensuring_function_triggers`.

.. _`recording warnings`:

.. _recwarn:

Recording warnings
------------------------

You can record raised warnings either using ``pytest.warns`` or with
the ``recwarn`` fixture.

To record with ``pytest.warns`` without asserting anything about the warnings,
pass ``None`` as the expected warning type::

    with pytest.warns(None) as record:
        warnings.warn("user", UserWarning)
        warnings.warn("runtime", RuntimeWarning)

    assert len(record) == 2
    assert str(record[0].message) == "user"
    assert str(record[1].message) == "runtime"

The ``recwarn`` fixture will record warnings for the whole function::

    import warnings

    def test_hello(recwarn):
        warnings.warn("hello", UserWarning)
        assert len(recwarn) == 1
        w = recwarn.pop(UserWarning)
        assert issubclass(w.category, UserWarning)
        assert str(w.message) == "hello"
        assert w.filename
        assert w.lineno

Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
warnings: a WarningsRecorder instance. To view the recorded warnings, you can
iterate over this instance, call ``len`` on it to get the number of recorded
warnings, or index into it to get a particular recorded warning. It also
provides these methods:

.. autoclass:: _pytest.recwarn.WarningsRecorder()
    :members:

Each recorded warning has the attributes ``message``, ``category``,
``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
class of the warning. The ``message`` is the warning itself; calling
``str(message)`` will return the actual message of the warning.

.. note::
    :class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1

.. note::
    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
    differently; see :ref:`ensuring_function_triggers`.

.. _`ensuring a function triggers a deprecation warning`:

.. _ensuring_function_triggers:

Ensuring a function triggers a deprecation warning
-------------------------------------------------------

You can also call a global helper for checking
that a certain function call triggers a ``DeprecationWarning`` or
``PendingDeprecationWarning``::

    import pytest

    def test_global():
        pytest.deprecated_call(myfunction, 17)

By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
them. If you wish to record them in your own code, use the
command ``warnings.simplefilter('always')``::

    import warnings
    import pytest

    def test_deprecation(recwarn):
        warnings.simplefilter('always')
        warnings.warn("deprecated", DeprecationWarning)
        assert len(recwarn) == 1
        assert recwarn.pop(DeprecationWarning)

You can also use it as a contextmanager::

    def test_global():
        with pytest.deprecated_call():
            myobject.deprecated_method()
