#!/usr/bin/env bash # scripts/quality/coverage.sh # # Measure line/branch test coverage of code/include/ via gcov + lcov. # # Builds the test suite with `--coverage` (gcc) or `-fprofile-instr-generate # -fcoverage-mapping` (clang), runs ctest, and emits: # * build-coverage/coverage.info — lcov tracefile # * build-coverage/lcov-html/index.html — browseable HTML report # * stdout: per-file summary + grand total # # Local-only. Not gated in CI yet; once a coverage threshold is agreed # with the reviewer (e.g. 80 %), the gate can be a single line in # cpp-tests.yml. # # Usage: # bash scripts/quality/coverage.sh # gcc default # CXX=g++-13 bash scripts/quality/coverage.sh # specific compiler # # Prerequisites: gcov + lcov (apt install lcov / brew install lcov) # # Exit codes: # 0 coverage report generated; prints % # 1 tests failed (no usable trace) # 2 prerequisite missing set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$ROOT" BUILD_DIR="build-coverage" command -v lcov >/dev/null 2>&1 || { echo "FAIL: lcov not found in PATH. Install via:" >&2 echo " macOS: brew install lcov" >&2 echo " Linux: sudo apt install lcov" >&2 exit 2 } CXX_BIN="${CXX:-g++}" command -v "$CXX_BIN" >/dev/null 2>&1 || { echo "FAIL: $CXX_BIN not found" >&2; exit 2; } echo "========================================" echo " Coverage build (gcov + lcov)" echo " CXX: $CXX_BIN ($($CXX_BIN --version | head -1))" echo " build: $BUILD_DIR" echo "========================================" echo cmake -S code -B "$BUILD_DIR" \ -DCMAKE_CXX_COMPILER="$CXX_BIN" \ -DCMAKE_CXX_FLAGS="--coverage -O0 -g" \ -DCMAKE_EXE_LINKER_FLAGS="--coverage" \ -DCMAKE_BUILD_TYPE=Debug \ -Wno-dev nice -n 19 cmake --build "$BUILD_DIR" --target conformallab_tests \ -j"$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 2)" # Run the non-CGAL test suite. (CGAL tests under coverage instrumentation # blow up to ~6 GB RAM during compilation; out-of-scope for this gate.) cd "$BUILD_DIR" if ! ctest -E "^cgal\." --output-on-failure; then cd "$ROOT" echo "FAIL: coverage build's test run did not complete cleanly." >&2 exit 1 fi cd "$ROOT" # ── Capture trace ──────────────────────────────────────────────────────────── lcov --capture --directory "$BUILD_DIR" --output-file "$BUILD_DIR/coverage.raw.info" \ --rc lcov_branch_coverage=1 \ --no-external \ >/dev/null 2>&1 || true # Restrict to code/include/ (our public API surface; ignore deps/tests). lcov --extract "$BUILD_DIR/coverage.raw.info" \ "*/code/include/*" \ --output-file "$BUILD_DIR/coverage.info" \ --rc lcov_branch_coverage=1 \ >/dev/null 2>&1 # ── HTML report ────────────────────────────────────────────────────────────── genhtml --branch-coverage --legend \ --output-directory "$BUILD_DIR/lcov-html" \ "$BUILD_DIR/coverage.info" >/dev/null # ── Summary to stdout ──────────────────────────────────────────────────────── echo echo "── Coverage summary (code/include/) ─────────────────────────" lcov --summary "$BUILD_DIR/coverage.info" --rc lcov_branch_coverage=1 \ | grep -E "lines\.\.\.\.|functions|branches" \ | sed 's/^/ /' echo echo "HTML report: $BUILD_DIR/lcov-html/index.html" echo " open $BUILD_DIR/lcov-html/index.html"