diff --git a/changelog/3691.bugfix.rst b/changelog/3691.bugfix.rst new file mode 100644 index 00000000000..f30dd67a1fe --- /dev/null +++ b/changelog/3691.bugfix.rst @@ -0,0 +1,2 @@ +Python 2: safely format warning message about passing unicode strings to ``warnings.warn``, which may cause +surprising ``MemoryError`` exception when monkey patching ``warnings.warn`` itself. diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 5574eee8ea9..e900ff0672c 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -123,7 +123,7 @@ def warning_record_to_str(warning_message): if unicode_warning: warnings.warn( "Warning is using unicode non convertible to ascii, " - "converting to a safe representation:\n %s" % msg, + "converting to a safe representation:\n {!r}".format(compat.safe_str(msg)), UnicodeWarning, ) return msg diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 7825f216782..10eb5ea33f9 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -3,6 +3,8 @@ import sys +import six + import pytest @@ -562,3 +564,30 @@ def test_hidden_by_system(self, testdir, monkeypatch): 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*"]) diff --git a/tox.ini b/tox.ini index 86b3b94581e..dbfd4eef5c4 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ envlist = [testenv] commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof {posargs} coverage: coverage combine coverage: coverage report passenv = USER USERNAME COVERAGE_* TRAVIS @@ -41,7 +41,7 @@ deps = py27: mock nose commands = - pytest -n auto --runpytest=subprocess + pytest -n auto --runpytest=subprocess {posargs} [testenv:linting] @@ -58,7 +58,7 @@ deps = hypothesis>=3.56 {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} [testenv:py36-xdist] # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.