Merge pull request #4657 from asottile/py_io_saferepr
copy saferepr from pylib
This commit is contained in:
commit
f01f434311
|
@ -54,5 +54,5 @@ repos:
|
|||
- id: py-deprecated
|
||||
name: py library is deprecated
|
||||
language: pygrep
|
||||
entry: '\bpy\.(builtin\.|code\.|std\.)'
|
||||
entry: '\bpy\.(builtin\.|code\.|std\.|io\.saferepr)'
|
||||
types: [python]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Copy saferepr from pylib
|
|
@ -36,10 +36,11 @@ platforms = unix, linux, osx, cygwin, win32
|
|||
zip_safe = no
|
||||
packages =
|
||||
_pytest
|
||||
_pytest.assertion
|
||||
_pytest._code
|
||||
_pytest.mark
|
||||
_pytest._io
|
||||
_pytest.assertion
|
||||
_pytest.config
|
||||
_pytest.mark
|
||||
|
||||
py_modules = pytest
|
||||
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
|
|
|
@ -18,6 +18,7 @@ import six
|
|||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import _PY2
|
||||
from _pytest.compat import _PY3
|
||||
from _pytest.compat import PY35
|
||||
|
@ -144,7 +145,7 @@ class Frame(object):
|
|||
def repr(self, object):
|
||||
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||
"""
|
||||
return py.io.saferepr(object)
|
||||
return saferepr(object)
|
||||
|
||||
def is_true(self, object):
|
||||
return object
|
||||
|
@ -423,7 +424,7 @@ class ExceptionInfo(object):
|
|||
if exprinfo is None and isinstance(tup[1], AssertionError):
|
||||
exprinfo = getattr(tup[1], "msg", None)
|
||||
if exprinfo is None:
|
||||
exprinfo = py.io.saferepr(tup[1])
|
||||
exprinfo = saferepr(tup[1])
|
||||
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
|
||||
_striptext = "AssertionError: "
|
||||
|
||||
|
@ -620,7 +621,7 @@ class FormattedExcinfo(object):
|
|||
return source
|
||||
|
||||
def _saferepr(self, obj):
|
||||
return py.io.saferepr(obj)
|
||||
return saferepr(obj)
|
||||
|
||||
def repr_args(self, entry):
|
||||
if self.funcargs:
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import sys
|
||||
|
||||
from six.moves import reprlib
|
||||
|
||||
|
||||
class SafeRepr(reprlib.Repr):
|
||||
"""subclass of repr.Repr that limits the resulting size of repr()
|
||||
and includes information on exceptions raised during the call.
|
||||
"""
|
||||
|
||||
def repr(self, x):
|
||||
return self._callhelper(reprlib.Repr.repr, self, x)
|
||||
|
||||
def repr_unicode(self, x, level):
|
||||
# Strictly speaking wrong on narrow builds
|
||||
def repr(u):
|
||||
if "'" not in u:
|
||||
return u"'%s'" % u
|
||||
elif '"' not in u:
|
||||
return u'"%s"' % u
|
||||
else:
|
||||
return u"'%s'" % u.replace("'", r"\'")
|
||||
|
||||
s = repr(x[: self.maxstring])
|
||||
if len(s) > self.maxstring:
|
||||
i = max(0, (self.maxstring - 3) // 2)
|
||||
j = max(0, self.maxstring - 3 - i)
|
||||
s = repr(x[:i] + x[len(x) - j :])
|
||||
s = s[:i] + "..." + s[len(s) - j :]
|
||||
return s
|
||||
|
||||
def repr_instance(self, x, level):
|
||||
return self._callhelper(repr, x)
|
||||
|
||||
def _callhelper(self, call, x, *args):
|
||||
try:
|
||||
# Try the vanilla repr and make sure that the result is a string
|
||||
s = call(x, *args)
|
||||
except Exception:
|
||||
cls, e, tb = sys.exc_info()
|
||||
exc_name = getattr(cls, "__name__", "unknown")
|
||||
try:
|
||||
exc_info = str(e)
|
||||
except Exception:
|
||||
exc_info = "unknown"
|
||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
||||
exc_name,
|
||||
exc_info,
|
||||
x.__class__.__name__,
|
||||
id(x),
|
||||
)
|
||||
else:
|
||||
if len(s) > self.maxsize:
|
||||
i = max(0, (self.maxsize - 3) // 2)
|
||||
j = max(0, self.maxsize - 3 - i)
|
||||
s = s[:i] + "..." + s[len(s) - j :]
|
||||
return s
|
||||
|
||||
|
||||
def saferepr(obj, maxsize=240):
|
||||
"""return a size-limited safe repr-string for the given object.
|
||||
Failing __repr__ functions of user instances will be represented
|
||||
with a short exception info and 'saferepr' generally takes
|
||||
care to never raise exceptions itself. This function is a wrapper
|
||||
around the Repr/reprlib functionality of the standard 2.6 lib.
|
||||
"""
|
||||
# review exception handling
|
||||
srepr = SafeRepr()
|
||||
srepr.maxstring = maxsize
|
||||
srepr.maxsize = maxsize
|
||||
srepr.maxother = 160
|
||||
return srepr.repr(obj)
|
|
@ -19,6 +19,7 @@ import atomicwrites
|
|||
import py
|
||||
import six
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.assertion import util
|
||||
from _pytest.compat import spec_from_file_location
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
|
@ -484,7 +485,7 @@ def _saferepr(obj):
|
|||
JSON reprs.
|
||||
|
||||
"""
|
||||
r = py.io.saferepr(obj)
|
||||
r = saferepr(obj)
|
||||
# only occurs in python2.x, repr must return text in python3+
|
||||
if isinstance(r, bytes):
|
||||
# Represent unprintable bytes as `\x##`
|
||||
|
@ -503,7 +504,7 @@ def _format_assertmsg(obj):
|
|||
|
||||
For strings this simply replaces newlines with '\n~' so that
|
||||
util.format_explanation() will preserve them instead of escaping
|
||||
newlines. For other objects py.io.saferepr() is used first.
|
||||
newlines. For other objects saferepr() is used first.
|
||||
|
||||
"""
|
||||
# reprlib appears to have a bug which means that if a string
|
||||
|
@ -512,7 +513,7 @@ def _format_assertmsg(obj):
|
|||
# However in either case we want to preserve the newline.
|
||||
replaces = [(u"\n", u"\n~"), (u"%", u"%%")]
|
||||
if not isinstance(obj, six.string_types):
|
||||
obj = py.io.saferepr(obj)
|
||||
obj = saferepr(obj)
|
||||
replaces.append((u"\\n", u"\n~"))
|
||||
|
||||
if isinstance(obj, bytes):
|
||||
|
@ -753,7 +754,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
return ast.Name(name, ast.Load())
|
||||
|
||||
def display(self, expr):
|
||||
"""Call py.io.saferepr on the expression."""
|
||||
"""Call saferepr on the expression."""
|
||||
return self.helper("saferepr", expr)
|
||||
|
||||
def helper(self, name, *args):
|
||||
|
|
|
@ -5,11 +5,11 @@ from __future__ import print_function
|
|||
|
||||
import pprint
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from ..compat import Sequence
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
# The _reprcompare attribute on the util module is used by the new assertion
|
||||
# interpretation code and assertion rewriter to detect this plugin was
|
||||
|
@ -105,8 +105,8 @@ except NameError:
|
|||
def assertrepr_compare(config, op, left, right):
|
||||
"""Return specialised explanations for some operators/operands"""
|
||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||
left_repr = py.io.saferepr(left, maxsize=int(width // 2))
|
||||
right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
|
||||
left_repr = saferepr(left, maxsize=int(width // 2))
|
||||
right_repr = saferepr(right, maxsize=width - len(left_repr))
|
||||
|
||||
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
|
||||
|
||||
|
@ -282,12 +282,12 @@ def _compare_eq_sequence(left, right, verbose=False):
|
|||
if len(left) > len(right):
|
||||
explanation += [
|
||||
u"Left contains more items, first extra item: %s"
|
||||
% py.io.saferepr(left[len(right)])
|
||||
% saferepr(left[len(right)])
|
||||
]
|
||||
elif len(left) < len(right):
|
||||
explanation += [
|
||||
u"Right contains more items, first extra item: %s"
|
||||
% py.io.saferepr(right[len(left)])
|
||||
% saferepr(right[len(left)])
|
||||
]
|
||||
return explanation
|
||||
|
||||
|
@ -299,11 +299,11 @@ def _compare_eq_set(left, right, verbose=False):
|
|||
if diff_left:
|
||||
explanation.append(u"Extra items in the left set:")
|
||||
for item in diff_left:
|
||||
explanation.append(py.io.saferepr(item))
|
||||
explanation.append(saferepr(item))
|
||||
if diff_right:
|
||||
explanation.append(u"Extra items in the right set:")
|
||||
for item in diff_right:
|
||||
explanation.append(py.io.saferepr(item))
|
||||
explanation.append(saferepr(item))
|
||||
return explanation
|
||||
|
||||
|
||||
|
@ -320,9 +320,7 @@ def _compare_eq_dict(left, right, verbose=False):
|
|||
if diff:
|
||||
explanation += [u"Differing items:"]
|
||||
for k in diff:
|
||||
explanation += [
|
||||
py.io.saferepr({k: left[k]}) + " != " + py.io.saferepr({k: right[k]})
|
||||
]
|
||||
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
|
||||
extra_left = set(left) - set(right)
|
||||
if extra_left:
|
||||
explanation.append(u"Left contains more items:")
|
||||
|
@ -376,7 +374,7 @@ def _notin_text(term, text, verbose=False):
|
|||
tail = text[index + len(term) :]
|
||||
correct_text = head + tail
|
||||
diff = _diff_text(correct_text, text, verbose)
|
||||
newdiff = [u"%s is contained here:" % py.io.saferepr(term, maxsize=42)]
|
||||
newdiff = [u"%s is contained here:" % saferepr(term, maxsize=42)]
|
||||
for line in diff:
|
||||
if line.startswith(u"Skipping"):
|
||||
continue
|
||||
|
|
|
@ -17,6 +17,7 @@ import six
|
|||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
|
@ -294,7 +295,7 @@ def get_real_func(obj):
|
|||
else:
|
||||
raise ValueError(
|
||||
("could not find real function of {start}\nstopped at {current}").format(
|
||||
start=py.io.saferepr(start_obj), current=py.io.saferepr(obj)
|
||||
start=saferepr(start_obj), current=saferepr(obj)
|
||||
)
|
||||
)
|
||||
if isinstance(obj, functools.partial):
|
||||
|
|
|
@ -20,6 +20,7 @@ import six
|
|||
|
||||
import pytest
|
||||
from _pytest._code import Source
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.assertion.rewrite import AssertionRewritingHook
|
||||
from _pytest.capture import MultiCapture
|
||||
from _pytest.capture import SysCapture
|
||||
|
@ -1225,9 +1226,7 @@ def getdecoded(out):
|
|||
try:
|
||||
return out.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
|
||||
py.io.saferepr(out),
|
||||
)
|
||||
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (saferepr(out),)
|
||||
|
||||
|
||||
class LineComp(object):
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
|
||||
def test_simple_repr():
|
||||
assert saferepr(1) == "1"
|
||||
assert saferepr(None) == "None"
|
||||
|
||||
|
||||
def test_maxsize():
|
||||
s = saferepr("x" * 50, maxsize=25)
|
||||
assert len(s) == 25
|
||||
expected = repr("x" * 10 + "..." + "x" * 10)
|
||||
assert s == expected
|
||||
|
||||
|
||||
def test_maxsize_error_on_instance():
|
||||
class A:
|
||||
def __repr__():
|
||||
raise ValueError("...")
|
||||
|
||||
s = saferepr(("*" * 50, A()), maxsize=25)
|
||||
assert len(s) == 25
|
||||
assert s[0] == "(" and s[-1] == ")"
|
||||
|
||||
|
||||
def test_exceptions():
|
||||
class BrokenRepr:
|
||||
def __init__(self, ex):
|
||||
self.ex = ex
|
||||
|
||||
def __repr__(self):
|
||||
raise self.ex
|
||||
|
||||
class BrokenReprException(Exception):
|
||||
__str__ = None
|
||||
__repr__ = None
|
||||
|
||||
assert "Exception" in saferepr(BrokenRepr(Exception("broken")))
|
||||
s = saferepr(BrokenReprException("really broken"))
|
||||
assert "TypeError" in s
|
||||
assert "TypeError" in saferepr(BrokenRepr("string"))
|
||||
|
||||
s2 = saferepr(BrokenRepr(BrokenReprException("omg even worse")))
|
||||
assert "NameError" not in s2
|
||||
assert "unknown" in s2
|
||||
|
||||
|
||||
def test_big_repr():
|
||||
from _pytest._io.saferepr import SafeRepr
|
||||
|
||||
assert len(saferepr(range(1000))) <= len("[" + SafeRepr().maxlist * "1000" + "]")
|
||||
|
||||
|
||||
def test_repr_on_newstyle():
|
||||
class Function(object):
|
||||
def __repr__(self):
|
||||
return "<%s>" % (self.name)
|
||||
|
||||
assert saferepr(Function())
|
||||
|
||||
|
||||
def test_unicode():
|
||||
val = u"£€"
|
||||
reprval = u"'£€'"
|
||||
assert saferepr(val) == reprval
|
Loading…
Reference in New Issue