Chris@87: """ Chris@87: Nose test running. Chris@87: Chris@87: This module implements ``test()`` and ``bench()`` functions for NumPy modules. Chris@87: Chris@87: """ Chris@87: from __future__ import division, absolute_import, print_function Chris@87: Chris@87: import os Chris@87: import sys Chris@87: import warnings Chris@87: from numpy.compat import basestring Chris@87: from numpy import ModuleDeprecationWarning Chris@87: Chris@87: Chris@87: def get_package_name(filepath): Chris@87: """ Chris@87: Given a path where a package is installed, determine its name. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: filepath : str Chris@87: Path to a file. If the determination fails, "numpy" is returned. Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: >>> np.testing.nosetester.get_package_name('nonsense') Chris@87: 'numpy' Chris@87: Chris@87: """ Chris@87: Chris@87: fullpath = filepath[:] Chris@87: pkg_name = [] Chris@87: while 'site-packages' in filepath or 'dist-packages' in filepath: Chris@87: filepath, p2 = os.path.split(filepath) Chris@87: if p2 in ('site-packages', 'dist-packages'): Chris@87: break Chris@87: pkg_name.append(p2) Chris@87: Chris@87: # if package name determination failed, just default to numpy/scipy Chris@87: if not pkg_name: Chris@87: if 'scipy' in fullpath: Chris@87: return 'scipy' Chris@87: else: Chris@87: return 'numpy' Chris@87: Chris@87: # otherwise, reverse to get correct order and return Chris@87: pkg_name.reverse() Chris@87: Chris@87: # don't include the outer egg directory Chris@87: if pkg_name[0].endswith('.egg'): Chris@87: pkg_name.pop(0) Chris@87: Chris@87: return '.'.join(pkg_name) Chris@87: Chris@87: def import_nose(): Chris@87: """ Import nose only when needed. Chris@87: """ Chris@87: fine_nose = True Chris@87: minimum_nose_version = (0, 10, 0) Chris@87: try: Chris@87: import nose Chris@87: except ImportError: Chris@87: fine_nose = False Chris@87: else: Chris@87: if nose.__versioninfo__ < minimum_nose_version: Chris@87: fine_nose = False Chris@87: Chris@87: if not fine_nose: Chris@87: msg = 'Need nose >= %d.%d.%d for tests - see ' \ Chris@87: 'http://somethingaboutorange.com/mrl/projects/nose' % \ Chris@87: minimum_nose_version Chris@87: Chris@87: raise ImportError(msg) Chris@87: Chris@87: return nose Chris@87: Chris@87: def run_module_suite(file_to_run=None, argv=None): Chris@87: """ Chris@87: Run a test module. Chris@87: Chris@87: Equivalent to calling ``$ nosetests `` from Chris@87: the command line Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: file_to_run: str, optional Chris@87: Path to test module, or None. Chris@87: By default, run the module from which this function is called. Chris@87: argv: list of strings Chris@87: Arguments to be passed to the nose test runner. ``argv[0]`` is Chris@87: ignored. All command line arguments accepted by ``nosetests`` Chris@87: will work. Chris@87: Chris@87: .. versionadded:: 1.9.0 Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: Adding the following:: Chris@87: Chris@87: if __name__ == "__main__" : Chris@87: run_module_suite(argv=sys.argv) Chris@87: Chris@87: at the end of a test module will run the tests when that module is Chris@87: called in the python interpreter. Chris@87: Chris@87: Alternatively, calling:: Chris@87: Chris@87: >>> run_module_suite(file_to_run="numpy/tests/test_matlib.py") Chris@87: Chris@87: from an interpreter will run all the test routine in 'test_matlib.py'. Chris@87: """ Chris@87: if file_to_run is None: Chris@87: f = sys._getframe(1) Chris@87: file_to_run = f.f_locals.get('__file__', None) Chris@87: if file_to_run is None: Chris@87: raise AssertionError Chris@87: Chris@87: if argv is None: Chris@87: argv = ['', file_to_run] Chris@87: else: Chris@87: argv = argv + [file_to_run] Chris@87: Chris@87: nose = import_nose() Chris@87: from .noseclasses import KnownFailure Chris@87: nose.run(argv=argv, addplugins=[KnownFailure()]) Chris@87: Chris@87: Chris@87: class NoseTester(object): Chris@87: """ Chris@87: Nose test runner. Chris@87: Chris@87: This class is made available as numpy.testing.Tester, and a test function Chris@87: is typically added to a package's __init__.py like so:: Chris@87: Chris@87: from numpy.testing import Tester Chris@87: test = Tester().test Chris@87: Chris@87: Calling this test function finds and runs all tests associated with the Chris@87: package and all its sub-packages. Chris@87: Chris@87: Attributes Chris@87: ---------- Chris@87: package_path : str Chris@87: Full path to the package to test. Chris@87: package_name : str Chris@87: Name of the package to test. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: package : module, str or None, optional Chris@87: The package to test. If a string, this should be the full path to Chris@87: the package. If None (default), `package` is set to the module from Chris@87: which `NoseTester` is initialized. Chris@87: raise_warnings : str or sequence of warnings, optional Chris@87: This specifies which warnings to configure as 'raise' instead Chris@87: of 'warn' during the test execution. Valid strings are: Chris@87: Chris@87: - "develop" : equals ``(DeprecationWarning, RuntimeWarning)`` Chris@87: - "release" : equals ``()``, don't raise on any warnings. Chris@87: Chris@87: See Notes for more details. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The default for `raise_warnings` is Chris@87: ``(DeprecationWarning, RuntimeWarning)`` for the master branch of NumPy, Chris@87: and ``()`` for maintenance branches and released versions. The purpose Chris@87: of this switching behavior is to catch as many warnings as possible Chris@87: during development, but not give problems for packaging of released Chris@87: versions. Chris@87: Chris@87: """ Chris@87: # Stuff to exclude from tests. These are from numpy.distutils Chris@87: excludes = ['f2py_ext', Chris@87: 'f2py_f90_ext', Chris@87: 'gen_ext', Chris@87: 'pyrex_ext', Chris@87: 'swig_ext'] Chris@87: Chris@87: def __init__(self, package=None, raise_warnings="release"): Chris@87: package_name = None Chris@87: if package is None: Chris@87: f = sys._getframe(1) Chris@87: package_path = f.f_locals.get('__file__', None) Chris@87: if package_path is None: Chris@87: raise AssertionError Chris@87: package_path = os.path.dirname(package_path) Chris@87: package_name = f.f_locals.get('__name__', None) Chris@87: elif isinstance(package, type(os)): Chris@87: package_path = os.path.dirname(package.__file__) Chris@87: package_name = getattr(package, '__name__', None) Chris@87: else: Chris@87: package_path = str(package) Chris@87: Chris@87: self.package_path = package_path Chris@87: Chris@87: # Find the package name under test; this name is used to limit coverage Chris@87: # reporting (if enabled). Chris@87: if package_name is None: Chris@87: package_name = get_package_name(package_path) Chris@87: self.package_name = package_name Chris@87: Chris@87: # Set to "release" in constructor in maintenance branches. Chris@87: self.raise_warnings = raise_warnings Chris@87: Chris@87: def _test_argv(self, label, verbose, extra_argv): Chris@87: ''' Generate argv for nosetest command Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: label : {'fast', 'full', '', attribute identifier}, optional Chris@87: see ``test`` docstring Chris@87: verbose : int, optional Chris@87: Verbosity value for test outputs, in the range 1-10. Default is 1. Chris@87: extra_argv : list, optional Chris@87: List with any extra arguments to pass to nosetests. Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: argv : list Chris@87: command line arguments that will be passed to nose Chris@87: ''' Chris@87: argv = [__file__, self.package_path, '-s'] Chris@87: if label and label != 'full': Chris@87: if not isinstance(label, basestring): Chris@87: raise TypeError('Selection label should be a string') Chris@87: if label == 'fast': Chris@87: label = 'not slow' Chris@87: argv += ['-A', label] Chris@87: argv += ['--verbosity', str(verbose)] Chris@87: Chris@87: # When installing with setuptools, and also in some other cases, the Chris@87: # test_*.py files end up marked +x executable. Nose, by default, does Chris@87: # not run files marked with +x as they might be scripts. However, in Chris@87: # our case nose only looks for test_*.py files under the package Chris@87: # directory, which should be safe. Chris@87: argv += ['--exe'] Chris@87: Chris@87: if extra_argv: Chris@87: argv += extra_argv Chris@87: return argv Chris@87: Chris@87: def _show_system_info(self): Chris@87: nose = import_nose() Chris@87: Chris@87: import numpy Chris@87: print("NumPy version %s" % numpy.__version__) Chris@87: npdir = os.path.dirname(numpy.__file__) Chris@87: print("NumPy is installed in %s" % npdir) Chris@87: Chris@87: if 'scipy' in self.package_name: Chris@87: import scipy Chris@87: print("SciPy version %s" % scipy.__version__) Chris@87: spdir = os.path.dirname(scipy.__file__) Chris@87: print("SciPy is installed in %s" % spdir) Chris@87: Chris@87: pyversion = sys.version.replace('\n', '') Chris@87: print("Python version %s" % pyversion) Chris@87: print("nose version %d.%d.%d" % nose.__versioninfo__) Chris@87: Chris@87: def _get_custom_doctester(self): Chris@87: """ Return instantiated plugin for doctests Chris@87: Chris@87: Allows subclassing of this class to override doctester Chris@87: Chris@87: A return value of None means use the nose builtin doctest plugin Chris@87: """ Chris@87: from .noseclasses import NumpyDoctest Chris@87: return NumpyDoctest() Chris@87: Chris@87: def prepare_test_args(self, label='fast', verbose=1, extra_argv=None, Chris@87: doctests=False, coverage=False): Chris@87: """ Chris@87: Run tests for module using nose. Chris@87: Chris@87: This method does the heavy lifting for the `test` method. It takes all Chris@87: the same arguments, for details see `test`. Chris@87: Chris@87: See Also Chris@87: -------- Chris@87: test Chris@87: Chris@87: """ Chris@87: # fail with nice error message if nose is not present Chris@87: import_nose() Chris@87: # compile argv Chris@87: argv = self._test_argv(label, verbose, extra_argv) Chris@87: # bypass tests noted for exclude Chris@87: for ename in self.excludes: Chris@87: argv += ['--exclude', ename] Chris@87: # our way of doing coverage Chris@87: if coverage: Chris@87: argv+=['--cover-package=%s' % self.package_name, '--with-coverage', Chris@87: '--cover-tests', '--cover-erase'] Chris@87: # construct list of plugins Chris@87: import nose.plugins.builtin Chris@87: from .noseclasses import KnownFailure, Unplugger Chris@87: plugins = [KnownFailure()] Chris@87: plugins += [p() for p in nose.plugins.builtin.plugins] Chris@87: # add doctesting if required Chris@87: doctest_argv = '--with-doctest' in argv Chris@87: if doctests == False and doctest_argv: Chris@87: doctests = True Chris@87: plug = self._get_custom_doctester() Chris@87: if plug is None: Chris@87: # use standard doctesting Chris@87: if doctests and not doctest_argv: Chris@87: argv += ['--with-doctest'] Chris@87: else: # custom doctesting Chris@87: if doctest_argv: # in fact the unplugger would take care of this Chris@87: argv.remove('--with-doctest') Chris@87: plugins += [Unplugger('doctest'), plug] Chris@87: if doctests: Chris@87: argv += ['--with-' + plug.name] Chris@87: return argv, plugins Chris@87: Chris@87: def test(self, label='fast', verbose=1, extra_argv=None, Chris@87: doctests=False, coverage=False, Chris@87: raise_warnings=None): Chris@87: """ Chris@87: Run tests for module using nose. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: label : {'fast', 'full', '', attribute identifier}, optional Chris@87: Identifies the tests to run. This can be a string to pass to Chris@87: the nosetests executable with the '-A' option, or one of several Chris@87: special values. Special values are: Chris@87: * 'fast' - the default - which corresponds to the ``nosetests -A`` Chris@87: option of 'not slow'. Chris@87: * 'full' - fast (as above) and slow tests as in the Chris@87: 'no -A' option to nosetests - this is the same as ''. Chris@87: * None or '' - run all tests. Chris@87: attribute_identifier - string passed directly to nosetests as '-A'. Chris@87: verbose : int, optional Chris@87: Verbosity value for test outputs, in the range 1-10. Default is 1. Chris@87: extra_argv : list, optional Chris@87: List with any extra arguments to pass to nosetests. Chris@87: doctests : bool, optional Chris@87: If True, run doctests in module. Default is False. Chris@87: coverage : bool, optional Chris@87: If True, report coverage of NumPy code. Default is False. Chris@87: (This requires the `coverage module: Chris@87: `_). Chris@87: raise_warnings : str or sequence of warnings, optional Chris@87: This specifies which warnings to configure as 'raise' instead Chris@87: of 'warn' during the test execution. Valid strings are: Chris@87: Chris@87: - "develop" : equals ``(DeprecationWarning, RuntimeWarning)`` Chris@87: - "release" : equals ``()``, don't raise on any warnings. Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: result : object Chris@87: Returns the result of running the tests as a Chris@87: ``nose.result.TextTestResult`` object. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: Each NumPy module exposes `test` in its namespace to run all tests for it. Chris@87: For example, to run all tests for numpy.lib: Chris@87: Chris@87: >>> np.lib.test() #doctest: +SKIP Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: >>> result = np.lib.test() #doctest: +SKIP Chris@87: Running unit tests for numpy.lib Chris@87: ... Chris@87: Ran 976 tests in 3.933s Chris@87: Chris@87: OK Chris@87: Chris@87: >>> result.errors #doctest: +SKIP Chris@87: [] Chris@87: >>> result.knownfail #doctest: +SKIP Chris@87: [] Chris@87: """ Chris@87: Chris@87: # cap verbosity at 3 because nose becomes *very* verbose beyond that Chris@87: verbose = min(verbose, 3) Chris@87: Chris@87: from . import utils Chris@87: utils.verbose = verbose Chris@87: Chris@87: if doctests: Chris@87: print("Running unit tests and doctests for %s" % self.package_name) Chris@87: else: Chris@87: print("Running unit tests for %s" % self.package_name) Chris@87: Chris@87: self._show_system_info() Chris@87: Chris@87: # reset doctest state on every run Chris@87: import doctest Chris@87: doctest.master = None Chris@87: Chris@87: if raise_warnings is None: Chris@87: raise_warnings = self.raise_warnings Chris@87: Chris@87: _warn_opts = dict(develop=(DeprecationWarning, RuntimeWarning), Chris@87: release=()) Chris@87: if raise_warnings in _warn_opts.keys(): Chris@87: raise_warnings = _warn_opts[raise_warnings] Chris@87: Chris@87: with warnings.catch_warnings(): Chris@87: # Reset the warning filters to the default state, Chris@87: # so that running the tests is more repeatable. Chris@87: warnings.resetwarnings() Chris@87: # If deprecation warnings are not set to 'error' below, Chris@87: # at least set them to 'warn'. Chris@87: warnings.filterwarnings('always', category=DeprecationWarning) Chris@87: # Force the requested warnings to raise Chris@87: for warningtype in raise_warnings: Chris@87: warnings.filterwarnings('error', category=warningtype) Chris@87: # Filter out annoying import messages. Chris@87: warnings.filterwarnings('ignore', message='Not importing directory') Chris@87: warnings.filterwarnings("ignore", message="numpy.dtype size changed") Chris@87: warnings.filterwarnings("ignore", message="numpy.ufunc size changed") Chris@87: warnings.filterwarnings("ignore", category=ModuleDeprecationWarning) Chris@87: warnings.filterwarnings("ignore", category=FutureWarning) Chris@87: # Filter out boolean '-' deprecation messages. This allows Chris@87: # older versions of scipy to test without a flood of messages. Chris@87: warnings.filterwarnings("ignore", message=".*boolean negative.*") Chris@87: warnings.filterwarnings("ignore", message=".*boolean subtract.*") Chris@87: Chris@87: from .noseclasses import NumpyTestProgram Chris@87: Chris@87: argv, plugins = self.prepare_test_args( Chris@87: label, verbose, extra_argv, doctests, coverage) Chris@87: t = NumpyTestProgram(argv=argv, exit=False, plugins=plugins) Chris@87: Chris@87: return t.result Chris@87: Chris@87: def bench(self, label='fast', verbose=1, extra_argv=None): Chris@87: """ Chris@87: Run benchmarks for module using nose. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: label : {'fast', 'full', '', attribute identifier}, optional Chris@87: Identifies the benchmarks to run. This can be a string to pass to Chris@87: the nosetests executable with the '-A' option, or one of several Chris@87: special values. Special values are: Chris@87: * 'fast' - the default - which corresponds to the ``nosetests -A`` Chris@87: option of 'not slow'. Chris@87: * 'full' - fast (as above) and slow benchmarks as in the Chris@87: 'no -A' option to nosetests - this is the same as ''. Chris@87: * None or '' - run all tests. Chris@87: attribute_identifier - string passed directly to nosetests as '-A'. Chris@87: verbose : int, optional Chris@87: Verbosity value for benchmark outputs, in the range 1-10. Default is 1. Chris@87: extra_argv : list, optional Chris@87: List with any extra arguments to pass to nosetests. Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: success : bool Chris@87: Returns True if running the benchmarks works, False if an error Chris@87: occurred. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: Benchmarks are like tests, but have names starting with "bench" instead Chris@87: of "test", and can be found under the "benchmarks" sub-directory of the Chris@87: module. Chris@87: Chris@87: Each NumPy module exposes `bench` in its namespace to run all benchmarks Chris@87: for it. Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: >>> success = np.lib.bench() #doctest: +SKIP Chris@87: Running benchmarks for numpy.lib Chris@87: ... Chris@87: using 562341 items: Chris@87: unique: Chris@87: 0.11 Chris@87: unique1d: Chris@87: 0.11 Chris@87: ratio: 1.0 Chris@87: nUnique: 56230 == 56230 Chris@87: ... Chris@87: OK Chris@87: Chris@87: >>> success #doctest: +SKIP Chris@87: True Chris@87: Chris@87: """ Chris@87: Chris@87: print("Running benchmarks for %s" % self.package_name) Chris@87: self._show_system_info() Chris@87: Chris@87: argv = self._test_argv(label, verbose, extra_argv) Chris@87: argv += ['--match', r'(?:^|[\\b_\\.%s-])[Bb]ench' % os.sep] Chris@87: Chris@87: # import nose or make informative error Chris@87: nose = import_nose() Chris@87: Chris@87: # get plugin to disable doctests Chris@87: from .noseclasses import Unplugger Chris@87: add_plugins = [Unplugger('doctest')] Chris@87: Chris@87: return nose.run(argv=argv, addplugins=add_plugins)