add tinyusb

This commit is contained in:
Joey Castillo
2021-08-28 12:50:18 -04:00
parent c9e00b83bb
commit 39a5c822a2
1054 changed files with 188322 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
ceedling-gcov
=============
# Plugin Overview
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 and/or XML reports as a
utility. The normal gcov plugin _must_ be run first for this report to generate.
## Installation
Gcovr can be installed via pip like so:
```
pip install gcovr
```
## Configuration
The gcov plugin supports configuration options via your `project.yml` provided
by Ceedling.
Generation of HTML reports may be enabled or disabled with the following
config. Set to `true` to enable or set to `false` to disable.
```
:gcov:
:html_report: true
```
Generation of XML reports may be enabled or disabled with the following
config. Set to `true` to enable or set to `false` to disable.
```
:gcov:
:xml_report: true
```
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
```
There are a number of options to control which files are considered part of
the coverage report. Most often, we only care about coverage on our source code, and not
on tests or automatically generated mocks, runners, etc. However, there are times
where this isn't true... or there are times where we've moved ceedling's directory
structure so that the project file isn't at the root of the project anymore. In these
cases, you may need to tweak the following:
```
:gcov:
:report_root: "."
:report_exclude: "^build|^vendor|^test|^support"
:report_include: "^src"
```
One important note about html_report_root: gcovr will only take a single root folder, unlike
Ceedling's ability to take as many as you like. So you will need to choose a folder which is
a superset of ALL the folders you want, and then use the include or exclude options to set up
patterns of files to pay attention to or ignore. It's not ideal, but it works.
Finally, there are a number of settings which can be specified in order to adjust the
default behaviors of gcov:
```
:gcov:
:html_medium_threshold: 75
:html_high_threshold: 90
:fail_under_line: 30
:fail_under_branch: 30
```
These HTML and XML reports will be found in `build/artifacts/gcov`.
## Example Usage
```
ceedling gcov:all utils: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,73 @@
---
: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
- -DCODE_COVERAGE
- -c "${1}"
- -o "${2}"
:gcov_linker:
:executable: gcc
:arguments:
- -fprofile-arcs
- -ftest-coverage
- ${1}
- -o ${2}
- ${3}
: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
- ${1}
- --html
- -o GcovCoverageResults.html
:gcov_post_report_basic:
:executable: gcovr
:optional: TRUE
:arguments:
- -p
- -b
- ${1}
- --html
- -o "$": GCOV_ARTIFACTS_FILE
:gcov_post_report_advanced:
:executable: gcovr
:optional: TRUE
:arguments:
- -p
- -b
- ${1}
- --html
- --html-details
- -o "$": GCOV_ARTIFACTS_FILE
:gcov_post_report_xml:
:executable: gcovr
:optional: TRUE
:arguments:
- -p
- -b
- ${1}
- --xml
- -o "$": GCOV_ARTIFACTS_FILE_XML
...

View File

@@ -0,0 +1,220 @@
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|
lib_args = @ceedling[:test_invoker].convert_libraries_to_arguments()
@ceedling[:generator].generate_executable_file(
TOOLS_GCOV_LINKER,
GCOV_SYM,
bin_file.prerequisites,
bin_file.name,
lib_args,
@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
def gcov_args_builder(opts)
args = ""
args += "-r \"#{opts[:gcov_report_root] || '.'}\" "
args += "-f \"#{opts[:gcov_report_include]}\" " unless opts[:gcov_report_include].nil?
args += "-e \"#{opts[:gcov_report_exclude] || GCOV_FILTER_EXCLUDE}\" "
[ :gcov_fail_under_line, :gcov_fail_under_branch, :gcov_html_medium_threshold, :gcov_html_high_threshold].each do |opt|
args += "--#{opt.to_s.gsub('_','-').sub(/:?gcov-/,'')} #{opts[opt]} " unless opts[opt].nil?
end
return args
end
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
args = gcov_args_builder(@ceedling[:configurator].project_config_hash)
if @ceedling[:configurator].project_config_hash[:gcov_html_report].nil?
puts "In your project.yml, define: \n\n:gcov:\n :html_report:\n\n to true or false to refine this feature."
puts "For now, assumimg you want an html report generated."
html_enabled = true
else
html_enabled = @ceedling[:configurator].project_config_hash[:gcov_html_report]
end
if @ceedling[:configurator].project_config_hash[:gcov_xml_report].nil?
puts "In your project.yml, define: \n\n:gcov:\n :xml_report:\n\n to true or false to refine this feature."
puts "For now, assumimg you do not want an xml report generated."
xml_enabled = false
else
xml_enabled = @ceedling[:configurator].project_config_hash[:gcov_xml_report]
end
if html_enabled
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, [], args)
@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, [], args)
@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, [], args)
@ceedling[:tool_executor].exec(command[:line], command[:options])
end
end
if xml_enabled
puts "Creating an xml report of gcov results in #{GCOV_ARTIFACTS_FILE_XML}..."
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_POST_REPORT_XML, [], filter)
@ceedling[:tool_executor].exec(command[:line], command[:options])
end
puts "Done."
end
end

View File

@@ -0,0 +1,113 @@
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'],
gcov_html_report_filter: GCOV_FILTER_EXCLUDE
}
@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)
lib_args = @ceedling[:test_invoker].convert_libraries_to_arguments()
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),
lib_args
)
@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
COLLECTION_ALL_SOURCE.each do |source|
unless coverage_sources.include?(source)
@ceedling[:streaminator].stdout_puts("Could not find coverage results for " + source + "\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,19 @@
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_ARTIFACTS_FILE_XML = File.join(GCOV_ARTIFACTS_PATH, "GcovCoverageResults.xml")
GCOV_IGNORE_SOURCES = %w(unity cmock cexception).freeze
GCOV_FILTER_EXCLUDE = '^vendor.*|^build.*|^test.*|^lib.*'