fold/scripts/coverage.sh
trvon b0b51f1e71
feat: converge quality and scan interfaces
- fail closed on MCP input and compile database policy
- add shared scan catalogs, finding queries, and tiered gates
2026-07-29 00:57:33 -04:00

125 lines
4.5 KiB
Bash
Executable file

#!/bin/bash
# Build fold with coverage instrumentation, run the test suite, and emit a
# line/function coverage report (terminal summary + HTML). Vendored third-party
# code (Ghidra, Capstone) is excluded. Uses gcovr via `uvx` (no system install)
# and clang's `llvm-cov gcov` to parse clang's coverage data.
#
# ./scripts/coverage.sh
#
# Report lands in build/coverage/coverage.{html,txt}.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BUILD_DIR="${COVERAGE_BUILD_DIR:-$ROOT/build/coverage}"
OS="$(uname -s)"
# Reviewed local baseline: 72.8% line coverage (39,498 / 54,270 lines).
# Keep the required floor below that baseline to tolerate small platform variance.
COVERAGE_MIN_LINE="${COVERAGE_MIN_LINE:-70}"
# --- locate LLVM (honor LLVM_BIN / LLVM_CONFIG; else Homebrew/PATH) ---
if [ -n "${LLVM_BIN:-}" ] && [ -d "$LLVM_BIN" ]; then
LLVM_CONFIG_PATH="$LLVM_BIN/llvm-config"
elif [ -n "${LLVM_CONFIG:-}" ] && command -v "$LLVM_CONFIG" >/dev/null 2>&1; then
LLVM_CONFIG_PATH="$(command -v "$LLVM_CONFIG")"
elif [ "$OS" = "Darwin" ] && command -v brew >/dev/null 2>&1; then
LLVM_CONFIG_PATH="$(brew --prefix llvm 2>/dev/null)/bin/llvm-config"
elif command -v llvm-config >/dev/null 2>&1; then
LLVM_CONFIG_PATH="$(command -v llvm-config)"
fi
if [ -z "${LLVM_CONFIG_PATH:-}" ] || [ ! -x "$LLVM_CONFIG_PATH" ]; then
echo "ERROR: LLVM not found. Set LLVM_BIN or LLVM_CONFIG to a usable llvm-config executable." >&2
exit 1
fi
LLVM_BIN="$(dirname "$LLVM_CONFIG_PATH")"
LLVM_SUFFIX="${LLVM_CONFIG_PATH##*/llvm-config}"
LLVM_COV_PATH="${LLVM_COV:-$LLVM_BIN/llvm-cov$LLVM_SUFFIX}"
LLVM_AR_PATH="${LLVM_AR:-$LLVM_BIN/llvm-ar$LLVM_SUFFIX}"
if [ ! -x "$LLVM_COV_PATH" ]; then
echo "ERROR: llvm-cov not found next to $LLVM_CONFIG_PATH." >&2
exit 1
fi
export PATH="$LLVM_BIN:$PATH"
mkdir -p "$BUILD_DIR"
# llvm-ar native file (same trick as setup.sh) so static archiving works.
MESON_NATIVE_ARGS=(--native-file "$BUILD_DIR/conan_meson_native.ini")
if [ -x "$LLVM_AR_PATH" ]; then
printf "[binaries]\nar = '%s'\n" "$LLVM_AR_PATH" >"$BUILD_DIR/llvm_ar.ini"
MESON_NATIVE_ARGS+=(--native-file "$BUILD_DIR/llvm_ar.ini")
fi
echo "==> Conan install (coverage)"
conan install "$ROOT" -of "$BUILD_DIR" --build=missing \
-s build_type=Debug -s compiler.cppstd=23 -o "build_tests=True" ||
echo "Conan install had warnings (non-fatal)"
# z3.pc symlink (macOS Homebrew / Linux multiarch), mirroring setup.sh.
if [ "$OS" = "Darwin" ] && command -v brew >/dev/null 2>&1; then
Z3_PC="$(brew --prefix z3 2>/dev/null)/lib/pkgconfig/z3.pc"
[ -f "$Z3_PC" ] && ln -sf "$Z3_PC" "$BUILD_DIR/z3.pc"
else
for d in "/usr/lib/$(uname -m)-linux-gnu" /usr/lib/x86_64-linux-gnu /usr/lib; do
[ -f "$d/pkgconfig/z3.pc" ] && ln -sf "$d/pkgconfig/z3.pc" "$BUILD_DIR/z3.pc" && break
done
fi
echo "==> Meson setup (-Db_coverage=true, debug, ghidra+capstone)"
meson setup "$BUILD_DIR" \
--reconfigure \
"${MESON_NATIVE_ARGS[@]}" \
--wrap-mode=default \
-Dcpp_std=c++23 \
-Dbuildtype=debug \
-Db_coverage=true \
-Dbuild-tests=true \
-Dghidra=auto \
-Dcapstone=auto \
-Dllvm-config-path="$LLVM_CONFIG_PATH"
echo "==> Build + test (generates coverage data)"
ninja -C "$BUILD_DIR"
test_status=0
meson test -C "$BUILD_DIR" || test_status=$?
if [ "$test_status" -ne 0 ]; then
echo "WARNING: test suite failed (exit $test_status); generating coverage from completed tests" >&2
fi
echo "==> gcovr report (uvx; excludes third_party/ + subprojects/)"
GCOVR_COVERAGE_GATE=()
if [ -n "$COVERAGE_MIN_LINE" ]; then
case "$COVERAGE_MIN_LINE" in
*[!0-9.]* | "")
echo "ERROR: COVERAGE_MIN_LINE must be a percentage in [0, 100]" >&2
exit 2
;;
esac
if ! awk "BEGIN { exit !($COVERAGE_MIN_LINE >= 0 && $COVERAGE_MIN_LINE <= 100) }"; then
echo "ERROR: COVERAGE_MIN_LINE must be a percentage in [0, 100]" >&2
exit 2
fi
GCOVR_COVERAGE_GATE=(--fail-under-line "$COVERAGE_MIN_LINE")
fi
coverage_status=0
uvx gcovr \
--root "$ROOT" \
--gcov-executable "$LLVM_COV_PATH gcov" \
--exclude '.*third_party/.*' \
--exclude '.*subprojects/.*' \
--exclude '.*/tests/.*' \
--txt "$BUILD_DIR/coverage.txt" \
--html-details "$BUILD_DIR/coverage.html" \
--print-summary \
"${GCOVR_COVERAGE_GATE[@]}" \
"$BUILD_DIR" || coverage_status=$?
if [ "$coverage_status" -eq 0 ]; then
echo ""
echo "Coverage report: $BUILD_DIR/coverage.txt (summary above) + $BUILD_DIR/coverage.html"
else
echo "ERROR: coverage report or line-coverage gate failed (exit $coverage_status)" >&2
fi
if [ "$test_status" -ne 0 ] || [ "$coverage_status" -ne 0 ]; then
echo "ERROR: coverage gate failed: tests=$test_status coverage=$coverage_status" >&2
exit 1
fi