765 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			765 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | """Nose Plugin that supports yap_ipython doctests.
 | ||
|  | 
 | ||
|  | Limitations: | ||
|  | 
 | ||
|  | - When generating examples for use as doctests, make sure that you have | ||
|  |   pretty-printing OFF.  This can be done either by setting the | ||
|  |   ``PlainTextFormatter.pprint`` option in your configuration file to  False, or | ||
|  |   by interactively disabling it with  %Pprint.  This is required so that yap_ipython | ||
|  |   output matches that of normal Python, which is used by doctest for internal | ||
|  |   execution. | ||
|  | 
 | ||
|  | - Do not rely on specific prompt numbers for results (such as using | ||
|  |   '_34==True', for example).  For yap_ipython tests run via an external process the | ||
|  |   prompt numbers may be different, and yap_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 builtins as builtin_mod | ||
|  | import doctest | ||
|  | import inspect | ||
|  | import logging | ||
|  | import os | ||
|  | import re | ||
|  | import sys | ||
|  | from importlib import import_module | ||
|  | from io import StringIO | ||
|  | 
 | ||
|  | from testpath import modified_env | ||
|  | 
 | ||
|  | from inspect import getmodule | ||
|  | 
 | ||
|  | # We are overriding the default doctest runner, so we need to import a few | ||
|  | # things from doctest directly | ||
|  | from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, | ||
|  |                      _unittest_reportflags, DocTestRunner, | ||
|  |                      _extract_future_flags, pdb, _OutputRedirectingPdb, | ||
|  |                      _exception_traceback, | ||
|  |                      linecache) | ||
|  | 
 | ||
|  | # Third-party modules | ||
|  | 
 | ||
|  | from nose.plugins import doctests, Plugin | ||
|  | from nose.util import anyp, tolist | ||
|  | 
 | ||
|  | #----------------------------------------------------------------------------- | ||
|  | # Module globals and other constants | ||
|  | #----------------------------------------------------------------------------- | ||
|  | 
 | ||
|  | log = logging.getLogger(__name__) | ||
|  | 
 | ||
|  | 
 | ||
|  | #----------------------------------------------------------------------------- | ||
|  | # Classes and functions | ||
|  | #----------------------------------------------------------------------------- | ||
|  | 
 | ||
|  | 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') | ||
|  | 
 | ||
|  | 
 | ||
|  | class DocTestSkip(object): | ||
|  |     """Object wrapper for doctests to be skipped.""" | ||
|  | 
 | ||
|  |     ds_skip = """Doctest to skip.
 | ||
|  |     >>> 1 #doctest: +SKIP | ||
|  |     """
 | ||
|  | 
 | ||
|  |     def __init__(self,obj): | ||
|  |         self.obj = obj | ||
|  | 
 | ||
|  |     def __getattribute__(self,key): | ||
|  |         if key == '__doc__': | ||
|  |             return DocTestSkip.ds_skip | ||
|  |         else: | ||
|  |             return getattr(object.__getattribute__(self,'obj'),key) | ||
|  | 
 | ||
|  | # 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): | ||
|  |             return module.__dict__ is object.__globals__ | ||
|  |         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 | ||
|  |             return module.__name__ == object.__self__.__class__.__module__ | ||
|  |         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. | ||
|  |         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 | ||
|  |         else: | ||
|  |             raise ValueError("object must be a class or function, got %r" % object) | ||
|  | 
 | ||
|  |     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`. | ||
|  |         """
 | ||
|  |         print('_find for:', obj, name, module)  # dbg | ||
|  |         if hasattr(obj,"skip_doctest"): | ||
|  |             #print 'SKIPPING DOCTEST FOR:',obj  # dbg | ||
|  |             obj = DocTestSkip(obj) | ||
|  |          | ||
|  |         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 | ||
|  |         from inspect import isroutine, isclass | ||
|  | 
 | ||
|  |         # 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): | ||
|  |                     val = getattr(obj, valname).__func__ | ||
|  | 
 | ||
|  |                 # 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) | ||
|  | 
 | ||
