The repository had no licence at all, so every source was all-rights-reserved by default. Add the verbatim MIT text at LICENSE (Copyright (c) 2026 Martino Ferrari) and a two-line SPDX header to each of the 113 first-party sources -- 48 include/plot, 26 src, 10 adapters, 5 examples, 21 tests, 3 tools -- so the grant travels with each file rather than relying on the root document alone. Scripts keep their shebang on line 1; the SPDX lines follow it. Genuinely vendored code is untouched: third_party/stb_truetype.h carries no header of ours. src/render/embedded_mono_font.cpp is generated, so tools/fonts/gen_atlas.py emit() now writes the two lines into its header comment and the file was regenerated -- gen_atlas.py --check passes and the regeneration adds only those comment lines to the previously committed file. README's provenance section now states MIT and links LICENSE. No code, test or public API change.
110 lines
4.6 KiB
Bash
Executable File
110 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-License-Identifier: MIT
|
|
# SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
|
|
#
|
|
# 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 floors are tools/coverage_floor.py's constants; the measured
|
|
# table and the floor verdict are written back into docs/coverage.md's generated
|
|
# region by `coverage_floor.py --write-doc`, from the very report this run
|
|
# captured, so that document cannot drift from the command that measures it.
|
|
#
|
|
# 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"
|
|
readonly DOC="$ROOT/docs/coverage.md"
|
|
|
|
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
|
|
|
|
# The measured table is written into the document that states it, from the very
|
|
# report this run captured, so docs/coverage.md cannot drift from the command
|
|
# that produces it. This happens before the floor gate below, so a failing tree
|
|
# still gets an honest table (and its failure recorded) in the document.
|
|
log "regenerating the measured table in $DOC"
|
|
python3 "$FLOOR" --write-doc "$DOC" "$INFO"
|
|
|
|
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"
|