Tests workflow (justquick/django-activity-stream)
The Tests workflow from justquick/django-activity-stream, explained and optimized by Latchkey.
CI health: F - at risk
Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Tests workflow from the justquick/django-activity-stream 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
name: Tests
on:
push:
branches:
- main
paths:
- '**.py'
- '**.txt'
- '**.yaml'
- '**.toml'
pull_request:
branches:
- main
paths:
- '**.py'
- '**.txt'
- '**.toml'
- '**.yaml'
env:
GITHUB_WORKFLOW: true
jobs:
test:
name: Test
runs-on: ubuntu-latest
# test matrix
strategy:
fail-fast: false
matrix:
python:
- 3.8
- 3.9
- "3.10"
- 3.11
- 3.12
django:
- 3.2
- 4.0
- 4.1
- 4.2
- 5.0
- 5.1
database:
- sqlite
- mysql
- postgres
exclude:
# django 3.2
- python: 3.11
django: 3.2
- python: 3.12
django: 3.2
# django 4.0
- python: 3.11
django: 4.0
- python: 3.12
django: 4.0
# django 4.1
- python: 3.12
django: 4.1
# django 5.0
- python: 3.8
django: 5.0
- python: 3.9
django: 5.0
# django 5.1
- python: 3.8
django: 5.1
- python: 3.9
django: 5.1
# additional service containers to run
services:
# postgres service
postgres:
# docker hub image
image: postgres:13-alpine
# configure the instance
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
# set health checks to wait until service has started
options: >-
--health-cmd="pg_isready"
--health-interval=10s
--health-timeout=5s
--health-retries=5
# mysql service
mysql:
# docker hub image
image: mysql:8
# configure the instance
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: test
ports:
- 3306:3306
# set health checks to wait until service has started
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
# check out revision to test
- uses: actions/checkout@v4
# install python
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
# upgrade pip
- name: Update pip
run: python -m pip install --upgrade pip
# install environment specific dependencies
- name: Install coverage
run: pip install coverage>=7.6.0 setuptools
- name: Install Django 3.2
if: matrix.django == 3.2
run: pip install "Django>=3.2,<4.0"
- name: Install Django 4.0
if: matrix.django == 4.0
run: pip install "Django>=4.0,<4.1"
- name: Install Django 4.1
if: matrix.django == 4.1
run: pip install "Django>=4.1,<4.2"
- name: Install Django 4.2
if: matrix.django == 4.2
run: pip install "Django>=4.2,<5.0"
- name: Install Django 5.0
if: matrix.django == 5.0
run: pip install "Django>=5.0,<5.1"
- name: Install Django 5.1
if: matrix.django == 5.1
run: pip install "Django>=5.1,<5.2"
- name: Install MySQL libs
if: matrix.database == 'mysql'
run: pip install mysqlclient>=2.2.4 django-mysql>=4.14.0
- name: Install postgres libs
if: matrix.database == 'postgres'
run: pip install psycopg2-binary>=2.9.9
- name: Install Django ReST framework libraries
run: pip install -U django-rest-framework rest-framework-generic-relations drf-spectacular
# install our package
- name: Install package
run: pip install -e .
# execute the tests
- name: Run tests
run: coverage run runtests/manage.py test -v3 --noinput actstream testapp testapp_nested
env:
DATABASE_ENGINE: ${{ matrix.database }}
# COVERAGE_FILE: ".coverage.${{ matrix.python_version }}"
- name: Store coverage file
uses: actions/upload-artifact@v3
with:
name: coverage
path: .coverage #.${{ matrix.python_version }}
- name: Coveralls
uses: AndreMiras/coveralls-python-action@develop
with:
parallel: true
flag-name: Unit Test
coveralls_finish:
needs: test
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: AndreMiras/coveralls-python-action@develop
with:
parallel-finished: true
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: Tests on: push: branches: - main paths: - '**.py' - '**.txt' - '**.yaml' - '**.toml' pull_request: branches: - main paths: - '**.py' - '**.txt' - '**.toml' - '**.yaml' env: GITHUB_WORKFLOW: true concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: timeout-minutes: 30 name: Test runs-on: latchkey-small # test matrix strategy: fail-fast: false matrix: python: - 3.8 - 3.9 - "3.10" - 3.11 - 3.12 django: - 3.2 - 4.0 - 4.1 - 4.2 - 5.0 - 5.1 database: - sqlite - mysql - postgres exclude: # django 3.2 - python: 3.11 django: 3.2 - python: 3.12 django: 3.2 # django 4.0 - python: 3.11 django: 4.0 - python: 3.12 django: 4.0 # django 4.1 - python: 3.12 django: 4.1 # django 5.0 - python: 3.8 django: 5.0 - python: 3.9 django: 5.0 # django 5.1 - python: 3.8 django: 5.1 - python: 3.9 django: 5.1 # additional service containers to run services: # postgres service postgres: # docker hub image image: postgres:13-alpine # configure the instance env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres ports: - 5432:5432 # set health checks to wait until service has started options: >- --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=5 # mysql service mysql: # docker hub image image: mysql:8 # configure the instance env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: test ports: - 3306:3306 # set health checks to wait until service has started options: >- --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 steps: # check out revision to test - uses: actions/checkout@v4 # install python - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 with: cache: 'pip' python-version: ${{ matrix.python }} # upgrade pip - name: Update pip run: python -m pip install --upgrade pip # install environment specific dependencies - name: Install coverage run: pip install coverage>=7.6.0 setuptools - name: Install Django 3.2 if: matrix.django == 3.2 run: pip install "Django>=3.2,<4.0" - name: Install Django 4.0 if: matrix.django == 4.0 run: pip install "Django>=4.0,<4.1" - name: Install Django 4.1 if: matrix.django == 4.1 run: pip install "Django>=4.1,<4.2" - name: Install Django 4.2 if: matrix.django == 4.2 run: pip install "Django>=4.2,<5.0" - name: Install Django 5.0 if: matrix.django == 5.0 run: pip install "Django>=5.0,<5.1" - name: Install Django 5.1 if: matrix.django == 5.1 run: pip install "Django>=5.1,<5.2" - name: Install MySQL libs if: matrix.database == 'mysql' run: pip install mysqlclient>=2.2.4 django-mysql>=4.14.0 - name: Install postgres libs if: matrix.database == 'postgres' run: pip install psycopg2-binary>=2.9.9 - name: Install Django ReST framework libraries run: pip install -U django-rest-framework rest-framework-generic-relations drf-spectacular # install our package - name: Install package run: pip install -e . # execute the tests - name: Run tests run: coverage run runtests/manage.py test -v3 --noinput actstream testapp testapp_nested env: DATABASE_ENGINE: ${{ matrix.database }} # COVERAGE_FILE: ".coverage.${{ matrix.python_version }}" - name: Store coverage file uses: actions/upload-artifact@v3 with: name: coverage path: .coverage #.${{ matrix.python_version }} - name: Coveralls uses: AndreMiras/coveralls-python-action@develop with: parallel: true flag-name: Unit Test coveralls_finish: timeout-minutes: 30 needs: test runs-on: latchkey-small steps: - name: Coveralls Finished uses: AndreMiras/coveralls-python-action@develop with: parallel-finished: true
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.
- Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 2 jobs (91 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.