- Changed option directives to be example-specific. (i.e., they now

modify option flags for a single example; they do not turn options
  on or off.)
- Added "indent" and "options" attributes for Example
- Got rid of add_newlines param to DocTestParser._parse_example (it's
  no longer needed; Example's constructor now takes care of it).
- Added some docstrings
This commit is contained in:
Edward Loper 2004-08-12 02:27:44 +00:00
parent ac20f57c28
commit 74bca7aa44
2 changed files with 254 additions and 79 deletions

View File

@ -367,19 +367,29 @@ class Example:
A single doctest example, consisting of source code and expected
output. `Example` defines the following attributes:
- source: A single Python statement, always ending with a newline.
- source: A single Python statement, always ending with a newline.
The constructor adds a newline if needed.
- want: The expected output from running the source code (either
- want: The expected output from running the source code (either
from stdout, or a traceback in case of exception). `want` ends
with a newline unless it's empty, in which case it's an empty
string. The constructor adds a newline if needed.
- lineno: The line number within the DocTest string containing
- lineno: The line number within the DocTest string containing
this Example where the Example begins. This line number is
zero-based, with respect to the beginning of the DocTest.
- indent: The example's indentation in the DocTest string.
I.e., the number of space characters that preceed the
example's first prompt.
- options: A dictionary mapping from option flags to True or
False, which is used to override default options for this
example. Any option flags not contained in this dictionary
are left at their default value (as specified by the
DocTestRunner's optionflags). By default, no options are set.
"""
def __init__(self, source, want, lineno):
def __init__(self, source, want, lineno, indent=0, options=None):
# Normalize inputs.
if not source.endswith('\n'):
source += '\n'
@ -389,6 +399,9 @@ class Example:
self.source = source
self.want = want
self.lineno = lineno
self.indent = indent
if options is None: options = {}
self.options = options
class DocTest:
"""
@ -515,13 +528,16 @@ class DocTestParser:
for m in self._EXAMPLE_RE.finditer(string.expandtabs()):
# Update lineno (lines before this example)
lineno += string.count('\n', charno, m.start())
# Extract source/want from the regexp match.
(source, want) = self._parse_example(m, name, lineno)
# Extract extra options from the source.
options = self._find_options(source, name, lineno)
# If it contains no real source, then ignore it.
if self._IS_BLANK_OR_COMMENT(source):
continue
examples.append( Example(source, want, lineno) )
# Create an Example, and add it to the list.
examples.append( Example(source, want, lineno,
len(m.group('indent')), options) )
# Update lineno (lines inside this example)
lineno += string.count('\n', m.start(), m.end())
# Update charno.
@ -578,7 +594,7 @@ class DocTestParser:
lineno += len(lines)
# Extract source/want from the regexp match.
(source, want) = self._parse_example(m, name, lineno, False)
(source, want) = self._parse_example(m, name, lineno)
# Display the source
output.append(source)
# Display the expected output, if any
@ -600,7 +616,17 @@ class DocTestParser:
# Combine the output, and return it.
return '\n'.join(output)
def _parse_example(self, m, name, lineno, add_newlines=True):
def _parse_example(self, m, name, lineno):
"""
Given a regular expression match from `_EXAMPLE_RE` (`m`),
return a pair `(source, want)`, where `source` is the matched
example's source code (with prompts and indentation stripped);
and `want` is the example's expected output (with indentation
stripped).
`name` is the string's name, and `lineno` is the line number
where the example starts; both are used for error messages.
"""
# Get the example's indentation level.
indent = len(m.group('indent'))
@ -610,8 +636,6 @@ class DocTestParser:
self._check_prompt_blank(source_lines, indent, name, lineno)
self._check_prefix(source_lines[1:], ' '*indent+'.', name, lineno)
source = '\n'.join([sl[indent+4:] for sl in source_lines])
if len(source_lines) > 1 and add_newlines:
source += '\n'
# Divide want into lines; check that it's properly
# indented; and then strip the indentation.
@ -619,12 +643,47 @@ class DocTestParser:
self._check_prefix(want_lines, ' '*indent, name,
lineno+len(source_lines))
want = '\n'.join([wl[indent:] for wl in want_lines])
if len(want) > 0 and add_newlines:
want += '\n'
return source, want
# This regular expression looks for option directives in the
# source code of an example. Option directives are comments
# starting with "doctest:". Warning: this may give false
# positives for string-literals that contain the string
# "#doctest:". Eliminating these false positives would require
# actually parsing the string; but we limit them by ignoring any
# line containing "#doctest:" that is *followed* by a quote mark.
_OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$',
re.MULTILINE)
def _find_options(self, source, name, lineno):
"""
Return a dictionary containing option overrides extracted from
option directives in the given source string.
`name` is the string's name, and `lineno` is the line number
where the example starts; both are used for error messages.
"""
options = {}
# (note: with the current regexp, this will match at most once:)
for m in self._OPTION_DIRECTIVE_RE.finditer(source):
option_strings = m.group(1).replace(',', ' ').split()
for option in option_strings:
if (option[0] not in '+-' or
option[1:] not in OPTIONFLAGS_BY_NAME):
raise ValueError('line %r of the doctest for %s '
'has an invalid option: %r' %
(lineno+1, name, option))
flag = OPTIONFLAGS_BY_NAME[option[1:]]
options[flag] = (option[0] == '+')
if options and self._IS_BLANK_OR_COMMENT(source):
raise ValueError('line %r of the doctest for %s has an option '
'directive on a line with no example: %r' %
(lineno, name, source))
return options
def _comment_line(self, line):
"Return a commented form of the given line"
line = line.rstrip()
if line:
return '# '+line
@ -632,6 +691,12 @@ class DocTestParser:
return '#'
def _check_prompt_blank(self, lines, indent, name, lineno):
"""
Given the lines of a source string (including prompts and
leading indentation), check to make sure that every prompt is
followed by a space character. If any line is not followed by
a space character, then raise ValueError.
"""
for i, line in enumerate(lines):
if len(line) >= indent+4 and line[indent+3] != ' ':
raise ValueError('line %r of the docstring for %s '
@ -640,6 +705,10 @@ class DocTestParser:
line[indent:indent+3], line))
def _check_prefix(self, lines, prefix, name, lineno):
"""
Check that every line in the given list starts with the given
prefix; if any line does not, then raise a ValueError.
"""
for i, line in enumerate(lines):
if line and not line.startswith(prefix):
raise ValueError('line %r of the docstring for %s has '
@ -1105,32 +1174,6 @@ class DocTestRunner:
('most recent call last', 'innermost last'),
re.MULTILINE | re.DOTALL)
_OPTION_DIRECTIVE_RE = re.compile('\s*doctest:\s*(?P<flags>[^#\n]*)')
def __handle_directive(self, example):
"""
Check if the given example is actually a directive to doctest
(to turn an optionflag on or off); and if it is, then handle
the directive.
Return true iff the example is actually a directive (and so
should not be executed).
"""
m = self._OPTION_DIRECTIVE_RE.match(example.source)
if m is None:
return False
for flag in m.group('flags').upper().split():
if (flag[:1] not in '+-' or
flag[1:] not in OPTIONFLAGS_BY_NAME):
raise ValueError('Bad doctest option directive: '+flag)
if flag[0] == '+':
self.optionflags |= OPTIONFLAGS_BY_NAME[flag[1:]]
else:
self.optionflags &= ~OPTIONFLAGS_BY_NAME[flag[1:]]
return True
def __run(self, test, compileflags, out):
"""
Run the examples in `test`. Write the outcome of each example
@ -1150,10 +1193,14 @@ class DocTestRunner:
# Process each example.
for example in test.examples:
# Check if it's an option directive. If it is, then handle
# it, and go on to the next example.
if self.__handle_directive(example):
continue
# Merge in the example's options.
self.optionflags = original_optionflags
if example.options:
for (optionflag, val) in example.options.items():
if val:
self.optionflags |= optionflag
else:
self.optionflags &= ~optionflag
# Record that we started this example.
tries += 1
@ -1349,12 +1396,13 @@ class OutputChecker:
"""
def check_output(self, want, got, optionflags):
"""
Return True iff the actual output (`got`) matches the expected
output (`want`). These strings are always considered to match
if they are identical; but depending on what option flags the
test runner is using, several non-exact match types are also
possible. See the documentation for `TestRunner` for more
information about option flags.
Return True iff the actual output from an example (`got`)
matches the expected output (`want`). These strings are
always considered to match if they are identical; but
depending on what option flags the test runner is using,
several non-exact match types are also possible. See the
documentation for `TestRunner` for more information about
option flags.
"""
# Handle the common case first, for efficiency:
# if they're string-identical, always return true.
@ -1411,7 +1459,10 @@ class OutputChecker:
def output_difference(self, want, got, optionflags):
"""
Return a string describing the differences between the
expected output (`want`) and the actual output (`got`).
expected output for an example (`want`) and the actual output
(`got`). `optionflags` is the set of option flags used to
compare `want` and `got`. `indent` is the indentation of the
original example.
"""
# If <BLANKLINE>s are being used, then replace <BLANKLINE>
# with blank lines in the expected output string.

View File

@ -267,20 +267,16 @@ Finding Tests in Functions
For a function whose docstring contains examples, DocTestFinder.find()
will return a single test (for that function's docstring):
>>> # Allow ellipsis in the following examples (since the filename
>>> # and line number in the traceback can vary):
>>> doctest: +ELLIPSIS
>>> finder = doctest.DocTestFinder()
>>> tests = finder.find(sample_func)
>>> print tests
>>> print tests # doctest: +ELLIPSIS
[<DocTest sample_func from ...:12 (1 example)>]
>>> e = tests[0].examples[0]
>>> (e.source, e.want, e.lineno)
('print sample_func(22)\n', '44\n', 3)
>>> doctest: -ELLIPSIS # Turn ellipsis back off
If an object has no docstring, then a test is not created for it:
>>> def no_docstring(v):
@ -638,10 +634,6 @@ message is raised, then it is reported as a failure:
If an exception is raised but not expected, then it is reported as an
unexpected exception:
>>> # Allow ellipsis in the following examples (since the filename
>>> # and line number in the traceback can vary):
>>> doctest: +ELLIPSIS
>>> def f(x):
... r'''
... >>> 1/0
@ -649,6 +641,7 @@ unexpected exception:
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
... # doctest: +ELLIPSIS
**********************************************************************
Failure in example: 1/0
from line #1 of f
@ -657,8 +650,6 @@ unexpected exception:
...
ZeroDivisionError: integer division or modulo by zero
(1, 1)
>>> doctest: -ELLIPSIS # Turn ellipsis back off:
"""
def optionflags(): r"""
Tests of `DocTestRunner`'s option flag handling.
@ -863,20 +854,57 @@ and actual outputs to be displayed using a context diff:
def option_directives(): r"""
Tests of `DocTestRunner`'s option directive mechanism.
Option directives can be used to turn option flags on or off from
within a DocTest case. The following example shows how a flag can be
turned on and off. Note that comments on the same line as the option
directive are ignored.
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 range(10) # should fail: no ellipsis
... [0, 1, ..., 9]
...
... >>> print range(10) # doctest: +ELLIPSIS
... [0, 1, ..., 9]
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
**********************************************************************
Failure in example: print range(10) # should fail: no ellipsis
from line #1 of f
Expected: [0, 1, ..., 9]
Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(1, 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 range(10)
... [0, 1, ..., 9]
...
... >>> # should fail: no ellipsis
... >>> print range(10) # doctest: -ELLIPSIS
... [0, 1, ..., 9]
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False,
... optionflags=doctest.ELLIPSIS).run(test)
**********************************************************************
Failure in example: print range(10) # doctest: -ELLIPSIS
from line #6 of f
Expected: [0, 1, ..., 9]
Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(1, 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 range(10) # Should fail: no ellipsis
... [0, 1, ..., 9]
...
... >>> doctest: +ELLIPSIS # turn ellipsis on.
... >>> print range(10) # Should succeed
... >>> print range(10) # doctest: +ELLIPSIS
... [0, 1, ..., 9]
...
... >>> doctest: -ELLIPSIS # turn ellipsis back off.
... >>> print range(10) # Should fail: no ellipsis
... [0, 1, ..., 9]
... '''
@ -889,18 +917,19 @@ directive are ignored.
Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
**********************************************************************
Failure in example: print range(10) # Should fail: no ellipsis
from line #9 of f
from line #7 of f
Expected: [0, 1, ..., 9]
Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(2, 3)
Multiple flags can be toggled by a single option directive:
Multiple options may be modified by a single option directive. They
may be separated by whitespace, commas, or both:
>>> def f(x): r'''
... >>> print range(10) # Should fail
... [0, 1, ..., 9]
... >>> doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
... >>> print range(10) # Should succeed
... ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
... [0, 1, ..., 9]
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
@ -911,6 +940,104 @@ Multiple flags can be toggled by a single option directive:
Expected: [0, 1, ..., 9]
Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(1, 2)
>>> def f(x): r'''
... >>> print range(10) # Should fail
... [0, 1, ..., 9]
... >>> print range(10) # Should succeed
... ... # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE
... [0, 1, ..., 9]
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
**********************************************************************
Failure in example: print range(10) # Should fail
from line #1 of f
Expected: [0, 1, ..., 9]
Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(1, 2)
>>> def f(x): r'''
... >>> print range(10) # Should fail
... [0, 1, ..., 9]
... >>> print range(10) # Should succeed
... ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... [0, 1, ..., 9]
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
**********************************************************************
Failure in example: print range(10) # Should fail
from line #1 of f
Expected: [0, 1, ..., 9]
Got: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(1, 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 range(10)
... ... # doctest: +ELLIPSIS
... [0, 1, ..., 9]
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
(0, 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,
... 0 1 2 ... 9
...
... >>> for x in range(10):
... ... print x, # doctest: +ELLIPSIS
... 0 1 2 ... 9
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
(0, 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, # doctest: +NORMALIZE_WHITESPACE
... 0 1 2...9
... '''
>>> test = doctest.DocTestFinder().find(f)[0]
>>> doctest.DocTestRunner(verbose=False).run(test)
(0, 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"""
@ -971,12 +1098,12 @@ Create some fake stdin input, to feed to the debugger:
Run the debugger on the docstring, and then restore sys.stdin.
>>> doctest: +NORMALIZE_WHITESPACE
>>> try:
... doctest.debug_src(s)
... finally:
... sys.stdin = real_stdin
... fake_stdin.close()
... # doctest: +NORMALIZE_WHITESPACE
> <string>(1)?()
(Pdb) 12
--Return--
@ -1019,8 +1146,7 @@ def test_pdb_set_trace():
>>> real_stdin = sys.stdin
>>> sys.stdin = fake_stdin
>>> doctest: +ELLIPSIS
>>> runner.run(test)
>>> runner.run(test) # doctest: +ELLIPSIS
--Return--
> ...set_trace()->None
-> Pdb().set_trace()
@ -1057,7 +1183,7 @@ def test_pdb_set_trace():
>>> real_stdin = sys.stdin
>>> sys.stdin = fake_stdin
>>> runner.run(test)
>>> runner.run(test) # doctest: +ELLIPSIS
--Return--
> ...set_trace()->None
-> Pdb().set_trace()
@ -1068,8 +1194,6 @@ def test_pdb_set_trace():
(Pdb) > <string>(1)?()
(Pdb) 1
(Pdb) (0, 2)
>>> doctest: -ELLIPSIS
"""
def test_DocTestSuite():