Skip to content

Commit

Permalink
✨ GCov plugin now checks on gcov: tasks in CLI
Browse files Browse the repository at this point in the history
Also refactored to use clear instance variables instead of direct references to the system objects hash.
  • Loading branch information
mkarlesky committed Jul 16, 2024
1 parent e70fbb2 commit 088c460
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 47 deletions.
84 changes: 49 additions & 35 deletions plugins/gcov/lib/gcov.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ def setup
@result_list = []

@project_config = @ceedling[:configurator].project_config_hash

# Are any reports enabled?
@reports_enabled = reports_enabled?( @project_config[:gcov_reports] )

# Was a gcov: task on the command line?
@cli_gcov_task = @ceedling[:system_wrapper].get_cmdline().any?{|item| item.include?( GCOV_TASK_ROOT )}

# Validate the gcov tools if coverage summaries are enabled (summaries rely on the `gcov` tool)
# Note: This gcov tool is a different configuration than the gcov tool used by ReportGenerator
Expand All @@ -30,8 +35,21 @@ def setup
)
end

# Validate tools and configuration while building reportinators
@reportinators = build_reportinators( @project_config[:gcov_utilities], @reports_enabled )
# Validate configuration and tools while building Reportinators
@reportinators = build_reportinators(
@project_config[:gcov_utilities],
@reports_enabled,
@cli_gcov_task
)

# Convenient instance variable references
@configurator = @ceedling[:configurator]
@loginator = @ceedling[:loginator]
@test_invoker = @ceedling[:test_invoker]
@plugin_reportinator = @ceedling[:plugin_reportinator]
@file_path_utils = @ceedling[:file_path_utils]
@file_wrapper = @ceedling[:file_wrapper]
@tool_executor = @ceedling[:tool_executor]

@mutex = Mutex.new()
end
Expand All @@ -51,12 +69,12 @@ def generate_coverage_object_file(test, source, object)
if File.extname(source) == EXTENSION_ASSEMBLY
tool = TOOLS_TEST_ASSEMBLER
# If a source file (not unity, mocks, etc.) is to be compiled use code coverage compiler
elsif @ceedling[:configurator].collection_all_source.include?(source)
elsif @configurator.collection_all_source.include?(source)
tool = TOOLS_GCOV_COMPILER
msg = "Compiling #{File.basename(source)} with coverage..."
end

@ceedling[:test_invoker].compile_test_component(
@test_invoker.compile_test_component(
tool: tool,
context: GCOV_SYM,
test: test,
Expand All @@ -79,16 +97,16 @@ def post_test_fixture_execute(arg_hash)

# `Plugin` build step hook
def post_build
# Do nothing unless a gcov: task was used
return unless @ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}/)
# Do nothing unless a gcov: task was on the command line
return unless @cli_gcov_task

# Only present plugin-based test results if raw test results disabled by a reporting plugin
if !@ceedling[:configurator].plugins_display_raw_test_results
if !@configurator.plugins_display_raw_test_results
results = {}

# Assemble test results
@mutex.synchronize do
results = @ceedling[:plugin_reportinator].assemble_test_results( @result_list )
results = @plugin_reportinator.assemble_test_results( @result_list )
end

hash = {
Expand All @@ -97,7 +115,7 @@ def post_build
}

# Print unit test suite results
@ceedling[:plugin_reportinator].run_test_results_report( hash ) do
@plugin_reportinator.run_test_results_report( hash ) do
message = ''
message = 'Unit test failures.' if results[:counts][:failed] > 0
message
Expand All @@ -114,10 +132,10 @@ def post_build
# `Plugin` build step hook
def summary
# Only present plugin-based test results if raw test results disabled by a reporting plugin
return if @ceedling[:configurator].plugins_display_raw_test_results
return if @configurator.plugins_display_raw_test_results

