Skip to content

Unit Testing and Deployment #132

Unit Testing and Deployment

Unit Testing and Deployment #132

name: Unit Testing and Deployment
# zizmor ignore note: All caching for pushes to main should be disabled with the `USE_CACHE` env var
on: # zizmor: ignore[cache-poisoning]
pull_request:
merge_group:
workflow_dispatch:
schedule:
- cron: "0 4 * * *"
push:
tags:
- "*"
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
ALLOW_PLOTTING: true
SHELLOPTS: "errexit:pipefail"
TOX_COLORED: "yes"
USE_CACHE: ${{
(
(github.event_name == 'workflow_dispatch' && github.event.inputs.cache == 'true') ||
(github.event_name == 'pull_request') ||
(github.event_name == 'push')
) &&
!startsWith(github.ref, 'refs/tags/v') &&
!startsWith(github.ref, 'refs/heads/release/') &&
!startsWith(github.ref, 'refs/heads/main')
}}
CACHE_FOLDER_NAME: pyvista-data
PYVISTA_VTK_DATA: ${{ github.workspace }}/pyvista-data
permissions:
id-token: none
jobs:
# Save PR metadata as an artifact so the separate deploy workflow (triggered
# via workflow_run) can associate the build with its PR — workflow_run events
# do not include pull_request data for fork PRs.
pr-info:
name: Save PR info
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Save PR metadata
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
run: |
mkdir -p pr-info
printf '%s' "$PR_NUMBER" > pr-info/pr_number.txt
printf '%s' "$HEAD_SHA" > pr-info/head_sha.txt
printf '%s' "$IS_FORK" > pr-info/is_fork.txt
- uses: actions/upload-artifact@v7
with:
name: pr-info
path: pr-info/
# Cache pyvista/data for each runner separately
# Runner labels should match the actual runners used by the different jobs in this file
# GitHub-hosted runners can re-use the cache across OS's.
# Here we run the cache on ubuntu, but it's usable by windows GitHub runners too
cache-github-hosted:
name: Cache pyvista/data
uses: ./.github/workflows/cache-pyvista-data.yml
with:
runs-on: "ubuntu-22.04"
# The macOS self-hosted runner needs its own cache (GitHub-hosted runners
# share cache-github-hosted across Linux and Windows).
cache-macOS-self-hosted:
name: Cache pyvista/data
uses: ./.github/workflows/cache-pyvista-data.yml
with:
runs-on: "macos-15-self-hosted"
macOS:
name: ${{ matrix.name }}
needs: cache-macOS-self-hosted
strategy:
fail-fast: false
matrix:
include:
# Self-hosted runner configurations
- name: MacOS Unit Testing (3.10)
python-version: "3.10"
runner-labels: "macos-15-self-hosted"
- name: MacOS Unit Testing (3.11)
python-version: "3.11"
runner-labels: "macos-15-self-hosted"
- name: MacOS Unit Testing (3.12)
python-version: "3.12"
runner-labels: "macos-15-self-hosted"
- name: MacOS Unit Testing (3.13)
python-version: "3.13"
runner-labels: "macos-15-self-hosted"
- name: MacOS Unit Testing (3.14)
python-version: "3.14"
runner-labels: "macos-15-self-hosted"
runs-on: ${{ matrix.runner-labels }}
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
if: runner.environment != 'self-hosted'
uses: actions/setup-python@v7
continue-on-error: true
with:
python-version: ${{ matrix.python-version }}
cache: ${{ env.USE_CACHE == 'true' && 'pip' || '' }}
- name: Set Python PATH and create a virtual env
if: runner.environment == 'self-hosted'
run: |
/opt/homebrew/bin/python${{ matrix.python-version }} -m venv .venv
echo "${{ github.workspace }}/.venv/bin" >> $GITHUB_PATH
- name: Restore pyvista/data cache
uses: actions/cache/restore@v6
with:
path: ${{ env.CACHE_FOLDER_NAME }}
key: ${{ needs.cache-macOS-self-hosted.outputs.key }}
enableCrossOsArchive: true
- name: Install tox-uv
run: pip install tox-uv
- name: Core Testing (no GL)
run: tox run -e py${{ matrix.python-version }}-core
- name: Plotting Testing (uses GL)
if: ${{ !cancelled() }}
run: tox run -e py${{ matrix.python-version }}-plotting
- name: Upload Images for Failed Tests
if: always()
uses: actions/upload-artifact@v7
with:
name: failed_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _failed_test_images
- name: Upload Generated Images
if: always()
uses: actions/upload-artifact@v7
with:
name: generated_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _generated_test_images
Linux:
name: Linux Unit Testing (${{ matrix.python-version }}, ${{ matrix.vtk-version }}, ${{ matrix.numpy-version }})
runs-on: ubuntu-22.04
needs: cache-github-hosted
strategy:
fail-fast: false
# see discussion at https://github.com/pyvista/pyvista/issues/2867
matrix:
include:
# numeric numpy versions are ~= conditions, e.g. "1.23" means "numpy~=1.23.0"
- python-version: "3.10"
vtk-version: "9.3.1"
numpy-version: "latest"
- python-version: "3.11"
vtk-version: "9.4.2"
numpy-version: "latest"
- python-version: "3.12"
vtk-version: "9.5.2"
numpy-version: "latest"
- python-version: "3.13"
vtk-version: "9.6.2"
numpy-version: "latest"
- python-version: "3.14"
vtk-version: "latest"
numpy-version: "latest"
- python-version: "3.14"
vtk-version: "latest"
numpy-version: "nightly"
env:
TOX_FACTOR: py${{matrix.python-version}}-numpy_${{matrix.numpy-version}}-vtk_${{matrix.vtk-version}}
PARALLEL: "-n auto" # Use all available cores on the selected runner
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 2
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9
with:
packages: xvfb libgl1 libglx-mesa0 libosmesa6
version: 3.0
- name: Restore pyvista/data cache
uses: actions/cache/restore@v6
with:
path: ${{ env.CACHE_FOLDER_NAME }}
key: ${{ needs.cache-github-hosted.outputs.key }}
enableCrossOsArchive: true
- name: Install tox-uv
run: pip install tox-uv
- name: Core Testing (no GL)
env:
TOX_ENV: ${{ env.TOX_FACTOR }}-cov-core
run: tox run -e "$TOX_ENV"
- name: Plotting Testing (uses GL)
if: ${{ !cancelled() }}
env:
TOX_ENV: ${{ env.TOX_FACTOR }}-cov-plotting
run: xvfb-run tox run -e "$TOX_ENV"
- name: Upload Images for Failed Tests
if: always()
uses: actions/upload-artifact@v7
with:
name: failed_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _failed_test_images
- name: Upload Generated Images
if: always()
uses: actions/upload-artifact@v7
with:
name: generated_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _generated_test_images
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage_reports-${{ github.job }}-${{ join(matrix.* , '-') }}
path: coverage*.xml
if-no-files-found: error
- name: Check package
run: |
pip install build twine
python -m build
twine check --strict dist/*
VTK-dev:
# For PRs, only run this job if the 'vtk-dev-testing' label is applied
if: |
(github.event_name != 'pull_request' && github.event_name != 'push' && github.event_name != 'merge_group') ||
contains(github.event.pull_request.labels.*.name, 'vtk-dev-testing')
name: Linux VTK Dev Testing
permissions:
contents: read
runs-on: ubuntu-22.04
needs: cache-github-hosted
strategy:
fail-fast: false
matrix:
python-version: ["3.14"]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 2
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9
with:
packages: xvfb libgl1 libglx-mesa0 libosmesa6
version: 3.0
- name: Restore pyvista/data cache
uses: actions/cache/restore@v6
with:
path: ${{ env.CACHE_FOLDER_NAME }}
key: ${{ needs.cache-github-hosted.outputs.key }}
enableCrossOsArchive: true
- name: Install tox-uv
run: pip install tox-uv
- name: Core Testing (no GL)
run: tox run -e py${{ matrix.python-version }}-cov-core-vtk_dev
- name: Plotting Testing (uses GL)
if: ${{ !cancelled() }}
run: xvfb-run tox run -e py${{ matrix.python-version }}-cov-plotting-vtk_dev
- name: Upload Images for Failed Tests
if: always()
uses: actions/upload-artifact@v7
with:
name: failed_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _failed_test_images
- name: Upload Generated Images
if: always()
uses: actions/upload-artifact@v7
with:
name: generated_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _generated_test_images
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage_reports-${{ github.job }}-${{ join(matrix.* , '-') }}
path: coverage*.xml
if-no-files-found: error
windows:
name: Windows Unit Testing
runs-on: windows-latest
timeout-minutes: 80
needs: cache-github-hosted
env:
CI_WINDOWS: true
PARALLEL: "-n auto" # Use all available cores on the selected runner
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- name: Restore pyvista/data cache
uses: actions/cache/restore@v6
with:
path: ${{ env.CACHE_FOLDER_NAME }}
key: ${{ needs.cache-github-hosted.outputs.key }}
enableCrossOsArchive: true
- name: Set up headless display
uses: pyvista/setup-headless-display-action@5bc8de3bc71fcda7a96439571287a554901541a0
- name: Install tox-uv
run: pip install tox-uv
- name: Core Testing (no GL)
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
with:
timeout_minutes: 12
max_attempts: 3
retry_on: timeout
command: tox run -e py${{ matrix.python-version }}-core
- name: Plotting Testing (uses GL)
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
if: ${{ !cancelled() }}
with:
timeout_minutes: 30
max_attempts: 3
retry_on: timeout
command: tox run -e py${{ matrix.python-version }}-plotting
- name: Upload Images for Failed Tests
if: always()
uses: actions/upload-artifact@v7
with:
name: failed_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _failed_test_images
- name: Upload Generated Images
if: always()
uses: actions/upload-artifact@v7
with:
name: generated_test_images-${{ github.job }}-${{ join(matrix.* , '-') }}
path: _generated_test_images
# The test image report Netlify deploy lives in test-image-report-deploy.yml,
# triggered via workflow_run, so fork PR CI is never blocked on deployment
# approval.
upload-coverage-:
name: Upload coverage reports
runs-on: ubuntu-22.04
needs: [Linux, VTK-dev]
# `always()` ensures this still runs when VTK-dev is skipped because the
# 'vtk-dev-testing' label is not applied. When VTK-dev does run, its
# coverage artifacts get bundled into this single CodeCov upload.
# Run on push/merge_group as well as pull_request so Codecov's
# main-branch baseline keeps advancing with every merge.
if: always() && needs.Linux.result == 'success'
permissions:
# id-token: write authorizes OIDC token minting so codecov-action can
# authenticate to Codecov without a long-lived CODECOV_TOKEN secret.
id-token: write
contents: read
steps:
# Needs to checkout only for codecov to match file names
- uses: actions/checkout@v7
with:
fetch-depth: 2
persist-credentials: false
- name: Download coverage reports
uses: actions/download-artifact@v8
with:
pattern: coverage_reports-*
path: coverage*.xml
- name: Upload coverage to CodeCov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f
with:
fail_ci_if_error: true
use_oidc: true
# Publish to PyPI using trusted publishing
release:
name: Release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
needs: [Linux, windows, macOS]
runs-on: ubuntu-24.04
environment:
name: pypi
url: https://pypi.org/p/pyvista
permissions:
id-token: write # this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: 3.14
- name: Build package
run: |
pip install build
python -m build
- name: Upload to PyPi
if: startsWith(github.ref, 'refs/tags/v')
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247