Skip to content

Commit

Permalink
refactor: simplify bound method representation (#12492)
Browse files Browse the repository at this point in the history
Co-authored-by: Sviatoslav Sydorenko <webknjaz@redhat.com>
Co-authored-by: Farbod Ahmadian <farbod@datachef.com>
  • Loading branch information
3 people committed Jun 21, 2024
1 parent 9947ec3 commit 34e2829
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 2 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ Evgeny Seliverstov
Fabian Sturm
Fabien Zarifian
Fabio Zadrozny
Farbod Ahmadian
faph
Felix Hofstätter
Felix Nieuwenhuizen
Expand Down
38 changes: 38 additions & 0 deletions changelog/389.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
The readability of assertion introspection of bound methods has been enhanced
-- by :user:`farbodahm`, :user:`webknjaz`, :user:`obestwalter`, :user:`flub`
and :user:`glyphack`.

Earlier, it was like:

.. code-block:: console
=================================== FAILURES ===================================
_____________________________________ test _____________________________________
def test():
> assert Help().fun() == 2
E assert 1 == 2
E + where 1 = <bound method Help.fun of <example.Help instance at 0x256a830>>()
E + where <bound method Help.fun of <example.Help instance at 0x256a830>> = <example.Help instance at 0x256a830>.fun
E + where <example.Help instance at 0x256a830> = Help()
example.py:7: AssertionError
=========================== 1 failed in 0.03 seconds ===========================
And now it's like:

.. code-block:: console
=================================== FAILURES ===================================
_____________________________________ test _____________________________________
def test():
> assert Help().fun() == 2
E assert 1 == 2
E + where 1 = fun()
E + where fun = <test_local.Help object at 0x1074be230>.fun
E + where <test_local.Help object at 0x1074be230> = Help()
test_local.py:13: AssertionError
=========================== 1 failed in 0.03 seconds ===========================
1 change: 0 additions & 1 deletion src/_pytest/_io/saferepr.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def repr(self, x: object) -> str:
s = ascii(x)
else:
s = super().repr(x)

except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
Expand Down
4 changes: 4 additions & 0 deletions src/_pytest/assertion/rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ def _saferepr(obj: object) -> str:
sequences, especially '\n{' and '\n}' are likely to be present in
JSON reprs.
"""
if isinstance(obj, types.MethodType):
# for bound methods, skip redundant <bound method ...> information
return obj.__name__

maxsize = _get_maxsize_for_saferepr(util._config)
return saferepr(obj, maxsize=maxsize).replace("\n", "\\n")

Expand Down
29 changes: 28 additions & 1 deletion testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
from pathlib import Path
import py_compile
import re
import stat
import sys
import textwrap
Expand All @@ -24,6 +25,7 @@
from _pytest.assertion import util
from _pytest.assertion.rewrite import _get_assertion_exprs
from _pytest.assertion.rewrite import _get_maxsize_for_saferepr
from _pytest.assertion.rewrite import _saferepr
from _pytest.assertion.rewrite import AssertionRewritingHook
from _pytest.assertion.rewrite import get_cache_dir
from _pytest.assertion.rewrite import PYC_TAIL
Expand Down Expand Up @@ -2036,7 +2038,9 @@ def test_foo():
assert test_foo_pyc.is_file()

# normal file: not touched by pytest, normal cache tag
bar_init_pyc = get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc"
bar_init_pyc = (
get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc"
)
assert bar_init_pyc.is_file()


Expand Down Expand Up @@ -2103,3 +2107,26 @@ def test_foo():
)
result = pytester.runpytest()
assert result.ret == 0


class TestSafereprUnbounded:
class Help:
def bound_method(self): # pragma: no cover
pass

def test_saferepr_bound_method(self):
"""saferepr() of a bound method should show only the method name"""
assert _saferepr(self.Help().bound_method) == "bound_method"

def test_saferepr_unbounded(self):
"""saferepr() of an unbound method should still show the full information"""
obj = self.Help()
# using id() to fetch memory address fails on different platforms
pattern = re.compile(
rf"<{Path(__file__).stem}.{self.__class__.__name__}.Help object at 0x[0-9a-fA-F]*>",
)
assert pattern.match(_saferepr(obj))
assert (
_saferepr(self.Help)
== f"<class '{Path(__file__).stem}.{self.__class__.__name__}.Help'>"
)

0 comments on commit 34e2829

Please sign in to comment.