xo-umbrella2/xo-cmake/bin/xo-cmake-llvmcov-harness.in
2026-04-20 08:57:22 -04:00

118 lines
3.6 KiB
Bash

#!/usr/bin/env bash
#
# xo-cmake-llvmcov-harness:
# clang/llvm source-based coverage driver.
# Parallel to xo-cmake-lcov-harness (gcov path).
#
# Pipeline:
# 1. run ctest with LLVM_PROFILE_FILE pointing into ${builddir}/ccov/raw
# 2. merge the produced .profraw files to ${outputstem}.profdata
# 3. read ${builddir}/ccov/utest-binaries.list (written by xo_umbrella_coverage_config)
# 4. emit browsable HTML under ${builddir}/ccov/html
# 5. print summary table to stdout
#
set -euo pipefail
srcdir=${1:-}
builddir=${2:-}
outputstem=${3:-}
if [[ -z "${srcdir}" || -z "${builddir}" ]]; then
echo "usage: $0 srcdir builddir [outputstem]" >&2
exit 1
fi
if [[ -z "${outputstem}" ]]; then
outputstem=${builddir}/ccov/out
fi
llvm_profdata=@LLVM_PROFDATA_EXECUTABLE@
llvm_cov=@LLVM_COV_EXECUTABLE@
for kv in "llvm-profdata:${llvm_profdata}" "llvm-cov:${llvm_cov}"; do
name=${kv%%:*}
path=${kv#*:}
if [[ -z "${path}" || "${path}" == *NOTFOUND ]]; then
echo "xo-cmake-llvmcov-harness: ${name} not found during xo-cmake build/install" >&2
exit 1
fi
done
manifest=${builddir}/ccov/utest-binaries.list
if [[ ! -f "${manifest}" ]]; then
echo "xo-cmake-llvmcov-harness: missing ${manifest}" >&2
echo "xo-cmake-llvmcov-harness: expected xo_umbrella_coverage_config() to produce it" >&2
exit 1
fi
rawdir=${builddir}/ccov/raw
htmldir=${builddir}/ccov/html
mkdir -p "${rawdir}" "${htmldir}"
# clear prior raws so we don't mix stale data from a past run
rm -f "${rawdir}"/*.profraw
# 1. run tests. LLVM_PROFILE_FILE pattern: %h=hostname %m=binary-signature %p=pid
# keeps output unique across parallel tests and across binaries.
#
# tolerate ctest failures: a crashing/asserting utest still emits .profraw
# before dying, and we want a coverage report even when some tests are broken.
# Surface the exit code at the end.
ctest_rc=0
(cd "${builddir}" && \
LLVM_PROFILE_FILE="${rawdir}/%h-%m-%p.profraw" \
ctest --output-on-failure) || ctest_rc=$?
# 2. merge .profraw -> .profdata
shopt -s nullglob
raws=("${rawdir}"/*.profraw)
if [[ ${#raws[@]} -eq 0 ]]; then
echo "xo-cmake-llvmcov-harness: no .profraw produced - was the build instrumented?" >&2
exit 1
fi
"${llvm_profdata}" merge -sparse "${raws[@]}" -o "${outputstem}.profdata"
# 3. read binary list. drop blank lines and anything not executable
# (target may be in manifest but unbuilt if 'ninja ccov' was invoked
# before a full build; defensive).
readarray -t all_bins < "${manifest}"
bins=()
for b in "${all_bins[@]}"; do
[[ -n "${b}" && -x "${b}" ]] && bins+=("${b}")
done
if [[ ${#bins[@]} -eq 0 ]]; then
echo "xo-cmake-llvmcov-harness: no executable utests found in manifest" >&2
exit 1
fi
# llvm-cov takes the first binary positionally; the rest via -object.
first=${bins[0]}
objargs=()
if [[ ${#bins[@]} -gt 1 ]]; then
for b in "${bins[@]:1}"; do objargs+=(-object "${b}"); done
fi
# exclude test code and toolchain headers from the report
ignore_re='/utest/|/nix/store/'
# 4. HTML
"${llvm_cov}" show \
-instr-profile="${outputstem}.profdata" \
-format=html \
-output-dir="${htmldir}" \
-show-line-counts-or-regions \
-ignore-filename-regex="${ignore_re}" \
"${first}" "${objargs[@]}"
# 5. summary to stdout
"${llvm_cov}" report \
-instr-profile="${outputstem}.profdata" \
-ignore-filename-regex="${ignore_re}" \
"${first}" "${objargs[@]}"
echo
echo "xo-cmake-llvmcov-harness: HTML report -> ${htmldir}/index.html"
if [[ ${ctest_rc} -ne 0 ]]; then
echo "xo-cmake-llvmcov-harness: WARNING ctest exited ${ctest_rc} - coverage is from successful tests only" >&2
fi