Skip to content

Commit a4b7a75

Browse files
committed
Issue #3158: doctest can now find doctests in functions and methods
written in C. As a part of this, a few doctests have been added to the builtins module (on hex(), oct(), and bin()), a doctest has been fixed (hopefully on all platforms) on float, and test_builtins now runs doctests in builtins.
1 parent 091167c commit a4b7a75

File tree

7 files changed

+65
-28
lines changed

7 files changed

+65
-28
lines changed

Doc/library/doctest.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ strings are treated as if they were docstrings. In output, a key ``K`` in
278278
Any classes found are recursively searched similarly, to test docstrings in
279279
their contained methods and nested classes.
280280

281+
.. impl-detail::
282+
Prior to version 3.4, extension modules written in C were not fully
283+
searched by doctest.
284+
281285

282286
.. _doctest-finding-examples:
283287

@@ -1285,9 +1289,8 @@ DocTestFinder objects
12851289

12861290
A processing class used to extract the :class:`DocTest`\ s that are relevant to
12871291
a given object, from its docstring and the docstrings of its contained objects.
1288-
:class:`DocTest`\ s can currently be extracted from the following object types:
1289-
modules, functions, classes, methods, staticmethods, classmethods, and
1290-
properties.
1292+
:class:`DocTest`\ s can be extracted from modules, classes, functions,
1293+
methods, staticmethods, classmethods, and properties.
12911294

12921295
The optional argument *verbose* can be used to display the objects searched by
12931296
the finder. It defaults to ``False`` (no output).

