Skip to content

ci: populate baselines and gate benchmarks at +20% (#91) #200

ci: populate baselines and gate benchmarks at +20% (#91)

ci: populate baselines and gate benchmarks at +20% (#91) #200

Workflow file for this run

name: CI
on:
push:
branches:
- master
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
prod-install-smoke:
name: Verify prod requirements.txt is sufficient
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: requirements.txt
- name: Install production dependencies only
run: pip install -r requirements.txt
# Boots the app under test_client without pytest. Catches the case
# where someone adds a real third-party import (e.g. werkzeug.X)
# to the source without bumping requirements.txt — that would
# surface here as ImportError before merge.
- name: Import + boot app
run: |
python - <<'PY'
from app import create_app
app = create_app()
client = app.test_client()
assert client.get("/").status_code == 200
PY
lint-and-audit:
name: ruff + pip-audit (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
requirements.txt
requirements-dev.txt
- name: Install dev dependencies
run: pip install -r requirements-dev.txt
- name: Ruff
run: ruff check .
- name: Ruff format check
run: ruff format --check .
- name: pip-audit
run: pip-audit -r requirements.txt
pytest:
name: pytest (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
requirements.txt
requirements-dev.txt
- name: Install dev dependencies (Flask + pytest)
run: pip install -r requirements-dev.txt
# Full suite gate (pyproject addopts include --cov + 60% fail-under).
- name: Run tests
run: pytest --tb=short -q
mypy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
requirements.txt
requirements-dev.txt
- name: Install dev dependencies
run: pip install -r requirements-dev.txt
- name: Run mypy (strict, production packages)
run: mypy -p api -p utils -p models
- name: Run mypy on tests (non-strict smoke)
run: mypy tests --config-file mypy-tests.ini --follow-imports skip
integration-tests:
name: API integration tests + coverage (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
permissions:
contents: read
actions: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
requirements.txt
requirements-dev.txt
- name: Install dev dependencies
run: pip install -r requirements-dev.txt
# Intentional overlap with pytest job: re-runs API/search subset with --cov
# and uploads coverage.xml per OS (pytest job is the full-suite gate).
- name: Run integration tests with coverage
run: pytest tests/test_api_integration.py tests/test_search.py -v --cov=api --cov=utils --cov-report=xml --cov-fail-under=0
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-report-${{ matrix.os }}
path: coverage.xml
js-tests:
name: Frontend unit tests (vitest) (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "20"
cache: npm
- run: npm ci
# npm optional-deps bug: platform rollup binaries are sometimes skipped after npm ci
# (https://github.com/npm/cli/issues/4828). Install the native package explicitly.
- name: Ensure Rollup native binary
shell: bash
run: |
ROLLUP_VERSION=$(node -p "require('./node_modules/rollup/package.json').version")
case "$(node -p "process.platform + '-' + process.arch")" in
darwin-arm64) PKG="@rollup/rollup-darwin-arm64" ;;
darwin-x64) PKG="@rollup/rollup-darwin-x64" ;;
linux-x64) PKG="@rollup/rollup-linux-x64-gnu" ;;
win32-x64) PKG="@rollup/rollup-win32-x64-msvc" ;;
win32-arm64) PKG="@rollup/rollup-win32-arm64-msvc" ;;
*) echo "Unsupported Node platform: $(node -p "process.platform + '-' + process.arch")"; exit 1 ;;
esac
npm install --no-save "${PKG}@${ROLLUP_VERSION}"
# Same vitest suite as npm test; --coverage enforces thresholds in vitest.config.js
- run: npm run test:coverage
benchmarks:
name: Performance benchmarks (gated)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
requirements.txt
requirements-dev.txt
- name: Install dev dependencies
run: pip install -r requirements-dev.txt
- name: Run benchmarks
run: >
pytest tests/benchmarks/
--benchmark-only
--benchmark-json=benchmark-results.json
--benchmark-columns=min,max,mean,stddev,rounds
-o addopts=
- name: Regression gate
run: python scripts/check_benchmark_regression.py benchmark-results.json benchmarks/baselines.json
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: benchmark-results
path: benchmark-results.json