Files
Juan Denis 05e651be9d Fail CI on undefined names in the backend
A name used before it exists only fails when that line runs, so a
rarely-taken branch ships broken. Two routes 500'd on every call and
Stop crashed on compose-slot apps, both found by pyflakes in seconds.
The check now runs in the ratchet job and in preflight.
2026-10-10 21:56:37 -04:00

169 lines
7.0 KiB
YAML

name: Backend CI
# Runs the full backend pytest suite on every change to backend/. This is the
# net that catches regressions in the panel — including the agent gateway,
# registry, pairing, and the panel<->agent command loop exercised end-to-end
# by tests/test_agent_poll_e2e.py — instead of finding out at install time.
# main is deliberately absent from `push`: release.yml gates itself on this
# workflow via `ci-gate`, so a push to main already runs the suite once. Listing
# main here ran it a second time, standalone, on every merge.
on:
push:
branches: [dev]
paths:
- 'backend/**'
- '.github/workflows/backend-ci.yml'
pull_request:
branches: [main, dev]
paths:
- 'backend/**'
- '.github/workflows/backend-ci.yml'
workflow_call:
jobs:
# The ratchet needs to see the WHOLE suite, so it cannot live inside a shard —
# each shard only collects its own quarter. Its own job costs no wall-clock
# time: it runs beside the shards and finishes in seconds.
ratchet:
name: test-count ratchet
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
cache-dependency-path: backend/requirements.txt
- name: Install dependencies
working-directory: backend
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Test-count ratchet
# Fails if collected-test count drops below backend/tests/BASELINE_COUNT.
# Converts "code + its tests vanished together" (the 2026-07 data loss,
# where a green suite hid ~363 lost tests) from invisible to a red X.
# Lowering the floor requires editing BASELINE_COUNT in the same commit.
working-directory: backend
run: python tests/check_test_count.py
- name: Undefined names
# A name used before it exists only fails when that line runs, so a
# rarely-taken branch ships broken: two routes that always 500'd on a
# missing import, and a Stop button that crashed on a shadowing local
# import. Pyflakes catches both statically in about a second.
working-directory: backend
run: |
pip install ruff==0.14.14
ruff check --select F821,F823 app tests
# Sharded because this job WAS the entire wait: 21m50s of a ~22m pipeline,
# while every other workflow finished inside a minute. Splitting the suite
# over 4 runners cuts the critical path to roughly a quarter.
#
# Sharding rather than pytest-xdist is deliberate: each shard is its own VM,
# so the process-shared state that makes in-process parallelism unsafe here
# (templates.json, APPS_DIR — see the per-PID DB dance in tests/conftest.py)
# simply isn't shared. No test code has to change.
#
# NOTE: with no .test_durations file, pytest-split balances by test COUNT,
# not by time — and these tests range from 0.1s to 10s+. If one shard lands
# most of the slow ones it becomes the new critical path. If that shows up,
# run once with `--store-durations` and commit backend/.test_durations to
# switch it to duration-based balancing.
pytest:
name: pytest (${{ matrix.group }}/4)
runs-on: ubuntu-latest
strategy:
# Report every failing shard in one pass instead of hiding shards 2-4
# behind the first failure — one round of fixes instead of four.
fail-fast: false
matrix:
group: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
cache-dependency-path: backend/requirements.txt
- name: Install dependencies
working-directory: backend
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-split
- name: Run tests (shard ${{ matrix.group }} of 4)
# Scoped to `tests` rather than a bare `pytest`. This collects the same
# suite in CI (backend/dev-data/ is gitignored), while keeping the
# command reproducible on a dev box: a
# bare pytest there tries to collect the locally deployed apps under
# backend/dev-data/ and dies during collection. Copy this line verbatim
# to debug a red shard locally.
working-directory: backend
run: python -m pytest tests -v --splits 4 --group ${{ matrix.group }}
# The @real_binaries leg (plan 82 §E): the same service code the shards run
# against fake_subprocess meets the REAL parsers here — the panel's
# generated crontab text goes through `crontab -`/`crontab -l`, and every
# rendered vhost flavor through `nginx -t`. This is the class of bug the
# #117 cron fix taught us write-only assertions cannot see. The tests skip
# themselves everywhere unless SERVERKIT_REAL_BINARIES=1, so the shards
# above never mutate a runner's crontab by surprise.
real-binaries:
name: real binaries
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
cache-dependency-path: backend/requirements.txt
- name: Install nginx and cron
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq nginx-core cron
- name: Install dependencies
working-directory: backend
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Run real-binaries tests
working-directory: backend
env:
SERVERKIT_REAL_BINARIES: '1'
run: python -m pytest tests -m real_binaries -v
# The @docker_builds leg: generated buildpack Dockerfiles meet the REAL
# docker daemon — detect() -> generate_dockerfile() -> `docker build` ->
# `docker run` -> HTTP probe, per stack. Same lesson as real-binaries:
# test_buildpacks.py asserts on Dockerfile TEXT, which cannot see a bug
# that only fails at build or boot time (the `npm install --omit=dev ||
# npm install` dead fallback exited 0 and shipped `vite: not found` to a
# user's deploy). The tests skip themselves everywhere unless
# SERVERKIT_DOCKER_BUILDS=1, so the shards above never pull images.
docker-builds:
name: docker builds
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
cache-dependency-path: backend/requirements.txt
- name: Install dependencies
working-directory: backend
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Run docker-builds tests
working-directory: backend
env:
SERVERKIT_DOCKER_BUILDS: '1'
run: python -m pytest tests -m docker_builds -v