Lib/doctest.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,8 @@ def _from_module(self, module, object):
918918
return module is inspect.getmodule(object)
919919
elif inspect.isfunction(object):
920920
return module.__dict__ is object.__globals__
921+
elif inspect.ismethoddescriptor(object):
922+
return module.__name__ == object.__objclass__.__module__
921923
elif inspect.isclass(object):
922924
return module.__name__ == object.__module__
923925
elif hasattr(object, '__module__'):
@@ -950,7 +952,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
950952
for valname, val in obj.__dict__.items():
951953
valname = '%s.%s' % (name, valname)
952954
# Recurse to functions & classes.
953-
if ((inspect.isfunction(val) or inspect.isclass(val)) and
955+
if ((inspect.isroutine(val) or inspect.isclass(val)) and
954956
self._from_module(module, val)):
955957
self._find(tests, val, valname, module, source_lines,
956958
globs, seen)
@@ -962,9 +964,8 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
962964
raise ValueError("DocTestFinder.find: __test__ keys "
963965
"must be strings: %r" %
964966
(type(valname),))
965-
if not (inspect.isfunction(val) or inspect.isclass(val) or
966-
inspect.ismethod(val) or inspect.ismodule(val) or
967-
isinstance(val, str)):
967+
if not (inspect.isroutine(val) or inspect.isclass(val) or
968+
inspect.ismodule(val) or isinstance(val, str)):
968969
raise ValueError("DocTestFinder.find: __test__ values "
969970
"must be strings, functions, methods, "
970971
"classes, or modules: %r" %
@@ -983,7 +984,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
983984
val = getattr(obj, valname).__func__
984985

985986
# Recurse to methods, properties, and nested classes.
986-
if ((inspect.isfunction(val) or inspect.isclass(val) or
987+
if ((inspect.isroutine(val) or inspect.isclass(val) or
987988
isinstance(val, property)) and
988989
self._from_module(module, val)):
989990
valname = '%s.%s' % (name, valname)

Lib/test/test_builtin.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,21 +1592,10 @@ def test_baddecorator(self):
15921592
data = 'The quick Brown fox Jumped over The lazy Dog'.split()
15931593
self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0)
15941594

1595-
def test_main(verbose=None):
1596-
test_classes = (BuiltinTest, TestSorted)
1597-
1598-
run_unittest(*test_classes)
1599-
1600-
# verify reference counting
1601-
if verbose and hasattr(sys, "gettotalrefcount"):
1602-
import gc
1603-
counts = [None] * 5
1604-
for i in range(len(counts)):
1605-
run_unittest(*test_classes)
1606-
gc.collect()
1607-
counts[i] = sys.gettotalrefcount()
1608-
print(counts)
1609-
1595+
def load_tests(loader, tests, pattern):
1596+
from doctest import DocTestSuite
1597+
tests.addTest(DocTestSuite(builtins))
1598+
return tests
16101599

16111600
if __name__ == "__main__":
1612-
test_main(verbose=True)
1601+
unittest.main()

Lib/test/test_doctest.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,35 @@ def test_DocTestFinder(): r"""
644644
>>> test = doctest.DocTestFinder().find(f)[0]
645645
>>> [e.lineno for e in test.examples]
646646
[1, 9, 12]
647+
648+
Finding Doctests in Modules Not Written in Python
649+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
650+
DocTestFinder can also find doctests in most modules not written in Python.
651+
We'll use builtins as an example, since it almost certainly isn't written in
652+
plain ol' Python and is guaranteed to be available.
653+
654+
>>> import builtins
655+
>>> tests = doctest.DocTestFinder().find(builtins)
656+
>>> len(tests) # how many objects checked for doctests
657+
794
658+
>>> real_tests = [t for t in tests if len(t.examples) > 0]
659+
>>> len(real_tests) # how many objects actually have doctests
660+
8
661+
>>> for t in real_tests:
662+
... print('{} {}'.format(len(t.examples), t.name))
663+
...
664+
1 builtins.bin
665+
3 builtins.float.as_integer_ratio
666+
2 builtins.float.fromhex
667+
2 builtins.float.hex
668+
1 builtins.hex
669+
1 builtins.int
670+
2 builtins.int.bit_length
671+
1 builtins.oct
672+
673+
Note here that 'bin', 'oct', and 'hex' are functions; 'float.as_integer_ratio',
674+
'float.hex', and 'int.bit_length' are methods; 'float.fromhex' is a classmethod,
675+
and 'int' is a type.
647676
"""
648677

649678
def test_DocTestParser(): r"""

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Core and Builtins
6868
Library
6969
-------
7070

71+
- Issue #3158: doctest can now find doctests in functions and methods
72+
written in C.
73+
7174
- Issue #13477: Added command line interface to the tarfile module.
7275
Original patch by Berker Peksag.
7376

Objects/floatobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1417,7 +1417,7 @@ Create a floating-point number from a hexadecimal string.\n\
14171417
>>> float.fromhex('0x1.ffffp10')\n\
14181418
2047.984375\n\
14191419
>>> float.fromhex('-0x1p-1074')\n\
1420-
-4.9406564584124654e-324");
1420+
-5e-324");
14211421

14221422

14231423
static PyObject *

Python/bltinmodule.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,11 @@ builtin_bin(PyObject *self, PyObject *v)
350350
PyDoc_STRVAR(bin_doc,
351351
"bin(number) -> string\n\
352352
\n\
353-
Return the binary representation of an integer.");
353+
Return the binary representation of an integer.\n\
354+
\n\
355+
>>> bin(2796202)\n\
356+
'0b1010101010101010101010'\n\
357+
");
354358

355359

356360
static PyObject *
@@ -1276,7 +1280,11 @@ builtin_hex(PyObject *self, PyObject *v)
12761280
PyDoc_STRVAR(hex_doc,
12771281
"hex(number) -> string\n\
12781282
\n\
1279-
Return the hexadecimal representation of an integer.");
1283+
Return the hexadecimal representation of an integer.\n\
1284+
\n\
1285+
>>> hex(3735928559)\n\
1286+
'0xdeadbeef'\n\
1287+
");
12801288

12811289

12821290
static PyObject *
@@ -1476,7 +1484,11 @@ builtin_oct(PyObject *self, PyObject *v)
14761484
PyDoc_STRVAR(oct_doc,
14771485
"oct(number) -> string\n\
14781486
\n\
1479-
Return the octal representation of an integer.");
1487+
Return the octal representation of an integer.\n\
1488+
\n\
1489+
>>> oct(342391)\n\
1490+
'0o1234567'\n\
1491+
");
14801492

14811493

14821494
static PyObject *

0 commit comments

Comments
 (0)