Skip to content

Commit f6eb39d

Browse files
Merge pull request #4001 from asottile/fix_bytes_repr_text_mix_python_2
Fix UnicodeDecodeError in assertion with mixed non-ascii bytes repr + text
2 parents 7a5e11b + 7122fa5 commit f6eb39d

File tree

3 files changed

+27
-5
lines changed

3 files changed

+27
-5
lines changed

changelog/3999.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``UnicodeDecodeError`` in python2.x when a class returns a non-ascii binary ``__repr__`` in an assertion which also contains non-ascii text.

src/_pytest/assertion/rewrite.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import re
1010
import six
11+
import string
1112
import struct
1213
import sys
1314
import types
@@ -466,10 +467,14 @@ def _saferepr(obj):
466467
467468
"""
468469
r = py.io.saferepr(obj)
469-
if isinstance(r, six.text_type):
470-
return r.replace(u"\n", u"\\n")
471-
else:
472-
return r.replace(b"\n", b"\\n")
470+
# only occurs in python2.x, repr must return text in python3+
471+
if isinstance(r, bytes):
472+
# Represent unprintable bytes as `\x##`
473+
r = u"".join(
474+
u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
475+
for c in r
476+
)
477+
return r.replace(u"\n", u"\\n")
473478

474479

475480
from _pytest.assertion.util import format_explanation as _format_explanation # noqa

testing/test_assertrewrite.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: utf-8 -*-
12
from __future__ import absolute_import, division, print_function
23

34
import glob
@@ -57,7 +58,7 @@ def getmsg(f, extra_ns=None, must_pass=False):
5758
except AssertionError:
5859
if must_pass:
5960
pytest.fail("shouldn't have raised")
60-
s = str(sys.exc_info()[1])
61+
s = six.text_type(sys.exc_info()[1])
6162
if not s.startswith("assert"):
6263
return "AssertionError: " + s
6364
return s
@@ -608,6 +609,21 @@ def __repr__(self):
608609

609610
assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0]
610611

612+
def test_custom_repr_non_ascii(self):
613+
def f():
614+
class A(object):
615+
name = u"ä"
616+
617+
def __repr__(self):
618+
return self.name.encode("UTF-8") # only legal in python2
619+
620+
a = A()
621+
assert not a.name
622+
623+
msg = getmsg(f)
624+
assert "UnicodeDecodeError" not in msg
625+
assert "UnicodeEncodeError" not in msg
626+
611627

612628
class TestRewriteOnImport(object):
613629
def test_pycache_is_a_file(self, testdir):

0 commit comments

Comments
 (0)