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
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def _format_repr_exception(exc: Exception, obj: Any) -> str:
|
def _try_repr_or_str(obj):
|
||||||
exc_name = type(exc).__name__
|
|
||||||
try:
|
try:
|
||||||
exc_info = str(exc)
|
return repr(obj)
|
||||||
except Exception:
|
except (KeyboardInterrupt, SystemExit):
|
||||||
exc_info = "unknown"
|
raise
|
||||||
return '<[{}("{}") raised in repr()] {} object at 0x{:x}>'.format(
|
except BaseException:
|
||||||
exc_name, exc_info, obj.__class__.__name__, id(obj)
|
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:
|
def repr(self, x: Any) -> str:
|
||||||
try:
|
try:
|
||||||
s = super().repr(x)
|
s = super().repr(x)
|
||||||
except Exception as exc:
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
s = _format_repr_exception(exc, x)
|
s = _format_repr_exception(exc, x)
|
||||||
return _ellipsize(s, self.maxsize)
|
return _ellipsize(s, self.maxsize)
|
||||||
|
|
||||||
def repr_instance(self, x: Any, level: int) -> str:
|
def repr_instance(self, x: Any, level: int) -> str:
|
||||||
try:
|
try:
|
||||||
s = repr(x)
|
s = repr(x)
|
||||||
except Exception as exc:
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
s = _format_repr_exception(exc, x)
|
s = _format_repr_exception(exc, x)
|
||||||
return _ellipsize(s, self.maxsize)
|
return _ellipsize(s, self.maxsize)
|
||||||
|
|
||||||
|
|
|
@ -584,7 +584,7 @@ raise ValueError()
|
||||||
reprlocals = p.repr_locals(loc)
|
reprlocals = p.repr_locals(loc)
|
||||||
assert reprlocals.lines
|
assert reprlocals.lines
|
||||||
assert reprlocals.lines[0] == "__builtins__ = <builtins>"
|
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):
|
def test_repr_local_with_exception_in_class_property(self):
|
||||||
class ExceptionWithBrokenClass(Exception):
|
class ExceptionWithBrokenClass(Exception):
|
||||||
|
@ -602,7 +602,7 @@ raise ValueError()
|
||||||
reprlocals = p.repr_locals(loc)
|
reprlocals = p.repr_locals(loc)
|
||||||
assert reprlocals.lines
|
assert reprlocals.lines
|
||||||
assert reprlocals.lines[0] == "__builtins__ = <builtins>"
|
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):
|
def test_repr_local_truncated(self):
|
||||||
loc = {"l": [i for i in range(10)]}
|
loc = {"l": [i for i in range(10)]}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import pytest
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,9 +41,81 @@ def test_exceptions():
|
||||||
assert "TypeError" in s
|
assert "TypeError" in s
|
||||||
assert "TypeError" in saferepr(BrokenRepr("string"))
|
assert "TypeError" in saferepr(BrokenRepr("string"))
|
||||||
|
|
||||||
s2 = saferepr(BrokenRepr(BrokenReprException("omg even worse")))
|
none = None
|
||||||
assert "NameError" not in s2
|
try:
|
||||||
assert "unknown" in s2
|
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():
|
def test_buggy_builtin_repr():
|
||||||
|
|
|
@ -102,15 +102,20 @@ class SessionTests:
|
||||||
p = testdir.makepyfile(
|
p = testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
class reprexc(BaseException):
|
||||||
|
def __str__(self):
|
||||||
|
return "Ha Ha fooled you, I'm a broken repr()."
|
||||||
|
|
||||||
class BrokenRepr1(object):
|
class BrokenRepr1(object):
|
||||||
foo=0
|
foo=0
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
raise Exception("Ha Ha fooled you, I'm a broken repr().")
|
raise reprexc
|
||||||
|
|
||||||
class TestBrokenClass(object):
|
class TestBrokenClass(object):
|
||||||
def test_explicit_bad_repr(self):
|
def test_explicit_bad_repr(self):
|
||||||
t = BrokenRepr1()
|
t = BrokenRepr1()
|
||||||
with pytest.raises(Exception, match="I'm a broken repr"):
|
with pytest.raises(BaseException, match="broken repr"):
|
||||||
repr(t)
|
repr(t)
|
||||||
|
|
||||||
def test_implicit_bad_repr1(self):
|
def test_implicit_bad_repr1(self):
|
||||||
|
@ -123,12 +128,7 @@ class SessionTests:
|
||||||
passed, skipped, failed = reprec.listoutcomes()
|
passed, skipped, failed = reprec.listoutcomes()
|
||||||
assert (len(passed), len(skipped), len(failed)) == (1, 0, 1)
|
assert (len(passed), len(skipped), len(failed)) == (1, 0, 1)
|
||||||
out = failed[0].longrepr.reprcrash.message
|
out = failed[0].longrepr.reprcrash.message
|
||||||
assert (
|
assert out.find("<[reprexc() raised in repr()] BrokenRepr1") != -1
|
||||||
out.find(
|
|
||||||
"""[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]"""
|
|
||||||
)
|
|
||||||
!= -1
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_broken_repr_with_showlocals_verbose(self, testdir):
|
def test_broken_repr_with_showlocals_verbose(self, testdir):
|
||||||
p = testdir.makepyfile(
|
p = testdir.makepyfile(
|
||||||
|
@ -151,7 +151,7 @@ class SessionTests:
|
||||||
assert repr_locals.lines
|
assert repr_locals.lines
|
||||||
assert len(repr_locals.lines) == 1
|
assert len(repr_locals.lines) == 1
|
||||||
assert repr_locals.lines[0].startswith(
|
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):
|
def test_skip_file_by_conftest(self, testdir):
|
||||||
|
|
Loading…
Reference in New Issue