##// END OF EJS Templates
Remove iptest and other Nose-dependent code
Remove iptest and other Nose-dependent code

File last commit:

r27042:360df2b6
r27042:360df2b6
Show More
ipdoctest.py
452 lines | 17.9 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 inspect
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
def is_extension_module(filename):
"""Return whether the given filename is an extension module.
This simply checks that the extension is either .so or .pyd.
"""
return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
Fernando Perez
Slight improvements to doctest skipping....
r1764 class DocTestSkip(object):
"""Object wrapper for doctests to be skipped."""
Bernardo B. Marques
remove all trailling spaces
r4872
Fernando Perez
Slight improvements to doctest skipping....
r1764 ds_skip = """Doctest to skip.
>>> 1 #doctest: +SKIP
"""
Fernando Perez
Complete first pass on testing system. All tests pass on my box. Whew....
r1435 def __init__(self,obj):
self.obj = obj
def __getattribute__(self,key):
if key == '__doc__':
Fernando Perez
Slight improvements to doctest skipping....
r1764 return DocTestSkip.ds_skip
Fernando Perez
Complete first pass on testing system. All tests pass on my box. Whew....
r1435 else:
return getattr(object.__getattribute__(self,'obj'),key)
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 # Modified version of the one in the stdlib, that fixes a python bug (doctests
# not found in extension modules, http://bugs.python.org/issue3158)
class DocTestFinder(doctest.DocTestFinder):
def _from_module(self, module, object):
"""
Return true if the given object is defined in the given
module.
"""
if module is None:
return True
elif inspect.isfunction(object):
Thomas Kluyver
Update function attribute names...
r13362 return module.__dict__ is object.__globals__
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 elif inspect.isbuiltin(object):
return module.__name__ == object.__module__
elif inspect.isclass(object):
return module.__name__ == object.__module__
elif inspect.ismethod(object):
# This one may be a bug in cython that fails to correctly set the
# __module__ attribute of methods, but since the same error is easy
# to make by extension code writers, having this safety in place
# isn't such a bad idea
Thomas Kluyver
Fix method special attributes...
r13370 return module.__name__ == object.__self__.__class__.__module__
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 elif inspect.getmodule(object) is not None:
return module is inspect.getmodule(object)
elif hasattr(object, '__module__'):
return module.__name__ == object.__module__
elif isinstance(object, property):
return True # [XX] no way not be sure.
Julian Taylor
Work around problem in doctest discovery in Python 3.4 with PyQt...
r14966 elif inspect.ismethoddescriptor(object):
# Unbound PyQt signals reach this point in Python 3.4b3, and we want
# to avoid throwing an error. See also http://bugs.python.org/issue3158
return False
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 else:
Thomas Kluyver
Fix tests in utils
r13373 raise ValueError("object must be a class or function, got %r" % object)
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
def _find(self, tests, obj, name, module, source_lines, globs, seen):
"""
Find tests for the given object and any contained objects, and
add them to `tests`.
"""
Matthias Bussonnier
actually warn if somebody use it
r21777 print('_find for:', obj, name, module) # dbg
Nikita Kniazev
Fix unintentional skipping of module level doctests...
r26873 if bool(getattr(obj, "__skip_doctest__", False)):
Fernando Perez
Complete first pass on testing system. All tests pass on my box. Whew....
r1435 #print 'SKIPPING DOCTEST FOR:',obj # dbg
Fernando Perez
Slight improvements to doctest skipping....
r1764 obj = DocTestSkip(obj)
Matthias Bussonnier
actually warn if somebody use it
r21777
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 doctest.DocTestFinder._find(self,tests, obj, name, module,
source_lines, globs, seen)
# Below we re-run pieces of the above method with manual modifications,
# because the original code is buggy and fails to correctly identify
# doctests in extension modules.
# Local shorthands
Matthias Bussonnier
'remove unused import'
r22207 from inspect import isroutine, isclass
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
# Look for tests in a module's contained objects.
if inspect.ismodule(obj) and self._recurse:
for valname, val in obj.__dict__.items():
valname1 = '%s.%s' % (name, valname)
if ( (isroutine(val) or isclass(val))
and self._from_module(module, val) ):
self._find(tests, val, valname1, module, source_lines,
globs, seen)
# Look for tests in a class's contained objects.
if inspect.isclass(obj) and self._recurse:
#print 'RECURSE into class:',obj # dbg
for valname, val in obj.__dict__.items():
# Special handling for staticmethod/classmethod.
if isinstance(val, staticmethod):
val = getattr(obj, valname)
if isinstance(val, classmethod):
Thomas Kluyver
Fix method special attributes...
r13370 val = getattr(obj, valname).__func__
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334
# Recurse to methods, properties, and nested classes.
if ((inspect.isfunction(val) or inspect.isclass(val) or
inspect.ismethod(val) or
isinstance(val, property)) and
self._from_module(module, val)):
valname = '%s.%s' % (name, valname)
self._find(tests, val, valname, module, source_lines,
globs, seen)
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 IPExternalExample(doctest.Example):
"""Doctest examples to be run in an external process."""
def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
options=None):
# Parent constructor
doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
# An EXTRA newline is needed to prevent pexpect hangs
self.source += '\n'
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
# Mark tests to be executed in an external process - currently unsupported.
Fernando Perez
Cleanups, document, working on support for full random tests.
r1429 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
Bernardo B. Marques
remove all trailling spaces
r4872
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
#print '-'*70 # dbg
#print 'PyExample, Source:\n',string # dbg
#print '-'*70 # dbg
Example = doctest.Example
else:
# It's an ipython example. Note that IPExamples are run
# in-process, so their syntax must be turned into valid python.
# IPExternalExamples are run out-of-process (via pexpect) so they
# don't need any filtering (a real ipython will be executing them).
terms = list(self._EXAMPLE_RE_IP.finditer(string))
Fernando Perez
Cleanups, document, working on support for full random tests.
r1429 if self._EXTERNAL_IP.search(string):
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 #print '-'*70 # dbg
#print 'IPExternalExample, Source:\n',string # dbg
#print '-'*70 # dbg
Example = IPExternalExample
else:
#print '-'*70 # dbg
#print 'IPExample, Source:\n',string # dbg
#print '-'*70 # dbg
Example = IPExample
ip2py = True
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 if Example is IPExternalExample:
options[doctest.NORMALIZE_WHITESPACE] = True
want += '\n'
Fernando Perez
Implement support for all random tests. Other minor cleanups.
r1430
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):
Fernando Perez
Vastly cleaner solution for the namespace problem!
r1427 # Hack: ipython needs access to the execution context of the example,
# so that it can propagate user variables loaded by %run into
# test.globs. We put them here into our modified %run as a function
# attribute. Our new %run will then only make the namespace update
Min ho Kim
Fix typos
r25146 # when called (rather than unconditionally updating test.globs here
Fernando Perez
Vastly cleaner solution for the namespace problem!
r1427 # for all examples, most of which won't be calling %run anyway).
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414 #_ip._ipdoctest_test_globs = test.globs
#_ip._ipdoctest_test_filename = test.filename
test.globs.update(_ip.user_ns)
Bernardo B. Marques
remove all trailling spaces
r4872
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)
Fernando Perez
Checkpoint with more tests working....
r1420
Fernando Perez
Added Nose support for IPython doctests and extension modules.
r1334 class DocFileCase(doctest.DocFileCase):
"""Overrides to provide filename
"""
def address(self):
return (self._dt_test.filename, None, None)