''' Nose test running Implements test and bench functions for modules. ''' import os import sys import warnings def get_package_name(filepath): # find the package name given a path name that's part of the package fullpath = filepath[:] pkg_name = [] while 'site-packages' in filepath: filepath, p2 = os.path.split(filepath) if p2 == 'site-packages': break pkg_name.append(p2) # if package name determination failed, just default to numpy/scipy if not pkg_name: if 'scipy' in fullpath: return 'scipy' else: return 'numpy' # otherwise, reverse to get correct order and return pkg_name.reverse() return '.'.join(pkg_name) def import_nose(): """ Import nose only when needed. """ fine_nose = True minimum_nose_version = (0,10,0) try: import nose from nose.tools import raises except ImportError: fine_nose = False else: if nose.__versioninfo__ < minimum_nose_version: fine_nose = False if not fine_nose: msg = 'Need nose >= %d.%d.%d for tests - see ' \ 'http://somethingaboutorange.com/mrl/projects/nose' % \ minimum_nose_version raise ImportError(msg) return nose def run_module_suite(file_to_run = None): if file_to_run is None: f = sys._getframe(1) file_to_run = f.f_locals.get('__file__', None) assert file_to_run is not None import_nose().run(argv=['',file_to_run]) # contructs NoseTester method docstrings def _docmethod(meth, testtype): if not meth.__doc__: return test_header = \ '''Parameters ---------- label : {'fast', 'full', '', attribute identifer} Identifies the %(testtype)ss to run. This can be a string to pass to the nosetests executable with the '-A' option, or one of several special values. Special values are: 'fast' - the default - which corresponds to nosetests -A option of 'not slow'. 'full' - fast (as above) and slow %(testtype)ss as in the no -A option to nosetests - same as '' None or '' - run all %(testtype)ss attribute_identifier - string passed directly to nosetests as '-A' verbose : integer verbosity value for test outputs, 1-10 extra_argv : list List with any extra args to pass to nosetests''' \ % {'testtype': testtype} meth.__doc__ = meth.__doc__ % {'test_header':test_header} class NoseTester(object): """ Nose test runner. Usage: NoseTester().test() is package path or module Default for package is None. A value of None finds the calling module path. This class is made available as numpy.testing.Tester, and a test function is typically added to a package's __init__.py like so: >>> from numpy.testing import Tester >>> test = Tester().test Calling this test function finds and runs all tests associated with the package and all its subpackages. """ def __init__(self, package=None): ''' Test class init Parameters ---------- package : string or module If string, gives full path to package If None, extract calling module path Default is None ''' if package is None: f = sys._getframe(1) package = f.f_locals.get('__file__', None) assert package is not None package = os.path.dirname(package) elif isinstance(package, type(os)): package = os.path.dirname(package.__file__) self.package_path = package # find the package name under test; this name is used to limit coverage # reporting (if enabled) self.package_name = get_package_name(package) def _test_argv(self, label, verbose, extra_argv): ''' Generate argv for nosetest command %(test_header)s ''' argv = [__file__, self.package_path, '-s'] if label and label != 'full': if not isinstance(label, basestring): raise TypeError, 'Selection label should be a string' if label == 'fast': label = 'not slow' argv += ['-A', label] argv += ['--verbosity', str(verbose)] if extra_argv: argv += extra_argv return argv def _show_system_info(self): nose = import_nose() import numpy print "NumPy version %s" % numpy.__version__ npdir = os.path.dirname(numpy.__file__) print "NumPy is installed in %s" % npdir if 'scipy' in self.package_name: import scipy print "SciPy version %s" % scipy.__version__ spdir = os.path.dirname(scipy.__file__) print "SciPy is installed in %s" % spdir pyversion = sys.version.replace('\n','') print "Python version %s" % pyversion print "nose version %d.%d.%d" % nose.__versioninfo__ def test(self, label='fast', verbose=1, extra_argv=None, doctests=False, coverage=False, **kwargs): ''' Run tests for module using nose %(test_header)s doctests : boolean If True, run doctests in module, default False coverage : boolean If True, report coverage of NumPy code, default False (Requires the coverage module: http://nedbatchelder.com/code/modules/coverage.html) ''' old_args = set(['level', 'verbosity', 'all', 'sys_argv', 'testcase_pattern']) unexpected_args = set(kwargs.keys()) - old_args if len(unexpected_args) > 0: ua = ', '.join(unexpected_args) raise TypeError("test() got unexpected arguments: %s" % ua) # issue a deprecation warning if any of the pre-1.2 arguments to # test are given if old_args.intersection(kwargs.keys()): warnings.warn("This method's signature will change in the next " \ "release; the level, verbosity, all, sys_argv, " \ "and testcase_pattern keyword arguments will be " \ "removed. Please update your code.", DeprecationWarning, stacklevel=2) # Use old arguments if given (where it makes sense) # For the moment, level and sys_argv are ignored # replace verbose with verbosity if kwargs.get('verbosity') is not None: verbose = kwargs.get('verbosity') # cap verbosity at 3 because nose becomes *very* verbose beyond that verbose = min(verbose, 3) import utils utils.verbose = verbose # if all evaluates as True, omit attribute filter and run doctests if kwargs.get('all'): label = '' doctests = True # if doctests is in the extra args, remove it and set the doctest # flag so the NumPy doctester is used instead if extra_argv and '--with-doctest' in extra_argv: extra_argv.remove('--with-doctest') doctests = True argv = self._test_argv(label, verbose, extra_argv) if doctests: argv += ['--with-numpydoctest'] print "Running unit tests and doctests for %s" % self.package_name else: print "Running unit tests for %s" % self.package_name if coverage: argv+=['--cover-package=%s' % self.package_name, '--with-coverage', '--cover-tests', '--cover-inclusive', '--cover-erase'] # bypass these samples under distutils argv += ['--exclude','f2py_ext'] argv += ['--exclude','f2py_f90_ext'] argv += ['--exclude','gen_ext'] argv += ['--exclude','pyrex_ext'] argv += ['--exclude','swig_ext'] argv += ['--exclude','array_from_pyobj'] self._show_system_info() nose = import_nose() # Because nose currently discards the test result object, but we need # to return it to the user, override TestProgram.runTests to retain # the result class NumpyTestProgram(nose.core.TestProgram): def runTests(self): """Run Tests. Returns true on success, false on failure, and sets self.success to the same value. """ if self.testRunner is None: self.testRunner = nose.core.TextTestRunner(stream=self.config.stream, verbosity=self.config.verbosity, config=self.config) plug_runner = self.config.plugins.prepareTestRunner(self.testRunner) if plug_runner is not None: self.testRunner = plug_runner self.result = self.testRunner.run(self.test) self.success = self.result.wasSuccessful() return self.success # reset doctest state on every run import doctest doctest.master = None # construct list of plugins, omitting the existing doctest plugin import nose.plugins.builtin from noseclasses import NumpyDoctest, KnownFailure plugins = [NumpyDoctest(), KnownFailure()] for p in nose.plugins.builtin.plugins: plug = p() if plug.name == 'doctest': continue plugins.append(plug) t = NumpyTestProgram(argv=argv, exit=False, plugins=plugins) return t.result def bench(self, label='fast', verbose=1, extra_argv=None): ''' Run benchmarks for module using nose %(test_header)s''' print "Running benchmarks for %s" % self.package_name self._show_system_info() nose = import_nose() argv = self._test_argv(label, verbose, extra_argv) argv += ['--match', r'(?:^|[\\b_\\.%s-])[Bb]ench' % os.sep] return nose.run(argv=argv) # generate method docstrings _docmethod(_test_argv, '(testtype)') _docmethod(test, 'test') _docmethod(bench, 'benchmark') ######################################################################## # Doctests for NumPy-specific nose/doctest modifications # try the #random directive on the output line def check_random_directive(): ''' >>> 2+2 #random: may vary on your system ''' # check the implicit "import numpy as np" def check_implicit_np(): ''' >>> np.array([1,2,3]) array([1, 2, 3]) ''' # there's some extraneous whitespace around the correct responses def check_whitespace_enabled(): ''' # whitespace after the 3 >>> 1+2 3 # whitespace before the 7 >>> 3+4 7 '''