|  | 
 | ||
|  | class IPDoctestOutputChecker(doctest.OutputChecker): | ||
|  |     """Second-chance checker with support for random tests.
 | ||
|  | 
 | ||
|  |     If the default comparison doesn't pass, this checker looks in the expected | ||
|  |     output string for flags that tell us to ignore the output. | ||
|  |     """
 | ||
|  | 
 | ||
|  |     random_re = re.compile(r'#\s*random\s+') | ||
|  | 
 | ||
|  |     def check_output(self, want, got, optionflags): | ||
|  |         """Check output, accepting special markers embedded in the output.
 | ||
|  | 
 | ||
|  |         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. | ||
|  |         ret = doctest.OutputChecker.check_output(self, want, got, | ||
|  |                                                  optionflags) | ||
|  |         if not ret and self.random_re.search(want): | ||
|  |             #print >> sys.stderr, 'RANDOM OK:',want  # dbg | ||
|  |             return True | ||
|  | 
 | ||
|  |         return ret | ||
|  | 
 | ||
|  | 
 | ||
|  | class DocTestCase(doctests.DocTestCase): | ||
|  |     """Proxy for DocTestCase: provides an address() method that
 | ||
|  |     returns the correct address for the doctest case. Otherwise | ||
|  |     acts as a proxy to the test case. To provide hints for address(), | ||
|  |     an obj may also be passed -- this will be used as the test object | ||
|  |     for purposes of determining the test address, if it is provided. | ||
|  |     """
 | ||
|  | 
 | ||
|  |     # Note: this method was taken from numpy's nosetester module. | ||
|  | 
 | ||
|  |     # Subclass nose.plugins.doctests.DocTestCase to work around a bug in | ||
|  |     # its constructor that blocks non-default arguments from being passed | ||
|  |     # down into doctest.DocTestCase | ||
|  | 
 | ||
|  |     def __init__(self, test, optionflags=0, setUp=None, tearDown=None, | ||
|  |                  checker=None, obj=None, result_var='_'): | ||
|  |         self._result_var = result_var | ||
|  |         doctests.DocTestCase.__init__(self, test, | ||
|  |                                       optionflags=optionflags, | ||
|  |                                       setUp=setUp, tearDown=tearDown, | ||
|  |                                       checker=checker) | ||
|  |         # Now we must actually copy the original constructor from the stdlib | ||
|  |         # doctest class, because we can't call it directly and a bug in nose | ||
|  |         # means it never gets passed the right arguments. | ||
|  | 
 | ||
|  |         self._dt_optionflags = optionflags | ||
|  |         self._dt_checker = checker | ||
|  |         self._dt_test = test | ||
|  |         self._dt_test_globs_ori = test.globs | ||
|  |         self._dt_setUp = setUp | ||
|  |         self._dt_tearDown = tearDown | ||
|  | 
 | ||
|  |         # XXX - store this runner once in the object! | ||
|  |         runner = IPDocTestRunner(optionflags=optionflags, | ||
|  |                                  checker=checker, verbose=False) | ||
|  |         self._dt_runner = runner | ||
|  | 
 | ||
|  | 
 | ||
|  |         # Each doctest should remember the directory it was loaded from, so | ||
|  |         # things like %run work without too many contortions | ||
|  |         self._ori_dir = os.path.dirname(test.filename) | ||
|  | 
 | ||
|  |     # Modified runTest from the default stdlib | ||
|  |     def runTest(self): | ||
|  |         test = self._dt_test | ||
|  |         runner = self._dt_runner | ||
|  | 
 | ||
|  |         old = sys.stdout | ||
|  |         new = StringIO() | ||
|  |         optionflags = self._dt_optionflags | ||
|  | 
 | ||
|  |         if not (optionflags & REPORTING_FLAGS): | ||
|  |             # The option flags don't include any reporting flags, | ||
|  |             # so add the default reporting flags | ||
|  |             optionflags |= _unittest_reportflags | ||
|  | 
 | ||
|  |         try: | ||
|  |             # Save our current directory and switch out to the one where the | ||
|  |             # test was originally created, in case another doctest did a | ||
|  |             # directory change.  We'll restore this in the finally clause. | ||
|  |             curdir = os.getcwd() | ||
|  |             #print 'runTest in dir:', self._ori_dir  # dbg | ||
|  |             os.chdir(self._ori_dir) | ||
|  | 
 | ||
|  |             runner.DIVIDER = "-"*70 | ||
|  |             failures, tries = runner.run(test,out=new.write, | ||
|  |                                          clear_globs=False) | ||
|  |         finally: | ||
|  |             sys.stdout = old | ||
|  |             os.chdir(curdir) | ||
|  | 
 | ||
|  |         if failures: | ||
|  |             raise self.failureException(self.format_failure(new.getvalue())) | ||
|  | 
 | ||
|  |     def setUp(self): | ||
|  |         """Modified test setup that syncs with ipython namespace""" | ||
|  |         #print "setUp test", self._dt_test.examples # dbg | ||
|  |         if isinstance(self._dt_test.examples[0], IPExample): | ||
|  |             # for yap_ipython examples *only*, we swap the globals with the ipython | ||
|  |             # namespace, after updating it with the globals (which doctest | ||
|  |             # fills with the necessary info from the module being tested). | ||
|  |             self.user_ns_orig = {} | ||
|  |             self.user_ns_orig.update(_ip.user_ns) | ||
|  |             _ip.user_ns.update(self._dt_test.globs) | ||
|  |             # We must remove the _ key in the namespace, so that Python's | ||
|  |             # doctest code sets it naturally | ||
|  |             _ip.user_ns.pop('_', None) | ||
|  |             _ip.user_ns['__builtins__'] = builtin_mod | ||
|  |             self._dt_test.globs = _ip.user_ns | ||
|  | 
 | ||
|  |         super(DocTestCase, self).setUp() | ||
|  | 
 | ||
|  |     def tearDown(self): | ||
|  | 
 | ||
|  |         # Undo the test.globs reassignment we made, so that the parent class | ||
|  |         # teardown doesn't destroy the ipython namespace | ||
|  |         if isinstance(self._dt_test.examples[0], IPExample): | ||
|  |             self._dt_test.globs = self._dt_test_globs_ori | ||
|  |             _ip.user_ns.clear() | ||
|  |             _ip.user_ns.update(self.user_ns_orig) | ||
|  | 
 | ||
|  |         # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but | ||
|  |         # it does look like one to me: its tearDown method tries to run | ||
|  |         # | ||
|  |         # delattr(builtin_mod, self._result_var) | ||
|  |         # | ||
|  |         # without checking that the attribute really is there; it implicitly | ||
|  |         # assumes it should have been set via displayhook.  But if the | ||
|  |         # displayhook was never called, this doesn't necessarily happen.  I | ||
|  |         # haven't been able to find a little self-contained example outside of | ||
|  |         # ipython that would show the problem so I can report it to the nose | ||
|  |         # team, but it does happen a lot in our code. | ||
|  |         # | ||
|  |         # So here, we just protect as narrowly as possible by trapping an | ||
|  |         # attribute error whose message would be the name of self._result_var, | ||
|  |         # and letting any other error propagate. | ||
|  |         try: | ||
|  |             super(DocTestCase, self).tearDown() | ||
|  |         except AttributeError as exc: | ||
|  |             if exc.args[0] != self._result_var: | ||
|  |                 raise | ||
|  | 
 | ||
|  | 
 | ||
|  | # A simple subclassing of the original with a different class name, so we can | ||
|  | # distinguish and treat differently yap_ipython examples from pure python ones. | ||
|  | class IPExample(doctest.Example): pass | ||
|  | 
 | ||
|  | 
 | ||
|  | 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' | ||
|  | 
 | ||
|  | 
 | ||
|  | class IPDocTestParser(doctest.DocTestParser): | ||
|  |     """
 | ||
