diff --git a/.travis.yml b/.travis.yml index 98e12e3d76d2..a544ff11c67f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,7 +45,6 @@ env: - NPROC=2 - INSTALL_PEP8= - RUN_PEP8= - - NOSE_ARGS="-j $NPROC" - PYTEST_ARGS="-ra --maxfail=1 --timeout=300 --durations=25 --cov-report= --cov=lib -n $NPROC" - PYTHON_ARGS= - DELETE_FONT_CACHE= @@ -58,7 +57,7 @@ matrix: - python: 3.4 env: PYTHON_ARGS=-OO - python: 3.5 - env: PANDAS=pandas NOSE_ARGS=--with-coverage DELETE_FONT_CACHE=1 + env: PANDAS=pandas DELETE_FONT_CACHE=1 - python: 3.5 env: BUILD_DOCS=true - python: 3.5 @@ -70,7 +69,7 @@ matrix: - os: osx osx_image: xcode7.3 language: generic # https://github.com/travis-ci/travis-ci/issues/2312 - env: MOCK=mock NOSE_ARGS= + env: MOCK=mock cache: # As for now travis caches only "$HOME/.cache/pip" # https://docs.travis-ci.com/user/caching/#pip-cache @@ -117,9 +116,6 @@ install: pip install $PRE python-dateutil $NUMPY pyparsing!=2.1.6 $PANDAS cycler coveralls coverage $MOCK pip install $PRE -r doc-requirements.txt - # Install nose from a build which has partial - # support for python36 and suport for coverage output suppressing - pip install git+https://github.com/jenshnielsen/nose.git@matplotlibnose # pytest-cov>=2.3.1 due to https://github.com/pytest-dev/pytest-cov/issues/124 pip install $PRE pytest 'pytest-cov>=2.3.1' pytest-timeout pytest-xdist pytest-faulthandler $INSTALL_PEP8 @@ -155,21 +151,20 @@ script: if [[ $DELETE_FONT_CACHE == 1 ]]; then rm -rf ~/.cache/matplotlib fi + # Workaround for pytest-xdist flaky collection order + # https://github.com/pytest-dev/pytest/issues/920 + # https://github.com/pytest-dev/pytest/issues/1075 + export PYTHONHASHSEED=$(shuf -i 1-4294967295 -n 1) + echo PYTHONHASHSEED=$PYTHONHASHSEED + + echo The following args are passed to pytest $PYTEST_ARGS $RUN_PEP8 if [[ $USE_PYTEST == false ]]; then - echo The following args are passed to nose $NOSE_ARGS $RUN_PEP8 if [[ $TRAVIS_OS_NAME == 'osx' ]]; then - python tests.py $NOSE_ARGS $RUN_PEP8 + python tests.py $PYTEST_ARGS $RUN_PEP8 else - gdb -return-child-result -batch -ex r -ex bt --args python $PYTHON_ARGS tests.py $NOSE_ARGS $RUN_PEP8 + gdb -return-child-result -batch -ex r -ex bt --args python $PYTHON_ARGS tests.py $PYTEST_ARGS $RUN_PEP8 fi else - # Workaround for pytest-xdist flaky colletion order - # https://github.com/pytest-dev/pytest/issues/920 - # https://github.com/pytest-dev/pytest/issues/1075 - export PYTHONHASHSEED=$(shuf -i 1-4294967295 -n 1) - echo PYTHONHASHSEED=$PYTHONHASHSEED - - echo The following args are passed to pytest $PYTEST_ARGS $RUN_PEP8 py.test $PYTEST_ARGS $RUN_PEP8 fi else @@ -201,6 +196,11 @@ after_failure: fi after_success: + - | + if [[ $BUILD_DOCS == false ]]; then + coveralls + bash <(curl -s https://codecov.io/bash) + fi - | if [[ $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' && $BUILD_DOCS == true && $TRAVIS_BRANCH == 'master' ]]; then cd $TRAVIS_BUILD_DIR @@ -225,7 +225,3 @@ after_success: else echo "Will only deploy docs build from matplotlib master branch" fi - if [[ $NOSE_ARGS =~ "--with-coverage" || $USE_PYTEST == true ]]; then - coveralls - bash <(curl -s https://codecov.io/bash) - fi diff --git a/appveyor.yml b/appveyor.yml index fb6a0c76d139..6dfd857838df 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,11 +14,11 @@ environment: CMD_IN_ENV: "cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd" # Workaround for https://github.com/conda/conda-build/issues/636 PYTHONIOENCODING: "UTF-8" - PYTEST_ARGS: -ra --timeout=300 --durations=25 #--cov-report= --cov=lib #-n %NUMBER_OF_PROCESSORS% + PYTEST_ARGS: -ra --timeout=300 --durations=25 -n %NUMBER_OF_PROCESSORS% #--cov-report= --cov=lib USE_PYTEST: no - #PYTHONHASHSEED: 0 # Workaround for pytest-xdist flaky colletion order - # # https://github.com/pytest-dev/pytest/issues/920 - # # https://github.com/pytest-dev/pytest/issues/1075 + PYTHONHASHSEED: 0 # Workaround for pytest-xdist flaky collection order + # https://github.com/pytest-dev/pytest/issues/920 + # https://github.com/pytest-dev/pytest/issues/1075 matrix: # for testing purpose: numpy 1.8 on py2.7, for the rest use 1.10/latest @@ -91,12 +91,12 @@ install: - conda create -q -n test-environment python=%PYTHON_VERSION% pip setuptools numpy python-dateutil freetype=2.6 msinttypes "tk=8.5" pyparsing pytz tornado "libpng>=1.6.21,<1.7" "zlib=1.2" "cycler>=0.10" - nose mock sphinx + mock sphinx - activate test-environment - cmd: echo %PYTHON_VERSION% %TARGET_ARCH% - cmd: IF %PYTHON_VERSION% == 2.7 conda install -q functools32 # pytest-cov>=2.3.1 due to https://github.com/pytest-dev/pytest-cov/issues/124 - - if x%USE_PYTEST% == xyes conda install -q pytest "pytest-cov>=2.3.1" pytest-timeout #pytest-xdist + - conda install -q pytest "pytest-cov>=2.3.1" pytest-timeout pytest-xdist # Let the install prefer the static builds of the libs - set LIBRARY_LIB=%CONDA_PREFIX%\Library\lib @@ -116,7 +116,7 @@ install: test_script: # Now build the thing.. - - '%CMD_IN_ENV% python setup.py develop' + - '%CMD_IN_ENV% pip install --no-deps -ve .' # these should show no z, png, or freetype dll... - set "DUMPBIN=%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe" #- cmd: '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\_png*.pyd' @@ -135,9 +135,9 @@ test_script: # Test import of tkagg backend - python -c "import matplotlib as m; m.use('tkagg'); import matplotlib.pyplot as plt; print(plt.get_backend())" # tests - - if x%USE_PYTEST% == xyes echo The following args are passed to pytest %PYTEST_ARGS% + - echo The following args are passed to pytest %PYTEST_ARGS% - if x%USE_PYTEST% == xyes py.test %PYTEST_ARGS% - - if x%USE_PYTEST% == xno python tests.py + - if x%USE_PYTEST% == xno python tests.py %PYTEST_ARGS% # Generate a html for visual tests - python visual_tests.py diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 18c720ba7002..000000000000 --- a/conftest.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import inspect -import os -import pytest -import unittest - -import matplotlib -matplotlib.use('agg') - -from matplotlib import default_test_modules - - -IGNORED_TESTS = { - 'matplotlib': [], -} - - -def blacklist_check(path): - """Check if test is blacklisted and should be ignored""" - head, tests_dir = os.path.split(path.dirname) - if tests_dir != 'tests': - return True - head, top_module = os.path.split(head) - return path.purebasename in IGNORED_TESTS.get(top_module, []) - - -def whitelist_check(path): - """Check if test is not whitelisted and should be ignored""" - left = path.dirname - last_left = None - module_path = path.purebasename - while len(left) and left != last_left: - last_left = left - left, tail = os.path.split(left) - module_path = '.'.join([tail, module_path]) - if module_path in default_test_modules: - return False - return True - - -COLLECT_FILTERS = { - 'none': lambda _: False, - 'blacklist': blacklist_check, - 'whitelist': whitelist_check, -} - - -def pytest_addoption(parser): - group = parser.getgroup("matplotlib", "matplotlib custom options") - - group.addoption('--collect-filter', action='store', - choices=COLLECT_FILTERS, default='blacklist', - help='filter tests during collection phase') - - group.addoption('--no-pep8', action='store_true', - help='skip PEP8 compliance tests') - - -def pytest_configure(config): - matplotlib._called_from_pytest = True - matplotlib._init_tests() - - if config.getoption('--no-pep8'): - IGNORED_TESTS['matplotlib'] += 'test_coding_standards' - - -def pytest_unconfigure(config): - matplotlib._called_from_pytest = False - - -def pytest_ignore_collect(path, config): - if path.ext == '.py': - collect_filter = config.getoption('--collect-filter') - return COLLECT_FILTERS[collect_filter](path) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 788d87e2ff92..86631ab05300 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1474,9 +1474,10 @@ def _jupyter_nbextension_paths(): default_test_modules = [ - 'matplotlib.tests.test_png', - 'matplotlib.tests.test_units', - ] + 'matplotlib.tests', + 'matplotlib.sphinxext.tests', + 'mpl_toolkits.tests', +] def _init_tests(): @@ -1510,19 +1511,51 @@ def _init_tests(): ) ) - from .testing._nose import check_deps - check_deps() + try: + import pytest + try: + from unittest import mock + except ImportError: + import mock + except ImportError: + print("matplotlib.test requires pytest and mock to run.") + raise -def test(verbosity=1, coverage=False, **kwargs): +def test(verbosity=None, coverage=False, switch_backend_warn=True, + recursionlimit=0, **kwargs): """run the matplotlib test suite""" _init_tests() - from .testing._nose import test as nose_test - return nose_test(verbosity, coverage, **kwargs) + old_backend = get_backend() + old_recursionlimit = sys.getrecursionlimit() + try: + use('agg') + if recursionlimit: + sys.setrecursionlimit(recursionlimit) + import pytest + + args = kwargs.pop('argv', []) + if not any(os.path.exists(arg) for arg in args): + args += ['--pyargs'] + default_test_modules + + if coverage: + args += ['--cov'] + + if verbosity: + args += ['-' + 'v' * verbosity] + + retcode = pytest.main(args, **kwargs) + finally: + if old_backend.lower() != 'agg': + use(old_backend, warn=switch_backend_warn) + if recursionlimit: + sys.setrecursionlimit(old_recursionlimit) + + return retcode -test.__test__ = False # nose: this function is not a test +test.__test__ = False # pytest: this function is not a test def _replacer(data, key): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b699f7d047ec..330cae8b79af 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1555,7 +1555,7 @@ def gca(self, **kwargs): else: warnings.warn('Requested projection is different from ' 'current axis projection, creating new axis ' - 'with requested projection.') + 'with requested projection.', stacklevel=2) # no axes found, so create one which spans the figure return self.add_subplot(1, 1, 1, **kwargs) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 4bb0b0c5e97f..64b9406efc66 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1284,13 +1284,16 @@ def findfont(self, prop, fontext='ttf', directory=None, cached = _lookup_cache[fontext].get(prop) if cached is not None: return cached + else: + directory = os.path.normcase(directory) best_score = 1e64 best_font = None for font in fontlist: if (directory is not None and - os.path.commonprefix([font.fname, directory]) != directory): + os.path.commonprefix([os.path.normcase(font.fname), + directory]) != directory): continue # Matching family should have highest priority, so it is multiplied # by 10.0 diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 5476c5dfbe2d..a708b0fd0bb7 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -453,7 +453,6 @@ def get_position(self, fig, return_all=False): else: return figbox - def get_topmost_subplotspec(self): 'get the topmost SubplotSpec instance associated with the subplot' gridspec = self.get_gridspec() @@ -473,6 +472,10 @@ def __eq__(self, other): self.num1 == other.num1, self.num2 == other.num2)) + if six.PY2: + def __ne__(self, other): + return not self == other + def __hash__(self): return (hash(self._gridspec) ^ hash(self.num1) ^ diff --git a/lib/matplotlib/sphinxext/tests/conftest.py b/lib/matplotlib/sphinxext/tests/conftest.py index 0433c4979085..900743e86003 100644 --- a/lib/matplotlib/sphinxext/tests/conftest.py +++ b/lib/matplotlib/sphinxext/tests/conftest.py @@ -1,4 +1,5 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -from matplotlib.tests.conftest import mpl_test_settings +from matplotlib.tests.conftest import (mpl_test_settings, + pytest_configure, pytest_unconfigure) diff --git a/lib/matplotlib/tests/conftest.py b/lib/matplotlib/tests/conftest.py index 5f2075a09485..918bafc873b9 100644 --- a/lib/matplotlib/tests/conftest.py +++ b/lib/matplotlib/tests/conftest.py @@ -6,6 +6,16 @@ import matplotlib +def pytest_configure(config): + matplotlib.use('agg') + matplotlib._called_from_pytest = True + matplotlib._init_tests() + + +def pytest_unconfigure(config): + matplotlib._called_from_pytest = False + + @pytest.fixture(autouse=True) def mpl_test_settings(request): from matplotlib.testing.decorators import _do_cleanup diff --git a/lib/matplotlib/tests/test_backend_pgf.py b/lib/matplotlib/tests/test_backend_pgf.py index edcab0d143ca..52151256f71a 100644 --- a/lib/matplotlib/tests/test_backend_pgf.py +++ b/lib/matplotlib/tests/test_backend_pgf.py @@ -99,9 +99,8 @@ def test_xelatex(): def test_pdflatex(): import os if os.environ.get('APPVEYOR', False): - from matplotlib.testing import xfail - xfail("pdflatex test does not work on appveyor due " - "to missing latex fonts") + pytest.xfail("pdflatex test does not work on appveyor due to missing " + "LaTeX fonts") rc_pdflatex = {'font.family': 'serif', 'pgf.rcfonts': False, diff --git a/lib/matplotlib/tests/test_category.py b/lib/matplotlib/tests/test_category.py index 6e5c43d76fb9..83847e3150bb 100644 --- a/lib/matplotlib/tests/test_category.py +++ b/lib/matplotlib/tests/test_category.py @@ -3,6 +3,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +from distutils.version import LooseVersion + import pytest import numpy as np @@ -12,6 +14,11 @@ import unittest +needs_new_numpy = pytest.mark.xfail( + LooseVersion(np.__version__) < LooseVersion('1.8.0'), + reason='NumPy < 1.8.0 is broken.') + + class TestUnitData(object): testdata = [("hello world", ["hello world"], [0]), ("Здравствуйте мир", ["Здравствуйте мир"], [0]), @@ -21,12 +28,14 @@ class TestUnitData(object): ids = ["single", "unicode", "mixed"] + @needs_new_numpy @pytest.mark.parametrize("data, seq, locs", testdata, ids=ids) def test_unit(self, data, seq, locs): act = cat.UnitData(data) assert act.seq == seq assert act.locs == locs + @needs_new_numpy def test_update_map(self): data = ['a', 'd'] oseq = ['a', 'd'] @@ -78,6 +87,7 @@ class TestStrCategoryConverter(object): def mock_axis(self, request): self.cc = cat.StrCategoryConverter() + @needs_new_numpy @pytest.mark.parametrize("data, unitmap, exp", testdata, ids=ids) def test_convert(self, data, unitmap, exp): MUD = MockUnitData(unitmap) diff --git a/lib/mpl_toolkits/tests/conftest.py b/lib/mpl_toolkits/tests/conftest.py index 0433c4979085..900743e86003 100644 --- a/lib/mpl_toolkits/tests/conftest.py +++ b/lib/mpl_toolkits/tests/conftest.py @@ -1,4 +1,5 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -from matplotlib.tests.conftest import mpl_test_settings +from matplotlib.tests.conftest import (mpl_test_settings, + pytest_configure, pytest_unconfigure) diff --git a/pytest.ini b/pytest.ini index 7a50474fe7e3..d9132bfa6002 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,6 @@ [pytest] -norecursedirs = .git build ci dist extern release tools unit venv +norecursedirs = .git build ci dist doc extern lib/mpl_examples release tools unit venv +python_files = test_*.py pep8ignore = * E111 E114 E115 E116 E121 E122 E123 E124 E125 E126 E127 E128 E129 E131 E226 E240 E241 E242 E243 E244 E245 E246 E247 E248 E249 E265 E266 E704 W503 diff --git a/test_only.py b/setup_tests_only.py similarity index 100% rename from test_only.py rename to setup_tests_only.py diff --git a/tests.py b/tests.py index e2eeebc8d9e8..6a86ae84d4fd 100755 --- a/tests.py +++ b/tests.py @@ -4,10 +4,9 @@ # # $ python tests.py -v -d # -# The arguments are identical to the arguments accepted by nosetests. +# The arguments are identical to the arguments accepted by py.test. # -# See https://nose.readthedocs.org/ for a detailed description of -# these options. +# See http://doc.pytest.org/ for a detailed description of these options. import sys import argparse @@ -22,13 +21,13 @@ # The warnings need to be before any of matplotlib imports, but after # setuptools (if present) which has syntax error with the warnings enabled. - # Filtering by module does not work as this will be raised by Python itself. + # Filtering by module does not work as this will be raised by Python itself # so `module=matplotlib.*` is out of questions. import warnings - # Python 3.6 deprecate invalid character-pairs \A, \* ... in non raw-strings - # and other things. Let's not re-introduce them + # Python 3.6 deprecate invalid character-pairs \A, \* ... in non + # raw-strings and other things. Let's not re-introduce them warnings.filterwarnings('error', '.*invalid escape sequence.*', category=DeprecationWarning) warnings.filterwarnings( @@ -39,14 +38,8 @@ from matplotlib import test parser = argparse.ArgumentParser(add_help=False) - parser.add_argument('--no-pep8', action='store_true', - help='Run all tests except PEP8 testing') - parser.add_argument('--pep8', action='store_true', - help='Run only PEP8 testing') parser.add_argument('--no-network', action='store_true', help='Run tests without network connection') - parser.add_argument('-j', type=int, - help='Shortcut for specifying number of test processes') parser.add_argument('--recursionlimit', type=int, default=0, help='Specify recursionlimit for test run') args, extra_args = parser.parse_known_args() @@ -54,15 +47,10 @@ if args.no_network: from matplotlib.testing import disable_internet disable_internet.turn_off_internet() - extra_args.extend(['-a', '!network']) - if args.j: - extra_args.extend([ - '--processes={}'.format(args.j), - '--process-timeout=300' - ]) + extra_args.extend(['-m', 'not network']) - print('Python byte-compilation optimization level: %d' % sys.flags.optimize) + print('Python byte-compilation optimization level:', sys.flags.optimize) - success = test(argv=sys.argv[0:1] + extra_args, switch_backend_warn=False, + retcode = test(argv=extra_args, switch_backend_warn=False, recursionlimit=args.recursionlimit) - sys.exit(not success) + sys.exit(retcode)