##// END OF EJS Templates
Merge pull request #13708 from suzaku/improve-automatch-perf...
Merge pull request #13708 from suzaku/improve-automatch-perf Fix #13654, improve performance of auto match for quotes

File last commit:

r27157:140d3835
r27784:7dbb26a6 merge
Show More
ipdoctest.py
300 lines | 11.6 KiB | text/x-python | PythonLexer
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 """Nose Plugin that supports IPython doctests.
Limitations:
- When generating examples for use as doctests, make sure that you have
Bernardo B. Marques
remove all trailling spaces
r4872 pretty-printing OFF. This can be done either by setting the
``PlainTextFormatter.pprint`` option in your configuration file to False, or
Erik Tollerud
updated references to configuration file options to specify the class as well as the option itself
r4468 by interactively disabling it with %Pprint. This is required so that IPython
Bernardo B. Marques
remove all trailling spaces
r4872 output matches that of normal Python, which is used by doctest for internal
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 execution.
- Do not rely on specific prompt numbers for results (such as using
'_34==True', for example). For IPython tests run via an external process the
prompt numbers may be different, and IPython tests run as normal python code
won't even have these special _NN variables set at all.
"""
#-----------------------------------------------------------------------------
# Module imports
# From the standard library
import doctest
import logging
import os
import re
Thomas Kluyver
Override terminal size in doctests to standardise traceback format...
r22157 from testpath import modified_env
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 #-----------------------------------------------------------------------------
# Module globals and other constants
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414 #-----------------------------------------------------------------------------
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
log = logging.getLogger(__name__)
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414 #-----------------------------------------------------------------------------
Fernando Perez
Minor cleanups.
r1377 # Classes and functions
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414 #-----------------------------------------------------------------------------
Fernando Perez
Minor cleanups.
r1377
Bernardo B. Marques
remove all trailling spaces
r4872
Nikita Kniazev
Refactor doctest-skipping implementation...
r27121 class DocTestFinder(doctest.DocTestFinder):
def _get_test(self, obj, name, module, globs, source_lines):
test = super()._get_test(obj, name, module, globs, source_lines)
Fernando Perez
Complete first pass on testing system. All tests pass on my box. Whew....
r1435
Nikita Kniazev
Refactor doctest-skipping implementation...
r27121 if bool(getattr(obj, "__skip_doctest__", False)) and test is not None:
for example in test.examples:
example.options[doctest.SKIP] = True
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
Nikita Kniazev
Refactor doctest-skipping implementation...
r27121 return test
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
Fernando Perez
Local checkpoint of changes to testing machinery. Work in progress.
r1403 class IPDoctestOutputChecker(doctest.OutputChecker):
Fernando Perez
Cleanups, document, working on support for full random tests.
r1429 """Second-chance checker with support for random tests.
Bernardo B. Marques
remove all trailling spaces
r4872
Fernando Perez
Cleanups, document, working on support for full random tests.
r1429 If the default comparison doesn't pass, this checker looks in the expected
output string for flags that tell us to ignore the output.
"""
Fernando Perez
Adjust regexps for random tests
r1453 random_re = re.compile(r'#\s*random\s+')
Bernardo B. Marques
remove all trailling spaces
r4872
Fernando Perez
Local checkpoint of changes to testing machinery. Work in progress.
r1403 def check_output(self, want, got, optionflags):
Fernando Perez
Cleanups, document, working on support for full random tests.
r1429 """Check output, accepting special markers embedded in the output.
Fernando Perez
Checkpoint with more tests working....
r1420
Fernando Perez
Cleanups, document, working on support for full random tests.
r1429 If the output didn't pass the default validation but the special string
'#random' is included, we accept it."""
# Let the original tester verify first, in case people have valid tests
# that happen to have a comment saying '#random' embedded in.
Fernando Perez
Checkpoint with more tests working....
r1420 ret = doctest.OutputChecker.check_output(self, want, got,
Fernando Perez
Local checkpoint of changes to testing machinery. Work in progress.
r1403 optionflags)
Fernando Perez
Cleanups, document, working on support for full random tests.
r1429 if not ret and self.random_re.search(want):
#print >> sys.stderr, 'RANDOM OK:',want # dbg
return True
Fernando Perez
Local checkpoint of changes to testing machinery. Work in progress.
r1403
return ret
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 # A simple subclassing of the original with a different class name, so we can
# distinguish and treat differently IPython examples from pure python ones.
class IPExample(doctest.Example): pass
Fernando Perez
Minor cleanups.
r1377
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 class IPDocTestParser(doctest.DocTestParser):
"""
A class used to parse strings containing doctest examples.
Note: This is a version modified to properly recognize IPython input and
convert any IPython examples into valid Python ones.
"""
# This regular expression is used to find doctest examples in a
# string. It defines three groups: `source` is the source code
# (including leading indentation and prompts); `indent` is the
# indentation of the first (PS1) line of the source code; and
# `want` is the expected output (including leading indentation).
# Classic Python prompts or default IPython ones
_PS1_PY = r'>>>'
_PS2_PY = r'\.\.\.'
_PS1_IP = r'In\ \[\d+\]:'
_PS2_IP = r'\ \ \ \.\.\.+:'
_RE_TPL = r'''
# Source consists of a PS1 line followed by zero or more PS2 lines.
(?P<source>
(?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
(?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
\n? # a newline
# Want consists of any non-blank lines that do not start with PS1.
(?P<want> (?:(?![ ]*$) # Not a blank line
(?![ ]*%s) # Not a line starting with PS1
(?![ ]*%s) # Not a line starting with PS2
.*$\n? # But any other line
)*)
'''
_EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
re.MULTILINE | re.VERBOSE)
_EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
re.MULTILINE | re.VERBOSE)
Fernando Perez
Implement support for all random tests. Other minor cleanups.
r1430 # Mark a test as being fully random. In this case, we simply append the
# random marker ('#random') to each individual example's output. This way
# we don't need to modify any other code.
Fernando Perez
Adjust regexps for random tests
r1453 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
Fernando Perez
Implement support for all random tests. Other minor cleanups.
r1430
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 def ip2py(self,source):
"""Convert input IPython source into valid Python."""
Thomas Kluyver
Refactor paste magics and ipdoctest
r10754 block = _ip.input_transformer_manager.transform_cell(source)
if len(block.splitlines()) == 1:
return _ip.prefilter(block)
else:
return block
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
def parse(self, string, name='<string>'):
"""
Divide the given string into examples and intervening text,
and return them as a list of alternating Examples and strings.
Line numbers for the Examples are 0-based. The optional
argument `name` is a name identifying this string, and is only
used for error messages.
"""
Fernando Perez
Checkpoint with more tests working....
r1420
Fernando Perez
Local checkpoint of changes to testing machinery. Work in progress.
r1403 #print 'Parse string:\n',string # dbg
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
string = string.expandtabs()
# If all lines begin with the same indentation, then strip it.
min_indent = self._min_indent(string)
if min_indent > 0:
string = '\n'.join([l[min_indent:] for l in string.split('\n')])
output = []
charno, lineno = 0, 0
Fernando Perez
Correctly implement namespace handling. Minor cleanups....
r1509 # We make 'all random' tests by adding the '# random' mark to every
# block of output in the test.
Fernando Perez
Implement support for all random tests. Other minor cleanups.
r1430 if self._RANDOM_TEST.search(string):
random_marker = '\n# random'
else:
random_marker = ''
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 # Whether to convert the input from ipython to python syntax
ip2py = False
# Find all doctest examples in the string. First, try them as Python
# examples, then as IPython ones
terms = list(self._EXAMPLE_RE_PY.finditer(string))
if terms:
# Normal Python example
Example = doctest.Example
else:
Nikita Kniazev
Remove unimplemented `ipdoctest: external` support
r27157 # It's an ipython example.
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 terms = list(self._EXAMPLE_RE_IP.finditer(string))
Nikita Kniazev
Remove unimplemented `ipdoctest: external` support
r27157 Example = IPExample
ip2py = True
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
for m in terms:
# Add the pre-example text to `output`.
output.append(string[charno:m.start()])
# Update lineno (lines before this example)
lineno += string.count('\n', charno, m.start())
# Extract info from the regexp match.
(source, options, want, exc_msg) = \
self._parse_example(m, name, lineno,ip2py)
Fernando Perez
Implement support for all random tests. Other minor cleanups.
r1430
# Append the random-output marker (it defaults to empty in most
# cases, it's only non-empty for 'all-random' tests):
want += random_marker
Bernardo B. Marques
remove all trailling spaces
r4872
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 # Create an Example, and add it to the list.
if not self._IS_BLANK_OR_COMMENT(source):
output.append(Example(source, want, exc_msg,
lineno=lineno,
indent=min_indent+len(m.group('indent')),
options=options))
# Update lineno (lines inside this example)
lineno += string.count('\n', m.start(), m.end())
# Update charno.
charno = m.end()
# Add any remaining post-example text to `output`.
output.append(string[charno:])
return output
def _parse_example(self, m, name, lineno,ip2py=False):
"""
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.
Optional:
`ip2py`: if true, filter the input via IPython to convert the syntax
into valid python.
"""
# Get the example's indentation level.
indent = len(m.group('indent'))
# Divide source into lines; check that they're properly
# indented; and then strip their indentation & prompts.
source_lines = m.group('source').split('\n')
# We're using variable-length input prompts
ps1 = m.group('ps1')
ps2 = m.group('ps2')
ps1_len = len(ps1)
self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
if ps2:
self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
if ip2py:
# Convert source input from IPython into valid Python syntax
source = self.ip2py(source)
# Divide want into lines; check that it's properly indented; and
# then strip the indentation. Spaces before the last newline should
# be preserved, so plain rstrip() isn't good enough.
want = m.group('want')
want_lines = want.split('\n')
if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
del want_lines[-1] # forget final newline & spaces after it
self._check_prefix(want_lines, ' '*indent, name,
lineno + len(source_lines))
# Remove ipython output prompt that might be present in the first line
want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
want = '\n'.join([wl[indent:] for wl in want_lines])
# If `want` contains a traceback message, then extract it.
m = self._EXCEPTION_RE.match(want)
if m:
exc_msg = m.group('msg')
else:
exc_msg = None
# Extract options from the source.
options = self._find_options(source, name, lineno)
return source, options, want, exc_msg
def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
"""
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.
Note: IPython-modified version which takes the input prompt length as a
parameter, so that prompts of variable length can be dealt with.
"""
space_idx = indent+ps1_len
min_len = space_idx+1
for i, line in enumerate(lines):
if len(line) >= min_len and line[space_idx] != ' ':
raise ValueError('line %r of the docstring for %s '
'lacks blank after %s: %r' %
(lineno+i+1, name,
line[indent:space_idx], line))
Fernando Perez
Improvements to namespace handling with %run....
r1425
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 SKIP = doctest.register_optionflag('SKIP')
Fernando Perez
Vastly cleaner solution for the namespace problem!
r1427 class IPDocTestRunner(doctest.DocTestRunner,object):
"""Test runner that synchronizes the IPython namespace with test globals.
"""
Bernardo B. Marques
remove all trailling spaces
r4872
Fernando Perez
Checkpoint with more tests working....
r1420 def run(self, test, compileflags=None, out=None, clear_globs=True):
Thomas Kluyver
Override terminal size in doctests to standardise traceback format...
r22157 # Override terminal size to standardise traceback format
with modified_env({'COLUMNS': '80', 'LINES': '24'}):
return super(IPDocTestRunner,self).run(test,
compileflags,out,clear_globs)