Skip to content

Commit a9eba91

Browse files
Farbod Ahmadianfarbodahm
authored andcommitted
refactor: move bound_method repr from global version to rewrite module
1 parent b9f9d25 commit a9eba91

File tree

4 files changed

+51
-36
lines changed

4 files changed

+51
-36
lines changed

src/_pytest/_io/saferepr.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,7 @@ def repr(self, x: object) -> str:
6060
if self.use_ascii:
6161
s = ascii(x)
6262
else:
63-
if isinstance(x, MethodType):
64-
# for bound methods, skip redundant <bound method ...> information
65-
s = x.__name__
66-
else:
67-
s = super().repr(x)
68-
63+
s = super().repr(x)
6964
except (KeyboardInterrupt, SystemExit):
7065
raise
7166
except BaseException as exc:

src/_pytest/assertion/rewrite.py

Lines changed: 22 additions & 6 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

@@ -1014,7 +1018,9 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]:
10141018
]
10151019
):
10161020
pytest_temp = self.variable()
1017-
self.variables_overwrite[self.scope][v.left.target.id] = v.left # type:ignore[assignment]
1021+
self.variables_overwrite[self.scope][
1022+
v.left.target.id
1023+
] = v.left # type:ignore[assignment]
10181024
v.left.target.id = pytest_temp
10191025
self.push_format_context()
10201026
res, expl = self.visit(v)
@@ -1058,15 +1064,19 @@ def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]:
10581064
if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get(
10591065
self.scope, {}
10601066
):
1061-
arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment]
1067+
arg = self.variables_overwrite[self.scope][
1068+
arg.id
1069+
] # type:ignore[assignment]
10621070
res, expl = self.visit(arg)
10631071
arg_expls.append(expl)
10641072
new_args.append(res)
10651073
for keyword in call.keywords:
10661074
if isinstance(
10671075
keyword.value, ast.Name
10681076
) and keyword.value.id in self.variables_overwrite.get(self.scope, {}):
1069-
keyword.value = self.variables_overwrite[self.scope][keyword.value.id] # type:ignore[assignment]
1077+
keyword.value = self.variables_overwrite[self.scope][
1078+
keyword.value.id
1079+
] # type:ignore[assignment]
10701080
res, expl = self.visit(keyword.value)
10711081
new_kwargs.append(ast.keyword(keyword.arg, res))
10721082
if keyword.arg:
@@ -1103,9 +1113,13 @@ def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
11031113
if isinstance(
11041114
comp.left, ast.Name
11051115
) and comp.left.id in self.variables_overwrite.get(self.scope, {}):
1106-
comp.left = self.variables_overwrite[self.scope][comp.left.id] # type:ignore[assignment]
1116+
comp.left = self.variables_overwrite[self.scope][
1117+
comp.left.id
1118+
] # type:ignore[assignment]
11071119
if isinstance(comp.left, ast.NamedExpr):
1108-
self.variables_overwrite[self.scope][comp.left.target.id] = comp.left # type:ignore[assignment]
1120+
self.variables_overwrite[self.scope][
1121+
comp.left.target.id
1122+
] = comp.left # type:ignore[assignment]
11091123
left_res, left_expl = self.visit(comp.left)
11101124
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
11111125
left_expl = f"({left_expl})"
@@ -1123,7 +1137,9 @@ def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
11231137
and next_operand.target.id == left_res.id
11241138
):
11251139
next_operand.target.id = self.variable()
1126-
self.variables_overwrite[self.scope][left_res.id] = next_operand # type:ignore[assignment]
1140+
self.variables_overwrite[self.scope][
1141+
left_res.id
1142+
] = next_operand # type:ignore[assignment]
11271143
next_res, next_expl = self.visit(next_operand)
11281144
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
11291145
next_expl = f"({next_expl})"

testing/io/test_saferepr.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -194,26 +194,3 @@ def __repr__(self):
194194
assert saferepr_unlimited(A()).startswith(
195195
"<[ValueError(42) raised in repr()] A object at 0x"
196196
)
197-
198-
199-
class TestSafereprUnbounded:
200-
class Help:
201-
def bound_method(self): # pragma: no cover
202-
pass
203-
204-
def test_saferepr_bound_method(self):
205-
"""saferepr() of a bound method should show only the method name"""
206-
assert saferepr(self.Help().bound_method) == "bound_method"
207-
208-
def test_saferepr_unbounded(self):
209-
"""saferepr() of an unbound method should still show the full information"""
210-
obj = self.Help()
211-
# using id() to fetch memory address fails on different platforms
212-
pattern = re.compile(
213-
r"<test_saferepr.TestSafereprUnbounded.Help object at 0x[0-9a-fA-F]*>",
214-
)
215-
assert pattern.match(saferepr(obj))
216-
assert (
217-
saferepr(self.Help)
218-
== f"<class 'test_saferepr.{self.__class__.__name__}.Help'>"
219-
)

testing/test_assertrewrite.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from typing import Mapping
1919
from unittest import mock
2020
import zipfile
21+
import re
2122

2223
import _pytest._code
2324
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
@@ -33,6 +34,7 @@
3334
from _pytest.config import ExitCode
3435
from _pytest.pathlib import make_numbered_dir
3536
from _pytest.pytester import Pytester
37+
from _pytest.assertion.rewrite import _saferepr
3638
import pytest
3739

3840

@@ -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)