Skip to content

Commit 34e2829

Browse files
farbodahmwebknjazFarbod Ahmadian
authored
refactor: simplify bound method representation (#12492)
Co-authored-by: Sviatoslav Sydorenko <[email protected]> Co-authored-by: Farbod Ahmadian <[email protected]>
1 parent 9947ec3 commit 34e2829

File tree

5 files changed

+71
-2
lines changed

5 files changed

+71
-2
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Evgeny Seliverstov
149149
Fabian Sturm
150150
Fabien Zarifian
151151
Fabio Zadrozny
152+
Farbod Ahmadian
152153
faph
153154
Felix Hofstätter
154155
Felix Nieuwenhuizen

changelog/389.improvement.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
The readability of assertion introspection of bound methods has been enhanced
2+
-- by :user:`farbodahm`, :user:`webknjaz`, :user:`obestwalter`, :user:`flub`
3+
and :user:`glyphack`.
4+
5+
Earlier, it was like:
6+
7+
.. code-block:: console
8+
9+
=================================== FAILURES ===================================
10+
_____________________________________ test _____________________________________
11+
12+
def test():
13+
> assert Help().fun() == 2
14+
E assert 1 == 2
15+
E + where 1 = <bound method Help.fun of <example.Help instance at 0x256a830>>()
16+
E + where <bound method Help.fun of <example.Help instance at 0x256a830>> = <example.Help instance at 0x256a830>.fun
17+
E + where <example.Help instance at 0x256a830> = Help()
18+
19+
example.py:7: AssertionError
20+
=========================== 1 failed in 0.03 seconds ===========================
21+
22+
23+
And now it's like:
24+
25+
.. code-block:: console
26+
27+
=================================== FAILURES ===================================
28+
_____________________________________ test _____________________________________
29+
30+
def test():
31+
> assert Help().fun() == 2
32+
E assert 1 == 2
33+
E + where 1 = fun()
34+
E + where fun = <test_local.Help object at 0x1074be230>.fun
35+
E + where <test_local.Help object at 0x1074be230> = Help()
36+
37+
test_local.py:13: AssertionError
38+
=========================== 1 failed in 0.03 seconds ===========================

src/_pytest/_io/saferepr.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ def repr(self, x: object) -> str:
6060
s = ascii(x)
6161
else:
6262
s = super().repr(x)
63-
6463
except (KeyboardInterrupt, SystemExit):
6564
raise
6665
except BaseException as exc:

src/_pytest/assertion/rewrite.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ def _saferepr(obj: object) -> str:
417417
sequences, especially '\n{' and '\n}' are likely to be present in
418418
JSON reprs.
419419
"""
420+
if isinstance(obj, types.MethodType):
421+
# for bound methods, skip redundant <bound method ...> information
422+
return obj.__name__
423+
420424
maxsize = _get_maxsize_for_saferepr(util._config)
421425
return saferepr(obj, maxsize=maxsize).replace("\n", "\\n")
422426

testing/test_assertrewrite.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import os
1111
from pathlib import Path
1212
import py_compile
13+
import re
1314
import stat
1415
import sys
1516
import textwrap
@@ -24,6 +25,7 @@
2425
from _pytest.assertion import util
2526
from _pytest.assertion.rewrite import _get_assertion_exprs
2627
from _pytest.assertion.rewrite import _get_maxsize_for_saferepr
28+
from _pytest.assertion.rewrite import _saferepr
2729
from _pytest.assertion.rewrite import AssertionRewritingHook
2830
from _pytest.assertion.rewrite import get_cache_dir
2931
from _pytest.assertion.rewrite import PYC_TAIL
@@ -2036,7 +2038,9 @@ def test_foo():
20362038
assert test_foo_pyc.is_file()
20372039

20382040
# normal file: not touched by pytest, normal cache tag
2039-
bar_init_pyc = get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc"
2041+
bar_init_pyc = (
2042+
get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc"
2043+
)
20402044
assert bar_init_pyc.is_file()
20412045

20422046

@@ -2103,3 +2107,26 @@ def test_foo():
21032107
)
21042108
result = pytester.runpytest()
21052109
assert result.ret == 0
2110+
2111+
2112+
class TestSafereprUnbounded:
2113+
class Help:
2114+
def bound_method(self): # pragma: no cover
2115+
pass
2116+
2117+
def test_saferepr_bound_method(self):
2118+
"""saferepr() of a bound method should show only the method name"""
2119+
assert _saferepr(self.Help().bound_method) == "bound_method"
2120+
2121+
def test_saferepr_unbounded(self):
2122+
"""saferepr() of an unbound method should still show the full information"""
2123+
obj = self.Help()
2124+
# using id() to fetch memory address fails on different platforms
2125+
pattern = re.compile(
2126+
rf"<{Path(__file__).stem}.{self.__class__.__name__}.Help object at 0x[0-9a-fA-F]*>",
2127+
)
2128+
assert pattern.match(_saferepr(obj))
2129+
assert (
2130+
_saferepr(self.Help)
2131+
== f"<class '{Path(__file__).stem}.{self.__class__.__name__}.Help'>"
2132+
)

0 commit comments

Comments
 (0)