Skip to content

Commit

Permalink
[3.10] bpo-28249: fix lineno location for empty DocTest instances (
Browse files Browse the repository at this point in the history
…GH-30498) (#92981)

(cherry picked from commit 8db2b3b)

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
  • Loading branch information
3 people committed May 19, 2022
1 parent 5d7f3dc commit c146525
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 6 deletions.
14 changes: 9 additions & 5 deletions Lib/doctest.py
Expand Up @@ -1085,19 +1085,21 @@ def _get_test(self, obj, name, module, globs, source_lines):

def _find_lineno(self, obj, source_lines):
"""
Return a line number of the given object's docstring. Note:
this method assumes that the object has a docstring.
Return a line number of the given object's docstring.
Returns `None` if the given object does not have a docstring.
"""
lineno = None
docstring = getattr(obj, '__doc__', None)

# Find the line number for modules.
if inspect.ismodule(obj):
if inspect.ismodule(obj) and docstring is not None:
lineno = 0

# Find the line number for classes.
# Note: this could be fooled if a class is defined multiple
# times in a single file.
if inspect.isclass(obj):
if inspect.isclass(obj) and docstring is not None:
if source_lines is None:
return None
pat = re.compile(r'^\s*class\s*%s\b' %
Expand All @@ -1109,7 +1111,9 @@ def _find_lineno(self, obj, source_lines):

# Find the line number for functions & methods.
if inspect.ismethod(obj): obj = obj.__func__
if inspect.isfunction(obj): obj = obj.__code__
if inspect.isfunction(obj) and getattr(obj, '__doc__', None):
# We don't use `docstring` var here, because `obj` can be changed.
obj = obj.__code__
if inspect.istraceback(obj): obj = obj.tb_frame
if inspect.isframe(obj): obj = obj.f_code
if inspect.iscode(obj):
Expand Down
50 changes: 50 additions & 0 deletions Lib/test/doctest_lineno.py
@@ -0,0 +1,50 @@
# This module is used in `test_doctest`.
# It must not have a docstring.

def func_with_docstring():
"""Some unrelated info."""


def func_without_docstring():
pass


def func_with_doctest():
"""
This function really contains a test case.
>>> func_with_doctest.__name__
'func_with_doctest'
"""
return 3


class ClassWithDocstring:
"""Some unrelated class information."""


class ClassWithoutDocstring:
pass


class ClassWithDoctest:
"""This class really has a test case in it.
>>> ClassWithDoctest.__name__
'ClassWithDoctest'
"""


class MethodWrapper:
def method_with_docstring(self):
"""Method with a docstring."""

def method_without_docstring(self):
pass

def method_with_doctest(self):
"""
This has a doctest!
>>> MethodWrapper.method_with_doctest.__name__
'method_with_doctest'
"""
23 changes: 22 additions & 1 deletion Lib/test/test_doctest.py
Expand Up @@ -20,6 +20,7 @@

# NOTE: There are some additional tests relating to interaction with
# zipimport in the test_zipimport_support test module.
# There are also related tests in `test_doctest2` module.

######################################################################
## Sample Objects (used by test cases)
Expand Down Expand Up @@ -455,7 +456,7 @@ def basics(): r"""
>>> tests = finder.find(sample_func)
>>> print(tests) # doctest: +ELLIPSIS
[<DocTest sample_func from test_doctest.py:28 (1 example)>]
[<DocTest sample_func from test_doctest.py:29 (1 example)>]
The exact name depends on how test_doctest was invoked, so allow for
leading path components.
Expand Down Expand Up @@ -637,6 +638,26 @@ def basics(): r"""
1 SampleClass.double
1 SampleClass.get
When used with `exclude_empty=False` we are also interested in line numbers
of doctests that are empty.
It used to be broken for quite some time until `bpo-28249`.
>>> from test import doctest_lineno
>>> tests = doctest.DocTestFinder(exclude_empty=False).find(doctest_lineno)
>>> for t in tests:
... print('%5s %s' % (t.lineno, t.name))
None test.doctest_lineno
22 test.doctest_lineno.ClassWithDocstring
30 test.doctest_lineno.ClassWithDoctest
None test.doctest_lineno.ClassWithoutDocstring
None test.doctest_lineno.MethodWrapper
39 test.doctest_lineno.MethodWrapper.method_with_docstring
45 test.doctest_lineno.MethodWrapper.method_with_doctest
None test.doctest_lineno.MethodWrapper.method_without_docstring
4 test.doctest_lineno.func_with_docstring
12 test.doctest_lineno.func_with_doctest
None test.doctest_lineno.func_without_docstring
Turning off Recursion
~~~~~~~~~~~~~~~~~~~~~
DocTestFinder can be told not to look for tests in contained objects
Expand Down
@@ -0,0 +1,2 @@
Set :attr:`doctest.DocTest.lineno` to ``None`` when object does not have
:attr:`__doc__`.

0 comments on commit c146525

Please sign in to comment.