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"