diff --git a/_pytest/compat.py b/_pytest/compat.py index 45491a722c1..7bf3bb9b89f 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -149,7 +149,7 @@ def _bytes_to_ascii(val): # empty bytes crashes codecs.escape_encode (#1087) return '' - def _ascii_escaped(val): + def ascii_escaped(val): """If val is pure ascii, returns it as a str(). Otherwise, escapes bytes objects into a sequence of escaped bytes: @@ -177,7 +177,7 @@ def _ascii_escaped(val): from itertools import imap, izip # NOQA - def _ascii_escaped(val): + def ascii_escaped(val): """In py2 bytes and str are the same type, so return if it's a bytes object, return it unchanged if it is a full ascii string, otherwise escape it into its binary form. diff --git a/_pytest/python.py b/_pytest/python.py index e3d79b2e975..0161c10e8ec 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -19,7 +19,7 @@ from _pytest import fixtures from _pytest import main from _pytest.compat import ( - isclass, isfunction, is_generator, _ascii_escaped, + isclass, isfunction, is_generator, ascii_escaped, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, get_real_func, getfslineno, safe_getattr, safe_str, getlocation, enum, @@ -922,7 +922,7 @@ def _idval(val, argname, idx, idfn, config=None): msg += '\nUpdate your code as this will raise an error in pytest-4.0.' warnings.warn(msg, DeprecationWarning) if s: - return _ascii_escaped(s) + return ascii_escaped(s) if config: hook_id = config.hook.pytest_make_parametrize_id( @@ -931,11 +931,11 @@ def _idval(val, argname, idx, idfn, config=None): return hook_id if isinstance(val, STRING_TYPES): - return _ascii_escaped(val) + return ascii_escaped(val) elif isinstance(val, (float, int, bool, NoneType)): return str(val) elif isinstance(val, REGEX_TYPE): - return _ascii_escaped(val.pattern) + return ascii_escaped(val.pattern) elif enum is not None and isinstance(val, enum.Enum): return str(val) elif isclass(val) and hasattr(val, '__name__'): @@ -951,7 +951,7 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): for val, argname in zip(parameterset.values, argnames)] return "-".join(this_id) else: - return _ascii_escaped(ids[idx]) + return ascii_escaped(ids[idx]) def idmaker(argnames, parametersets, idfn=None, ids=None, config=None): diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 926b1f58112..847771daa25 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -72,8 +72,8 @@ def catch_warnings_for_item(item): unicode_warning = False if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args): - new_args = [compat.safe_str(m) for m in warn_msg.args] - unicode_warning = warn_msg.args != new_args + new_args = [compat.ascii_escaped(m) for m in warn_msg.args] + unicode_warning = list(warn_msg.args) != new_args warn_msg.args = new_args msg = warnings.formatwarning( diff --git a/changelog/2809.bugfix b/changelog/2809.bugfix new file mode 100644 index 00000000000..6db7e8c6c57 --- /dev/null +++ b/changelog/2809.bugfix @@ -0,0 +1 @@ +Pytest no longer complains about warnings with unicode messages being non-ascii compatible even for ascii-compatible messages. As a result of this, warnings with unicode messages are converted first to an ascii representation for safety. \ No newline at end of file diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 1328cc3f2b8..4beff45480c 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -163,13 +163,33 @@ def test_func(fix): result.stdout.fnmatch_lines([ '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - '*test_py2_unicode.py:8: UserWarning: \u6d4b\u8bd5', + '*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('''