adding new ceedling test project

This commit is contained in:
hathach
2019-06-10 16:18:27 +07:00
parent 579f468d38
commit 97c5c7a937
199 changed files with 23201 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
ceedling-gcov
=============
Plugin for integrating GNU GCov code coverage tool into Ceedling projects.
Currently only designed for the gcov command (like LCOV for example). In the
future we could configure this to work with other code coverage tools.
This plugin currently uses `gcovr` to generate HTML reports as a utility. The
normal gcov plugin _must_ be run first for this report to generate.
Gcovr can be installed via pip like so:
```
pip install gcovr
```
There are two types of gcovr HTML reports that can be configured in your
`project.yml`. To create a basic HTML report with only the overall file
information use the following config.
```
:gcov:
:html_report_type: basic
```
To create a detailed HTML report with line by line breakdown of the coverage use
the following config.
```
:gcov:
:html_report_type: detailed
```
These reports will be found in `build/artifacts/gcov`.
# To-Do list
- Generate overall report (combined statistics from all files with coverage)
- Generate coverage output files
- Easier option override for better customisation

View File

@@ -0,0 +1,15 @@
% function_string = hash[:coverage][:functions].to_s
% branch_string = hash[:coverage][:branches].to_s
% format_string = "%#{[function_string.length, branch_string.length].max}i"
<%=@ceedling[:plugin_reportinator].generate_banner("#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY")%>
% if (!hash[:coverage][:functions].nil?)
FUNCTIONS: <%=sprintf(format_string, hash[:coverage][:functions])%>%
% else
FUNCTIONS: none
% end
% if (!hash[:coverage][:branches].nil?)
BRANCHES: <%=sprintf(format_string, hash[:coverage][:branches])%>%
% else
BRANCHES: none
% end

View File

@@ -0,0 +1,66 @@
---
:tools:
:gcov_compiler:
:executable: gcc
:arguments:
- -g
- -fprofile-arcs
- -ftest-coverage
- -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
- -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE
- -D$: COLLECTION_DEFINES_TEST_AND_VENDOR
- -DGCOV_COMPILER
- -c "${1}"
- -o "${2}"
:gcov_linker:
:executable: gcc
:arguments:
- -fprofile-arcs
- -ftest-coverage
- ${1}
- -o ${2}
:gcov_fixture:
:executable: ${1}
:gcov_report:
:executable: gcov
:arguments:
- -n
- -p
- -b
- -o "$": GCOV_BUILD_OUTPUT_PATH
- "\"${1}\""
:gcov_post_report:
:executable: gcovr
:optional: TRUE
:arguments:
- -p
- -b
- -e "${1}"
- --html
- -r .
- -o GcovCoverageResults.html
:gcov_post_report_basic:
:executable: gcovr
:optional: TRUE
:arguments:
- -p
- -b
- -e "${1}"
- --html
- -r .
- -o "$": GCOV_ARTIFACTS_FILE
:gcov_post_report_advanced:
:executable: gcovr
:optional: TRUE
:arguments:
- -p
- -b
- -e "${1}"
- --html
- --html-details
- -r .
- -o "$": GCOV_ARTIFACTS_FILE
...

View File

