From 16aa6e9bc6665926dde24b0bdec84f9c09ae5bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 25 Aug 2026 16:51:03 -0400 Subject: [PATCH] Fixed the coverage report's paths and scoping The Cobertura XML embedded absolute machine paths, and the flag that looked like it scoped the report to one build configuration was doing nothing at all. Both coverage.sh scripts had the defect; both are fixed here, because the SMP report is published to the same Pages site as the ThreadX one. Paths. -r was the build directory and -f pointed outside it, so gcovr could not express the sources relative to the root and fell back to absolute paths. The result named files as /home/runner/work/threadx/threadx/common/src/... while the element beside them said build/default_build_coverage, so the two halves of the same file disagreed and nothing could map coverage back to the repository. -r is the repository root now and -f an absolute path beneath it, which gives filename="common/src/tx_block_allocate.c". Both must be absolute: -r ../../.. -f common/src produces a report of zero files and exits 0, which is the worst failure mode available here. Scoping. --object-directory does not restrict which gcda files are found -- it tells gcovr how to get from a gcda file back to the compiler's working directory. Pointed at an empty directory it still produced the full 177-file report. That was harmless only by accident, because -r build/$1 constrained the search instead; moving -r to the repository root removes that accident, so the two changes have to land together. Measured, with a second instrumented configuration deliberately made sparser than the first: scoped by the positional search path 3221 of 3827 lines -- the truth no search path, -r at the repo root 3827 of 3827 -- silently merged --object-directory at the sparse tree 3827 of 3827 -- scopes nothing So the search path is load-bearing, and it matters ahead of instrumenting all five configurations: without it each configuration would have reported the union as its own. An empty report is not an error to gcovr -- it warns and exits 0 -- and it carries line-rate="1.0" next to lines-valid="0", so a consumer reads no data at all as fully covered. No coverage threshold can catch that, since an empty report passes any threshold. Hence the explicit assertion that the report has content, which fires with exit 1 on an object directory that exists but is empty, where the old shape returned 177 files and exit 0. Also says out loud that ports/linux/gnu/src is deliberately outside the filter. gcno files exist for it and it is dropped without a word today. Number-neutral, and that was the test. Over the same frozen gcda, changing only the gcovr invocation: ThreadX 3827 of 3827 lines and 1993 of 1994 branches across 177 files, SMP 4739 of 4791 and 2417 of 2430 across 185, before and after alike. Same answers on gcovr 7.0, 8.3 and 8.6, so the change is not wedged to the current pin. End to end through run.sh, 96 of 96 and 110 of 110 pass with the reports written. Assisted-by: Claude Opus 5 --- test/smp/cmake/coverage.sh | 59 ++++++++++++++++++++++++++++++++++++-- test/tx/cmake/coverage.sh | 55 +++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/test/smp/cmake/coverage.sh b/test/smp/cmake/coverage.sh index a7312e1f4..abcf7cceb 100755 --- a/test/smp/cmake/coverage.sh +++ b/test/smp/cmake/coverage.sh @@ -14,7 +14,6 @@ set -e cd $(dirname $0) -threadx_smp=$(realpath ../../../common_smp/src) mkdir -p coverage_report/$1 # gcov reads a data format tied to the compiler that produced it, so gcov has to match @@ -42,5 +41,59 @@ if ! command -v "$GCOV" >/dev/null 2>&1; then exit 1 fi -gcovr --gcov-executable "$GCOV" --object-directory=build/$1/threadx_smp/CMakeFiles/threadx_smp.dir/$threadx_smp -r build/$1 -f ../../../common_smp/src --xml-pretty --output coverage_report/$1.xml -gcovr --gcov-executable "$GCOV" --object-directory=build/$1/threadx_smp/CMakeFiles/threadx_smp.dir/$threadx_smp -r build/$1 -f ../../../common_smp/src --html --html-details --output coverage_report/$1/index.html +# gcovr is given three paths below, and each of them has to be absolute, for a +# different reason. +# +# -r is the repository root rather than the build directory, so the report names +# files the way the repository does -- "common_smp/src/tx_block_allocate.c" +# instead of "/home/runner/work/threadx/threadx/common_smp/src/tx_block_allocate.c". +# With -r inside build/, gcovr cannot express the sources relative to it, because +# they are outside it, and falls back to absolute paths. Those paths then differ +# on every machine and disagree with the element written beside them in +# the same file, so anything that maps coverage back to the repository -- PR +# annotations, Codecov, SonarQube -- cannot follow them. +# +# Both -r and -f must be absolute. "-r ../../.. -f common_smp/src" produces a +# report containing zero files and exits 0, which is the worst failure mode +# available here: a green run carrying an empty report. Measured, not assumed. +repo_root=$(cd ../../.. && pwd) + +# This is what actually scopes the report to one build configuration, and it is +# the positional search path -- not --object-directory, which used to be here +# and was doing nothing at all. That flag tells gcovr how to get from a gcda +# file back to the compiler's working directory; it does not restrict which gcda +# files are found. Pointed at an empty directory it still produced the full +# report, because gcovr searches -r as well. +# +# That matters more now than it did before. While -r was build/$1 it happened to +# constrain the search to this configuration by accident. -r is the repository +# root now, and every configuration's gcda lies somewhere under it, so without +# an explicit search path the report would silently merge all five. +# +# The odd shape of this path is CMake's, not ours: common_smp/src sits outside +# this directory's source tree, so CMake mangles each source file's absolute +# path into the object path, minus the leading slash. Hence the concatenation +# rather than a join -- $repo_root already begins with one. +objdir=$PWD/build/$1/threadx_smp/CMakeFiles/threadx_smp.dir$repo_root/common_smp/src + +# The Linux port's own sources are deliberately outside -f. gcno files exist for +# two directories -- common_smp/src and ports_smp/linux/gnu/src -- and only the +# first is reported. The kernel is what this suite is here to cover; the +# architecture ports are validated functionally rather than structurally, and +# linux/gnu is a development host port that nothing ships on. Written down +# because a filter argument on its own is not a decision the next reader can see. +filter=$repo_root/common_smp/src + +gcovr --gcov-executable "$GCOV" -r "$repo_root" -f "$filter" "$objdir" --xml-pretty --output coverage_report/$1.xml +gcovr --gcov-executable "$GCOV" -r "$repo_root" -f "$filter" "$objdir" --html --html-details --output coverage_report/$1/index.html + +# An empty report is not an error as far as gcovr is concerned: it warns and +# exits 0. Worse, it advertises line-rate="1.0" alongside lines-valid="0", so +# every downstream consumer reads "no data at all" as "100% covered". A coverage +# threshold cannot catch that, because an empty report passes any threshold. So +# the assertion belongs here, next to the paths that would cause it. +if ! grep -q "&2 + echo "Expected gcda files under $objdir." >&2 + exit 1 +fi diff --git a/test/tx/cmake/coverage.sh b/test/tx/cmake/coverage.sh index 9b47339e8..aabf039f7 100755 --- a/test/tx/cmake/coverage.sh +++ b/test/tx/cmake/coverage.sh @@ -41,5 +41,56 @@ if ! command -v "$GCOV" >/dev/null 2>&1; then exit 1 fi -gcovr --gcov-executable "$GCOV" --object-directory=build/$1/threadx/CMakeFiles/threadx.dir/common/src -r build/$1 -f ../../../common/src --xml-pretty --output coverage_report/$1.xml -gcovr --gcov-executable "$GCOV" --object-directory=build/$1/threadx/CMakeFiles/threadx.dir/common/src -r build/$1 -f ../../../common/src --html --html-details --output coverage_report/$1/index.html +# gcovr is given three paths below, and each of them has to be absolute, for a +# different reason. +# +# -r is the repository root rather than the build directory, so the report names +# files the way the repository does -- "common/src/tx_block_allocate.c" instead +# of "/home/runner/work/threadx/threadx/common/src/tx_block_allocate.c". With -r +# inside build/, gcovr cannot express the sources relative to it, because they +# are outside it, and falls back to absolute paths. Those paths then differ on +# every machine and disagree with the element written beside them in +# the same file, so anything that maps coverage back to the repository -- PR +# annotations, Codecov, SonarQube -- cannot follow them. +# +# Both -r and -f must be absolute. "-r ../../.. -f common/src" produces a report +# containing zero files and exits 0, which is the worst failure mode available +# here: a green run carrying an empty report. Measured, not assumed. +repo_root=$(cd ../../.. && pwd) + +# This is what actually scopes the report to one build configuration, and it is +# the positional search path -- not --object-directory, which used to be here +# and was doing nothing at all. That flag tells gcovr how to get from a gcda +# file back to the compiler's working directory; it does not restrict which gcda +# files are found. Pointed at an empty directory it still produced the full +# 177-file report, because gcovr searches -r as well. +# +# That matters more now than it did before. While -r was build/$1 it happened to +# constrain the search to this configuration by accident. -r is the repository +# root now, and every configuration's gcda lies somewhere under it, so without +# an explicit search path the report would silently merge all five. Measured on +# gcovr 8.6: with this search path, an empty directory yields an empty report +# and the real one yields 177 files. +objdir=$PWD/build/$1/threadx/CMakeFiles/threadx.dir/common/src + +# The Linux port's own sources are deliberately outside -f. gcno files exist for +# two directories -- common/src and ports/linux/gnu/src -- and only the first is +# reported. The kernel is what this suite is here to cover; the architecture +# ports are validated functionally rather than structurally, and linux/gnu is a +# development host port that nothing ships on. Written down because a filter +# argument on its own is not a decision the next reader can see. +filter=$repo_root/common/src + +gcovr --gcov-executable "$GCOV" -r "$repo_root" -f "$filter" "$objdir" --xml-pretty --output coverage_report/$1.xml +gcovr --gcov-executable "$GCOV" -r "$repo_root" -f "$filter" "$objdir" --html --html-details --output coverage_report/$1/index.html + +# An empty report is not an error as far as gcovr is concerned: it warns and +# exits 0. Worse, it advertises line-rate="1.0" alongside lines-valid="0", so +# every downstream consumer reads "no data at all" as "100% covered". A coverage +# threshold cannot catch that, because an empty report passes any threshold. So +# the assertion belongs here, next to the paths that would cause it. +if ! grep -q "&2 + echo "Expected gcda files under $objdir." >&2 + exit 1 +fi