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.
This commit is contained in:
Alfred Perlstein 2021-05-05 10:33:17 -07:00 committed by GitHub
parent cf86996a8e
commit 565a31804c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 29 additions and 3 deletions

View File

@ -973,6 +973,17 @@ class DocTestFinder:
else: else:
raise ValueError("object must be a class or function") 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): def _find(self, tests, obj, name, module, source_lines, globs, seen):
""" """
Find tests for the given object and any contained objects, and Find tests for the given object and any contained objects, and
@ -995,9 +1006,9 @@ class DocTestFinder:
if inspect.ismodule(obj) and self._recurse: if inspect.ismodule(obj) and self._recurse:
for valname, val in obj.__dict__.items(): for valname, val in obj.__dict__.items():
valname = '%s.%s' % (name, valname) valname = '%s.%s' % (name, valname)
# Recurse to functions & classes. # Recurse to functions & classes.
if ((inspect.isroutine(inspect.unwrap(val)) if ((self._is_routine(val) or inspect.isclass(val)) and
or inspect.isclass(val)) and
self._from_module(module, val)): self._from_module(module, val)):
self._find(tests, val, valname, module, source_lines, self._find(tests, val, valname, module, source_lines,
globs, seen) globs, seen)

View File

@ -15,6 +15,7 @@ import importlib.util
import unittest import unittest
import tempfile import tempfile
import shutil import shutil
import types
import contextlib import contextlib
# NOTE: There are some additional tests relating to interaction with # 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) >>> tests = finder.find(sample_func)
>>> print(tests) # doctest: +ELLIPSIS >>> 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 The exact name depends on how test_doctest was invoked, so allow for
leading path components. leading path components.
@ -698,6 +699,18 @@ and 'int' is a type.
class TestDocTestFinder(unittest.TestCase): 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): def test_empty_namespace_package(self):
pkg_name = 'doctest_empty_pkg' pkg_name = 'doctest_empty_pkg'
with tempfile.TemporaryDirectory() as parent_dir: with tempfile.TemporaryDirectory() as parent_dir:

View File

@ -0,0 +1,2 @@
Fix crash in doctest when doctest parses modules that include unwrappable
functions by skipping those functions.