summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/doctest.py15
-rw-r--r--Lib/test/test_doctest.py15
-rw-r--r--Misc/NEWS.d/next/Tests/2020-10-25-19-20-26.bpo-35753.2LT-hO.rst2
3 files changed, 29 insertions, 3 deletions
diff --git a/Lib/doctest.py b/Lib/doctest.py
index e95c333..ba898f6 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -973,6 +973,17 @@ class DocTestFinder:
else:
raise ValueError("object must be a class or function")
+ def _is_routine(self, obj):
+ """
+ Safely unwrap objects and determine if they are functions.
+ """
+ maybe_routine = obj
+ try:
+ maybe_routine = inspect.unwrap(maybe_routine)
+ except ValueError:
+ pass
+ return inspect.isroutine(maybe_routine)
+
def _find(self, tests, obj, name, module, source_lines, globs, seen):
"""
Find tests for the given object and any contained objects, and
@@ -995,9 +1006,9 @@ class DocTestFinder:
if inspect.ismodule(obj) and self._recurse:
for valname, val in obj.__dict__.items():
valname = '%s.%s' % (name, valname)
+
# Recurse to functions & classes.
- if ((inspect.isroutine(inspect.unwrap(val))
- or inspect.isclass(val)) and
+ if ((self._is_routine(val) or inspect.isclass(val)) and
self._from_module(module, val)):
self._find(tests, val, valname, module, source_lines,
globs, seen)
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index 6f51b1b..828a0ff 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -15,6 +15,7 @@ import importlib.util
import unittest
import tempfile
import shutil
+import types
import contextlib
# NOTE: There are some additional tests relating to interaction with
@@ -443,7 +444,7 @@ We'll simulate a __file__ attr that ends in pyc:
>>> tests = finder.find(sample_func)
>>> print(tests) # doctest: +ELLIPSIS
- [<DocTest sample_func from ...:27 (1 example)>]
+ [<DocTest sample_func from test_doctest.py:28 (1 example)>]
The exact name depends on how test_doctest was invoked, so allow for
leading path components.
@@ -698,6 +699,18 @@ and 'int' is a type.
class TestDocTestFinder(unittest.TestCase):
+ def test_issue35753(self):
+ # This import of `call` should trigger issue35753 when
+ # `support.run_doctest` is called due to unwrap failing,
+ # however with a patched doctest this should succeed.
+ from unittest.mock import call
+ dummy_module = types.ModuleType("dummy")
+ dummy_module.__dict__['inject_call'] = call
+ try:
+ support.run_doctest(dummy_module, verbosity=True)
+ except ValueError as e:
+ raise support.TestFailed("Doctest unwrap failed") from e
+
def test_empty_namespace_package(self):
pkg_name = 'doctest_empty_pkg'
with tempfile.TemporaryDirectory() as parent_dir:
diff --git a/Misc/NEWS.d/next/Tests/2020-10-25-19-20-26.bpo-35753.2LT-hO.rst b/Misc/NEWS.d/next/Tests/2020-10-25-19-20-26.bpo-35753.2LT-hO.rst
new file mode 100644
index 0000000..eddfc25
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2020-10-25-19-20-26.bpo-35753.2LT-hO.rst
@@ -0,0 +1,2 @@
+Fix crash in doctest when doctest parses modules that include unwrappable
+functions by skipping those functions.