mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
174 lines
6.5 KiB
Bash
174 lines
6.5 KiB
Bash
#!/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
|