Skip to content

Commit 7191dcf

Browse files
include all modules in doctests
This patch changes doctest's test finder to include all files in the nutils directory, rather than the ones listed in nutils.__all__ which is due to be removed. The change includes a modified version of DocTestFinder to fix a bug triggered by the SI module (formerly untested) and for which a PR is created for cpython (python/cpython#107716). The patch also includes a small fix in SI's documentation.
1 parent e8a5bbe commit 7191dcf

File tree

3 files changed

+63
-3
lines changed

3 files changed

+63
-3
lines changed

nutils/SI.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
prefixes. Dimensional values are generated primarily by instantiating the
1313
Quantity type with a string value.
1414
15+
>>> from nutils import SI
1516
>>> v = SI.parse('7μN*5h/6g')
1617
1718
The Quantity constructor recognizes the multiplication (\*) and division (/)

nutils/testing.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import warnings as _builtin_warnings
1818
import logging
1919
import numpy
20+
import inspect
2021
from nutils import warnings, numeric
2122

2223

@@ -316,4 +317,62 @@ def _parse_array_tokens(cls, tokens):
316317
else:
317318
return float(token)
318319

320+
321+
class DocTestFinder(doctest.DocTestFinder):
322+
323+
# This is a modified version of doctest.DocTestFinder to fix issue
324+
# https://github.com/python/cpython/issues/107715, which prevents doctest
325+
# operation for the SI module. The modification assumes that `find`
326+
# continues to rely on the internal `_find_lineno` method, which will
327+
# hopefully be the case until Python merges #107716.
328+
329+
def _find_lineno(self, obj, source_lines):
330+
"""
331+
Return a line number of the given object's docstring. Note:
332+
this method assumes that the object has a docstring.
333+
"""
334+
lineno = None
335+
336+
# Find the line number for modules.
337+
if inspect.ismodule(obj):
338+
lineno = 0
339+
340+
# Find the line number for classes.
341+
# Note: this could be fooled if a class is defined multiple
342+
# times in a single file.
343+
if inspect.isclass(obj):
344+
if source_lines is None:
345+
return None
346+
pat = re.compile(r'^\s*class\s*%s\b' %
347+
re.escape(getattr(obj, '__name__', '-')))
348+
for i, line in enumerate(source_lines):
349+
if pat.match(line):
350+
lineno = i
351+
break
352+
353+
# Find the line number for functions & methods.
354+
if inspect.ismethod(obj): obj = obj.__func__
355+
if inspect.isfunction(obj): obj = obj.__code__
356+
if inspect.istraceback(obj): obj = obj.tb_frame
357+
if inspect.isframe(obj): obj = obj.f_code
358+
if inspect.iscode(obj):
359+
lineno = getattr(obj, 'co_firstlineno', None)-1
360+
361+
# Find the line number where the docstring starts. Assume
362+
# that it's the first line that begins with a quote mark.
363+
# Note: this could be fooled by a multiline function
364+
# signature, where a continuation line begins with a quote
365+
# mark.
366+
if lineno is not None:
367+
if source_lines is None:
368+
return lineno+1
369+
pat = re.compile(r'(^|.*:)\s*\w*("|\')')
370+
for lineno in range(lineno, len(source_lines)):
371+
if pat.match(source_lines[lineno]):
372+
return lineno
373+
374+
# We couldn't find the line number.
375+
return None
376+
377+
319378
# vim:sw=4:sts=4:et

tests/test_docs.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ def __repr__(self):
5959

6060
doctest = unittest.TestSuite()
6161
parser = _doctest.DocTestParser()
62-
finder = _doctest.DocTestFinder(parser=parser)
62+
finder = nutils.testing.DocTestFinder(parser=parser)
6363
checker = nutils.testing.FloatNeighborhoodOutputChecker()
6464
root = pathlib.Path(__file__).parent.parent
65-
for name in nutils.__all__:
66-
module = importlib.import_module('.'+name, 'nutils')
65+
for path in sorted((root/'nutils').glob('*.py')):
66+
module = importlib.import_module('.'+path.stem, 'nutils')
6767
for test in sorted(finder.find(module)):
6868
if len(test.examples) == 0:
6969
continue

0 commit comments

Comments
 (0)