saferepr: handle BaseExceptions (#6047)
This commit is contained in:
commit
ab101658f0
|
@ -0,0 +1 @@
|
|||
BaseExceptions are handled in ``saferepr``, which includes ``pytest.fail.Exception`` etc.
|
|
@ -3,14 +3,24 @@ import reprlib
|
|||
from typing import Any
|
||||
|
||||
|
||||
def _format_repr_exception(exc: Exception, obj: Any) -> str:
|
||||
exc_name = type(exc).__name__
|
||||
def _try_repr_or_str(obj):
|
||||
try:
|
||||
exc_info = str(exc)
|
||||
except Exception:
|
||||
exc_info = "unknown"
|
||||
return '<[{}("{}") raised in repr()] {} object at 0x{:x}>'.format(
|
||||
exc_name, exc_info, obj.__class__.__name__, id(obj)
|
||||
return repr(obj)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except BaseException:
|
||||
return '{}("{}")'.format(type(obj).__name__, obj)
|
||||
|
||||
|
||||
def _format_repr_exception(exc: BaseException, obj: Any) -> str:
|
||||
try:
|
||||
exc_info = _try_repr_or_str(exc)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
exc_info = "unpresentable exception ({})".format(_try_repr_or_str(exc))
|
||||
return "<[{} raised in repr()] {} object at 0x{:x}>".format(
|
||||
exc_info, obj.__class__.__name__, id(obj)
|
||||
)
|
||||
|
||||
|
||||
|
@ -35,14 +45,18 @@ class SafeRepr(reprlib.Repr):
|
|||
def repr(self, x: Any) -> str:
|
||||
try:
|
||||
s = super().repr(x)
|
||||
except Exception as exc:
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
s = _format_repr_exception(exc, x)
|
||||
return _ellipsize(s, self.maxsize)
|
||||
|
||||
def repr_instance(self, x: Any, level: int) -> str:
|
||||
try:
|
||||
s = repr(x)
|
||||
except Exception as exc:
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
s = _format_repr_exception(exc, x)
|
||||
return _ellipsize(s, self.maxsize)
|
||||
|
||||
|
|
|
@ -584,7 +584,7 @@ raise ValueError()
|
|||
reprlocals = p.repr_locals(loc)
|
||||
assert reprlocals.lines
|
||||
assert reprlocals.lines[0] == "__builtins__ = <builtins>"
|
||||
assert '[NotImplementedError("") raised in repr()]' in reprlocals.lines[1]
|
||||
assert "[NotImplementedError() raised in repr()]" in reprlocals.lines[1]
|
||||
|
||||
def test_repr_local_with_exception_in_class_property(self):
|
||||
class ExceptionWithBrokenClass(Exception):
|
||||
|
@ -602,7 +602,7 @@ raise ValueError()
|
|||
reprlocals = p.repr_locals(loc)
|
||||
assert reprlocals.lines
|
||||
assert reprlocals.lines[0] == "__builtins__ = <builtins>"
|
||||
assert '[ExceptionWithBrokenClass("") raised in repr()]' in reprlocals.lines[1]
|
||||
assert "[ExceptionWithBrokenClass() raised in repr()]" in reprlocals.lines[1]
|
||||
|
||||
def test_repr_local_truncated(self):
|
||||
loc = {"l": [i for i in range(10)]}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import pytest
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
|
||||
|
@ -40,9 +41,81 @@ def test_exceptions():
|
|||
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
|
||||
none = None
|
||||
try:
|
||||
none()
|
||||
except BaseException as exc:
|
||||
exp_exc = repr(exc)
|
||||
obj = BrokenRepr(BrokenReprException("omg even worse"))
|
||||
s2 = saferepr(obj)
|
||||
assert s2 == (
|
||||
"<[unpresentable exception ({!s}) raised in repr()] BrokenRepr object at 0x{:x}>".format(
|
||||
exp_exc, id(obj)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_baseexception():
|
||||
"""Test saferepr() with BaseExceptions, which includes pytest outcomes."""
|
||||
|
||||
class RaisingOnStrRepr(BaseException):
|
||||
def __init__(self, exc_types):
|
||||
self.exc_types = exc_types
|
||||
|
||||
def raise_exc(self, *args):
|
||||
try:
|
||||
self.exc_type = self.exc_types.pop(0)
|
||||
except IndexError:
|
||||
pass
|
||||
if hasattr(self.exc_type, "__call__"):
|
||||
raise self.exc_type(*args)
|
||||
raise self.exc_type
|
||||
|
||||
def __str__(self):
|
||||
self.raise_exc("__str__")
|
||||
|
||||
def __repr__(self):
|
||||
self.raise_exc("__repr__")
|
||||
|
||||
class BrokenObj:
|
||||
def __init__(self, exc):
|
||||
self.exc = exc
|
||||
|
||||
def __repr__(self):
|
||||
raise self.exc
|
||||
|
||||
__str__ = __repr__
|
||||
|
||||
baseexc_str = BaseException("__str__")
|
||||
obj = BrokenObj(RaisingOnStrRepr([BaseException]))
|
||||
assert saferepr(obj) == (
|
||||
"<[unpresentable exception ({!r}) "
|
||||
"raised in repr()] BrokenObj object at 0x{:x}>".format(baseexc_str, id(obj))
|
||||
)
|
||||
obj = BrokenObj(RaisingOnStrRepr([RaisingOnStrRepr([BaseException])]))
|
||||
assert saferepr(obj) == (
|
||||
"<[{!r} raised in repr()] BrokenObj object at 0x{:x}>".format(
|
||||
baseexc_str, id(obj)
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
saferepr(BrokenObj(KeyboardInterrupt()))
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
saferepr(BrokenObj(SystemExit()))
|
||||
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
saferepr(BrokenObj(RaisingOnStrRepr([KeyboardInterrupt])))
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
saferepr(BrokenObj(RaisingOnStrRepr([SystemExit])))
|
||||
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
print(saferepr(BrokenObj(RaisingOnStrRepr([BaseException, KeyboardInterrupt]))))
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
saferepr(BrokenObj(RaisingOnStrRepr([BaseException, SystemExit])))
|
||||
|
||||
|
||||
def test_buggy_builtin_repr():
|
||||
|
|
|
@ -102,15 +102,20 @@ class SessionTests:
|
|||
p = testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
class reprexc(BaseException):
|
||||
def __str__(self):
|
||||
return "Ha Ha fooled you, I'm a broken repr()."
|
||||
|
||||
class BrokenRepr1(object):
|
||||
foo=0
|
||||
def __repr__(self):
|
||||
raise Exception("Ha Ha fooled you, I'm a broken repr().")
|
||||
raise reprexc
|
||||
|
||||
class TestBrokenClass(object):
|
||||
def test_explicit_bad_repr(self):
|
||||
t = BrokenRepr1()
|
||||
with pytest.raises(Exception, match="I'm a broken repr"):
|
||||
with pytest.raises(BaseException, match="broken repr"):
|
||||
repr(t)
|
||||
|
||||
def test_implicit_bad_repr1(self):
|
||||
|
@ -123,12 +128,7 @@ class SessionTests:
|
|||
passed, skipped, failed = reprec.listoutcomes()
|
||||
assert (len(passed), len(skipped), len(failed)) == (1, 0, 1)
|
||||
out = failed[0].longrepr.reprcrash.message
|
||||
assert (
|
||||
out.find(
|
||||
"""[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]"""
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
assert out.find("<[reprexc() raised in repr()] BrokenRepr1") != -1
|
||||
|
||||
def test_broken_repr_with_showlocals_verbose(self, testdir):
|
||||
p = testdir.makepyfile(
|
||||
|
@ -151,7 +151,7 @@ class SessionTests:
|
|||
assert repr_locals.lines
|
||||
assert len(repr_locals.lines) == 1
|
||||
assert repr_locals.lines[0].startswith(
|
||||
'x = <[NotImplementedError("") raised in repr()] ObjWithErrorInRepr'
|
||||
"x = <[NotImplementedError() raised in repr()] ObjWithErrorInRepr"
|
||||
)
|
||||
|
||||
def test_skip_file_by_conftest(self, testdir):
|
||||
|
|
Loading…
Reference in New Issue