5624025b02
test / integration-docker (pull_request) Successful in 9s
test / unit (pull_request) Successful in 30s
lint / lint (push) Successful in 2m24s
tracker-policy-pr / check-pr (pull_request) Failing after 11s
test / integration-firecracker (pull_request) Has been cancelled
test / coverage (pull_request) Has been cancelled
Add separate `integration-docker` and `integration-firecracker` jobs, each with an explicit BOT_BOTTLE_BACKEND env var, so the backend used is visible in CI output and skipped backends surface as a distinct job rather than silent unittest.skip lines. - integration-docker: ubuntu-latest, BOT_BOTTLE_BACKEND=docker - integration-firecracker: [self-hosted, kvm], BOT_BOTTLE_BACKEND=firecracker, same-repo PRs + push + workflow_dispatch only (untrusted fork PRs do not execute on the privileged KVM runner) - coverage: same same-repo restriction; refs #414 for the planned follow-up that moves coverage to ubuntu-latest via artifact combination Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
166 lines
6.5 KiB
YAML
166 lines
6.5 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 backend; skip cleanly when
|
|
# the backend isn't available on the runner
|
|
# tests/canaries/ — upstream regression canaries; run on a separate
|
|
# schedule (see canaries.yml), not here
|
|
#
|
|
# Integration tests run once per backend in separate jobs. Each job sets
|
|
# BOT_BOTTLE_BACKEND explicitly so the test suite uses the right backend.
|
|
# Backends that aren't available on the runner fail the preflight step
|
|
# rather than silently skipping inside the test output.
|
|
|
|
name: test
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- '**.py'
|
|
- '.gitea/workflows/**.yml'
|
|
- 'scripts/**'
|
|
- 'README.md'
|
|
pull_request:
|
|
paths:
|
|
- '**.py'
|
|
- '.gitea/workflows/**.yml'
|
|
- 'scripts/**'
|
|
- 'README.md'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
unit:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# No actions/setup-python: the runner image already ships Python 3.12,
|
|
# and older act_runner engines mishandle setup-python's PATH (coverage
|
|
# lands in one interpreter, `python3` resolves to another). Install
|
|
# straight into the ephemeral job container's system Python —
|
|
# --break-system-packages is safe because the container is disposable.
|
|
- name: Install dev requirements
|
|
run: python3 -m pip install --break-system-packages -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-docker:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# No actions/setup-python (see the note in the `unit` job); the
|
|
# container's system Python 3.12 runs the stdlib test suite directly.
|
|
- 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 (docker)
|
|
env:
|
|
BOT_BOTTLE_BACKEND: docker
|
|
run: python3 -m unittest discover -t . -s tests/integration -v
|
|
|
|
# Integration tests against the Firecracker backend. Runs on a self-hosted
|
|
# KVM runner (label `kvm`) where /dev/kvm and the TAP/nft pool are available.
|
|
#
|
|
# Restricted to same-repo PRs, push to main, and workflow_dispatch — fork
|
|
# PRs don't execute untrusted code on the privileged runner.
|
|
#
|
|
# Runner prerequisites (provision once; see README "Firecracker on Linux"):
|
|
# `firecracker` on PATH, `/dev/kvm` accessible, Docker, cached kernel +
|
|
# static dropbear, and the pool as a persistent systemd unit.
|
|
integration-firecracker:
|
|
runs-on: [self-hosted, kvm]
|
|
if: >-
|
|
github.event_name == 'push' ||
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(github.event_name == 'pull_request' &&
|
|
github.event.pull_request.head.repo.full_name == github.repository)
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- 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: Install dev requirements
|
|
run: python3 -m pip install --user -r requirements-dev.txt
|
|
|
|
- name: Run integration tests (firecracker)
|
|
env:
|
|
BOT_BOTTLE_BACKEND: firecracker
|
|
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.
|
|
#
|
|
# Restricted to the same events as integration-firecracker (same-repo PRs,
|
|
# push, workflow_dispatch) for the same security reason.
|
|
#
|
|
# See #414 for the planned follow-up: artifact-based coverage combination
|
|
# (run tests once in their respective jobs, combine .coverage files here).
|
|
coverage:
|
|
runs-on: [self-hosted, kvm]
|
|
if: >-
|
|
github.event_name == 'push' ||
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(github.event_name == 'pull_request' &&
|
|
github.event.pull_request.head.repo.full_name == github.repository)
|
|
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: Install dev requirements
|
|
run: python3 -m pip install --user -r requirements-dev.txt
|
|
|
|
- name: Combined coverage (unit + integration, incl. firecracker)
|
|
env:
|
|
BOT_BOTTLE_BACKEND: 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
|