diff --git a/warnings-root/.gitignore b/warnings-root/.gitignore new file mode 100644 index 000000000..80b4d47de --- /dev/null +++ b/warnings-root/.gitignore @@ -0,0 +1,9 @@ +/.cache/ +/.coverage +/.tox/ +/bin/ +/dist/ +/htmlcov/ +/include/ +/lib/ +/pip-selfcheck.json diff --git a/warnings-root/LICENSE b/warnings-root/LICENSE new file mode 100644 index 000000000..8a30978e3 --- /dev/null +++ b/warnings-root/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/warnings-root/MANIFEST.in b/warnings-root/MANIFEST.in new file mode 100644 index 000000000..1652af658 --- /dev/null +++ b/warnings-root/MANIFEST.in @@ -0,0 +1,4 @@ +include README.rst +include tox.ini +include LICENSE +include tests/*.py diff --git a/warnings-root/README.rst b/warnings-root/README.rst new file mode 100644 index 000000000..c598259c3 --- /dev/null +++ b/warnings-root/README.rst @@ -0,0 +1,70 @@ +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/pytest_warnings/__init__.py b/warnings-root/pytest_warnings/__init__.py new file mode 100644 index 000000000..84f64ea92 --- /dev/null +++ b/warnings-root/pytest_warnings/__init__.py @@ -0,0 +1,83 @@ +from _pytest.recwarn import RecordedWarning, WarningsRecorder +import inspect +import os +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 -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) +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') + for arg in args: + wrec._module._setoption(arg) + + for arg in inifilters: + _setoption(wrec._module, 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/warnings-root/setup.py b/warnings-root/setup.py new file mode 100644 index 000000000..19a4c6e80 --- /dev/null +++ b/warnings-root/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.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 new file mode 100644 index 000000000..ba88aa31d --- /dev/null +++ b/warnings-root/tests/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/warnings-root/tests/helper_test_b.py b/warnings-root/tests/helper_test_b.py new file mode 100644 index 000000000..3c00a6114 --- /dev/null +++ b/warnings-root/tests/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/warnings-root/tests/test_warnings.py b/warnings-root/tests/test_warnings.py new file mode 100644 index 000000000..bc65220c7 --- /dev/null +++ b/warnings-root/tests/test_warnings.py @@ -0,0 +1,95 @@ +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 new file mode 100644 index 000000000..d4c9be0ea --- /dev/null +++ b/warnings-root/tests/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/warnings-root/tox.ini b/warnings-root/tox.ini new file mode 100644 index 000000000..ec2e5622d --- /dev/null +++ b/warnings-root/tox.ini @@ -0,0 +1,18 @@ +[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 .*