Fix doctest collection of `functools.cached_property` objects.

This commit is contained in:
Tyler Smart 2023-08-16 00:54:38 -06:00
parent 15fadd8c5c
commit d4fb6ac9f7
4 changed files with 37 additions and 1 deletions

View File

@ -379,6 +379,7 @@ Tor Colvin
Trevor Bekolay Trevor Bekolay
Tushar Sadhwani Tushar Sadhwani
Tyler Goodlet Tyler Goodlet
Tyler Smart
Tzu-ping Chung Tzu-ping Chung
Vasily Kuznetsov Vasily Kuznetsov
Victor Maryama Victor Maryama

View File

@ -0,0 +1 @@
Fix doctest collection of `functools.cached_property` objects.

View File

@ -1,5 +1,6 @@
"""Discover and run doctests in modules and test files.""" """Discover and run doctests in modules and test files."""
import bdb import bdb
import functools
import inspect import inspect
import os import os
import platform import platform
@ -536,6 +537,21 @@ class DoctestModule(Module):
tests, obj, name, module, source_lines, globs, seen tests, obj, name, module, source_lines, globs, seen
) )
class CachedPropertyAwareDocTestFinder(MockAwareDocTestFinder):
def _from_module(self, module, object):
"""Doctest code does not take into account `@cached_property`,
this is a hackish way to fix it. https://github.com/python/cpython/issues/107995
Wrap Doctest finder so that when it calls `_from_module` for
a cached_property it uses the underlying function instead of the
wrapped cached_property object.
"""
if isinstance(object, functools.cached_property):
object = object.func
# Type ignored because this is a private function.
return super()._from_module(module, object) # type: ignore[misc]
if self.path.name == "conftest.py": if self.path.name == "conftest.py":
module = self.config.pluginmanager._importconftest( module = self.config.pluginmanager._importconftest(
self.path, self.path,
@ -555,7 +571,7 @@ class DoctestModule(Module):
else: else:
raise raise
# Uses internal doctest module parsing mechanism. # Uses internal doctest module parsing mechanism.
finder = MockAwareDocTestFinder() finder = CachedPropertyAwareDocTestFinder()
optionflags = get_optionflags(self) optionflags = get_optionflags(self)
runner = _get_runner( runner = _get_runner(
verbose=False, verbose=False,

View File

@ -482,6 +482,24 @@ class TestDoctests:
reprec = pytester.inline_run(p, "--doctest-modules") reprec = pytester.inline_run(p, "--doctest-modules")
reprec.assertoutcome(failed=1) reprec.assertoutcome(failed=1)
def test_doctest_cached_property(self, pytester: Pytester):
p = pytester.makepyfile(
"""
import functools
class Foo:
@functools.cached_property
def foo(self):
'''
>>> assert False, "Tacos!"
'''
...
"""
)
result = pytester.runpytest(p, "--doctest-modules")
result.assert_outcomes(failed=1)
assert "Tacos!" in result.stdout.str()
def test_doctestmodule_external_and_issue116(self, pytester: Pytester): def test_doctestmodule_external_and_issue116(self, pytester: Pytester):
p = pytester.mkpydir("hello") p = pytester.mkpydir("hello")
p.joinpath("__init__.py").write_text( p.joinpath("__init__.py").write_text(