Skip to content

Gcov plugin: version-gating/arg-building refactor, bug fixes, unit tests - #1261

Merged
mkarlesky merged 5 commits into
next_versionfrom
gcov-plugin-review
Sep 4, 2026
Merged

Gcov plugin: version-gating/arg-building refactor, bug fixes, unit tests#1261
mkarlesky merged 5 commits into
next_versionfrom
gcov-plugin-review

Conversation

@mkarlesky

Copy link
Copy Markdown
Member

Summary

Code review of plugins/gcov/lib/ against its own docs and against gcovr's/ReportGenerator's real CLI and version history, per a planned pass covering confirmed bugs, a DRY refactor of the shared version-gating and argument-building logic, and net-new unit test coverage (previously zero) for the plugin.

Bug fixes (each verified against real tools, not just documentation)

  • :gcov ↳ :gcovr ↳ :decisions version floor corrected 6.0 → 5.1 — confirmed via a real gcovr 5.1/5.0 pip install (5.1 accepts --decisions, 5.0 rejects it as unrecognized).
  • :fail_under_* now rejects 0 (documented range is 1–100; 0 was silently accepted).
  • :object_directory/:source_encoding values are now quoted, matching every other string-valued gcovr option.
  • Config-driven values (test/mock prefixes, build root) are now Regexp.escaped before being spliced into exclusion-pattern regexes, in both reportinators.
  • :mcdc's GCC-version check moved out of Gcov#setup() (ran for every build the plugin was merely enabled for) into a lazy, memoized check that only fires on a real gcov:-context compile/link.
  • gcovr_exec_exception? now reports every simultaneously violated :fail_under_* threshold, not just the first.
  • Added the missing .dup guard in ReportGeneratorReportinator#collect_reportgenerator_opts, mirroring the existing gcovr-side guard.
  • gcovr 7.0+ deprecated flag migration (--branches--txt-metric branch, etc.), silencing gcovr's own deprecation warnings on newer gcovr while still using the pre-7.0 names below it.

One audit finding (ReportGenerator settings: missing a leading -) was investigated, disproven, and reverted rather than shipped — a real reportgenerator run showed the leading - breaks it; the original dash-less form is correct.

Refactor

Two generic, shared utilities on GcovReportinator, each with a declarative table per reportinator:

  • ToolVersionGating (version detection + gating), also used by Gcov's own GCC-version check.
  • build_args_from_table (declarative CLI-arg building), replacing hand-written string concatenation in both args_builder_common and build_optional_args.

Tests

4 new spec files, 59 new examples. Full unit suite: 2991 examples, 0 failures. Full Gcov system-test suite via throwtheswitch/madsciencelab-plugins Docker: 26 examples, 0 failures, 2 pending (gdb unavailable, unrelated).

Changelog

Intentionally withheld from this PR — reserved for a follow-on documentation-only pass, and for splitting between a master 1.1.8 port and next_version's own 1.2.0 entry once CI has run here.

🤖 Generated with Claude Code

mkarlesky and others added 5 commits September 4, 2026 12:19
Code review of plugins/gcov/lib/ against its own docs and against gcovr's
and ReportGenerator's real CLI/version history turned up several confirmed
bugs and a DRY opportunity spanning the two report-generation backends.

Bug fixes (each verified against real tools, not just documentation):
- :gcov ↳ :gcovr ↳ :decisions version floor corrected 6.0 -> 5.1 -- gcovr's
  own changelog and a real gcovr 5.1/5.0 pip install confirm --decisions was
  introduced in 5.1, not 6.0 as the recent #1080 fix stated.
- :fail_under_* now rejects 0 (documented range is 1-100; 0 was silently
  accepted).
- :object_directory/:source_encoding values are now quoted, matching every
  other string-valued gcovr option -- an unquoted value containing a space
  previously broke the constructed command line.
- Config-driven values (test/mock prefixes, build root) are now
  Regexp.escape'd before being spliced into exclusion-pattern regexes, in
  both gcovr_reportinator.rb and reportgenerator_reportinator.rb.
- :mcdc's GCC-version check moved out of Gcov#setup(), which runs for every
  build the plugin is merely enabled for, into a lazily-invoked, memoized
  check that only fires on a real gcov:-context compile/link -- consistent
  with this same file's existing lazy-validation rationale for the gcovr/
  ReportGenerator Reportinators themselves.
- gcovr_exec_exception? now reports every simultaneously violated
  :fail_under_* threshold instead of only the first.
- Added the missing .dup guard in
  ReportGeneratorReportinator#collect_reportgenerator_opts, mirroring the
  existing guard on the gcovr side.
- gcovr 7.0+ deprecated flag migration: :branches/:sort_uncovered/
  :sort_percentage now use --txt-metric branch/--sort uncovered-number/
  --sort uncovered-percent at gcovr 7.0+, silencing gcovr's own deprecation
  warnings; the pre-7.0 names are still used automatically below that.

