You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3188 lines
101 KiB
3188 lines
101 KiB
3 years ago
|
"""
|
||
|
Test script for doctest.
|
||
|
"""
|
||
|
|
||
|
from test import support
|
||
|
from test.support import import_helper
|
||
|
from test.support import os_helper
|
||
|
import doctest
|
||
|
import functools
|
||
|
import os
|
||
|
import sys
|
||
|
import importlib
|
||
|
import importlib.abc
|
||
|
import importlib.util
|
||
|
import unittest
|
||
|
import tempfile
|
||
|
import shutil
|
||
|
import types
|
||
|
import contextlib
|
||
|
|
||
|
# 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)
|
||
|
######################################################################
|
||
|
|
||
|
def sample_func(v):
|
||
|
"""
|
||
|
Blah blah
|
||
|
|
||
|
>>> print(sample_func(22))
|
||
|
44
|
||
|
|
||
|
Yee ha!
|
||
|
"""
|
||
|
return v+v
|
||
|
|
||
|
class SampleClass:
|
||
|
"""
|
||
|
>>> print(1)
|
||
|
1
|
||
|
|
||
|
>>> # comments get ignored. so are empty PS1 and PS2 prompts:
|
||
|
>>>
|
||
|
...
|
||
|
|
||
|
Multiline example:
|
||
|
>>> sc = SampleClass(3)
|
||
|
>>> for i in range(10):
|
||
|
... sc = sc.double()
|
||
|
... print(' ', sc.get(), sep='', end='')
|
||
|
6 12 24 48 96 192 384 768 1536 3072
|
||
|
"""
|
||
|
def __init__(self, val):
|
||
|
"""
|
||
|
>>> print(SampleClass(12).get())
|
||
|
12
|
||
|
"""
|
||
|
self.val = val
|
||
|
|
||
|
def double(self):
|
||
|
"""
|
||
|
>>> print(SampleClass(12).double().get())
|
||
|
24
|
||
|
"""
|
||
|
return SampleClass(self.val + self.val)
|
||
|
|
||
|
def get(self):
|
||
|
"""
|
||
|
>>> print(SampleClass(-5).get())
|
||
|
-5
|
||
|
"""
|
||
|
return self.val
|
||
|
|
||
|
def a_staticmethod(v):
|
||
|
"""
|
||
|
>>> print(SampleClass.a_staticmethod(10))
|
||
|
11
|
||
|
"""
|
||
|
return v+1
|
||
|
a_staticmethod = staticmethod(a_staticmethod)
|
||
|
|
||
|
def a_classmethod(cls, v):
|
||
|
"""
|
||
|
>>> print(SampleClass.a_classmethod(10))
|
||
|
12
|
||
|
>>> print(SampleClass(0).a_classmethod(10))
|
||
|
12
|
||
|
"""
|
||
|
return v+2
|
||
|
a_classmethod = classmethod(a_classmethod)
|
||
|
|
||
|
a_property = property(get, doc="""
|
||
|
>>> print(SampleClass(22).a_property)
|
||
|
22
|
||
|
""")
|
||
|
|
||
|
a_class_attribute = 42
|
||
|
|
||
|
@classmethod
|
||
|
@property
|
||
|
def a_classmethod_property(cls):
|
||
|
"""
|
||
|
>>> print(SampleClass.a_classmethod_property)
|
||
|
42
|
||
|
"""
|
||
|
return cls.a_class_attribute
|
||
|
|
||
|
class NestedClass:
|
||
|
"""
|
||
|
>>> x = SampleClass.NestedClass(5)
|
||
|
>>> y = x.square()
|
||
|
>>> print(y.get())
|
||
|
25
|
||
|
"""
|
||
|
def __init__(self, val=0):
|
||
|
"""
|
||
|
>>> print(SampleClass.NestedClass().get())
|
||
|
0
|
||
|
"""
|
||
|
self.val = val
|
||
|
def square(self):
|
||
|
return SampleClass.NestedClass(self.val*self.val)
|
||
|
def get(self):
|
||
|
return self.val
|
||
|
|
||
|
class SampleNewStyleClass(object):
|
||
|
r"""
|
||
|
>>> print('1\n2\n3')
|
||
|
1
|
||
|
2
|
||
|
3
|
||
|
"""
|
||
|
def __init__(self, val):
|
||
|
"""
|
||
|
>>> print(SampleNewStyleClass(12).get())
|
||
|
12
|
||
|
"""
|
||
|
self.val = val
|
||
|
|
||
|
def double(self):
|
||
|
"""
|
||
|
>>> print(SampleNewStyleClass(12).double().get())
|
||
|
24
|
||
|
"""
|
||
|
return SampleNewStyleClass(self.val + self.val)
|
||
|
|
||
|
def get(self):
|
||
|
"""
|
||
|
>>> print(SampleNewStyleClass(-5).get())
|
||
|
-5
|
||
|
"""
|
||
|
return self.val
|
||
|
|
||
|
######################################################################
|
||
|
## Fake stdin (for testing interactive debugging)
|
||
|
######################################################################
|
||
|
|
||
|
class _FakeInput:
|
||
|
"""
|
||
|
A fake input stream for pdb's interactive debugger. Whenever a
|
||
|
line is read, print it (to simulate the user typing it), and then
|
||
|
return it. The set of lines to return is specified in the
|
||
|
constructor; they should not have trailing newlines.
|
||
|
"""
|
||
|
def __init__(self, lines):
|
||
|
self.lines = lines
|
||
|
|
||
|
def readline(self):
|
||
|
line = self.lines.pop(0)
|
||
|
print(line)
|
||
|
return line+'\n'
|
||
|
|
||
|
######################################################################
|
||
|
## Test Cases
|
||
|
######################################################################
|
||
|
|
||
|
def test_Example(): r"""
|
||
|
Unit tests for the `Example` class.
|
||
|
|
||
|
Example is a simple container class that holds:
|
||
|
- `source`: A source string.
|
||
|
- `want`: An expected output string.
|
||
|
- `exc_msg`: An expected exception message string (or None if no
|
||
|
exception is expected).
|
||
|
- `lineno`: A line number (within the docstring).
|
||
|
- `indent`: The example's indentation in the input string.
|
||
|
- `options`: An option dictionary, mapping option flags to True or
|
||
|
False.
|
||
|
|
||
|
These attributes are set by the constructor. `source` and `want` are
|
||
|
required; the other attributes all have default values:
|
||
|
|
||
|
>>> example = doctest.Example('print(1)', '1\n')
|
||
|
>>> (example.source, example.want, example.exc_msg,
|
||
|
... example.lineno, example.indent, example.options)
|
||
|
('print(1)\n', '1\n', None, 0, 0, {})
|
||
|
|
||
|
The first three attributes (`source`, `want`, and `exc_msg`) may be
|
||
|
specified positionally; the remaining arguments should be specified as
|
||
|
keyword arguments:
|
||
|
|
||
|
>>> exc_msg = 'IndexError: pop from an empty list'
|
||
|
>>> example = doctest.Example('[].pop()', '', exc_msg,
|
||
|
... lineno=5, indent=4,
|
||
|
... options={doctest.ELLIPSIS: True})
|
||
|
>>> (example.source, example.want, example.exc_msg,
|
||
|
... example.lineno, example.indent, example.options)
|
||
|
('[].pop()\n', '', 'IndexError: pop from an empty list\n', 5, 4, {8: True})
|
||
|
|
||
|
The constructor normalizes the `source` string to end in a newline:
|
||
|
|
||
|
Source spans a single line: no terminating newline.
|
||
|
>>> e = doctest.Example('print(1)', '1\n')
|
||
|
>>> e.source, e.want
|
||
|
('print(1)\n', '1\n')
|
||
|
|
||
|
>>> e = doctest.Example('print(1)\n', '1\n')
|
||
|
>>> e.source, e.want
|
||
|
('print(1)\n', '1\n')
|
||
|
|
||
|
Source spans multiple lines: require terminating newline.
|
||
|
>>> e = doctest.Example('print(1);\nprint(2)\n', '1\n2\n')
|
||
|
>>> e.source, e.want
|
||
|
('print(1);\nprint(2)\n', '1\n2\n')
|
||
|
|
||
|
>>> e = doctest.Example('print(1);\nprint(2)', '1\n2\n')
|
||
|
>>> e.source, e.want
|
||
|
('print(1);\nprint(2)\n', '1\n2\n')
|
||
|
|
||
|
Empty source string (which should never appear in real examples)
|
||
|
>>> e = doctest.Example('', '')
|
||
|
>>> e.source, e.want
|
||
|
('\n', '')
|
||
|
|
||
|
The constructor normalizes the `want` string to end in a newline,
|
||
|
unless it's the empty string:
|
||
|
|
||
|
>>> e = doctest.Example('print(1)', '1\n')
|
||
|
>>> e.source, e.want
|
||
|
('print(1)\n', '1\n')
|
||
|
|
||
|
>>> e = doctest.Example('print(1)', '1')
|
||
|
>>> e.source, e.want
|
||
|
('print(1)\n', '1\n')
|
||
|
|
||
|
>>> e = doctest.Example('print', '')
|
||
|
>>> e.source, e.want
|
||
|
('print\n', '')
|
||
|
|
||
|
The constructor normalizes the `exc_msg` string to end in a newline,
|
||
|
unless it's `None`:
|
||
|
|
||
|
Message spans one line
|
||
|
>>> exc_msg = 'IndexError: pop from an empty list'
|
||
|
>>> e = doctest.Example('[].pop()', '', exc_msg)
|
||
|
>>> e.exc_msg
|
||
|
'IndexError: pop from an empty list\n'
|
||
|
|
||
|
>>> exc_msg = 'IndexError: pop from an empty list\n'
|
||
|
>>> e = doctest.Example('[].pop()', '', exc_msg)
|
||
|
>>> e.exc_msg
|
||
|
'IndexError: pop from an empty list\n'
|
||
|
|
||
|
Message spans multiple lines
|
||
|
>>> exc_msg = 'ValueError: 1\n 2'
|
||
|
>>> e = doctest.Example('raise ValueError("1\n 2")', '', exc_msg)
|
||
|
>>> e.exc_msg
|
||
|
'ValueError: 1\n 2\n'
|
||
|
|
||
|
>>> exc_msg = 'ValueError: 1\n 2\n'
|
||
|
>>> e = doctest.Example('raise ValueError("1\n 2")', '', exc_msg)
|
||
|
>>> e.exc_msg
|
||
|
'ValueError: 1\n 2\n'
|
||
|
|
||
|
Empty (but non-None) exception message (which should never appear
|
||
|
in real examples)
|
||
|
>>> exc_msg = ''
|
||
|
>>> e = doctest.Example('raise X()', '', exc_msg)
|
||
|
>>> e.exc_msg
|
||
|
'\n'
|
||
|
|
||
|
Compare `Example`:
|
||
|
>>> example = doctest.Example('print 1', '1\n')
|
||
|
>>> same_example = doctest.Example('print 1', '1\n')
|
||
|
>>> other_example = doctest.Example('print 42', '42\n')
|
||
|
>>> example == same_example
|
||
|
True
|
||
|
>>> example != same_example
|
||
|
False
|
||
|
>>> hash(example) == hash(same_example)
|
||
|
True
|
||
|
>>> example == other_example
|
||
|
False
|
||
|
>>> example != other_example
|
||
|
True
|
||
|
"""
|
||
|
|
||
|
def test_DocTest(): r"""
|
||
|
Unit tests for the `DocTest` class.
|
||
|
|
||
|
DocTest is a collection of examples, extracted from a docstring, along
|
||
|
with information about where the docstring comes from (a name,
|
||
|
filename, and line number). The docstring is parsed by the `DocTest`
|
||
|
constructor:
|
||
|
|
||
|
>>> docstring = '''
|
||
|
... >>> print(12)
|
||
|
... 12
|
||
|
...
|
||
|
... Non-example text.
|
||
|
...
|
||
|
... >>> print('another\\example')
|
||
|
... another
|
||
|
... example
|
||
|
... '''
|
||
|
>>> globs = {} # globals to run the test in.
|
||
|
>>> parser = doctest.DocTestParser()
|
||
|
>>> test = parser.get_doctest(docstring, globs, 'some_test',
|
||
|
... 'some_file', 20)
|
||
|
>>> print(test)
|
||
|
<DocTest some_test from some_file:20 (2 examples)>
|
||
|
>>> len(test.examples)
|
||
|
2
|
||
|
>>> e1, e2 = test.examples
|
||
|
>>> (e1.source, e1.want, e1.lineno)
|
||
|
('print(12)\n', '12\n', 1)
|
||
|
>>> (e2.source, e2.want, e2.lineno)
|
||
|
("print('another\\example')\n", 'another\nexample\n', 6)
|
||
|
|
||
|
Source information (name, filename, and line number) is available as
|
||
|
attributes on the doctest object:
|
||
|
|
||
|
>>> (test.name, test.filename, test.lineno)
|
||
|
('some_test', 'some_file', 20)
|
||
|
|
||
|
The line number of an example within its containing file is found by
|
||
|
adding the line number of the example and the line number of its
|
||
|
containing test:
|
||
|
|
||
|
>>> test.lineno + e1.lineno
|
||
|
21
|
||
|
>>> test.lineno + e2.lineno
|
||
|
26
|
||
|
|
||
|
If the docstring contains inconsistent leading whitespace in the
|
||
|
expected output of an example, then `DocTest` will raise a ValueError:
|
||
|
|
||
|
>>> docstring = r'''
|
||
|
... >>> print('bad\nindentation')
|
||
|
... bad
|
||
|
... indentation
|
||
|
... '''
|
||
|
>>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0)
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: line 4 of the docstring for some_test has inconsistent leading whitespace: 'indentation'
|
||
|
|
||
|
If the docstring contains inconsistent leading whitespace on
|
||
|
continuation lines, then `DocTest` will raise a ValueError:
|
||
|
|
||
|
>>> docstring = r'''
|
||
|
... >>> print(('bad indentation',
|
||
|
... ... 2))
|
||
|
... ('bad', 'indentation')
|
||
|
... '''
|
||
|
>>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0)
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: line 2 of the docstring for some_test has inconsistent leading whitespace: '... 2))'
|
||
|
|
||
|
If there's no blank space after a PS1 prompt ('>>>'), then `DocTest`
|
||
|
will raise a ValueError:
|
||
|
|
||
|
>>> docstring = '>>>print(1)\n1'
|
||
|
>>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0)
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: line 1 of the docstring for some_test lacks blank after >>>: '>>>print(1)'
|
||
|
|
||
|
If there's no blank space after a PS2 prompt ('...'), then `DocTest`
|
||
|
will raise a ValueError:
|
||
|
|
||
|
>>> docstring = '>>> if 1:\n...print(1)\n1'
|
||
|
>>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0)
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: line 2 of the docstring for some_test lacks blank after ...: '...print(1)'
|
||
|
|
||
|
Compare `DocTest`:
|
||
|
|
||
|
>>> docstring = '''
|
||
|
... >>> print 12
|
||
|
... 12
|
||
|
... '''
|
||
|
>>> test = parser.get_doctest(docstring, globs, 'some_test',
|
||
|
... 'some_test', 20)
|
||
|
>>> same_test = parser.get_doctest(docstring, globs, 'some_test',
|
||
|
... 'some_test', 20)
|
||
|
>>> test == same_test
|
||
|
True
|
||
|
>>> test != same_test
|
||
|
False
|
||
|
>>> hash(test) == hash(same_test)
|
||
|
True
|
||
|
>>> docstring = '''
|
||
|
... >>> print 42
|
||
|
... 42
|
||
|
... '''
|
||
|
>>> other_test = parser.get_doctest(docstring, globs, 'other_test',
|
||
|
... 'other_file', 10)
|
||
|
>>> test == other_test
|
||
|
False
|
||
|
>>> test != other_test
|
||
|
True
|
||
|
|
||
|
Compare `DocTestCase`:
|
||
|
|
||
|
>>> DocTestCase = doctest.DocTestCase
|
||
|
>>> test_case = DocTestCase(test)
|
||
|
>>> same_test_case = DocTestCase(same_test)
|
||
|
>>> other_test_case = DocTestCase(other_test)
|
||
|
>>> test_case == same_test_case
|
||
|
True
|
||
|
>>> test_case != same_test_case
|
||
|
False
|
||
|
>>> hash(test_case) == hash(same_test_case)
|
||
|
True
|
||
|
>>> test == other_test_case
|
||
|
False
|
||
|
>>> test != other_test_case
|
||
|
True
|
||
|
|
||
|
"""
|
||
|
|
||
|
class test_DocTestFinder:
|
||
|
def basics(): r"""
|
||
|
Unit tests for the `DocTestFinder` class.
|
||
|
|
||
|
DocTestFinder is used to extract DocTests from an object's docstring
|
||
|
and the docstrings of its contained objects. It can be used with
|
||
|
modules, functions, classes, methods, staticmethods, classmethods, and
|
||
|
properties.
|
||
|
|
||
|
Finding Tests in Functions
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
For a function whose docstring contains examples, DocTestFinder.find()
|
||
|
will return a single test (for that function's docstring):
|
||
|
|
||
|
>>> finder = doctest.DocTestFinder()
|
||
|
|
||
|
We'll simulate a __file__ attr that ends in pyc:
|
||
|
|
||
|
>>> import test.test_doctest
|
||
|
>>> old = test.test_doctest.__file__
|
||
|
>>> test.test_doctest.__file__ = 'test_doctest.pyc'
|
||
|
|
||
|
>>> tests = finder.find(sample_func)
|
||
|
|
||
|
>>> print(tests) # doctest: +ELLIPSIS
|
||
|
[<DocTest sample_func from test_doctest.py:29 (1 example)>]
|
||
|
|
||
|
The exact name depends on how test_doctest was invoked, so allow for
|
||
|
leading path components.
|
||
|
|
||
|
>>> tests[0].filename # doctest: +ELLIPSIS
|
||
|
'...test_doctest.py'
|
||
|
|
||
|
>>> test.test_doctest.__file__ = old
|
||
|
|
||
|
|
||
|
>>> e = tests[0].examples[0]
|
||
|
>>> (e.source, e.want, e.lineno)
|
||
|
('print(sample_func(22))\n', '44\n', 3)
|
||
|
|
||
|
By default, tests are created for objects with no docstring:
|
||
|
|
||
|
>>> def no_docstring(v):
|
||
|
... pass
|
||
|
>>> finder.find(no_docstring)
|
||
|
[]
|
||
|
|
||
|
However, the optional argument `exclude_empty` to the DocTestFinder
|
||
|
constructor can be used to exclude tests for objects with empty
|
||
|
docstrings:
|
||
|
|
||
|
>>> def no_docstring(v):
|
||
|
... pass
|
||
|
>>> excl_empty_finder = doctest.DocTestFinder(exclude_empty=True)
|
||
|
>>> excl_empty_finder.find(no_docstring)
|
||
|
[]
|
||
|
|
||
|
If the function has a docstring with no examples, then a test with no
|
||
|
examples is returned. (This lets `DocTestRunner` collect statistics
|
||
|
about which functions have no tests -- but is that useful? And should
|
||
|
an empty test also be created when there's no docstring?)
|
||
|
|
||
|
>>> def no_examples(v):
|
||
|
... ''' no doctest examples '''
|
||
|
>>> finder.find(no_examples) # doctest: +ELLIPSIS
|
||
|
[<DocTest no_examples from ...:1 (no examples)>]
|
||
|
|
||
|
Finding Tests in Classes
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
For a class, DocTestFinder will create a test for the class's
|
||
|
docstring, and will recursively explore its contents, including
|
||
|
methods, classmethods, staticmethods, properties, and nested classes.
|
||
|
|
||
|
>>> finder = doctest.DocTestFinder()
|
||
|
>>> tests = finder.find(SampleClass)
|
||
|
>>> for t in tests:
|
||
|
... print('%2s %s' % (len(t.examples), t.name))
|
||
|
3 SampleClass
|
||
|
3 SampleClass.NestedClass
|
||
|
1 SampleClass.NestedClass.__init__
|
||
|
1 SampleClass.__init__
|
||
|
2 SampleClass.a_classmethod
|
||
|
1 SampleClass.a_classmethod_property
|
||
|
1 SampleClass.a_property
|
||
|
1 SampleClass.a_staticmethod
|
||
|
1 SampleClass.double
|
||
|
1 SampleClass.get
|
||
|
|
||
|
New-style classes are also supported:
|
||
|
|
||
|
>>> tests = finder.find(SampleNewStyleClass)
|
||
|
>>> for t in tests:
|
||
|
... print('%2s %s' % (len(t.examples), t.name))
|
||
|
1 SampleNewStyleClass
|
||
|
1 SampleNewStyleClass.__init__
|
||
|
1 SampleNewStyleClass.double
|
||
|
1 SampleNewStyleClass.get
|
||
|
|
||
|
Finding Tests in Modules
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
For a module, DocTestFinder will create a test for the class's
|
||
|
docstring, and will recursively explore its contents, including
|
||
|
functions, classes, and the `__test__` dictionary, if it exists:
|
||
|
|
||
|
>>> # A module
|
||
|
>>> import types
|
||
|
>>> m = types.ModuleType('some_module')
|
||
|
>>> def triple(val):
|
||
|
... '''
|
||
|
... >>> print(triple(11))
|
||
|
... 33
|
||
|
... '''
|
||
|
... return val*3
|
||
|
>>> m.__dict__.update({
|
||
|
... 'sample_func': sample_func,
|
||
|
... 'SampleClass': SampleClass,
|
||
|
... '__doc__': '''
|
||
|
... Module docstring.
|
||
|
... >>> print('module')
|
||
|
... module
|
||
|
... ''',
|
||
|
... '__test__': {
|
||
|
... 'd': '>>> print(6)\n6\n>>> print(7)\n7\n',
|
||
|
... 'c': triple}})
|
||
|
|
||
|
>>> finder = doctest.DocTestFinder()
|
||
|
>>> # Use module=test.test_doctest, to prevent doctest from
|
||
|
>>> # ignoring the objects since they weren't defined in m.
|
||
|
>>> import test.test_doctest
|
||
|
>>> tests = finder.find(m, module=test.test_doctest)
|
||
|
>>> for t in tests:
|
||
|
... print('%2s %s' % (len(t.examples), t.name))
|
||
|
1 some_module
|
||
|
3 some_module.SampleClass
|
||
|
3 some_module.SampleClass.NestedClass
|
||
|
1 some_module.SampleClass.NestedClass.__init__
|
||
|
1 some_module.SampleClass.__init__
|
||
|
2 some_module.SampleClass.a_classmethod
|
||
|
1 some_module.SampleClass.a_classmethod_property
|
||
|
1 some_module.SampleClass.a_property
|
||
|
1 some_module.SampleClass.a_staticmethod
|
||
|
1 some_module.SampleClass.double
|
||
|
1 some_module.SampleClass.get
|
||
|
1 some_module.__test__.c
|
||
|
2 some_module.__test__.d
|
||
|
1 some_module.sample_func
|
||
|
|
||
|
Duplicate Removal
|
||
|
~~~~~~~~~~~~~~~~~
|
||
|
If a single object is listed twice (under different names), then tests
|
||
|
will only be generated for it once:
|
||
|
|
||
|
>>> from test import doctest_aliases
|
||
|
>>> assert doctest_aliases.TwoNames.f
|
||
|
>>> assert doctest_aliases.TwoNames.g
|
||
|
>>> tests = excl_empty_finder.find(doctest_aliases)
|
||
|
>>> print(len(tests))
|
||
|
2
|
||
|
>>> print(tests[0].name)
|
||
|
test.doctest_aliases.TwoNames
|
||
|
|
||
|
TwoNames.f and TwoNames.g are bound to the same object.
|
||
|
We can't guess which will be found in doctest's traversal of
|
||
|
TwoNames.__dict__ first, so we have to allow for either.
|
||
|
|
||
|
>>> tests[1].name.split('.')[-1] in ['f', 'g']
|
||
|
True
|
||
|
|
||
|
Empty Tests
|
||
|
~~~~~~~~~~~
|
||
|
By default, an object with no doctests doesn't create any tests:
|
||
|
|
||
|
>>> tests = doctest.DocTestFinder().find(SampleClass)
|
||
|
>>> for t in tests:
|
||
|
... print('%2s %s' % (len(t.examples), t.name))
|
||
|
3 SampleClass
|
||
|
3 SampleClass.NestedClass
|
||
|
1 SampleClass.NestedClass.__init__
|
||
|
1 SampleClass.__init__
|
||
|
2 SampleClass.a_classmethod
|
||
|
1 SampleClass.a_classmethod_property
|
||
|
1 SampleClass.a_property
|
||
|
1 SampleClass.a_staticmethod
|
||
|
1 SampleClass.double
|
||
|
1 SampleClass.get
|
||
|
|
||
|
By default, that excluded objects with no doctests. exclude_empty=False
|
||
|
tells it to include (empty) tests for objects with no doctests. This feature
|
||
|
is really to support backward compatibility in what doctest.master.summarize()
|
||
|
displays.
|
||
|
|
||
|
>>> tests = doctest.DocTestFinder(exclude_empty=False).find(SampleClass)
|
||
|
>>> for t in tests:
|
||
|
... print('%2s %s' % (len(t.examples), t.name))
|
||
|
3 SampleClass
|
||
|
3 SampleClass.NestedClass
|
||
|
1 SampleClass.NestedClass.__init__
|
||
|
0 SampleClass.NestedClass.get
|
||
|
0 SampleClass.NestedClass.square
|
||
|
1 SampleClass.__init__
|
||
|
2 SampleClass.a_classmethod
|
||
|
1 SampleClass.a_classmethod_property
|
||
|
1 SampleClass.a_property
|
||
|
1 SampleClass.a_staticmethod
|
||
|
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
|
||
|
using the `recurse` flag:
|
||
|
|
||
|
>>> tests = doctest.DocTestFinder(recurse=False).find(SampleClass)
|
||
|
>>> for t in tests:
|
||
|
... print('%2s %s' % (len(t.examples), t.name))
|
||
|
3 SampleClass
|
||
|
|
||
|
Line numbers
|
||
|
~~~~~~~~~~~~
|
||
|
DocTestFinder finds the line number of each example:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '''
|
||
|
... >>> x = 12
|
||
|
...
|
||
|
... some text
|
||
|
...
|
||
|
... >>> # examples are not created for comments & bare prompts.
|
||
|
... >>>
|
||
|
... ...
|
||
|
...
|
||
|
... >>> for x in range(10):
|
||
|
... ... print(x, end=' ')
|
||
|
... 0 1 2 3 4 5 6 7 8 9
|
||
|
... >>> x//2
|
||
|
... 6
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> [e.lineno for e in test.examples]
|
||
|
[1, 9, 12]
|
||
|
"""
|
||
|
|
||
|
if int.__doc__: # simple check for --without-doc-strings, skip if lacking
|
||
|
def non_Python_modules(): r"""
|
||
|
|
||
|
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)
|
||
|
>>> 816 < len(tests) < 836 # approximate number of objects with docstrings
|
||
|
True
|
||
|
>>> real_tests = [t for t in tests if len(t.examples) > 0]
|
||
|
>>> len(real_tests) # objects that actually have doctests
|
||
|
14
|
||
|
>>> for t in real_tests:
|
||
|
... print('{} {}'.format(len(t.examples), t.name))
|
||
|
...
|
||
|
1 builtins.bin
|
||
|
5 builtins.bytearray.hex
|
||
|
5 builtins.bytes.hex
|
||
|
3 builtins.float.as_integer_ratio
|
||
|
2 builtins.float.fromhex
|
||
|
2 builtins.float.hex
|
||
|
1 builtins.hex
|
||
|
1 builtins.int
|
||
|
3 builtins.int.as_integer_ratio
|
||
|
2 builtins.int.bit_count
|
||
|
2 builtins.int.bit_length
|
||
|
5 builtins.memoryview.hex
|
||
|
1 builtins.oct
|
||
|
1 builtins.zip
|
||
|
|
||
|
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.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class TestDocTestFinder(unittest.TestCase):
|
||
|
|
||
|
def test_issue35753(self):
|
||
|
# This import of `call` should trigger issue35753 when
|
||
|
# `support.run_doctest` is called due to unwrap failing,
|
||
|
# however with a patched doctest this should succeed.
|
||
|
from unittest.mock import call
|
||
|
dummy_module = types.ModuleType("dummy")
|
||
|
dummy_module.__dict__['inject_call'] = call
|
||
|
try:
|
||
|
support.run_doctest(dummy_module, verbosity=True)
|
||
|
except ValueError as e:
|
||
|
raise support.TestFailed("Doctest unwrap failed") from e
|
||
|
|
||
|
def test_empty_namespace_package(self):
|
||
|
pkg_name = 'doctest_empty_pkg'
|
||
|
with tempfile.TemporaryDirectory() as parent_dir:
|
||
|
pkg_dir = os.path.join(parent_dir, pkg_name)
|
||
|
os.mkdir(pkg_dir)
|
||
|
sys.path.append(parent_dir)
|
||
|
try:
|
||
|
mod = importlib.import_module(pkg_name)
|
||
|
finally:
|
||
|
import_helper.forget(pkg_name)
|
||
|
sys.path.pop()
|
||
|
|
||
|
include_empty_finder = doctest.DocTestFinder(exclude_empty=False)
|
||
|
exclude_empty_finder = doctest.DocTestFinder(exclude_empty=True)
|
||
|
|
||
|
self.assertEqual(len(include_empty_finder.find(mod)), 1)
|
||
|
self.assertEqual(len(exclude_empty_finder.find(mod)), 0)
|
||
|
|
||
|
def test_DocTestParser(): r"""
|
||
|
Unit tests for the `DocTestParser` class.
|
||
|
|
||
|
DocTestParser is used to parse docstrings containing doctest examples.
|
||
|
|
||
|
The `parse` method divides a docstring into examples and intervening
|
||
|
text:
|
||
|
|
||
|
>>> s = '''
|
||
|
... >>> x, y = 2, 3 # no output expected
|
||
|
... >>> if 1:
|
||
|
... ... print(x)
|
||
|
... ... print(y)
|
||
|
... 2
|
||
|
... 3
|
||
|
...
|
||
|
... Some text.
|
||
|
... >>> x+y
|
||
|
... 5
|
||
|
... '''
|
||
|
>>> parser = doctest.DocTestParser()
|
||
|
>>> for piece in parser.parse(s):
|
||
|
... if isinstance(piece, doctest.Example):
|
||
|
... print('Example:', (piece.source, piece.want, piece.lineno))
|
||
|
... else:
|
||
|
... print(' Text:', repr(piece))
|
||
|
Text: '\n'
|
||
|
Example: ('x, y = 2, 3 # no output expected\n', '', 1)
|
||
|
Text: ''
|
||
|
Example: ('if 1:\n print(x)\n print(y)\n', '2\n3\n', 2)
|
||
|
Text: '\nSome text.\n'
|
||
|
Example: ('x+y\n', '5\n', 9)
|
||
|
Text: ''
|
||
|
|
||
|
The `get_examples` method returns just the examples:
|
||
|
|
||
|
>>> for piece in parser.get_examples(s):
|
||
|
... print((piece.source, piece.want, piece.lineno))
|
||
|
('x, y = 2, 3 # no output expected\n', '', 1)
|
||
|
('if 1:\n print(x)\n print(y)\n', '2\n3\n', 2)
|
||
|
('x+y\n', '5\n', 9)
|
||
|
|
||
|
The `get_doctest` method creates a Test from the examples, along with the
|
||
|
given arguments:
|
||
|
|
||
|
>>> test = parser.get_doctest(s, {}, 'name', 'filename', lineno=5)
|
||
|
>>> (test.name, test.filename, test.lineno)
|
||
|
('name', 'filename', 5)
|
||
|
>>> for piece in test.examples:
|
||
|
... print((piece.source, piece.want, piece.lineno))
|
||
|
('x, y = 2, 3 # no output expected\n', '', 1)
|
||
|
('if 1:\n print(x)\n print(y)\n', '2\n3\n', 2)
|
||
|
('x+y\n', '5\n', 9)
|
||
|
"""
|
||
|
|
||
|
class test_DocTestRunner:
|
||
|
def basics(): r"""
|
||
|
Unit tests for the `DocTestRunner` class.
|
||
|
|
||
|
DocTestRunner is used to run DocTest test cases, and to accumulate
|
||
|
statistics. Here's a simple DocTest case we can use:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '''
|
||
|
... >>> x = 12
|
||
|
... >>> print(x)
|
||
|
... 12
|
||
|
... >>> x//2
|
||
|
... 6
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
|
||
|
The main DocTestRunner interface is the `run` method, which runs a
|
||
|
given DocTest case in a given namespace (globs). It returns a tuple
|
||
|
`(f,t)`, where `f` is the number of failed tests and `t` is the number
|
||
|
of tried tests.
|
||
|
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=3)
|
||
|
|
||
|
If any example produces incorrect output, then the test runner reports
|
||
|
the failure and proceeds to the next example:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '''
|
||
|
... >>> x = 12
|
||
|
... >>> print(x)
|
||
|
... 14
|
||
|
... >>> x//2
|
||
|
... 6
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=True).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
Trying:
|
||
|
x = 12
|
||
|
Expecting nothing
|
||
|
ok
|
||
|
Trying:
|
||
|
print(x)
|
||
|
Expecting:
|
||
|
14
|
||
|
**********************************************************************
|
||
|
File ..., line 4, in f
|
||
|
Failed example:
|
||
|
print(x)
|
||
|
Expected:
|
||
|
14
|
||
|
Got:
|
||
|
12
|
||
|
Trying:
|
||
|
x//2
|
||
|
Expecting:
|
||
|
6
|
||
|
ok
|
||
|
TestResults(failed=1, attempted=3)
|
||
|
"""
|
||
|
def verbose_flag(): r"""
|
||
|
The `verbose` flag makes the test runner generate more detailed
|
||
|
output:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '''
|
||
|
... >>> x = 12
|
||
|
... >>> print(x)
|
||
|
... 12
|
||
|
... >>> x//2
|
||
|
... 6
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
|
||
|
>>> doctest.DocTestRunner(verbose=True).run(test)
|
||
|
Trying:
|
||
|
x = 12
|
||
|
Expecting nothing
|
||
|
ok
|
||
|
Trying:
|
||
|
print(x)
|
||
|
Expecting:
|
||
|
12
|
||
|
ok
|
||
|
Trying:
|
||
|
x//2
|
||
|
Expecting:
|
||
|
6
|
||
|
ok
|
||
|
TestResults(failed=0, attempted=3)
|
||
|
|
||
|
If the `verbose` flag is unspecified, then the output will be verbose
|
||
|
iff `-v` appears in sys.argv:
|
||
|
|
||
|
>>> # Save the real sys.argv list.
|
||
|
>>> old_argv = sys.argv
|
||
|
|
||
|
>>> # If -v does not appear in sys.argv, then output isn't verbose.
|
||
|
>>> sys.argv = ['test']
|
||
|
>>> doctest.DocTestRunner().run(test)
|
||
|
TestResults(failed=0, attempted=3)
|
||
|
|
||
|
>>> # If -v does appear in sys.argv, then output is verbose.
|
||
|
>>> sys.argv = ['test', '-v']
|
||
|
>>> doctest.DocTestRunner().run(test)
|
||
|
Trying:
|
||
|
x = 12
|
||
|
Expecting nothing
|
||
|
ok
|
||
|
Trying:
|
||
|
print(x)
|
||
|
Expecting:
|
||
|
12
|
||
|
ok
|
||
|
Trying:
|
||
|
x//2
|
||
|
Expecting:
|
||
|
6
|
||
|
ok
|
||
|
TestResults(failed=0, attempted=3)
|
||
|
|
||
|
>>> # Restore sys.argv
|
||
|
>>> sys.argv = old_argv
|
||
|
|
||
|
In the remaining examples, the test runner's verbosity will be
|
||
|
explicitly set, to ensure that the test behavior is consistent.
|
||
|
"""
|
||
|
def exceptions(): r"""
|
||
|
Tests of `DocTestRunner`'s exception handling.
|
||
|
|
||
|
An expected exception is specified with a traceback message. The
|
||
|
lines between the first line and the type/value may be omitted or
|
||
|
replaced with any other string:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '''
|
||
|
... >>> x = 12
|
||
|
... >>> print(x//0)
|
||
|
... Traceback (most recent call last):
|
||
|
... ZeroDivisionError: integer division or modulo by zero
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
An example may not generate output before it raises an exception; if
|
||
|
it does, then the traceback message will not be recognized as
|
||
|
signaling an expected exception, so the example will be reported as an
|
||
|
unexpected exception:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '''
|
||
|
... >>> x = 12
|
||
|
... >>> print('pre-exception output', x//0)
|
||
|
... pre-exception output
|
||
|
... Traceback (most recent call last):
|
||
|
... ZeroDivisionError: integer division or modulo by zero
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 4, in f
|
||
|
Failed example:
|
||
|
print('pre-exception output', x//0)
|
||
|
Exception raised:
|
||
|
...
|
||
|
ZeroDivisionError: integer division or modulo by zero
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
Exception messages may contain newlines:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> raise ValueError('multi\nline\nmessage')
|
||
|
... Traceback (most recent call last):
|
||
|
... ValueError: multi
|
||
|
... line
|
||
|
... message
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
If an exception is expected, but an exception with the wrong type or
|
||
|
message is raised, then it is reported as a failure:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> raise ValueError('message')
|
||
|
... Traceback (most recent call last):
|
||
|
... ValueError: wrong message
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
raise ValueError('message')
|
||
|
Expected:
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: wrong message
|
||
|
Got:
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
ValueError: message
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
However, IGNORE_EXCEPTION_DETAIL can be used to allow a mismatch in the
|
||
|
detail:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> raise ValueError('message') #doctest: +IGNORE_EXCEPTION_DETAIL
|
||
|
... Traceback (most recent call last):
|
||
|
... ValueError: wrong message
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
IGNORE_EXCEPTION_DETAIL also ignores difference in exception formatting
|
||
|
between Python versions. For example, in Python 2.x, the module path of
|
||
|
the exception is not in the output, but this will fail under Python 3:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> from http.client import HTTPException
|
||
|
... >>> raise HTTPException('message')
|
||
|
... Traceback (most recent call last):
|
||
|
... HTTPException: message
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 4, in f
|
||
|
Failed example:
|
||
|
raise HTTPException('message')
|
||
|
Expected:
|
||
|
Traceback (most recent call last):
|
||
|
HTTPException: message
|
||
|
Got:
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
http.client.HTTPException: message
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
But in Python 3 the module path is included, and therefore a test must look
|
||
|
like the following test to succeed in Python 3. But that test will fail under
|
||
|
Python 2.
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> from http.client import HTTPException
|
||
|
... >>> raise HTTPException('message')
|
||
|
... Traceback (most recent call last):
|
||
|
... http.client.HTTPException: message
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
However, with IGNORE_EXCEPTION_DETAIL, the module name of the exception
|
||
|
(or its unexpected absence) will be ignored:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> from http.client import HTTPException
|
||
|
... >>> raise HTTPException('message') #doctest: +IGNORE_EXCEPTION_DETAIL
|
||
|
... Traceback (most recent call last):
|
||
|
... HTTPException: message
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
The module path will be completely ignored, so two different module paths will
|
||
|
still pass if IGNORE_EXCEPTION_DETAIL is given. This is intentional, so it can
|
||
|
be used when exceptions have changed module.
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> from http.client import HTTPException
|
||
|
... >>> raise HTTPException('message') #doctest: +IGNORE_EXCEPTION_DETAIL
|
||
|
... Traceback (most recent call last):
|
||
|
... foo.bar.HTTPException: message
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
But IGNORE_EXCEPTION_DETAIL does not allow a mismatch in the exception type:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> raise ValueError('message') #doctest: +IGNORE_EXCEPTION_DETAIL
|
||
|
... Traceback (most recent call last):
|
||
|
... TypeError: wrong type
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
raise ValueError('message') #doctest: +IGNORE_EXCEPTION_DETAIL
|
||
|
Expected:
|
||
|
Traceback (most recent call last):
|
||
|
TypeError: wrong type
|
||
|
Got:
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
ValueError: message
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
If the exception does not have a message, you can still use
|
||
|
IGNORE_EXCEPTION_DETAIL to normalize the modules between Python 2 and 3:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> from http.client import HTTPException
|
||
|
... >>> raise HTTPException() #doctest: +IGNORE_EXCEPTION_DETAIL
|
||
|
... Traceback (most recent call last):
|
||
|
... foo.bar.HTTPException
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
Note that a trailing colon doesn't matter either:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> from http.client import HTTPException
|
||
|
... >>> raise HTTPException() #doctest: +IGNORE_EXCEPTION_DETAIL
|
||
|
... Traceback (most recent call last):
|
||
|
... foo.bar.HTTPException:
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
If an exception is raised but not expected, then it is reported as an
|
||
|
unexpected exception:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> 1//0
|
||
|
... 0
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
1//0
|
||
|
Exception raised:
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
ZeroDivisionError: integer division or modulo by zero
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
"""
|
||
|
def displayhook(): r"""
|
||
|
Test that changing sys.displayhook doesn't matter for doctest.
|
||
|
|
||
|
>>> import sys
|
||
|
>>> orig_displayhook = sys.displayhook
|
||
|
>>> def my_displayhook(x):
|
||
|
... print('hi!')
|
||
|
>>> sys.displayhook = my_displayhook
|
||
|
>>> def f():
|
||
|
... '''
|
||
|
... >>> 3
|
||
|
... 3
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> r = doctest.DocTestRunner(verbose=False).run(test)
|
||
|
>>> post_displayhook = sys.displayhook
|
||
|
|
||
|
We need to restore sys.displayhook now, so that we'll be able to test
|
||
|
results.
|
||
|
|
||
|
>>> sys.displayhook = orig_displayhook
|
||
|
|
||
|
Ok, now we can check that everything is ok.
|
||
|
|
||
|
>>> r
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
>>> post_displayhook is my_displayhook
|
||
|
True
|
||
|
"""
|
||
|
def optionflags(): r"""
|
||
|
Tests of `DocTestRunner`'s option flag handling.
|
||
|
|
||
|
Several option flags can be used to customize the behavior of the test
|
||
|
runner. These are defined as module constants in doctest, and passed
|
||
|
to the DocTestRunner constructor (multiple constants should be ORed
|
||
|
together).
|
||
|
|
||
|
The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False
|
||
|
and 1/0:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '>>> True\n1\n'
|
||
|
|
||
|
>>> # Without the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
>>> # With the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.DONT_ACCEPT_TRUE_FOR_1
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
True
|
||
|
Expected:
|
||
|
1
|
||
|
Got:
|
||
|
True
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
The DONT_ACCEPT_BLANKLINE flag disables the match between blank lines
|
||
|
and the '<BLANKLINE>' marker:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '>>> print("a\\n\\nb")\na\n<BLANKLINE>\nb\n'
|
||
|
|
||
|
>>> # Without the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
>>> # With the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.DONT_ACCEPT_BLANKLINE
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print("a\n\nb")
|
||
|
Expected:
|
||
|
a
|
||
|
<BLANKLINE>
|
||
|
b
|
||
|
Got:
|
||
|
a
|
||
|
<BLANKLINE>
|
||
|
b
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
The NORMALIZE_WHITESPACE flag causes all sequences of whitespace to be
|
||
|
treated as equal:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '>>> print(1, 2, 3)\n 1 2\n 3'
|
||
|
|
||
|
>>> # Without the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print(1, 2, 3)
|
||
|
Expected:
|
||
|
1 2
|
||
|
3
|
||
|
Got:
|
||
|
1 2 3
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
>>> # With the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.NORMALIZE_WHITESPACE
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
An example from the docs:
|
||
|
>>> print(list(range(20))) #doctest: +NORMALIZE_WHITESPACE
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||
|
10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
|
||
|
|
||
|
The ELLIPSIS flag causes ellipsis marker ("...") in the expected
|
||
|
output to match any substring in the actual output:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... '>>> print(list(range(15)))\n[0, 1, 2, ..., 14]\n'
|
||
|
|
||
|
>>> # Without the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print(list(range(15)))
|
||
|
Expected:
|
||
|
[0, 1, 2, ..., 14]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
>>> # With the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.ELLIPSIS
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
... also matches nothing:
|
||
|
|
||
|
>>> if 1:
|
||
|
... for i in range(100):
|
||
|
... print(i**2, end=' ') #doctest: +ELLIPSIS
|
||
|
... print('!')
|
||
|
0 1...4...9 16 ... 36 49 64 ... 9801 !
|
||
|
|
||
|
... can be surprising; e.g., this test passes:
|
||
|
|
||
|
>>> if 1: #doctest: +ELLIPSIS
|
||
|
... for i in range(20):
|
||
|
... print(i, end=' ')
|
||
|
... print(20)
|
||
|
0 1 2 ...1...2...0
|
||
|
|
||
|
Examples from the docs:
|
||
|
|
||
|
>>> print(list(range(20))) # doctest:+ELLIPSIS
|
||
|
[0, 1, ..., 18, 19]
|
||
|
|
||
|
>>> print(list(range(20))) # doctest: +ELLIPSIS
|
||
|
... # doctest: +NORMALIZE_WHITESPACE
|
||
|
[0, 1, ..., 18, 19]
|
||
|
|
||
|
The SKIP flag causes an example to be skipped entirely. I.e., the
|
||
|
example is not run. It can be useful in contexts where doctest
|
||
|
examples serve as both documentation and test cases, and an example
|
||
|
should be included for documentation purposes, but should not be
|
||
|
checked (e.g., because its output is random, or depends on resources
|
||
|
which would be unavailable.) The SKIP flag can also be used for
|
||
|
'commenting out' broken examples.
|
||
|
|
||
|
>>> import unavailable_resource # doctest: +SKIP
|
||
|
>>> unavailable_resource.do_something() # doctest: +SKIP
|
||
|
>>> unavailable_resource.blow_up() # doctest: +SKIP
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
UncheckedBlowUpError: Nobody checks me.
|
||
|
|
||
|
>>> import random
|
||
|
>>> print(random.random()) # doctest: +SKIP
|
||
|
0.721216923889
|
||
|
|
||
|
The REPORT_UDIFF flag causes failures that involve multi-line expected
|
||
|
and actual outputs to be displayed using a unified diff:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> print('\n'.join('abcdefg'))
|
||
|
... a
|
||
|
... B
|
||
|
... c
|
||
|
... d
|
||
|
... f
|
||
|
... g
|
||
|
... h
|
||
|
... '''
|
||
|
|
||
|
>>> # Without the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
print('\n'.join('abcdefg'))
|
||
|
Expected:
|
||
|
a
|
||
|
B
|
||
|
c
|
||
|
d
|
||
|
f
|
||
|
g
|
||
|
h
|
||
|
Got:
|
||
|
a
|
||
|
b
|
||
|
c
|
||
|
d
|
||
|
e
|
||
|
f
|
||
|
g
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
>>> # With the flag:
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.REPORT_UDIFF
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
print('\n'.join('abcdefg'))
|
||
|
Differences (unified diff with -expected +actual):
|
||
|
@@ -1,7 +1,7 @@
|
||
|
a
|
||
|
-B
|
||
|
+b
|
||
|
c
|
||
|
d
|
||
|
+e
|
||
|
f
|
||
|
g
|
||
|
-h
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
The REPORT_CDIFF flag causes failures that involve multi-line expected
|
||
|
and actual outputs to be displayed using a context diff:
|
||
|
|
||
|
>>> # Reuse f() from the REPORT_UDIFF example, above.
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.REPORT_CDIFF
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
print('\n'.join('abcdefg'))
|
||
|
Differences (context diff with expected followed by actual):
|
||
|
***************
|
||
|
*** 1,7 ****
|
||
|
a
|
||
|
! B
|
||
|
c
|
||
|
d
|
||
|
f
|
||
|
g
|
||
|
- h
|
||
|
--- 1,7 ----
|
||
|
a
|
||
|
! b
|
||
|
c
|
||
|
d
|
||
|
+ e
|
||
|
f
|
||
|
g
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
|
||
|
The REPORT_NDIFF flag causes failures to use the difflib.Differ algorithm
|
||
|
used by the popular ndiff.py utility. This does intraline difference
|
||
|
marking, as well as interline differences.
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> print("a b c d e f g h i j k l m")
|
||
|
... a b c d e f g h i j k 1 m
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.REPORT_NDIFF
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
print("a b c d e f g h i j k l m")
|
||
|
Differences (ndiff with -expected +actual):
|
||
|
- a b c d e f g h i j k 1 m
|
||
|
? ^
|
||
|
+ a b c d e f g h i j k l m
|
||
|
? + ++ ^
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
The REPORT_ONLY_FIRST_FAILURE suppresses result output after the first
|
||
|
failing example:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> print(1) # first success
|
||
|
... 1
|
||
|
... >>> print(2) # first failure
|
||
|
... 200
|
||
|
... >>> print(3) # second failure
|
||
|
... 300
|
||
|
... >>> print(4) # second success
|
||
|
... 4
|
||
|
... >>> print(5) # third failure
|
||
|
... 500
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.REPORT_ONLY_FIRST_FAILURE
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 5, in f
|
||
|
Failed example:
|
||
|
print(2) # first failure
|
||
|
Expected:
|
||
|
200
|
||
|
Got:
|
||
|
2
|
||
|
TestResults(failed=3, attempted=5)
|
||
|
|
||
|
However, output from `report_start` is not suppressed:
|
||
|
|
||
|
>>> doctest.DocTestRunner(verbose=True, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
Trying:
|
||
|
print(1) # first success
|
||
|
Expecting:
|
||
|
1
|
||
|
ok
|
||
|
Trying:
|
||
|
print(2) # first failure
|
||
|
Expecting:
|
||
|
200
|
||
|
**********************************************************************
|
||
|
File ..., line 5, in f
|
||
|
Failed example:
|
||
|
print(2) # first failure
|
||
|
Expected:
|
||
|
200
|
||
|
Got:
|
||
|
2
|
||
|
TestResults(failed=3, attempted=5)
|
||
|
|
||
|
The FAIL_FAST flag causes the runner to exit after the first failing example,
|
||
|
so subsequent examples are not even attempted:
|
||
|
|
||
|
>>> flags = doctest.FAIL_FAST
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 5, in f
|
||
|
Failed example:
|
||
|
print(2) # first failure
|
||
|
Expected:
|
||
|
200
|
||
|
Got:
|
||
|
2
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
Specifying both FAIL_FAST and REPORT_ONLY_FIRST_FAILURE is equivalent to
|
||
|
FAIL_FAST only:
|
||
|
|
||
|
>>> flags = doctest.FAIL_FAST | doctest.REPORT_ONLY_FIRST_FAILURE
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 5, in f
|
||
|
Failed example:
|
||
|
print(2) # first failure
|
||
|
Expected:
|
||
|
200
|
||
|
Got:
|
||
|
2
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
For the purposes of both REPORT_ONLY_FIRST_FAILURE and FAIL_FAST, unexpected
|
||
|
exceptions count as failures:
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> print(1) # first success
|
||
|
... 1
|
||
|
... >>> raise ValueError(2) # first failure
|
||
|
... 200
|
||
|
... >>> print(3) # second failure
|
||
|
... 300
|
||
|
... >>> print(4) # second success
|
||
|
... 4
|
||
|
... >>> print(5) # third failure
|
||
|
... 500
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.REPORT_ONLY_FIRST_FAILURE
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 5, in f
|
||
|
Failed example:
|
||
|
raise ValueError(2) # first failure
|
||
|
Exception raised:
|
||
|
...
|
||
|
ValueError: 2
|
||
|
TestResults(failed=3, attempted=5)
|
||
|
>>> flags = doctest.FAIL_FAST
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 5, in f
|
||
|
Failed example:
|
||
|
raise ValueError(2) # first failure
|
||
|
Exception raised:
|
||
|
...
|
||
|
ValueError: 2
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
New option flags can also be registered, via register_optionflag(). Here
|
||
|
we reach into doctest's internals a bit.
|
||
|
|
||
|
>>> unlikely = "UNLIKELY_OPTION_NAME"
|
||
|
>>> unlikely in doctest.OPTIONFLAGS_BY_NAME
|
||
|
False
|
||
|
>>> new_flag_value = doctest.register_optionflag(unlikely)
|
||
|
>>> unlikely in doctest.OPTIONFLAGS_BY_NAME
|
||
|
True
|
||
|
|
||
|
Before 2.4.4/2.5, registering a name more than once erroneously created
|
||
|
more than one flag value. Here we verify that's fixed:
|
||
|
|
||
|
>>> redundant_flag_value = doctest.register_optionflag(unlikely)
|
||
|
>>> redundant_flag_value == new_flag_value
|
||
|
True
|
||
|
|
||
|
Clean up.
|
||
|
>>> del doctest.OPTIONFLAGS_BY_NAME[unlikely]
|
||
|
|
||
|
"""
|
||
|
|
||
|
def option_directives(): r"""
|
||
|
Tests of `DocTestRunner`'s option directive mechanism.
|
||
|
|
||
|
Option directives can be used to turn option flags on or off for a
|
||
|
single example. To turn an option on for an example, follow that
|
||
|
example with a comment of the form ``# doctest: +OPTION``:
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> print(list(range(10))) # should fail: no ellipsis
|
||
|
... [0, 1, ..., 9]
|
||
|
...
|
||
|
... >>> print(list(range(10))) # doctest: +ELLIPSIS
|
||
|
... [0, 1, ..., 9]
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print(list(range(10))) # should fail: no ellipsis
|
||
|
Expected:
|
||
|
[0, 1, ..., 9]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
To turn an option off for an example, follow that example with a
|
||
|
comment of the form ``# doctest: -OPTION``:
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> print(list(range(10)))
|
||
|
... [0, 1, ..., 9]
|
||
|
...
|
||
|
... >>> # should fail: no ellipsis
|
||
|
... >>> print(list(range(10))) # doctest: -ELLIPSIS
|
||
|
... [0, 1, ..., 9]
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False,
|
||
|
... optionflags=doctest.ELLIPSIS).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 6, in f
|
||
|
Failed example:
|
||
|
print(list(range(10))) # doctest: -ELLIPSIS
|
||
|
Expected:
|
||
|
[0, 1, ..., 9]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
Option directives affect only the example that they appear with; they
|
||
|
do not change the options for surrounding examples:
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> print(list(range(10))) # Should fail: no ellipsis
|
||
|
... [0, 1, ..., 9]
|
||
|
...
|
||
|
... >>> print(list(range(10))) # doctest: +ELLIPSIS
|
||
|
... [0, 1, ..., 9]
|
||
|
...
|
||
|
... >>> print(list(range(10))) # Should fail: no ellipsis
|
||
|
... [0, 1, ..., 9]
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print(list(range(10))) # Should fail: no ellipsis
|
||
|
Expected:
|
||
|
[0, 1, ..., 9]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
|
**********************************************************************
|
||
|
File ..., line 8, in f
|
||
|
Failed example:
|
||
|
print(list(range(10))) # Should fail: no ellipsis
|
||
|
Expected:
|
||
|
[0, 1, ..., 9]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
|
TestResults(failed=2, attempted=3)
|
||
|
|
||
|
Multiple options may be modified by a single option directive. They
|
||
|
may be separated by whitespace, commas, or both:
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> print(list(range(10))) # Should fail
|
||
|
... [0, 1, ..., 9]
|
||
|
... >>> print(list(range(10))) # Should succeed
|
||
|
... ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
|
||
|
... [0, 1, ..., 9]
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print(list(range(10))) # Should fail
|
||
|
Expected:
|
||
|
[0, 1, ..., 9]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> print(list(range(10))) # Should fail
|
||
|
... [0, 1, ..., 9]
|
||
|
... >>> print(list(range(10))) # Should succeed
|
||
|
... ... # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE
|
||
|
... [0, 1, ..., 9]
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print(list(range(10))) # Should fail
|
||
|
Expected:
|
||
|
[0, 1, ..., 9]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> print(list(range(10))) # Should fail
|
||
|
... [0, 1, ..., 9]
|
||
|
... >>> print(list(range(10))) # Should succeed
|
||
|
... ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||
|
... [0, 1, ..., 9]
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 2, in f
|
||
|
Failed example:
|
||
|
print(list(range(10))) # Should fail
|
||
|
Expected:
|
||
|
[0, 1, ..., 9]
|
||
|
Got:
|
||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
|
||
|
The option directive may be put on the line following the source, as
|
||
|
long as a continuation prompt is used:
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> print(list(range(10)))
|
||
|
... ... # doctest: +ELLIPSIS
|
||
|
... [0, 1, ..., 9]
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
For examples with multi-line source, the option directive may appear
|
||
|
at the end of any line:
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... >>> for x in range(10): # doctest: +ELLIPSIS
|
||
|
... ... print(' ', x, end='', sep='')
|
||
|
... 0 1 2 ... 9
|
||
|
...
|
||
|
... >>> for x in range(10):
|
||
|
... ... print(' ', x, end='', sep='') # doctest: +ELLIPSIS
|
||
|
... 0 1 2 ... 9
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
If more than one line of an example with multi-line source has an
|
||
|
option directive, then they are combined:
|
||
|
|
||
|
>>> def f(x): r'''
|
||
|
... Should fail (option directive not on the last line):
|
||
|
... >>> for x in range(10): # doctest: +ELLIPSIS
|
||
|
... ... print(x, end=' ') # doctest: +NORMALIZE_WHITESPACE
|
||
|
... 0 1 2...9
|
||
|
... '''
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
|
||
|
It is an error to have a comment of the form ``# doctest:`` that is
|
||
|
*not* followed by words of the form ``+OPTION`` or ``-OPTION``, where
|
||
|
``OPTION`` is an option that has been registered with
|
||
|
`register_option`:
|
||
|
|
||
|
>>> # Error: Option not registered
|
||
|
>>> s = '>>> print(12) #doctest: +BADOPTION'
|
||
|
>>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0)
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: line 1 of the doctest for s has an invalid option: '+BADOPTION'
|
||
|
|
||
|
>>> # Error: No + or - prefix
|
||
|
>>> s = '>>> print(12) #doctest: ELLIPSIS'
|
||
|
>>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0)
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: line 1 of the doctest for s has an invalid option: 'ELLIPSIS'
|
||
|
|
||
|
It is an error to use an option directive on a line that contains no
|
||
|
source:
|
||
|
|
||
|
>>> s = '>>> # doctest: +ELLIPSIS'
|
||
|
>>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0)
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS'
|
||
|
"""
|
||
|
|
||
|
def test_testsource(): r"""
|
||
|
Unit tests for `testsource()`.
|
||
|
|
||
|
The testsource() function takes a module and a name, finds the (first)
|
||
|
test with that name in that module, and converts it to a script. The
|
||
|
example code is converted to regular Python code. The surrounding
|
||
|
words and expected output are converted to comments:
|
||
|
|
||
|
>>> import test.test_doctest
|
||
|
>>> name = 'test.test_doctest.sample_func'
|
||
|
>>> print(doctest.testsource(test.test_doctest, name))
|
||
|
# Blah blah
|
||
|
#
|
||
|
print(sample_func(22))
|
||
|
# Expected:
|
||
|
## 44
|
||
|
#
|
||
|
# Yee ha!
|
||
|
<BLANKLINE>
|
||
|
|
||
|
>>> name = 'test.test_doctest.SampleNewStyleClass'
|
||
|
>>> print(doctest.testsource(test.test_doctest, name))
|
||
|
print('1\n2\n3')
|
||
|
# Expected:
|
||
|
## 1
|
||
|
## 2
|
||
|
## 3
|
||
|
<BLANKLINE>
|
||
|
|
||
|
>>> name = 'test.test_doctest.SampleClass.a_classmethod'
|
||
|
>>> print(doctest.testsource(test.test_doctest, name))
|
||
|
print(SampleClass.a_classmethod(10))
|
||
|
# Expected:
|
||
|
## 12
|
||
|
print(SampleClass(0).a_classmethod(10))
|
||
|
# Expected:
|
||
|
## 12
|
||
|
<BLANKLINE>
|
||
|
"""
|
||
|
|
||
|
def test_debug(): r"""
|
||
|
|
||
|
Create a docstring that we want to debug:
|
||
|
|
||
|
>>> s = '''
|
||
|
... >>> x = 12
|
||
|
... >>> print(x)
|
||
|
... 12
|
||
|
... '''
|
||
|
|
||
|
Create some fake stdin input, to feed to the debugger:
|
||
|
|
||
|
>>> real_stdin = sys.stdin
|
||
|
>>> sys.stdin = _FakeInput(['next', 'print(x)', 'continue'])
|
||
|
|
||
|
Run the debugger on the docstring, and then restore sys.stdin.
|
||
|
|
||
|
>>> try: doctest.debug_src(s)
|
||
|
... finally: sys.stdin = real_stdin
|
||
|
> <string>(1)<module>()
|
||
|
(Pdb) next
|
||
|
12
|
||
|
--Return--
|
||
|
> <string>(1)<module>()->None
|
||
|
(Pdb) print(x)
|
||
|
12
|
||
|
(Pdb) continue
|
||
|
|
||
|
"""
|
||
|
|
||
|
if not hasattr(sys, 'gettrace') or not sys.gettrace():
|
||
|
def test_pdb_set_trace():
|
||
|
"""Using pdb.set_trace from a doctest.
|
||
|
|
||
|
You can use pdb.set_trace from a doctest. To do so, you must
|
||
|
retrieve the set_trace function from the pdb module at the time
|
||
|
you use it. The doctest module changes sys.stdout so that it can
|
||
|
capture program output. It also temporarily replaces pdb.set_trace
|
||
|
with a version that restores stdout. This is necessary for you to
|
||
|
see debugger output.
|
||
|
|
||
|
>>> doc = '''
|
||
|
... >>> x = 42
|
||
|
... >>> raise Exception('clé')
|
||
|
... Traceback (most recent call last):
|
||
|
... Exception: clé
|
||
|
... >>> import pdb; pdb.set_trace()
|
||
|
... '''
|
||
|
>>> parser = doctest.DocTestParser()
|
||
|
>>> test = parser.get_doctest(doc, {}, "foo-bar@baz", "foo-bar@baz.py", 0)
|
||
|
>>> runner = doctest.DocTestRunner(verbose=False)
|
||
|
|
||
|
To demonstrate this, we'll create a fake standard input that
|
||
|
captures our debugger input:
|
||
|
|
||
|
>>> real_stdin = sys.stdin
|
||
|
>>> sys.stdin = _FakeInput([
|
||
|
... 'print(x)', # print data defined by the example
|
||
|
... 'continue', # stop debugging
|
||
|
... ''])
|
||
|
|
||
|
>>> try: runner.run(test)
|
||
|
... finally: sys.stdin = real_stdin
|
||
|
--Return--
|
||
|
> <doctest foo-bar@baz[2]>(1)<module>()->None
|
||
|
-> import pdb; pdb.set_trace()
|
||
|
(Pdb) print(x)
|
||
|
42
|
||
|
(Pdb) continue
|
||
|
TestResults(failed=0, attempted=3)
|
||
|
|
||
|
You can also put pdb.set_trace in a function called from a test:
|
||
|
|
||
|
>>> def calls_set_trace():
|
||
|
... y=2
|
||
|
... import pdb; pdb.set_trace()
|
||
|
|
||
|
>>> doc = '''
|
||
|
... >>> x=1
|
||
|
... >>> calls_set_trace()
|
||
|
... '''
|
||
|
>>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0)
|
||
|
>>> real_stdin = sys.stdin
|
||
|
>>> sys.stdin = _FakeInput([
|
||
|
... 'print(y)', # print data defined in the function
|
||
|
... 'up', # out of function
|
||
|
... 'print(x)', # print data defined by the example
|
||
|
... 'continue', # stop debugging
|
||
|
... ''])
|
||
|
|
||
|
>>> try:
|
||
|
... runner.run(test)
|
||
|
... finally:
|
||
|
... sys.stdin = real_stdin
|
||
|
--Return--
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace[7]>(3)calls_set_trace()->None
|
||
|
-> import pdb; pdb.set_trace()
|
||
|
(Pdb) print(y)
|
||
|
2
|
||
|
(Pdb) up
|
||
|
> <doctest foo-bar@baz[1]>(1)<module>()
|
||
|
-> calls_set_trace()
|
||
|
(Pdb) print(x)
|
||
|
1
|
||
|
(Pdb) continue
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
|
||
|
During interactive debugging, source code is shown, even for
|
||
|
doctest examples:
|
||
|
|
||
|
>>> doc = '''
|
||
|
... >>> def f(x):
|
||
|
... ... g(x*2)
|
||
|
... >>> def g(x):
|
||
|
... ... print(x+3)
|
||
|
... ... import pdb; pdb.set_trace()
|
||
|
... >>> f(3)
|
||
|
... '''
|
||
|
>>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0)
|
||
|
>>> real_stdin = sys.stdin
|
||
|
>>> sys.stdin = _FakeInput([
|
||
|
... 'list', # list source from example 2
|
||
|
... 'next', # return from g()
|
||
|
... 'list', # list source from example 1
|
||
|
... 'next', # return from f()
|
||
|
... 'list', # list source from example 3
|
||
|
... 'continue', # stop debugging
|
||
|
... ''])
|
||
|
>>> try: runner.run(test)
|
||
|
... finally: sys.stdin = real_stdin
|
||
|
... # doctest: +NORMALIZE_WHITESPACE
|
||
|
--Return--
|
||
|
> <doctest foo-bar@baz[1]>(3)g()->None
|
||
|
-> import pdb; pdb.set_trace()
|
||
|
(Pdb) list
|
||
|
1 def g(x):
|
||
|
2 print(x+3)
|
||
|
3 -> import pdb; pdb.set_trace()
|
||
|
[EOF]
|
||
|
(Pdb) next
|
||
|
--Return--
|
||
|
> <doctest foo-bar@baz[0]>(2)f()->None
|
||
|
-> g(x*2)
|
||
|
(Pdb) list
|
||
|
1 def f(x):
|
||
|
2 -> g(x*2)
|
||
|
[EOF]
|
||
|
(Pdb) next
|
||
|
--Return--
|
||
|
> <doctest foo-bar@baz[2]>(1)<module>()->None
|
||
|
-> f(3)
|
||
|
(Pdb) list
|
||
|
1 -> f(3)
|
||
|
[EOF]
|
||
|
(Pdb) continue
|
||
|
**********************************************************************
|
||
|
File "foo-bar@baz.py", line 7, in foo-bar@baz
|
||
|
Failed example:
|
||
|
f(3)
|
||
|
Expected nothing
|
||
|
Got:
|
||
|
9
|
||
|
TestResults(failed=1, attempted=3)
|
||
|
"""
|
||
|
|
||
|
def test_pdb_set_trace_nested():
|
||
|
"""This illustrates more-demanding use of set_trace with nested functions.
|
||
|
|
||
|
>>> class C(object):
|
||
|
... def calls_set_trace(self):
|
||
|
... y = 1
|
||
|
... import pdb; pdb.set_trace()
|
||
|
... self.f1()
|
||
|
... y = 2
|
||
|
... def f1(self):
|
||
|
... x = 1
|
||
|
... self.f2()
|
||
|
... x = 2
|
||
|
... def f2(self):
|
||
|
... z = 1
|
||
|
... z = 2
|
||
|
|
||
|
>>> calls_set_trace = C().calls_set_trace
|
||
|
|
||
|
>>> doc = '''
|
||
|
... >>> a = 1
|
||
|
... >>> calls_set_trace()
|
||
|
... '''
|
||
|
>>> parser = doctest.DocTestParser()
|
||
|
>>> runner = doctest.DocTestRunner(verbose=False)
|
||
|
>>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0)
|
||
|
>>> real_stdin = sys.stdin
|
||
|
>>> sys.stdin = _FakeInput([
|
||
|
... 'print(y)', # print data defined in the function
|
||
|
... 'step', 'step', 'step', 'step', 'step', 'step', 'print(z)',
|
||
|
... 'up', 'print(x)',
|
||
|
... 'up', 'print(y)',
|
||
|
... 'up', 'print(foo)',
|
||
|
... 'continue', # stop debugging
|
||
|
... ''])
|
||
|
|
||
|
>>> try:
|
||
|
... runner.run(test)
|
||
|
... finally:
|
||
|
... sys.stdin = real_stdin
|
||
|
... # doctest: +REPORT_NDIFF
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(5)calls_set_trace()
|
||
|
-> self.f1()
|
||
|
(Pdb) print(y)
|
||
|
1
|
||
|
(Pdb) step
|
||
|
--Call--
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(7)f1()
|
||
|
-> def f1(self):
|
||
|
(Pdb) step
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(8)f1()
|
||
|
-> x = 1
|
||
|
(Pdb) step
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(9)f1()
|
||
|
-> self.f2()
|
||
|
(Pdb) step
|
||
|
--Call--
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(11)f2()
|
||
|
-> def f2(self):
|
||
|
(Pdb) step
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(12)f2()
|
||
|
-> z = 1
|
||
|
(Pdb) step
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(13)f2()
|
||
|
-> z = 2
|
||
|
(Pdb) print(z)
|
||
|
1
|
||
|
(Pdb) up
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(9)f1()
|
||
|
-> self.f2()
|
||
|
(Pdb) print(x)
|
||
|
1
|
||
|
(Pdb) up
|
||
|
> <doctest test.test_doctest.test_pdb_set_trace_nested[0]>(5)calls_set_trace()
|
||
|
-> self.f1()
|
||
|
(Pdb) print(y)
|
||
|
1
|
||
|
(Pdb) up
|
||
|
> <doctest foo-bar@baz[1]>(1)<module>()
|
||
|
-> calls_set_trace()
|
||
|
(Pdb) print(foo)
|
||
|
*** NameError: name 'foo' is not defined
|
||
|
(Pdb) continue
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
"""
|
||
|
|
||
|
def test_DocTestSuite():
|
||
|
"""DocTestSuite creates a unittest test suite from a doctest.
|
||
|
|
||
|
We create a Suite by providing a module. A module can be provided
|
||
|
by passing a module object:
|
||
|
|
||
|
>>> import unittest
|
||
|
>>> import test.sample_doctest
|
||
|
>>> suite = doctest.DocTestSuite(test.sample_doctest)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=4>
|
||
|
|
||
|
We can also supply the module by name:
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=4>
|
||
|
|
||
|
The module need not contain any doctest examples:
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest_no_doctests')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=0 errors=0 failures=0>
|
||
|
|
||
|
The module need not contain any docstrings either:
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=0 errors=0 failures=0>
|
||
|
|
||
|
We can use the current module:
|
||
|
|
||
|
>>> suite = test.sample_doctest.test_suite()
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=4>
|
||
|
|
||
|
We can also provide a DocTestFinder:
|
||
|
|
||
|
>>> finder = doctest.DocTestFinder()
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest',
|
||
|
... test_finder=finder)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=4>
|
||
|
|
||
|
The DocTestFinder need not return any tests:
|
||
|
|
||
|
>>> finder = doctest.DocTestFinder()
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings',
|
||
|
... test_finder=finder)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=0 errors=0 failures=0>
|
||
|
|
||
|
We can supply global variables. If we pass globs, they will be
|
||
|
used instead of the module globals. Here we'll pass an empty
|
||
|
globals, triggering an extra error:
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest', globs={})
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=5>
|
||
|
|
||
|
Alternatively, we can provide extra globals. Here we'll make an
|
||
|
error go away by providing an extra global variable:
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest',
|
||
|
... extraglobs={'y': 1})
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=3>
|
||
|
|
||
|
You can pass option flags. Here we'll cause an extra error
|
||
|
by disabling the blank-line feature:
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest',
|
||
|
... optionflags=doctest.DONT_ACCEPT_BLANKLINE)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=5>
|
||
|
|
||
|
You can supply setUp and tearDown functions:
|
||
|
|
||
|
>>> def setUp(t):
|
||
|
... import test.test_doctest
|
||
|
... test.test_doctest.sillySetup = True
|
||
|
|
||
|
>>> def tearDown(t):
|
||
|
... import test.test_doctest
|
||
|
... del test.test_doctest.sillySetup
|
||
|
|
||
|
Here, we installed a silly variable that the test expects:
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest',
|
||
|
... setUp=setUp, tearDown=tearDown)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=3>
|
||
|
|
||
|
But the tearDown restores sanity:
|
||
|
|
||
|
>>> import test.test_doctest
|
||
|
>>> test.test_doctest.sillySetup
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
AttributeError: module 'test.test_doctest' has no attribute 'sillySetup'
|
||
|
|
||
|
The setUp and tearDown functions are passed test objects. Here
|
||
|
we'll use the setUp function to supply the missing variable y:
|
||
|
|
||
|
>>> def setUp(test):
|
||
|
... test.globs['y'] = 1
|
||
|
|
||
|
>>> suite = doctest.DocTestSuite('test.sample_doctest', setUp=setUp)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=3>
|
||
|
|
||
|
Here, we didn't need to use a tearDown function because we
|
||
|
modified the test globals, which are a copy of the
|
||
|
sample_doctest module dictionary. The test globals are
|
||
|
automatically cleared for us after a test.
|
||
|
"""
|
||
|
|
||
|
def test_DocFileSuite():
|
||
|
"""We can test tests found in text files using a DocFileSuite.
|
||
|
|
||
|
We create a suite by providing the names of one or more text
|
||
|
files that include examples:
|
||
|
|
||
|
>>> import unittest
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... 'test_doctest2.txt',
|
||
|
... 'test_doctest4.txt')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=3 errors=0 failures=2>
|
||
|
|
||
|
The test files are looked for in the directory containing the
|
||
|
calling module. A package keyword argument can be provided to
|
||
|
specify a different relative location.
|
||
|
|
||
|
>>> import unittest
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... 'test_doctest2.txt',
|
||
|
... 'test_doctest4.txt',
|
||
|
... package='test')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=3 errors=0 failures=2>
|
||
|
|
||
|
Support for using a package's __loader__.get_data() is also
|
||
|
provided.
|
||
|
|
||
|
>>> import unittest, pkgutil, test
|
||
|
>>> added_loader = False
|
||
|
>>> if not hasattr(test, '__loader__'):
|
||
|
... test.__loader__ = pkgutil.get_loader(test)
|
||
|
... added_loader = True
|
||
|
>>> try:
|
||
|
... suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... 'test_doctest2.txt',
|
||
|
... 'test_doctest4.txt',
|
||
|
... package='test')
|
||
|
... suite.run(unittest.TestResult())
|
||
|
... finally:
|
||
|
... if added_loader:
|
||
|
... del test.__loader__
|
||
|
<unittest.result.TestResult run=3 errors=0 failures=2>
|
||
|
|
||
|
'/' should be used as a path separator. It will be converted
|
||
|
to a native separator at run time:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('../test/test_doctest.txt')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=1 errors=0 failures=1>
|
||
|
|
||
|
If DocFileSuite is used from an interactive session, then files
|
||
|
are resolved relative to the directory of sys.argv[0]:
|
||
|
|
||
|
>>> import types, os.path, test.test_doctest
|
||
|
>>> save_argv = sys.argv
|
||
|
>>> sys.argv = [test.test_doctest.__file__]
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... package=types.ModuleType('__main__'))
|
||
|
>>> sys.argv = save_argv
|
||
|
|
||
|
By setting `module_relative=False`, os-specific paths may be
|
||
|
used (including absolute paths and paths relative to the
|
||
|
working directory):
|
||
|
|
||
|
>>> # Get the absolute path of the test package.
|
||
|
>>> test_doctest_path = os.path.abspath(test.test_doctest.__file__)
|
||
|
>>> test_pkg_path = os.path.split(test_doctest_path)[0]
|
||
|
|
||
|
>>> # Use it to find the absolute path of test_doctest.txt.
|
||
|
>>> test_file = os.path.join(test_pkg_path, 'test_doctest.txt')
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite(test_file, module_relative=False)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=1 errors=0 failures=1>
|
||
|
|
||
|
It is an error to specify `package` when `module_relative=False`:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite(test_file, module_relative=False,
|
||
|
... package='test')
|
||
|
Traceback (most recent call last):
|
||
|
ValueError: Package may only be specified for module-relative paths.
|
||
|
|
||
|
You can specify initial global variables:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... 'test_doctest2.txt',
|
||
|
... 'test_doctest4.txt',
|
||
|
... globs={'favorite_color': 'blue'})
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=3 errors=0 failures=1>
|
||
|
|
||
|
In this case, we supplied a missing favorite color. You can
|
||
|
provide doctest options:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... 'test_doctest2.txt',
|
||
|
... 'test_doctest4.txt',
|
||
|
... optionflags=doctest.DONT_ACCEPT_BLANKLINE,
|
||
|
... globs={'favorite_color': 'blue'})
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=3 errors=0 failures=2>
|
||
|
|
||
|
And, you can provide setUp and tearDown functions:
|
||
|
|
||
|
>>> def setUp(t):
|
||
|
... import test.test_doctest
|
||
|
... test.test_doctest.sillySetup = True
|
||
|
|
||
|
>>> def tearDown(t):
|
||
|
... import test.test_doctest
|
||
|
... del test.test_doctest.sillySetup
|
||
|
|
||
|
Here, we installed a silly variable that the test expects:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... 'test_doctest2.txt',
|
||
|
... 'test_doctest4.txt',
|
||
|
... setUp=setUp, tearDown=tearDown)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=3 errors=0 failures=1>
|
||
|
|
||
|
But the tearDown restores sanity:
|
||
|
|
||
|
>>> import test.test_doctest
|
||
|
>>> test.test_doctest.sillySetup
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
AttributeError: module 'test.test_doctest' has no attribute 'sillySetup'
|
||
|
|
||
|
The setUp and tearDown functions are passed test objects.
|
||
|
Here, we'll use a setUp function to set the favorite color in
|
||
|
test_doctest.txt:
|
||
|
|
||
|
>>> def setUp(test):
|
||
|
... test.globs['favorite_color'] = 'blue'
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=1 errors=0 failures=0>
|
||
|
|
||
|
Here, we didn't need to use a tearDown function because we
|
||
|
modified the test globals. The test globals are
|
||
|
automatically cleared for us after a test.
|
||
|
|
||
|
Tests in a file run using `DocFileSuite` can also access the
|
||
|
`__file__` global, which is set to the name of the file
|
||
|
containing the tests:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest3.txt')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=1 errors=0 failures=0>
|
||
|
|
||
|
If the tests contain non-ASCII characters, we have to specify which
|
||
|
encoding the file is encoded with. We do so by using the `encoding`
|
||
|
parameter:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... 'test_doctest2.txt',
|
||
|
... 'test_doctest4.txt',
|
||
|
... encoding='utf-8')
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=3 errors=0 failures=2>
|
||
|
|
||
|
"""
|
||
|
|
||
|
def test_trailing_space_in_test():
|
||
|
"""
|
||
|
Trailing spaces in expected output are significant:
|
||
|
|
||
|
>>> x, y = 'foo', ''
|
||
|
>>> print(x, y)
|
||
|
foo \n
|
||
|
"""
|
||
|
|
||
|
class Wrapper:
|
||
|
def __init__(self, func):
|
||
|
self.func = func
|
||
|
functools.update_wrapper(self, func)
|
||
|
|
||
|
def __call__(self, *args, **kwargs):
|
||
|
self.func(*args, **kwargs)
|
||
|
|
||
|
@Wrapper
|
||
|
def test_look_in_unwrapped():
|
||
|
"""
|
||
|
Docstrings in wrapped functions must be detected as well.
|
||
|
|
||
|
>>> 'one other test'
|
||
|
'one other test'
|
||
|
"""
|
||
|
|
||
|
def test_unittest_reportflags():
|
||
|
"""Default unittest reporting flags can be set to control reporting
|
||
|
|
||
|
Here, we'll set the REPORT_ONLY_FIRST_FAILURE option so we see
|
||
|
only the first failure of each test. First, we'll look at the
|
||
|
output without the flag. The file test_doctest.txt file has two
|
||
|
tests. They both fail if blank lines are disabled:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... optionflags=doctest.DONT_ACCEPT_BLANKLINE)
|
||
|
>>> import unittest
|
||
|
>>> result = suite.run(unittest.TestResult())
|
||
|
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS
|
||
|
Traceback ...
|
||
|
Failed example:
|
||
|
favorite_color
|
||
|
...
|
||
|
Failed example:
|
||
|
if 1:
|
||
|
...
|
||
|
|
||
|
Note that we see both failures displayed.
|
||
|
|
||
|
>>> old = doctest.set_unittest_reportflags(
|
||
|
... doctest.REPORT_ONLY_FIRST_FAILURE)
|
||
|
|
||
|
Now, when we run the test:
|
||
|
|
||
|
>>> result = suite.run(unittest.TestResult())
|
||
|
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS
|
||
|
Traceback ...
|
||
|
Failed example:
|
||
|
favorite_color
|
||
|
Exception raised:
|
||
|
...
|
||
|
NameError: name 'favorite_color' is not defined
|
||
|
<BLANKLINE>
|
||
|
<BLANKLINE>
|
||
|
|
||
|
We get only the first failure.
|
||
|
|
||
|
If we give any reporting options when we set up the tests,
|
||
|
however:
|
||
|
|
||
|
>>> suite = doctest.DocFileSuite('test_doctest.txt',
|
||
|
... optionflags=doctest.DONT_ACCEPT_BLANKLINE | doctest.REPORT_NDIFF)
|
||
|
|
||
|
Then the default eporting options are ignored:
|
||
|
|
||
|
>>> result = suite.run(unittest.TestResult())
|
||
|
|
||
|
*NOTE*: These doctest are intentionally not placed in raw string to depict
|
||
|
the trailing whitespace using `\x20` in the diff below.
|
||
|
|
||
|
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS
|
||
|
Traceback ...
|
||
|
Failed example:
|
||
|
favorite_color
|
||
|
...
|
||
|
Failed example:
|
||
|
if 1:
|
||
|
print('a')
|
||
|
print()
|
||
|
print('b')
|
||
|
Differences (ndiff with -expected +actual):
|
||
|
a
|
||
|
- <BLANKLINE>
|
||
|
+\x20
|
||
|
b
|
||
|
<BLANKLINE>
|
||
|
<BLANKLINE>
|
||
|
|
||
|
|
||
|
Test runners can restore the formatting flags after they run:
|
||
|
|
||
|
>>> ignored = doctest.set_unittest_reportflags(old)
|
||
|
|
||
|
"""
|
||
|
|
||
|
def test_testfile(): r"""
|
||
|
Tests for the `testfile()` function. This function runs all the
|
||
|
doctest examples in a given file. In its simple invocation, it is
|
||
|
called with the name of a file, which is taken to be relative to the
|
||
|
calling module. The return value is (#failures, #tests).
|
||
|
|
||
|
We don't want `-v` in sys.argv for these tests.
|
||
|
|
||
|
>>> save_argv = sys.argv
|
||
|
>>> if '-v' in sys.argv:
|
||
|
... sys.argv = [arg for arg in save_argv if arg != '-v']
|
||
|
|
||
|
|
||
|
>>> doctest.testfile('test_doctest.txt') # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "...", line 6, in test_doctest.txt
|
||
|
Failed example:
|
||
|
favorite_color
|
||
|
Exception raised:
|
||
|
...
|
||
|
NameError: name 'favorite_color' is not defined
|
||
|
**********************************************************************
|
||
|
1 items had failures:
|
||
|
1 of 2 in test_doctest.txt
|
||
|
***Test Failed*** 1 failures.
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
(Note: we'll be clearing doctest.master after each call to
|
||
|
`doctest.testfile`, to suppress warnings about multiple tests with the
|
||
|
same name.)
|
||
|
|
||
|
Globals may be specified with the `globs` and `extraglobs` parameters:
|
||
|
|
||
|
>>> globs = {'favorite_color': 'blue'}
|
||
|
>>> doctest.testfile('test_doctest.txt', globs=globs)
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
>>> extraglobs = {'favorite_color': 'red'}
|
||
|
>>> doctest.testfile('test_doctest.txt', globs=globs,
|
||
|
... extraglobs=extraglobs) # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "...", line 6, in test_doctest.txt
|
||
|
Failed example:
|
||
|
favorite_color
|
||
|
Expected:
|
||
|
'blue'
|
||
|
Got:
|
||
|
'red'
|
||
|
**********************************************************************
|
||
|
1 items had failures:
|
||
|
1 of 2 in test_doctest.txt
|
||
|
***Test Failed*** 1 failures.
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
The file may be made relative to a given module or package, using the
|
||
|
optional `module_relative` parameter:
|
||
|
|
||
|
>>> doctest.testfile('test_doctest.txt', globs=globs,
|
||
|
... module_relative='test')
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
Verbosity can be increased with the optional `verbose` parameter:
|
||
|
|
||
|
>>> doctest.testfile('test_doctest.txt', globs=globs, verbose=True)
|
||
|
Trying:
|
||
|
favorite_color
|
||
|
Expecting:
|
||
|
'blue'
|
||
|
ok
|
||
|
Trying:
|
||
|
if 1:
|
||
|
print('a')
|
||
|
print()
|
||
|
print('b')
|
||
|
Expecting:
|
||
|
a
|
||
|
<BLANKLINE>
|
||
|
b
|
||
|
ok
|
||
|
1 items passed all tests:
|
||
|
2 tests in test_doctest.txt
|
||
|
2 tests in 1 items.
|
||
|
2 passed and 0 failed.
|
||
|
Test passed.
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
The name of the test may be specified with the optional `name`
|
||
|
parameter:
|
||
|
|
||
|
>>> doctest.testfile('test_doctest.txt', name='newname')
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "...", line 6, in newname
|
||
|
...
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
The summary report may be suppressed with the optional `report`
|
||
|
parameter:
|
||
|
|
||
|
>>> doctest.testfile('test_doctest.txt', report=False)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "...", line 6, in test_doctest.txt
|
||
|
Failed example:
|
||
|
favorite_color
|
||
|
Exception raised:
|
||
|
...
|
||
|
NameError: name 'favorite_color' is not defined
|
||
|
TestResults(failed=1, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
The optional keyword argument `raise_on_error` can be used to raise an
|
||
|
exception on the first error (which may be useful for postmortem
|
||
|
debugging):
|
||
|
|
||
|
>>> doctest.testfile('test_doctest.txt', raise_on_error=True)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
Traceback (most recent call last):
|
||
|
doctest.UnexpectedException: ...
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
If the tests contain non-ASCII characters, the tests might fail, since
|
||
|
it's unknown which encoding is used. The encoding can be specified
|
||
|
using the optional keyword argument `encoding`:
|
||
|
|
||
|
>>> doctest.testfile('test_doctest4.txt', encoding='latin-1') # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "...", line 7, in test_doctest4.txt
|
||
|
Failed example:
|
||
|
'...'
|
||
|
Expected:
|
||
|
'f\xf6\xf6'
|
||
|
Got:
|
||
|
'f\xc3\xb6\xc3\xb6'
|
||
|
**********************************************************************
|
||
|
...
|
||
|
**********************************************************************
|
||
|
1 items had failures:
|
||
|
2 of 2 in test_doctest4.txt
|
||
|
***Test Failed*** 2 failures.
|
||
|
TestResults(failed=2, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
>>> doctest.testfile('test_doctest4.txt', encoding='utf-8')
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
|
||
|
Test the verbose output:
|
||
|
|
||
|
>>> doctest.testfile('test_doctest4.txt', encoding='utf-8', verbose=True)
|
||
|
Trying:
|
||
|
'föö'
|
||
|
Expecting:
|
||
|
'f\xf6\xf6'
|
||
|
ok
|
||
|
Trying:
|
||
|
'bąr'
|
||
|
Expecting:
|
||
|
'b\u0105r'
|
||
|
ok
|
||
|
1 items passed all tests:
|
||
|
2 tests in test_doctest4.txt
|
||
|
2 tests in 1 items.
|
||
|
2 passed and 0 failed.
|
||
|
Test passed.
|
||
|
TestResults(failed=0, attempted=2)
|
||
|
>>> doctest.master = None # Reset master.
|
||
|
>>> sys.argv = save_argv
|
||
|
"""
|
||
|
|
||
|
class TestImporter(importlib.abc.MetaPathFinder, importlib.abc.ResourceLoader):
|
||
|
|
||
|
def find_spec(self, fullname, path, target=None):
|
||
|
return importlib.util.spec_from_file_location(fullname, path, loader=self)
|
||
|
|
||
|
def get_data(self, path):
|
||
|
with open(path, mode='rb') as f:
|
||
|
return f.read()
|
||
|
|
||
|
class TestHook:
|
||
|
|
||
|
def __init__(self, pathdir):
|
||
|
self.sys_path = sys.path[:]
|
||
|
self.meta_path = sys.meta_path[:]
|
||
|
self.path_hooks = sys.path_hooks[:]
|
||
|
sys.path.append(pathdir)
|
||
|
sys.path_importer_cache.clear()
|
||
|
self.modules_before = sys.modules.copy()
|
||
|
self.importer = TestImporter()
|
||
|
sys.meta_path.append(self.importer)
|
||
|
|
||
|
def remove(self):
|
||
|
sys.path[:] = self.sys_path
|
||
|
sys.meta_path[:] = self.meta_path
|
||
|
sys.path_hooks[:] = self.path_hooks
|
||
|
sys.path_importer_cache.clear()
|
||
|
sys.modules.clear()
|
||
|
sys.modules.update(self.modules_before)
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def test_hook(pathdir):
|
||
|
hook = TestHook(pathdir)
|
||
|
try:
|
||
|
yield hook
|
||
|
finally:
|
||
|
hook.remove()
|
||
|
|
||
|
|
||
|
def test_lineendings(): r"""
|
||
|
*nix systems use \n line endings, while Windows systems use \r\n, and
|
||
|
old Mac systems used \r, which Python still recognizes as a line ending. Python
|
||
|
handles this using universal newline mode for reading files. Let's make
|
||
|
sure doctest does so (issue 8473) by creating temporary test files using each
|
||
|
of the three line disciplines. At least one will not match either the universal
|
||
|
newline \n or os.linesep for the platform the test is run on.
|
||
|
|
||
|
Windows line endings first:
|
||
|
|
||
|
>>> import tempfile, os
|
||
|
>>> fn = tempfile.mktemp()
|
||
|
>>> with open(fn, 'wb') as f:
|
||
|
... f.write(b'Test:\r\n\r\n >>> x = 1 + 1\r\n\r\nDone.\r\n')
|
||
|
35
|
||
|
>>> doctest.testfile(fn, module_relative=False, verbose=False)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
>>> os.remove(fn)
|
||
|
|
||
|
And now *nix line endings:
|
||
|
|
||
|
>>> fn = tempfile.mktemp()
|
||
|
>>> with open(fn, 'wb') as f:
|
||
|
... f.write(b'Test:\n\n >>> x = 1 + 1\n\nDone.\n')
|
||
|
30
|
||
|
>>> doctest.testfile(fn, module_relative=False, verbose=False)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
>>> os.remove(fn)
|
||
|
|
||
|
And finally old Mac line endings:
|
||
|
|
||
|
>>> fn = tempfile.mktemp()
|
||
|
>>> with open(fn, 'wb') as f:
|
||
|
... f.write(b'Test:\r\r >>> x = 1 + 1\r\rDone.\r')
|
||
|
30
|
||
|
>>> doctest.testfile(fn, module_relative=False, verbose=False)
|
||
|
TestResults(failed=0, attempted=1)
|
||
|
>>> os.remove(fn)
|
||
|
|
||
|
Now we test with a package loader that has a get_data method, since that
|
||
|
bypasses the standard universal newline handling so doctest has to do the
|
||
|
newline conversion itself; let's make sure it does so correctly (issue 1812).
|
||
|
We'll write a file inside the package that has all three kinds of line endings
|
||
|
in it, and use a package hook to install a custom loader; on any platform,
|
||
|
at least one of the line endings will raise a ValueError for inconsistent
|
||
|
whitespace if doctest does not correctly do the newline conversion.
|
||
|
|
||
|
>>> dn = tempfile.mkdtemp()
|
||
|
>>> pkg = os.path.join(dn, "doctest_testpkg")
|
||
|
>>> os.mkdir(pkg)
|
||
|
>>> os_helper.create_empty_file(os.path.join(pkg, "__init__.py"))
|
||
|
>>> fn = os.path.join(pkg, "doctest_testfile.txt")
|
||
|
>>> with open(fn, 'wb') as f:
|
||
|
... f.write(
|
||
|
... b'Test:\r\n\r\n'
|
||
|
... b' >>> x = 1 + 1\r\n\r\n'
|
||
|
... b'Done.\r\n'
|
||
|
... b'Test:\n\n'
|
||
|
... b' >>> x = 1 + 1\n\n'
|
||
|
... b'Done.\n'
|
||
|
... b'Test:\r\r'
|
||
|
... b' >>> x = 1 + 1\r\r'
|
||
|
... b'Done.\r'
|
||
|
... )
|
||
|
95
|
||
|
>>> with test_hook(dn):
|
||
|
... doctest.testfile("doctest_testfile.txt", package="doctest_testpkg", verbose=False)
|
||
|
TestResults(failed=0, attempted=3)
|
||
|
>>> shutil.rmtree(dn)
|
||
|
|
||
|
"""
|
||
|
|
||
|
def test_testmod(): r"""
|
||
|
Tests for the testmod function. More might be useful, but for now we're just
|
||
|
testing the case raised by Issue 6195, where trying to doctest a C module would
|
||
|
fail with a UnicodeDecodeError because doctest tried to read the "source" lines
|
||
|
out of the binary module.
|
||
|
|
||
|
>>> import unicodedata
|
||
|
>>> doctest.testmod(unicodedata, verbose=False)
|
||
|
TestResults(failed=0, attempted=0)
|
||
|
"""
|
||
|
|
||
|
try:
|
||
|
os.fsencode("foo-bär@baz.py")
|
||
|
except UnicodeEncodeError:
|
||
|
# Skip the test: the filesystem encoding is unable to encode the filename
|
||
|
pass
|
||
|
else:
|
||
|
def test_unicode(): """
|
||
|
Check doctest with a non-ascii filename:
|
||
|
|
||
|
>>> doc = '''
|
||
|
... >>> raise Exception('clé')
|
||
|
... '''
|
||
|
...
|
||
|
>>> parser = doctest.DocTestParser()
|
||
|
>>> test = parser.get_doctest(doc, {}, "foo-bär@baz", "foo-bär@baz.py", 0)
|
||
|
>>> test
|
||
|
<DocTest foo-bär@baz from foo-bär@baz.py:0 (1 example)>
|
||
|
>>> runner = doctest.DocTestRunner(verbose=False)
|
||
|
>>> runner.run(test) # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "foo-bär@baz.py", line 2, in foo-bär@baz
|
||
|
Failed example:
|
||
|
raise Exception('clé')
|
||
|
Exception raised:
|
||
|
Traceback (most recent call last):
|
||
|
File ...
|
||
|
exec(compile(example.source, filename, "single",
|
||
|
File "<doctest foo-bär@baz[0]>", line 1, in <module>
|
||
|
raise Exception('clé')
|
||
|
Exception: clé
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
"""
|
||
|
|
||
|
def test_CLI(): r"""
|
||
|
The doctest module can be used to run doctests against an arbitrary file.
|
||
|
These tests test this CLI functionality.
|
||
|
|
||
|
We'll use the support module's script_helpers for this, and write a test files
|
||
|
to a temp dir to run the command against. Due to a current limitation in
|
||
|
script_helpers, though, we need a little utility function to turn the returned
|
||
|
output into something we can doctest against:
|
||
|
|
||
|
>>> def normalize(s):
|
||
|
... return '\n'.join(s.decode().splitlines())
|
||
|
|
||
|
With those preliminaries out of the way, we'll start with a file with two
|
||
|
simple tests and no errors. We'll run both the unadorned doctest command, and
|
||
|
the verbose version, and then check the output:
|
||
|
|
||
|
>>> from test.support import script_helper
|
||
|
>>> from test.support.os_helper import temp_dir
|
||
|
>>> with temp_dir() as tmpdir:
|
||
|
... fn = os.path.join(tmpdir, 'myfile.doc')
|
||
|
... with open(fn, 'w', encoding='utf-8') as f:
|
||
|
... _ = f.write('This is a very simple test file.\n')
|
||
|
... _ = f.write(' >>> 1 + 1\n')
|
||
|
... _ = f.write(' 2\n')
|
||
|
... _ = f.write(' >>> "a"\n')
|
||
|
... _ = f.write(" 'a'\n")
|
||
|
... _ = f.write('\n')
|
||
|
... _ = f.write('And that is it.\n')
|
||
|
... rc1, out1, err1 = script_helper.assert_python_ok(
|
||
|
... '-m', 'doctest', fn)
|
||
|
... rc2, out2, err2 = script_helper.assert_python_ok(
|
||
|
... '-m', 'doctest', '-v', fn)
|
||
|
|
||
|
With no arguments and passing tests, we should get no output:
|
||
|
|
||
|
>>> rc1, out1, err1
|
||
|
(0, b'', b'')
|
||
|
|
||
|
With the verbose flag, we should see the test output, but no error output:
|
||
|
|
||
|
>>> rc2, err2
|
||
|
(0, b'')
|
||
|
>>> print(normalize(out2))
|
||
|
Trying:
|
||
|
1 + 1
|
||
|
Expecting:
|
||
|
2
|
||
|
ok
|
||
|
Trying:
|
||
|
"a"
|
||
|
Expecting:
|
||
|
'a'
|
||
|
ok
|
||
|
1 items passed all tests:
|
||
|
2 tests in myfile.doc
|
||
|
2 tests in 1 items.
|
||
|
2 passed and 0 failed.
|
||
|
Test passed.
|
||
|
|
||
|
Now we'll write a couple files, one with three tests, the other a python module
|
||
|
with two tests, both of the files having "errors" in the tests that can be made
|
||
|
non-errors by applying the appropriate doctest options to the run (ELLIPSIS in
|
||
|
the first file, NORMALIZE_WHITESPACE in the second). This combination will
|
||
|
allow thoroughly testing the -f and -o flags, as well as the doctest command's
|
||
|
ability to process more than one file on the command line and, since the second
|
||
|
file ends in '.py', its handling of python module files (as opposed to straight
|
||
|
text files).
|
||
|
|
||
|
>>> from test.support import script_helper
|
||
|
>>> from test.support.os_helper import temp_dir
|
||
|
>>> with temp_dir() as tmpdir:
|
||
|
... fn = os.path.join(tmpdir, 'myfile.doc')
|
||
|
... with open(fn, 'w', encoding="utf-8") as f:
|
||
|
... _ = f.write('This is another simple test file.\n')
|
||
|
... _ = f.write(' >>> 1 + 1\n')
|
||
|
... _ = f.write(' 2\n')
|
||
|
... _ = f.write(' >>> "abcdef"\n')
|
||
|
... _ = f.write(" 'a...f'\n")
|
||
|
... _ = f.write(' >>> "ajkml"\n')
|
||
|
... _ = f.write(" 'a...l'\n")
|
||
|
... _ = f.write('\n')
|
||
|
... _ = f.write('And that is it.\n')
|
||
|
... fn2 = os.path.join(tmpdir, 'myfile2.py')
|
||
|
... with open(fn2, 'w', encoding='utf-8') as f:
|
||
|
... _ = f.write('def test_func():\n')
|
||
|
... _ = f.write(' \"\"\"\n')
|
||
|
... _ = f.write(' This is simple python test function.\n')
|
||
|
... _ = f.write(' >>> 1 + 1\n')
|
||
|
... _ = f.write(' 2\n')
|
||
|
... _ = f.write(' >>> "abc def"\n')
|
||
|
... _ = f.write(" 'abc def'\n")
|
||
|
... _ = f.write("\n")
|
||
|
... _ = f.write(' \"\"\"\n')
|
||
|
... rc1, out1, err1 = script_helper.assert_python_failure(
|
||
|
... '-m', 'doctest', fn, fn2)
|
||
|
... rc2, out2, err2 = script_helper.assert_python_ok(
|
||
|
... '-m', 'doctest', '-o', 'ELLIPSIS', fn)
|
||
|
... rc3, out3, err3 = script_helper.assert_python_ok(
|
||
|
... '-m', 'doctest', '-o', 'ELLIPSIS',
|
||
|
... '-o', 'NORMALIZE_WHITESPACE', fn, fn2)
|
||
|
... rc4, out4, err4 = script_helper.assert_python_failure(
|
||
|
... '-m', 'doctest', '-f', fn, fn2)
|
||
|
... rc5, out5, err5 = script_helper.assert_python_ok(
|
||
|
... '-m', 'doctest', '-v', '-o', 'ELLIPSIS',
|
||
|
... '-o', 'NORMALIZE_WHITESPACE', fn, fn2)
|
||
|
|
||
|
Our first test run will show the errors from the first file (doctest stops if a
|
||
|
file has errors). Note that doctest test-run error output appears on stdout,
|
||
|
not stderr:
|
||
|
|
||
|
>>> rc1, err1
|
||
|
(1, b'')
|
||
|
>>> print(normalize(out1)) # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "...myfile.doc", line 4, in myfile.doc
|
||
|
Failed example:
|
||
|
"abcdef"
|
||
|
Expected:
|
||
|
'a...f'
|
||
|
Got:
|
||
|
'abcdef'
|
||
|
**********************************************************************
|
||
|
File "...myfile.doc", line 6, in myfile.doc
|
||
|
Failed example:
|
||
|
"ajkml"
|
||
|
Expected:
|
||
|
'a...l'
|
||
|
Got:
|
||
|
'ajkml'
|
||
|
**********************************************************************
|
||
|
1 items had failures:
|
||
|
2 of 3 in myfile.doc
|
||
|
***Test Failed*** 2 failures.
|
||
|
|
||
|
With -o ELLIPSIS specified, the second run, against just the first file, should
|
||
|
produce no errors, and with -o NORMALIZE_WHITESPACE also specified, neither
|
||
|
should the third, which ran against both files:
|
||
|
|
||
|
>>> rc2, out2, err2
|
||
|
(0, b'', b'')
|
||
|
>>> rc3, out3, err3
|
||
|
(0, b'', b'')
|
||
|
|
||
|
The fourth run uses FAIL_FAST, so we should see only one error:
|
||
|
|
||
|
>>> rc4, err4
|
||
|
(1, b'')
|
||
|
>>> print(normalize(out4)) # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File "...myfile.doc", line 4, in myfile.doc
|
||
|
Failed example:
|
||
|
"abcdef"
|
||
|
Expected:
|
||
|
'a...f'
|
||
|
Got:
|
||
|
'abcdef'
|
||
|
**********************************************************************
|
||
|
1 items had failures:
|
||
|
1 of 2 in myfile.doc
|
||
|
***Test Failed*** 1 failures.
|
||
|
|
||
|
The fifth test uses verbose with the two options, so we should get verbose
|
||
|
success output for the tests in both files:
|
||
|
|
||
|
>>> rc5, err5
|
||
|
(0, b'')
|
||
|
>>> print(normalize(out5))
|
||
|
Trying:
|
||
|
1 + 1
|
||
|
Expecting:
|
||
|
2
|
||
|
ok
|
||
|
Trying:
|
||
|
"abcdef"
|
||
|
Expecting:
|
||
|
'a...f'
|
||
|
ok
|
||
|
Trying:
|
||
|
"ajkml"
|
||
|
Expecting:
|
||
|
'a...l'
|
||
|
ok
|
||
|
1 items passed all tests:
|
||
|
3 tests in myfile.doc
|
||
|
3 tests in 1 items.
|
||
|
3 passed and 0 failed.
|
||
|
Test passed.
|
||
|
Trying:
|
||
|
1 + 1
|
||
|
Expecting:
|
||
|
2
|
||
|
ok
|
||
|
Trying:
|
||
|
"abc def"
|
||
|
Expecting:
|
||
|
'abc def'
|
||
|
ok
|
||
|
1 items had no tests:
|
||
|
myfile2
|
||
|
1 items passed all tests:
|
||
|
2 tests in myfile2.test_func
|
||
|
2 tests in 2 items.
|
||
|
2 passed and 0 failed.
|
||
|
Test passed.
|
||
|
|
||
|
We should also check some typical error cases.
|
||
|
|
||
|
Invalid file name:
|
||
|
|
||
|
>>> rc, out, err = script_helper.assert_python_failure(
|
||
|
... '-m', 'doctest', 'nosuchfile')
|
||
|
>>> rc, out
|
||
|
(1, b'')
|
||
|
>>> # The exact error message changes depending on the platform.
|
||
|
>>> print(normalize(err)) # doctest: +ELLIPSIS
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
FileNotFoundError: [Errno ...] ...nosuchfile...
|
||
|
|
||
|
Invalid doctest option:
|
||
|
|
||
|
>>> rc, out, err = script_helper.assert_python_failure(
|
||
|
... '-m', 'doctest', '-o', 'nosuchoption')
|
||
|
>>> rc, out
|
||
|
(2, b'')
|
||
|
>>> print(normalize(err)) # doctest: +ELLIPSIS
|
||
|
usage...invalid...nosuchoption...
|
||
|
|
||
|
"""
|
||
|
|
||
|
def test_no_trailing_whitespace_stripping():
|
||
|
r"""
|
||
|
The fancy reports had a bug for a long time where any trailing whitespace on
|
||
|
the reported diff lines was stripped, making it impossible to see the
|
||
|
differences in line reported as different that differed only in the amount of
|
||
|
trailing whitespace. The whitespace still isn't particularly visible unless
|
||
|
you use NDIFF, but at least it is now there to be found.
|
||
|
|
||
|
*NOTE*: This snippet was intentionally put inside a raw string to get rid of
|
||
|
leading whitespace error in executing the example below
|
||
|
|
||
|
>>> def f(x):
|
||
|
... r'''
|
||
|
... >>> print('\n'.join(['a ', 'b']))
|
||
|
... a
|
||
|
... b
|
||
|
... '''
|
||
|
"""
|
||
|
"""
|
||
|
*NOTE*: These doctest are not placed in raw string to depict the trailing whitespace
|
||
|
using `\x20`
|
||
|
|
||
|
>>> test = doctest.DocTestFinder().find(f)[0]
|
||
|
>>> flags = doctest.REPORT_NDIFF
|
||
|
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||
|
... # doctest: +ELLIPSIS
|
||
|
**********************************************************************
|
||
|
File ..., line 3, in f
|
||
|
Failed example:
|
||
|
print('\n'.join(['a ', 'b']))
|
||
|
Differences (ndiff with -expected +actual):
|
||
|
- a
|
||
|
+ a
|
||
|
b
|
||
|
TestResults(failed=1, attempted=1)
|
||
|
|
||
|
*NOTE*: `\x20` is for checking the trailing whitespace on the +a line above.
|
||
|
We cannot use actual spaces there, as a commit hook prevents from committing
|
||
|
patches that contain trailing whitespace. More info on Issue 24746.
|
||
|
"""
|
||
|
|
||
|
|
||
|
def test_run_doctestsuite_multiple_times():
|
||
|
"""
|
||
|
It was not possible to run the same DocTestSuite multiple times
|
||
|
http://bugs.python.org/issue2604
|
||
|
http://bugs.python.org/issue9736
|
||
|
|
||
|
>>> import unittest
|
||
|
>>> import test.sample_doctest
|
||
|
>>> suite = doctest.DocTestSuite(test.sample_doctest)
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=4>
|
||
|
>>> suite.run(unittest.TestResult())
|
||
|
<unittest.result.TestResult run=9 errors=0 failures=4>
|
||
|
"""
|
||
|
|
||
|
|
||
|
def load_tests(loader, tests, pattern):
|
||
|
tests.addTest(doctest.DocTestSuite(doctest))
|
||
|
tests.addTest(doctest.DocTestSuite())
|
||
|
return tests
|
||
|
|
||
|
|
||
|
def test_coverage(coverdir):
|
||
|
trace = import_helper.import_module('trace')
|
||
|
tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
|
||
|
trace=0, count=1)
|
||
|
tracer.run('test_main()')
|
||
|
r = tracer.results()
|
||
|
print('Writing coverage results...')
|
||
|
r.write_results(show_missing=True, summary=True,
|
||
|
coverdir=coverdir)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
if '-c' in sys.argv:
|
||
|
test_coverage('/tmp/doctest.cover')
|
||
|
else:
|
||
|
unittest.main()
|