diff --git a/_pytest/warnings.py b/_pytest/warnings.py index cbed43b8f..b227dd68a 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -53,7 +53,6 @@ def catch_warnings_for_item(item): args = item.config.getoption('pythonwarnings') or [] inifilters = item.config.getini("filterwarnings") with warnings.catch_warnings(record=True) as log: - warnings.simplefilter('once') for arg in args: warnings._setoption(arg) diff --git a/changelog/2390.doc b/changelog/2390.doc index 2d255b0e1..460107dc7 100644 --- a/changelog/2390.doc +++ b/changelog/2390.doc @@ -1 +1 @@ -initial addition of towncrier +Addition of towncrier for changelog management. diff --git a/changelog/2430.bugfix b/changelog/2430.bugfix new file mode 100644 index 000000000..26dd6b248 --- /dev/null +++ b/changelog/2430.bugfix @@ -0,0 +1,4 @@ +pytest warning capture no longer overrides existing warning filters. The previous +behaviour would override all filters and caused regressions in test suites which configure warning +filters to match their needs. Note that as a side-effect of this is that ``DeprecationWarning`` +and ``PendingDeprecationWarning`` are no longer shown by default. diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 20ea00a65..8633adca4 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -5,32 +5,18 @@ Warnings Capture .. versionadded:: 3.1 -.. warning:: - pytest captures all warnings between tests, which prevents custom warning - filters in existing test suites from working. If this causes problems to your test suite, - this plugin can be disabled in your ``pytest.ini`` file with: - - .. code-block:: ini - - [pytest] - addopts = -p no:warnings - - There's an ongoing discussion about this on `#2430 - `_. - - -Starting from version ``3.1``, pytest now automatically catches all warnings during test execution +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 deprecated_function(): - warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) + def api_v1(): + warnings.warn(UserWarning("api v1, should use functions from v2")) return 1 def test_one(): - assert deprecated_function() == 1 + assert api_v1() == 1 Running pytest now produces this output:: @@ -39,40 +25,42 @@ Running pytest now produces this output:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items - + test_show_warnings.py . - + ======= warnings summary ======== test_show_warnings.py::test_one $REGENDOC_TMPDIR/test_show_warnings.py:4: DeprecationWarning: this function is deprecated, use another_function() warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) - + -- 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::DeprecationWarning + $ pytest -q test_show_warnings.py -W error::UserWarning F ======= FAILURES ======== _______ test_one ________ - + def test_one(): > assert deprecated_function() == 1 - - test_show_warnings.py:8: - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - + + test_show_warnings.py:8: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + def deprecated_function(): > warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) E DeprecationWarning: this function is deprecated, use another_function() - + test_show_warnings.py:4: DeprecationWarning 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 deprecation warnings, but will transform +For example, the configuration below will ignore all user warnings, but will transform all other warnings into errors. .. code-block:: ini @@ -80,7 +68,7 @@ all other warnings into errors. [pytest] filterwarnings = error - ignore::DeprecationWarning + ignore::UserWarning When a warning matches more than one option in the list, the action for the last matching option @@ -90,6 +78,19 @@ Both ``-W`` command-line option and ``filterwarnings`` ini option are based on P `-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python documentation for other examples and advanced usage. +.. 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.* @@ -97,6 +98,19 @@ documentation for other examples and advanced usage. .. _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: diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 2a379c6ce..75dacc040 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -2,6 +2,8 @@ from __future__ import absolute_import, division, print_function import warnings import re import py +import sys + import pytest from _pytest.recwarn import WarningsRecorder @@ -149,9 +151,12 @@ class TestDeprecatedCall(object): pytest.deprecated_call(deprecated_function) """) result = testdir.runpytest() - # the 2 tests must pass, but the call to test_one() will generate a warning - # in pytest's summary - result.stdout.fnmatch_lines('*=== 2 passed, 1 warnings in *===') + # for some reason in py26 catch_warnings manages to catch the deprecation warning + # from deprecated_function(), even with default filters active (which ignore deprecation + # warnings) + py26 = sys.version_info[:2] == (2, 6) + expected = '*=== 2 passed in *===' if not py26 else '*=== 2 passed, 1 warnings in *===' + result.stdout.fnmatch_lines(expected) class TestWarns(object): diff --git a/testing/test_warnings.py b/testing/test_warnings.py index df9c4cdc4..ca4345abc 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -20,8 +20,8 @@ def pyfile_with_warnings(testdir, request): module_name: ''' import warnings def foo(): - warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) - warnings.warn(DeprecationWarning("functionality is deprecated")) + warnings.warn(UserWarning("user warning")) + warnings.warn(RuntimeWarning("runtime warning")) return 1 ''', test_name: ''' @@ -43,11 +43,11 @@ def test_normal_flow(testdir, pyfile_with_warnings): '*test_normal_flow.py::test_func', - '*normal_flow_module.py:3: PendingDeprecationWarning: functionality is pending deprecation', - '* warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', + '*normal_flow_module.py:3: UserWarning: user warning', + '* warnings.warn(UserWarning("user warning"))', - '*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated', - '* warnings.warn(DeprecationWarning("functionality is deprecated"))', + '*normal_flow_module.py:4: RuntimeWarning: runtime warning', + '* warnings.warn(RuntimeWarning("runtime warning"))', '* 1 passed, 2 warnings*', ]) assert result.stdout.str().count('test_normal_flow.py::test_func') == 1 @@ -90,8 +90,8 @@ def test_as_errors(testdir, pyfile_with_warnings, method): ''') result = testdir.runpytest(*args) result.stdout.fnmatch_lines([ - 'E PendingDeprecationWarning: functionality is pending deprecation', - 'as_errors_module.py:3: PendingDeprecationWarning', + 'E UserWarning: user warning', + 'as_errors_module.py:3: UserWarning', '* 1 failed in *', ]) @@ -133,9 +133,7 @@ def test_unicode(testdir, pyfile_with_warnings): result = testdir.runpytest() result.stdout.fnmatch_lines([ '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - - '*test_unicode.py:8: UserWarning: \u6d4b\u8bd5', - '*warnings.warn(u"\u6d4b\u8bd5")', + '*test_unicode.py:8: UserWarning: \u6d4b\u8bd5*', '* 1 passed, 1 warnings*', ]) @@ -163,6 +161,30 @@ def test_py2_unicode(testdir, pyfile_with_warnings): '*test_py2_unicode.py:8: UserWarning: \u6d4b\u8bd5', '*warnings.warn(u"\u6d4b\u8bd5")', - '*warnings.py:82: UnicodeWarning: This warning*\u6d4b\u8bd5', + '*warnings.py:*: UnicodeWarning: This warning*\u6d4b\u8bd5', '* 1 passed, 2 warnings*', ]) + + +def test_works_with_filterwarnings(testdir): + """Ensure our warnings capture does not mess with pre-installed filters (#2430).""" + testdir.makepyfile(''' + import warnings + + class MyWarning(Warning): + pass + + warnings.filterwarnings("error", category=MyWarning) + + class TestWarnings(object): + def test_my_warning(self): + try: + warnings.warn(MyWarning("warn!")) + assert False + except MyWarning: + assert True + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*== 1 passed in *', + ]) diff --git a/tox.ini b/tox.ini index ecde435da..b73deca7d 100644 --- a/tox.ini +++ b/tox.ini @@ -184,7 +184,7 @@ python_files=test_*.py *_test.py testing/*/*.py python_classes=Test Acceptance python_functions=test norecursedirs = .tox ja .hg cx_freeze_source -filterwarnings= error +filterwarnings= # produced by path.local ignore:bad escape.*:DeprecationWarning:re # produced by path.readlines