Prepare for cross-framework test suite · matplotlib/matplotlib@362c271 · GitHub
Skip to content

Commit 362c271

Browse files
committed
Prepare for cross-framework test suite
- introduced `xfail(reason)` function - uses: `raise KnownFailureTest(msg)` -> `xfail(reason)` - same name and signature as in pytest - introduced `skip(reason)` function - uses: `raise SkipTest(msg)` -> `skip(reason)` - same name and signature as in pytest - introduced `skipif(condition, reason=None)` decorator - uses: replaces `def func(): if condition: skip()` - same name and signature as in pytest - can be used with functions, classes, and methods - supports string condition (evaluated at runtime) - moved nose related code to `testing.nose` submodule - plugins in `testing.nose.plugins` submodule - decorators implementation in `testing.nose.decorators` (interface is still in `testing.decorators`, implementation will have been chosen at runtime according to used test framework) - `matplotlib.test` function unifications - `tests.py` now uses `matplotlib.test()`
1 parent 888bf17 commit 362c271

17 files changed

Lines changed: 286 additions & 168 deletions

lib/matplotlib/__init__.py

Lines changed: 6 additions & 59 deletions

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 & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@
44
import six
55

66
import functools
7-
import gc
87
import inspect
98
import os
109
import sys
1110
import shutil
1211
import warnings
1312
import unittest
1413

15-
import nose
16-
import numpy as np
17-
1814
import matplotlib as mpl
1915
import matplotlib.style
2016
import matplotlib.units
@@ -24,13 +20,23 @@
2420
from matplotlib import pyplot as plt
2521
from matplotlib import ft2font
2622
from matplotlib import rcParams
27-
from matplotlib.testing.noseclasses import KnownFailureTest, \
28-
KnownFailureDidNotFailTest, ImageComparisonFailure
2923
from matplotlib.testing.compare import comparable_formats, compare_images, \
3024
make_test_filename
25+
from . import copy_metadata, skip, xfail
26+
from .exceptions import ImageComparisonFailure
27+
28+
29+
def skipif(condition, *args, **kwargs):
30+
"""Skip the given test function if eval(condition) results in a True
31+
value.
32+
33+
Optionally specify a reason for better reporting.
34+
"""
35+
from .nose.decorators import skipif
36+
return skipif(condition, *args, **kwargs)
3137

3238

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

7254

7355
def _do_cleanup(original_units_registry, original_settings):
@@ -211,7 +193,7 @@ def remove_text(figure):
211193
def test(self):
212194
baseline_dir, result_dir = _image_directories(self._func)
213195
if self._style != 'classic':
214-
raise KnownFailureTest('temporarily disabled until 2.0 tag')
196+
xfail('temporarily disabled until 2.0 tag')
215197
for fignum, baseline in zip(plt.get_fignums(), self._baseline_images):
216198
for extension in self._extensions:
217199
will_fail = not extension in comparable_formats()
@@ -263,13 +245,14 @@ def do_test():
263245
'(RMS %(rms).3f)'%err)
264246
except ImageComparisonFailure:
265247
if not check_freetype_version(self._freetype_version):
266-
raise KnownFailureTest(
248+
xfail(
267249
"Mismatched version of freetype. Test requires '%s', you have '%s'" %
268250
(self._freetype_version, ft2font.__freetype_version__))
269251
raise
270252

271253
yield (do_test,)
272254

255+
273256
def image_comparison(baseline_images=None, extensions=None, tol=0,
274257
freetype_version=None, remove_text=False,
275258
savefig_kwarg=None, style='classic'):
@@ -428,7 +411,7 @@ def backend_switcher(*args, **kwargs):
428411
plt.switch_backend(prev_backend)
429412
return result
430413

431-
return nose.tools.make_decorator(func)(backend_switcher)
414+
return copy_metadata(func, backend_switcher)
432415
return switch_backend_decorator
433416

434417

@@ -447,7 +430,6 @@ def skip_if_command_unavailable(cmd):
447430
try:
448431
check_output(cmd)
449432
except:
450-
from nose import SkipTest
451-
raise SkipTest('missing command: %s' % cmd[0])
433+
skip('missing command: %s' % cmd[0])
452434

453435
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

0 commit comments

Comments
 (0)