Test workflow (1313e/CMasher)
The Test workflow from 1313e/CMasher, explained and optimized by Latchkey.
C
CI health: C - fair
Point runs-on at Latchkey and get run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Test workflow from the 1313e/CMasher repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-3-Clause license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
workflow (.yml)
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Test
on:
push:
branches:
- master
tags: v*
pull_request:
schedule:
# run this on first day of the month at 3 am UTC
- cron: 0 3 1 * *
workflow_dispatch:
jobs:
build:
name: ${{ matrix.os }}, Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version:
- '3.10'
- '3.11'
- '3.12'
- '3.13'
- '3.14'
include:
- os: ubuntu-22.04
python-version: 3.10.0
oldestdeps: true
- os: macos-latest
python-version: '3.14'
- os: windows-latest
python-version: '3.14'
concurrency:
group: ${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-build
cancel-in-progress: true
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Set UV_RESOLUTION
if: ${{ matrix.oldestdeps }}
run: |
echo "UV_RESOLUTION=lowest" >> $GITHUB_ENV
- run: uv sync --group test --compile-bytecode
- name: Test package (no coverage)
if: ${{ !startsWith( matrix.os , 'ubuntu' ) }}
shell: bash # windows compat
run: |
uv run --no-sync --no-editable \
pytest --color=yes --mpl
- name: Test package (with coverage)
if: startsWith( matrix.os , 'ubuntu' )
# cannot use --frozen flag here because it would nullify UV_RESOLUTION
run: |
uv run --group covcheck \
coverage run --parallel-mode -m pytest --color=yes --mpl
- name: Upload coverage data
# only using reports from ubuntu because
# combining reports from multiple platforms is tricky (or impossible ?)
if: startsWith( matrix.os , 'ubuntu' )
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cmasher_coverage_data-${{ matrix.os }}-${{ matrix.python-version }}${{ matrix.oldestdeps }}
path: .coverage.*
if-no-files-found: ignore
include-hidden-files: true
coverage:
name: Combine & check coverage
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: cmasher_coverage_data-*
merge-multiple: true
- run: uv sync --only-group covcheck
- name: Check coverage
run: |
uv run --no-project coverage combine
uv run --no-project coverage html --skip-covered --skip-empty
uv run --no-project coverage report --fail-under=98
- name: Upload HTML report if check failed.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cmasher_coverage_report
path: htmlcov
if: ${{ failure() }}
type-check:
name: type check w/ Python ${{ matrix.python-version }}
strategy:
matrix:
python-version:
- '3.10'
- '3.13'
runs-on: ubuntu-latest
concurrency:
group: ${{ github.ref }}-${{ matrix.python-version }}-typecheck
cancel-in-progress: true
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Run mypy
run: uv run --no-editable --group typecheck mypy src/cmasher
docs:
name: Build docs
runs-on: ubuntu-latest
concurrency:
group: ${{ github.ref }}-docs
cancel-in-progress: true
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: '3.13'
- name: Build
run: |
uv run --frozen --group docs --compile-bytecode \
sphinx-build -M html docs/source site -W
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: site
path: site
build-artifacts:
name: Build artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- run: uv build
- run: uvx twine check dist/*
- run: uv run scripts/check_dist.py
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist
publish:
name: Publish to PyPI
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs:
- build
- type-check
- docs
- build-artifacts
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/CMasher
permissions:
id-token: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist
- name: Publish package distributions to PyPI
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Test on: push: branches: - master tags: v* pull_request: schedule: # run this on first day of the month at 3 am UTC - cron: 0 3 1 * * workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: timeout-minutes: 30 name: ${{ matrix.os }}, Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] python-version: - '3.10' - '3.11' - '3.12' - '3.13' - '3.14' include: - os: ubuntu-22.04 python-version: 3.10.0 oldestdeps: true - os: macos-latest python-version: '3.14' - os: windows-latest python-version: '3.14' concurrency: group: ${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-build cancel-in-progress: true steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: python-version: ${{ matrix.python-version }} - name: Set UV_RESOLUTION if: ${{ matrix.oldestdeps }} run: | echo "UV_RESOLUTION=lowest" >> $GITHUB_ENV - run: uv sync --group test --compile-bytecode - name: Test package (no coverage) if: ${{ !startsWith( matrix.os , 'ubuntu' ) }} shell: bash # windows compat run: | uv run --no-sync --no-editable \ pytest --color=yes --mpl - name: Test package (with coverage) if: startsWith( matrix.os , 'ubuntu' ) # cannot use --frozen flag here because it would nullify UV_RESOLUTION run: | uv run --group covcheck \ coverage run --parallel-mode -m pytest --color=yes --mpl - name: Upload coverage data # only using reports from ubuntu because # combining reports from multiple platforms is tricky (or impossible ?) if: startsWith( matrix.os , 'ubuntu' ) uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: cmasher_coverage_data-${{ matrix.os }}-${{ matrix.python-version }}${{ matrix.oldestdeps }} path: .coverage.* if-no-files-found: ignore include-hidden-files: true coverage: timeout-minutes: 30 name: Combine & check coverage runs-on: latchkey-small needs: build steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: cmasher_coverage_data-* merge-multiple: true - run: uv sync --only-group covcheck - name: Check coverage run: | uv run --no-project coverage combine uv run --no-project coverage html --skip-covered --skip-empty uv run --no-project coverage report --fail-under=98 - name: Upload HTML report if check failed. uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: cmasher_coverage_report path: htmlcov if: ${{ failure() }} type-check: timeout-minutes: 30 name: type check w/ Python ${{ matrix.python-version }} strategy: matrix: python-version: - '3.10' - '3.13' runs-on: latchkey-small concurrency: group: ${{ github.ref }}-${{ matrix.python-version }}-typecheck cancel-in-progress: true steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: python-version: ${{ matrix.python-version }} - name: Run mypy run: uv run --no-editable --group typecheck mypy src/cmasher docs: timeout-minutes: 30 name: Build docs runs-on: latchkey-small concurrency: group: ${{ github.ref }}-docs cancel-in-progress: true steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: python-version: '3.13' - name: Build run: | uv run --frozen --group docs --compile-bytecode \ sphinx-build -M html docs/source site -W - name: Upload artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: site path: site build-artifacts: timeout-minutes: 30 name: Build artifacts runs-on: latchkey-small steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - run: uv build - run: uvx twine check dist/* - run: uv run scripts/check_dist.py - name: Upload artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: dist path: dist publish: timeout-minutes: 30 name: Publish to PyPI if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') needs: - build - type-check - docs - build-artifacts runs-on: latchkey-small environment: name: pypi url: https://pypi.org/p/CMasher permissions: id-token: write steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist path: dist - name: Publish package distributions to PyPI if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
This workflow runs 6 jobs (11 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.