refactor: move bound_method repr from global version to rewrite module

This commit is contained in:
Farbod Ahmadian 2024-06-21 17:32:46 +02:00 committed by Farbod Ahmadian
parent b9f9d25507
commit a9eba91f7f
4 changed files with 51 additions and 36 deletions

View File

@ -60,12 +60,7 @@ class SafeRepr(reprlib.Repr):
if self.use_ascii: if self.use_ascii:
s = ascii(x) s = ascii(x)
else: else:
if isinstance(x, MethodType): s = super().repr(x)
# for bound methods, skip redundant <bound method ...> information
s = x.__name__
else:
s = super().repr(x)
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
raise raise
except BaseException as exc: except BaseException as exc:

View File

@ -417,6 +417,10 @@ def _saferepr(obj: object) -> str:
sequences, especially '\n{' and '\n}' are likely to be present in sequences, especially '\n{' and '\n}' are likely to be present in
JSON reprs. 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) maxsize = _get_maxsize_for_saferepr(util._config)
return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") return saferepr(obj, maxsize=maxsize).replace("\n", "\\n")
@ -1014,7 +1018,9 @@ class AssertionRewriter(ast.NodeVisitor):
] ]
): ):
pytest_temp = self.variable() pytest_temp = self.variable()
self.variables_overwrite[self.scope][v.left.target.id] = v.left # type:ignore[assignment] self.variables_overwrite[self.scope][
v.left.target.id
] = v.left # type:ignore[assignment]
v.left.target.id = pytest_temp v.left.target.id = pytest_temp
self.push_format_context() self.push_format_context()
res, expl = self.visit(v) res, expl = self.visit(v)
@ -1058,7 +1064,9 @@ class AssertionRewriter(ast.NodeVisitor):
if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get( if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get(
self.scope, {} self.scope, {}
): ):
arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment] arg = self.variables_overwrite[self.scope][
arg.id
] # type:ignore[assignment]
res, expl = self.visit(arg) res, expl = self.visit(arg)
arg_expls.append(expl) arg_expls.append(expl)
new_args.append(res) new_args.append(res)
@ -1066,7 +1074,9 @@ class AssertionRewriter(ast.NodeVisitor):
if isinstance( if isinstance(
keyword.value, ast.Name keyword.value, ast.Name
) and keyword.value.id in self.variables_overwrite.get(self.scope, {}): ) and keyword.value.id in self.variables_overwrite.get(self.scope, {}):
keyword.value = self.variables_overwrite[self.scope][keyword.value.id] # type:ignore[assignment] keyword.value = self.variables_overwrite[self.scope][
keyword.value.id
] # type:ignore[assignment]
res, expl = self.visit(keyword.value) res, expl = self.visit(keyword.value)
new_kwargs.append(ast.keyword(keyword.arg, res)) new_kwargs.append(ast.keyword(keyword.arg, res))
if keyword.arg: if keyword.arg:
@ -1103,9 +1113,13 @@ class AssertionRewriter(ast.NodeVisitor):
if isinstance( if isinstance(
comp.left, ast.Name comp.left, ast.Name
) and comp.left.id in self.variables_overwrite.get(self.scope, {}): ) and comp.left.id in self.variables_overwrite.get(self.scope, {}):
comp.left = self.variables_overwrite[self.scope][comp.left.id] # type:ignore[assignment] comp.left = self.variables_overwrite[self.scope][
comp.left.id
] # type:ignore[assignment]
if isinstance(comp.left, ast.NamedExpr): if isinstance(comp.left, ast.NamedExpr):
self.variables_overwrite[self.scope][comp.left.target.id] = comp.left # type:ignore[assignment] self.variables_overwrite[self.scope][
comp.left.target.id
] = comp.left # type:ignore[assignment]
left_res, left_expl = self.visit(comp.left) left_res, left_expl = self.visit(comp.left)
if isinstance(comp.left, (ast.Compare, ast.BoolOp)): if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
left_expl = f"({left_expl})" left_expl = f"({left_expl})"
@ -1123,7 +1137,9 @@ class AssertionRewriter(ast.NodeVisitor):
and next_operand.target.id == left_res.id and next_operand.target.id == left_res.id
): ):
next_operand.target.id = self.variable() next_operand.target.id = self.variable()
self.variables_overwrite[self.scope][left_res.id] = next_operand # type:ignore[assignment] self.variables_overwrite[self.scope][
left_res.id
] = next_operand # type:ignore[assignment]
next_res, next_expl = self.visit(next_operand) next_res, next_expl = self.visit(next_operand)
if isinstance(next_operand, (ast.Compare, ast.BoolOp)): if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
next_expl = f"({next_expl})" next_expl = f"({next_expl})"

View File

@ -194,26 +194,3 @@ def test_saferepr_unlimited_exc():
assert saferepr_unlimited(A()).startswith( assert saferepr_unlimited(A()).startswith(
"<[ValueError(42) raised in repr()] A object at 0x" "<[ValueError(42) raised in repr()] A object at 0x"
) )
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(
r"<test_saferepr.TestSafereprUnbounded.Help object at 0x[0-9a-fA-F]*>",
)
assert pattern.match(saferepr(obj))
assert (
saferepr(self.Help)
== f"<class 'test_saferepr.{self.__class__.__name__}.Help'>"
)

View File

@ -18,6 +18,7 @@ from typing import Generator
from typing import Mapping from typing import Mapping
from unittest import mock from unittest import mock
import zipfile import zipfile
import re
import _pytest._code import _pytest._code
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
@ -33,6 +34,7 @@ from _pytest.config import Config
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.pathlib import make_numbered_dir from _pytest.pathlib import make_numbered_dir
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
from _pytest.assertion.rewrite import _saferepr
import pytest import pytest
@ -2036,7 +2038,9 @@ class TestPyCacheDir:
assert test_foo_pyc.is_file() assert test_foo_pyc.is_file()
# normal file: not touched by pytest, normal cache tag # 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() assert bar_init_pyc.is_file()
@ -2103,3 +2107,26 @@ class TestIssue11140:
) )
result = pytester.runpytest() result = pytester.runpytest()
assert result.ret == 0 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'>"
)