Add code coverage measurement feature
The COVERAGE CMake option enables the code coverage features of the
compiler and adds additional targets for generating LCOV coverage info
files and HTML based coverage reports.
The feature is currently only supported by the GCC compiler.
Change-Id: I4481a9ac3c7a4b2d3a94d6cb26579b6bd14f3557
Signed-off-by: Imre Kis <imre.kis@arm.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d7c20b1..7b14ab7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,6 +24,7 @@
set(UNIT_TEST_COMMON_SOURCES ${CMAKE_CURRENT_LIST_DIR}/common/main.cpp)
set(CMAKE_CXX_STANDARD 11)
set(CLANG_LIBRARY_PATH_HELP "libclang directory for c-picker")
+option(COVERAGE "Enable code coverage measurement" OFF)
# Checking TF-A
if (NOT TF_A_PATH)
@@ -108,6 +109,42 @@
include_directories(${CppUTest_INCLUDE_DIRS})
link_libraries(${CppUTest_LIBRARIES})
+# Coverage
+if (COVERAGE)
+ include(Coverage)
+
+ set(COVERAGE_FILE "coverage.info")
+ set(TF_A_COVERAGE_FILE "trusted-firmware-a-coverage.info")
+ set(TF_A_UT_COVERAGE_FILE "tf-a-unit-tests-coverage.info")
+ set(TF_A_COVERAGE_REPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/trusted-firmware-a-coverage")
+ set(TF_A_UT_COVERAGE_REPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/tf-a-unit-tests-coverage")
+
+ # Collecting coverage
+ coverage_generate(NAME "Unit test" OUTPUT_FILE ${COVERAGE_FILE})
+
+ # Filtering TF-A and unit test coverage
+ coverage_filter(
+ INPUT_FILE ${COVERAGE_FILE}
+ OUTPUT_FILE ${TF_A_COVERAGE_FILE}
+ INCLUDE_DIRECTORY ${TF_A_PATH}
+ )
+ coverage_filter(
+ INPUT_FILE ${COVERAGE_FILE}
+ OUTPUT_FILE ${TF_A_UT_COVERAGE_FILE}
+ INCLUDE_DIRECTORY ${TF_A_UNIT_TESTS_PATH}
+ )
+
+ # Coverage reports
+ coverage_generate_report(
+ INPUT_FILE trusted-firmware-a-coverage.info
+ OUTPUT_DIRECTORY ${TF_A_COVERAGE_REPORT_DIR}
+ )
+ coverage_generate_report(
+ INPUT_FILE tf-a-unit-tests-coverage.info
+ OUTPUT_DIRECTORY ${TF_A_UT_COVERAGE_REPORT_DIR}
+ )
+endif()
+
# Project level include directory
include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
diff --git a/cmake/Coverage.cmake b/cmake/Coverage.cmake
new file mode 100644
index 0000000..5bae2df
--- /dev/null
+++ b/cmake/Coverage.cmake
@@ -0,0 +1,122 @@
+#
+# Copyright (c) 2020, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+include_guard(DIRECTORY)
+
+# Checking GCC
+if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ message(FATAL_ERROR "Coverage measurement is only supported when using GCC")
+endif()
+
+# Checking lcov
+find_program(LCOV_COMMAND "lcov")
+if (NOT LCOV_COMMAND)
+ message(FATAL_ERROR "Please install lcov")
+endif()
+
+# Checking c-picker-coverage-mapper
+find_program(CPICKER_COVERAGE_MAPPER_COMMAND "c-picker-coverage-mapper")
+if (NOT CPICKER_COVERAGE_MAPPER_COMMAND)
+ message(FATAL_ERROR "Please install c-picker-coverage-mapper using pip (part of c-picker)")
+endif()
+
+# Checking genhtml
+find_program(GENHTML_COMMAND "genhtml")
+if (NOT GENHTML_COMMAND)
+ message(FATAL_ERROR "Please install genhtml with genhtml (part of lcov)")
+endif()
+
+# Including this file enables code coverage measurement by adding the necessary compiler and
+# linker flags.
+set(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
+set(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage -fno-exceptions")
+set(CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage")
+
+# Adding custom targets
+add_custom_target(coverage)
+add_custom_target(coverage_report)
+
+# Generates LCOV coverage info file by processing the .gcda and .gcno files.
+# The function also maps coverage of the c-picker generated files to the original source lines
+# Global dependencies:
+# CPICKER_CACHE_PATH: root directory of the c-picker generated files
+function(coverage_generate)
+ set(_OPTIONS_ARGS)
+ set(_ONE_VALUE_ARGS NAME OUTPUT_FILE)
+ set(_MULTI_VALUE_ARGS)
+ cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+ set(TEST_NAME ${_MY_PARAMS_NAME})
+ set(TEMP_FILE temp_${_MY_PARAMS_OUTPUT_FILE})
+ set(OUTPUT_FILE ${_MY_PARAMS_OUTPUT_FILE})
+
+ # This is a workaround for running test target before expecting to have coverage.
+ # Currently there's no other way to set a dependency for this target.
+ add_custom_command(
+ OUTPUT test.stamp
+ COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR} -- test
+ COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/test.stamp
+ )
+
+ # Collecting information from .gcda and .gcno files into an lcov .info file
+ # Mapping c-picker generated files' coverage info to the original source lines
+ add_custom_command(
+ OUTPUT ${TEMP_FILE} ${OUTPUT_FILE}
+ COMMAND ${LCOV_COMMAND}
+ --capture
+ --test-name ${TEST_NAME}
+ --directory ${CMAKE_CURRENT_BINARY_DIR}
+ --base-directory ${TF_A_PATH}
+ --output-file ${TEMP_FILE}
+ COMMAND ${CPICKER_COVERAGE_MAPPER_COMMAND}
+ --input ${TEMP_FILE}
+ --output ${OUTPUT_FILE}
+ --mapping-path ${CPICKER_CACHE_PATH}
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test.stamp
+ )
+endfunction()
+
+# Filters coverage info of files from the matching directory
+function(coverage_filter)
+ set(_OPTIONS_ARGS)
+ set(_ONE_VALUE_ARGS INPUT_FILE OUTPUT_FILE INCLUDE_DIRECTORY)
+ set(_MULTI_VALUE_ARGS)
+ cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+ # The pattern must be an absolute path ending with an asterisk
+ get_filename_component(INCLUDE_DIRECTORY_ABSPATH "${_MY_PARAMS_INCLUDE_DIRECTORY}" ABSOLUTE)
+ set(INCLUDE_DIRECTORY_ABSPATH "${INCLUDE_DIRECTORY_ABSPATH}/*")
+
+ add_custom_command(
+ OUTPUT ${_MY_PARAMS_OUTPUT_FILE}
+ COMMAND ${LCOV_COMMAND}
+ --extract ${_MY_PARAMS_INPUT_FILE} \"${INCLUDE_DIRECTORY_ABSPATH}\"
+ --output-file ${_MY_PARAMS_OUTPUT_FILE}
+ DEPENDS ${_MY_PARAMS_INPUT_FILE}
+ )
+
+ add_custom_target(coverage_target_${_MY_PARAMS_OUTPUT_FILE} DEPENDS ${_MY_PARAMS_OUTPUT_FILE})
+ add_dependencies(coverage coverage_target_${_MY_PARAMS_OUTPUT_FILE})
+endfunction()
+
+# Generated an HTML report from the LCOV info file
+function(coverage_generate_report)
+ set(_OPTIONS_ARGS)
+ set(_ONE_VALUE_ARGS INPUT_FILE OUTPUT_DIRECTORY)
+ set(_MULTI_VALUE_ARGS)
+ cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+ add_custom_command(
+ OUTPUT ${_MY_PARAMS_OUTPUT_DIRECTORY}
+ COMMAND genhtml ${_MY_PARAMS_INPUT_FILE}
+ --show-details
+ --output-directory ${_MY_PARAMS_OUTPUT_DIRECTORY}
+ DEPENDS ${_MY_PARAMS_INPUT_FILE}
+ )
+
+ add_custom_target(coverage_report_target_${_MY_PARAMS_INPUT_FILE} DEPENDS ${_MY_PARAMS_OUTPUT_DIRECTORY})
+ add_dependencies(coverage_report coverage_report_target_${_MY_PARAMS_INPUT_FILE})
+endfunction()