diff --git a/changelog/15333.bugfix.rst b/changelog/15333.bugfix.rst new file mode 100644 index 00000000000..f916aa8520c --- /dev/null +++ b/changelog/15333.bugfix.rst @@ -0,0 +1 @@ +Fixed excessive memory usage during doctest collection on modules importing ``unittest.mock.call`` when the recursion limit is raised. diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 04d707113cc..d4504b2d51f 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -547,6 +547,22 @@ def _find_lineno(self, obj, source_lines): source_lines, ) + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: + """Override _find to work around issue in stdlib. + + https://github.com/pytest-dev/pytest/issues/3456 + https://github.com/python/cpython/issues/69718 + """ + if _is_mocked(obj): + return # pragma: no cover + with _patch_unwrap_mock_aware(): + # Type ignored because this is a private function. + super()._find( # type:ignore[misc] + tests, obj, name, module, source_lines, globs, seen + ) + if sys.version_info < (3, 13): def _from_module(self, module, object): diff --git a/testing/test_doctest.py b/testing/test_doctest.py index a322e9eb2a3..a172570aaff 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1812,6 +1812,26 @@ class Example(object): result.stdout.fnmatch_lines(["* 1 passed *"]) +def test_doctest_mock_objects_high_recursion_limit(pytester: Pytester) -> None: + pytester.makeconftest( + """ + import sys + sys.setrecursionlimit(5000) + """ + ) + pytester.makepyfile( + """ + ''' + >>> 1 + 1 + ''' + from unittest.mock import call + """ + ) + result = pytester.runpytest("--doctest-modules") + result.stdout.fnmatch_lines(["* 1 passed *"]) + + class Broken: def __getattr__(self, _): raise KeyError("This should be an AttributeError")