From 6b135c83be8cef769dbc860a4706a95603d01d09 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Wed, 22 Jun 2016 12:21:51 +0200 Subject: [PATCH 01/27] Initial commit. --- .gitignore | 6 ++++++ LICENSE | 19 ++++++++++++++++++ README.rst | 37 ++++++++++++++++++++++++++++++++++ pytest_warnings.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 17 ++++++++++++++++ test_warnings.py | 27 +++++++++++++++++++++++++ test_warnings2.py | 20 +++++++++++++++++++ tox.ini | 17 ++++++++++++++++ 8 files changed, 193 insertions(+) create mode 100644 .gitignore create mode 100755 LICENSE create mode 100644 README.rst create mode 100644 pytest_warnings.py create mode 100644 setup.py create mode 100644 test_warnings.py create mode 100644 test_warnings2.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..dac3fe1aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.cache/ +/.tox/ +/bin/ +/include/ +/lib/ +/pip-selfcheck.json diff --git a/LICENSE b/LICENSE new file mode 100755 index 000000000..8a30978e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + diff --git a/README.rst b/README.rst new file mode 100644 index 000000000..02389db73 --- /dev/null +++ b/README.rst @@ -0,0 +1,37 @@ +pytest-warnings +=============== + +py.test plugin to list Python warnings in pytest report + + +Usage +----- + +install via:: + + pip install pytest-warnings + +if you then type:: + + py.test -rw + +any warnings in your code are reported in the pytest report. +You can use the ``-W`` option or ``--pythonwarnings`` exactly like for the ``python`` executable. + +The following example ignores all warnings, but prints DeprecationWarnings once per occurrence:: + + py.test -rw -W ignore -W once::DeprecationWarning + +You can also turn warnings into actual errors:: + + py.test -W error + + +Changes +======= + +0.1 - Unreleased +---------------- + +- Initial release. + [fschulze (Florian Schulze)] diff --git a/pytest_warnings.py b/pytest_warnings.py new file mode 100644 index 000000000..1813cf519 --- /dev/null +++ b/pytest_warnings.py @@ -0,0 +1,50 @@ +from _pytest.recwarn import RecordedWarning, WarningsRecorder +import inspect +import os +import pytest +import warnings + + +def pytest_addoption(parser): + group = parser.getgroup("pytest-warnings") + group.addoption( + '-W', '--pythonwarnings', action='append', + help="set which warnings to report, see ...") + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_call(item): + wrec = WarningsRecorder() + + def showwarning(message, category, filename, lineno, file=None, line=None): + frame = inspect.currentframe() + if '/_pytest/recwarn' in frame.f_back.f_code.co_filename: + # we are in test recorder, so this warning is already handled + return + wrec._list.append(RecordedWarning( + message, category, filename, lineno, file, line)) + # still perform old showwarning functionality + wrec._showwarning( + message, category, filename, lineno, file=file, line=line) + + args = item.config.getoption('pythonwarnings') or [] + with wrec: + _showwarning = wrec._showwarning + warnings.showwarning = showwarning + wrec._module.simplefilter('once') + for arg in args: + wrec._module._setoption(arg) + yield + wrec._showwarning = _showwarning + + for warning in wrec.list: + msg = warnings.formatwarning( + warning.message, warning.category, + os.path.relpath(warning.filename), warning.lineno, warning.line) + fslocation = getattr(item, "location", None) + if fslocation is None: + fslocation = getattr(item, "fspath", None) + else: + fslocation = "%s:%s" % fslocation[:2] + fslocation = "in %s the following warning was recorded:\n" % fslocation + item.config.warn("W0", msg, fslocation=fslocation) diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..fd478664c --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup + + +setup( + name="pytest-warnings", + description='pytest plugin to list Python warnings in pytest report', + long_description=open("README.rst").read(), + license="MIT license", + version='0.1.0', + author='Florian Schulze', + author_email='florian.schulze@gmx.net', + url='https://github.com/fschulze/pytest-warnings', + py_modules=["pytest_warnings"], + entry_points={'pytest11': ['pytest_warnings = pytest_warnings']}, + install_requires=['pytest'], + classifiers=[ + "Framework :: Pytest"]) diff --git a/test_warnings.py b/test_warnings.py new file mode 100644 index 000000000..62167992f --- /dev/null +++ b/test_warnings.py @@ -0,0 +1,27 @@ +import pytest +import warnings + + +def test_warnings(): + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + + +def test_warnings1(): + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + + +def test_warn(): + with pytest.warns(DeprecationWarning): + warnings.warn("Bar", DeprecationWarning) diff --git a/test_warnings2.py b/test_warnings2.py new file mode 100644 index 000000000..d4c9be0ea --- /dev/null +++ b/test_warnings2.py @@ -0,0 +1,20 @@ +def test_warnings(): + import warnings + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + + +def test_warnings1(): + import warnings + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", RuntimeWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..977819b65 --- /dev/null +++ b/tox.ini @@ -0,0 +1,17 @@ +[tox] +envlist = py27,py33,py34,py35 + +[testenv] +usedevelop = true +deps = + pytest + pytest-flakes + pytest-pep8 + coverage +commands = + {envbindir}/py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} + +[pytest] +addopts = --flakes --pep8 +pep8ignore = E501 +norecursedirs = bin lib include Scripts .* From b9c4ecf5a86aa9054234f4f2f7b99a7030b5b1d7 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 27 Jun 2016 11:32:38 +0200 Subject: [PATCH 02/27] Add MANIFEST.in. --- MANIFEST.in | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..fb7dc7474 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include README.rst +include tox.ini +include LICENSE +include test*.py From 3feee0c48386f172effd8056361cce4a1b87ef4e Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 27 Jun 2016 11:32:54 +0200 Subject: [PATCH 03/27] Prepare 0.1 release. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 02389db73..aa8146bed 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ You can also turn warnings into actual errors:: Changes ======= -0.1 - Unreleased +0.1 - 2016-06-27 ---------------- - Initial release. From bc5a8c761e596fbfc661adb26889951140622203 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 27 Jun 2016 11:33:36 +0200 Subject: [PATCH 04/27] Fix version in readme. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index aa8146bed..483d258ea 100644 --- a/README.rst +++ b/README.rst @@ -30,8 +30,8 @@ You can also turn warnings into actual errors:: Changes ======= -0.1 - 2016-06-27 ----------------- +0.1.0 - 2016-06-27 +------------------ - Initial release. [fschulze (Florian Schulze)] From bc94a51a965ef20e9a66fc678718ce20746a1a0b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 17 Oct 2016 11:34:30 -0700 Subject: [PATCH 05/27] Add a warning option which does not escape its arguments. This is useful when to use regular expressions, like for example ignore a bunch of dynamic messages --filterwarnigns 'ignore:Please use assert.* instead.:' Or ignore all the warnings in a sub-backage --filterwarnigns 'ignore:::package.submodule.*' This is also available in the ini file as the filterwarnigns options --- README.rst | 21 ++++++++++++++ helper_test_a.py | 10 +++++++ helper_test_b.py | 11 ++++++++ pytest_warnings.py | 35 +++++++++++++++++++++++- test_warnings.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 helper_test_a.py create mode 100644 helper_test_b.py diff --git a/README.rst b/README.rst index 483d258ea..def6147ca 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,27 @@ You can also turn warnings into actual errors:: py.test -W error +Advance usage +============= + +You can get more fine grained filtering of warnings by using the +``filterwarnings`` configuration option. + +``filterwarnings`` works like the python's ``-W`` flag except it will not +escape special characters. + +Example +------- + +.. code:: + + # pytest.ini + [pytest] + filterwarnings= default + ignore:.*is deprecated.*:Warning + error::DeprecationWarning:importlib.* + + Changes ======= diff --git a/helper_test_a.py b/helper_test_a.py new file mode 100644 index 000000000..ba88aa31d --- /dev/null +++ b/helper_test_a.py @@ -0,0 +1,10 @@ +import warnings + + +def deprecated_a(): + """ + A warning triggered in __this__ module for testing. + """ + globals()['__warningregistry__'] = {} + warnings.warn("This is deprecated message_a", + DeprecationWarning, stacklevel=0) diff --git a/helper_test_b.py b/helper_test_b.py new file mode 100644 index 000000000..3c00a6114 --- /dev/null +++ b/helper_test_b.py @@ -0,0 +1,11 @@ +import warnings + + +def user_warning_b(): + """ + A warning triggered in __this__ module for testing. + """ + # reset the "once" filters + # globals()['__warningregistry__'] = {} + warnings.warn("This is deprecated message_b different from a", + UserWarning, stacklevel=1) diff --git a/pytest_warnings.py b/pytest_warnings.py index 1813cf519..84f64ea92 100644 --- a/pytest_warnings.py +++ b/pytest_warnings.py @@ -5,11 +5,39 @@ import pytest import warnings +def _setoption(wmod, arg): + """ + Copy of the warning._setoption function but does not escape arguments. + """ + parts = arg.split(':') + if len(parts) > 5: + raise wmod._OptionError("too many fields (max 5): %r" % (arg,)) + while len(parts) < 5: + parts.append('') + action, message, category, module, lineno = [s.strip() + for s in parts] + action = wmod._getaction(action) + category = wmod._getcategory(category) + if lineno: + try: + lineno = int(lineno) + if lineno < 0: + raise ValueError + except (ValueError, OverflowError): + raise wmod._OptionError("invalid lineno %r" % (lineno,)) + else: + lineno = 0 + wmod.filterwarnings(action, message, category, module, lineno) + + def pytest_addoption(parser): group = parser.getgroup("pytest-warnings") group.addoption( '-W', '--pythonwarnings', action='append', - help="set which warnings to report, see ...") + help="set which warnings to report, see -W option of python itself.") + parser.addini("filterwarnings", type="linelist", + help="Each line specifies warning filter pattern which would be passed" + "to warnings.filterwarnings. Process after -W and --pythonwarnings.") @pytest.hookimpl(hookwrapper=True) @@ -28,12 +56,17 @@ def pytest_runtest_call(item): message, category, filename, lineno, file=file, line=line) args = item.config.getoption('pythonwarnings') or [] + inifilters = item.config.getini("filterwarnings") with wrec: _showwarning = wrec._showwarning warnings.showwarning = showwarning wrec._module.simplefilter('once') for arg in args: wrec._module._setoption(arg) + + for arg in inifilters: + _setoption(wrec._module, arg) + yield wrec._showwarning = _showwarning diff --git a/test_warnings.py b/test_warnings.py index 62167992f..bc65220c7 100644 --- a/test_warnings.py +++ b/test_warnings.py @@ -1,6 +1,10 @@ import pytest import warnings +from pytest_warnings import _setoption +from helper_test_a import deprecated_a +from helper_test_b import user_warning_b + def test_warnings(): warnings.warn("Foo", DeprecationWarning) @@ -25,3 +29,67 @@ def test_warnings1(): def test_warn(): with pytest.warns(DeprecationWarning): warnings.warn("Bar", DeprecationWarning) + + +# This section test the ability to filter selectively warnings using regular +# expressions on messages. + +def test_filters_setoption(): + "A alone works" + + with pytest.warns(DeprecationWarning): + deprecated_a() + + +def test_filters_setoption_2(): + "B alone works" + + with pytest.warns(UserWarning) as record: + user_warning_b() + + assert len(record) == 1 + + +def test_filters_setoption_3(): + "A and B works" + + with pytest.warns(None) as record: + user_warning_b() + deprecated_a() + assert len(record) == 2 + + +def test_filters_setoption_4(): + "A works, B is filtered" + + with pytest.warns(None) as record: + _setoption(warnings, 'ignore:.*message_a.*') + deprecated_a() + user_warning_b() + + assert len(record) == 1, "Only `A` should be filtered out" + + +def test_filters_setoption_4b(): + "A works, B is filtered" + + with pytest.warns(None) as record: + _setoption(warnings, 'ignore:.*message_b.*') + _setoption(warnings, 'ignore:.*message_a.*') + _setoption(warnings, 'always:::.*helper_test_a.*') + deprecated_a() + user_warning_b() + + assert len(record) == 1, "`A` and `B` should be visible, second filter reenable A" + + +def test_filters_setoption_5(): + "B works, A is filtered" + + with pytest.warns(None) as records: + _setoption(warnings, 'always:::.*helper_test_a.*') + _setoption(warnings, 'ignore::UserWarning') + deprecated_a() + user_warning_b() + + assert len(records) == 1, "Only `B` should be filtered out" From f229b573fa57d69fa55ad3280f03c5a3525aa9bc Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 24 Oct 2016 12:08:00 +0200 Subject: [PATCH 06/27] Bump version, add changelog entry and move stuff around for added coverage reporting. --- .gitignore | 3 +++ MANIFEST.in | 2 +- README.rst | 7 +++++++ pytest_warnings.py => pytest_warnings/__init__.py | 0 setup.py | 4 ++-- helper_test_a.py => tests/helper_test_a.py | 0 helper_test_b.py => tests/helper_test_b.py | 0 test_warnings.py => tests/test_warnings.py | 0 test_warnings2.py => tests/test_warnings2.py | 0 tox.ini | 3 ++- 10 files changed, 15 insertions(+), 4 deletions(-) rename pytest_warnings.py => pytest_warnings/__init__.py (100%) rename helper_test_a.py => tests/helper_test_a.py (100%) rename helper_test_b.py => tests/helper_test_b.py (100%) rename test_warnings.py => tests/test_warnings.py (100%) rename test_warnings2.py => tests/test_warnings2.py (100%) diff --git a/.gitignore b/.gitignore index dac3fe1aa..80b4d47de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ /.cache/ +/.coverage /.tox/ /bin/ +/dist/ +/htmlcov/ /include/ /lib/ /pip-selfcheck.json diff --git a/MANIFEST.in b/MANIFEST.in index fb7dc7474..1652af658 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include README.rst include tox.ini include LICENSE -include test*.py +include tests/*.py diff --git a/README.rst b/README.rst index def6147ca..63e5feff3 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,13 @@ Example Changes ======= +0.2.0 - Unreleased +------------------ + +- Add ``filterwarnings`` option. + [Carreau (Matthias Bussonnier)] + + 0.1.0 - 2016-06-27 ------------------ diff --git a/pytest_warnings.py b/pytest_warnings/__init__.py similarity index 100% rename from pytest_warnings.py rename to pytest_warnings/__init__.py diff --git a/setup.py b/setup.py index fd478664c..ea4d193fd 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,11 @@ setup( description='pytest plugin to list Python warnings in pytest report', long_description=open("README.rst").read(), license="MIT license", - version='0.1.0', + version='0.2.0', author='Florian Schulze', author_email='florian.schulze@gmx.net', url='https://github.com/fschulze/pytest-warnings', - py_modules=["pytest_warnings"], + packages=['pytest_warnings'], entry_points={'pytest11': ['pytest_warnings = pytest_warnings']}, install_requires=['pytest'], classifiers=[ diff --git a/helper_test_a.py b/tests/helper_test_a.py similarity index 100% rename from helper_test_a.py rename to tests/helper_test_a.py diff --git a/helper_test_b.py b/tests/helper_test_b.py similarity index 100% rename from helper_test_b.py rename to tests/helper_test_b.py diff --git a/test_warnings.py b/tests/test_warnings.py similarity index 100% rename from test_warnings.py rename to tests/test_warnings.py diff --git a/test_warnings2.py b/tests/test_warnings2.py similarity index 100% rename from test_warnings2.py rename to tests/test_warnings2.py diff --git a/tox.ini b/tox.ini index 977819b65..ec2e5622d 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = py27,py33,py34,py35 usedevelop = true deps = pytest + pytest-cov pytest-flakes pytest-pep8 coverage @@ -12,6 +13,6 @@ commands = {envbindir}/py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} [pytest] -addopts = --flakes --pep8 +addopts = --flakes --pep8 --cov pytest_warnings --cov tests --no-cov-on-fail pep8ignore = E501 norecursedirs = bin lib include Scripts .* From ce138060acd9107efac1365a96b14ee8e839c0d9 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 24 Oct 2016 12:09:49 +0200 Subject: [PATCH 07/27] Prepare pytest-warnings 0.2.0. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 63e5feff3..86d1d0985 100644 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ Example Changes ======= -0.2.0 - Unreleased +0.2.0 - 2016-10-24 ------------------ - Add ``filterwarnings`` option. From 6ec0c3f3690efb500155a44b65c0f1719dad905b Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 24 Oct 2016 12:10:49 +0200 Subject: [PATCH 08/27] Bump. --- README.rst | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 86d1d0985..c598259c3 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,11 @@ Example Changes ======= +0.3.0 - Unreleased +------------------ + + + 0.2.0 - 2016-10-24 ------------------ diff --git a/setup.py b/setup.py index ea4d193fd..19a4c6e80 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( description='pytest plugin to list Python warnings in pytest report', long_description=open("README.rst").read(), license="MIT license", - version='0.2.0', + version='0.3.0.dev0', author='Florian Schulze', author_email='florian.schulze@gmx.net', url='https://github.com/fschulze/pytest-warnings', From e31421a5d242739f44531cfc6525984c5f99046d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2016 07:27:26 -0200 Subject: [PATCH 09/27] Moving all stuff to a subdirectory to try to retain history --- .gitignore => warnings-root/.gitignore | 0 LICENSE => warnings-root/LICENSE | 0 MANIFEST.in => warnings-root/MANIFEST.in | 0 README.rst => warnings-root/README.rst | 0 {pytest_warnings => warnings-root/pytest_warnings}/__init__.py | 0 setup.py => warnings-root/setup.py | 0 {tests => warnings-root/tests}/helper_test_a.py | 0 {tests => warnings-root/tests}/helper_test_b.py | 0 {tests => warnings-root/tests}/test_warnings.py | 0 {tests => warnings-root/tests}/test_warnings2.py | 0 tox.ini => warnings-root/tox.ini | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename .gitignore => warnings-root/.gitignore (100%) rename LICENSE => warnings-root/LICENSE (100%) mode change 100755 => 100644 rename MANIFEST.in => warnings-root/MANIFEST.in (100%) rename README.rst => warnings-root/README.rst (100%) rename {pytest_warnings => warnings-root/pytest_warnings}/__init__.py (100%) rename setup.py => warnings-root/setup.py (100%) rename {tests => warnings-root/tests}/helper_test_a.py (100%) rename {tests => warnings-root/tests}/helper_test_b.py (100%) rename {tests => warnings-root/tests}/test_warnings.py (100%) rename {tests => warnings-root/tests}/test_warnings2.py (100%) rename tox.ini => warnings-root/tox.ini (100%) diff --git a/.gitignore b/warnings-root/.gitignore similarity index 100% rename from .gitignore rename to warnings-root/.gitignore diff --git a/LICENSE b/warnings-root/LICENSE old mode 100755 new mode 100644 similarity index 100% rename from LICENSE rename to warnings-root/LICENSE diff --git a/MANIFEST.in b/warnings-root/MANIFEST.in similarity index 100% rename from MANIFEST.in rename to warnings-root/MANIFEST.in diff --git a/README.rst b/warnings-root/README.rst similarity index 100% rename from README.rst rename to warnings-root/README.rst diff --git a/pytest_warnings/__init__.py b/warnings-root/pytest_warnings/__init__.py similarity index 100% rename from pytest_warnings/__init__.py rename to warnings-root/pytest_warnings/__init__.py diff --git a/setup.py b/warnings-root/setup.py similarity index 100% rename from setup.py rename to warnings-root/setup.py diff --git a/tests/helper_test_a.py b/warnings-root/tests/helper_test_a.py similarity index 100% rename from tests/helper_test_a.py rename to warnings-root/tests/helper_test_a.py diff --git a/tests/helper_test_b.py b/warnings-root/tests/helper_test_b.py similarity index 100% rename from tests/helper_test_b.py rename to warnings-root/tests/helper_test_b.py diff --git a/tests/test_warnings.py b/warnings-root/tests/test_warnings.py similarity index 100% rename from tests/test_warnings.py rename to warnings-root/tests/test_warnings.py diff --git a/tests/test_warnings2.py b/warnings-root/tests/test_warnings2.py similarity index 100% rename from tests/test_warnings2.py rename to warnings-root/tests/test_warnings2.py diff --git a/tox.ini b/warnings-root/tox.ini similarity index 100% rename from tox.ini rename to warnings-root/tox.ini From 1da19064839d7773ddc01489ae237c389cf35514 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2016 07:38:12 -0200 Subject: [PATCH 10/27] Rename code to _pytest.warnings and delete old files from the repository --- .../__init__.py => _pytest/warnings.py | 0 warnings-root/.gitignore | 9 -- warnings-root/LICENSE | 19 ---- warnings-root/MANIFEST.in | 4 - warnings-root/README.rst | 70 -------------- warnings-root/setup.py | 17 ---- warnings-root/tests/helper_test_a.py | 10 -- warnings-root/tests/helper_test_b.py | 11 --- warnings-root/tests/test_warnings.py | 95 ------------------- warnings-root/tests/test_warnings2.py | 20 ---- warnings-root/tox.ini | 18 ---- 11 files changed, 273 deletions(-) rename warnings-root/pytest_warnings/__init__.py => _pytest/warnings.py (100%) delete mode 100644 warnings-root/.gitignore delete mode 100644 warnings-root/LICENSE delete mode 100644 warnings-root/MANIFEST.in delete mode 100644 warnings-root/README.rst delete mode 100644 warnings-root/setup.py delete mode 100644 warnings-root/tests/helper_test_a.py delete mode 100644 warnings-root/tests/helper_test_b.py delete mode 100644 warnings-root/tests/test_warnings.py delete mode 100644 warnings-root/tests/test_warnings2.py delete mode 100644 warnings-root/tox.ini diff --git a/warnings-root/pytest_warnings/__init__.py b/_pytest/warnings.py similarity index 100% rename from warnings-root/pytest_warnings/__init__.py rename to _pytest/warnings.py diff --git a/warnings-root/.gitignore b/warnings-root/.gitignore deleted file mode 100644 index 80b4d47de..000000000 --- a/warnings-root/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -/.cache/ -/.coverage -/.tox/ -/bin/ -/dist/ -/htmlcov/ -/include/ -/lib/ -/pip-selfcheck.json diff --git a/warnings-root/LICENSE b/warnings-root/LICENSE deleted file mode 100644 index 8a30978e3..000000000 --- a/warnings-root/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - diff --git a/warnings-root/MANIFEST.in b/warnings-root/MANIFEST.in deleted file mode 100644 index 1652af658..000000000 --- a/warnings-root/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include README.rst -include tox.ini -include LICENSE -include tests/*.py diff --git a/warnings-root/README.rst b/warnings-root/README.rst deleted file mode 100644 index c598259c3..000000000 --- a/warnings-root/README.rst +++ /dev/null @@ -1,70 +0,0 @@ -pytest-warnings -=============== - -py.test plugin to list Python warnings in pytest report - - -Usage ------ - -install via:: - - pip install pytest-warnings - -if you then type:: - - py.test -rw - -any warnings in your code are reported in the pytest report. -You can use the ``-W`` option or ``--pythonwarnings`` exactly like for the ``python`` executable. - -The following example ignores all warnings, but prints DeprecationWarnings once per occurrence:: - - py.test -rw -W ignore -W once::DeprecationWarning - -You can also turn warnings into actual errors:: - - py.test -W error - - -Advance usage -============= - -You can get more fine grained filtering of warnings by using the -``filterwarnings`` configuration option. - -``filterwarnings`` works like the python's ``-W`` flag except it will not -escape special characters. - -Example -------- - -.. code:: - - # pytest.ini - [pytest] - filterwarnings= default - ignore:.*is deprecated.*:Warning - error::DeprecationWarning:importlib.* - - -Changes -======= - -0.3.0 - Unreleased ------------------- - - - -0.2.0 - 2016-10-24 ------------------- - -- Add ``filterwarnings`` option. - [Carreau (Matthias Bussonnier)] - - -0.1.0 - 2016-06-27 ------------------- - -- Initial release. - [fschulze (Florian Schulze)] diff --git a/warnings-root/setup.py b/warnings-root/setup.py deleted file mode 100644 index 19a4c6e80..000000000 --- a/warnings-root/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup - - -setup( - name="pytest-warnings", - description='pytest plugin to list Python warnings in pytest report', - long_description=open("README.rst").read(), - license="MIT license", - version='0.3.0.dev0', - author='Florian Schulze', - author_email='florian.schulze@gmx.net', - url='https://github.com/fschulze/pytest-warnings', - packages=['pytest_warnings'], - entry_points={'pytest11': ['pytest_warnings = pytest_warnings']}, - install_requires=['pytest'], - classifiers=[ - "Framework :: Pytest"]) diff --git a/warnings-root/tests/helper_test_a.py b/warnings-root/tests/helper_test_a.py deleted file mode 100644 index ba88aa31d..000000000 --- a/warnings-root/tests/helper_test_a.py +++ /dev/null @@ -1,10 +0,0 @@ -import warnings - - -def deprecated_a(): - """ - A warning triggered in __this__ module for testing. - """ - globals()['__warningregistry__'] = {} - warnings.warn("This is deprecated message_a", - DeprecationWarning, stacklevel=0) diff --git a/warnings-root/tests/helper_test_b.py b/warnings-root/tests/helper_test_b.py deleted file mode 100644 index 3c00a6114..000000000 --- a/warnings-root/tests/helper_test_b.py +++ /dev/null @@ -1,11 +0,0 @@ -import warnings - - -def user_warning_b(): - """ - A warning triggered in __this__ module for testing. - """ - # reset the "once" filters - # globals()['__warningregistry__'] = {} - warnings.warn("This is deprecated message_b different from a", - UserWarning, stacklevel=1) diff --git a/warnings-root/tests/test_warnings.py b/warnings-root/tests/test_warnings.py deleted file mode 100644 index bc65220c7..000000000 --- a/warnings-root/tests/test_warnings.py +++ /dev/null @@ -1,95 +0,0 @@ -import pytest -import warnings - -from pytest_warnings import _setoption -from helper_test_a import deprecated_a -from helper_test_b import user_warning_b - - -def test_warnings(): - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - - -def test_warnings1(): - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - - -def test_warn(): - with pytest.warns(DeprecationWarning): - warnings.warn("Bar", DeprecationWarning) - - -# This section test the ability to filter selectively warnings using regular -# expressions on messages. - -def test_filters_setoption(): - "A alone works" - - with pytest.warns(DeprecationWarning): - deprecated_a() - - -def test_filters_setoption_2(): - "B alone works" - - with pytest.warns(UserWarning) as record: - user_warning_b() - - assert len(record) == 1 - - -def test_filters_setoption_3(): - "A and B works" - - with pytest.warns(None) as record: - user_warning_b() - deprecated_a() - assert len(record) == 2 - - -def test_filters_setoption_4(): - "A works, B is filtered" - - with pytest.warns(None) as record: - _setoption(warnings, 'ignore:.*message_a.*') - deprecated_a() - user_warning_b() - - assert len(record) == 1, "Only `A` should be filtered out" - - -def test_filters_setoption_4b(): - "A works, B is filtered" - - with pytest.warns(None) as record: - _setoption(warnings, 'ignore:.*message_b.*') - _setoption(warnings, 'ignore:.*message_a.*') - _setoption(warnings, 'always:::.*helper_test_a.*') - deprecated_a() - user_warning_b() - - assert len(record) == 1, "`A` and `B` should be visible, second filter reenable A" - - -def test_filters_setoption_5(): - "B works, A is filtered" - - with pytest.warns(None) as records: - _setoption(warnings, 'always:::.*helper_test_a.*') - _setoption(warnings, 'ignore::UserWarning') - deprecated_a() - user_warning_b() - - assert len(records) == 1, "Only `B` should be filtered out" diff --git a/warnings-root/tests/test_warnings2.py b/warnings-root/tests/test_warnings2.py deleted file mode 100644 index d4c9be0ea..000000000 --- a/warnings-root/tests/test_warnings2.py +++ /dev/null @@ -1,20 +0,0 @@ -def test_warnings(): - import warnings - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - - -def test_warnings1(): - import warnings - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", RuntimeWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) diff --git a/warnings-root/tox.ini b/warnings-root/tox.ini deleted file mode 100644 index ec2e5622d..000000000 --- a/warnings-root/tox.ini +++ /dev/null @@ -1,18 +0,0 @@ -[tox] -envlist = py27,py33,py34,py35 - -[testenv] -usedevelop = true -deps = - pytest - pytest-cov - pytest-flakes - pytest-pep8 - coverage -commands = - {envbindir}/py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} - -[pytest] -addopts = --flakes --pep8 --cov pytest_warnings --cov tests --no-cov-on-fail -pep8ignore = E501 -norecursedirs = bin lib include Scripts .* From 26ca5a702e2655302a1aeec08a82c8f474f8425f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2016 08:26:43 -0200 Subject: [PATCH 11/27] Add tests and integrated the original code into the core --- _pytest/config.py | 2 +- testing/test_warnings.py | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 testing/test_warnings.py diff --git a/_pytest/config.py b/_pytest/config.py index 5df198e21..04ebfec03 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -74,7 +74,7 @@ default_plugins = ( "mark main terminal runner python fixtures debugging unittest capture skipping " "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " "junitxml resultlog doctest cacheprovider freeze_support " - "setuponly setupplan").split() + "setuponly setupplan warnings").split() builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") diff --git a/testing/test_warnings.py b/testing/test_warnings.py new file mode 100644 index 000000000..34badaee8 --- /dev/null +++ b/testing/test_warnings.py @@ -0,0 +1,60 @@ +import pytest + + +@pytest.fixture +def pyfile_with_warnings(testdir): + testdir.makepyfile(''' + import warnings + def test_func(): + warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) + warnings.warn(DeprecationWarning("functionality is deprecated")) + ''') + + +def test_normal_flow(testdir, pyfile_with_warnings): + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines([ + '*== pytest-warning summary ==*', + + 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', + ' test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', + ' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', + + 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', + ' test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', + ' warnings.warn(DeprecationWarning("functionality is deprecated"))', + '* 1 passed, 2 pytest-warnings*', + ]) + + +@pytest.mark.parametrize('method', ['cmdline', 'ini']) +def test_as_errors(testdir, pyfile_with_warnings, method): + args = ('-W', 'error') if method == 'cmdline' else () + if method == 'ini': + testdir.makeini(''' + [pytest] + filterwarnings= error + ''') + result = testdir.runpytest_subprocess(*args) + result.stdout.fnmatch_lines([ + 'E PendingDeprecationWarning: functionality is pending deprecation', + 'test_as_errors.py:3: PendingDeprecationWarning', + '* 1 failed in *', + ]) + + +@pytest.mark.parametrize('method', ['cmdline', 'ini']) +def test_ignore(testdir, pyfile_with_warnings, method): + args = ('-W', 'ignore') if method == 'cmdline' else () + if method == 'ini': + testdir.makeini(''' + [pytest] + filterwarnings= ignore + ''') + + result = testdir.runpytest_subprocess(*args) + result.stdout.fnmatch_lines([ + '* 1 passed in *', + ]) + assert 'pytest-warning summary' not in result.stdout.str() + From 82785fcd4025c6827f504ce082d99116bff54e11 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 18 Feb 2017 12:57:26 -0200 Subject: [PATCH 12/27] Use warnings.catch_warnings instead of WarningsRecorder This has the benefical side-effect of not calling the original warnings.showwarnings function, which in its original form only writes the formatted warning to sys.stdout. Calling the original warnings.showwarnings has the effect that nested WarningsRecorder all catch the warnings: with WarningsRecorder() as rec1: with WarningsRecorder() as rec2: warnings.warn(UserWarning, 'some warning') (both rec1 and rec2 sees the warning) When running tests with `testdir`, the main pytest session would then see the warnings created by the internal code being tested (if any), and the main pytest session would end up with warnings as well. --- _pytest/warnings.py | 28 +++++----------------------- testing/test_warnings.py | 6 +++--- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 84f64ea92..d654901d0 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,5 +1,3 @@ -from _pytest.recwarn import RecordedWarning, WarningsRecorder -import inspect import os import pytest import warnings @@ -42,35 +40,19 @@ def pytest_addoption(parser): @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(item): - wrec = WarningsRecorder() - - def showwarning(message, category, filename, lineno, file=None, line=None): - frame = inspect.currentframe() - if '/_pytest/recwarn' in frame.f_back.f_code.co_filename: - # we are in test recorder, so this warning is already handled - return - wrec._list.append(RecordedWarning( - message, category, filename, lineno, file, line)) - # still perform old showwarning functionality - wrec._showwarning( - message, category, filename, lineno, file=file, line=line) - args = item.config.getoption('pythonwarnings') or [] inifilters = item.config.getini("filterwarnings") - with wrec: - _showwarning = wrec._showwarning - warnings.showwarning = showwarning - wrec._module.simplefilter('once') + with warnings.catch_warnings(record=True) as log: + warnings.simplefilter('once') for arg in args: - wrec._module._setoption(arg) + warnings._setoption(arg) for arg in inifilters: - _setoption(wrec._module, arg) + _setoption(warnings, arg) yield - wrec._showwarning = _showwarning - for warning in wrec.list: + for warning in log: msg = warnings.formatwarning( warning.message, warning.category, os.path.relpath(warning.filename), warning.lineno, warning.line) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 34badaee8..738ef347b 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -12,7 +12,7 @@ def pyfile_with_warnings(testdir): def test_normal_flow(testdir, pyfile_with_warnings): - result = testdir.runpytest_subprocess() + result = testdir.runpytest() result.stdout.fnmatch_lines([ '*== pytest-warning summary ==*', @@ -35,7 +35,7 @@ def test_as_errors(testdir, pyfile_with_warnings, method): [pytest] filterwarnings= error ''') - result = testdir.runpytest_subprocess(*args) + result = testdir.runpytest(*args) result.stdout.fnmatch_lines([ 'E PendingDeprecationWarning: functionality is pending deprecation', 'test_as_errors.py:3: PendingDeprecationWarning', @@ -52,7 +52,7 @@ def test_ignore(testdir, pyfile_with_warnings, method): filterwarnings= ignore ''') - result = testdir.runpytest_subprocess(*args) + result = testdir.runpytest(*args) result.stdout.fnmatch_lines([ '* 1 passed in *', ]) From e24081bf76568cb4fc7b96b981c752647231fc5b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 25 Feb 2017 12:02:48 -0300 Subject: [PATCH 13/27] Change warning output --- _pytest/terminal.py | 22 ++++++++++++---------- _pytest/warnings.py | 10 ++-------- testing/test_warnings.py | 14 +++++++------- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index f509283cb..f8d3ec2ce 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -24,9 +24,9 @@ def pytest_addoption(parser): help="show extra test summary info as specified by chars (f)ailed, " "(E)error, (s)skipped, (x)failed, (X)passed, " "(p)passed, (P)passed with output, (a)all except pP. " - "The pytest warnings are displayed at all times except when " - "--disable-pytest-warnings is set") - group._addoption('--disable-pytest-warnings', default=False, + "Warnings are displayed at all times except when " + "--disable-warnings is set") + group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False, dest='disablepytestwarnings', action='store_true', help='disable warnings summary, overrides -r w flag') group._addoption('-l', '--showlocals', @@ -441,10 +441,14 @@ class TerminalReporter(object): warnings = self.stats.get("warnings") if not warnings: return - self.write_sep("=", "pytest-warning summary") + self.write_sep("=", "warnings summary") for w in warnings: - self._tw.line("W%s %s %s" % (w.code, - w.fslocation, w.message)) + msg = '' + if w.fslocation: + msg += str(w.fslocation) + ' ' + msg += w.message + self._tw.line(msg) + self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html') def summary_passes(self): if self.config.option.tbstyle != "no": @@ -546,8 +550,7 @@ def flatten(l): def build_summary_stats_line(stats): keys = ("failed passed skipped deselected " - "xfailed xpassed warnings error").split() - key_translation = {'warnings': 'pytest-warnings'} + "xfailed xpassed warnings error").split() unknown_key_seen = False for key in stats.keys(): if key not in keys: @@ -558,8 +561,7 @@ def build_summary_stats_line(stats): for key in keys: val = stats.get(key, None) if val: - key_name = key_translation.get(key, key) - parts.append("%d %s" % (len(val), key_name)) + parts.append("%d %s" % (len(val), key)) if parts: line = ", ".join(parts) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index d654901d0..b27c20b9d 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -55,11 +55,5 @@ def pytest_runtest_call(item): for warning in log: msg = warnings.formatwarning( warning.message, warning.category, - os.path.relpath(warning.filename), warning.lineno, warning.line) - fslocation = getattr(item, "location", None) - if fslocation is None: - fslocation = getattr(item, "fspath", None) - else: - fslocation = "%s:%s" % fslocation[:2] - fslocation = "in %s the following warning was recorded:\n" % fslocation - item.config.warn("W0", msg, fslocation=fslocation) + warning.filename, warning.lineno, warning.line) + item.config.warn("W0", msg, fslocation=None) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 738ef347b..943c8244c 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,6 +1,8 @@ import pytest +WARNINGS_SUMMARY_HEADER = 'warnings summary' + @pytest.fixture def pyfile_with_warnings(testdir): testdir.makepyfile(''' @@ -14,16 +16,14 @@ def pyfile_with_warnings(testdir): def test_normal_flow(testdir, pyfile_with_warnings): result = testdir.runpytest() result.stdout.fnmatch_lines([ - '*== pytest-warning summary ==*', + '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', - ' test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', + '*test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', ' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', - 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', - ' test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', + '*test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', ' warnings.warn(DeprecationWarning("functionality is deprecated"))', - '* 1 passed, 2 pytest-warnings*', + '* 1 passed, 2 warnings*', ]) @@ -56,5 +56,5 @@ def test_ignore(testdir, pyfile_with_warnings, method): result.stdout.fnmatch_lines([ '* 1 passed in *', ]) - assert 'pytest-warning summary' not in result.stdout.str() + assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() From de09023e450462f78a562ea802b9d327af19661d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 4 Mar 2017 16:07:37 -0300 Subject: [PATCH 14/27] Also capture warnings during setup/teardown --- _pytest/warnings.py | 32 +++++++++++++++++++++++++++++--- testing/test_warnings.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index b27c20b9d..1458c25c3 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,4 +1,6 @@ import os +from contextlib import contextmanager + import pytest import warnings @@ -38,8 +40,13 @@ def pytest_addoption(parser): "to warnings.filterwarnings. Process after -W and --pythonwarnings.") -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_call(item): +@contextmanager +def catch_warnings_for_item(item): + """ + catches the warnings generated during setup/call/teardown execution + of the given item and after it is done posts them as warnings to this + item. + """ args = item.config.getoption('pythonwarnings') or [] inifilters = item.config.getini("filterwarnings") with warnings.catch_warnings(record=True) as log: @@ -56,4 +63,23 @@ def pytest_runtest_call(item): msg = warnings.formatwarning( warning.message, warning.category, warning.filename, warning.lineno, warning.line) - item.config.warn("W0", msg, fslocation=None) + item.config.warn("unused", msg, fslocation=None) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_call(item): + with catch_warnings_for_item(item): + yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_setup(item): + with catch_warnings_for_item(item): + yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_teardown(item): + with catch_warnings_for_item(item): + yield + diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 943c8244c..77eefb7e3 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -27,6 +27,34 @@ def test_normal_flow(testdir, pyfile_with_warnings): ]) +def test_setup_teardown_warnings(testdir, pyfile_with_warnings): + testdir.makepyfile(''' + import warnings + import pytest + + @pytest.fixture + def fix(): + warnings.warn(UserWarning("warning during setup")) + yield + warnings.warn(UserWarning("warning during teardown")) + + def test_func(fix): + pass + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*== %s ==*' % WARNINGS_SUMMARY_HEADER, + + '*test_setup_teardown_warnings.py:6: UserWarning: warning during setup', + ' warnings.warn(UserWarning("warning during setup"))', + + '*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown', + ' warnings.warn(UserWarning("warning during teardown"))', + '* 1 passed, 2 warnings*', + ]) + + + @pytest.mark.parametrize('method', ['cmdline', 'ini']) def test_as_errors(testdir, pyfile_with_warnings, method): args = ('-W', 'error') if method == 'cmdline' else () From bddb922f7bd39c378fb97d42ed04d49108c27362 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 4 Mar 2017 16:32:10 -0300 Subject: [PATCH 15/27] Rename internal option to disable_warnings --- _pytest/terminal.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index f8d3ec2ce..d0bb4f4b5 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -27,7 +27,7 @@ def pytest_addoption(parser): "Warnings are displayed at all times except when " "--disable-warnings is set") group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False, - dest='disablepytestwarnings', action='store_true', + dest='disable_warnings', action='store_true', help='disable warnings summary, overrides -r w flag') group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, @@ -57,9 +57,9 @@ def pytest_configure(config): def getreportopt(config): reportopts = "" reportchars = config.option.reportchars - if not config.option.disablepytestwarnings and 'w' not in reportchars: + if not config.option.disable_warnings and 'w' not in reportchars: reportchars += 'w' - elif config.option.disablepytestwarnings and 'w' in reportchars: + elif config.option.disable_warnings and 'w' in reportchars: reportchars = reportchars.replace('w', '') if reportchars: for char in reportchars: From 272afa9422abff0070b47973ab69ccaca6fadeb1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 4 Mar 2017 20:53:42 -0300 Subject: [PATCH 16/27] Display node ids and the warnings generated by it The rationale of using node ids is that users can copy/paste it to run a chosen test --- _pytest/terminal.py | 22 +++++++++++---------- _pytest/warnings.py | 2 +- testing/test_warnings.py | 41 ++++++++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index d0bb4f4b5..fbfb03232 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -2,6 +2,9 @@ This is a good source for looking at the various reporting hooks. """ +import operator +import itertools + from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \ EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED import pytest @@ -28,7 +31,7 @@ def pytest_addoption(parser): "--disable-warnings is set") group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False, dest='disable_warnings', action='store_true', - help='disable warnings summary, overrides -r w flag') + help='disable warnings summary') group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default).") @@ -438,16 +441,15 @@ class TerminalReporter(object): def summary_warnings(self): if self.hasopt("w"): - warnings = self.stats.get("warnings") - if not warnings: + all_warnings = self.stats.get("warnings") + if not all_warnings: return - self.write_sep("=", "warnings summary") - for w in warnings: - msg = '' - if w.fslocation: - msg += str(w.fslocation) + ' ' - msg += w.message - self._tw.line(msg) + self.write_sep("=", "warnings summary", yellow=True, bold=False) + grouped = itertools.groupby(all_warnings, key=operator.attrgetter('nodeid')) + for nodeid, warnings in grouped: + self._tw.line(str(nodeid)) + for w in warnings: + self._tw.line(w.message) self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html') def summary_passes(self): diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 1458c25c3..9a91d181c 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -63,7 +63,7 @@ def catch_warnings_for_item(item): msg = warnings.formatwarning( warning.message, warning.category, warning.filename, warning.lineno, warning.line) - item.config.warn("unused", msg, fslocation=None) + item.warn("unused", msg) @pytest.hookimpl(hookwrapper=True) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 77eefb7e3..3b9908ef7 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -4,27 +4,48 @@ import pytest WARNINGS_SUMMARY_HEADER = 'warnings summary' @pytest.fixture -def pyfile_with_warnings(testdir): - testdir.makepyfile(''' - import warnings - def test_func(): - warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) - warnings.warn(DeprecationWarning("functionality is deprecated")) - ''') +def pyfile_with_warnings(testdir, request): + """ + Create a test file which calls a function in a module which generates warnings. + """ + testdir.syspathinsert() + test_name = request.function.__name__ + module_name = test_name.lstrip('test_') + '_module' + testdir.makepyfile(**{ + module_name: ''' + import warnings + def foo(): + warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) + warnings.warn(DeprecationWarning("functionality is deprecated")) + return 1 + ''', + test_name: ''' + import {module_name} + def test_func(): + assert {module_name}.foo() == 1 + '''.format(module_name=module_name) + }) def test_normal_flow(testdir, pyfile_with_warnings): + """ + Check that the warnings section is displayed, containing test node ids followed by + all warnings generated by that test node. + """ result = testdir.runpytest() result.stdout.fnmatch_lines([ '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - '*test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', + '*test_normal_flow.py::test_func', + + '*normal_flow_module.py:3: PendingDeprecationWarning: functionality is pending deprecation', ' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', - '*test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', + '*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated', ' warnings.warn(DeprecationWarning("functionality is deprecated"))', '* 1 passed, 2 warnings*', ]) + assert result.stdout.str().count('test_normal_flow.py::test_func') == 1 def test_setup_teardown_warnings(testdir, pyfile_with_warnings): @@ -66,7 +87,7 @@ def test_as_errors(testdir, pyfile_with_warnings, method): result = testdir.runpytest(*args) result.stdout.fnmatch_lines([ 'E PendingDeprecationWarning: functionality is pending deprecation', - 'test_as_errors.py:3: PendingDeprecationWarning', + 'as_errors_module.py:3: PendingDeprecationWarning', '* 1 failed in *', ]) From 337f891d7808b081161238bbb2391f7e8e2d66ec Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 7 Mar 2017 20:47:43 -0300 Subject: [PATCH 17/27] Fixed tests --- testing/deprecated_test.py | 2 +- testing/test_assertion.py | 2 +- testing/test_config.py | 7 ++++--- testing/test_junitxml.py | 7 +++++-- testing/test_pluginmanager.py | 4 ++-- testing/test_terminal.py | 16 ++++++++-------- testing/test_warnings.py | 1 - 7 files changed, 21 insertions(+), 18 deletions(-) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 6473989e6..bad844281 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -26,7 +26,7 @@ def test_funcarg_prefix_deprecation(testdir): """) result = testdir.runpytest('-ra') result.stdout.fnmatch_lines([ - ('WC1 None pytest_funcarg__value: ' + ('pytest_funcarg__value: ' 'declaring fixtures using "pytest_funcarg__" prefix is deprecated ' 'and scheduled to be removed in pytest 4.0. ' 'Please remove the prefix and use the @pytest.fixture decorator instead.'), diff --git a/testing/test_assertion.py b/testing/test_assertion.py index fc2819f69..7ab62e6f3 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -975,7 +975,7 @@ def test_assert_tuple_warning(testdir): assert(False, 'you shall not pass') """) result = testdir.runpytest('-rw') - result.stdout.fnmatch_lines('WR1*:2 assertion is always true*') + result.stdout.fnmatch_lines('*test_assert_tuple_warning.py:2 assertion is always true*') def test_assert_indirect_tuple_no_warning(testdir): testdir.makepyfile(""" diff --git a/testing/test_config.py b/testing/test_config.py index 7876063bd..4b2006a1d 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -632,13 +632,14 @@ class TestWarning(object): pass """) result = testdir.runpytest("--disable-pytest-warnings") - assert result.parseoutcomes()["pytest-warnings"] > 0 + assert result.parseoutcomes()["warnings"] > 0 assert "hello" not in result.stdout.str() result = testdir.runpytest() result.stdout.fnmatch_lines(""" - ===*pytest-warning summary*=== - *WT1*test_warn_on_test_item*:7 hello* + ===*warnings summary*=== + *test_warn_on_test_item_from_request::test_hello* + *hello* """) class TestRootdir(object): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 70c02332c..1dd9302f9 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -810,7 +810,10 @@ def test_record_property(testdir): pnodes = psnode.find_by_tag('property') pnodes[0].assert_attr(name="bar", value="1") pnodes[1].assert_attr(name="foo", value="<1") - result.stdout.fnmatch_lines('*C3*test_record_property.py*experimental*') + result.stdout.fnmatch_lines([ + 'test_record_property.py::test_record', + 'record_xml_property*experimental*', + ]) def test_record_property_same_name(testdir): @@ -986,4 +989,4 @@ def test_url_property(testdir): test_case = minidom.parse(str(path)).getElementsByTagName('testcase')[0] - assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" \ No newline at end of file + assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 3214d868b..3f4ce9aad 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -284,8 +284,8 @@ class TestPytestPluginManager(object): result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True) assert result.ret == EXIT_NOTESTSCOLLECTED result.stdout.fnmatch_lines([ - "WI1*skipped plugin*skipping1*hello*", - "WI1*skipped plugin*skipping2*hello*", + "*skipped plugin*skipping1*hello*", + "*skipped plugin*skipping2*hello*", ]) def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm): diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 77fb3b154..838ecf019 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -615,7 +615,7 @@ def test_getreportopt(): class config(object): class option(object): reportchars = "" - disablepytestwarnings = True + disable_warnings = True config.option.reportchars = "sf" assert getreportopt(config) == "sf" @@ -624,11 +624,11 @@ def test_getreportopt(): assert getreportopt(config) == "sfx" config.option.reportchars = "sfx" - config.option.disablepytestwarnings = False + config.option.disable_warnings = False assert getreportopt(config) == "sfxw" config.option.reportchars = "sfxw" - config.option.disablepytestwarnings = False + config.option.disable_warnings = False assert getreportopt(config) == "sfxw" @@ -837,8 +837,8 @@ def test_terminal_summary_warnings_are_displayed(testdir): """) result = testdir.runpytest('-rw') result.stdout.fnmatch_lines([ - '*C1*internal warning', - '*== 1 pytest-warnings in *', + '*internal warning', + '*== 1 warnings in *', ]) @@ -858,9 +858,9 @@ def test_terminal_summary_warnings_are_displayed(testdir): ("yellow", "1 weird", {"weird": (1,)}), ("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}), - ("yellow", "1 pytest-warnings", {"warnings": (1,)}), - ("yellow", "1 passed, 1 pytest-warnings", {"warnings": (1,), - "passed": (1,)}), + ("yellow", "1 warnings", {"warnings": (1,)}), + ("yellow", "1 passed, 1 warnings", {"warnings": (1,), + "passed": (1,)}), ("green", "5 passed", {"passed": (1,2,3,4,5)}), diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 3b9908ef7..ad80325f5 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -75,7 +75,6 @@ def test_setup_teardown_warnings(testdir, pyfile_with_warnings): ]) - @pytest.mark.parametrize('method', ['cmdline', 'ini']) def test_as_errors(testdir, pyfile_with_warnings, method): args = ('-W', 'error') if method == 'cmdline' else () From be5db6fa228e0d310194ecd64ad7e6795263c39f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 21:54:41 -0300 Subject: [PATCH 18/27] Capture warnings around the entire runtestprotocol This is necessary for the warnings plugin to play nice with the recwarn fixture --- _pytest/recwarn.py | 2 -- _pytest/warnings.py | 28 +++++++--------------------- testing/test_recwarn.py | 4 +++- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 43f68ed12..319bcb836 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -54,14 +54,12 @@ def deprecated_call(func=None, *args, **kwargs): def warn_explicit(message, category, *args, **kwargs): categories.append(category) - old_warn_explicit(message, category, *args, **kwargs) def warn(message, category=None, *args, **kwargs): if isinstance(message, Warning): categories.append(message.__class__) else: categories.append(category) - old_warn(message, category, *args, **kwargs) old_warn = warnings.warn old_warn_explicit = warnings.warn_explicit diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 9a91d181c..9b18185d2 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,4 +1,3 @@ -import os from contextlib import contextmanager import pytest @@ -50,7 +49,7 @@ 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') + warnings.simplefilter('always') for arg in args: warnings._setoption(arg) @@ -59,27 +58,14 @@ def catch_warnings_for_item(item): yield - for warning in log: - msg = warnings.formatwarning( - warning.message, warning.category, - warning.filename, warning.lineno, warning.line) - item.warn("unused", msg) + for warning in log: + msg = warnings.formatwarning( + warning.message, warning.category, + warning.filename, warning.lineno, warning.line) + item.warn("unused", msg) @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_call(item): +def pytest_runtest_protocol(item): with catch_warnings_for_item(item): yield - - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_setup(item): - with catch_warnings_for_item(item): - yield - - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_teardown(item): - with catch_warnings_for_item(item): - yield - diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 1269af431..5d5a68c89 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -145,7 +145,9 @@ class TestDeprecatedCall(object): pytest.deprecated_call(deprecated_function) """) result = testdir.runpytest() - result.stdout.fnmatch_lines('*=== 2 passed in *===') + # 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 *===') class TestWarns(object): From 78194093afe6bbb82aa2e636b67046ce96b7c238 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 21:55:03 -0300 Subject: [PATCH 19/27] Improve warning representation in terminal plugin and fix tests --- _pytest/config.py | 4 ++-- _pytest/fixtures.py | 2 +- _pytest/terminal.py | 43 +++++++++++++++++++++++++++++++------- testing/deprecated_test.py | 2 +- testing/python/collect.py | 10 ++++----- testing/test_assertion.py | 5 ++++- testing/test_config.py | 2 +- testing/test_junitxml.py | 2 +- testing/test_warnings.py | 8 +++---- 9 files changed, 55 insertions(+), 23 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 861775b87..0874c4599 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -910,11 +910,11 @@ class Config(object): fin = self._cleanup.pop() fin() - def warn(self, code, message, fslocation=None): + def warn(self, code, message, fslocation=None, nodeid=None): """ generate a warning for this test session. """ self.hook.pytest_logwarning.call_historic(kwargs=dict( code=code, message=message, - fslocation=fslocation, nodeid=None)) + fslocation=fslocation, nodeid=nodeid)) def get_terminal_writer(self): return self.pluginmanager.get_plugin("terminalreporter")._tw diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index c4d21635f..cd5c673ca 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -1080,7 +1080,7 @@ class FixtureManager(object): continue marker = defaultfuncargprefixmarker from _pytest import deprecated - self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name)) + self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid) name = name[len(self._argprefix):] elif not isinstance(marker, FixtureFunctionMarker): # magic globals with __getattr__ might have got us a wrong diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 741a0b600..dd92ddfa3 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -2,7 +2,6 @@ This is a good source for looking at the various reporting hooks. """ -import operator import itertools from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \ @@ -83,13 +82,40 @@ def pytest_report_teststatus(report): letter = "f" return report.outcome, letter, report.outcome.upper() + class WarningReport(object): + """ + Simple structure to hold warnings information captured by ``pytest_logwarning``. + """ def __init__(self, code, message, nodeid=None, fslocation=None): + """ + :param code: unused + :param str message: user friendly message about the warning + :param str|None nodeid: node id that generated the warning (see ``get_location``). + :param tuple|py.path.local fslocation: + file system location of the source of the warning (see ``get_location``). + """ self.code = code self.message = message self.nodeid = nodeid self.fslocation = fslocation + def get_location(self, config): + """ + Returns the more user-friendly information about the location + of a warning, or None. + """ + if self.nodeid: + return self.nodeid + if self.fslocation: + if isinstance(self.fslocation, tuple) and len(self.fslocation) == 2: + filename, linenum = self.fslocation + relpath = py.path.local(filename).relto(config.invocation_dir) + return '%s:%d' % (relpath, linenum) + else: + return str(self.fslocation) + return None + class TerminalReporter(object): def __init__(self, config, file=None): @@ -169,8 +195,6 @@ class TerminalReporter(object): def pytest_logwarning(self, code, fslocation, message, nodeid): warnings = self.stats.setdefault("warnings", []) - if isinstance(fslocation, tuple): - fslocation = "%s:%d" % fslocation warning = WarningReport(code=code, fslocation=fslocation, message=message, nodeid=nodeid) warnings.append(warning) @@ -444,12 +468,17 @@ class TerminalReporter(object): all_warnings = self.stats.get("warnings") if not all_warnings: return + + grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config)) + self.write_sep("=", "warnings summary", yellow=True, bold=False) - grouped = itertools.groupby(all_warnings, key=operator.attrgetter('nodeid')) - for nodeid, warnings in grouped: - self._tw.line(str(nodeid)) + for location, warnings in grouped: + self._tw.line(str(location) or '') for w in warnings: - self._tw.line(w.message) + lines = w.message.splitlines() + indented = '\n'.join(' ' + x for x in lines) + self._tw.line(indented) + self._tw.line() self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html') def summary_passes(self): diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index bad844281..ac1abda9c 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -26,7 +26,7 @@ def test_funcarg_prefix_deprecation(testdir): """) result = testdir.runpytest('-ra') result.stdout.fnmatch_lines([ - ('pytest_funcarg__value: ' + ('*pytest_funcarg__value: ' 'declaring fixtures using "pytest_funcarg__" prefix is deprecated ' 'and scheduled to be removed in pytest 4.0. ' 'Please remove the prefix and use the @pytest.fixture decorator instead.'), diff --git a/testing/python/collect.py b/testing/python/collect.py index e4069983a..57dcaf54f 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -113,9 +113,9 @@ class TestClass(object): pass """) result = testdir.runpytest("-rw") - result.stdout.fnmatch_lines_random(""" - WC1*test_class_with_init_warning.py*__init__* - """) + result.stdout.fnmatch_lines([ + "*cannot collect test class 'TestClass1' because it has a __init__ constructor", + ]) def test_class_subclassobject(self, testdir): testdir.getmodulecol(""" @@ -1241,8 +1241,8 @@ def test_dont_collect_non_function_callable(testdir): result = testdir.runpytest('-rw') result.stdout.fnmatch_lines([ '*collected 1 item*', - 'WC2 *', - '*1 passed, 1 pytest-warnings in *', + "*cannot collect 'test_a' because it is not a function*", + '*1 passed, 1 warnings in *', ]) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 8bfe65abd..2c04df63e 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -975,7 +975,10 @@ def test_assert_tuple_warning(testdir): assert(False, 'you shall not pass') """) result = testdir.runpytest('-rw') - result.stdout.fnmatch_lines('*test_assert_tuple_warning.py:2 assertion is always true*') + result.stdout.fnmatch_lines([ + '*test_assert_tuple_warning.py:2', + '*assertion is always true*', + ]) def test_assert_indirect_tuple_no_warning(testdir): testdir.makepyfile(""" diff --git a/testing/test_config.py b/testing/test_config.py index 21142c8df..40b944adc 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -638,7 +638,7 @@ class TestWarning(object): result = testdir.runpytest() result.stdout.fnmatch_lines(""" ===*warnings summary*=== - *test_warn_on_test_item_from_request::test_hello* + *test_warn_on_test_item_from_request.py::test_hello* *hello* """) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index d4e4d4c09..a87745350 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -854,7 +854,7 @@ def test_record_property(testdir): pnodes[1].assert_attr(name="foo", value="<1") result.stdout.fnmatch_lines([ 'test_record_property.py::test_record', - 'record_xml_property*experimental*', + '*record_xml_property*experimental*', ]) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index ad80325f5..e0baed8d1 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -39,10 +39,10 @@ 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"))', + '* warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', '*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated', - ' warnings.warn(DeprecationWarning("functionality is deprecated"))', + '* warnings.warn(DeprecationWarning("functionality is deprecated"))', '* 1 passed, 2 warnings*', ]) assert result.stdout.str().count('test_normal_flow.py::test_func') == 1 @@ -67,10 +67,10 @@ def test_setup_teardown_warnings(testdir, pyfile_with_warnings): '*== %s ==*' % WARNINGS_SUMMARY_HEADER, '*test_setup_teardown_warnings.py:6: UserWarning: warning during setup', - ' warnings.warn(UserWarning("warning during setup"))', + '*warnings.warn(UserWarning("warning during setup"))', '*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown', - ' warnings.warn(UserWarning("warning during teardown"))', + '*warnings.warn(UserWarning("warning during teardown"))', '* 1 passed, 2 warnings*', ]) From 3373e02eaeaec826d64de6dc6dff36d6072fe767 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 20:06:01 -0300 Subject: [PATCH 20/27] Add __future__ imports to warnings module --- _pytest/warnings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 9b18185d2..7d29460b3 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,7 +1,9 @@ +from __future__ import absolute_import, division, print_function + +import warnings from contextlib import contextmanager import pytest -import warnings def _setoption(wmod, arg): From d027f760c0aad45d038617971846c20f71b9b590 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 20:40:53 -0300 Subject: [PATCH 21/27] Avoid displaying the same warning multiple times for an item --- _pytest/warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 7d29460b3..bfa2b0087 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -51,7 +51,7 @@ 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('always') + warnings.simplefilter('once') for arg in args: warnings._setoption(arg) From fa56114115d8948c5960895034f6481700ffe6f2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 22:01:22 -0300 Subject: [PATCH 22/27] Clean up warnings generated by pytest's own suite --- _pytest/python.py | 2 +- testing/acceptance_test.py | 12 ++++++++--- testing/python/fixture.py | 41 ++++++++++++++++++++++++-------------- testing/python/metafunc.py | 3 ++- testing/test_config.py | 2 +- testing/test_parseopt.py | 15 +++++++------- tox.ini | 6 +++++- 7 files changed, 52 insertions(+), 29 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 81eed00f0..e763aa888 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -936,7 +936,7 @@ def _idval(val, argname, idx, idfn, config=None): import warnings msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx) msg += '\nUpdate your code as this will raise an error in pytest-4.0.' - warnings.warn(msg) + warnings.warn(msg, DeprecationWarning) if s: return _escape_strings(s) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index debda79ca..f0047574e 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -339,10 +339,16 @@ class TestGeneralUsage(object): "*ERROR*test_b.py::b*", ]) + @pytest.mark.usefixtures('recwarn') def test_namespace_import_doesnt_confuse_import_hook(self, testdir): - # Ref #383. Python 3.3's namespace package messed with our import hooks - # Importing a module that didn't exist, even if the ImportError was - # gracefully handled, would make our test crash. + """ + Ref #383. Python 3.3's namespace package messed with our import hooks + Importing a module that didn't exist, even if the ImportError was + gracefully handled, would make our test crash. + + Use recwarn here to silence this warning in Python 2.6 and 2.7: + ImportWarning: Not importing directory '...\not_a_package': missing __init__.py + """ testdir.mkdir('not_a_package') p = testdir.makepyfile(""" try: diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 269d2c68a..4c9ad7a91 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -545,22 +545,33 @@ class TestRequestBasic(object): return l.pop() def test_func(something): pass """) + import contextlib + if getfixmethod == 'getfuncargvalue': + warning_expectation = pytest.warns(DeprecationWarning) + else: + # see #1830 for a cleaner way to accomplish this + @contextlib.contextmanager + def expecting_no_warning(): yield + + warning_expectation = expecting_no_warning() + req = item._request - fixture_fetcher = getattr(req, getfixmethod) - pytest.raises(FixtureLookupError, fixture_fetcher, "notexists") - val = fixture_fetcher("something") - assert val == 1 - val = fixture_fetcher("something") - assert val == 1 - val2 = fixture_fetcher("other") - assert val2 == 2 - val2 = fixture_fetcher("other") # see about caching - assert val2 == 2 - pytest._fillfuncargs(item) - assert item.funcargs["something"] == 1 - assert len(get_public_names(item.funcargs)) == 2 - assert "request" in item.funcargs - #assert item.funcargs == {'something': 1, "other": 2} + with warning_expectation: + fixture_fetcher = getattr(req, getfixmethod) + with pytest.raises(FixtureLookupError): + fixture_fetcher("notexists") + val = fixture_fetcher("something") + assert val == 1 + val = fixture_fetcher("something") + assert val == 1 + val2 = fixture_fetcher("other") + assert val2 == 2 + val2 = fixture_fetcher("other") # see about caching + assert val2 == 2 + pytest._fillfuncargs(item) + assert item.funcargs["something"] == 1 + assert len(get_public_names(item.funcargs)) == 2 + assert "request" in item.funcargs def test_request_addfinalizer(self, testdir): item = testdir.getitem(""" diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index c347dc9e2..380dbf0e6 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -347,7 +347,8 @@ class TestMetafunc(object): def test_foo(arg): pass """) - result = testdir.runpytest("--collect-only") + with pytest.warns(DeprecationWarning): + result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines([ "", " ", diff --git a/testing/test_config.py b/testing/test_config.py index fed781ce0..0d8e6abfc 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -141,7 +141,7 @@ class TestConfigAPI(object): from __future__ import unicode_literals def pytest_addoption(parser): - parser.addoption('--hello', type='string') + parser.addoption('--hello', type=str) """) config = testdir.parseconfig('--hello=this') assert config.getoption('hello') == 'this' diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index f990e8b04..38542783a 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -34,15 +34,16 @@ class TestParser(object): ) def test_argument_type(self): - argument = parseopt.Argument('-t', dest='abc', type='int') + argument = parseopt.Argument('-t', dest='abc', type=int) assert argument.type is int - argument = parseopt.Argument('-t', dest='abc', type='string') + argument = parseopt.Argument('-t', dest='abc', type=str) assert argument.type is str argument = parseopt.Argument('-t', dest='abc', type=float) assert argument.type is float - with pytest.raises(KeyError): - argument = parseopt.Argument('-t', dest='abc', type='choice') - argument = parseopt.Argument('-t', dest='abc', type='choice', + with pytest.warns(DeprecationWarning): + with pytest.raises(KeyError): + argument = parseopt.Argument('-t', dest='abc', type='choice') + argument = parseopt.Argument('-t', dest='abc', type=str, choices=['red', 'blue']) assert argument.type is str @@ -176,8 +177,8 @@ class TestParser(object): elif option.type is str: option.default = "world" parser = parseopt.Parser(processopt=defaultget) - parser.addoption("--this", dest="this", type="int", action="store") - parser.addoption("--hello", dest="hello", type="string", action="store") + parser.addoption("--this", dest="this", type=int, action="store") + parser.addoption("--hello", dest="hello", type=str, action="store") parser.addoption("--no", dest="no", action="store_true") option = parser.parse([]) assert option.hello == "world" diff --git a/tox.ini b/tox.ini index 1b9fb9f5a..f8ea5fabd 100644 --- a/tox.ini +++ b/tox.ini @@ -176,7 +176,11 @@ python_files=test_*.py *_test.py testing/*/*.py python_classes=Test Acceptance python_functions=test norecursedirs = .tox ja .hg cx_freeze_source - +filterwarnings= error + # produced by path.local + ignore:bad escape.*:DeprecationWarning:re + # produced by path.readlines + ignore:.*U.*mode is deprecated:DeprecationWarning [flake8] ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402 From eabe3eed6b853b68b54cffe295adaf73be468eef Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 23:35:01 -0300 Subject: [PATCH 23/27] Add docs for the warnings functionality --- doc/en/contents.rst | 2 +- doc/en/customize.rst | 20 ++++ doc/en/recwarn.rst | 143 +--------------------------- doc/en/warnings.rst | 218 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+), 142 deletions(-) create mode 100644 doc/en/warnings.rst diff --git a/doc/en/contents.rst b/doc/en/contents.rst index d7f900810..f6a561839 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -18,7 +18,7 @@ Full pytest documentation monkeypatch tmpdir capture - recwarn + warnings doctest mark skipping diff --git a/doc/en/customize.rst b/doc/en/customize.rst index c6d3eb473..4421889db 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -240,3 +240,23 @@ Builtin configuration file options By default, pytest will stop searching for ``conftest.py`` files upwards from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any, or up to the file-system root. + + +.. confval:: filterwarnings + + .. versionadded:: 3.1 + + Sets a list of filters and actions that should be taken for matched + warnings. By default all warnings emitted during the test session + will be displayed in a summary at the end of the test session. + + .. code-block:: ini + + # content of pytest.ini + [pytest] + filterwarnings = + error + ignore::DeprecationWarning + + This tells pytest to ignore deprecation warnings and turn all other warnings + into errors. For more information please refer to :ref:`warnings`. diff --git a/doc/en/recwarn.rst b/doc/en/recwarn.rst index 823ba945b..513af0d45 100644 --- a/doc/en/recwarn.rst +++ b/doc/en/recwarn.rst @@ -1,142 +1,3 @@ -.. _`asserting warnings`: +:orphan: -.. _assertwarnings: - -Asserting Warnings -===================================================== - -.. _`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 `:: - - 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 ` 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() +This page has been moved, please see :ref:`assertwarnings`. diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst new file mode 100644 index 000000000..ae15e3129 --- /dev/null +++ b/doc/en/warnings.rst @@ -0,0 +1,218 @@ +.. _`warnings`: + +Warnings Capture +================ + +.. versionadded:: 3.1 + +Starting from version ``3.1``, pytest now automatically catches all 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) + return 1 + + def test_one(): + assert deprecated_function() == 1 + +Running pytest now produces this output:: + + $ pytest test_show_warnings.py + . + ============================== warnings summary =============================== + test_show_warning.py::test_one + C:\pytest\.tmp\test_show_warning.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.01 seconds + +The ``-W`` flag can be passed to control which warnings will be displayed or even turn +them into errors:: + + $ pytest -q test_show_warning.py -W error::DeprecationWarning + F + ================================== FAILURES =================================== + __________________________________ test_one ___________________________________ + + def test_one(): + > assert deprecated_function() == 1 + + test_show_warning.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_warning.py:4: DeprecationWarning + 1 failed in 0.02 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 +all other warnings into errors. + +.. code-block:: ini + + [pytest] + filterwarnings = + error + ignore::DeprecationWarning + + +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. + +*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 + +.. _`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 `:: + + 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 ` 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() From 916d272c443bf22ff73759e797be29979ec70b7a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 23:44:50 -0300 Subject: [PATCH 24/27] Fix test on linux --- testing/test_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_cache.py b/testing/test_cache.py index 47072e07e..f5904be39 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -55,7 +55,7 @@ class TestNewAPI(object): assert result.ret == 1 result.stdout.fnmatch_lines([ "*could not create cache path*", - "*1 pytest-warnings*", + "*1 warnings*", ]) def test_config_cache(self, testdir): From 2c730743f1b80b38b044ecc08c98609df60ba375 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Mar 2017 22:17:07 -0300 Subject: [PATCH 25/27] Fix errors related to warnings raised by xdist - pytester was creating a 'pexpect' directory to serve as temporary dir, but due to the fact that xdist adds the current directory to sys.path, that directory was being considered as candidate for import as a package. The directory is empty and a warning was being raised about it missing __init__ file, which is now turned into an error by our filterwarnings config in pytest.ini. - Decided to play it safe and ignore any warnings during `pytest.importorskip`. - pytest-xdist and execnet raise two warnings which should be fixed upstream: pytest-dev/pytest-xdist/issues/133 --- _pytest/pytester.py | 2 +- _pytest/runner.py | 17 ++++++++++++----- tox.ini | 4 ++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index ee0e5bbe7..6381595ee 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -1008,7 +1008,7 @@ class Testdir(object): The pexpect child is returned. """ - basetemp = self.tmpdir.mkdir("pexpect") + basetemp = self.tmpdir.mkdir("temp-pexpect") invoke = " ".join(map(str, self._getpytestargs())) cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string) return self.spawn(cmd, expect_timeout=expect_timeout) diff --git a/_pytest/runner.py b/_pytest/runner.py index 4277f8ee3..07f4be019 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -554,14 +554,21 @@ def importorskip(modname, minversion=None): __version__ attribute. If no minversion is specified the a skip is only triggered if the module can not be imported. """ + import warnings __tracebackhide__ = True compile(modname, '', 'eval') # to catch syntaxerrors should_skip = False - try: - __import__(modname) - except ImportError: - # Do not raise chained exception here(#1485) - should_skip = True + + with warnings.catch_warnings(): + # make sure to ignore ImportWarnings that might happen because + # of existing directories with the same name we're trying to + # import but without a __init__.py file + warnings.simplefilter('ignore') + try: + __import__(modname) + except ImportError: + # Do not raise chained exception here(#1485) + should_skip = True if should_skip: raise Skipped("could not import %r" %(modname,), allow_module_level=True) mod = sys.modules[modname] diff --git a/tox.ini b/tox.ini index f8ea5fabd..5c103f94c 100644 --- a/tox.ini +++ b/tox.ini @@ -181,6 +181,10 @@ filterwarnings= error ignore:bad escape.*:DeprecationWarning:re # produced by path.readlines ignore:.*U.*mode is deprecated:DeprecationWarning + # produced by pytest-xdist + ignore:.*type argument to addoption.*:DeprecationWarning + # produced by python >=3.5 on execnet (pytest-xdist) + ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning [flake8] ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402 From 74b54ac0ec9c64d8a6693afdb3e2a6da4eef92c6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Mar 2017 22:32:41 -0300 Subject: [PATCH 26/27] Fix errors related to warnings raised on pypy test environment For some reason pypy raises this warning in the line that the catch_warnings block was added: ______________________________ ERROR collecting ______________________________ C:\ProgramData\chocolatey\lib\python.pypy\tools\pypy2-v5.4.1-win32\lib-python\2.7\pkgutil.py:476: in find_loader loader = importer.find_module(fullname) c:\pytest\.tox\pypy\site-packages\_pytest\assertion\rewrite.py:75: in find_module fd, fn, desc = imp.find_module(lastname, path) /?:3: in anonymous ??? E ImportWarning: Not importing directory 'c:\users\bruno\appdata\local\temp\pytest-of-Bruno\pytest-3192\testdir\test_cmdline_python_package0' missing __init__.py --- testing/acceptance_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index f0047574e..fe02d82f0 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -530,6 +530,7 @@ class TestInvocationVariants(object): ]) def test_cmdline_python_package(self, testdir, monkeypatch): + import warnings monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False) path = testdir.mkpydir("tpkg") path.join("test_hello.py").write("def test_hello(): pass") @@ -552,7 +553,11 @@ class TestInvocationVariants(object): return what empty_package = testdir.mkpydir("empty_package") monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package)) - result = testdir.runpytest("--pyargs", ".") + # the path which is not a package raises a warning on pypy; + # no idea why only pypy and not normal python warn about it here + with warnings.catch_warnings(): + warnings.simplefilter('ignore', ImportWarning) + result = testdir.runpytest("--pyargs", ".") assert result.ret == 0 result.stdout.fnmatch_lines([ "*2 passed*" From 0c1c2580d0d3c8d037ab3e4f5524b626068c33e5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 22 Mar 2017 12:40:31 -0300 Subject: [PATCH 27/27] Add CHANGELOG entry --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 37570ed43..05c868079 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,10 @@ New Features * ``pytest.param`` can be used to declare test parameter sets with marks and test ids. Thanks `@RonnyPfannschmidt`_ for the PR. +* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically + captures and displays warnings at the end of the test session. + Thanks `@nicoddemus`_ for the PR. + Changes -------