f7538057d9
Re-land the coverage gate deferred from #343. The Firecracker VM/SSH orchestration (~230 lines) is only exercised by the integration suite, which needs /dev/kvm + the provisioned TAP/nft pool — a container runner skips it and those lines read uncovered, so the 90% diff gate can't pass on ubuntu-latest. Move the `coverage` job to a self-hosted `kvm` runner with a firecracker-readiness preflight (binary + /dev/kvm + `backend status`) so the integration test actually runs. Unit/lint stay on ubuntu-latest. README documents the runner prerequisites. Depends on a registered self-hosted runner labelled `kvm`; until one is provisioned this gate will not run. See PRD 0069 / #348. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
116 lines
4.2 KiB
YAML
116 lines
4.2 KiB
YAML
# Run the project's test suite on every PR push and on push to main.
|
|
#
|
|
# The suite uses stdlib `unittest` discovery — no external Python
|
|
# dependencies are required to execute it. Tests are split by directory:
|
|
#
|
|
# tests/unit/ — pure unit tests; always run
|
|
# tests/integration/ — need a reachable Docker daemon; skip cleanly
|
|
# (via tests/_docker.py:skip_unless_docker) when
|
|
# Docker isn't available on the runner
|
|
# tests/canaries/ — upstream regression canaries; run on a separate
|
|
# schedule (see canaries.yml), not here
|
|
#
|
|
# This workflow assumes the Gitea Actions runner exposes the host Docker
|
|
# socket to the job container so `docker` commands inside the job can
|
|
# reach the daemon. If that's not yet configured on the runner the
|
|
# integration tests will skip rather than fail.
|
|
|
|
name: test
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- '**.py'
|
|
pull_request:
|
|
paths:
|
|
- '**.py'
|
|
|
|
jobs:
|
|
unit:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Install dev requirements
|
|
run: python3 -m pip install -r requirements-dev.txt
|
|
|
|
- name: Run unit tests
|
|
run: python3 -m coverage run -m unittest discover -t . -s tests/unit -v
|
|
|
|
- name: Report unit coverage
|
|
run: python3 -m coverage report -m
|
|
|
|
integration:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Show environment
|
|
run: |
|
|
python3 --version
|
|
if command -v docker >/dev/null 2>&1; then
|
|
docker version || true
|
|
else
|
|
echo "docker not on PATH — integration tests will skip"
|
|
fi
|
|
|
|
- name: Run integration tests
|
|
run: python3 -m unittest discover -t . -s tests/integration -v
|
|
|
|
# Combined unit+integration coverage + the diff-coverage gate (the hard
|
|
# gate: new/changed lines >= 90%). See docs/decisions/0004-coverage-policy.md.
|
|
#
|
|
# This runs on a self-hosted KVM runner (label `kvm`), NOT ubuntu-latest,
|
|
# because the Firecracker backend's subprocess/VM orchestration
|
|
# (launch/boot/SSH/isolation-probe) is covered by the integration suite,
|
|
# and that suite needs `/dev/kvm` + the provisioned TAP/nft pool — which a
|
|
# container-based runner doesn't have. On such a runner the firecracker
|
|
# integration test skips and its ~230 orchestration lines read as
|
|
# uncovered, so the gate can't pass there.
|
|
#
|
|
# Runner prerequisites (provision once on the host; see the README
|
|
# "Firecracker on Linux" section): the `firecracker` binary on PATH,
|
|
# `/dev/kvm` accessible to the runner user, Docker, the cached guest
|
|
# kernel + static dropbear (BOT_BOTTLE_FC_KERNEL / BOT_BOTTLE_FC_DROPBEAR),
|
|
# and the network pool installed as the persistent systemd unit
|
|
# (`./cli.py backend setup --backend=firecracker`). The preflight step
|
|
# below fails fast with instructions if anything is missing.
|
|
coverage:
|
|
runs-on: [self-hosted, kvm]
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Preflight — Firecracker host is ready
|
|
run: |
|
|
command -v firecracker >/dev/null || {
|
|
echo "firecracker not on PATH — provision the runner (README: Firecracker on Linux)"; exit 1; }
|
|
test -e /dev/kvm || { echo "/dev/kvm missing — KVM not available on this runner"; exit 1; }
|
|
# `backend status` exits non-zero unless the TAP pool is up + no
|
|
# range overlap; it prints the exact `backend setup` fix.
|
|
python3 cli.py backend status --backend=firecracker
|
|
|
|
- name: Combined coverage (unit + integration, incl. firecracker)
|
|
run: PYTHON=python3 bash scripts/coverage.sh critical
|
|
|
|
- name: Diff-coverage gate (changed lines >= 90%)
|
|
run: |
|
|
git fetch --no-tags origin main:refs/remotes/origin/main
|
|
python3 scripts/diff_coverage.py --base origin/main --min 90
|