]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-35753: Fix crash in doctest with unwrap-able functions (#22981)
authorAlfred Perlstein <alfred@fb.com>
Wed, 5 May 2021 17:33:17 +0000 (10:33 -0700)
committerGitHub <noreply@github.com>
Wed, 5 May 2021 17:33:17 +0000 (19:33 +0200)
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.

Lib/doctest.py
Lib/test/test_doctest.py
Misc/NEWS.d/next/Tests/2020-10-25-19-20-26.bpo-35753.2LT-hO.rst [new file with mode: 0644]

index e95c333f48aad59cbb3f2b73c88936d9fee1eaef..ba898f65403df16340c3d4e3725d6eff8b30c49a 100644 (file)
@@ -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)
index 6f51b1bc4f0d1ff8c07479dbaa54efa224f096cb..828a0ff56763a40fef53d7e8c1b17a47e4f568bd 100644 (file)
@@ -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 (file)
index 0000000..eddfc25
--- /dev/null
@@ -0,0 +1,2 @@
+Fix crash in doctest when doctest parses modules that include unwrappable
+functions by skipping those functions.