Commit f1b1cdd5 authored by Zachary Ware's avatar Zachary Ware

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.
parent e6953daa
...@@ -278,6 +278,10 @@ strings are treated as if they were docstrings. In output, a key ``K`` in ...@@ -278,6 +278,10 @@ strings are treated as if they were docstrings. In output, a key ``K`` in
Any classes found are recursively searched similarly, to test docstrings in Any classes found are recursively searched similarly, to test docstrings in
their contained methods and nested classes. their contained methods and nested classes.
.. impl-detail::
Prior to version 3.4, extension modules written in C were not fully
searched by doctest.
.. _doctest-finding-examples: .. _doctest-finding-examples:
...@@ -1285,9 +1289,8 @@ DocTestFinder objects ...@@ -1285,9 +1289,8 @@ DocTestFinder objects
A processing class used to extract the :class:`DocTest`\ s that are relevant to A processing class used to extract the :class:`DocTest`\ s that are relevant to
a given object, from its docstring and the docstrings of its contained objects. a given object, from its docstring and the docstrings of its contained objects.
:class:`DocTest`\ s can currently be extracted from the following object types: :class:`DocTest`\ s can be extracted from modules, classes, functions,
modules, functions, classes, methods, staticmethods, classmethods, and methods, staticmethods, classmethods, and properties.
properties.
The optional argument *verbose* can be used to display the objects searched by The optional argument *verbose* can be used to display the objects searched by
the finder. It defaults to ``False`` (no output). the finder. It defaults to ``False`` (no output).
......
...@@ -918,6 +918,8 @@ class DocTestFinder: ...@@ -918,6 +918,8 @@ class DocTestFinder:
return module is inspect.getmodule(object) return module is inspect.getmodule(object)
elif inspect.isfunction(object): elif inspect.isfunction(object):
return module.__dict__ is object.__globals__ return module.__dict__ is object.__globals__
elif inspect.ismethoddescriptor(object):
return module.__name__ == object.__objclass__.__module__
elif inspect.isclass(object): elif inspect.isclass(object):
return module.__name__ == object.__module__ return module.__name__ == object.__module__
elif hasattr(object, '__module__'): elif hasattr(object, '__module__'):
...@@ -950,7 +952,7 @@ class DocTestFinder: ...@@ -950,7 +952,7 @@ class DocTestFinder:
for valname, val in obj.__dict__.items(): for valname, val in obj.__dict__.items():
valname = '%s.%s' % (name, valname) valname = '%s.%s' % (name, valname)
# Recurse to functions & classes. # Recurse to functions & classes.
if ((inspect.isfunction(val) or inspect.isclass(val)) and if ((inspect.isroutine(val) or inspect.isclass(val)) and
self._from_module(module, val)): self._from_module(module, val)):
self._find(tests, val, valname, module, source_lines, self._find(tests, val, valname, module, source_lines,
globs, seen) globs, seen)
...@@ -962,9 +964,8 @@ class DocTestFinder: ...@@ -962,9 +964,8 @@ class DocTestFinder:
raise ValueError("DocTestFinder.find: __test__ keys " raise ValueError("DocTestFinder.find: __test__ keys "
"must be strings: %r" % "must be strings: %r" %
(type(valname),)) (type(valname),))
if not (inspect.isfunction(val) or inspect.isclass(val) or if not (inspect.isroutine(val) or inspect.isclass(val) or
inspect.ismethod(val) or inspect.ismodule(val) or inspect.ismodule(val) or isinstance(val, str)):
isinstance(val, str)):
raise ValueError("DocTestFinder.find: __test__ values " raise ValueError("DocTestFinder.find: __test__ values "
"must be strings, functions, methods, " "must be strings, functions, methods, "
"classes, or modules: %r" % "classes, or modules: %r" %
...@@ -983,7 +984,7 @@ class DocTestFinder: ...@@ -983,7 +984,7 @@ class DocTestFinder:
val = getattr(obj, valname).__func__ val = getattr(obj, valname).__func__
# Recurse to methods, properties, and nested classes. # Recurse to methods, properties, and nested classes.
if ((inspect.isfunction(val) or inspect.isclass(val) or if ((inspect.isroutine(val) or inspect.isclass(val) or
isinstance(val, property)) and isinstance(val, property)) and
self._from_module(module, val)): self._from_module(module, val)):
valname = '%s.%s' % (name, valname) valname = '%s.%s' % (name, valname)
......
...@@ -1592,21 +1592,10 @@ class TestSorted(unittest.TestCase): ...@@ -1592,21 +1592,10 @@ class TestSorted(unittest.TestCase):
data = 'The quick Brown fox Jumped over The lazy Dog'.split() data = 'The quick Brown fox Jumped over The lazy Dog'.split()
self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0) self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0)
def test_main(verbose=None): def load_tests(loader, tests, pattern):
test_classes = (BuiltinTest, TestSorted) from doctest import DocTestSuite
tests.addTest(DocTestSuite(builtins))
run_unittest(*test_classes) return tests
# verify reference counting
if verbose and hasattr(sys, "gettotalrefcount"):
import gc
counts = [None] * 5
for i in range(len(counts)):
run_unittest(*test_classes)
gc.collect()
counts[i] = sys.gettotalrefcount()
print(counts)
if __name__ == "__main__": if __name__ == "__main__":
test_main(verbose=True) unittest.main()
...@@ -644,6 +644,35 @@ DocTestFinder finds the line number of each example: ...@@ -644,6 +644,35 @@ DocTestFinder finds the line number of each example:
>>> test = doctest.DocTestFinder().find(f)[0] >>> test = doctest.DocTestFinder().find(f)[0]
>>> [e.lineno for e in test.examples] >>> [e.lineno for e in test.examples]
[1, 9, 12] [1, 9, 12]
Finding Doctests in Modules Not Written in Python
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DocTestFinder can also find doctests in most modules not written in Python.
We'll use builtins as an example, since it almost certainly isn't written in
plain ol' Python and is guaranteed to be available.
>>> import builtins
>>> tests = doctest.DocTestFinder().find(builtins)
>>> len(tests) # how many objects checked for doctests
794
>>> real_tests = [t for t in tests if len(t.examples) > 0]
>>> len(real_tests) # how many objects actually have doctests
8
>>> for t in real_tests:
... print('{} {}'.format(len(t.examples), t.name))
...
1 builtins.bin
3 builtins.float.as_integer_ratio
2 builtins.float.fromhex
2 builtins.float.hex
1 builtins.hex
1 builtins.int
2 builtins.int.bit_length
1 builtins.oct
Note here that 'bin', 'oct', and 'hex' are functions; 'float.as_integer_ratio',
'float.hex', and 'int.bit_length' are methods; 'float.fromhex' is a classmethod,
and 'int' is a type.
""" """
def test_DocTestParser(): r""" def test_DocTestParser(): r"""
......
...@@ -68,6 +68,9 @@ Core and Builtins ...@@ -68,6 +68,9 @@ Core and Builtins
Library Library
------- -------
- Issue #3158: doctest can now find doctests in functions and methods
written in C.
- Issue #13477: Added command line interface to the tarfile module. - Issue #13477: Added command line interface to the tarfile module.
Original patch by Berker Peksag. Original patch by Berker Peksag.
......
...@@ -1417,7 +1417,7 @@ Create a floating-point number from a hexadecimal string.\n\ ...@@ -1417,7 +1417,7 @@ Create a floating-point number from a hexadecimal string.\n\
>>> float.fromhex('0x1.ffffp10')\n\ >>> float.fromhex('0x1.ffffp10')\n\
2047.984375\n\ 2047.984375\n\
>>> float.fromhex('-0x1p-1074')\n\ >>> float.fromhex('-0x1p-1074')\n\
-4.9406564584124654e-324"); -5e-324");
static PyObject * static PyObject *
......
...@@ -350,7 +350,11 @@ builtin_bin(PyObject *self, PyObject *v) ...@@ -350,7 +350,11 @@ builtin_bin(PyObject *self, PyObject *v)
PyDoc_STRVAR(bin_doc, PyDoc_STRVAR(bin_doc,
"bin(number) -> string\n\ "bin(number) -> string\n\
\n\ \n\
Return the binary representation of an integer."); Return the binary representation of an integer.\n\
\n\
>>> bin(2796202)\n\
'0b1010101010101010101010'\n\
");
static PyObject * static PyObject *
...@@ -1276,7 +1280,11 @@ builtin_hex(PyObject *self, PyObject *v) ...@@ -1276,7 +1280,11 @@ builtin_hex(PyObject *self, PyObject *v)
PyDoc_STRVAR(hex_doc, PyDoc_STRVAR(hex_doc,
"hex(number) -> string\n\ "hex(number) -> string\n\
\n\ \n\
Return the hexadecimal representation of an integer."); Return the hexadecimal representation of an integer.\n\
\n\
>>> hex(3735928559)\n\
'0xdeadbeef'\n\
");
static PyObject * static PyObject *
...@@ -1476,7 +1484,11 @@ builtin_oct(PyObject *self, PyObject *v) ...@@ -1476,7 +1484,11 @@ builtin_oct(PyObject *self, PyObject *v)
PyDoc_STRVAR(oct_doc, PyDoc_STRVAR(oct_doc,
"oct(number) -> string\n\ "oct(number) -> string\n\
\n\ \n\
Return the octal representation of an integer."); Return the octal representation of an integer.\n\
\n\
>>> oct(342391)\n\
'0o1234567'\n\
");
static PyObject * static PyObject *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment