# 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