From 5780b58f91ad4607b30ece77ed0276f69dbb3743 Mon Sep 17 00:00:00 2001 From: Aditya Kasar Date: Thu, 5 Dec 2024 22:24:23 +0530 Subject: [PATCH] chore: CPP CI unit test coverage report (#232) Co-authored-by: kschrief --- languages/cpp/src/shared/CMakeLists.txt | 6 +- .../cpp/src/shared/cmake/CodeCoverage.cmake | 163 ++++++++++++++++++ 2 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 languages/cpp/src/shared/cmake/CodeCoverage.cmake diff --git a/languages/cpp/src/shared/CMakeLists.txt b/languages/cpp/src/shared/CMakeLists.txt index a60211ec..43e7ebcd 100644 --- a/languages/cpp/src/shared/CMakeLists.txt +++ b/languages/cpp/src/shared/CMakeLists.txt @@ -26,7 +26,7 @@ set(FIREBOLT_LOGLEVEL "Info" CACHE STRING "Log level to be enabled") option(FIREBOLT_ENABLE_STATIC_LIB "Create Firebolt library as Static library" OFF) option(ENABLE_TESTS "Build openrpc native test" ON) option(ENABLE_UNIT_TESTS "Enable unit test" ON) -option(ENABLE_COVERAGE "Enable code coverage build." OFF) +option(ENABLE_COVERAGE "Enable code coverage build." ON) if (FIREBOLT_ENABLE_STATIC_LIB) set(FIREBOLT_LIBRARY_TYPE STATIC) @@ -100,14 +100,14 @@ message("CMAKE_PREFIX_PATH: " ${CMAKE_PREFIX_PATH}) find_package(WPEFramework CONFIG REQUIRED) -if (ENABLE_TESTS AND ENABLE_COVERAGE) +if (ENABLE_UNIT_TESTS AND ENABLE_COVERAGE) include(CodeCoverage) append_coverage_compiler_flags() endif() add_subdirectory(src) -if (ENABLE_TESTS) +if (ENABLE_TESTS OR ENABLE_UNIT_TESTS) enable_testing() add_subdirectory(test) endif() diff --git a/languages/cpp/src/shared/cmake/CodeCoverage.cmake b/languages/cpp/src/shared/cmake/CodeCoverage.cmake new file mode 100644 index 00000000..49513ad6 --- /dev/null +++ b/languages/cpp/src/shared/cmake/CodeCoverage.cmake @@ -0,0 +1,163 @@ + +include(CMakeParseArguments) + +if(CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(IS_CLANG TRUE) +else() + set(IS_CLANG FALSE) +endif() +if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(IS_GCC TRUE) +else() + set(IS_GCC FALSE) +endif() + +if(NOT ${IS_CLANG} AND NOT ${IS_GCC}) + message(FATAL_ERROR "Compiler is not gcc/clang! Aborting...") +endif() + +find_program(GCOVR_PATH gcovr) + +set(COVERAGE_COMPILER_FLAGS "-g3 -O0 --coverage") +set(CMAKE_CXX_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} FORCE) +set(CMAKE_C_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} FORCE) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "-lgcov" FORCE) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "" FORCE) +mark_as_advanced( + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE) + +if(NOT + CMAKE_BUILD_TYPE + STREQUAL + "Debug") + message(WARNING "Cov results with non-Debug build may be misleading") +endif() + +if(${IS_GCC}) + link_libraries(gcov) +endif() + +function(setup_target_for_coverage_gcovr_html) + message("Inside setup_target_for_coverage_gcovr_html()") + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs + EXCLUDE + EXECUTABLE + EXECUTABLE_ARGS + DEPENDENCIES) + cmake_parse_arguments( + Coverage + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN}) + + # 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) + else() + 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}) + 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_HTML_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}) + + # Running gcovr + set(GCOVR_EXTRA_FLAGS + --json-summary + --json-summary-pretty + --html-theme + github.green + --html-title "FireboltSDK Coverage Report" + ) + set(GCOVR_HTML_CMD + ${GCOVR_PATH} + ${GCOVR_EXTRA_FLAGS} + --html + ${Coverage_NAME}/index.html + --html-details + --json-summary + ${Coverage_NAME}/summary.json + --json-summary-pretty + --cobertura + ${Coverage_NAME}/coverage.cobertura.xml + --cobertura-pretty + --decisions + -r + ${BASEDIR} + ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} + --object-directory=${PROJECT_BINARY_DIR}) + + add_custom_target( + ${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} || true + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report + # directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML 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." + ) +endfunction() # setup_target_for_coverage_gcovr_html + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" + PARENT_SCOPE) + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" + PARENT_SCOPE) + message( + STATUS + "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}" + ) +endfunction()