diff options
author | 2022-05-19 11:03:06 -0700 | |
---|---|---|
committer | 2022-05-19 20:03:06 +0200 | |
commit | 57d7ddd6072ef8996f60ac06d6e2b2484692171b (patch) | |
tree | 6e41efe5e41e6db6a06c0512e5c82b07b0404ed0 | |
parent | gh-78630: Drop invalid HP aCC compiler switch -fPIC on HP-UX (GH-8847) (diff) | |
download | cpython-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.py | 14 | ||||
-rw-r--r-- | Lib/test/doctest_lineno.py | 50 | ||||
-rw-r--r-- | Lib/test/test_doctest.py | 23 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2022-01-09-14-23-00.bpo-28249.4dzB80.rst | 2 |
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__`. |