@@ -0,0 +1,180 @@
directory(GCOV_BUILD_OUTPUT_PATH)
directory(GCOV_RESULTS_PATH)
directory(GCOV_ARTIFACTS_PATH)
directory(GCOV_DEPENDENCIES_PATH)
CLEAN.include(File.join(GCOV_BUILD_OUTPUT_PATH, '*'))
CLEAN.include(File.join(GCOV_RESULTS_PATH, '*'))
CLEAN.include(File.join(GCOV_ARTIFACTS_PATH, '*'))
CLEAN.include(File.join(GCOV_DEPENDENCIES_PATH, '*'))
CLOBBER.include(File.join(GCOV_BUILD_PATH, '**/*'))
rule(/#{GCOV_BUILD_OUTPUT_PATH}\/#{'.+\\' + EXTENSION_OBJECT}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |object|
if File.basename(object.source) =~ /^(#{PROJECT_TEST_FILE_PREFIX}|#{CMOCK_MOCK_PREFIX}|#{GCOV_IGNORE_SOURCES.join('|')})/i
@ceedling[:generator].generate_object_file(
TOOLS_GCOV_COMPILER,
OPERATION_COMPILE_SYM,
GCOV_SYM,
object.source,
object.name,
@ceedling[:file_path_utils].form_test_build_list_filepath(object.name)
)
else
@ceedling[GCOV_SYM].generate_coverage_object_file(object.source, object.name)
end
end
rule(/#{GCOV_BUILD_OUTPUT_PATH}\/#{'.+\\' + EXTENSION_EXECUTABLE}$/) do |bin_file|
@ceedling[:generator].generate_executable_file(
TOOLS_GCOV_LINKER,
GCOV_SYM,
bin_file.prerequisites,
bin_file.name,
@ceedling[:file_path_utils].form_test_build_map_filepath(bin_file.name)
)
end
rule(/#{GCOV_RESULTS_PATH}\/#{'.+\\' + EXTENSION_TESTPASS}$/ => [
proc do |task_name|
@ceedling[:file_path_utils].form_test_executable_filepath(task_name)
end
]) do |test_result|
@ceedling[:generator].generate_test_results(TOOLS_GCOV_FIXTURE, GCOV_SYM, test_result.source, test_result.name)
end
rule(/#{GCOV_DEPENDENCIES_PATH}\/#{'.+\\' + EXTENSION_DEPENDENCIES}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |dep|
@ceedling[:generator].generate_dependencies_file(
TOOLS_TEST_DEPENDENCIES_GENERATOR,
GCOV_SYM,
dep.source,
File.join(GCOV_BUILD_OUTPUT_PATH, File.basename(dep.source).ext(EXTENSION_OBJECT)),
dep.name
)
end
task directories: [GCOV_BUILD_OUTPUT_PATH, GCOV_RESULTS_PATH, GCOV_DEPENDENCIES_PATH, GCOV_ARTIFACTS_PATH]
namespace GCOV_SYM do
task source_coverage: COLLECTION_ALL_SOURCE.pathmap("#{GCOV_BUILD_OUTPUT_PATH}/%n#{@ceedling[:configurator].extension_object}")
desc 'Run code coverage for all tests'
task all: [:directories] do
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, GCOV_SYM)
@ceedling[:configurator].restore_config
end
desc 'Run single test w/ coverage ([*] real test or source file name, no path).'
task :* do
message = "\nOops! '#{GCOV_ROOT_NAME}:*' isn't a real task. " \
"Use a real test or source file name (no path) in place of the wildcard.\n" \
"Example: rake #{GCOV_ROOT_NAME}:foo.c\n\n"
@ceedling[:streaminator].stdout_puts(message)
end
desc 'Run tests by matching regular expression pattern.'
task :pattern, [:regex] => [:directories] do |_t, args|
matches = []
COLLECTION_ALL_TESTS.each do |test|
matches << test if test =~ /#{args.regex}/
end
if !matches.empty?
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(matches, GCOV_SYM, force_run: false)
@ceedling[:configurator].restore_config
else
@ceedling[:streaminator].stdout_puts("\nFound no tests matching pattern /#{args.regex}/.")
end
end
desc 'Run tests whose test path contains [dir] or [dir] substring.'
task :path, [:dir] => [:directories] do |_t, args|
matches = []
COLLECTION_ALL_TESTS.each do |test|
matches << test if File.dirname(test).include?(args.dir.tr('\\', '/'))
end
if !matches.empty?
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(matches, GCOV_SYM, force_run: false)
@ceedling[:configurator].restore_config
else
@ceedling[:streaminator].stdout_puts("\nFound no tests including the given path or path component.")
end
end
desc 'Run code coverage for changed files'
task delta: [:directories] do
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, GCOV_SYM, force_run: false)
@ceedling[:configurator].restore_config
end
# use a rule to increase efficiency for large projects
# gcov test tasks by regex
rule(/^#{GCOV_TASK_ROOT}\S+$/ => [
proc do |task_name|
test = task_name.sub(/#{GCOV_TASK_ROOT}/, '')
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" unless test.start_with?(PROJECT_TEST_FILE_PREFIX)
@ceedling[:file_finder].find_test_from_file_path(test)
end
]) do |test|
@ceedling[:rake_wrapper][:directories].invoke
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke([test.source], GCOV_SYM)
@ceedling[:configurator].restore_config
end
end
if PROJECT_USE_DEEP_DEPENDENCIES
namespace REFRESH_SYM do
task GCOV_SYM do
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].refresh_deep_dependencies
@ceedling[:configurator].restore_config
end
end
end
namespace UTILS_SYM do
desc 'Create gcov code coverage html report (must run ceedling gcov first)'
task GCOV_SYM do
if !File.directory? GCOV_ARTIFACTS_PATH
FileUtils.mkdir_p GCOV_ARTIFACTS_PATH
end
filter = @ceedling[:configurator].project_config_hash[:gcov_html_report_filter] || GCOV_FILTER_EXPR
if @ceedling[:configurator].project_config_hash[:gcov_html_report_type] == 'basic'
puts "Creating a basic html report of gcov results in #{GCOV_ARTIFACTS_FILE}..."
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_POST_REPORT_BASIC, [], filter)
@ceedling[:tool_executor].exec(command[:line], command[:options])
elsif @ceedling[:configurator].project_config_hash[:gcov_html_report_type] == 'detailed'
puts "Creating a detailed html report of gcov results in #{GCOV_ARTIFACTS_FILE}..."
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_POST_REPORT_ADVANCED, [], filter)
@ceedling[:tool_executor].exec(command[:line], command[:options])
else
puts "In your project.yml, define: \n\n:gcov:\n :html_report_type:\n\n to basic or detailed to refine this feature."
puts "For now, just creating basic."
puts "Creating a basic html report of gcov results in #{GCOV_ARTIFACTS_FILE}..."
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_POST_REPORT_BASIC, [], filter)
@ceedling[:tool_executor].exec(command[:line], command[:options])
end
puts "Done."
end
end

