From b6c69770cf678f810f5855f58ef7484d30a0b125 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sat, 12 Sep 2020 08:48:51 +1200 Subject: [PATCH 1/5] Allow check_figures_equal to work with pytest parametrized Following cue of matplotlib's check_figures_equal decorator. --- pygmt/helpers/testing.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/pygmt/helpers/testing.py b/pygmt/helpers/testing.py index 34db14ae06c..b321c9f4c24 100644 --- a/pygmt/helpers/testing.py +++ b/pygmt/helpers/testing.py @@ -1,16 +1,17 @@ """ Helper functions for testing. """ - import inspect import os +import string from matplotlib.testing.compare import compare_images from ..exceptions import GMTImageComparisonFailure +from ..figure import Figure -def check_figures_equal(*, tol=0.0, result_dir="result_images"): +def check_figures_equal(*, extensions=("png",), tol=0.0, result_dir="result_images"): """ Decorator for test cases that generate and compare two figures. @@ -25,6 +26,8 @@ def check_figures_equal(*, tol=0.0, result_dir="result_images"): Parameters ---------- + extensions : list + The extensions to test. Default is ["png"]. tol : float The RMS threshold above which the test is considered failed. result_dir : str @@ -66,19 +69,27 @@ def check_figures_equal(*, tol=0.0, result_dir="result_images"): ... ) >>> shutil.rmtree(path="tmp_result_images") # cleanup folder if tests pass """ + ALLOWED_CHARS = set(string.digits + string.ascii_letters + "_-[]()") + KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY def decorator(func): + import pytest os.makedirs(result_dir, exist_ok=True) old_sig = inspect.signature(func) - def wrapper(*args, **kwargs): + @pytest.mark.parametrize("ext", extensions) + def wrapper(*args, ext, request, **kwargs): + if "ext" in old_sig.parameters: + kwargs["ext"] = ext + if "request" in old_sig.parameters: + kwargs["request"] = request + + file_name = "".join(c for c in request.node.name if c in ALLOWED_CHARS) try: fig_ref, fig_test = func(*args, **kwargs) - ref_image_path = os.path.join( - result_dir, func.__name__ + "-expected.png" - ) - test_image_path = os.path.join(result_dir, func.__name__ + ".png") + ref_image_path = os.path.join(result_dir, f"{file_name}-expected.{ext}") + test_image_path = os.path.join(result_dir, f"{file_name}.{ext}") fig_ref.savefig(ref_image_path) fig_test.savefig(test_image_path) @@ -109,9 +120,18 @@ def wrapper(*args, **kwargs): for param in old_sig.parameters.values() if param.name not in {"fig_test", "fig_ref"} ] + if "ext" not in old_sig.parameters: + parameters += [inspect.Parameter("ext", KEYWORD_ONLY)] + if "request" not in old_sig.parameters: + parameters += [inspect.Parameter("request", KEYWORD_ONLY)] new_sig = old_sig.replace(parameters=parameters) wrapper.__signature__ = new_sig + # reach a bit into pytest internals to hoist the marks from + # our wrapped function + new_marks = getattr(func, "pytestmark", []) + wrapper.pytestmark + wrapper.pytestmark = new_marks + return wrapper return decorator From c1696fd303d4882dc20569ff76044c9a187f194b Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sun, 6 Sep 2020 13:53:40 +1200 Subject: [PATCH 2/5] Silence pylint complaints on ALLOWED_CHARS & KEYWORD_ONLY variable names --- pygmt/helpers/testing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pygmt/helpers/testing.py b/pygmt/helpers/testing.py index b321c9f4c24..bc58c33b706 100644 --- a/pygmt/helpers/testing.py +++ b/pygmt/helpers/testing.py @@ -69,6 +69,7 @@ def check_figures_equal(*, extensions=("png",), tol=0.0, result_dir="result_imag ... ) >>> shutil.rmtree(path="tmp_result_images") # cleanup folder if tests pass """ + # pylint: disable=invalid-name ALLOWED_CHARS = set(string.digits + string.ascii_letters + "_-[]()") KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY From 52b7d40057dd1a533c8b7575653707a7152896ed Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Fri, 11 Sep 2020 15:17:09 +1200 Subject: [PATCH 3/5] Fix doctest failures on helpers/testing.py --- pygmt/helpers/testing.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pygmt/helpers/testing.py b/pygmt/helpers/testing.py index bc58c33b706..d32b2f9b2a3 100644 --- a/pygmt/helpers/testing.py +++ b/pygmt/helpers/testing.py @@ -80,13 +80,15 @@ def decorator(func): old_sig = inspect.signature(func) @pytest.mark.parametrize("ext", extensions) - def wrapper(*args, ext, request, **kwargs): + def wrapper(*args, ext="png", request=None, **kwargs): if "ext" in old_sig.parameters: kwargs["ext"] = ext if "request" in old_sig.parameters: kwargs["request"] = request - - file_name = "".join(c for c in request.node.name if c in ALLOWED_CHARS) + try: + file_name = "".join(c for c in request.node.name if c in ALLOWED_CHARS) + except AttributeError: # 'NoneType' object has no attribute 'node' + file_name = func.__name__ try: fig_ref, fig_test = func(*args, **kwargs) ref_image_path = os.path.join(result_dir, f"{file_name}-expected.{ext}") From e166564a2b6c449e1c4e4d77acc1df576414d130 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sat, 12 Sep 2020 08:54:40 +1200 Subject: [PATCH 4/5] Update documentation on check_figures_equal --- CONTRIBUTING.md | 3 +-- pygmt/helpers/testing.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d4f299fa777..919b381dc26 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -328,9 +328,8 @@ Here's an example: @check_figures_equal() def test_my_plotting_case(): "Test that my plotting function works" - fig_ref = Figure() + fig_ref, fig_test = Figure(), Figure() fig_ref.grdimage("@earth_relief_01d_g", projection="W120/15c", cmap="geo") - fig_test = Figure() fig_test.grdimage(grid, projection="W120/15c", cmap="geo") return fig_ref, fig_test ``` diff --git a/pygmt/helpers/testing.py b/pygmt/helpers/testing.py index d32b2f9b2a3..a55bd77cfcb 100644 --- a/pygmt/helpers/testing.py +++ b/pygmt/helpers/testing.py @@ -15,9 +15,8 @@ def check_figures_equal(*, extensions=("png",), tol=0.0, result_dir="result_imag """ Decorator for test cases that generate and compare two figures. - The decorated function must take two arguments, *fig_ref* and *fig_test*, - and draw the reference and test images on them. After the function - returns, the figures are saved and compared. + The decorated function must return two arguments, *fig_ref* and *fig_test*, + these two figures will then be saved and compared against each other. This decorator is practically identical to matplotlib's check_figures_equal function, but adapted for PyGMT figures. See also the original code at From caa92126219d309eb8a27593f26d244bb9172e33 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sat, 12 Sep 2020 09:17:44 +1200 Subject: [PATCH 5/5] Removed unused Figure import --- pygmt/helpers/testing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pygmt/helpers/testing.py b/pygmt/helpers/testing.py index a55bd77cfcb..f762051d8a1 100644 --- a/pygmt/helpers/testing.py +++ b/pygmt/helpers/testing.py @@ -6,9 +6,7 @@ import string from matplotlib.testing.compare import compare_images - from ..exceptions import GMTImageComparisonFailure -from ..figure import Figure def check_figures_equal(*, extensions=("png",), tol=0.0, result_dir="result_images"):