#!/bin/sh # Which lines of the Rust the tests reach (#390, #394), inside the limits builds are held to. # # sh scripts/coverage.sh the cargo tests, all three crates, then their report # sh scripts/coverage.sh ipx-feed one crate's cargo tests, then the report of what has run # sh scripts/coverage.sh api the browser suite against an instrumented ipx and # ipx-directory: what the API and the daemon run (and the # page's coverage, which the same run measures) # # A coverage build instruments every crate, dependencies included, in a target of its own: a whole # rebuild, the kind that took Tower down on 2026-11-04. So it builds in a container under the same # cap as scripts/build-image.sh's builder (6 GB, six threads), past which the kernel kills it and # production is left alone; and, as CLAUDE.md asks of tests, one crate at a time. If the cap kills # it, say so and leave the cap be. The browser suite runs here, as it always does, its servers the # container's build: the image is Ubuntu's, as code-server is, so they start here. # # Reports: target/coverage/cargo and target/coverage/api (summary.txt, a line a file; lcov.info; # html/index.html), target/coverage/page (npm run coverage:page's, or api's). Each run ends by # writing the numbers for Grafana (scripts/coverage-metrics.ts). set -eu MEM=6g CPUS=0-5 NEED_KB=$((9 * 1024 * 1024)) MAX_LOAD=12 IMAGE=192.168.1.130:5000/ipx-coverage:latest # The Docker daemon is Tower's, so its paths are Tower's: this tree is /mnt/user/vscode/ipx there, # and target/ is a link to target-real on /mnt/fast (builds through /mnt/user's FUSE took the load # to 248, CLAUDE.md). code-server sees /mnt/fast at the same path, so the instrumented binaries # and the profiles they write are at the same place for both. SRC=/mnt/user/vscode/ipx TARGET=/mnt/fast/vscode/ipx/target-real REGISTRY=$TARGET/coverage-registry LLVM_TARGET=$TARGET/llvm-cov-target cd "$(dirname "$0")/.." room() { avail=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo) load=$(cut -d' ' -f1 /proc/loadavg) echo "MemAvailable $((avail / 1024)) MB, load $load" if [ "$avail" -lt "$NEED_KB" ]; then echo "refusing: under $((NEED_KB / 1024 / 1024)) GB available. Wait, or find what is using it." >&2 exit 1 fi if awk -v l="$load" -v m="$MAX_LOAD" 'BEGIN { exit !(l > m) }'; then echo "refusing: load over $MAX_LOAD. Wait for it to come down." >&2 exit 1 fi } run() { # The source read only: nothing in the tree is the coverage run's to change. The registry is # kept between runs, so the crates are downloaded once. docker run --rm --memory "$MEM" --memory-swap "$MEM" --cpuset-cpus "$CPUS" \ -v "$SRC:/work:ro" -v "$TARGET:/target" -v "$REGISTRY:/usr/local/cargo/registry" \ -e CARGO_TARGET_DIR=/target/llvm-cov-target -e CARGO_LLVM_COV_TARGET_DIR=/target/llvm-cov-target \ "$IMAGE" "$@" || { code=$? [ "$code" -eq 137 ] && echo "killed at the ${MEM} memory cap: tell the user, do not raise it" >&2 exit "$code" } } # The report of the profiles in the target, into target/coverage/$1, with the tree's own paths. report() { room mkdir -p "$TARGET/coverage/$1" run sh -c "cargo llvm-cov report --lcov --output-path /target/coverage/$1/lcov.info \ && cargo llvm-cov report --html --output-dir /target/coverage/$1 \ && cargo llvm-cov report --summary-only > /target/coverage/$1/summary.txt" # The paths inside the container are /work/...; the tree's own, from here. sed -i 's#/work/##' "$TARGET/coverage/$1/summary.txt" "$TARGET/coverage/$1/lcov.info" tail -1 "$TARGET/coverage/$1/summary.txt" } docker pull -q "$IMAGE" >/dev/null mkdir -p "$REGISTRY" if [ "${1:-}" = api ]; then # Built as cargo-llvm-cov builds for a test of its own, instrumented, with no tests: the # binaries the browser suite starts. Profiles from an earlier run are not this one's. run cargo llvm-cov clean --workspace for p in ipx-directory ipx; do room echo "== build $p" run sh -c "eval \"\$(cargo llvm-cov show-env --export-prefix)\" && cargo build --locked -p $p" done room echo "== the browser suite" # Not `set -e`'s to stop on: a failing test still ran what it ran, and the report says so; the # suite's own exit code is this script's at the end. suite=0 IPX_API_COVERAGE=$LLVM_TARGET IPX_COVERAGE=1 LLVM_PROFILE_FILE="$LLVM_TARGET/ipx-browser-%p-%14m.profraw" \ npx playwright test || suite=$? ls "$LLVM_TARGET"/ipx-browser-*.profraw >/dev/null 2>&1 \ || { echo "no profiles were written: did the servers stop on SIGTERM?" >&2; exit 1; } report api node scripts/coverage-metrics.ts exit "$suite" fi crates=${1:-ipx-feed ipx-directory ipx} # A whole run starts from nothing; one crate adds to what has run. [ $# -eq 0 ] && run cargo llvm-cov clean --workspace for c in $crates; do room echo "== $c" run cargo llvm-cov --no-report --locked -p "$c" done report cargo node scripts/coverage-metrics.ts