97 lines
3.9 KiB
Bash
Executable File
97 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Reproducible coverage for src/**, with a floor.
|
|
#
|
|
# One command: configure and build the instrumented tree, run the whole suite,
|
|
# capture src/** with lcov into build/cov/coverage.info, render the HTML report
|
|
# under build/cov/html/, print the per-file table, and exit non-zero when
|
|
# tools/coverage_floor.py finds a file below 80% lines or the src/ aggregate
|
|
# below 90%. The numbers and the floors are the contract docs/coverage.md
|
|
# states; `docs/coverage.md` also records how to re-read the table from here.
|
|
#
|
|
# Idempotent by construction: gcov counters accumulate in .gcda files, so a
|
|
# second run that inherited the first run's counters would report a superset of
|
|
# what it exercised (and a source file deleted since the last run would report
|
|
# stale data for a file that no longer exists). This script therefore deletes
|
|
# every .gcda under the build tree *before* it runs the suite, so the captured
|
|
# counters are exactly the ones this run produced. Run it twice on an unchanged
|
|
# tree and build/cov/coverage.info is byte-for-byte identical.
|
|
#
|
|
# Usage: tools/coverage.sh [build-dir]
|
|
# build-dir defaults to build/cov, the linux-coverage preset's binaryDir.
|
|
# Any other directory is used as-is and must already be configured with
|
|
# --coverage (the `coverage` CMake target passes its own build directory).
|
|
|
|
set -euo pipefail
|
|
|
|
readonly ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
readonly PRESET_BUILD="$ROOT/build/cov"
|
|
readonly BUILD="${1:-$PRESET_BUILD}"
|
|
readonly FLOOR="$ROOT/tools/coverage_floor.py"
|
|
readonly RAW="$BUILD/coverage.raw.info"
|
|
readonly INFO="$BUILD/coverage.info"
|
|
readonly HTML="$BUILD/html"
|
|
|
|
log() { printf '==> %s\n' "$*"; }
|
|
|
|
for tool in cmake ctest lcov genhtml python3; do
|
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
printf 'coverage: %s is required (Arch: pacman -S cmake ninja lcov python)\n' "$tool" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
|
|
if [[ "$BUILD" == "$PRESET_BUILD" ]]; then
|
|
# The preset file lives at the repository root, but the `coverage` target runs
|
|
# this script with the build directory as its working directory (that is how
|
|
# ninja invokes a target's command), so the configure step states where to look
|
|
# rather than assuming. Every other path in this script is absolute for the
|
|
# same reason.
|
|
log "configuring $BUILD from the linux-coverage preset"
|
|
cd "$ROOT"
|
|
cmake --preset linux-coverage --log-level=WARNING
|
|
elif [[ ! -f "$BUILD/CMakeCache.txt" ]]; then
|
|
printf 'coverage: %s is not a configured build tree; use --preset linux-coverage\n' "$BUILD" >&2
|
|
exit 2
|
|
fi
|
|
|
|
log "building"
|
|
cmake --build "$BUILD"
|
|
|
|
# Before the suite, not after: see the idempotency note at the top of the file.
|
|
log "clearing stale gcov counters under $BUILD"
|
|
find "$BUILD" -name '*.gcda' -delete
|
|
|
|
log "running the suite"
|
|
ctest --test-dir "$BUILD" --output-on-failure
|
|
|
|
log "capturing src/** with lcov into $INFO"
|
|
# The unexecuted-block warning comes from libstdc++ headers on this toolchain;
|
|
# geninfo_unexecuted_blocks=1 records those blocks as unexecuted instead of
|
|
# warning about them, which is what the report means anyway.
|
|
lcov --capture --directory "$BUILD" --output-file "$RAW" \
|
|
--rc branch_coverage=0 --rc geninfo_unexecuted_blocks=1 \
|
|
--ignore-errors inconsistent,inconsistent
|
|
lcov --extract "$RAW" "$ROOT/src/*" --output-file "$INFO" --ignore-errors unused
|
|
rm -f "$RAW"
|
|
|
|
log "rendering the HTML report into $HTML/index.html"
|
|
rm -rf "$HTML"
|
|
genhtml "$INFO" --output-directory "$HTML" --prefix "$ROOT" \
|
|
--title "plot -- line coverage of src/**" --rc branch_coverage=0 \
|
|
--ignore-errors inconsistent,inconsistent
|
|
|
|
log "per-file coverage for src/**"
|
|
set +e
|
|
python3 "$FLOOR" --table "$INFO"
|
|
readonly FLOOR_STATUS=$?
|
|
set -e
|
|
|
|
if (( FLOOR_STATUS != 0 )); then
|
|
printf '\ncoverage: FLOOR FAILED (exit %d) -- report %s, HTML %s/index.html\n' \
|
|
"$FLOOR_STATUS" "$INFO" "$HTML" >&2
|
|
exit "$FLOOR_STATUS"
|
|
fi
|
|
|
|
printf '\ncoverage: floor satisfied -- report %s, HTML %s/index.html\n' "$INFO" "$HTML"
|