Files
ARMSX2/tools/coverage.sh
T

174 lines
6.5 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
#
# Source-based coverage for the ARM64 recompilers.
#
# Configures/builds the instrumented tree (build-coverage/), runs the gtest
# binaries, and reports line/region coverage scoped to the ARM64 sources --
# legacy x86 and shared PCSX2 code are filtered out at report time, so the
# percentage answers "how much of *our* JIT do the tests reach".
#
# Usage:
# tools/coverage.sh # build, run, print the per-file summary
# tools/coverage.sh --html # ...and write a browsable HTML report
# tools/coverage.sh --no-build # reuse the existing build-coverage/ tree
# tools/coverage.sh --uncovered # list the fully-unreached functions
# tools/coverage.sh --scope all # widen from pcsx2/arm64/ to every ARM64 file
#
# Any trailing arguments are passed to llvm-cov as extra -sources filters.
set -euo pipefail
# pwd -P, not pwd: /home/bmd/dev and /home/bmd/ARMSX2 are symlinks into the real
# tree, and configuring through a symlinked source dir breaks Qt AUTOMOC. Always
# resolve to the physical path before handing anything to cmake.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
SRC_DIR="$(dirname "${SCRIPT_DIR}")"
BUILD_DIR="${SRC_DIR}/build-coverage"
PROF_DIR="${BUILD_DIR}/profraw"
PROFDATA="${BUILD_DIR}/coverage.profdata"
HTML_DIR="${BUILD_DIR}/coverage-html"
LLVM_PROFDATA="${LLVM_PROFDATA:-llvm-profdata}"
LLVM_COV="${LLVM_COV:-llvm-cov}"
TESTS=(recompiler_tests core_test common_test gs_vertex_tests mvu_progcache_versioning_tests)
do_build=1
do_html=0
do_uncovered=0
scope=arm64
extra_sources=()
while [[ $# -gt 0 ]]; do
case "$1" in
--no-build) do_build=0 ;;
--html) do_html=1 ;;
--uncovered) do_uncovered=1 ;;
--scope) scope="$2"; shift ;;
-h|--help) sed -n '2,20p' "${BASH_SOURCE[0]}"; exit 0 ;;
*) extra_sources+=("$1") ;;
esac
shift
done
command -v "${LLVM_PROFDATA}" >/dev/null || { echo "error: ${LLVM_PROFDATA} not found" >&2; exit 1; }
command -v "${LLVM_COV}" >/dev/null || { echo "error: ${LLVM_COV} not found" >&2; exit 1; }
# ---------------------------------------------------------------- build
if [[ ${do_build} -eq 1 ]]; then
if [[ ! -f "${BUILD_DIR}/CMakeCache.txt" ]]; then
echo "==> configuring ${BUILD_DIR}"
# cd first: --preset takes the source dir from the working directory and
# ignores -S, so invoking this from the /home/bmd/ARMSX2 symlink would bake
# the symlinked path into CMAKE_HOME_DIRECTORY (and into every coverage
# mapping), which also trips the Qt AUTOMOC symlink bug.
(cd "${SRC_DIR}" && cmake --preset clang-coverage)
fi
echo "==> building ${#TESTS[@]} test targets"
cmake --build "${BUILD_DIR}" --target "${TESTS[@]}"
fi
# The prefix the compiler actually recorded in the coverage mappings. Deriving it
# from the cache rather than assuming ${SRC_DIR} is what makes --sources reliable:
# llvm-cov matches these by string, and a prefix that matches nothing is not an
# error -- it silently reports the whole tree, which reads as a plausible number.
[[ -f "${BUILD_DIR}/CMakeCache.txt" ]] || { echo "error: no ${BUILD_DIR}/CMakeCache.txt -- run without --no-build" >&2; exit 1; }
SRC_PREFIX="$(sed -n 's/^CMAKE_HOME_DIRECTORY:INTERNAL=//p' "${BUILD_DIR}/CMakeCache.txt")"
[[ -n "${SRC_PREFIX}" ]] || { echo "error: could not read CMAKE_HOME_DIRECTORY from ${BUILD_DIR}/CMakeCache.txt" >&2; exit 1; }
# The report scope. pcsx2/arm64/ is the JIT proper; --scope all adds the other
# files that only ever compile on ARM64. Everything else in the tree is either
# shared with upstream PCSX2 or x86-only, and is not what we gate on.
sources=("${SRC_PREFIX}/pcsx2/arm64")
if [[ "${scope}" == "all" ]]; then
sources+=(
"${SRC_PREFIX}/pcsx2/GS/GSVector4_arm64.h"
"${SRC_PREFIX}/pcsx2/GS/GSVector4i_arm64.h"
"${SRC_PREFIX}/pcsx2/GS/Renderers/SW/GSDrawScanlineCodeGenerator.arm64.cpp"
"${SRC_PREFIX}/pcsx2/GS/Renderers/SW/GSSetupPrimCodeGenerator.arm64.cpp"
"${SRC_PREFIX}/pcsx2/SPU2/spu2_neon.cpp"
)
fi
sources+=("${extra_sources[@]+"${extra_sources[@]}"}")
for s in "${sources[@]}"; do
[[ -e "${s}" ]] || { echo "error: coverage scope path does not exist: ${s}" >&2; exit 1; }
done
for t in "${TESTS[@]}"; do
[[ -x "${BUILD_DIR}/bin/${t}" ]] || { echo "error: ${BUILD_DIR}/bin/${t} missing -- run without --no-build" >&2; exit 1; }
done
# ---------------------------------------------------------------- run
rm -rf "${PROF_DIR}"; mkdir -p "${PROF_DIR}"
failed=()
for t in "${TESTS[@]}"; do
echo "==> running ${t}"
# %p keeps forked gtest death-test children from clobbering the parent's file.
if ! LLVM_PROFILE_FILE="${PROF_DIR}/${t}-%p.profraw" "${BUILD_DIR}/bin/${t}"; then
failed+=("${t}")
fi
done
if [[ ${#failed[@]} -gt 0 ]]; then
echo
echo "WARNING: these suites failed: ${failed[*]}"
echo " Coverage below still reflects the code they reached before failing."
fi
# ---------------------------------------------------------------- report
echo "==> merging profiles"
# shellcheck disable=SC2046
"${LLVM_PROFDATA}" merge -sparse -o "${PROFDATA}" $(find "${PROF_DIR}" -name '*.profraw')
objects=()
for t in "${TESTS[@]}"; do
objects+=(-object "${BUILD_DIR}/bin/${t}")
done
src_args=()
for s in "${sources[@]}"; do
src_args+=(-sources "${s}")
done
REPORT="${BUILD_DIR}/coverage-report.txt"
"${LLVM_COV}" report "${objects[@]}" -instr-profile="${PROFDATA}" \
-show-region-summary=true -show-branch-summary=false \
"${src_args[@]}" > "${REPORT}"
# Tripwire for the failure mode above: if --sources matched nothing, llvm-cov
# reports every file it has data for instead of erroring. The scoped report is a
# few dozen files; a few hundred means the filter fell off and the TOTAL is the
# whole tree, not the JIT.
rows="$(grep -cE '^[^ ].*[0-9]+\.[0-9]+%' "${REPORT}" || true)"
if [[ "${rows}" -gt 200 ]]; then
echo "error: report lists ${rows} files -- the --sources filter did not apply." >&2
echo " Coverage data is keyed to '${SRC_PREFIX}'; check that path is right." >&2
exit 1
fi
echo
cat "${REPORT}"
if [[ ${do_uncovered} -eq 1 ]]; then
echo
echo "==> functions with zero coverage"
"${LLVM_COV}" report "${objects[@]}" -instr-profile="${PROFDATA}" \
-show-functions "${src_args[@]}" 2>/dev/null |
awk '$0 !~ /^(Filename|---|TOTAL)/ && NF >= 4 && $4 == "0" { print }' | head -80
fi
if [[ ${do_html} -eq 1 ]]; then
rm -rf "${HTML_DIR}"
"${LLVM_COV}" show "${objects[@]}" -instr-profile="${PROFDATA}" \
-format=html -output-dir="${HTML_DIR}" \
-show-line-counts-or-regions -show-expansions \
"${src_args[@]}"
echo
echo "HTML report: ${HTML_DIR}/index.html"
fi