diff --git a/scripts/quality/coverage.sh b/scripts/quality/coverage.sh index b264168..8b9e050 100755 --- a/scripts/quality/coverage.sh +++ b/scripts/quality/coverage.sh @@ -37,7 +37,16 @@ command -v lcov >/dev/null 2>&1 || { exit 2 } -CXX_BIN="${CXX:-g++}" +# On macOS we prefer brew-installed LLVM clang++ because Apple Clang's +# GCov-compatible `--coverage` runtime is known to deadlock during +# static-initializer profiling on arm64 with template-heavy code +# (Eigen + CGAL); the LLVM build does not. Override with `CXX=...`. +DEFAULT_CXX="g++" +if [ -z "${CXX:-}" ] && [ "$(uname -s)" = "Darwin" ] \ + && [ -x /opt/homebrew/opt/llvm/bin/clang++ ]; then + DEFAULT_CXX="/opt/homebrew/opt/llvm/bin/clang++" +fi +CXX_BIN="${CXX:-$DEFAULT_CXX}" command -v "$CXX_BIN" >/dev/null 2>&1 || { echo "FAIL: $CXX_BIN not found" >&2; exit 2; } echo "========================================" @@ -52,6 +61,7 @@ cmake -S code -B "$BUILD_DIR" \ -DCMAKE_CXX_FLAGS="--coverage -O0 -g" \ -DCMAKE_EXE_LINKER_FLAGS="--coverage" \ -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST \ -Wno-dev nice -n 19 cmake --build "$BUILD_DIR" --target conformallab_tests \ @@ -68,29 +78,51 @@ fi cd "$ROOT" # ── Capture trace ──────────────────────────────────────────────────────────── -lcov --capture --directory "$BUILD_DIR" --output-file "$BUILD_DIR/coverage.raw.info" \ - --rc lcov_branch_coverage=1 \ +# lcov ≥ 2.0 became strict about "inconsistent" / "unsupported" / "negative" +# diagnostics from gcov data; the GTest sources reliably trigger +# "inconsistent" because of their preprocessor gymnastics, and brew clang's +# gcov shim is older than the function-end-line tracking lcov wants. +# These are noise we cannot fix in our source tree — suppress them. +LCOV_TOLERANT=( + --ignore-errors inconsistent + --ignore-errors unsupported + --ignore-errors negative + --ignore-errors empty + --ignore-errors mismatch + --rc lcov_branch_coverage=1 +) + +lcov --capture --directory "$BUILD_DIR" \ + --output-file "$BUILD_DIR/coverage.raw.info" \ --no-external \ + "${LCOV_TOLERANT[@]}" \ >/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 + "${LCOV_TOLERANT[@]}" \ + >/dev/null 2>&1 || true # ── HTML report ────────────────────────────────────────────────────────────── genhtml --branch-coverage --legend \ --output-directory "$BUILD_DIR/lcov-html" \ - "$BUILD_DIR/coverage.info" >/dev/null + "${LCOV_TOLERANT[@]}" \ + "$BUILD_DIR/coverage.info" >/dev/null 2>&1 || true # ── 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" +if [ -s "$BUILD_DIR/coverage.info" ]; then + lcov --summary "$BUILD_DIR/coverage.info" "${LCOV_TOLERANT[@]}" 2>/dev/null \ + | 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" +else + echo " WARN: coverage.info is empty — likely an lcov/gcov version" + echo " mismatch. Tests passed; raw .gcda files are in $BUILD_DIR." + echo " Inspect with: find $BUILD_DIR -name '*.gcda' | head" +fi diff --git a/scripts/quality/run-all.sh b/scripts/quality/run-all.sh index ebc2691..f01f27f 100755 --- a/scripts/quality/run-all.sh +++ b/scripts/quality/run-all.sh @@ -84,10 +84,11 @@ for entry in "${GATES[@]}"; do echo "──── [$i/${#GATES[@]}] $name ────" eval "$cmd" >"$log" 2>&1 rc=$? - # Exit code 2 from any of our gate scripts = "tool not installed". + # Exit code 2 from any of our gate scripts = "prerequisite missing" + # (tool not in PATH, no CGAL tarball, no second compiler, etc.). # Treat as SKIP rather than FAIL so a partial dev environment can # still run the rest of the sweep. - if [ "$rc" -eq 2 ] && head -3 "$log" | grep -qE "FAIL:.*not (in PATH|installed|found)"; then + if [ "$rc" -eq 2 ] && head -3 "$log" | grep -qE "FAIL:.*(not (in PATH|installed|found)|no .* found)"; then echo " SKIP (tool not installed — see $log)" results="${results} SKIP $name (missing tool) "