Chris@87: # These classes implement a doctest runner plugin for nose, a "known failure" Chris@87: # error class, and a customized TestProgram for NumPy. Chris@87: Chris@87: # Because this module imports nose directly, it should not Chris@87: # be used except by nosetester.py to avoid a general NumPy Chris@87: # dependency on nose. Chris@87: from __future__ import division, absolute_import, print_function Chris@87: Chris@87: import os Chris@87: import doctest Chris@87: Chris@87: import nose Chris@87: from nose.plugins import doctests as npd Chris@87: from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin Chris@87: from nose.plugins.base import Plugin Chris@87: from nose.util import src Chris@87: import numpy Chris@87: from .nosetester import get_package_name Chris@87: import inspect Chris@87: Chris@87: # Some of the classes in this module begin with 'Numpy' to clearly distinguish Chris@87: # them from the plethora of very similar names from nose/unittest/doctest Chris@87: Chris@87: #----------------------------------------------------------------------------- Chris@87: # Modified version of the one in the stdlib, that fixes a python bug (doctests Chris@87: # not found in extension modules, http://bugs.python.org/issue3158) Chris@87: class NumpyDocTestFinder(doctest.DocTestFinder): Chris@87: Chris@87: def _from_module(self, module, object): Chris@87: """ Chris@87: Return true if the given object is defined in the given Chris@87: module. Chris@87: """ Chris@87: if module is None: Chris@87: #print '_fm C1' # dbg Chris@87: return True Chris@87: elif inspect.isfunction(object): Chris@87: #print '_fm C2' # dbg Chris@87: return module.__dict__ is object.__globals__ Chris@87: elif inspect.isbuiltin(object): Chris@87: #print '_fm C2-1' # dbg Chris@87: return module.__name__ == object.__module__ Chris@87: elif inspect.isclass(object): Chris@87: #print '_fm C3' # dbg Chris@87: return module.__name__ == object.__module__ Chris@87: elif inspect.ismethod(object): Chris@87: # This one may be a bug in cython that fails to correctly set the Chris@87: # __module__ attribute of methods, but since the same error is easy Chris@87: # to make by extension code writers, having this safety in place Chris@87: # isn't such a bad idea Chris@87: #print '_fm C3-1' # dbg Chris@87: return module.__name__ == object.__self__.__class__.__module__ Chris@87: elif inspect.getmodule(object) is not None: Chris@87: #print '_fm C4' # dbg Chris@87: #print 'C4 mod',module,'obj',object # dbg Chris@87: return module is inspect.getmodule(object) Chris@87: elif hasattr(object, '__module__'): Chris@87: #print '_fm C5' # dbg Chris@87: return module.__name__ == object.__module__ Chris@87: elif isinstance(object, property): Chris@87: #print '_fm C6' # dbg Chris@87: return True # [XX] no way not be sure. Chris@87: else: Chris@87: raise ValueError("object must be a class or function") Chris@87: Chris@87: def _find(self, tests, obj, name, module, source_lines, globs, seen): Chris@87: """ Chris@87: Find tests for the given object and any contained objects, and Chris@87: add them to `tests`. Chris@87: """ Chris@87: Chris@87: doctest.DocTestFinder._find(self, tests, obj, name, module, Chris@87: source_lines, globs, seen) Chris@87: Chris@87: # Below we re-run pieces of the above method with manual modifications, Chris@87: # because the original code is buggy and fails to correctly identify Chris@87: # doctests in extension modules. Chris@87: Chris@87: # Local shorthands Chris@87: from inspect import isroutine, isclass, ismodule, isfunction, \ Chris@87: ismethod Chris@87: Chris@87: # Look for tests in a module's contained objects. Chris@87: if ismodule(obj) and self._recurse: Chris@87: for valname, val in obj.__dict__.items(): Chris@87: valname1 = '%s.%s' % (name, valname) Chris@87: if ( (isroutine(val) or isclass(val)) Chris@87: and self._from_module(module, val) ): Chris@87: Chris@87: self._find(tests, val, valname1, module, source_lines, Chris@87: globs, seen) Chris@87: Chris@87: Chris@87: # Look for tests in a class's contained objects. Chris@87: if isclass(obj) and self._recurse: Chris@87: #print 'RECURSE into class:',obj # dbg Chris@87: for valname, val in obj.__dict__.items(): Chris@87: #valname1 = '%s.%s' % (name, valname) # dbg Chris@87: #print 'N',name,'VN:',valname,'val:',str(val)[:77] # dbg Chris@87: # Special handling for staticmethod/classmethod. Chris@87: if isinstance(val, staticmethod): Chris@87: val = getattr(obj, valname) Chris@87: if isinstance(val, classmethod): Chris@87: val = getattr(obj, valname).__func__ Chris@87: Chris@87: # Recurse to methods, properties, and nested classes. Chris@87: if ((isfunction(val) or isclass(val) or Chris@87: ismethod(val) or isinstance(val, property)) and Chris@87: self._from_module(module, val)): Chris@87: valname = '%s.%s' % (name, valname) Chris@87: self._find(tests, val, valname, module, source_lines, Chris@87: globs, seen) Chris@87: Chris@87: Chris@87: # second-chance checker; if the default comparison doesn't Chris@87: # pass, then see if the expected output string contains flags that Chris@87: # tell us to ignore the output Chris@87: class NumpyOutputChecker(doctest.OutputChecker): Chris@87: def check_output(self, want, got, optionflags): Chris@87: ret = doctest.OutputChecker.check_output(self, want, got, Chris@87: optionflags) Chris@87: if not ret: Chris@87: if "#random" in want: Chris@87: return True Chris@87: Chris@87: # it would be useful to normalize endianness so that Chris@87: # bigendian machines don't fail all the tests (and there are Chris@87: # actually some bigendian examples in the doctests). Let's try Chris@87: # making them all little endian Chris@87: got = got.replace("'>", "'<") Chris@87: want= want.replace("'>", "'<") Chris@87: Chris@87: # try to normalize out 32 and 64 bit default int sizes Chris@87: for sz in [4, 8]: Chris@87: got = got.replace("'