|  |     A class used to parse strings containing doctest examples. | ||
|  | 
 | ||
|  |     Note: This is a version modified to properly recognize yap_ipython input and | ||
|  |     convert any yap_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 yap_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) | ||
|  | 
 | ||
|  |     # 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. | ||
|  |     _RANDOM_TEST = re.compile(r'#\s*all-random\s+') | ||
|  | 
 | ||
|  |     # Mark tests to be executed in an external process - currently unsupported. | ||
|  |     _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') | ||
|  | 
 | ||
|  |     def ip2py(self,source): | ||
|  |         """Convert input yap_ipython source into valid Python.""" | ||
|  |         block = _ip.input_transformer_manager.transform_cell(source) | ||
|  |         if len(block.splitlines()) == 1: | ||
|  |             return _ip.prefilter(block) | ||
|  |         else: | ||
|  |             return block | ||
|  | 
 | ||
|  |     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. | ||
|  |         """
 | ||
|  | 
 | ||
|  |         #print 'Parse string:\n',string # dbg | ||
|  | 
 | ||
|  |         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 | ||
|  | 
 | ||
|  |         # We make 'all random' tests by adding the '# random' mark to every | ||
|  |         # block of output in the test. | ||
|  |         if self._RANDOM_TEST.search(string): | ||
|  |             random_marker = '\n# random' | ||
|  |         else: | ||
|  |             random_marker = '' | ||
|  | 
 | ||
|  |         # 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 yap_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)) | ||
|  |             if self._EXTERNAL_IP.search(string): | ||
|  |                 #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) | ||
|  | 
 | ||
|  |             # Append the random-output marker (it defaults to empty in most | ||
|  |             # cases, it's only non-empty for 'all-random' tests): | ||
|  |             want += random_marker | ||
|  | 
 | ||
|  |             if Example is IPExternalExample: | ||
|  |                 options[doctest.NORMALIZE_WHITESPACE] = True | ||
|  |                 want += '\n' | ||
|  | 
 | ||
|  |             # 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 yap_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 yap_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: yap_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)) | ||
|  | 
 | ||
|  | 
 | ||
|  | SKIP = doctest.register_optionflag('SKIP') | ||
|  | 
 | ||
|  | 
 | ||
|  | class IPDocTestRunner(doctest.DocTestRunner,object): | ||
|  |     """Test runner that synchronizes the yap_ipython namespace with test globals.
 | ||
