Skip to content

Commit

Permalink
Rewrite CodeCoverage.cmake
Browse files Browse the repository at this point in the history
* Rewrite CodeCoverage.cmake to support all gcovr formats
* Add .clangd to .gitignore
* Set default coverage report format html-details
  • Loading branch information
Bronek committed Jan 3, 2024
1 parent 7491d54 commit da6dd57
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 131 deletions.
1 change: 1 addition & 0 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ jobs:
-DCMAKE_C_COMPILER=gcc \
-DCMAKE_CXX_COMPILER=g++ \
-DCODE_COVERAGE=True \
-DCODE_COVERAGE_REPORT_FORMAT=xml \
..
- name: build all targets
shell: bash
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.idea/
.idea_modules/
.build*/
.clangd
*.iws
*.swp
*.swo
Expand Down
250 changes: 121 additions & 129 deletions cmake/CodeCoverage.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@
# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR)
# Example:
# set(COVERAGE_EXCLUDES "dir1/*")
# setup_target_for_coverage_gcovr_html(
# setup_target_for_coverage_gcovr(
# NAME coverage
# EXECUTABLE testrunner
# FORMAT html-details
# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src"
# EXCLUDE "dir2/*")
#
Expand Down Expand Up @@ -95,7 +96,7 @@ elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
find_program( LLVMCOV_PATH llvm-cov )
endif()
if (NOT LLVMCOV_PATH)
message(FATAL_ERROR "llvm-cov tool not found! Aborting...")
message(FATAL_ERROR "Could not find llvm-cov tool! Aborting...")
endif()
set(GCOV_TOOL "${LLVMCOV_PATH} gcov")
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
Expand All @@ -104,7 +105,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
endif()

if(NOT GCOV_TOOL)
message(FATAL_ERROR "gcov tool not found! Aborting...")
message(FATAL_ERROR "Could not find gcov or llvm-cov tool! Aborting...")
endif() # NOT GCOV_TOOL

# Check supported compiler (Clang, GNU and Flang)
Expand Down Expand Up @@ -176,26 +177,35 @@ endif()
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# setup_target_for_coverage_gcovr_xml(
# setup_target_for_coverage_gcovr(
# NAME ctest_coverage # New target name
# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES executable_target # Dependencies to build first
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# FORMAT "cobertura" # Output format, one of:
# # xml cobertura sonarqube json-summary
# # json-details coveralls csv txt
# # html-single html-nested html-details
# # (xml is an alias to cobertura;
# # if no format is set, defaults to xml;
# # if any of the supported formats is
# # set in GCOVR_ADDITIONAL_ARGS options
# # it will always take priority)
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
# # to BASE_DIRECTORY, with CMake 3.4+)
# )
# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
# GCVOR command.
function(setup_target_for_coverage_gcovr_xml)
function(setup_target_for_coverage_gcovr)
set(options NONE)
set(oneValueArgs BASE_DIRECTORY NAME)
set(oneValueArgs BASE_DIRECTORY NAME FORMAT)
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

if(NOT GCOVR_PATH)
message(FATAL_ERROR "gcovr not found! Aborting...")
endif() # NOT GCOVR_PATH
message(FATAL_ERROR "Could not find gcovr tool! Aborting...")
endif()

# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
if(DEFINED Coverage_BASE_DIRECTORY)
Expand All @@ -204,109 +214,87 @@ function(setup_target_for_coverage_gcovr_xml)
set(BASEDIR ${PROJECT_SOURCE_DIR})
endif()

# Collect excludes (CMake 3.4+: Also compute absolute paths)
set(GCOVR_EXCLUDES "")
foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
if(CMAKE_VERSION VERSION_GREATER 3.4)
get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
if("--txt" IN_LIST GCOVR_ADDITIONAL_ARGS)
set(Coverage_FORMAT txt)
elseif("--csv" IN_LIST GCOVR_ADDITIONAL_ARGS)
set(Coverage_FORMAT csv)
elseif(("--cobertura" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("--cobertura-pretty" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("--xml" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("--xml-pretty" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("-x" IN_LIST GCOVR_ADDITIONAL_ARGS))
set(Coverage_FORMAT cobertura)
elseif("--sonarqube" IN_LIST GCOVR_ADDITIONAL_ARGS)
set(Coverage_FORMAT sonarqube)
elseif(("--json-summary" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("--json-summary-pretty" IN_LIST GCOVR_ADDITIONAL_ARGS))
set(Coverage_FORMAT json-summary)
elseif(("--json" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("--json-pretty" IN_LIST GCOVR_ADDITIONAL_ARGS))
set(Coverage_FORMAT json-details)
elseif(("--coveralls" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("--coveralls-pretty" IN_LIST GCOVR_ADDITIONAL_ARGS))
set(Coverage_FORMAT coveralls)
elseif(("--html" IN_LIST GCOVR_ADDITIONAL_ARGS)
OR ("--html-self-contained" IN_LIST GCOVR_ADDITIONAL_ARGS))
set(Coverage_FORMAT html-single)
elseif("--html-details" IN_LIST GCOVR_ADDITIONAL_ARGS)
set(Coverage_FORMAT html-details)
elseif("--html-nested" IN_LIST GCOVR_ADDITIONAL_ARGS)
set(Coverage_FORMAT html-nested)
else()
if(NOT DEFINED Coverage_FORMAT)
set(Coverage_FORMAT xml)
endif()
list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
endforeach()
list(REMOVE_DUPLICATES GCOVR_EXCLUDES)

# Combine excludes to several -e arguments
set(GCOVR_EXCLUDE_ARGS "")
foreach(EXCLUDE ${GCOVR_EXCLUDES})
list(APPEND GCOVR_EXCLUDE_ARGS "-e")
list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
endforeach()

# Set up commands which will be run to generate coverage data
# Run tests
set(GCOVR_XML_EXEC_TESTS_CMD
${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
)
# Running gcovr
set(GCOVR_XML_CMD
${GCOVR_PATH}
--gcov-executable ${GCOV_TOOL}
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
--output ${Coverage_NAME}.xml
--xml
-r ${BASEDIR}
${GCOVR_ADDITIONAL_ARGS}
${GCOVR_EXCLUDE_ARGS}
--object-directory=${PROJECT_BINARY_DIR}
)

if(CODE_COVERAGE_VERBOSE)
message(STATUS "Executed command report")

message(STATUS "Command to run tests: ")
string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}")
message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}")

message(STATUS "Command to generate gcovr XML coverage data: ")
string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}")
message(STATUS "${GCOVR_XML_CMD_SPACED}")
if ((Coverage_FORMAT STREQUAL "cobertura")
OR (Coverage_FORMAT STREQUAL "xml"))
list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura-pretty)
set(Coverage_FORMAT cobertura) # overwrite xml
elseif(Coverage_FORMAT STREQUAL "sonarqube")
list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube)
elseif(Coverage_FORMAT STREQUAL "json-summary")
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty)
elseif(Coverage_FORMAT STREQUAL "json-details")
list(APPEND GCOVR_ADDITIONAL_ARGS --json-pretty)
elseif(Coverage_FORMAT STREQUAL "coveralls")
list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls-pretty)
elseif(Coverage_FORMAT STREQUAL "csv")
list(APPEND GCOVR_ADDITIONAL_ARGS --csv)
elseif(Coverage_FORMAT STREQUAL "txt")
list(APPEND GCOVR_ADDITIONAL_ARGS --txt)
elseif(Coverage_FORMAT STREQUAL "html-single")
list(APPEND GCOVR_ADDITIONAL_ARGS --html --html-self-contained)
elseif(Coverage_FORMAT STREQUAL "html-nested")
list(APPEND GCOVR_ADDITIONAL_ARGS --html --html-nested)
elseif(Coverage_FORMAT STREQUAL "html-details")
list(APPEND GCOVR_ADDITIONAL_ARGS --html --html-details)
else()
message(FATAL_ERROR "Unsupported output style ${Coverage_FORMAT}! Aborting...")
endif()
endif()

add_custom_target(${Coverage_NAME}
COMMAND ${GCOVR_XML_EXEC_TESTS_CMD}
COMMAND ${GCOVR_XML_CMD}

BYPRODUCTS ${Coverage_NAME}.xml
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
VERBATIM # Protect arguments to commands
COMMENT "Running gcovr to produce Cobertura code coverage report."
)

# Show info where to find the report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
)
endfunction() # setup_target_for_coverage_gcovr_xml

# Defines a target for running and collection code coverage information
# Builds dependencies, runs the given executable and outputs reports.
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# setup_target_for_coverage_gcovr_html(
# NAME ctest_coverage # New target name
# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES executable_target # Dependencies to build first
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
# # to BASE_DIRECTORY, with CMake 3.4+)
# )
# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
# GCVOR command.
function(setup_target_for_coverage_gcovr_html)
set(options NONE)
set(oneValueArgs BASE_DIRECTORY NAME)
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

if(NOT GCOVR_PATH)
message(FATAL_ERROR "gcovr not found! Aborting...")
endif() # NOT GCOVR_PATH

# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
if(DEFINED Coverage_BASE_DIRECTORY)
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
if("--output" IN_LIST GCOVR_ADDITIONAL_ARGS)
message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting...")
else()
set(BASEDIR ${PROJECT_SOURCE_DIR})
endif()

