Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: simplify bound method representation #12492

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'>"
)
Loading