diff --git a/.travis.yml b/.travis.yml index fefdf90e7aeb..e885924c7489 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ env: - secure: RgJI7BBL8aX5FTOQe7xiXqWHMxWokd6GNUWp1NUV2mRLXPb9dI0RXqZt3UJwKTAzf1z/OtlHDmEkBoTVK81E9iUxK5npwyyjhJ8yTJmwfQtQF2n51Q1Ww9p+XSLORrOzZc7kAo6Kw6FIXN1pfctgYq2bQkrwJPRx/oPR8f6hcbY= - secure: E7OCdqhZ+PlwJcn+Hd6ns9TDJgEUXiUNEI0wu7xjxB2vBRRIKtZMbuaZjd+iKDqCKuVOJKu0ClBUYxmgmpLicTwi34CfTUYt6D4uhrU+8hBBOn1iiK51cl/aBvlUUrqaRLVhukNEBGZcyqAjXSA/Qsnp2iELEmAfOUa92ZYo1sk= - BUILD_DOCS=false - - TEST_ARGS=--no-pep8 + - TEST_ARGS=--omit-pep8 language: python @@ -16,7 +16,7 @@ matrix: - python: 3.3 - python: 3.4 - python: 2.7 - env: TEST_ARGS=--pep8 + env: TEST_ARGS=--pep8-only - python: 2.7 env: BUILD_DOCS=true @@ -36,9 +36,7 @@ script: # Generate the font caches in a single process before starting the # multiple processes - python -c "from matplotlib import font_manager" - - if [[ $BUILD_DOCS == false ]]; then mkdir ../tmp_test_dir; fi - - if [[ $BUILD_DOCS == false ]]; then cd ../tmp_test_dir; fi - - if [[ $BUILD_DOCS == false ]]; then python ../matplotlib/tests.py -sv --processes=8 --process-timeout=300 $TEST_ARGS; fi + - if [[ $BUILD_DOCS == false ]]; then python setup.py test --nocapture --nose-verbose --processes=8 --process-timeout=300 $TEST_ARGS; fi - if [[ $BUILD_DOCS == true ]]; then cd doc; python make.py html --small; fi # We don't build the LaTeX docs here, so linkchecker will complain - if [[ $BUILD_DOCS == true ]]; then touch build/html/Matplotlib.pdf; fi diff --git a/README.rst b/README.rst index 6e01ea7275cc..af3dbdd95b17 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Testing After installation, you can launch the test suite:: - python tests.py + python setup.py tests Consider reading http://matplotlib.org/devel/coding_guide.html#testing for more information. diff --git a/setup.py b/setup.py index aeeb5816f38b..4eec84ae1919 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ # This needs to be the very first thing to use distribute from distribute_setup import use_setuptools use_setuptools() +from setuptools.command.test import test as TestCommand import sys @@ -122,6 +123,105 @@ 'Topic :: Scientific/Engineering :: Visualization', ] + +class NoseTestCommand(TestCommand): + """Invoke unit tests using nose after an in-place build.""" + + description = "Invoke unit tests using nose after an in-place build." + user_options = [ + ("pep8-only", None, "pep8 checks"), + ("omit-pep8", None, "Do not perform pep8 checks"), + ("nocapture", None, "do not capture stdout (nosetests)"), + ("nose-verbose", None, "be verbose (nosetests)"), + ("processes=", None, "number of processes (nosetests)"), + ("process-timeout=", None, "process timeout (nosetests)"), + ] + + def initialize_options(self): + self.pep8_only = None + self.omit_pep8 = None + + # parameters passed to nose tests + self.processes = None + self.process_timeout = None + self.nose_verbose = None + self.nocapture = None + + def finalize_options(self): + self.test_args = [] + if self.pep8_only: + self.pep8_only = True + if self.omit_pep8: + self.omit_pep8 = True + + if self.pep8_only and self.omit_pep8: + from distutils.errors import DistutilsOptionError + raise DistutilsOptionError( + "You are using several options for the test command in an " + "incompatible manner. Please use either one of --pep8-only," + "--omit-pep8" + ) + + if self.processes: + self.test_args.append("--processes={prc}".format( + prc=self.processes)) + + if self.process_timeout: + self.test_args.append("--process-timeout={tout}".format( + tout=self.process_timeout)) + + if self.nose_verbose: + self.test_args.append("--verbose") + + if self.nocapture: + self.test_args.append("--nocapture") + + def run(self): + if self.distribution.install_requires: + self.distribution.fetch_build_eggs( + self.distribution.install_requires) + if self.distribution.tests_require: + self.distribution.fetch_build_eggs(self.distribution.tests_require) + + self.announce('running unittests with nose') + self.with_project_on_sys_path(self.run_tests) + + + def run_tests(self): + try: + import matplotlib + matplotlib.use('agg') + import nose + from matplotlib.testing.noseclasses import KnownFailure + from matplotlib import default_test_modules as testmodules + from matplotlib import font_manager + import time + # Make sure the font caches are created before starting any possibly + # parallel tests + if font_manager._fmcache is not None: + while not os.path.exists(font_manager._fmcache): + time.sleep(0.5) + plugins = [KnownFailure] + + # Nose doesn't automatically instantiate all of the plugins in the + # child processes, so we have to provide the multiprocess plugin + # with a list. + from nose.plugins import multiprocess + multiprocess._instantiate_plugins = plugins + + if self.omit_pep8: + testmodules.remove('matplotlib.tests.test_coding_standards') + elif self.pep8_only: + testmodules = ['matplotlib.tests.test_coding_standards'] + + nose.main(addplugins=[x() for x in plugins], + defaultTest=testmodules, + argv=['nosetests'] + self.test_args, + exit=False) + except ImportError: + sys.exit(-1) + + # One doesn't normally see `if __name__ == '__main__'` blocks in a setup.py, # however, this is needed on Windows to avoid creating infinite subprocesses # when using multiprocessing. @@ -136,6 +236,7 @@ package_dir = {'': 'lib'} install_requires = [] setup_requires = [] + tests_require = [] default_backend = None @@ -199,6 +300,7 @@ package_data[key] = list(set(val + package_data[key])) install_requires.extend(package.get_install_requires()) setup_requires.extend(package.get_setup_requires()) + tests_require.extend(package.get_tests_require()) # Write the default matplotlibrc file if default_backend is None: @@ -259,11 +361,13 @@ # List third-party Python packages that we require install_requires=install_requires, setup_requires=setup_requires, + tests_require=tests_require, # matplotlib has C/C++ extensions, so it's not zip safe. # Telling setuptools this prevents it from doing an automatic # check for zip safety. zip_safe=False, + cmdclass={'test': NoseTestCommand}, **extra_args ) diff --git a/setupext.py b/setupext.py index 0d0624b31333..3ac39352bd0b 100755 --- a/setupext.py +++ b/setupext.py @@ -414,6 +414,12 @@ def get_setup_requires(self): """ return [] + def get_tests_require(self): + """ + Get a list of Python packages that we require for executing tests. + """ + return [] + def _check_for_pkg_config(self, package, include_file, min_version=None, version=None): """ @@ -688,7 +694,7 @@ def get_package_data(self): 'tests/test_rcparams.rc' ]} - def get_install_requires(self): + def get_tests_require(self): requires = ['nose>=%s' % self.nose_min_version] if not sys.version_info >= (3, 3): requires += ['mock'] diff --git a/tests.py b/tests.py deleted file mode 100755 index d27055c75257..000000000000 --- a/tests.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -# -# This allows running the matplotlib tests from the command line: e.g. -# -# $ python tests.py -v -d -# -# The arguments are identical to the arguments accepted by nosetests. -# -# See https://nose.readthedocs.org/ for a detailed description of -# these options. - -import os -import sys -import time - -import matplotlib -matplotlib.use('agg') - -import nose -from matplotlib.testing.noseclasses import KnownFailure -from matplotlib import default_test_modules - -from matplotlib import font_manager -# Make sure the font caches are created before starting any possibly -# parallel tests -if font_manager._fmcache is not None: - while not os.path.exists(font_manager._fmcache): - time.sleep(0.5) - -plugins = [KnownFailure] - -# Nose doesn't automatically instantiate all of the plugins in the -# child processes, so we have to provide the multiprocess plugin with -# a list. -from nose.plugins import multiprocess -multiprocess._instantiate_plugins = plugins - -def run(): - nose.main(addplugins=[x() for x in plugins], - defaultTest=default_test_modules) - -if __name__ == '__main__': - if '--no-pep8' in sys.argv: - default_test_modules.remove('matplotlib.tests.test_coding_standards') - sys.argv.remove('--no-pep8') - elif '--pep8' in sys.argv: - default_test_modules = ['matplotlib.tests.test_coding_standards'] - sys.argv.remove('--pep8') - - run() diff --git a/tox.ini b/tox.ini index b34e6ea7af13..296cefb56281 100644 --- a/tox.ini +++ b/tox.ini @@ -13,4 +13,5 @@ commands = {envpython} {toxinidir}/tests.py --processes=-1 --process-timeout=300 deps = nose + mock numpy