10000 bpo-35753: Fix crash in doctest with unwrap-able functions (#22981) · python/cpython@565a318 · GitHub
[go: up one dir, main page]

Skip to content

Commit 565a318

Browse files
authored
bpo-35753: Fix crash in doctest with unwrap-able functions (#22981)
Ignore objects that inspect.unwrap throws due to too many wrappers. This is a very rare case, however it can easily be surfaced when a module under doctest imports unitest.mock.call into its namespace. We simply skip any object that throws this exception. This should handle the majority of cases.
1 parent cf86996 commit 565a318

File tree

3 files changed

+29
-3
lines changed

3 files changed

+29
-3
lines changed

Lib/doctest.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,17 @@ def _from_module(self, module, object):
973973
else:
974974
raise ValueError("object must be a class or function")
975975

976+
def _is_routine(self, obj):
977+
"""
978+
Safely unwrap objects and determine if they are functions.
979+
"""
980+
maybe_routine = obj
981+
try:
982+
maybe_routine = inspect.unwrap(maybe_routine)
983+
except ValueError:
984+
pass
985+
return inspect.isroutine(maybe_routine)
986+
976987
def _find(self, tests, obj, name, module, source_lines, globs, seen):
977988
"""
978989
Find tests for the given object and any contained objects, and
@@ -995,9 +1006,9 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
9951006
if inspect.ismodule(obj) and self._recurse:
9961007
for valname, val in obj.__dict__.items():
9971008
valname = '%s.%s' % (name, valname)
1009+
9981010
# Recurse to functions & classes.
999-
if ((inspect.isroutine(inspect.unwrap(val))
1000-
or inspect.isclass(val)) and
1011+
if ((self._is_routine(val) or inspect.isclass(val)) and
10011012
self._from_module(module, val)):
10021013
self._find(tests, val, valname, module, source_lines,
10031014
globs, seen)

Lib/test/test_doctest.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import unittest
1616
import tempfile
1717
import shutil
18+
import types
1819
import contextlib
1920

2021
# NOTE: There are some additional tests relating to interaction with
@@ -443,7 +444,7 @@ def basics(): r"""
443444
>>> tests = finder.find(sample_func)
444445
445446
>>> print(tests) # doctest: +ELLIPSIS
446-
[<DocTest sample_func from ...:27 (1 example)>]
447+
[<DocTest sample_func from test_doctest.py:28 (1 example)>]
447448
448449
The exact name depends on how test_doctest was invoked, so allow for
449450
leading path components.
@@ -698,6 +699,18 @@ def non_Python_modules(): r"""
698699

699700
class TestDocTestFinder(unittest.TestCase):
700701

702+
def test_issue35753(self):
703+
# This import of `call` should trigger issue35753 when
704+
# `support.run_doctest` is called due to unwrap failing,
705+
# however with a patched doctest this should succeed.
706+
from unittest.mock import call
707+
dummy_module = types.ModuleType("dummy")
708+
dummy_module.__dict__['inject_call'] = call
709+
try:
710+
support.run_doctest(dummy_module, verbosity=True)
711+
except ValueError as e:
712+
raise support.TestFailed("Doctest unwrap failed") from e
713+
701714
def test_empty_namespace_package(self):
702715
pkg_name = 'doctest_empty_pkg'
703716
with tempfile.TemporaryDirectory() as parent_dir:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash in doctest when doctest parses modules that include unwrappable
2+
functions by skipping those functions.

0 commit comments

Comments
 (0)
0