Skip to content

Commit 00e6c38

Browse files
authored
Merge pull request #6920 from Kojoley/prepare-for-cross-framework-test-suite
TST: Prepare for cross-framework test suite
2 parents b7b3445 + f20efb9 commit 00e6c38

17 files changed

+286
-170
lines changed

lib/matplotlib/__init__.py

Lines changed: 6 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,7 @@ def use(arg, warn=True, force=False):
14011401
if 'matplotlib.backends' in sys.modules:
14021402
# Warn only if called with a different name
14031403
if (rcParams['backend'] != name) and warn:
1404-
warnings.warn(_use_error_msg)
1404+
warnings.warn(_use_error_msg, stacklevel=2)
14051405

14061406
# Unless we've been told to force it, just return
14071407
if not force:
@@ -1586,70 +1586,17 @@ def _init_tests():
15861586
)
15871587
)
15881588

1589-
try:
1590-
import nose
1591-
try:
1592-
from unittest import mock
1593-
except:
1594-
import mock
1595-
except ImportError:
1596-
print("matplotlib.test requires nose and mock to run.")
1597-
raise
1598-
1599-
1600-
def _get_extra_test_plugins():
1601-
from .testing.performgc import PerformGC
1602-
from .testing.noseclasses import KnownFailure
1603-
from nose.plugins import attrib
1589+
from .testing.nose import check_deps
1590+
check_deps()
16041591

1605-
return [PerformGC, KnownFailure, attrib.Plugin]
16061592

1607-
1608-
def _get_nose_env():
1609-
env = {'NOSE_COVER_PACKAGE': ['matplotlib', 'mpl_toolkits'],
1610-
'NOSE_COVER_HTML': 1,
1611-
'NOSE_COVER_NO_PRINT': 1}
1612-
return env
1613-
1614-
1615-
def test(verbosity=1, coverage=False):
1593+
def test(verbosity=1, coverage=False, **kwargs):
16161594
"""run the matplotlib test suite"""
16171595
_init_tests()
16181596

1619-
old_backend = rcParams['backend']
1620-
try:
1621-
use('agg')
1622-
import nose
1623-
import nose.plugins.builtin
1624-
from nose.plugins.manager import PluginManager
1625-
from nose.plugins import multiprocess
1626-
1627-
# store the old values before overriding
1628-
plugins = _get_extra_test_plugins()
1629-
plugins.extend([plugin for plugin in nose.plugins.builtin.plugins])
1630-
1631-
manager = PluginManager(plugins=[x() for x in plugins])
1632-
config = nose.config.Config(verbosity=verbosity, plugins=manager)
1633-
1634-
# Nose doesn't automatically instantiate all of the plugins in the
1635-
# child processes, so we have to provide the multiprocess plugin with
1636-
# a list.
1637-
multiprocess._instantiate_plugins = plugins
1638-
1639-
env = _get_nose_env()
1640-
if coverage:
1641-
env['NOSE_WITH_COVERAGE'] = 1
1642-
1643-
success = nose.run(
1644-
defaultTest=default_test_modules,
1645-
config=config,
1646-
env=env,
1647-
)
1648-
finally:
1649-
if old_backend.lower() != 'agg':
1650-
use(old_backend)
1597+
from .testing.nose import test as nose_test
1598+
return nose_test(verbosity, coverage, **kwargs)
16511599

1652-
return success
16531600

16541601
test.__test__ = False # nose: this function is not a test
16551602

lib/matplotlib/testing/__init__.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import (absolute_import, division, print_function,
22
unicode_literals)
33

4+
import inspect
45
import warnings
56
from contextlib import contextmanager
67

@@ -13,6 +14,49 @@ def _is_list_like(obj):
1314
return not is_string_like(obj) and iterable(obj)
1415

1516

