From f7538057d9a904f58dcf679c6ac97c5758572558 Mon Sep 17 00:00:00 2001 From: didericis Date: Sun, 12 Jul 2026 16:50:02 -0400 Subject: [PATCH] ci(coverage): run the diff-coverage gate on a self-hosted KVM runner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck --- .gitea/workflows/test.yml | 49 +++++++++++++++++++++++++-------------- README.md | 2 ++ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 081b544..746c863 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -71,30 +71,45 @@ jobs: - name: Run integration tests run: python3 -m unittest discover -t . -s tests/integration -v - # Combined unit+integration coverage report (informational). See - # docs/decisions/0004-coverage-policy.md. + # Combined unit+integration coverage + the diff-coverage gate (the hard + # gate: new/changed lines >= 90%). See docs/decisions/0004-coverage-policy.md. # - # The hard diff-coverage gate (changed lines >= 90%) is DEFERRED: the - # Firecracker backend's VM/SSH orchestration is covered by the integration - # suite, which needs /dev/kvm + the provisioned TAP/nft pool — a - # container-based runner skips it and those lines read uncovered, so the - # gate can't pass here. Re-enabling it on a self-hosted KVM runner is - # tracked separately (see PRD 0069 / #348 and the ci-runner branch). + # 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: ubuntu-latest + runs-on: [self-hosted, kvm] steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" + - 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 -r requirements-dev.txt - - - name: Combined coverage report (unit + integration) + - 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 diff --git a/README.md b/README.md index c117e1a..26c5a2e 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ BOT_BOTTLE_BACKEND=firecracker ./cli.py start > **NixOS:** enable `virtualisation.docker`, ensure the KVM module is loaded (`boot.kernelModules = [ "kvm-intel" ];` or `kvm-amd`), and add your user to the `kvm` and `docker` groups. For the network pool, consume the flake module — `imports = [ inputs.bot-bottle.nixosModules.firecracker-netpool ]; services.bot-bottle-firecracker = { enable = true; owner = "you"; };` — then `nixos-rebuild switch` (imperative nft/TAP rules don't survive a rebuild; channel users can `imports = [ /nix/firecracker-netpool.nix ]`). `firecracker` isn't in nixpkgs by default as a user binary — install the release binary (pin the version) and put it on `PATH`. +> **CI:** the coverage gate (`.gitea/workflows/test.yml` → `coverage` job) runs on a self-hosted runner labelled `kvm`, because the Firecracker backend's VM/SSH orchestration is exercised only by the integration suite, which needs `/dev/kvm` + the provisioned pool (a container runner would skip it and read as uncovered). Provision that runner exactly like a normal Firecracker host — `firecracker` on `PATH`, `/dev/kvm`, Docker, the cached guest kernel + static dropbear, and the pool installed as the persistent systemd unit — then register it with the `kvm` label. The unit/lint jobs still run on `ubuntu-latest`. + ```sh ./cli.py start # builds the image on first run, drops you into claude ```