# Optionally set --html-details
if((NOT "--html-details" IN_LIST GCOVR_ADDITIONAL_ARGS)
AND (NOT "--html-nested" IN_LIST GCOVR_ADDITIONAL_ARGS)
AND (NOT "--html" IN_LIST GCOVR_ADDITIONAL_ARGS))
list(APPEND GCOVR_ADDITIONAL_ARGS --html-details)
if((Coverage_FORMAT STREQUAL "html-details")
OR (Coverage_FORMAT STREQUAL "html-nested"))
set(GCOVR_OUTPUT_FILE ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html)
set(GCOVR_CREATE_FOLDER ${PROJECT_BINARY_DIR}/${Coverage_NAME})
elseif(Coverage_FORMAT STREQUAL "html-single")
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.html)
elseif((Coverage_FORMAT STREQUAL "json-summary")
OR (Coverage_FORMAT STREQUAL "json-details")
OR (Coverage_FORMAT STREQUAL "coveralls"))
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.json)
elseif(Coverage_FORMAT STREQUAL "txt")
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt)
elseif(Coverage_FORMAT STREQUAL "csv")
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv)
else()
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
endif()
list(APPEND GCOVR_ADDITIONAL_ARGS --output ${GCOVR_OUTPUT_FILE})
endif()

# Collect excludes (CMake 3.4+: Also compute absolute paths)
Expand All @@ -328,20 +316,23 @@ function(setup_target_for_coverage_gcovr_html)

# Set up commands which will be run to generate coverage data
# Run tests
set(GCOVR_HTML_EXEC_TESTS_CMD
set(GCOVR_EXEC_TESTS_CMD
${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
)

# Create folder
set(GCOVR_HTML_FOLDER_CMD
${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME}
)
if(DEFINED GCOVR_CREATE_FOLDER)
set(GCOVR_FOLDER_CMD
${CMAKE_COMMAND} -E make_directory ${GCOVR_CREATE_FOLDER})
else()
set(GCOVR_FOLDER_CMD echo) # dummy
endif()

# Running gcovr
set(GCOVR_HTML_CMD
set(GCOVR_CMD
${GCOVR_PATH}
--gcov-executable ${GCOV_TOOL}
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
--output ${Coverage_NAME}/index.html
--html
-r ${BASEDIR}
${GCOVR_ADDITIONAL_ARGS}
${GCOVR_EXCLUDE_ARGS}
Expand All @@ -352,37 +343,38 @@ function(setup_target_for_coverage_gcovr_html)
message(STATUS "Executed command report")

message(STATUS "Command to run tests: ")
string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}")
message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}")
string(REPLACE ";" " " GCOVR_EXEC_TESTS_CMD_SPACED "${GCOVR_EXEC_TESTS_CMD}")
message(STATUS "${GCOVR_EXEC_TESTS_CMD_SPACED}")

message(STATUS "Command to create a folder: ")
string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}")
message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}")
if(NOT GCOVR_FOLDER_CMD STREQUAL "echo")
message(STATUS "Command to create a folder: ")
string(REPLACE ";" " " GCOVR_FOLDER_CMD_SPACED "${GCOVR_FOLDER_CMD}")
message(STATUS "${GCOVR_FOLDER_CMD_SPACED}")
endif()

message(STATUS "Command to generate gcovr HTML coverage data: ")
string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}")
message(STATUS "${GCOVR_HTML_CMD_SPACED}")
message(STATUS "Command to generate gcovr coverage data: ")
string(REPLACE ";" " " GCOVR_CMD_SPACED "${GCOVR_CMD}")
message(STATUS "${GCOVR_CMD_SPACED}")
endif()

add_custom_target(${Coverage_NAME}
COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD}
COMMAND ${GCOVR_HTML_FOLDER_CMD}
COMMAND ${GCOVR_HTML_CMD}
COMMAND ${GCOVR_EXEC_TESTS_CMD}
COMMAND ${GCOVR_FOLDER_CMD}
COMMAND ${GCOVR_CMD}

BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory
BYPRODUCTS ${GCOVR_OUTPUT_FILE}
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
VERBATIM # Protect arguments to commands
COMMENT "Running gcovr to produce HTML code coverage report."
COMMENT "Running gcovr to produce code coverage report."
)

# Show info where to find the report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
COMMENT "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}"
)

endfunction() # setup_target_for_coverage_gcovr_html
endfunction() # setup_target_for_coverage_gcovr

function(append_coverage_compiler_flags)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
Expand Down
11 changes: 9 additions & 2 deletions cmake/MarketCov.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ if (CODE_COVERAGE)
message(FATAL_ERROR "Cannot find gcovr, aborting ...")
endif()

set (GCOVR_ADDITIONAL_ARGS --cobertura-pretty --exclude-noncode-lines --exclude-unreachable-branches -s)
if (DEFINED CODE_COVERAGE_REPORT_FORMAT)
set(CODE_COVERAGE_FORMAT ${CODE_COVERAGE_REPORT_FORMAT})
else()
set(CODE_COVERAGE_FORMAT html-details)
endif()

set (GCOVR_ADDITIONAL_ARGS --exclude-throw-branches -s)

setup_target_for_coverage_gcovr_xml(
setup_target_for_coverage_gcovr(
NAME coverage_report
FORMAT ${CODE_COVERAGE_FORMAT}
EXECUTABLE tests
EXECUTABLE_ARGS -r console
EXCLUDE "tests" "third_party"
Expand Down

0 comments on commit da6dd57

Please sign in to comment.