From 129600d698622633a9e8036f89e88b50aa101abd Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 14 Jul 2019 22:24:12 +0300 Subject: [PATCH] saferepr: Avoid indirect function calls The DRY savings they provide are rather small, while they make it harder to type-check, and IMO harder to understand. --- changelog/5603.trivial.rst | 1 + src/_pytest/_io/saferepr.py | 53 +++++++++++++++++++++---------------- testing/io/test_saferepr.py | 11 ++++++++ 3 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 changelog/5603.trivial.rst diff --git a/changelog/5603.trivial.rst b/changelog/5603.trivial.rst new file mode 100644 index 000000000..310e88562 --- /dev/null +++ b/changelog/5603.trivial.rst @@ -0,0 +1 @@ +Simplified internal ``SafeRepr`` class and removed some dead code. diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index a97411876..7704421a2 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -2,19 +2,23 @@ import pprint import reprlib -def _call_and_format_exception(call, x, *args): +def _format_repr_exception(exc, obj): + exc_name = type(exc).__name__ try: - # Try the vanilla repr and make sure that the result is a string - return call(x, *args) - except Exception as exc: - exc_name = type(exc).__name__ - try: - exc_info = str(exc) - except Exception: - exc_info = "unknown" - return '<[{}("{}") raised in repr()] {} object at 0x{:x}>'.format( - exc_name, exc_info, x.__class__.__name__, id(x) - ) + 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) + ) + + +def _ellipsize(s, maxsize): + if len(s) > maxsize: + i = max(0, (maxsize - 3) // 2) + j = max(0, maxsize - 3 - i) + return s[:i] + "..." + s[len(s) - j :] + return s class SafeRepr(reprlib.Repr): @@ -28,18 +32,18 @@ class SafeRepr(reprlib.Repr): self.maxsize = maxsize def repr(self, x): - return self._callhelper(reprlib.Repr.repr, self, x) + try: + s = super().repr(x) + except Exception as exc: + s = _format_repr_exception(exc, x) + return _ellipsize(s, self.maxsize) def repr_instance(self, x, level): - return self._callhelper(repr, x) - - def _callhelper(self, call, x, *args): - s = _call_and_format_exception(call, x, *args) - 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 + try: + s = repr(x) + except Exception as exc: + s = _format_repr_exception(exc, x) + return _ellipsize(s, self.maxsize) def safeformat(obj): @@ -47,7 +51,10 @@ def safeformat(obj): Failing __repr__ functions of user instances will be represented with a short exception info. """ - return _call_and_format_exception(pprint.pformat, obj) + try: + return pprint.pformat(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) def saferepr(obj, maxsize=240): diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index f005f0cc4..86897b57c 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -45,6 +45,17 @@ def test_exceptions(): assert "unknown" in s2 +def test_buggy_builtin_repr(): + # Simulate a case where a repr for a builtin raises. + # reprlib dispatches by type name, so use "int". + + class int: + def __repr__(self): + raise ValueError("Buggy repr!") + + assert "Buggy" in saferepr(int()) + + def test_big_repr(): from _pytest._io.saferepr import SafeRepr