From bec3ca0217b3337ea141dd974b5eecebc7865c28 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 27 Mar 2018 17:08:29 -0700 Subject: [PATCH] Py3fy testing machinery. --- lib/matplotlib/testing/__init__.py | 7 ---- lib/matplotlib/testing/compare.py | 19 ++++----- lib/matplotlib/testing/decorators.py | 63 ++++++++++------------------ 3 files changed, 30 insertions(+), 59 deletions(-) diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 863848af0c28..e9af802daa70 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -10,13 +10,6 @@ def is_called_from_pytest(): return getattr(mpl, '_called_from_pytest', False) -def _copy_metadata(src_func, tgt_func): - """Replicates metadata of the function. Returns target function.""" - functools.update_wrapper(tgt_func, src_func) - tgt_func.__wrapped__ = src_func # Python2 compatibility. - return tgt_func - - def set_font_settings_for_testing(): mpl.rcParams['font.family'] = 'DejaVu Sans' mpl.rcParams['text.hinting'] = False diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 6745802c4da4..f80de10184d5 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -181,15 +181,14 @@ def __call__(self, orig, dest): # reported as a regular exception below). env.pop("DISPLAY", None) # May already be unset. # Do not load any user options. - # `os.environ` needs native strings on Py2+Windows. - env[str("INKSCAPE_PROFILE_DIR")] = os.devnull + env["INKSCAPE_PROFILE_DIR"] = os.devnull # Old versions of Inkscape (0.48.3.1, used on Travis as of now) # seem to sometimes deadlock when stderr is redirected to a pipe, # so we redirect it to a temporary file instead. This is not # necessary anymore as of Inkscape 0.92.1. self._stderr = TemporaryFile() self._proc = subprocess.Popen( - [str("inkscape"), "--without-gui", "--shell"], + ["inkscape", "--without-gui", "--shell"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=self._stderr, env=env) if not self._read_to_prompt(): @@ -210,7 +209,7 @@ def fsencode(s): # slow solution (Inkscape uses `fgets` so it will always stop at a # newline). return make_external_conversion_command(lambda old, new: [ - str('inkscape'), '-z', old, '--export-png', new])(orig, dest) + 'inkscape', '-z', old, '--export-png', new])(orig, dest) self._proc.stdin.write(orig_b + b" --export-png=" + dest_b + b"\n") self._proc.stdin.flush() if not self._read_to_prompt(): @@ -329,8 +328,8 @@ def calculate_rms(expectedImage, actualImage): "Calculate the per-pixel errors, then compute the root mean square error." if expectedImage.shape != actualImage.shape: raise ImageComparisonFailure( - "Image sizes do not match expected size: {0} " - "actual size {1}".format(expectedImage.shape, actualImage.shape)) + "Image sizes do not match expected size: {} " + "actual size {}".format(expectedImage.shape, actualImage.shape)) # Convert to float to avoid overflowing finite integer types. return np.sqrt(((expectedImage - actualImage).astype(float) ** 2).mean()) @@ -361,7 +360,7 @@ def compare_images(expected, actual, tol, in_decorator=False): -------- img1 = "./baseline/plot.png" img2 = "./output/plot.png" - compare_images( img1, img2, 0.001 ): + compare_images(img1, img2, 0.001): """ if not os.path.exists(actual): @@ -391,7 +390,7 @@ def compare_images(expected, actual, tol, in_decorator=False): diff_image = make_test_filename(actual, 'failed-diff') - if tol <= 0.0: + if tol <= 0: if np.array_equal(expectedImage, actualImage): return None @@ -431,8 +430,8 @@ def save_diff_image(expected, actual, output): actualImage = np.array(actualImage).astype(float) if expectedImage.shape != actualImage.shape: raise ImageComparisonFailure( - "Image sizes do not match expected size: {0} " - "actual size {1}".format(expectedImage.shape, actualImage.shape)) + "Image sizes do not match expected size: {} " + "actual size {}".format(expectedImage.shape, actualImage.shape)) absDiffImage = np.abs(expectedImage - actualImage) # expand differences in luminance domain diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index d579a4713535..6e5be64b1d91 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -1,6 +1,5 @@ from distutils.version import StrictVersion import functools -import inspect import os from pathlib import Path import shutil @@ -23,7 +22,7 @@ from matplotlib import ft2font from matplotlib.testing.compare import ( comparable_formats, compare_images, make_test_filename) -from . import _copy_metadata, is_called_from_pytest +from . import is_called_from_pytest from .exceptions import ImageComparisonFailure @@ -250,9 +249,9 @@ def copy_baseline(self, baseline, extension): if os.path.exists(orig_expected_fname): shutil.copyfile(orig_expected_fname, expected_fname) else: - reason = ("Do not have baseline image {0} because this " - "file does not exist: {1}".format(expected_fname, - orig_expected_fname)) + reason = ("Do not have baseline image {} because this " + "file does not exist: {}".format(expected_fname, + orig_expected_fname)) raise ImageComparisonFailure(reason) return expected_fname @@ -327,11 +326,12 @@ def __call__(self, func): self.delayed_init(func) import nose.tools + @functools.wraps(func) @nose.tools.with_setup(self.setup, self.teardown) def runner_wrapper(): yield from self.nose_runner() - return _copy_metadata(func, runner_wrapper) + return runner_wrapper def _pytest_image_comparison(baseline_images, extensions, tol, @@ -350,6 +350,7 @@ def _pytest_image_comparison(baseline_images, extensions, tol, extensions = map(_mark_xfail_if_format_is_uncomparable, extensions) def decorator(func): + @functools.wraps(func) # Parameter indirection; see docstring above and comment below. @pytest.mark.usefixtures('mpl_image_comparison_parameters') @pytest.mark.parametrize('extension', extensions) @@ -379,8 +380,7 @@ def wrapper(*args, **kwargs): for idx, baseline in enumerate(baseline_images): img.compare(idx, baseline, extension) - wrapper.__wrapped__ = func # For Python 2.7. - return _copy_metadata(func, wrapper) + return wrapper return decorator @@ -408,7 +408,7 @@ def image_comparison(baseline_images, extensions=None, tol=0, extensions : [ None | list ] If None, defaults to all supported extensions. - Otherwise, a list of extensions to test. For example ['png','pdf']. + Otherwise, a list of extensions to test, e.g. ``['png', 'pdf']``. tol : float, optional, default: 0 The RMS threshold above which the test is considered failed. @@ -464,10 +464,10 @@ def _image_directories(func): # FIXME: this won't work for nested packages in matplotlib.tests warnings.warn( 'Test module run as script. Guessing baseline image locations.') - script_name = sys.argv[0] - basedir = os.path.abspath(os.path.dirname(script_name)) - subdir = os.path.splitext(os.path.split(script_name)[1])[0] + module_path = Path(sys.argv[0]).resolve() + subdir = module_path.stem else: + module_path = Path(sys.modules[func.__module__].__file__) mods = module_name.split('.') if len(mods) >= 3: mods.pop(0) @@ -486,50 +486,29 @@ def _image_directories(func): "file (can be empty).".format(module_name)) subdir = os.path.join(*mods) - import imp - def find_dotted_module(module_name, path=None): - """A version of imp which can handle dots in the module name. - As for imp.find_module(), the return value is a 3-element - tuple (file, pathname, description).""" - res = None - for sub_mod in module_name.split('.'): - try: - res = file, path, _ = imp.find_module(sub_mod, path) - path = [path] - if file is not None: - file.close() - except ImportError: - # assume namespace package - path = list(sys.modules[sub_mod].__path__) - res = None, path, None - return res - - mod_file = find_dotted_module(func.__module__)[1] - basedir = os.path.dirname(mod_file) + baseline_dir = module_path.parent / 'baseline_images' / subdir + result_dir = Path().resolve() / 'result_images' / subdir + result_dir.mkdir(parents=True, exist_ok=True) - baseline_dir = os.path.join(basedir, 'baseline_images', subdir) - result_dir = os.path.abspath(os.path.join('result_images', subdir)) - Path(result_dir).mkdir(parents=True, exist_ok=True) - - return baseline_dir, result_dir + return str(baseline_dir), str(result_dir) def switch_backend(backend): - # Local import to avoid a hard nose dependency and only incur the - # import time overhead at actual test-time. + def switch_backend_decorator(func): + @functools.wraps(func) def backend_switcher(*args, **kwargs): try: prev_backend = mpl.get_backend() matplotlib.testing.setup() plt.switch_backend(backend) - result = func(*args, **kwargs) + return func(*args, **kwargs) finally: plt.switch_backend(prev_backend) - return result - return _copy_metadata(func, backend_switcher) + return backend_switcher + return switch_backend_decorator