One audit finding was investigated, disproven, and reverted rather than
shipped: ReportGenerator's `settings:` arguments were flagged as missing a
leading '-' to match every sibling option. A real reportgenerator run proved
the opposite -- a leading '-' makes it log "Unknown command line parameter
'settings'" and the setting never applies; the original dash-less form is
correct and unchanged.

Refactor: two generic, shared utilities on GcovReportinator, replacing
repeated hand-written version-gate/argument-string boilerplate with one
declarative table per reportinator --
- ToolVersionGating (gcov_types.rb): generalizes the gcovr-only
  GcovToolVersion struct and min_version? into a reusable ToolVersion +
  detect_tool_version/enforce_version_gates!, used by GcovrReportinator's own
  gcovr-version gates and by Gcov's GCC-version gate alike. detect_tool_version
  is also the one seam a unit test now stubs directly, instead of faking a
  full tool_executor.build_command_line/.exec/regex-parse round-trip.
- build_args_from_table (gcov_reportinator.rb): a declarative
  option-symbol -> CLI-flag table drives both gcovr's args_builder_common and
  ReportGenerator's build_optional_args, replacing ad hoc string
  concatenation with plain, readable data (including handling for a
  version-dependent flag name, via a Proc, for the deprecated-flag
  migration above).
- build_custom_args lifted to the shared base class (was byte-identical in
  both reportinators).

Unit tests: net-new coverage for a plugin that had none before -- only real
tools -- gcov/gcovr/reportgenerator -- are ever exercised via system tests;
`@tool_executor.exec` is always stubbed here.
- spec/units/plugins/gcov_reportinator_spec.rb: the shared base-class
  utilities.
- spec/units/plugins/gcovr_reportinator_spec.rb: version gating,
  args_builder_common, config-file exclusion warning, exclusion-regex
  escaping, multi-violation exec-exception reporting.
- spec/units/plugins/reportgenerator_reportinator_spec.rb: build_optional_args
  (including a regression test locking in the settings: dash-less form above),
  the .dup mutation guard, exclusion-regex escaping.
- spec/units/plugins/gcov_spec.rb: the lazy, memoized :mcdc/GCC-version check.

Verified: full unit suite (2991 examples, 0 failures), mkdocs build --strict,
and the full Gcov system-test suite (26 examples, 0 failures, 2 pending --
gdb unavailable, unrelated) run against real gcc/gcovr/reportgenerator via
throwtheswitch/madsciencelab-plugins Docker.

Changelog entries intentionally withheld from this commit -- reserved for a
follow-on documentation-only pass alongside separately-deferred doc
corrections, and for splitting between a master-branch 1.1.8 port and
next_version's own 1.2.0 entry once CI has run on this branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's coverage report on PR #1261 revealed the Gcov plugin's just-added unit
coverage was still thin relative to file size. This adds ~172 new examples
targeting core branching, parsing, and dispatch logic across the four
under-covered files -- explicitly avoiding tests that just mirror a single
line of production code back as an assertion. Coverage:

  console_reportinator.rb          18% -> 90% (15/81  -> 75/83)
  gcov.rb                          22% -> 79% (40/182 -> 143/182)
  gcovr_reportinator.rb            44% -> 93% (88/200 -> 185/200)
  reportgenerator_reportinator.rb  59% -> 85% (67/113 -> 97/114)
  gcov_reportinator.rb             86% -> 93% (36/42  -> 39/42, untouched --
                                    already well covered)

New spec file: spec/units/plugins/console_reportinator_spec.rb (24 examples)
covering the real gcov-text-output parsing logic (remap_partial_sources,
extract_gcov_source_path, log_coverage_report, run_gcov_summary) that had
zero coverage before.

Extended spec/units/plugins/gcov_spec.rb (+41 examples): process_untested_sources
(the largest single gap -- all three :ignore/:list/:compile modes, MC/DC flag
injection, ShellException guidance handling), generate_coverage_reports
(reportinator memoization, per-reportinator exception isolation as a build
failure rather than aborting the whole build), post_build's gating chain,
build_reportinators' utility-to-class dispatch, validate_untested_sources/
validate_utilities_config, collect_untested_sources, post_test_fixture_execute,
and the MC/DC-flag-injection pre_test_compile_register/pre_test_link_register
paths. Deliberately excludes thin one-line predicates, pure field-copies, and
setup() itself (would need an enormous collaborator double for wiring already
covered piecemeal by the extracted methods above).

Extended spec/units/plugins/gcovr_reportinator_spec.rb (+39 examples): the four
args_builder_* report-type methods, generate_reports_modern's skip-when-nothing-
enabled branch, collect_gcovr_opts' config-file-vs-normal exclusion handling,
run_gcovr's exception/summary flow, and the two previously-untested pure text
parsers extract_gcovr_error_message/extract_gcovr_summary (including the
multiple-summary-blocks-picks-the-last-one case a naive test would get wrong).

