Skip to content

Commit

Permalink
✨ Added support for gcovr merge-mode-functions
Browse files Browse the repository at this point in the history
Without this, gcovr can experience a fatal error for versions greater than 6.0 when encountering the likely scenario of the same source function coverage tested in multiple builds.
  • Loading branch information
mkarlesky committed Aug 28, 2024
1 parent de35e2e commit fd893a4
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 16 deletions.
7 changes: 7 additions & 0 deletions plugins/gcov/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,13 @@ root of your project, you may need set `:report_root` as well as
# generates for exception handling. (gcovr --exclude-throw-branches).
:exclude_throw_branches: <true|false>
# For Gcovr 6.0+, multiple instances of the same function in coverage results can
# cause a fatal error. Since Ceedling can test multiple build variations of the
# same source function, this is bad.
# Default value for Gcov plugin is 'merge-use-line-max'. See Gcovr docs for more.
# https://gcovr.com/en/stable/guide/merging.html
:merge_mode_function: <...>
# Use existing gcov files for analysis. Default: False. (gcovr --use-gcov-files)
:use_gcov_files: <true|false>
Expand Down
8 changes: 8 additions & 0 deletions plugins/gcov/config/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@
:gcov:
:summaries: TRUE # Enable simple coverage summaries to console after tests
:report_task: FALSE # Disabled dedicated report generation task (this enables automatic report generation)

:utilities:
- gcovr # Defaults to `gcovr` as report generation utility

:reports: [] # User must specify a report to enable report generation

:gcovr:
:report_root: "." # Gcovr defaults to scanning for results starting in working directory

# For v6.0+ merge coverage results for same function tested multiple times
# The default behavior is 'strict' which will cause a gcovr exception for many users
:merge_mode_function: merge-use-line-max

:report_generator:
:verbosity: Warning # Default verbosity
:collection_paths_source: [] # Explicitly defined as default empty array to simplify option validation code
Expand Down
51 changes: 35 additions & 16 deletions plugins/gcov/lib/gcovr_reportinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def initialize(system_objects)
# Generate the gcovr report(s) specified in the options.
def generate_reports(opts)
# Get the gcovr version number.
gcovr_version_info = get_gcovr_version()
gcovr_version = get_gcovr_version()

# Get gcovr options from project configuration options
gcovr_opts = get_gcovr_opts(opts)
Expand All @@ -42,15 +42,15 @@ def generate_reports(opts)
exception_on_fail = !!gcovr_opts[:exception_on_fail]

# Build the common gcovr arguments.
args_common = args_builder_common(gcovr_opts)
args_common = args_builder_common( gcovr_opts, gcovr_version )

msg = @reportinator.generate_heading( "Running Gcovr Coverage Reports" )
@loginator.log( msg )

if ((gcovr_version_info[0] == 4) && (gcovr_version_info[1] >= 2)) || (gcovr_version_info[0] > 4)
# gcovr version 4.2 and later supports generating multiple reports with a single call.
if min_version?( gcovr_version, 4, 2 )
reports = []

# gcovr version 4.2 and later supports generating multiple reports with a single call.
args = args_common

args += (_args = args_builder_cobertura(opts, false))
Expand Down Expand Up @@ -83,10 +83,11 @@ def generate_reports(opts)
if !(args == args_common)
run( gcovr_opts, args, exception_on_fail )
end

# gcovr version 4.1 and earlier supports HTML and Cobertura XML reports.
# It does not support SonarQube and JSON reports.
# Reports must also be generated separately.
else
# gcovr version 4.1 and earlier supports HTML and Cobertura XML reports.
# It does not support SonarQube and JSON reports.
# Reports must also be generated separately.
args_cobertura = args_builder_cobertura(opts, true)
args_html = args_builder_html(opts, true)

Expand Down Expand Up @@ -123,7 +124,7 @@ def generate_reports(opts)
GCOVR_SETTING_PREFIX = "gcov_gcovr"

# Build the gcovr report generation common arguments.
def args_builder_common(gcovr_opts)
def args_builder_common(gcovr_opts, gcovr_version)
args = ""
args += "--root \"#{gcovr_opts[:report_root]}\" " unless gcovr_opts[:report_root].nil?
args += "--config \"#{gcovr_opts[:config_file]}\" " unless gcovr_opts[:config_file].nil?
Expand All @@ -145,6 +146,11 @@ def args_builder_common(gcovr_opts)
args += "--delete " if gcovr_opts[:delete]
args += "-j #{gcovr_opts[:threads]} " if !(gcovr_opts[:threads].nil?) && (gcovr_opts[:threads].is_a? Integer)

# Version check -- merge mode is only available and relevant as of gcovr 6.0
if min_version?( gcovr_version, 6, 0 )
args += "--merge-mode-functions \"#{gcovr_opts[:merge_mode_function]}\" " unless gcovr_opts[:merge_mode_function].nil?
end

[:fail_under_line,
:fail_under_branch,
:fail_under_decision,
Expand Down Expand Up @@ -216,11 +222,11 @@ def args_builder_sonarqube(opts, use_output_option=false)

# Build the gcovr JSON report generation arguments.
def args_builder_json(opts, use_output_option=false)
gcovr_opts = get_gcovr_opts(opts)
gcovr_opts = get_gcovr_opts( opts )
args = ""

# Determine if the gcovr JSON report is enabled. Defaults to disabled.
if report_enabled?(opts, ReportTypes::JSON)
if report_enabled?( opts, ReportTypes::JSON )
# Determine the JSON report file name.
artifacts_file_json = GCOV_GCOVR_ARTIFACTS_FILE_JSON
if !(gcovr_opts[:json_artifact_filename].nil?)
Expand Down Expand Up @@ -317,10 +323,10 @@ def run(opts, args, boom)


# Get the gcovr version number as components
# Return [major, minor]
# Return {:major, :minor}
def get_gcovr_version()
version_number_major = 0
version_number_minor = 0
major = 0
minor = 0

command = @tool_executor.build_command_line(TOOLS_GCOV_GCOVR_REPORT, [], "--version")

Expand All @@ -331,13 +337,26 @@ def get_gcovr_version()
version_number_match_data = shell_result[:output].match(/gcovr ([0-9]+)\.([0-9]+)/)

if !(version_number_match_data.nil?) && !(version_number_match_data[1].nil?) && !(version_number_match_data[2].nil?)
version_number_major = version_number_match_data[1].to_i
version_number_minor = version_number_match_data[2].to_i
major = version_number_match_data[1].to_i
minor = version_number_match_data[2].to_i
else
raise CeedlingException.new( "Could not collect `gcovr` version from its command line" )
end

return version_number_major, version_number_minor
return {:major => major, :minor => minor}
end


# Process version hash from `get_gcovr_version()`
def min_version?(version, major, minor)
# Meet minimum requirement if major version is greater than minimum major threshold
return true if version[:major] > major

# Meet minimum requirement only if greater than or equal to minor version for the same major version
return true if version[:major] == major and version[:minor] >= minor

# Version is less than major.minor
return false
end


Expand Down

0 comments on commit fd893a4

Please sign in to comment.