|  |     """
 | ||
|  | 
 | ||
|  |     def run(self, test, compileflags=None, out=None, clear_globs=True): | ||
|  | 
 | ||
|  |         # 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 | ||
|  |         # when called (rather than unconconditionally updating test.globs here | ||
|  |         # for all examples, most of which won't be calling %run anyway). | ||
|  |         #_ip._ipdoctest_test_globs = test.globs | ||
|  |         #_ip._ipdoctest_test_filename = test.filename | ||
|  | 
 | ||
|  |         test.globs.update(_ip.user_ns) | ||
|  | 
 | ||
|  |         # Override terminal size to standardise traceback format | ||
|  |         with modified_env({'COLUMNS': '80', 'LINES': '24'}): | ||
|  |             return super(IPDocTestRunner,self).run(test, | ||
|  |                                                    compileflags,out,clear_globs) | ||
|  | 
 | ||
|  | 
 | ||
|  | class DocFileCase(doctest.DocFileCase): | ||
|  |     """Overrides to provide filename
 | ||
|  |     """
 | ||
|  |     def address(self): | ||
|  |         return (self._dt_test.filename, None, None) | ||
|  | 
 | ||
|  | 
 | ||
|  | class ExtensionDoctest(doctests.Doctest): | ||
|  |     """Nose Plugin that supports doctests in extension modules.
 | ||
|  |     """
 | ||
|  |     name = 'extdoctest'   # call nosetests with --with-extdoctest | ||
|  |     enabled = True | ||
|  | 
 | ||
|  |     def options(self, parser, env=os.environ): | ||
|  |         Plugin.options(self, parser, env) | ||
|  |         parser.add_option('--doctest-tests', action='store_true', | ||
|  |                           dest='doctest_tests', | ||
|  |                           default=env.get('NOSE_DOCTEST_TESTS',True), | ||
|  |                           help="Also look for doctests in test modules. " | ||
|  |                           "Note that classes, methods and functions should " | ||
|  |                           "have either doctests or non-doctest tests, " | ||
|  |                           "not both. [NOSE_DOCTEST_TESTS]") | ||
|  |         parser.add_option('--doctest-extension', action="append", | ||
|  |                           dest="doctestExtension", | ||
|  |                           help="Also look for doctests in files with " | ||
|  |                           "this extension [NOSE_DOCTEST_EXTENSION]") | ||
|  |         # Set the default as a list, if given in env; otherwise | ||
|  |         # an additional value set on the command line will cause | ||
|  |         # an error. | ||
|  |         env_setting = env.get('NOSE_DOCTEST_EXTENSION') | ||
|  |         if env_setting is not None: | ||
|  |             parser.set_defaults(doctestExtension=tolist(env_setting)) | ||
|  | 
 | ||
|  | 
 | ||
|  |     def configure(self, options, config): | ||
|  |         Plugin.configure(self, options, config) | ||
|  |         # Pull standard doctest plugin out of config; we will do doctesting | ||
|  |         config.plugins.plugins = [p for p in config.plugins.plugins | ||
|  |                                   if p.name != 'doctest'] | ||
|  |         self.doctest_tests = options.doctest_tests | ||
|  |         self.extension = tolist(options.doctestExtension) | ||
|  | 
 | ||
|  |         self.parser = doctest.DocTestParser() | ||
|  |         self.finder = DocTestFinder() | ||
|  |         self.checker = IPDoctestOutputChecker() | ||
|  |         self.globs = None | ||
|  |         self.extraglobs = None | ||
|  | 
 | ||
|  | 
 | ||
|  |     def loadTestsFromExtensionModule(self,filename): | ||
|  |         bpath,mod = os.path.split(filename) | ||
|  |         modname = os.path.splitext(mod)[0] | ||
|  |         try: | ||
|  |             sys.path.append(bpath) | ||
|  |             module = import_module(modname) | ||
|  |             tests = list(self.loadTestsFromModule(module)) | ||
|  |         finally: | ||
|  |             sys.path.pop() | ||
|  |         return tests | ||
|  | 
 | ||
|  |     # NOTE: the method below is almost a copy of the original one in nose, with | ||
|  |     # a  few modifications to control output checking. | ||
|  | 
 | ||
|  |     def loadTestsFromModule(self, module): | ||
|  |         #print '*** ipdoctest - lTM',module  # dbg | ||
|  | 
 | ||
|  |         if not self.matches(module.__name__): | ||
|  |             log.debug("Doctest doesn't want module %s", module) | ||
|  |             return | ||
|  | 
 | ||
|  |         tests = self.finder.find(module,globs=self.globs, | ||
|  |                                  extraglobs=self.extraglobs) | ||
|  |         if not tests: | ||
|  |             return | ||
|  | 
 | ||
|  |         # always use whitespace and ellipsis options | ||
|  |         optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | ||
|  | 
 | ||
|  |         tests.sort() | ||
|  |         module_file = module.__file__ | ||
|  |         if module_file[-4:] in ('.pyc', '.pyo'): | ||
|  |             module_file = module_file[:-1] | ||
|  |         for test in tests: | ||
|  |             if not test.examples: | ||
|  |                 continue | ||
|  |             if not test.filename: | ||
|  |                 test.filename = module_file | ||
|  | 
 | ||
|  |             yield DocTestCase(test, | ||
|  |                               optionflags=optionflags, | ||
|  |                               checker=self.checker) | ||
|  | 
 | ||
|  | 
 | ||
|  |     def loadTestsFromFile(self, filename): | ||
|  |         #print "ipdoctest - from file", filename # dbg | ||
|  |         if is_extension_module(filename): | ||
|  |             for t in self.loadTestsFromExtensionModule(filename): | ||
|  |                 yield t | ||
|  |         else: | ||
|  |             if self.extension and anyp(filename.endswith, self.extension): | ||
|  |                 name = os.path.basename(filename) | ||
|  |                 dh = open(filename) | ||
|  |                 try: | ||
|  |                     doc = dh.read() | ||
|  |                 finally: | ||
|  |                     dh.close() | ||
|  |                 test = self.parser.get_doctest( | ||
|  |                     doc, globs={'__file__': filename}, name=name, | ||
|  |                     filename=filename, lineno=0) | ||
|  |                 if test.examples: | ||
|  |                     #print 'FileCase:',test.examples  # dbg | ||
|  |                     yield DocFileCase(test) | ||
|  |                 else: | ||
|  |                     yield False # no tests to load | ||
|  | 
 | ||
|  | 
 | ||
|  | class IPythonDoctest(ExtensionDoctest): | ||
|  |     """Nose Plugin that supports doctests in extension modules.
 | ||
