From 3b6a384569fe6d9dd3b9847543018d3124a697c9 Mon Sep 17 00:00:00 2001 From: Alfred Perlstein Date: Tue, 20 Oct 2020 10:15:40 -0700 Subject: [PATCH] [3.8] bpo-35753: fix doctest on unwrappables funcs Summary: 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. --- Lib/doctest.py | 10 ++++-- Lib/test/test_doctest5.py | 33 +++++++++++++++++++ .../2020-10-25-19-20-26.bpo-35753.2LT-hO.rst | 2 ++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 Lib/test/test_doctest5.py create mode 100644 Misc/NEWS.d/next/Tests/2020-10-25-19-20-26.bpo-35753.2LT-hO.rst diff --git a/Lib/doctest.py b/Lib/doctest.py index ee71984a7d97cf..3f6a65c037370a 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -994,9 +994,15 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): if inspect.ismodule(obj) and self._recurse: for valname, val in obj.__dict__.items(): valname = '%s.%s' % (name, valname) + + # Protect against objects that cause unwrap to throw + isroutine = False + try: + isroutine = inspect.isroutine(inspect.unwrap(val)) + except ValueError: + pass # Recurse to functions & classes. - if ((inspect.isroutine(inspect.unwrap(val)) - or inspect.isclass(val)) and + if ((isroutine 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_doctest5.py b/Lib/test/test_doctest5.py new file mode 100644 index 00000000000000..74a4ac0b6dcd86 --- /dev/null +++ b/Lib/test/test_doctest5.py @@ -0,0 +1,33 @@ +"""A module to test whether doctest works properly with +un-unwrappable functions. + +See: https://bugs.python.org/issue35753 +""" + +# This should trigger issue35753 when doctest called +from unittest.mock import call + +import sys +import unittest +from test import support +if sys.flags.optimize >= 2: + raise unittest.SkipTest("Cannot test docstrings with -O2") + + +def test_main(): + from test import test_doctest5 + EXPECTED = 0 + try: + f, t = support.run_doctest(test_doctest5) + if t != EXPECTED: + raise support.TestFailed("expected %d tests to run, not %d" % + (EXPECTED, t)) + except ValueError as e: + raise support.TestFailed("Doctest unwrap failed") from e + +# Pollute the namespace with a bunch of imported functions and classes, +# to make sure they don't get tested. +# from doctest import * + +if __name__ == '__main__': + test_main() 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 00000000000000..eddfc25906da9e --- /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.