From 6a50d95f5c4a9a45b0a4aeb371d1fc765862a2fd Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Mon, 9 Nov 2020 14:06:28 +0000 Subject: [PATCH 01/16] Implement always compare mode for hash library --- pytest_mpl/plugin.py | 75 +++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/pytest_mpl/plugin.py b/pytest_mpl/plugin.py index 578ebb56..1b7d8041 100644 --- a/pytest_mpl/plugin.py +++ b/pytest_mpl/plugin.py @@ -155,6 +155,12 @@ def pytest_addoption(parser): results_path_help = "directory for test results, relative to location where py.test is run" group.addoption('--mpl-results-path', help=results_path_help, action='store') parser.addini('mpl-results-path', help=results_path_help) + + results_always_help = "Always generate result images, not just for failed tests." + group.addoption('--mpl-results-always', action='store_true', + help=results_always_help) + parser.addini('mpl-results-always', help=results_always_help) + parser.addini('mpl-use-full-test-name', help="use fully qualified test name as the filename.", type='bool') @@ -175,6 +181,8 @@ def pytest_configure(config): results_dir = config.getoption("--mpl-results-path") or config.getini("mpl-results-path") hash_library = config.getoption("--mpl-hash-library") generate_summary = config.getoption("--mpl-generate-summary") + results_always = config.getoption("--mpl-results-always") or config.getini("mpl-results-always") + if config.getoption("--mpl-baseline-relative"): baseline_relative_dir = config.getoption("--mpl-baseline-path") @@ -205,7 +213,8 @@ def pytest_configure(config): results_dir=results_dir, hash_library=hash_library, generate_hash_library=generate_hash_lib, - generate_summary=generate_summary)) + generate_summary=generate_summary, + results_always=results_always)) else: @@ -262,7 +271,8 @@ def __init__(self, results_dir=None, hash_library=None, generate_hash_library=None, - generate_summary=None + generate_summary=None, + results_always=False ): self.config = config self.baseline_dir = baseline_dir @@ -274,6 +284,7 @@ def __init__(self, if generate_summary and generate_summary.lower() not in ("html",): raise ValueError(f"The mpl summary type '{generate_summary}' is not supported.") self.generate_summary = generate_summary + self.results_always = results_always # Generate the containing dir for all test results if not self.results_dir: @@ -389,7 +400,6 @@ def generate_baseline_image(self, item, fig): **savefig_kwargs) close_mpl_figure(fig) - pytest.skip("Skipping test, since generating image") def generate_image_hash(self, item, fig): """ @@ -455,6 +465,10 @@ def load_hash_library(self, library_path): return json.load(fp) def compare_image_to_hash_library(self, item, fig, result_dir): + new_test = False + hash_comparison_pass = False + baseline_image_path = None + compare = self.get_compare(item) savefig_kwargs = compare.kwargs.get('savefig_kwargs', {}) @@ -470,33 +484,52 @@ def compare_image_to_hash_library(self, item, fig, result_dir): test_hash = self.generate_image_hash(item, fig) if hash_name not in hash_library: - return (f"Hash for test '{hash_name}' not found in {hash_library_filename}. " - f"Generated hash is {test_hash}.") + new_test = True + error_message = (f"Hash for test '{hash_name}' not found in {hash_library_filename}. " + f"Generated hash is {test_hash}.") - if test_hash == hash_library[hash_name]: - return - error_message = (f"Hash {test_hash} doesn't match hash " - f"{hash_library[hash_name]} in library " - f"{hash_library_filename} for test {hash_name}.") + # Save the figure for later summary (will be removed later if not needed) + test_image = (result_dir / "result.png").absolute() + fig.savefig(str(test_image), **savefig_kwargs) + + if not new_test: + if test_hash == hash_library[hash_name]: + hash_comparison_pass = True + else: + error_message = (f"Hash {test_hash} doesn't match hash " + f"{hash_library[hash_name]} in library " + f"{hash_library_filename} for test {hash_name}.") # If the compare has only been specified with hash and not baseline # dir, don't attempt to find a baseline image at the default path. - if not self.baseline_directory_specified(item): - # Save the figure for later summary - test_image = (result_dir / "result.png").absolute() - fig.savefig(str(test_image), **savefig_kwargs) + if not hash_comparison_pass and not self.baseline_directory_specified(item) or new_test: return error_message - baseline_image_path = self.obtain_baseline_image(item, result_dir) + # Get the baseline and generate a diff image, always so that + # --mpl-results-always can be respected. + # Ignore Errors here as it's possible the reference image dosen't exist yet. + try: + baseline_comparison = self.compare_image_to_baseline(item, fig, result_dir) + except Exception as e: + pass + + # If the hash comparison passes then return + if hash_comparison_pass: + return + + # If this is not a new test try and get the baseline image. + if not new_test: + baseline_image_path = self.obtain_baseline_image(item, result_dir) + try: baseline_image = baseline_image_path - baseline_image = None if not baseline_image.exists() else baseline_image + baseline_image = None if (baseline_image and not baseline_image.exists()) else baseline_image except Exception: baseline_image = None if baseline_image is None: - error_message += f"\nUnable to find baseline image {baseline_image_path}." + error_message += f"\nUnable to find baseline image {baseline_image_path or ''}." return error_message # Override the tolerance (if not explicitly set) to 0 as the hashes are not forgiving @@ -504,7 +537,7 @@ def compare_image_to_hash_library(self, item, fig, result_dir): if not tolerance: compare.kwargs['tolerance'] = 0 - comparison_error = (self.compare_image_to_baseline(item, fig, result_dir) or + comparison_error = (baseline_comparison or "\nHowever, the comparison to the baseline image succeeded.") return f"{error_message}\n{comparison_error}" @@ -552,10 +585,13 @@ def item_function_wrapper(*args, **kwargs): # reference images or simply running the test. if self.generate_dir is not None: self.generate_baseline_image(item, fig) + if self.generate_hash_library is None: + pytest.skip("Skipping test, since generating image.") if self.generate_hash_library is not None: hash_name = self.generate_test_name(item) self._generated_hash_library[hash_name] = self.generate_image_hash(item, fig) + pytest.skip("Skipping test as generating hash library.") # Only test figures if we are not generating hashes or images if self.generate_dir is None and self.generate_hash_library is None: @@ -572,7 +608,8 @@ def item_function_wrapper(*args, **kwargs): close_mpl_figure(fig) if msg is None: - shutil.rmtree(result_dir) + if not self.results_always: + shutil.rmtree(result_dir) else: pytest.fail(msg, pytrace=False) From e2cdacd0a6d82217015c156a99409dda44605fc5 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Mon, 9 Nov 2020 14:31:20 +0000 Subject: [PATCH 02/16] Some error ordering issues --- pytest_mpl/plugin.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/pytest_mpl/plugin.py b/pytest_mpl/plugin.py index 1b7d8041..daebe0b0 100644 --- a/pytest_mpl/plugin.py +++ b/pytest_mpl/plugin.py @@ -293,6 +293,7 @@ def __init__(self, # We need global state to store all the hashes generated over the run self._generated_hash_library = {} + self._test_results = {} def get_compare(self, item): """ @@ -506,21 +507,16 @@ def compare_image_to_hash_library(self, item, fig, result_dir): if not hash_comparison_pass and not self.baseline_directory_specified(item) or new_test: return error_message - # Get the baseline and generate a diff image, always so that - # --mpl-results-always can be respected. - # Ignore Errors here as it's possible the reference image dosen't exist yet. - try: - baseline_comparison = self.compare_image_to_baseline(item, fig, result_dir) - except Exception as e: - pass - - # If the hash comparison passes then return - if hash_comparison_pass: - return - # If this is not a new test try and get the baseline image. if not new_test: - baseline_image_path = self.obtain_baseline_image(item, result_dir) + # Ignore Errors here as it's possible the reference image dosen't exist yet. + try: + baseline_image_path = self.obtain_baseline_image(item, result_dir) + # Get the baseline and generate a diff image, always so that + # --mpl-results-always can be respected. + baseline_comparison = self.compare_image_to_baseline(item, fig, result_dir) + except Exception as e: + warnings.warn(str(e)) try: baseline_image = baseline_image_path @@ -528,6 +524,10 @@ def compare_image_to_hash_library(self, item, fig, result_dir): except Exception: baseline_image = None + # If the hash comparison passes then return + if hash_comparison_pass: + return + if baseline_image is None: error_message += f"\nUnable to find baseline image {baseline_image_path or ''}." return error_message @@ -581,6 +581,8 @@ def item_function_wrapper(*args, **kwargs): if remove_text: remove_ticks_and_titles(fig) + test_name = self.generate_test_name(item) + # What we do now depends on whether we are generating the # reference images or simply running the test. if self.generate_dir is not None: @@ -589,8 +591,7 @@ def item_function_wrapper(*args, **kwargs): pytest.skip("Skipping test, since generating image.") if self.generate_hash_library is not None: - hash_name = self.generate_test_name(item) - self._generated_hash_library[hash_name] = self.generate_image_hash(item, fig) + self._generated_hash_library[test_name] = self.generate_image_hash(item, fig) pytest.skip("Skipping test as generating hash library.") # Only test figures if we are not generating hashes or images @@ -607,6 +608,8 @@ def item_function_wrapper(*args, **kwargs): close_mpl_figure(fig) + self._test_results[str(pathify(test_name))] = msg or True + if msg is None: if not self.results_always: shutil.rmtree(result_dir) @@ -629,8 +632,10 @@ def generate_summary_html(self, dir_list): f.write(HTML_INTRO) for directory in dir_list: + test_name = directory.parts[-1] + test_result = 'passed' if self._test_results[test_name] is True else 'failed' f.write('' - f'{directory.parts[-1]}\n' + f'{test_name} ({test_result})\n' f'\n' f'\n' f'\n' From 5ddddc8d73ed4c0b30c99128c4435dbef5cf3d93 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 2 Jul 2021 16:18:47 +0100 Subject: [PATCH 03/16] Undefined variable --- pytest_mpl/plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pytest_mpl/plugin.py b/pytest_mpl/plugin.py index 1adeab87..754fc71c 100644 --- a/pytest_mpl/plugin.py +++ b/pytest_mpl/plugin.py @@ -509,6 +509,7 @@ def compare_image_to_hash_library(self, item, fig, result_dir): # If this is not a new test try and get the baseline image. if not new_test: + baseline_error = None # Ignore Errors here as it's possible the reference image dosen't exist yet. try: baseline_image_path = self.obtain_baseline_image(item, result_dir) @@ -525,7 +526,9 @@ def compare_image_to_hash_library(self, item, fig, result_dir): return if baseline_image is None: - error_message += f"\nUnable to find baseline image for {item}.\n{baseline_error}" + error_message += f"\nUnable to find baseline image for {item}." + if baseline_error: + error_message += f"\n{baseline_error}" return error_message # Override the tolerance (if not explicitly set) to 0 as the hashes are not forgiving From 6c2b18a54cf18de071c1585f5ff51af7d14bb47b Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 2 Jul 2021 12:43:57 +0100 Subject: [PATCH 04/16] Document minimum Python version correctly --- README.rst | 8 +------- setup.cfg | 2 ++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 933c67e8..49a68d03 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,3 @@ -|Travis Build Status| |Coveralls coverage| - About ----- @@ -18,7 +16,7 @@ section below. Installing ---------- -This plugin is compatible with Python 2.7, and 3.5 and later, and +This plugin is compatible with Python 2.7, and 3.6 and later, and requires `pytest `__ and `matplotlib ` to be installed. @@ -285,10 +283,6 @@ install the latest version of the plugin then do:: The reason for having to install the plugin first is to ensure that the plugin is correctly loaded as part of the test suite. -.. |Travis Build Status| image:: https://travis-ci.org/matplotlib/pytest-mpl.svg?branch=master - :target: https://travis-ci.org/matplotlib/pytest-mpl -.. |Coveralls coverage| image:: https://coveralls.io/repos/matplotlib/pytest-mpl/badge.svg - :target: https://coveralls.io/r/matplotlib/pytest-mpl .. |expected| image:: images/baseline-coords_overlay_auto_coord_meta.png .. |actual| image:: images/coords_overlay_auto_coord_meta.png .. |diff| image:: images/coords_overlay_auto_coord_meta-failed-diff.png diff --git a/setup.cfg b/setup.cfg index 38f6129e..b56d9f49 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,10 +19,12 @@ classifiers = license = BSD description = pytest plugin to help with testing figures output from Matplotlib long_description = file: README.rst +long_description_content_type = text/x-rst [options] zip_safe = True packages = find: +python_requires = >=3.6 install_requires = pytest matplotlib From 5155dfd49f1956b9ca69166976ac555177645fe4 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 2 Jul 2021 15:44:53 +0100 Subject: [PATCH 05/16] Always generate hashes when requested --- pytest_mpl/plugin.py | 4 ++-- tests/test_pytest_mpl.py | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/pytest_mpl/plugin.py b/pytest_mpl/plugin.py index 754fc71c..d01dee89 100644 --- a/pytest_mpl/plugin.py +++ b/pytest_mpl/plugin.py @@ -593,8 +593,8 @@ def item_function_wrapper(*args, **kwargs): self._generated_hash_library[test_name] = self.generate_image_hash(item, fig) pytest.skip("Skipping test as generating hash library.") - # Only test figures if we are not generating hashes or images - if self.generate_dir is None and self.generate_hash_library is None: + # Only test figures if not generating images + if self.generate_dir is None: result_dir = self.make_test_results_dir(item) # Compare to hash library diff --git a/tests/test_pytest_mpl.py b/tests/test_pytest_mpl.py index 146bc0a9..735defd9 100644 --- a/tests/test_pytest_mpl.py +++ b/tests/test_pytest_mpl.py @@ -51,6 +51,8 @@ def assert_pytest_fails_with(args, output_substring): output = exc.output.decode() assert output_substring in output, output return output + else: + raise RuntimeError(f'pytest did not fail with args {args}') @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir_local, @@ -187,10 +189,10 @@ def test_generate(tmpdir): assert code == 0 assert os.path.exists(os.path.join(gen_dir, 'test_gen.png')) - # If we do generate hash, the test should succeed and a new file will appear + # If we do generate hash, the test will fail as no image is present hash_file = os.path.join(gen_dir, 'test_hashes.json') code = call_pytest([f'--mpl-generate-hash-library={hash_file}', test_file]) - assert code == 0 + assert code == 1 assert os.path.exists(hash_file) with open(hash_file) as fp: @@ -348,6 +350,38 @@ def test_hash_fail_hybrid(tmpdir): assert code == 0 +TEST_FAILING_NEW_HASH = r""" +import pytest +import matplotlib.pyplot as plt +@pytest.mark.mpl_image_compare +def test_hash_fails(): + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,2]) + return fig +""" + + +@pytest.mark.skipif(ftv != '261', reason="Incorrect freetype version for hash check") +def test_hash_fail_new_hashes(tmpdir): + # Check that the hash comparison fails even if a new hash file is requested + test_file = tmpdir.join('test.py').strpath + with open(test_file, 'w', encoding='ascii') as f: + f.write(TEST_FAILING_NEW_HASH) + + # Assert that image comparison runs and fails + assert_pytest_fails_with(['--mpl', test_file, + f'--mpl-hash-library={fail_hash_library}'], + "doesn't match hash FAIL in library") + + hash_file = tmpdir.join('new_hashes.json').strpath + # Assert that image comparison runs and fails + assert_pytest_fails_with(['--mpl', test_file, + f'--mpl-hash-library={fail_hash_library}', + f'--mpl-generate-hash-library={hash_file}'], + "doesn't match hash FAIL") + + TEST_MISSING_HASH = """ import pytest import matplotlib.pyplot as plt From 28a24e04c74cdf50386eddd6b55d495802279a1e Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 2 Jul 2021 16:30:49 +0100 Subject: [PATCH 06/16] Write changelog for 0.13 --- CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 400a2a2e..c3e445f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,19 @@ +0.13 (2021-07-02) +--------------------- + +- Ensure all test files are included in the sdist. [#109] + +- Print hash for new figure tests. [#111] + +- Do not error if a baseline image can not be retrieved when using figure hashes. [#118] + +- Allow generation of hash library and testing against hash library simultaneously. [#121] + +0.12.1 (2021-07-02) +------------------- + +- Fix specification of required Python version in setup.cfg. [#122] + 0.12 (2020-11-05) ----------------- From 23b4ff37776e8e4e633947cd01a65209d1398508 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 2 Jul 2021 16:31:24 +0100 Subject: [PATCH 07/16] Release 0.13 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b56d9f49..17e311d5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] license_file = LICENSE name = pytest-mpl -version = 0.12 +version = 0.13 url = https://github.com/matplotlib/pytest-mpl author = Thomas Robitaille author_email = thomas.robitaille@gmail.com From e13aadf75366034fd94bc1d3dbb5d9f3d40ad8e5 Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Thu, 9 Dec 2021 23:55:27 +0000 Subject: [PATCH 08/16] Add test --- tests/test_pytest_mpl.py | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/test_pytest_mpl.py b/tests/test_pytest_mpl.py index 735defd9..f7ea32fb 100644 --- a/tests/test_pytest_mpl.py +++ b/tests/test_pytest_mpl.py @@ -411,3 +411,60 @@ def test_hash_missing(tmpdir): # If we don't use --mpl option, the test should succeed code = call_pytest([test_file]) assert code == 0 + + +TEST_RESULTS_ALWAYS = """ +import pytest +import matplotlib.pyplot as plt +def plot(): + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,2]) + return fig +@pytest.mark.mpl_image_compare +def test_modified(): return plot() +@pytest.mark.mpl_image_compare +def test_new(): return plot() +@pytest.mark.mpl_image_compare +def test_unmodified(): return plot() +""" + + +def test_results_always(tmpdir): + + test_file = tmpdir.join('test.py').strpath + with open(test_file, 'w') as f: + f.write(TEST_RESULTS_ALWAYS) + results_path = tmpdir.mkdir('results') + + code = call_pytest(['--mpl', test_file, '--mpl-results-always', + rf'--mpl-hash-library={hash_library}', + rf'--mpl-baseline-path={baseline_dir_abs}', + '--mpl-generate-summary=html', + rf'--mpl-results-path={results_path.strpath}']) + assert code == 0 # hashes correct, so all should pass + + comparison_file = results_path.join('fig_comparison.html') + with open(comparison_file, 'r') as f: + html = f.read() + + # each test, and which images should exist + for test, exists in [ + ('test_modified', ['baseline', 'result-failed-diff', 'result']), + ('test_new', ['result']), + ('test_unmodified', ['baseline', 'result']), + ]: + + test_name = f'test.{test}' + + summary = f'{test_name} (passed)' + assert summary in html + + for image_type in ['baseline', 'result-failed-diff', 'result']: + image = f'{test_name}/{image_type}.png' + assert image in html # is present even if 404 + image_exists = results_path.join(*image.split('/')).exists() + if image_type in exists: + assert image_exists + else: + assert not image_exists From a41e0076dd5bc669d60443b534f406c232e07aad Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 00:16:16 +0000 Subject: [PATCH 09/16] Add hash placeholders Signed-off-by: Conor MacBride --- tests/baseline/hashes/mpl20_ft2104.json | 7 +++++-- tests/baseline/hashes/mpl21_ft2104.json | 7 +++++-- tests/baseline/hashes/mpl22_ft2104.json | 7 +++++-- tests/baseline/hashes/mpl30_ft2104.json | 7 +++++-- tests/baseline/hashes/mpl31_ft261.json | 7 +++++-- tests/baseline/hashes/mpl32_ft261.json | 7 +++++-- tests/baseline/hashes/mpl33_ft261.json | 5 ++++- 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/tests/baseline/hashes/mpl20_ft2104.json b/tests/baseline/hashes/mpl20_ft2104.json index b16aba5d..00983ea9 100644 --- a/tests/baseline/hashes/mpl20_ft2104.json +++ b/tests/baseline/hashes/mpl20_ft2104.json @@ -8,5 +8,8 @@ "test_pytest_mpl.test_remove_text": "9c284d7bcbbb1d6c1362b417859e4ce842b573a2fe32c7ceaafcf328a1eb7057", "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", - "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498" -} \ No newline at end of file + "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" +} diff --git a/tests/baseline/hashes/mpl21_ft2104.json b/tests/baseline/hashes/mpl21_ft2104.json index b16aba5d..00983ea9 100644 --- a/tests/baseline/hashes/mpl21_ft2104.json +++ b/tests/baseline/hashes/mpl21_ft2104.json @@ -8,5 +8,8 @@ "test_pytest_mpl.test_remove_text": "9c284d7bcbbb1d6c1362b417859e4ce842b573a2fe32c7ceaafcf328a1eb7057", "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", - "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498" -} \ No newline at end of file + "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" +} diff --git a/tests/baseline/hashes/mpl22_ft2104.json b/tests/baseline/hashes/mpl22_ft2104.json index b16aba5d..00983ea9 100644 --- a/tests/baseline/hashes/mpl22_ft2104.json +++ b/tests/baseline/hashes/mpl22_ft2104.json @@ -8,5 +8,8 @@ "test_pytest_mpl.test_remove_text": "9c284d7bcbbb1d6c1362b417859e4ce842b573a2fe32c7ceaafcf328a1eb7057", "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", - "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498" -} \ No newline at end of file + "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" +} diff --git a/tests/baseline/hashes/mpl30_ft2104.json b/tests/baseline/hashes/mpl30_ft2104.json index b16aba5d..00983ea9 100644 --- a/tests/baseline/hashes/mpl30_ft2104.json +++ b/tests/baseline/hashes/mpl30_ft2104.json @@ -8,5 +8,8 @@ "test_pytest_mpl.test_remove_text": "9c284d7bcbbb1d6c1362b417859e4ce842b573a2fe32c7ceaafcf328a1eb7057", "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", - "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498" -} \ No newline at end of file + "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" +} diff --git a/tests/baseline/hashes/mpl31_ft261.json b/tests/baseline/hashes/mpl31_ft261.json index 588466c9..d0c7081a 100644 --- a/tests/baseline/hashes/mpl31_ft261.json +++ b/tests/baseline/hashes/mpl31_ft261.json @@ -9,5 +9,8 @@ "test_pytest_mpl.test_parametrized[5]": "be7dc9de64a5d6fd458c1f930d4aa56cf8196ddb0e8b5b07ab79a1f0ea9eb820", "test_pytest_mpl.test_parametrized[50]": "a8ae2427337803dc864784d88c4428a6af5a3e47d2bfc84c98b68b25fde75704", "test_pytest_mpl.test_parametrized[500]": "590ef42388378173e293bd37e95ff22d8e753d53327d1fb5d6bdf2bac4f84d01", - "test_pytest_mpl.test_hash_succeeds": "2a4da3a36b384df539f3f47d476f67a918f5eee1df360dbab9469b96260df78f" -} \ No newline at end of file + "test_pytest_mpl.test_hash_succeeds": "2a4da3a36b384df539f3f47d476f67a918f5eee1df360dbab9469b96260df78f", + "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" +} diff --git a/tests/baseline/hashes/mpl32_ft261.json b/tests/baseline/hashes/mpl32_ft261.json index ac0bc8fb..762d4d54 100644 --- a/tests/baseline/hashes/mpl32_ft261.json +++ b/tests/baseline/hashes/mpl32_ft261.json @@ -9,5 +9,8 @@ "test_pytest_mpl.test_parametrized[5]": "9b2b5b1df784c8f9a5fc624840138fe7b4dbdd42cf592fe5733c9c825e5dda91", "test_pytest_mpl.test_parametrized[50]": "fcf0566ef5514674e2b4bf1e9b4c7f52451c6f98abdc75dc876f43c97a23bc32", "test_pytest_mpl.test_parametrized[500]": "38dccccfc980b44359bc1b325bef48471bc084db37ed622af00a553792a8b093", - "test_pytest_mpl.test_hash_succeeds": "8b8ff9ce044bc9075876278781667a708414460209bba25a39d8262ed73d0f04" -} \ No newline at end of file + "test_pytest_mpl.test_hash_succeeds": "8b8ff9ce044bc9075876278781667a708414460209bba25a39d8262ed73d0f04", + "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" +} diff --git a/tests/baseline/hashes/mpl33_ft261.json b/tests/baseline/hashes/mpl33_ft261.json index e8502633..77c5f03f 100644 --- a/tests/baseline/hashes/mpl33_ft261.json +++ b/tests/baseline/hashes/mpl33_ft261.json @@ -9,5 +9,8 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", - "test_pytest_mpl.test_hash_succeeds": "55ad74a44c99606f1df1e79f67a59a4986bddc2b48ea2b2d7ea8365db91dc7ca" + "test_pytest_mpl.test_hash_succeeds": "55ad74a44c99606f1df1e79f67a59a4986bddc2b48ea2b2d7ea8365db91dc7ca", + "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", + "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" } From c53daf72ba806d77ea2d8325abb84dcdf1b14f0f Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 00:17:43 +0000 Subject: [PATCH 10/16] Results always docs Signed-off-by: Conor MacBride --- README.rst | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 49a68d03..86b3e6b1 100644 --- a/README.rst +++ b/README.rst @@ -84,19 +84,19 @@ The hash library can be generated with ``--mpl-generate-hash-library=path_to_file.json``. The hash library to be used can either be specified via the ``--mpl-hash-library=`` command line argument, or via the ``hash_library=`` keyword argument to the -``@pytest.mark.mpl_image_comapre`` decorator. +``@pytest.mark.mpl_image_compare`` decorator. Hybrid Mode: Hashes and Images ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is possible to configure both hashes and baseline images. In this scenario -the hashes will be compared first, and the baseline images used if the hash +the hashes will be compared first, with the baseline images only used if the hash comparison fails. This is especially useful if the baseline images are external to the repository -with the tests in, and can be accessed remotely. In this situation if the hashes -match the baseline images wont be retrieved, saving time and bandwidth. Also it +containing the tests, and are accessed via HTTP. In this situation, if the hashes +match, the baseline images won't be retrieved, saving time and bandwidth. Also, it allows the tests to be modified and the hashes updated to reflect the changes without having to modify the external images. @@ -111,16 +111,16 @@ against, the tests can be run with:: and the tests will pass if the images are the same. If you omit the ``--mpl`` option, the tests will run but will only check that the code -runs without checking the output images. +runs, without checking the output images. Generating a Failure Summary ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By specifying the ``--mpl-generate-summary=html`` CLI argument a HTML summary +By specifying the ``--mpl-generate-summary=html`` CLI argument, a HTML summary page will be generated showing the baseline, diff and result image for each failing test. If no baseline images are configured, just the result images will -be displayed. +be displayed. (See also, the **Results always** section below.) Options ------- @@ -182,6 +182,24 @@ are run. In addition, if both this option and the ``baseline_dir`` option in the ``mpl_image_compare`` decorator are used, the one in the decorator takes precedence. +Results always +^^^^^^^^^^^^^^ + +By default, result images are only generated for tests that fail. +Passing ``--mpl-results-always`` to pytest will force result images +to be generated for all tests, even for tests that pass. +If a baseline image exists, a diff image will also be generated. +All of these images will be shown in the summary (if requested). + +This option is useful for always *comparing* the result images again +the baseline images, while only *assessing* the tests against the +hash library. +If you only update your baseline images after merging a PR, this +option means that the generated summary will always show how the +PR affects the baseline images, with the success status of each +test (based on the hash library) also shown in the generated +summary. + Base style ^^^^^^^^^^ From 6b7c3544fb632256334fc1cdaf7ba74ace813cd0 Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 11:19:44 +0000 Subject: [PATCH 11/16] Fix codestyle Signed-off-by: Conor MacBride --- pytest_mpl/plugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pytest_mpl/plugin.py b/pytest_mpl/plugin.py index d01dee89..3481f87b 100644 --- a/pytest_mpl/plugin.py +++ b/pytest_mpl/plugin.py @@ -181,8 +181,8 @@ def pytest_configure(config): results_dir = config.getoption("--mpl-results-path") or config.getini("mpl-results-path") hash_library = config.getoption("--mpl-hash-library") generate_summary = config.getoption("--mpl-generate-summary") - results_always = config.getoption("--mpl-results-always") or config.getini("mpl-results-always") - + results_always = (config.getoption("--mpl-results-always") or + config.getini("mpl-results-always")) if config.getoption("--mpl-baseline-relative"): baseline_relative_dir = config.getoption("--mpl-baseline-path") @@ -489,7 +489,6 @@ def compare_image_to_hash_library(self, item, fig, result_dir): error_message = (f"Hash for test '{hash_name}' not found in {hash_library_filename}. " f"Generated hash is {test_hash}.") - # Save the figure for later summary (will be removed later if not needed) test_image = (result_dir / "result.png").absolute() fig.savefig(str(test_image), **savefig_kwargs) @@ -514,12 +513,14 @@ def compare_image_to_hash_library(self, item, fig, result_dir): try: baseline_image_path = self.obtain_baseline_image(item, result_dir) baseline_image = baseline_image_path - baseline_image = None if (baseline_image and not baseline_image.exists()) else baseline_image + if baseline_image and not baseline_image.exists(): + baseline_image = None # Get the baseline and generate a diff image, always so that # --mpl-results-always can be respected. baseline_comparison = self.compare_image_to_baseline(item, fig, result_dir) - except Exception as baseline_error: + except Exception as e: baseline_image = None + baseline_error = e # If the hash comparison passes then return if hash_comparison_pass: From cebac89bf7974df0ae3e1254f2563bb7967fc5a2 Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 11:37:15 +0000 Subject: [PATCH 12/16] Add baseline images Signed-off-by: Conor MacBride --- tests/baseline/2.0.x/test_modified.png | Bin 0 -> 22906 bytes tests/baseline/2.0.x/test_unmodified.png | Bin 0 -> 16904 bytes tests/test_pytest_mpl.py | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 tests/baseline/2.0.x/test_modified.png create mode 100644 tests/baseline/2.0.x/test_unmodified.png diff --git a/tests/baseline/2.0.x/test_modified.png b/tests/baseline/2.0.x/test_modified.png new file mode 100644 index 0000000000000000000000000000000000000000..402b29fc84ea78631fddd571080f1026ae41fe3c GIT binary patch literal 22906 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 literal 0 HcmV?d00001 diff --git a/tests/baseline/2.0.x/test_unmodified.png b/tests/baseline/2.0.x/test_unmodified.png new file mode 100644 index 0000000000000000000000000000000000000000..beb80cd16ded507dd0f453b46d6c6a4cc630ab9b GIT binary patch literal 16904 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 is present even if 404 image_exists = results_path.join(*image.split('/')).exists() - if image_type in exists: - assert image_exists + if image_type in exists: # assert image so pytest prints it on error + assert image and image_exists else: - assert not image_exists + assert image and not image_exists From 3b6ebeff8e581a0504909201b23f7b6ce104ee3c Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 13:00:24 +0000 Subject: [PATCH 13/16] Make compatible with matplotlib/pytest-mpl#121 Signed-off-by: Conor MacBride --- pytest_mpl/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytest_mpl/plugin.py b/pytest_mpl/plugin.py index 3481f87b..2e1e3ef7 100644 --- a/pytest_mpl/plugin.py +++ b/pytest_mpl/plugin.py @@ -592,7 +592,6 @@ def item_function_wrapper(*args, **kwargs): if self.generate_hash_library is not None: self._generated_hash_library[test_name] = self.generate_image_hash(item, fig) - pytest.skip("Skipping test as generating hash library.") # Only test figures if not generating images if self.generate_dir is None: From 3d5381ee4f06b1d3a5dc1d963cf512a3bb092a9f Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 13:31:54 +0000 Subject: [PATCH 14/16] Rename ft2104 to ft261 Signed-off-by: Conor MacBride --- tests/baseline/hashes/{mpl20_ft2104.json => mpl20_ft261.json} | 0 tests/baseline/hashes/{mpl21_ft2104.json => mpl21_ft261.json} | 0 tests/baseline/hashes/{mpl22_ft2104.json => mpl22_ft261.json} | 0 tests/baseline/hashes/{mpl30_ft2104.json => mpl30_ft261.json} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename tests/baseline/hashes/{mpl20_ft2104.json => mpl20_ft261.json} (100%) rename tests/baseline/hashes/{mpl21_ft2104.json => mpl21_ft261.json} (100%) rename tests/baseline/hashes/{mpl22_ft2104.json => mpl22_ft261.json} (100%) rename tests/baseline/hashes/{mpl30_ft2104.json => mpl30_ft261.json} (100%) diff --git a/tests/baseline/hashes/mpl20_ft2104.json b/tests/baseline/hashes/mpl20_ft261.json similarity index 100% rename from tests/baseline/hashes/mpl20_ft2104.json rename to tests/baseline/hashes/mpl20_ft261.json diff --git a/tests/baseline/hashes/mpl21_ft2104.json b/tests/baseline/hashes/mpl21_ft261.json similarity index 100% rename from tests/baseline/hashes/mpl21_ft2104.json rename to tests/baseline/hashes/mpl21_ft261.json diff --git a/tests/baseline/hashes/mpl22_ft2104.json b/tests/baseline/hashes/mpl22_ft261.json similarity index 100% rename from tests/baseline/hashes/mpl22_ft2104.json rename to tests/baseline/hashes/mpl22_ft261.json diff --git a/tests/baseline/hashes/mpl30_ft2104.json b/tests/baseline/hashes/mpl30_ft261.json similarity index 100% rename from tests/baseline/hashes/mpl30_ft2104.json rename to tests/baseline/hashes/mpl30_ft261.json From bcee6f4af629e45ff2a134ba69a2542e2fd9fe34 Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 13:52:44 +0000 Subject: [PATCH 15/16] Update hashes Signed-off-by: Conor MacBride --- tests/baseline/hashes/mpl20_ft261.json | 6 +++--- tests/baseline/hashes/mpl21_ft261.json | 6 +++--- tests/baseline/hashes/mpl22_ft261.json | 6 +++--- tests/baseline/hashes/mpl30_ft261.json | 6 +++--- tests/baseline/hashes/mpl31_ft261.json | 6 +++--- tests/baseline/hashes/mpl32_ft261.json | 6 +++--- tests/test_pytest_mpl.py | 1 + 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/baseline/hashes/mpl20_ft261.json b/tests/baseline/hashes/mpl20_ft261.json index 00983ea9..246f3f76 100644 --- a/tests/baseline/hashes/mpl20_ft261.json +++ b/tests/baseline/hashes/mpl20_ft261.json @@ -9,7 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", - "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" + "test.test_modified": "54f6cf83d5b06fa2ecb7fa23d6e87898679178ef5d0dfdd2551a139f1932127b", + "test.test_new": "54f6cf83d5b06fa2ecb7fa23d6e87898679178ef5d0dfdd2551a139f1932127b", + "test.test_unmodified": "54f6cf83d5b06fa2ecb7fa23d6e87898679178ef5d0dfdd2551a139f1932127b" } diff --git a/tests/baseline/hashes/mpl21_ft261.json b/tests/baseline/hashes/mpl21_ft261.json index 00983ea9..18538f00 100644 --- a/tests/baseline/hashes/mpl21_ft261.json +++ b/tests/baseline/hashes/mpl21_ft261.json @@ -9,7 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", - "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" + "test.test_modified": "14d326881467bc613e6504b87bd7d556a5e58668ff16b896fa3c15745cfb6336", + "test.test_new": "14d326881467bc613e6504b87bd7d556a5e58668ff16b896fa3c15745cfb6336", + "test.test_unmodified": "14d326881467bc613e6504b87bd7d556a5e58668ff16b896fa3c15745cfb6336" } diff --git a/tests/baseline/hashes/mpl22_ft261.json b/tests/baseline/hashes/mpl22_ft261.json index 00983ea9..f3cc8711 100644 --- a/tests/baseline/hashes/mpl22_ft261.json +++ b/tests/baseline/hashes/mpl22_ft261.json @@ -9,7 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", - "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" + "test.test_modified": "80e0ee6df7cf7d9d9407395a25af30beb8763e98820a7be972764899246d2cd7", + "test.test_new": "80e0ee6df7cf7d9d9407395a25af30beb8763e98820a7be972764899246d2cd7", + "test.test_unmodified": "80e0ee6df7cf7d9d9407395a25af30beb8763e98820a7be972764899246d2cd7" } diff --git a/tests/baseline/hashes/mpl30_ft261.json b/tests/baseline/hashes/mpl30_ft261.json index 00983ea9..49c7a677 100644 --- a/tests/baseline/hashes/mpl30_ft261.json +++ b/tests/baseline/hashes/mpl30_ft261.json @@ -9,7 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", - "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" + "test.test_modified": "6e2e4ba7b77caf62df24f6b92d6fc51ab1b837bf98039750334f65c0a6c5d898", + "test.test_new": "6e2e4ba7b77caf62df24f6b92d6fc51ab1b837bf98039750334f65c0a6c5d898", + "test.test_unmodified": "6e2e4ba7b77caf62df24f6b92d6fc51ab1b837bf98039750334f65c0a6c5d898" } diff --git a/tests/baseline/hashes/mpl31_ft261.json b/tests/baseline/hashes/mpl31_ft261.json index d0c7081a..dbde4d54 100644 --- a/tests/baseline/hashes/mpl31_ft261.json +++ b/tests/baseline/hashes/mpl31_ft261.json @@ -10,7 +10,7 @@ "test_pytest_mpl.test_parametrized[50]": "a8ae2427337803dc864784d88c4428a6af5a3e47d2bfc84c98b68b25fde75704", "test_pytest_mpl.test_parametrized[500]": "590ef42388378173e293bd37e95ff22d8e753d53327d1fb5d6bdf2bac4f84d01", "test_pytest_mpl.test_hash_succeeds": "2a4da3a36b384df539f3f47d476f67a918f5eee1df360dbab9469b96260df78f", - "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" + "test.test_modified": "3675e5a48388e8cc341580e9b41115d3cf63d2465cf11eeed3faa23e84030fc2", + "test.test_new": "3675e5a48388e8cc341580e9b41115d3cf63d2465cf11eeed3faa23e84030fc2", + "test.test_unmodified": "3675e5a48388e8cc341580e9b41115d3cf63d2465cf11eeed3faa23e84030fc2" } diff --git a/tests/baseline/hashes/mpl32_ft261.json b/tests/baseline/hashes/mpl32_ft261.json index 762d4d54..c39964b0 100644 --- a/tests/baseline/hashes/mpl32_ft261.json +++ b/tests/baseline/hashes/mpl32_ft261.json @@ -10,7 +10,7 @@ "test_pytest_mpl.test_parametrized[50]": "fcf0566ef5514674e2b4bf1e9b4c7f52451c6f98abdc75dc876f43c97a23bc32", "test_pytest_mpl.test_parametrized[500]": "38dccccfc980b44359bc1b325bef48471bc084db37ed622af00a553792a8b093", "test_pytest_mpl.test_hash_succeeds": "8b8ff9ce044bc9075876278781667a708414460209bba25a39d8262ed73d0f04", - "test.test_modified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_new": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181", - "test.test_unmodified": "ce07de6b726c3b01afb03aa7c9e939d584bc71a54b9737d69853a0d915cd6181" + "test.test_modified": "3b7db65812fd59403d17a2fba3ebe1fd0abdfde8633df06636e4e1daea259da0", + "test.test_new": "3b7db65812fd59403d17a2fba3ebe1fd0abdfde8633df06636e4e1daea259da0", + "test.test_unmodified": "3b7db65812fd59403d17a2fba3ebe1fd0abdfde8633df06636e4e1daea259da0" } diff --git a/tests/test_pytest_mpl.py b/tests/test_pytest_mpl.py index 15e65142..559e0499 100644 --- a/tests/test_pytest_mpl.py +++ b/tests/test_pytest_mpl.py @@ -430,6 +430,7 @@ def test_unmodified(): return plot() """ +@pytest.mark.skipif(not hash_library.exists(), reason="No hash library for this mpl version") def test_results_always(tmpdir): test_file = tmpdir.join('test.py').strpath From 445799806ab935411d51a518f5ce5f5475b5cf68 Mon Sep 17 00:00:00 2001 From: Conor MacBride Date: Fri, 10 Dec 2021 13:58:41 +0000 Subject: [PATCH 16/16] Add missing hashes Signed-off-by: Conor MacBride --- tests/baseline/hashes/mpl20_ft261.json | 1 + tests/baseline/hashes/mpl21_ft261.json | 1 + tests/baseline/hashes/mpl22_ft261.json | 1 + tests/baseline/hashes/mpl30_ft261.json | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/baseline/hashes/mpl20_ft261.json b/tests/baseline/hashes/mpl20_ft261.json index 246f3f76..76322063 100644 --- a/tests/baseline/hashes/mpl20_ft261.json +++ b/tests/baseline/hashes/mpl20_ft261.json @@ -9,6 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test_pytest_mpl.test_hash_succeeds": "480062c2239ed9d70e361d1a5b578dc2aa756971161ac6e7287b492ae6118c59", "test.test_modified": "54f6cf83d5b06fa2ecb7fa23d6e87898679178ef5d0dfdd2551a139f1932127b", "test.test_new": "54f6cf83d5b06fa2ecb7fa23d6e87898679178ef5d0dfdd2551a139f1932127b", "test.test_unmodified": "54f6cf83d5b06fa2ecb7fa23d6e87898679178ef5d0dfdd2551a139f1932127b" diff --git a/tests/baseline/hashes/mpl21_ft261.json b/tests/baseline/hashes/mpl21_ft261.json index 18538f00..8b2beb5a 100644 --- a/tests/baseline/hashes/mpl21_ft261.json +++ b/tests/baseline/hashes/mpl21_ft261.json @@ -9,6 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test_pytest_mpl.test_hash_succeeds": "17b65dd0247b0dfd8c1b4b079352414ae0fe03c0a3e79d63c8b8670d84d4098f", "test.test_modified": "14d326881467bc613e6504b87bd7d556a5e58668ff16b896fa3c15745cfb6336", "test.test_new": "14d326881467bc613e6504b87bd7d556a5e58668ff16b896fa3c15745cfb6336", "test.test_unmodified": "14d326881467bc613e6504b87bd7d556a5e58668ff16b896fa3c15745cfb6336" diff --git a/tests/baseline/hashes/mpl22_ft261.json b/tests/baseline/hashes/mpl22_ft261.json index f3cc8711..08c26f4d 100644 --- a/tests/baseline/hashes/mpl22_ft261.json +++ b/tests/baseline/hashes/mpl22_ft261.json @@ -9,6 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test_pytest_mpl.test_hash_succeeds": "e80557c8784fb920fb79b03b26dc072649a98811f00a8c212df8761e4351acde", "test.test_modified": "80e0ee6df7cf7d9d9407395a25af30beb8763e98820a7be972764899246d2cd7", "test.test_new": "80e0ee6df7cf7d9d9407395a25af30beb8763e98820a7be972764899246d2cd7", "test.test_unmodified": "80e0ee6df7cf7d9d9407395a25af30beb8763e98820a7be972764899246d2cd7" diff --git a/tests/baseline/hashes/mpl30_ft261.json b/tests/baseline/hashes/mpl30_ft261.json index 49c7a677..313078ea 100644 --- a/tests/baseline/hashes/mpl30_ft261.json +++ b/tests/baseline/hashes/mpl30_ft261.json @@ -9,6 +9,7 @@ "test_pytest_mpl.test_parametrized[5]": "04c998af2d7932ca4a851d610e8a020d94a2f623d1301dbe9b59fe6efd28a5f7", "test_pytest_mpl.test_parametrized[50]": "937d986ab6b209e7d48eb30cc30e9db62c93bbc4c86768e276a5b454e63bca93", "test_pytest_mpl.test_parametrized[500]": "e39ed724b0762b8736879801e32dc0c1525afd03c0567a43b119435aaa608498", + "test_pytest_mpl.test_hash_succeeds": "4e1157a93733cdb327f1741afdb0525f4d0e3f12e60b54f72c93db9f9c9ae27f", "test.test_modified": "6e2e4ba7b77caf62df24f6b92d6fc51ab1b837bf98039750334f65c0a6c5d898", "test.test_new": "6e2e4ba7b77caf62df24f6b92d6fc51ab1b837bf98039750334f65c0a6c5d898", "test.test_unmodified": "6e2e4ba7b77caf62df24f6b92d6fc51ab1b837bf98039750334f65c0a6c5d898"