|  |     """
 | ||
|  |     name = 'ipdoctest'   # call nosetests with --with-ipdoctest | ||
|  |     enabled = True | ||
|  | 
 | ||
|  |     def makeTest(self, obj, parent): | ||
|  |         """Look for doctests in the given object, which will be a
 | ||
|  |         function, method or class. | ||
|  |         """
 | ||
|  |         #print 'Plugin analyzing:', obj, parent  # dbg | ||
|  |         # always use whitespace and ellipsis options | ||
|  |         optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | ||
|  | 
 | ||
|  |         doctests = self.finder.find(obj, module=getmodule(parent)) | ||
|  |         if doctests: | ||
|  |             for test in doctests: | ||
|  |                 if len(test.examples) == 0: | ||
|  |                     continue | ||
|  | 
 | ||
|  |                 yield DocTestCase(test, obj=obj, | ||
|  |                                   optionflags=optionflags, | ||
|  |                                   checker=self.checker) | ||
|  | 
 | ||
|  |     def options(self, parser, env=os.environ): | ||
|  |         #print "Options for nose plugin:", self.name # dbg | ||
|  |         Plugin.options(self, parser, env) | ||
|  |         parser.add_option('--ipdoctest-tests', action='store_true', | ||
|  |                           dest='ipdoctest_tests', | ||
|  |                           default=env.get('NOSE_IPDOCTEST_TESTS',True), | ||
|  |                           help="Also look for doctests in test modules. " | ||
|  |                           "Note that classes, methods and functions should " | ||
|  |                           "have either doctests or non-doctest tests, " | ||
|  |                           "not both. [NOSE_IPDOCTEST_TESTS]") | ||
|  |         parser.add_option('--ipdoctest-extension', action="append", | ||
|  |                           dest="ipdoctest_extension", | ||
|  |                           help="Also look for doctests in files with " | ||
|  |                           "this extension [NOSE_IPDOCTEST_EXTENSION]") | ||
|  |         # Set the default as a list, if given in env; otherwise | ||
|  |         # an additional value set on the command line will cause | ||
|  |         # an error. | ||
|  |         env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') | ||
|  |         if env_setting is not None: | ||
|  |             parser.set_defaults(ipdoctest_extension=tolist(env_setting)) | ||
|  | 
 | ||
|  |     def configure(self, options, config): | ||
|  |         #print "Configuring nose plugin:", self.name # dbg | ||
|  |         Plugin.configure(self, options, config) | ||
|  |         # Pull standard doctest plugin out of config; we will do doctesting | ||
|  |         config.plugins.plugins = [p for p in config.plugins.plugins | ||
|  |                                   if p.name != 'doctest'] | ||
|  |         self.doctest_tests = options.ipdoctest_tests | ||
|  |         self.extension = tolist(options.ipdoctest_extension) | ||
|  | 
 | ||
|  |         self.parser = IPDocTestParser() | ||
|  |         self.finder = DocTestFinder(parser=self.parser) | ||
|  |         self.checker = IPDoctestOutputChecker() | ||
|  |         self.globs = None | ||
|  |         self.extraglobs = None |