Skip to content

Fixed the coverage report's paths and scoping - #664

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:fix/coverage-report-paths
Aug 25, 2026
Merged

Fixed the coverage report's paths and scoping#664
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:fix/coverage-report-paths

Conversation

@fdesbiens

@fdesbiens fdesbiens commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

The Cobertura XML embeds absolute machine paths, and the flag that looks like it scopes the report to one build configuration is doing nothing at all. Both coverage.sh scripts carry 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:

<source>build/default_build_coverage</source>
<class filename="/home/fdesbiens/code/threadx-fd/common/src/tx_block_allocate.c" ...>

The two halves of the same file disagree, and on a runner those paths are /home/runner/work/threadx/threadx/..., so nothing that maps coverage back to the repository — PR annotations, Codecov, SonarQube — can follow them. irongut/CodeCoverageSummary only sums totals, which is why this has survived.

-r is the repository root now and -f an absolute path beneath it, giving filename="common/src/tx_block_allocate.c" under a <source> that agrees with it.

Both have to be absolute. -r ../../.. -f common/src produces a report of zero files and exits 0 — the worst failure mode available here, a green run carrying an empty report.

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, because gcovr searches -r as well.

That was harmless only by accident: -r build/$1 constrained the search instead. Moving -r to the repository root removes the accident, so the two changes have to land together. Measured with a second instrumented configuration deliberately made sparser than the first:

invocation lines covered
scoped by the positional search path 3221 / 3827 — the truth
no search path, -r at the repo root 3827 / 3827 — silently merged the other configuration in
--object-directory at the sparse tree 3827 / 3827 — scopes nothing

So the positional search path is load-bearing, and it matters ahead of instrumenting all five configurations: without it, each configuration would report the union as its own.

An empty report reads as 100%

An empty report is not an error to gcovr — it warns and exits 0 — and it carries line-rate="1.0" beside lines-valid="0":

<coverage line-rate="1.0" branch-rate="1.0" lines-covered="0" lines-valid="0" ...>
  <packages/>

So a consumer reads no data at all as fully covered, and no coverage threshold can catch it, because an empty report passes any threshold. Hence the explicit assertion that the report has content. It 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 today without a word.

Verification

Number-neutral, and that was the test. Over the same frozen .gcda, changing only the gcovr invocation:

before after
ThreadX lines 3827 / 3827 3827 / 3827
ThreadX branches 1993 / 1994 1993 / 1994
ThreadX files 177 177
SMP lines 4739 / 4791 4739 / 4791
SMP branches 2417 / 2430 2417 / 2430
SMP files 185 185

Comparing over frozen .gcda rather than fresh runs is deliberate: the numerator moves run to run on its own. A later full run of the SMP suite reported 4741 rather than 4739 with lines-valid unchanged at 4791, which is that same flicker and not this change.

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.

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 <source> 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 <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit b756220 into eclipse-threadx:dev Aug 25, 2026
8 checks passed
@fdesbiens
fdesbiens deleted the fix/coverage-report-paths branch August 25, 2026 21:16
fdesbiens added a commit that referenced this pull request Aug 26, 2026
The coverage summary reported a percentage and could not fail. Coverage
could fall from 99.97% to anything at all and every check stayed green,
against an AGENTS.md that asks for 100% test coverage -- a stated
requirement measured with a gauge that had no failure mode.

CodeCoverageSummary already takes thresholds and fail_below_min; neither
was set. Both are now, through a new coverage_thresholds input on the
template, because the two suites do not sit at the same figure: ThreadX
99, SMP 98.

Three things were probed against the pinned action on a runner before
picking those numbers, using the real merged.xml files from the dev push
run of #666.

The floor compares the line rate and nothing else. That mattered because
branch coverage is around 78% in both suites while line coverage is
98.8-100%, so a floor aimed at the line figure would have been an
immediate red wall had it tested branches or the lower of the two. The
ThreadX report at 100.00% lines and 77.67% branches clears a floor of 99.

The thresholds are whole numbers. '99.9 100' -- the value this was meant
to be -- is rejected with 'System.ArgumentException - Threshold parameter
set incorrectly.', and the step fails whether or not fail_below_min is
set. So the choice is 99 or 100 with nothing between.

100 would fail on a race. tx_thread_system_resume.c:529 is reached by
timing rather than by construction and flaps between runs of the same
green tree, which is why #666 left it; 4502/4503 fails a floor of 100 and
clears one of 99. A coverage gate that goes red on a coin toss is how
coverage gates get switched off.

SMP is 5114/5178 lines, 98.76%, with 64 uncovered lines across 11 files
of common_smp/src -- #666 closed the equivalent gaps in common/src only.
A shared floor of 99 would have failed that job on every run while
ThreadX passed.

One limit is recorded in the file rather than fixed: an empty report
reads as 100%. gcovr writes line-rate="1.0" beside lines-valid="0" when
it finds no data, and the action prints 'Line Rate = 100% (0 / 0)' and
passes any floor. The check for that is the emptiness assertion #664 put
in each suite's coverage.sh, not this one.

Also corrected two stale filenames in the deploy job's comment: since
#665 each coverage artifact carries merged.xml, not
default_build_coverage.xml. Verified on the runner.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant