From fbb9e9328bbc008be02ad050d5703a44c90e46d2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 2 Oct 2017 16:20:51 -0300 Subject: [PATCH 1/2] Fix warning about non-ascii warnings even when they are ascii Fix #2809 --- _pytest/warnings.py | 4 ++-- changelog/2809.bugfix | 1 + testing/test_warnings.py | 22 +++++++++++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 changelog/2809.bugfix diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 926b1f581..4a4b6e687 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 = [m.encode('ascii', 'replace') 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 000000000..6db7e8c6c --- /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 1328cc3f2..fea3959f9 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -163,13 +163,33 @@ def test_py2_unicode(testdir, pyfile_with_warnings): result.stdout.fnmatch_lines([ '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - '*test_py2_unicode.py:8: UserWarning: \u6d4b\u8bd5', + '*test_py2_unicode.py:8: UserWarning: ??', '*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(''' From df6d5cd4e7df94f276281a48826202b622c0a68d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 3 Oct 2017 11:09:24 -0300 Subject: [PATCH 2/2] Use ascii_escaped to escape unicode warnings --- _pytest/compat.py | 4 ++-- _pytest/python.py | 10 +++++----- _pytest/warnings.py | 2 +- testing/test_warnings.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index 45491a722..7bf3bb9b8 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -149,7 +149,7 @@ if _PY3: # 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 @@ else: 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 e3d79b2e9..0161c10e8 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -19,7 +19,7 @@ import pluggy 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 4a4b6e687..847771daa 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -72,7 +72,7 @@ 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 = [m.encode('ascii', 'replace') for m in warn_msg.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 diff --git a/testing/test_warnings.py b/testing/test_warnings.py index fea3959f9..4beff4548 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -163,7 +163,7 @@ def test_py2_unicode(testdir, pyfile_with_warnings): result.stdout.fnmatch_lines([ '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - '*test_py2_unicode.py:8: UserWarning: ??', + '*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*',