# Build up the list of passing results from all tests
result_list = @ceedling[:file_path_utils].form_pass_results_filelist(
result_list = @file_path_utils.form_pass_results_filelist(
GCOV_RESULTS_PATH,
COLLECTION_ALL_TESTS
)
Expand All @@ -126,13 +144,13 @@ def summary
header: GCOV_ROOT_NAME.upcase,
# Collect all existing test results (success or failing) in the filesystem,
# limited to our test collection
:results => @ceedling[:plugin_reportinator].assemble_test_results(
:results => @plugin_reportinator.assemble_test_results(
result_list,
{:boom => false}
)
}

@ceedling[:plugin_reportinator].run_test_results_report(hash)
@plugin_reportinator.run_test_results_report(hash)
end

# Called within class and also externally by conditionally regnerated Rake task
Expand All @@ -142,10 +160,10 @@ def generate_coverage_reports()

@reportinators.each do |reportinator|
# Create the artifacts output directory.
@ceedling[:file_wrapper].mkdir( reportinator.artifacts_path )
@file_wrapper.mkdir( reportinator.artifacts_path )

# Generate reports
reportinator.generate_reports( @ceedling[:configurator].project_config_hash )
reportinator.generate_reports( @configurator.project_config_hash )
end
end

Expand All @@ -166,18 +184,18 @@ def utility_enabled?(opts, utility_name)
end

def console_coverage_summaries()
banner = @ceedling[:plugin_reportinator].generate_banner( "#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" )
@ceedling[:loginator].log "\n" + banner
banner = @plugin_reportinator.generate_banner( "#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" )
@loginator.log "\n" + banner

# Iterate over each test run and its list of source files
@ceedling[:test_invoker].each_test_with_sources do |test, sources|
heading = @ceedling[:plugin_reportinator].generate_heading( test )
@ceedling[:loginator].log(heading)
@test_invoker.each_test_with_sources do |test, sources|
heading = @plugin_reportinator.generate_heading( test )
@loginator.log(heading)

sources.each do |source|
filename = File.basename(source)
name = filename.ext('')
command = @ceedling[:tool_executor].build_command_line(
command = @tool_executor.build_command_line(
TOOLS_GCOV_SUMMARY,
[], # No additional arguments
filename, # .c source file that should have been compiled with coverage
Expand All @@ -189,22 +207,22 @@ def console_coverage_summaries()
command[:options][:boom] = false

# Run the gcov tool and collect the raw coverage report
shell_results = @ceedling[:tool_executor].exec( command )
shell_results = @tool_executor.exec( command )
results = shell_results[:output].strip

# Handle errors instead of raising a shell exception
if shell_results[:exit_code] != 0
debug = "gcov error (#{shell_results[:exit_code]}) while processing #{filename}... #{results}"
@ceedling[:loginator].log( debug, Verbosity::DEBUG, LogLabels::ERROR )
@ceedling[:loginator].log( "gcov was unable to process coverage for #{filename}", Verbosity::COMPLAIN )
@loginator.log( debug, Verbosity::DEBUG, LogLabels::ERROR )
@loginator.log( "gcov was unable to process coverage for #{filename}", Verbosity::COMPLAIN )
next # Skip to next loop iteration
end

# A source component may have been compiled with coverage but none of its code actually called in a test.
# In this case, versions of gcov may not produce an error, only blank results.
if results.empty?
msg = "No functions called or code paths exercised by test for #{filename}"
@ceedling[:loginator].log( msg, Verbosity::COMPLAIN, LogLabels::NOTICE )
@loginator.log( msg, Verbosity::COMPLAIN, LogLabels::NOTICE )
next # Skip to next loop iteration
end

Expand All @@ -215,7 +233,7 @@ def console_coverage_summaries()
matches = results.match(/File\s+'(.+)'/)
if matches.nil? or matches.length() != 2
msg = "Could not extract filepath via regex from gcov results for #{test}::#{File.basename(source)}"
@ceedling[:loginator].log( msg, Verbosity::DEBUG, LogLabels::ERROR )
@loginator.log( msg, Verbosity::DEBUG, LogLabels::ERROR )
else
# Expand to full path from likely partial path to ensure correct matches on source component within gcov results
_source = File.expand_path(matches[1])
Expand All @@ -226,21 +244,22 @@ def console_coverage_summaries()
# Reformat from first line as filename banner to each line of statistics labeled with the filename
# Only extract the first four lines of the console report (to avoid spidering coverage reports through libs, etc.)
report = results.lines.to_a[1..4].map { |line| filename + ' | ' + line }.join('')
@ceedling[:loginator].log(report + "\n")
@loginator.log(report + "\n")

# Otherwise, found no coverage results
else
msg = "Found no coverage results for #{test}::#{File.basename(source)}"
@ceedling[:loginator].log( msg, Verbosity::COMPLAIN )
@loginator.log( msg, Verbosity::COMPLAIN )
end
end
end
end

def build_reportinators(config, enabled)
def build_reportinators(config, enabled, gcov_task)
reportinators = []

return reportinators if not enabled
# Do not instantiate reportinators (and tool validation) unless reports enabled and a gcov: task present in command line
return reportinators if ((!enabled) or (!gcov_task))

config.each do |reportinator|
if not GCOV_UTILITY_NAMES.map(&:upcase).include?( reportinator.upcase )
Expand All @@ -267,8 +286,3 @@ def build_reportinators(config, enabled)

end

# end blocks always executed following rake run
END {
# cache our input configurations to use in comparison upon next execution
@ceedling[:cacheinator].cache_test_config(@ceedling[:setupinator].config_hash) if @ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}/)
}
13 changes: 8 additions & 5 deletions plugins/gcov/lib/gcovr_reportinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def initialize(system_objects)
# Convenient instance variable references
@loginator = @ceedling[:loginator]
@reportinator = @ceedling[:reportinator]
@tool_executor = @ceedling[:tool_executor]
end

# Generate the gcovr report(s) specified in the options.
Expand Down Expand Up @@ -299,12 +300,12 @@ def get_gcovr_opts(opts)

# Run gcovr with the given arguments
def run(opts, args, boom)
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_GCOVR_REPORT, [], args)
command = @tool_executor.build_command_line(TOOLS_GCOV_GCOVR_REPORT, [], args)