17+
def xfail(msg=""):
18+
"""Explicitly fail an currently-executing test with the given message."""
19+
from .nose import knownfail
20+
knownfail(msg)
21+
22+
23+
def skip(msg=""):
24+
"""Skip an executing test with the given message."""
25+
from nose import SkipTest
26+
raise SkipTest(msg)
27+
28+
29+
# stolen from pytest
30+
def getrawcode(obj, trycall=True):
31+
"""Return code object for given function."""
32+
try:
33+
return obj.__code__
34+
except AttributeError:
35+
obj = getattr(obj, 'im_func', obj)
36+
obj = getattr(obj, 'func_code', obj)
37+
obj = getattr(obj, 'f_code', obj)
38+
obj = getattr(obj, '__code__', obj)
39+
if trycall and not hasattr(obj, 'co_firstlineno'):
40+
if hasattr(obj, '__call__') and not inspect.isclass(obj):
41+
x = getrawcode(obj.__call__, trycall=False)
42+
if hasattr(x, 'co_firstlineno'):
43+
return x
44+
return obj
45+
46+
47+
def copy_metadata(src_func, tgt_func):
48+
"""Replicates metadata of the function. Returns target function."""
49+
tgt_func.__dict__ = src_func.__dict__
50+
tgt_func.__doc__ = src_func.__doc__
51+
tgt_func.__module__ = src_func.__module__
52+
tgt_func.__name__ = src_func.__name__
53+
if hasattr(src_func, '__qualname__'):
54+
tgt_func.__qualname__ = src_func.__qualname__
55+
if not hasattr(tgt_func, 'compat_co_firstlineno'):
56+
tgt_func.compat_co_firstlineno = getrawcode(src_func).co_firstlineno
57+
return tgt_func
58+
59+
1660
# stolen from pandas
1761
@contextmanager
1862
def assert_produces_warning(expected_warning=Warning, filter_level="always",

lib/matplotlib/testing/decorators.py

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import six
55

66
import functools
7-
import gc
87
import inspect
98
import os
109
import sys
@@ -16,8 +15,6 @@
1615
# allows other functions here to be used by pytest-based testing suites without
1716
# requiring nose to be installed.
1817

19-
import numpy as np
20-
2118
import matplotlib as mpl
2219
import matplotlib.style
2320
import matplotlib.units
@@ -27,13 +24,23 @@
2724
from matplotlib import pyplot as plt
2825
from matplotlib import ft2font
2926
from matplotlib import rcParams
30-
from matplotlib.testing.noseclasses import KnownFailureTest, \
31-
KnownFailureDidNotFailTest, ImageComparisonFailure
3227
from matplotlib.testing.compare import comparable_formats, compare_images, \
3328
make_test_filename
29+
from . import copy_metadata, skip, xfail
30+
from .exceptions import ImageComparisonFailure
31+
32+
33+
def skipif(condition, *args, **kwargs):
34+
"""Skip the given test function if eval(condition) results in a True
35+
value.
36+
37+
Optionally specify a reason for better reporting.
38+
"""
39+
from .nose.decorators import skipif
40+
return skipif(condition, *args, **kwargs)
3441

3542

36-
def knownfailureif(fail_condition, msg=None, known_exception_class=None ):
43+
def knownfailureif(fail_condition, msg=None, known_exception_class=None):
3744
"""
3845
3946
Assume a will fail if *fail_condition* is True. *fail_condition*
@@ -45,32 +52,8 @@ def knownfailureif(fail_condition, msg=None, known_exception_class=None ):
4552
if the exception is an instance of this class. (Default = None)
4653
4754
"""
48-
# based on numpy.testing.dec.knownfailureif
49-
if msg is None:
50-
msg = 'Test known to fail'
51-
def known_fail_decorator(f):
52-
# Local import to avoid a hard nose dependency and only incur the
53-
# import time overhead at actual test-time.
54-
import nose
55-
def failer(*args, **kwargs):
56-
try:
57-
# Always run the test (to generate images).
58-
result = f(*args, **kwargs)
59-
except Exception as err:
60-
if fail_condition:
61-
if known_exception_class is not None:
62-
if not isinstance(err,known_exception_class):
63-
# This is not the expected exception
64-
raise
65-
# (Keep the next ultra-long comment so in shows in console.)
66-
raise KnownFailureTest(msg) # An error here when running nose means that you don't have the matplotlib.testing.noseclasses:KnownFailure plugin in use.
67-
else:
68-
raise
69-
if fail_condition and fail_condition != 'indeterminate':
70-
raise KnownFailureDidNotFailTest(msg)
71-
return result
72-
return nose.tools.make_decorator(f)(failer)
73-
return known_fail_decorator
55+
from .nose.decorators import knownfailureif
56+
return knownfailureif(fail_condition, msg, known_exception_class)
7457

7558

7659
def _do_cleanup(original_units_registry, original_settings):
@@ -214,7 +197,7 @@ def remove_text(figure):
214197
def test(self):
215198
baseline_dir, result_dir = _image_directories(self._func)
216199
if self._style != 'classic':
217-
raise KnownFailureTest('temporarily disabled until 2.0 tag')
200+
xfail('temporarily disabled until 2.0 tag')
218201
for fignum, baseline in zip(plt.get_fignums(), self._baseline_images):
219202
for extension in self._extensions:
220203
will_fail = not extension in comparable_formats()
@@ -266,13 +249,14 @@ def do_test():
266249
'(RMS %(rms).3f)'%err)
267250
except ImageComparisonFailure:
268251
if not check_freetype_version(self._freetype_version):
269-
raise KnownFailureTest(
252+
xfail(
270253
"Mismatched version of freetype. Test requires '%s', you have '%s'" %
271254
(self._freetype_version, ft2font.__freetype_version__))
272255
raise
273256

274257
yield (do_test,)
275258

259+
276260
def image_comparison(baseline_images=None, extensions=None, tol=0,
277261
freetype_version=None, remove_text=False,
278262
savefig_kwarg=None, style='classic'):
@@ -420,9 +404,6 @@ def find_dotted_module(module_name, path=None):
420404

421405

422406
def switch_backend(backend):
423-
# Local import to avoid a hard nose dependency and only incur the
424-
# import time overhead at actual test-time.
425-
import nose
426407
def switch_backend_decorator(func):
427408
def backend_switcher(*args, **kwargs):
428409
try:
@@ -434,7 +415,7 @@ def backend_switcher(*args, **kwargs):
434415
plt.switch_backend(prev_backend)
435416
return result
436417

437-
return nose.tools.make_decorator(func)(backend_switcher)
418+
return copy_metadata(func, backend_switcher)
438419
return switch_backend_decorator
439420

440421

@@ -453,7 +434,6 @@ def skip_if_command_unavailable(cmd):
453434
try:
454435
check_output(cmd)
455436
except:
456-
from nose import SkipTest
457-
raise SkipTest('missing command: %s' % cmd[0])
437+
skip('missing command: %s' % cmd[0])
458438

459439
return lambda f: f

lib/matplotlib/testing/exceptions.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
class KnownFailureTest(Exception):
2-
"""
3-
Raise this exception to mark a test as a known failing test.
4-
"""
5-
6-
7-
class KnownFailureDidNotFailTest(Exception):
8-
"""
9-
Raise this exception to mark a test should have failed but did not.
10-
"""
11-
12-
131
class ImageComparisonFailure(AssertionError):
142
"""
153
Raise this exception to mark a test as a comparison between two images.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from __future__ import (absolute_import, division, print_function,
2+
unicode_literals)
3+
4+
5+
def get_extra_test_plugins():
6+
from .plugins.performgc import PerformGC
7+
from .plugins.knownfailure import KnownFailure
8+
from nose.plugins import attrib
9+
10+
return [PerformGC, KnownFailure, attrib.Plugin]
11+
12+
13+
def get_env():
14+
env = {'NOSE_COVER_PACKAGE': ['matplotlib', 'mpl_toolkits'],
15+
'NOSE_COVER_HTML': 1,
16+
'NOSE_COVER_NO_PRINT': 1}
17+
return env
18+
19+
20+
def check_deps():
21+
try:
22+
import nose
23+
try:
24+
from unittest import mock
25+
except ImportError:
26+
import mock
27+
except ImportError:
28+
print("matplotlib.test requires nose and mock to run.")
29+
raise
30+
31+
32+
def test(verbosity=None, coverage=False, switch_backend_warn=True, **kwargs):
33+
from ... import default_test_modules, get_backend, use
34+
35+
old_backend = get_backend()
36+
try:
37+
use('agg')
38+
import nose
39+
from nose.plugins import multiprocess
40+
41+
# Nose doesn't automatically instantiate all of the plugins in the
42+
# child processes, so we have to provide the multiprocess plugin with
43+
# a list.
44+
extra_plugins = get_extra_test_plugins()
45+
multiprocess._instantiate_plugins = extra_plugins
46+
47+
env = get_env()
48+
if coverage:
49+
env['NOSE_WITH_COVERAGE'] = 1
50+
51+
if verbosity is not None:
52+
env['NOSE_VERBOSE'] = verbosity
53+
54+
success = nose.run(
55+
addplugins=[plugin() for plugin in extra_plugins],
56+
env=env,
57+
defaultTest=default_test_modules,
58+
**kwargs
59+
)
60+
finally:
61+
if old_backend.lower() != 'agg':
62+
use(old_backend, warn=switch_backend_warn)
63+
64+
return success
65+
66+
67+
def knownfail(msg):
68+
from .exceptions import KnownFailureTest
69+
# Keep the next ultra-long comment so it shows in console.
70+
raise KnownFailureTest(msg) # An error here when running nose means that you don't have the matplotlib.testing.nose.plugins:KnownFailure plugin in use. # noqa

0 commit comments

Comments
 (0)