test_ok2/testing/test_warnings.py

716 lines
21 KiB
Python

# -*- coding: utf8 -*-
from __future__ import unicode_literals
import sys
import warnings
import six
import pytest
WARNINGS_SUMMARY_HEADER = "warnings summary"
@pytest.fixture
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(UserWarning("user warning"))
warnings.warn(RuntimeWarning("runtime warning"))
return 1
""",
test_name: """
import {module_name}
def test_func():
assert {module_name}.foo() == 1
""".format(
module_name=module_name
),
}
)
@pytest.mark.filterwarnings("default")
def test_normal_flow(testdir, pyfile_with_warnings):
"""
Check that the warnings section is displayed.
"""
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
"test_normal_flow.py::test_func",
"*normal_flow_module.py:3: UserWarning: user warning",
'* warnings.warn(UserWarning("user warning"))',
"*normal_flow_module.py:4: RuntimeWarning: runtime warning",
'* warnings.warn(RuntimeWarning("runtime warning"))',
"* 1 passed, 2 warnings*",
]
)
@pytest.mark.filterwarnings("always")
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 ()
if method == "ini":
testdir.makeini(
"""
[pytest]
filterwarnings=error
"""
)
# Use a subprocess, since changing logging level affects other threads
# (xdist).
result = testdir.runpytest_subprocess(*args)
result.stdout.fnmatch_lines(
[
"E UserWarning: user warning",
"as_errors_module.py:3: UserWarning",
"* 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(*args)
result.stdout.fnmatch_lines(["* 1 passed in *"])
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
@pytest.mark.skipif(
sys.version_info < (3, 0), reason="warnings message is unicode is ok in python3"
)
@pytest.mark.filterwarnings("always")
def test_unicode(testdir, pyfile_with_warnings):
testdir.makepyfile(
"""
# -*- coding: utf8 -*-
import warnings
import pytest
@pytest.fixture
def fix():
warnings.warn(u"测试")
yield
def test_func(fix):
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
"*test_unicode.py:8: UserWarning: \u6d4b\u8bd5*",
"* 1 passed, 1 warnings*",
]
)
@pytest.mark.skipif(
sys.version_info >= (3, 0),
reason="warnings message is broken as it is not str instance",
)
def test_py2_unicode(testdir, pyfile_with_warnings):
if getattr(sys, "pypy_version_info", ())[:2] == (5, 9) and sys.platform.startswith(
"win"
):
pytest.xfail("fails with unicode error on PyPy2 5.9 and Windows (#2905)")
testdir.makepyfile(
"""
# -*- coding: utf8 -*-
import warnings
import pytest
@pytest.fixture
def fix():
warnings.warn(u"测试")
yield
@pytest.mark.filterwarnings('always')
def test_func(fix):
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
"*test_py2_unicode.py:8: UserWarning: \\u6d4b\\u8bd5",
'*warnings.warn(u"\u6d4b\u8bd5")',
"*warnings.py:*: UnicodeWarning: Warning is using unicode non*",
"* 1 passed, 2 warnings*",
]
)
def test_py2_unicode_ascii(testdir):
"""Ensure that our warning about 'unicode warnings containing non-ascii messages'
does not trigger with ascii-convertible messages"""
testdir.makeini("[pytest]")
testdir.makepyfile(
"""
import pytest
import warnings
@pytest.mark.filterwarnings('always')
def test_func():
warnings.warn(u"hello")
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
'*warnings.warn(u"hello")',
"* 1 passed, 1 warnings in*",
]
)
def test_works_with_filterwarnings(testdir):
"""Ensure our warnings capture does not mess with pre-installed filters (#2430)."""
testdir.makepyfile(
"""
import warnings
class MyWarning(Warning):
pass
warnings.filterwarnings("error", category=MyWarning)
class TestWarnings(object):
def test_my_warning(self):
try:
warnings.warn(MyWarning("warn!"))
assert False
except MyWarning:
assert True
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*== 1 passed in *"])
@pytest.mark.parametrize("default_config", ["ini", "cmdline"])
def test_filterwarnings_mark(testdir, default_config):
"""
Test ``filterwarnings`` mark works and takes precedence over command line and ini options.
"""
if default_config == "ini":
testdir.makeini(
"""
[pytest]
filterwarnings = always
"""
)
testdir.makepyfile(
"""
import warnings
import pytest
@pytest.mark.filterwarnings('ignore::RuntimeWarning')
def test_ignore_runtime_warning():
warnings.warn(RuntimeWarning())
@pytest.mark.filterwarnings('error')
def test_warning_error():
warnings.warn(RuntimeWarning())
def test_show_warning():
warnings.warn(RuntimeWarning())
"""
)
result = testdir.runpytest("-W always" if default_config == "cmdline" else "")
result.stdout.fnmatch_lines(["*= 1 failed, 2 passed, 1 warnings in *"])
def test_non_string_warning_argument(testdir):
"""Non-str argument passed to warning breaks pytest (#2956)"""
testdir.makepyfile(
"""
import warnings
import pytest
def test():
warnings.warn(UserWarning(1, u'foo'))
"""
)
result = testdir.runpytest("-W", "always")
result.stdout.fnmatch_lines(["*= 1 passed, 1 warnings in *"])
def test_filterwarnings_mark_registration(testdir):
"""Ensure filterwarnings mark is registered"""
testdir.makepyfile(
"""
import pytest
@pytest.mark.filterwarnings('error')
def test_func():
pass
"""
)
result = testdir.runpytest("--strict")
assert result.ret == 0
@pytest.mark.filterwarnings("always")
def test_warning_captured_hook(testdir):
testdir.makeconftest(
"""
from _pytest.warnings import _issue_warning_captured
def pytest_configure(config):
_issue_warning_captured(UserWarning("config warning"), config.hook, stacklevel=2)
"""
)
testdir.makepyfile(
"""
import pytest, warnings
warnings.warn(UserWarning("collect warning"))
@pytest.fixture
def fix():
warnings.warn(UserWarning("setup warning"))
yield 1
warnings.warn(UserWarning("teardown warning"))
def test_func(fix):
warnings.warn(UserWarning("call warning"))
assert fix == 1
"""
)
collected = []
class WarningCollector:
def pytest_warning_captured(self, warning_message, when, item):
imge_name = item.name if item is not None else ""
collected.append((str(warning_message.message), when, imge_name))
result = testdir.runpytest(plugins=[WarningCollector()])
result.stdout.fnmatch_lines(["*1 passed*"])
expected = [
("config warning", "config", ""),
("collect warning", "collect", ""),
("setup warning", "runtest", "test_func"),
("call warning", "runtest", "test_func"),
("teardown warning", "runtest", "test_func"),
]
assert collected == expected
@pytest.mark.filterwarnings("always")
def test_collection_warnings(testdir):
"""
Check that we also capture warnings issued during test collection (#3251).
"""
testdir.makepyfile(
"""
import warnings
warnings.warn(UserWarning("collection warning"))
def test_foo():
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
" *collection_warnings.py:3: UserWarning: collection warning",
' warnings.warn(UserWarning("collection warning"))',
"* 1 passed, 1 warnings*",
]
)
@pytest.mark.filterwarnings("always")
def test_mark_regex_escape(testdir):
"""@pytest.mark.filterwarnings should not try to escape regex characters (#3936)"""
testdir.makepyfile(
r"""
import pytest, warnings
@pytest.mark.filterwarnings(r"ignore:some \(warning\)")
def test_foo():
warnings.warn(UserWarning("some (warning)"))
"""
)
result = testdir.runpytest()
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
@pytest.mark.filterwarnings("default")
@pytest.mark.parametrize("ignore_pytest_warnings", ["no", "ini", "cmdline"])
def test_hide_pytest_internal_warnings(testdir, ignore_pytest_warnings):
"""Make sure we can ignore internal pytest warnings using a warnings filter."""
testdir.makepyfile(
"""
import pytest
import warnings
warnings.warn(pytest.PytestWarning("some internal warning"))
def test_bar():
pass
"""
)
if ignore_pytest_warnings == "ini":
testdir.makeini(
"""
[pytest]
filterwarnings = ignore::pytest.PytestWarning
"""
)
args = (
["-W", "ignore::pytest.PytestWarning"]
if ignore_pytest_warnings == "cmdline"
else []
)
result = testdir.runpytest(*args)
if ignore_pytest_warnings != "no":
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
else:
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
"*test_hide_pytest_internal_warnings.py:4: PytestWarning: some internal warning",
"* 1 passed, 1 warnings *",
]
)
@pytest.mark.parametrize("ignore_on_cmdline", [True, False])
def test_option_precedence_cmdline_over_ini(testdir, ignore_on_cmdline):
"""filters defined in the command-line should take precedence over filters in ini files (#3946)."""
testdir.makeini(
"""
[pytest]
filterwarnings = error
"""
)
testdir.makepyfile(
"""
import warnings
def test():
warnings.warn(UserWarning('hello'))
"""
)
args = ["-W", "ignore"] if ignore_on_cmdline else []
result = testdir.runpytest(*args)
if ignore_on_cmdline:
result.stdout.fnmatch_lines(["* 1 passed in*"])
else:
result.stdout.fnmatch_lines(["* 1 failed in*"])
def test_option_precedence_mark(testdir):
"""Filters defined by marks should always take precedence (#3946)."""
testdir.makeini(
"""
[pytest]
filterwarnings = ignore
"""
)
testdir.makepyfile(
"""
import pytest, warnings
@pytest.mark.filterwarnings('error')
def test():
warnings.warn(UserWarning('hello'))
"""
)
result = testdir.runpytest("-W", "ignore")
result.stdout.fnmatch_lines(["* 1 failed in*"])
class TestDeprecationWarningsByDefault:
"""
Note: all pytest runs are executed in a subprocess so we don't inherit warning filters
from pytest's own test suite
"""
def create_file(self, testdir, mark=""):
testdir.makepyfile(
"""
import pytest, warnings
warnings.warn(DeprecationWarning("collection"))
{mark}
def test_foo():
warnings.warn(PendingDeprecationWarning("test run"))
""".format(
mark=mark
)
)
@pytest.mark.parametrize("customize_filters", [True, False])
def test_shown_by_default(self, testdir, customize_filters):
"""Show deprecation warnings by default, even if user has customized the warnings filters (#4013)."""
self.create_file(testdir)
if customize_filters:
testdir.makeini(
"""
[pytest]
filterwarnings =
once::UserWarning
"""
)
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
"*test_shown_by_default.py:3: DeprecationWarning: collection",
"*test_shown_by_default.py:7: PendingDeprecationWarning: test run",
"* 1 passed, 2 warnings*",
]
)
def test_hidden_by_ini(self, testdir):
self.create_file(testdir)
testdir.makeini(
"""
[pytest]
filterwarnings =
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
"""
)
result = testdir.runpytest_subprocess()
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
def test_hidden_by_mark(self, testdir):
"""Should hide the deprecation warning from the function, but the warning during collection should
be displayed normally.
"""
self.create_file(
testdir,
mark='@pytest.mark.filterwarnings("ignore::PendingDeprecationWarning")',
)
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(
[
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
"*test_hidden_by_mark.py:3: DeprecationWarning: collection",
"* 1 passed, 1 warnings*",
]
)
def test_hidden_by_cmdline(self, testdir):
self.create_file(testdir)
result = testdir.runpytest_subprocess(
"-W",
"ignore::DeprecationWarning",
"-W",
"ignore::PendingDeprecationWarning",
)
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
def test_hidden_by_system(self, testdir, monkeypatch):
self.create_file(testdir)
monkeypatch.setenv(str("PYTHONWARNINGS"), str("once::UserWarning"))
result = testdir.runpytest_subprocess()
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
@pytest.mark.skipif(six.PY3, reason="Python 2 only issue")
def test_infinite_loop_warning_against_unicode_usage_py2(testdir):
"""
We need to be careful when raising the warning about unicode usage with "warnings.warn"
because it might be overwritten by users and this itself causes another warning (#3691).
"""
testdir.makepyfile(
"""
# -*- coding: utf8 -*-
from __future__ import unicode_literals
import warnings
import pytest
def _custom_showwarning(message, *a, **b):
return "WARNING: {}".format(message)
warnings.formatwarning = _custom_showwarning
@pytest.mark.filterwarnings("default")
def test_custom_warning_formatter():
warnings.warn("¥")
"""
)
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(["*1 passed, * warnings in*"])
@pytest.mark.parametrize("change_default", [None, "ini", "cmdline"])
def test_removed_in_pytest4_warning_as_error(testdir, change_default):
testdir.makepyfile(
"""
import warnings, pytest
def test():
warnings.warn(pytest.RemovedInPytest4Warning("some warning"))
"""
)
if change_default == "ini":
testdir.makeini(
"""
[pytest]
filterwarnings =
ignore::pytest.RemovedInPytest4Warning
"""
)
args = (
("-Wignore::pytest.RemovedInPytest4Warning",)
if change_default == "cmdline"
else ()
)
result = testdir.runpytest(*args)
if change_default is None:
result.stdout.fnmatch_lines(["* 1 failed in *"])
else:
assert change_default in ("ini", "cmdline")
result.stdout.fnmatch_lines(["* 1 passed in *"])
class TestAssertionWarnings:
@staticmethod
def assert_result_warns(result, msg):
result.stdout.fnmatch_lines(["*PytestAssertRewriteWarning: %s*" % msg])
def test_tuple_warning(self, testdir):
testdir.makepyfile(
"""
def test_foo():
assert (1,2)
"""
)
result = testdir.runpytest()
self.assert_result_warns(
result, "assertion is always true, perhaps remove parentheses?"
)
@staticmethod
def create_file(testdir, return_none):
testdir.makepyfile(
"""
def foo(return_none):
if return_none:
return None
else:
return False
def test_foo():
assert foo({return_none})
""".format(
return_none=return_none
)
)
def test_none_function_warns(self, testdir):
self.create_file(testdir, True)
result = testdir.runpytest()
self.assert_result_warns(
result, 'asserting the value None, please use "assert is None"'
)
def test_assert_is_none_no_warn(self, testdir):
testdir.makepyfile(
"""
def foo():
return None
def test_foo():
assert foo() is None
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed in*"])
def test_false_function_no_warn(self, testdir):
self.create_file(testdir, False)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 failed in*"])
def test_warnings_checker_twice():
"""Issue #4617"""
expectation = pytest.warns(UserWarning)
with expectation:
warnings.warn("Message A", UserWarning)
with expectation:
warnings.warn("Message B", UserWarning)
@pytest.mark.filterwarnings("always")
def test_group_warnings_by_message(testdir):
testdir.copy_example("warnings/test_group_warnings_by_message.py")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"test_group_warnings_by_message.py::test_foo[0]",
"test_group_warnings_by_message.py::test_foo[1]",
"test_group_warnings_by_message.py::test_foo[2]",
"test_group_warnings_by_message.py::test_foo[3]",
"test_group_warnings_by_message.py::test_foo[4]",
"test_group_warnings_by_message.py::test_bar",
]
)
warning_code = 'warnings.warn(UserWarning("foo"))'
assert warning_code in result.stdout.str()
assert result.stdout.str().count(warning_code) == 1