From 013d0e66c79acae06b9d61368b57f11223efadb5 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 25 Jun 2019 12:40:07 +0100 Subject: [PATCH 1/5] use safe_str to serialize Exceptions Fixes #5478 --- changelog/5478.bugfix.rst | 1 + src/_pytest/_code/code.py | 5 +++-- testing/python/raises.py | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 changelog/5478.bugfix.rst diff --git a/changelog/5478.bugfix.rst b/changelog/5478.bugfix.rst new file mode 100644 index 00000000000..b9e80a88ee6 --- /dev/null +++ b/changelog/5478.bugfix.rst @@ -0,0 +1 @@ +Use safe_str to serialize Exceptions in pytest.raises diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 8c73ccc6adc..e621f3ee048 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -572,8 +572,9 @@ def match(self, regexp): raised. """ __tracebackhide__ = True - if not re.search(regexp, str(self.value)): - assert 0, "Pattern '{!s}' not found in '{!s}'".format(regexp, self.value) + value = safe_str(self.value) + if not re.search(regexp, value): + assert 0, "Pattern '{!s}' not found in '{!s}'".format(regexp, value) return True diff --git a/testing/python/raises.py b/testing/python/raises.py index cd463d74b07..db34c6624f5 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -278,3 +278,7 @@ def __class__(self): with pytest.raises(CrappyClass()): pass assert "via __class__" in excinfo.value.args[0] + + def test_u(self): + with pytest.raises(AssertionError, match=u"\u2603"): + assert False, u"\u2603" From 86a4eb6008599b042cb32eae24370068bb584edb Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Thu, 27 Jun 2019 07:35:02 +0100 Subject: [PATCH 2/5] Update changelog/5478.bugfix.rst Co-Authored-By: Bruno Oliveira --- changelog/5478.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/5478.bugfix.rst b/changelog/5478.bugfix.rst index b9e80a88ee6..74d58e73489 100644 --- a/changelog/5478.bugfix.rst +++ b/changelog/5478.bugfix.rst @@ -1 +1 @@ -Use safe_str to serialize Exceptions in pytest.raises +Fix encode error when using unicode strings in exceptions with ``pytest.raises``. From 09dee292ca87424a763bbf0ffeb4bd0e346b76cd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 30 Jun 2019 10:34:40 -0300 Subject: [PATCH 3/5] Use unicode message if regex is also unicode in ExceptionInfo.match --- src/_pytest/_code/code.py | 8 ++++++-- testing/python/raises.py | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index e621f3ee048..e99575e1176 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -572,9 +572,13 @@ def match(self, regexp): raised. """ __tracebackhide__ = True - value = safe_str(self.value) + value = ( + text_type(self.value) if isinstance(regexp, text_type) else str(self.value) + ) if not re.search(regexp, value): - assert 0, "Pattern '{!s}' not found in '{!s}'".format(regexp, value) + raise AssertionError( + "Pattern '{!s}' not found in '{!s}'".format(regexp, value) + ) return True diff --git a/testing/python/raises.py b/testing/python/raises.py index db34c6624f5..ba7a04b8cfb 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -279,6 +279,8 @@ def __class__(self): pass assert "via __class__" in excinfo.value.args[0] - def test_u(self): + def test_unicode_message(self): + """pytest.raises should be able to match unicode messages when using a unicode regex (#5478) + """ with pytest.raises(AssertionError, match=u"\u2603"): assert False, u"\u2603" From a886015bfdf686021eb475ebdf2afe86f594a342 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 30 Jun 2019 21:08:40 -0300 Subject: [PATCH 4/5] Test various bytes <=> unicode cases as requested in review --- src/_pytest/_code/code.py | 2 +- testing/python/raises.py | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index e99575e1176..4e0c45a3785 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -577,7 +577,7 @@ def match(self, regexp): ) if not re.search(regexp, value): raise AssertionError( - "Pattern '{!s}' not found in '{!s}'".format(regexp, value) + u"Pattern '{}' not found in '{}'".format(regexp, value) ) return True diff --git a/testing/python/raises.py b/testing/python/raises.py index ba7a04b8cfb..70f4af306c2 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -4,6 +4,7 @@ import six import pytest +from _pytest.compat import dummy_context_manager from _pytest.outcomes import Failed from _pytest.warning_types import PytestDeprecationWarning @@ -279,8 +280,34 @@ def __class__(self): pass assert "via __class__" in excinfo.value.args[0] - def test_unicode_message(self): - """pytest.raises should be able to match unicode messages when using a unicode regex (#5478) - """ - with pytest.raises(AssertionError, match=u"\u2603"): - assert False, u"\u2603" + +class TestUnicodeHandling: + """Test various combinations of bytes and unicode with pytest.raises (#5478) + + https://github.com/pytest-dev/pytest/pull/5479#discussion_r298852433 + """ + + success = dummy_context_manager + py2_only = pytest.mark.skipif( + six.PY3, reason="bytes in raises only supported in Python 2" + ) + + @pytest.mark.parametrize( + "message, match, expectation", + [ + (u"\u2603", u"\u2603", success()), + (u"\u2603", u"\u2603foo", pytest.raises(AssertionError)), + pytest.param(b"hello", b"hello", success(), marks=py2_only), + pytest.param( + b"hello", b"world", pytest.raises(AssertionError), marks=py2_only + ), + pytest.param(u"hello", b"hello", success(), marks=py2_only), + pytest.param( + u"hello", b"world", pytest.raises(AssertionError), marks=py2_only + ), + ], + ) + def test_handling(self, message, match, expectation): + with expectation: + with pytest.raises(RuntimeError, match=match): + raise RuntimeError(message) From 34b4e216066cef4f301f1069981fae1cd39ceeee Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 4 Jul 2019 09:34:55 -0300 Subject: [PATCH 5/5] Include two more cases for non-ascii encoded bytes --- src/_pytest/_code/code.py | 2 +- testing/python/raises.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 4e0c45a3785..175d6fda01d 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -577,7 +577,7 @@ def match(self, regexp): ) if not re.search(regexp, value): raise AssertionError( - u"Pattern '{}' not found in '{}'".format(regexp, value) + u"Pattern {!r} not found in {!r}".format(regexp, value) ) return True diff --git a/testing/python/raises.py b/testing/python/raises.py index 70f4af306c2..9cd3ec71734 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -221,7 +221,7 @@ def test_raises_match(self): int("asdf") msg = "with base 16" - expr = r"Pattern '{}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format( + expr = r"Pattern '{}' not found in \"invalid literal for int\(\) with base 10: 'asdf'\"".format( msg ) with pytest.raises(AssertionError, match=expr): @@ -305,6 +305,18 @@ class TestUnicodeHandling: pytest.param( u"hello", b"world", pytest.raises(AssertionError), marks=py2_only ), + pytest.param( + u"😊".encode("UTF-8"), + b"world", + pytest.raises(AssertionError), + marks=py2_only, + ), + pytest.param( + u"world", + u"😊".encode("UTF-8"), + pytest.raises(AssertionError), + marks=py2_only, + ), ], ) def test_handling(self, message, match, expectation):