Extended spec/units/plugins/reportgenerator_reportinator_spec.rb (+10 examples):
build_report_types, and -- after a small testability refactor (below) --
run_gcov's gcov-output filename-extraction/rename regex logic (the biggest
previously-untested gap in this file) and generate_gcov_files' directory
discovery/sort/exclude logic.

Two small production changes, made while designing the above:

1. ConsoleReportinator#log_coverage_report: a Partial-implementation source
   whose gcov output can't be matched at all previously produced no report
   AND no log -- silently different from the equivalent non-Partial "found no
   coverage results" case just below it. Added the same COMPLAIN log to that
   branch.

2. ReportGeneratorReportinator previously called Dir.glob/File.exist?/
   File.rename directly in generate_gcov_files, run_reportgenerator, and
   run_gcov -- real filesystem I/O with no mockable seam, which is exactly why
   those methods had zero unit coverage. Routed all of it through FileWrapper
   (lib/ceedling/file_wrapper.rb), the same DI-friendly abstraction Gcov
   itself already uses: Dir.glob -> @file_wrapper.directory_listing,
   File.exist? -> @file_wrapper.exist?, File.rename -> @file_wrapper.mv.
   Added @file_wrapper = @ceedling[:file_wrapper] to initialize. No unit spec
   in this PR touches a real file or directory.

Verified: full unit suite (3107 examples, 0 failures), mkdocs build --strict,
and the full Gcov system-test suite (26 examples, 0 failures, 2 pending --
gdb unavailable, unrelated) run against real gcc/gcovr/reportgenerator via
throwtheswitch/madsciencelab-plugins Docker, confirming the FileWrapper
refactor and the log_coverage_report fix behave identically end-to-end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…reshold parity

Investigated the deferred items that were genuine open bugs/questions
(memory: gcov_plugin_review_deferred_items.md), fixed what's confirmed.

:mcdc dead-code question -- resolved, confirmed genuinely dead code:

GcovrReportinator#collect_gcovr_opts set an internal _opts[:mcdc] = true
flag that no args_builder_* method ever read. A prior session confirmed via
Docker that gcovr's JSON output shows GCC condition/MC-DC data identically
with or without --decisions; this session extended that check to HTML
specifically (the open half of the question) using a real gcovr 8.6 (the
plugin's own :mcdc version floor) against a real multi-condition
-fcondition-coverage build: --decisions produces a byte-identical
Condition-labeled HTML report, only adding a wholly separate Decision
column alongside it. Removed the dead line, with a comment recording why
gcovr needs no :mcdc-specific handling of its own at all.

:boom exception-handling "inconsistency" -- investigated, confirmed intended
behavior (console/reportgenerator's boom:false vs. gcovr's raise-and-rescue
each correctly match their own tool's actual failure semantics), not
touched.

ReportGenerator coverage-threshold parity -- new feature:

Added ReportGenerator's own -minimumCoverageThresholds:* settings (RG
v5.1.23+), the direct RG analogue of gcovr's :fail_under_* family: four new
:gcov ↳ :report_generator options (:fail_under_line, :fail_under_branch,
:fail_under_method, :fail_under_full_method), 1-100 validated the same way
gcovr's are. Implementation: a new declarative :inline_integer_percent type
on GcovReportinator#build_args_from_table (combines :inline_value's
flag-and-value-glued-together shape with :integer_percent's range check),
and a REPORT_GENERATOR_THRESHOLD_ARGS table folded into build_optional_args
alongside the existing settings: handling.

One flag-syntax mistake caught before it shipped, same pattern as this
branch's earlier settings: dash question: minimumCoverageThresholds: was
initially written with a leading '-' to match the other 1:1 options'
convention. A real reportgenerator run immediately rejected it ("Unknown
command line parameter 'minimumCoverageThresholds'"); the dash-less form
--- matching settings:'s own established exception --- is correct. Caught
and fixed via the exact same Docker verification that then went on to
confirm a configured threshold violation genuinely fails the build (exit 1,
BUILD FAILURE SUMMARY) and a passing one doesn't (exit 0) --- no new
:exception_on_fail toggle needed since run_reportgenerator already leaves
:boom at its default (true), so a threshold violation was already going to
raise and already gets caught by Gcov#generate_coverage_reports's existing
per-reportinator exception isolation.

Small doc additions for the four new options in
docs/mkdocs/plugins/gcov/reportgenerator.md and
docs/mkdocs/reference/gcov-plugin.md (documenting what's new; the broader
doc-corrections pass is a separate, later step).

Verified: full unit suite (3112 examples, 0 failures), mkdocs build
--strict, full Gcov system-test suite (26 examples, 0 failures, 2 pending
-- gdb, unrelated) via madsciencelab-plugins Docker, plus manual Docker
confirmation of both a threshold-violating and a threshold-passing
ReportGenerator build.

No Changelog entries in this commit -- reserved for a later step once
master-branch porting is scoped (see updated deferred-items memory).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mkarlesky
mkarlesky merged commit a4e7943 into next_version Sep 4, 2026
@mkarlesky
mkarlesky deleted the gcov-plugin-review branch September 4, 2026 22:54
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