View File

@@ -0,0 +1,106 @@
require 'ceedling/plugin'
require 'ceedling/constants'
require 'gcov_constants'
class Gcov < Plugin
attr_reader :config
def setup
@result_list = []
@config = {
project_test_build_output_path: GCOV_BUILD_OUTPUT_PATH,
project_test_build_output_c_path: GCOV_BUILD_OUTPUT_PATH,
project_test_results_path: GCOV_RESULTS_PATH,
project_test_dependencies_path: GCOV_DEPENDENCIES_PATH,
defines_test: DEFINES_TEST + ['CODE_COVERAGE'],
collection_defines_test_and_vendor: COLLECTION_DEFINES_TEST_AND_VENDOR + ['CODE_COVERAGE'],
gcov_html_report_filter: GCOV_FILTER_EXPR
}
@plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
@coverage_template_all = @ceedling[:file_wrapper].read(File.join(@plugin_root, 'assets/template.erb'))
end
def generate_coverage_object_file(source, object)
compile_command =
@ceedling[:tool_executor].build_command_line(
TOOLS_GCOV_COMPILER,
@ceedling[:flaginator].flag_down(OPERATION_COMPILE_SYM, GCOV_SYM, source),
source,
object,
@ceedling[:file_path_utils].form_test_build_list_filepath(object)
)
@ceedling[:streaminator].stdout_puts("Compiling #{File.basename(source)} with coverage...")
@ceedling[:tool_executor].exec(compile_command[:line], compile_command[:options])
end
def post_test_fixture_execute(arg_hash)
result_file = arg_hash[:result_file]
if (result_file =~ /#{GCOV_RESULTS_PATH}/) && !@result_list.include?(result_file)
@result_list << arg_hash[:result_file]
end
end
def post_build
return unless @ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}/)
# test results
results = @ceedling[:plugin_reportinator].assemble_test_results(@result_list)
hash = {
header: GCOV_ROOT_NAME.upcase,
results: results
}
@ceedling[:plugin_reportinator].run_test_results_report(hash) do
message = ''
message = 'Unit test failures.' if results[:counts][:failed] > 0
message
end
report_per_file_coverage_results(@ceedling[:test_invoker].sources)
end
def summary
result_list = @ceedling[:file_path_utils].form_pass_results_filelist(GCOV_RESULTS_PATH, COLLECTION_ALL_TESTS)
# test results
# get test results for only those tests in our configuration and of those only tests with results on disk
hash = {
header: GCOV_ROOT_NAME.upcase,
results: @ceedling[:plugin_reportinator].assemble_test_results(result_list, boom: false)
}
@ceedling[:plugin_reportinator].run_test_results_report(hash)
end
private ###################################
def report_per_file_coverage_results(sources)
banner = @ceedling[:plugin_reportinator].generate_banner "#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY"
@ceedling[:streaminator].stdout_puts "\n" + banner
coverage_sources = sources.clone
coverage_sources.delete_if { |item| item =~ /#{CMOCK_MOCK_PREFIX}.+#{EXTENSION_SOURCE}$/ }
coverage_sources.delete_if { |item| item =~ /#{GCOV_IGNORE_SOURCES.join('|')}#{EXTENSION_SOURCE}$/ }
coverage_sources.each do |source|
basename = File.basename(source)
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_REPORT, [], [basename])
shell_results = @ceedling[:tool_executor].exec(command[:line], command[:options])
coverage_results = shell_results[:output]
if coverage_results.strip =~ /(File\s+'#{Regexp.escape(source)}'.+$)/m
report = Regexp.last_match(1).lines.to_a[1..-1].map { |line| basename + ' ' + line }.join('')
@ceedling[:streaminator].stdout_puts(report + "\n\n")
end
end
end
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}/)
}

View File

@@ -0,0 +1,18 @@
GCOV_ROOT_NAME = 'gcov'.freeze
GCOV_TASK_ROOT = GCOV_ROOT_NAME + ':'
GCOV_SYM = GCOV_ROOT_NAME.to_sym
GCOV_BUILD_PATH = File.join(PROJECT_BUILD_ROOT, GCOV_ROOT_NAME)
GCOV_BUILD_OUTPUT_PATH = File.join(GCOV_BUILD_PATH, "out")
GCOV_RESULTS_PATH = File.join(GCOV_BUILD_PATH, "results")
GCOV_DEPENDENCIES_PATH = File.join(GCOV_BUILD_PATH, "dependencies")
GCOV_ARTIFACTS_PATH = File.join(PROJECT_BUILD_ARTIFACTS_ROOT, GCOV_ROOT_NAME)
GCOV_ARTIFACTS_FILE = File.join(GCOV_ARTIFACTS_PATH, "GcovCoverageResults.html")
GCOV_IGNORE_SOURCES = %w(unity cmock cexception).freeze
GCOV_FILTER_EXPR = '^vendor.*|^build.*|^test.*|^lib.*'