diff --git a/doc/source/api.rst b/doc/source/api.rst index 3c7ca6d5c2326..88efe27b16c2b 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -194,6 +194,14 @@ Top-level evaluation eval +Testing +~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + test + .. _api.series: Series diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index 8d8ff74b6c04e..05d5eb67d7a32 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -509,6 +509,15 @@ entire suite. This is done using one of the following constructs:: nosetests pandas/tests/[test-module].py:[TestClass] nosetests pandas/tests/[test-module].py:[TestClass].[test_method] + .. versionadded:: 0.18.0 + +Furthermore one can run + +.. code-block:: python + + pd.test() + +with an imported pandas to run tests similarly. Running the performance test suite ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/whatsnew/v0.18.0.txt b/doc/source/whatsnew/v0.18.0.txt index 2c97cae80ae2a..5149c91d04701 100644 --- a/doc/source/whatsnew/v0.18.0.txt +++ b/doc/source/whatsnew/v0.18.0.txt @@ -14,6 +14,7 @@ users upgrade to this version. Highlights include: - Window functions are now methods on ``.groupby`` like objects, see :ref:`here `. +- ``pd.test()`` top-level nose test runner is available (:issue:`4327`) Check the :ref:`API Changes ` and :ref:`deprecations ` before updating. diff --git a/pandas/__init__.py b/pandas/__init__.py index 68a90394cacf1..c2ead16b6f821 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -55,7 +55,12 @@ from pandas.tools.util import to_numeric from pandas.core.reshape import melt from pandas.util.print_versions import show_versions + +# define the testing framework import pandas.util.testing +from pandas.util.nosetester import NoseTester +test = NoseTester().test +del NoseTester # use the closest tagged version if possible from ._version import get_versions diff --git a/pandas/util/nosetester.py b/pandas/util/nosetester.py new file mode 100644 index 0000000000000..eee5dfee809be --- /dev/null +++ b/pandas/util/nosetester.py @@ -0,0 +1,207 @@ +""" +Nose test running. + +This module implements ``test()`` function for pandas modules. + +""" +from __future__ import division, absolute_import, print_function + +import os +import sys +import warnings +from pandas.compat import string_types +from numpy.testing import nosetester + + +def get_package_name(filepath): + """ + Given a path where a package is installed, determine its name. + + Parameters + ---------- + filepath : str + Path to a file. If the determination fails, "pandas" is returned. + + Examples + -------- + >>> pandas.util.nosetester.get_package_name('nonsense') + 'pandas' + + """ + + pkg_name = [] + while 'site-packages' in filepath or 'dist-packages' in filepath: + filepath, p2 = os.path.split(filepath) + if p2 in ('site-packages', 'dist-packages'): + break + pkg_name.append(p2) + + # if package name determination failed, just default to pandas + if not pkg_name: + return "pandas" + + # otherwise, reverse to get correct order and return + pkg_name.reverse() + + # don't include the outer egg directory + if pkg_name[0].endswith('.egg'): + pkg_name.pop(0) + + return '.'.join(pkg_name) + +import_nose = nosetester.import_nose +run_module_suite = nosetester.run_module_suite + + +class NoseTester(nosetester.NoseTester): + """ + Nose test runner. + + This class is made available as pandas.util.nosetester.NoseTester, 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 sub-packages. + + Attributes + ---------- + package_path : str + Full path to the package to test. + package_name : str + Name of the package to test. + + Parameters + ---------- + package : module, str or None, optional + The package to test. If a string, this should be the full path to + the package. If None (default), `package` is set to the module from + which `NoseTester` is initialized. + raise_warnings : None, str or sequence of warnings, optional + This specifies which warnings to configure as 'raise' instead + of 'warn' during the test execution. Valid strings are: + + - "develop" : equals ``(DeprecationWarning, RuntimeWarning)`` + - "release" : equals ``()``, don't raise on any warnings. + + See Notes for more details. + + Notes + ----- + The default for `raise_warnings` is + ``(DeprecationWarning, RuntimeWarning)`` for development versions of + pandas, and ``()`` for released versions. The purpose of this switching + behavior is to catch as many warnings as possible during development, but + not give problems for packaging of released versions. + + """ + excludes = [] + + def _show_system_info(self): + nose = import_nose() + + import pandas + print("pandas version %s" % pandas.__version__) + import numpy + print("numpy version %s" % numpy.__version__) + pddir = os.path.dirname(pandas.__file__) + print("pandas is installed in %s" % pddir) + + pyversion = sys.version.replace('\n', '') + print("Python version %s" % pyversion) + print("nose version %d.%d.%d" % nose.__versioninfo__) + + def _get_custom_doctester(self): + """ Return instantiated plugin for doctests + + Allows subclassing of this class to override doctester + + A return value of None means use the nose builtin doctest plugin + """ + return None + + def test(self, label='fast', verbose=1, extra_argv=None, + doctests=False, coverage=False, raise_warnings=None): + """ + Run tests for module using nose. + + Parameters + ---------- + label : {'fast', 'full', '', attribute identifier}, optional + Identifies the tests 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 the ``nosetests -A`` + option of 'not slow'. + * 'full' - fast (as above) and slow tests as in the + 'no -A' option to nosetests - this is the same as ''. + * None or '' - run all tests. + attribute_identifier - string passed directly to nosetests as '-A'. + verbose : int, optional + Verbosity value for test outputs, in the range 1-10. Default is 1. + extra_argv : list, optional + List with any extra arguments to pass to nosetests. + doctests : bool, optional + If True, run doctests in module. Default is False. + coverage : bool, optional + If True, report coverage of NumPy code. Default is False. + (This requires the `coverage module: + `_). + raise_warnings : str or sequence of warnings, optional + This specifies which warnings to configure as 'raise' instead + of 'warn' during the test execution. Valid strings are: + + - "develop" : equals ``(DeprecationWarning, RuntimeWarning)`` + - "release" : equals ``()``, don't raise on any warnings. + + Returns + ------- + result : object + Returns the result of running the tests as a + ``nose.result.TextTestResult`` object. + """ + + # cap verbosity at 3 because nose becomes *very* verbose beyond that + verbose = min(verbose, 3) + + if doctests: + print("Running unit tests and doctests for %s" % self.package_name) + else: + print("Running unit tests for %s" % self.package_name) + + self._show_system_info() + + # reset doctest state on every run + import doctest + doctest.master = None + + if raise_warnings is None: + raise_warnings = 'release' + + _warn_opts = dict(develop=(DeprecationWarning, RuntimeWarning), + release=()) + if isinstance(raise_warnings, string_types): + raise_warnings = _warn_opts[raise_warnings] + + with warnings.catch_warnings(): + # Reset the warning filters to the default state, + # so that running the tests is more repeatable. + warnings.resetwarnings() + # Set all warnings to 'warn', this is because the default 'once' + # has the bad property of possibly shadowing later warnings. + warnings.filterwarnings('always') + # Force the requested warnings to raise + for warningtype in raise_warnings: + warnings.filterwarnings('error', category=warningtype) + # Filter out annoying import messages. + warnings.filterwarnings("ignore", category=FutureWarning) + + from numpy.testing.noseclasses import NumpyTestProgram + + argv, plugins = self.prepare_test_args( + label, verbose, extra_argv, doctests, coverage) + t = NumpyTestProgram(argv=argv, exit=False, plugins=plugins) + + return t.result