diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 480abee..b0033fc 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,14 +4,14 @@ updates:
directory: "/" # Location of package manifests
insecure-external-code-execution: allow
schedule:
- interval: "daily"
+ interval: "monthly"
labels:
- "maintenance"
- "dependencies"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
- interval: "daily"
+ interval: "monthly"
labels:
- "maintenance"
- - "dependencies"
\ No newline at end of file
+ - "dependencies"
diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml
index d6d4e15..6f6cc58 100644
--- a/.github/workflows/ci_cd.yml
+++ b/.github/workflows/ci_cd.yml
@@ -22,7 +22,7 @@ jobs:
name: Build Documentation
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
@@ -59,8 +59,6 @@ jobs:
matrix:
include:
- - python-version: '3.7'
- vtk-version: '9.0.3'
- python-version: '3.8'
vtk-version: '9.0.3'
- python-version: '3.9'
@@ -70,7 +68,7 @@ jobs:
- python-version: '3.11'
vtk-version: 'latest'
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
@@ -87,6 +85,10 @@ jobs:
if: ${{ matrix.vtk-version != 'latest' }}
run: pip install vtk==${{ matrix.vtk-version }}
+ - name: Limit NumPy for VTK 9.0.3
+ if: ${{ matrix.vtk-version == '9.0.3' }}
+ run: pip install 'numpy<1.24'
+
- uses: awalsh128/cache-apt-pkgs-action@v1.1.3
with:
packages: libgl1-mesa-glx xvfb python-tk
@@ -113,7 +115,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
@@ -147,7 +149,7 @@ jobs:
name: Downstream tests
runs-on: ubuntu-20.04 # matching pyvista
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.11'
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 94be9fb..aab84d6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,26 +1,28 @@
+ci:
+ autofix_prs: false
+ autoupdate_schedule: monthly
+
repos:
- repo: https://github.com/psf/black
- rev: 22.10.0
+ rev: 23.9.1
hooks:
- id: black
- repo: https://github.com/pycqa/isort
- rev: 5.10.1
+ rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
- rev: 5.0.4
+ rev: 6.1.0
hooks:
- id: flake8
additional_dependencies: [
- "flake8-black==0.3.3",
- "flake8-isort==4.2.0",
- "flake8-quotes==3.3.1",
+ "flake8-quotes==3.3.2",
]
- repo: https://github.com/codespell-project/codespell
- rev: v2.2.2
+ rev: v2.2.5
hooks:
- id: codespell
args: [
@@ -29,7 +31,7 @@ repos:
]
- repo: https://github.com/pycqa/pydocstyle
- rev: 6.1.1
+ rev: 6.3.0
hooks:
- id: pydocstyle
additional_dependencies: [toml==0.10.2]
@@ -37,18 +39,18 @@ repos:
exclude: ^pytest-pyvista/ext/
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.982
+ rev: v1.5.1
hooks:
- id: mypy
exclude: ^(doc/|tests/|examples/|pytest-pyvista/ext/|examples_flask/)
additional_dependencies: [
- "mypy-extensions==0.4.3",
+ "mypy-extensions==1.0.0",
"toml==0.10.2",
"typing-extensions==4.1.1",
]
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
+ rev: v4.4.0
hooks:
- id: check-merge-conflict
- id: debug-statements
@@ -58,6 +60,6 @@ repos:
# this validates our github workflow files
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.18.4
+ rev: 0.26.3
hooks:
- id: check-github-workflows
diff --git a/LICENSE b/LICENSE
index 7cdac63..68e8075 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License
-Copyright (c) 2022 The PyVista Developers
+Copyright (c) 2022-2023 The PyVista Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.rst b/README.rst
index cd6dbe8..21f11b8 100644
--- a/README.rst
+++ b/README.rst
@@ -1,11 +1,15 @@
==============
pytest-pyvista
==============
-.. image:: https://img.shields.io/pypi/v/pytest-pyvista.svg
+.. image:: https://img.shields.io/pypi/v/pytest-pyvista.svg?color=orange&label=pypi&logo=python&logoColor=white
:target: https://pypi.org/project/pytest-pyvista
:alt: PyPI version
-.. image:: https://img.shields.io/pypi/pyversions/pytest-pyvista.svg
+.. image:: https://img.shields.io/conda/vn/conda-forge/pytest-pyvista?color=orange&label=conda-forge&logo=conda-forge&logoColor=white
+ :target: https://anaconda.org/conda-forge/pytest-pyvista
+ :alt: conda-forge version
+
+.. image:: https://img.shields.io/pypi/pyversions/pytest-pyvista.svg?color=orange&logo=python&label=python&logoColor=white
:target: https://pypi.org/project/pytest-pyvista
:alt: Python versions
@@ -34,7 +38,7 @@ fail.
Requirements
------------
-You must have a Python version greater than 3.7, as well as PyVista installed
+You must have a Python version >= 3.8, as well as PyVista installed
in your environment.
pyvista version >=0.37.0 and vtk version >=9.0.0 required.
@@ -45,6 +49,9 @@ You can install "pytest-pyvista" via `pip`_ from `PyPI`_::
$ pip install pytest-pyvista
+Alternatively, you can also install via `conda`_ or `mamba`_ from `conda-forge`_::
+
+ $ mamba install -c conda-forge pytest-pyvista
Usage
-----
@@ -104,6 +111,8 @@ These are the flags you can use when calling ``pytest`` in the command line:
* ``--image_cache_dir
`` sets the image cache dir. This will override any
configuration, see below.
+* ``--reset_only_failed`` reset the image cache of the failed tests only.
+
Test specific flags
-------------------
These are attributes of `verify_image_cache`. You can set them as ``True`` if needed
@@ -164,3 +173,6 @@ description.
.. _`tox`: https://tox.readthedocs.io/en/latest/
.. _`pip`: https://pypi.org/project/pip/
.. _`PyPI`: https://pypi.org/project
+.. _`conda`: https://github.com/conda/conda
+.. _`mamba`: https://github.com/mamba-org/mamba
+.. _`conda-forge`: https://anaconda.org/conda-forge/pytest-pyvista
diff --git a/pyproject.toml b/pyproject.toml
index 37439b5..88c39e7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,6 @@ classifiers=[
"Framework :: Pytest",
"Intended Audience :: Developers",
"Topic :: Software Development :: Testing",
- "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
@@ -22,7 +21,7 @@ classifiers=[
"License :: OSI Approved :: MIT License",
]
dependencies=["pytest>=3.5.0"]
-python_requires=">=3.7"
+python_requires=">=3.8"
[project.urls]
Home = "https://github.com/pyvista/pytest-pyvista"
@@ -31,15 +30,16 @@ Home = "https://github.com/pyvista/pytest-pyvista"
[project.optional-dependencies]
tests = [
"codecov<2.2.0",
- "coverage==7.2.2",
+ "coverage==7.3.1",
"pytest>=3.5.0",
- "pytest-cov==4.0.0",
- "numpy<1.24",
+ "pytest-cov==4.1.0",
+ "numpy<1.26",
]
docs = [
- "pydata-sphinx-theme==0.13.1",
- "sphinx_copybutton==0.5.1",
- "sphinx-notfound-page==0.8.3",
+ "pydata-sphinx-theme==0.13.3",
+ "sphinx==7.2.6",
+ "sphinx_copybutton==0.5.2",
+ "sphinx-notfound-page==1.0.0",
]
[project.entry-points."pytest11"]
diff --git a/pytest_pyvista/__init__.py b/pytest_pyvista/__init__.py
index a748244..ddb7ce6 100644
--- a/pytest_pyvista/__init__.py
+++ b/pytest_pyvista/__init__.py
@@ -1,5 +1,6 @@
"""Pytest-pyvista package"""
-__version__ = "0.1.8"
+__version__ = "0.1.9"
+
from .pytest_pyvista import VerifyImageCache
diff --git a/pytest_pyvista/pytest_pyvista.py b/pytest_pyvista/pytest_pyvista.py
index e88865b..8805251 100644
--- a/pytest_pyvista/pytest_pyvista.py
+++ b/pytest_pyvista/pytest_pyvista.py
@@ -8,6 +8,18 @@
import pyvista
+class RegressionError(RuntimeError):
+ """Error when regression does not meet the criteria"""
+
+ pass
+
+
+class RegressionFileNotFound(FileNotFoundError):
+ """Error when regression file is not found"""
+
+ pass
+
+
def pytest_addoption(parser):
"""Adds new flag options to the pyvista plugin."""
@@ -45,6 +57,11 @@ def pytest_addoption(parser):
default="image_cache_dir",
help="Path to the image cache folder.",
)
+ group.addoption(
+ "--reset_only_failed",
+ action="store_true",
+ help="Reset only the failed images in the PyVista cache.",
+ )
class VerifyImageCache:
@@ -103,6 +120,7 @@ class VerifyImageCache:
ignore_image_cache = False
fail_extra_image_cache = False
add_missing_images = False
+ reset_only_failed = False
def __init__(
self,
@@ -183,7 +201,9 @@ def __call__(self, plotter):
# cached image name. We remove the first 5 characters of the function name
# "test_" to get the name for the image.
- image_filename = os.path.join(self.cache_dir, test_name[5:] + ".png")
+ image_name = test_name[5:] + ".png"
+ image_filename = os.path.join(self.cache_dir, image_name)
+
if (
not os.path.isfile(image_filename)
and self.fail_extra_image_cache
@@ -191,13 +211,15 @@ def __call__(self, plotter):
):
# Make sure this doesn't get called again if this plotter doesn't close properly
plotter._before_close_callback = None
- raise RuntimeError(f"{image_filename} does not exist in image cache")
+ raise RegressionFileNotFound(
+ f"{image_filename} does not exist in image cache"
+ )
if (
self.add_missing_images
and not os.path.isfile(image_filename)
or self.reset_image_cache
- ):
+ ) and not self.reset_only_failed:
plotter.screenshot(image_filename)
if self.generated_image_dir is not None:
@@ -205,15 +227,24 @@ def __call__(self, plotter):
self.generated_image_dir, test_name[5:] + ".png"
)
plotter.screenshot(gen_image_filename)
+
error = pyvista.compare_images(image_filename, plotter)
if error > allowed_error:
- # Make sure this doesn't get called again if this plotter doesn't close properly
- plotter._before_close_callback = None
- raise RuntimeError(
- f"{test_name} Exceeded image regression error of "
- f"{allowed_error} with an image error equal to: {error}"
- )
+ if self.reset_only_failed:
+ warnings.warn(
+ f"{test_name} Exceeded image regression error of "
+ f"{allowed_error} with an image error equal to: {error}"
+ f"\nThis image will be reset in the cache."
+ )
+ plotter.screenshot(image_filename)
+ else:
+ # Make sure this doesn't get called again if this plotter doesn't close properly
+ plotter._before_close_callback = None
+ raise RegressionError(
+ f"{test_name} Exceeded image regression error of "
+ f"{allowed_error} with an image error equal to: {error}"
+ )
if error > allowed_warning:
warnings.warn(
f"{test_name} Exceeded image regression warning of "
@@ -233,6 +264,7 @@ def verify_image_cache(request, pytestconfig):
"fail_extra_image_cache"
)
VerifyImageCache.add_missing_images = pytestconfig.getoption("add_missing_images")
+ VerifyImageCache.reset_only_failed = pytestconfig.getoption("reset_only_failed")
cache_dir = pytestconfig.getoption("image_cache_dir")
if cache_dir is None:
diff --git a/tests/test_pyvista.py b/tests/test_pyvista.py
index 8d1fdb1..f0a0f0a 100644
--- a/tests/test_pyvista.py
+++ b/tests/test_pyvista.py
@@ -75,6 +75,8 @@ def test_imcache(verify_image_cache):
result = testdir.runpytest("--fail_extra_image_cache")
result.stdout.fnmatch_lines("*[Ff]ailed*")
result.stdout.fnmatch_lines("*Exceeded image regression error*")
+ result.stdout.fnmatch_lines("*pytest_pyvista.pytest_pyvista.RegressionError:*")
+ result.stdout.fnmatch_lines("*Exceeded image regression error of*")
def test_skip(testdir):
@@ -281,3 +283,47 @@ def test_imcache(cleanup_tester, verify_image_cache):
result = testdir.runpytest("--fail_extra_image_cache")
result.stdout.fnmatch_lines("*[Pp]assed*")
+
+
+def test_reset_only_failed(testdir):
+ """Test usage of the `reset_only_failed` flag."""
+ filename = make_cached_images(testdir.tmpdir)
+ filename_original = make_cached_images(testdir.tmpdir, name="original.png")
+ assert filecmp.cmp(filename, filename_original, shallow=False)
+
+ testdir.makepyfile(
+ """
+ import pyvista as pv
+ pv.OFF_SCREEN = True
+ def test_imcache(verify_image_cache):
+ sphere = pv.Box()
+ plotter = pv.Plotter()
+ plotter.add_mesh(sphere, color="blue")
+ plotter.show()
+ """
+ )
+
+ result = testdir.runpytest("--reset_only_failed")
+ result.stdout.fnmatch_lines("*[Pp]assed*")
+ result.stdout.fnmatch_lines("*This image will be reset in the cache.")
+ # file was overwritten
+ assert not filecmp.cmp(filename, filename_original, shallow=False)
+
+
+def test_file_not_found(testdir):
+ """Test RegressionFileNotFound is correctly raised."""
+ testdir.makepyfile(
+ """
+ import pyvista as pv
+ pv.OFF_SCREEN = True
+ def test_imcache_num2(verify_image_cache):
+ sphere = pv.Box()
+ plotter = pv.Plotter()
+ plotter.add_mesh(sphere, color="blue")
+ plotter.show()
+ """
+ )
+
+ result = testdir.runpytest("--fail_extra_image_cache")
+ result.stdout.fnmatch_lines("*RegressionFileNotFound*")
+ result.stdout.fnmatch_lines("*does not exist in image cache*")