aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2022-05-19 11:03:06 -0700
committerGitHub <noreply@github.com>2022-05-19 20:03:06 +0200
commit57d7ddd6072ef8996f60ac06d6e2b2484692171b (patch)
tree6e41efe5e41e6db6a06c0512e5c82b07b0404ed0
parentgh-78630: Drop invalid HP aCC compiler switch -fPIC on HP-UX (GH-8847) (diff)
downloadcpython-57d7ddd6072ef8996f60ac06d6e2b2484692171b.tar.gz
cpython-57d7ddd6072ef8996f60ac06d6e2b2484692171b.tar.bz2
cpython-57d7ddd6072ef8996f60ac06d6e2b2484692171b.zip
bpo-28249: fix `lineno` location for empty `DocTest` instances (GH-30498) (GH-92978)
(cherry picked from commit 8db2b3b6878aba9f12844526bce966b7eed81aee) 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>
-rw-r--r--Lib/doctest.py14
-rw-r--r--Lib/test/doctest_lineno.py50
-rw-r--r--Lib/test/test_doctest.py23
-rw-r--r--Misc/NEWS.d/next/Library/2022-01-09-14-23-00.bpo-28249.4dzB80.rst2
4 files changed, 83 insertions, 6 deletions
diff --git a/Lib/doctest.py b/Lib/doctest.py
index ed94d15c0e2..b2ef2ce6367 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -1085,19 +1085,21 @@ class DocTestFinder:
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' %
@@ -1109,7 +1111,9 @@ class DocTestFinder:
# 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):
diff --git a/Lib/test/doctest_lineno.py b/Lib/test/doctest_lineno.py
new file mode 100644
index 00000000000..be198513a15
--- /dev/null
+++ b/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'
+ """
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index 3e7f3782d89..a4aab6cf4db 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -25,6 +25,7 @@ if not support.has_subprocess_support:
# 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)
@@ -460,7 +461,7 @@ We'll simulate a __file__ attr that ends in pyc:
>>> tests = finder.find(sample_func)
>>> print(tests) # doctest: +ELLIPSIS
- [<DocTest sample_func from test_doctest.py:33 (1 example)>]
+ [<DocTest sample_func from test_doctest.py:34 (1 example)>]
The exact name depends on how test_doctest was invoked, so allow for
leading path components.
@@ -642,6 +643,26 @@ displays.
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
diff --git a/Misc/NEWS.d/next/Library/2022-01-09-14-23-00.bpo-28249.4dzB80.rst b/Misc/NEWS.d/next/Library/2022-01-09-14-23-00.bpo-28249.4dzB80.rst
new file mode 100644
index 00000000000..b5f1312d768
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-01-09-14-23-00.bpo-28249.4dzB80.rst
@@ -0,0 +1,2 @@
+Set :attr:`doctest.DocTest.lineno` to ``None`` when object does not have
+:attr:`__doc__`.