shell_result = nil

begin
shell_result = @ceedling[:tool_executor].exec( command )
shell_result = @tool_executor.exec( command )
rescue ShellExecutionException => ex
result = ex.shell_result
@reportinator_helper.print_shell_result( result )
Expand All @@ -321,17 +322,19 @@ def get_gcovr_version()
version_number_major = 0
version_number_minor = 0

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

msg = @reportinator.generate_progress("Collecting gcovr version for conditional feature handling")
@loginator.log(msg, Verbosity::OBNOXIOUS)
@loginator.log( msg, Verbosity::OBNOXIOUS )

shell_result = @ceedling[:tool_executor].exec( command )
shell_result = @tool_executor.exec( command )
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
else
raise CeedlingException.new( "Could not collect `gcovr` version from its command line" )
end

return version_number_major, version_number_minor
Expand Down
9 changes: 5 additions & 4 deletions plugins/gcov/lib/reportgenerator_reportinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def initialize(system_objects)
# Convenient instance variable references
@loginator = @ceedling[:loginator]
@reportinator = @ceedling[:reportinator]
@tool_executor = @ceedling[:tool_executor]
end


Expand Down Expand Up @@ -203,17 +204,17 @@ def get_opts(opts)

# Run ReportGenerator with the given arguments.
def run(args)
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_REPORTGENERATOR_REPORT, [], args)
command = @tool_executor.build_command_line(TOOLS_GCOV_REPORTGENERATOR_REPORT, [], args)

return @ceedling[:tool_executor].exec( command )
return @tool_executor.exec( command )
end


# Run gcov with the given arguments.
def run_gcov(args)
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_REPORT, [], args)
command = @tool_executor.build_command_line(TOOLS_GCOV_REPORT, [], args)

return @ceedling[:tool_executor].exec( command )
return @tool_executor.exec( command )
end

end
7 changes: 4 additions & 3 deletions plugins/gcov/lib/reportinator_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@
class ReportinatorHelper

def initialize(system_objects)
@ceedling = system_objects
# Convenience alias
@loginator = system_objects[:loginator]
end

# Output the shell result to the console.
def print_shell_result(shell_result)
if !(shell_result.nil?)
msg = "Done in %.3f seconds." % shell_result[:time]
@ceedling[:loginator].log(msg, Verbosity::NORMAL)
@loginator.log(msg, Verbosity::NORMAL)

if !(shell_result[:output].nil?) && (shell_result[:output].length > 0)
@ceedling[:loginator].log(shell_result[:output], Verbosity::OBNOXIOUS)
@loginator.log(shell_result[:output], Verbosity::OBNOXIOUS)
end
end
end
Expand Down

0 comments on commit 088c460

Please sign in to comment.