From a8bae96ad43b60e7be9112009af19b1b12c9193f Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 21 Dec 2023 15:08:32 +0000 Subject: [PATCH] Add coverage_report target (#1058) --- .codecov.yml | 11 + .github/actions/build_clio/action.yml | 6 +- .github/actions/generate/action.yml | 5 +- .github/workflows/build.yml | 63 ++++ CMake/CodeCoverage.cmake | 440 ++++++++++++++++++++++++++ CMake/Coverage.cmake | 125 -------- CMakeLists.txt | 35 +- README.md | 49 +++ 8 files changed, 602 insertions(+), 132 deletions(-) create mode 100644 .codecov.yml create mode 100644 CMake/CodeCoverage.cmake delete mode 100644 CMake/Coverage.cmake diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..3fc7bfb1 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: + target: 50% + threshold: 2% + + patch: + default: + target: 20% # Need to bump this number https://docs.codecov.com/docs/commit-status#patch-status + threshold: 2% diff --git a/.github/actions/build_clio/action.yml b/.github/actions/build_clio/action.yml index 794c5722..3316d2ed 100644 --- a/.github/actions/build_clio/action.yml +++ b/.github/actions/build_clio/action.yml @@ -1,5 +1,9 @@ name: Build clio description: Build clio in build directory +inputs: + target: + description: Build target name + default: all runs: using: composite steps: @@ -11,4 +15,4 @@ runs: shell: bash run: | cd build - cmake --build . --parallel ${{ steps.number_of_threads.outputs.threads_number }} + cmake --build . --parallel ${{ steps.number_of_threads.outputs.threads_number }} --target ${{ inputs.target }} diff --git a/.github/actions/generate/action.yml b/.github/actions/generate/action.yml index c546a416..bd0d9e0a 100644 --- a/.github/actions/generate/action.yml +++ b/.github/actions/generate/action.yml @@ -12,6 +12,9 @@ inputs: description: Build type for third-party libraries and clio. Could be 'Release', 'Debug' required: true default: 'Release' + extra_cmake_args: + description: Additional cmake options + default: null runs: using: composite steps: @@ -33,4 +36,4 @@ runs: BUILD_TYPE: "${{ inputs.build_type }}" run: | cd build - cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} .. -G Ninja + cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} ${{ inputs.extra_cmake_args }} .. -G Ninja diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca2db785..44f1e319 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,6 +97,7 @@ jobs: ccache_cache_miss_rate: ${{ steps.ccache_stats.outputs.miss_rate }} test: + name: Run Tests needs: build strategy: fail-fast: false @@ -117,3 +118,65 @@ jobs: run: | chmod +x ./clio_tests ./clio_tests --gtest_filter="-BackendCassandraBaseTest*:BackendCassandraTest*:BackendCassandraFactoryTestWithDB*" + + coverage: + name: Run Coverage Report + needs: lint + strategy: + fail-fast: false + matrix: + include: + - os: Linux + container: + image: rippleci/clio_ci:latest + runs-on: [self-hosted, "${{ matrix.os }}"] + container: ${{ matrix.container }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Prepare runner + uses: ./.github/actions/prepare_runner + with: + disable_ccache: true + + - name: Install gcovr + run: | + pip install gcovr + + - name: Setup conan + uses: ./.github/actions/setup_conan + id: conan + + - name: Run conan and cmake + uses: ./.github/actions/generate + with: + conan_profile: ${{ steps.conan.outputs.conan_profile }} + conan_cache_hit: false + build_type: 'Debug' + extra_cmake_args: >- + -Dcoverage=ON + -DCODE_COVERAGE_TESTS_ARGS="--gtest_filter=-BackendCassandra*" + -DCODE_COVERAGE_REPORT_FORMAT=xml + + - name: Run tests and coverage report + uses: ./.github/actions/build_clio + with: + target: coverage_report + + - name: Archive coverage report + uses: actions/upload-artifact@v3 + with: + name: coverage-report.xml + path: build/coverage_report.xml + retention-days: 30 + + - name: Upload coverage report + uses: codecov/codecov-action@v3 + with: + files: build/coverage_report.xml + fail_ci_if_error: true + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/CMake/CodeCoverage.cmake b/CMake/CodeCoverage.cmake new file mode 100644 index 00000000..52f95faa --- /dev/null +++ b/CMake/CodeCoverage.cmake @@ -0,0 +1,440 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o for --xml and --html output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# 2023-12-15, Bronek Kozicki +# - remove setup_target_for_coverage_lcov (slow) and setup_target_for_coverage_fastcov (no support for Clang) +# - fix Clang support by adding find_program( ... llvm-cov ) +# - add Apple Clang support by adding execute_process( COMMAND xcrun -f llvm-cov ... ) +# - add CODE_COVERAGE_GCOV_TOOL to explicitly select gcov tool and disable find_program +# - replace both functions setup_target_for_coverage_gcovr_* with single setup_target_for_coverage_gcovr +# - add support for all gcovr output formats +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_gcovr( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr( +# NAME coverage +# EXECUTABLE testrunner +# FORMAT html-details +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 4.b If you need to pass specific options to gcovr, specify them in +# GCOVR_ADDITIONAL_ARGS variable. +# Example: +# set (GCOVR_ADDITIONAL_ARGS --exclude-throw-branches --exclude-noncode-lines -s) +# setup_target_for_coverage_gcovr( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "src/dir1" "src/dir2") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) + +if (DEFINED CODE_COVERAGE_GCOV_TOOL) + set(GCOV_TOOL "${CODE_COVERAGE_GCOV_TOOL}") +elseif (DEFINED ENV{CODE_COVERAGE_GCOV_TOOL}) + set(GCOV_TOOL "$ENV{CODE_COVERAGE_GCOV_TOOL}") +elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if (APPLE) + execute_process( COMMAND xcrun -f llvm-cov + OUTPUT_VARIABLE LLVMCOV_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else() + find_program( LLVMCOV_PATH llvm-cov ) + endif() + if(LLVMCOV_PATH) + set(GCOV_TOOL "${LLVMCOV_PATH} gcov") + endif() +elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + find_program( GCOV_PATH gcov ) + set(GCOV_TOOL "${GCOV_PATH}") +endif() + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage" + CACHE INTERNAL "") +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() + include(CheckCCompilerFlag) + check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# 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( +# 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) +# 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) + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME FORMAT) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOV_TOOL) + message(FATAL_ERROR "Could not find gcov or llvm-cov tool! Aborting...") + endif() + + if(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) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + if(NOT DEFINED Coverage_FORMAT) + set(Coverage_FORMAT xml) + endif() + + if("--output" IN_LIST GCOVR_ADDITIONAL_ARGS) + message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting...") + else() + 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() + endif() + + if ((Coverage_FORMAT STREQUAL "cobertura") + OR (Coverage_FORMAT STREQUAL "xml")) + list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura "${GCOVR_OUTPUT_FILE}" ) + list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura-pretty ) + set(Coverage_FORMAT cobertura) # overwrite xml + elseif(Coverage_FORMAT STREQUAL "sonarqube") + list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}" ) + elseif(Coverage_FORMAT STREQUAL "json-summary") + list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}" ) + list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty) + elseif(Coverage_FORMAT STREQUAL "json-details") + list(APPEND GCOVR_ADDITIONAL_ARGS --json "${GCOVR_OUTPUT_FILE}" ) + list(APPEND GCOVR_ADDITIONAL_ARGS --json-pretty) + elseif(Coverage_FORMAT STREQUAL "coveralls") + list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls "${GCOVR_OUTPUT_FILE}" ) + list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls-pretty) + elseif(Coverage_FORMAT STREQUAL "csv") + list(APPEND GCOVR_ADDITIONAL_ARGS --csv "${GCOVR_OUTPUT_FILE}" ) + elseif(Coverage_FORMAT STREQUAL "txt") + list(APPEND GCOVR_ADDITIONAL_ARGS --txt "${GCOVR_OUTPUT_FILE}" ) + elseif(Coverage_FORMAT STREQUAL "html-single") + list(APPEND GCOVR_ADDITIONAL_ARGS --html "${GCOVR_OUTPUT_FILE}" ) + list(APPEND GCOVR_ADDITIONAL_ARGS --html-self-contained) + elseif(Coverage_FORMAT STREQUAL "html-nested") + list(APPEND GCOVR_ADDITIONAL_ARGS --html-nested "${GCOVR_OUTPUT_FILE}" ) + elseif(Coverage_FORMAT STREQUAL "html-details") + list(APPEND GCOVR_ADDITIONAL_ARGS --html-details "${GCOVR_OUTPUT_FILE}" ) + else() + message(FATAL_ERROR "Unsupported output style ${Coverage_FORMAT}! Aborting...") + 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_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + + # Create folder + 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_CMD + ${GCOVR_PATH} + --gcov-executable ${GCOV_TOOL} + --gcov-ignore-parse-errors=negative_hits.warn_once_per_file + -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_EXEC_TESTS_CMD_SPACED "${GCOVR_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_EXEC_TESTS_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 coverage data: ") + string(REPLACE ";" " " GCOVR_CMD_SPACED "${GCOVR_CMD}") + message(STATUS "${GCOVR_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_EXEC_TESTS_CMD} + COMMAND ${GCOVR_FOLDER_CMD} + COMMAND ${GCOVR_CMD} + + BYPRODUCTS ${GCOVR_OUTPUT_FILE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + 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 "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}" + ) +endfunction() # setup_target_for_coverage_gcovr + +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) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() diff --git a/CMake/Coverage.cmake b/CMake/Coverage.cmake deleted file mode 100644 index da6931ff..00000000 --- a/CMake/Coverage.cmake +++ /dev/null @@ -1,125 +0,0 @@ -# call add_coverage(module_name) to add coverage targets for the given module -function (add_coverage module) - if ("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" - OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") - message ("[Coverage] Building with llvm Code Coverage Tools") - # Using llvm gcov ; llvm install by xcode - set (LLVM_COV_PATH /Library/Developer/CommandLineTools/usr/bin) - if (NOT EXISTS ${LLVM_COV_PATH}/llvm-cov) - message (FATAL_ERROR "llvm-cov not found! Aborting.") - endif () - - # set Flags - target_compile_options (${module} PRIVATE - -fprofile-instr-generate - -fcoverage-mapping) - - target_link_options (${module} PUBLIC - -fprofile-instr-generate - -fcoverage-mapping) - - target_compile_options (clio PRIVATE - -fprofile-instr-generate - -fcoverage-mapping) - - target_link_options (clio PUBLIC - -fprofile-instr-generate - -fcoverage-mapping) - - # llvm-cov - add_custom_target (${module}-ccov-preprocessing - COMMAND LLVM_PROFILE_FILE=${module}.profraw $ - COMMAND ${LLVM_COV_PATH}/llvm-profdata merge -sparse ${module}.profraw -o - ${module}.profdata - DEPENDS ${module}) - - add_custom_target (${module}-ccov-show - COMMAND ${LLVM_COV_PATH}/llvm-cov show $ - -instr-profile=${module}.profdata -show-line-counts-or-regions - DEPENDS ${module}-ccov-preprocessing) - - # add summary for CI parse - add_custom_target (${module}-ccov-report - COMMAND - ${LLVM_COV_PATH}/llvm-cov report $ - -instr-profile=${module}.profdata - -ignore-filename-regex=".*_makefiles|.*unittests|.*_deps" - -show-region-summary=false - DEPENDS ${module}-ccov-preprocessing) - - # exclude libs and unittests self - add_custom_target (${module}-ccov - COMMAND - ${LLVM_COV_PATH}/llvm-cov show $ - -instr-profile=${module}.profdata -show-line-counts-or-regions - -output-dir=${module}-llvm-cov -format="html" - -ignore-filename-regex=".*_makefiles|.*unittests|.*_deps" > /dev/null 2>&1 - DEPENDS ${module}-ccov-preprocessing) - - add_custom_command ( - TARGET ${module}-ccov - POST_BUILD - COMMENT - "Open ${module}-llvm-cov/index.html in your browser to view the coverage report." - ) - elseif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - message ("[Coverage] Building with Gcc Code Coverage Tools") - - find_program (GCOV_PATH gcov) - if (NOT GCOV_PATH) - message (FATAL_ERROR "gcov not found! Aborting...") - endif () # NOT GCOV_PATH - find_program (GCOVR_PATH gcovr) - if (NOT GCOVR_PATH) - message (FATAL_ERROR "gcovr not found! Aborting...") - endif () # NOT GCOVR_PATH - - set (COV_OUTPUT_PATH ${module}-gcc-cov) - target_compile_options (${module} PRIVATE -fprofile-arcs -ftest-coverage - -fPIC) - target_link_libraries (${module} PRIVATE gcov) - - target_compile_options (clio PRIVATE -fprofile-arcs -ftest-coverage - -fPIC) - target_link_libraries (clio PRIVATE gcov) - # this target is used for CI as well generate the summary out.xml will send - # to github action to generate markdown, we can paste it to comments or - # readme - add_custom_target (${module}-ccov - COMMAND ${module} ${TEST_PARAMETER} - COMMAND rm -rf ${COV_OUTPUT_PATH} - COMMAND mkdir ${COV_OUTPUT_PATH} - COMMAND - gcovr -r ${CMAKE_SOURCE_DIR} --object-directory=${PROJECT_BINARY_DIR} -x - ${COV_OUTPUT_PATH}/out.xml --exclude='${CMAKE_SOURCE_DIR}/unittests/' - --exclude='${PROJECT_BINARY_DIR}/' - COMMAND - gcovr -r ${CMAKE_SOURCE_DIR} --object-directory=${PROJECT_BINARY_DIR} - --html ${COV_OUTPUT_PATH}/report.html - --exclude='${CMAKE_SOURCE_DIR}/unittests/' - --exclude='${PROJECT_BINARY_DIR}/' - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - COMMENT "Running gcovr to produce Cobertura code coverage report.") - - # generate the detail report - add_custom_target (${module}-ccov-report - COMMAND ${module} ${TEST_PARAMETER} - COMMAND rm -rf ${COV_OUTPUT_PATH} - COMMAND mkdir ${COV_OUTPUT_PATH} - COMMAND - gcovr -r ${CMAKE_SOURCE_DIR} --object-directory=${PROJECT_BINARY_DIR} - --html-details ${COV_OUTPUT_PATH}/index.html - --exclude='${CMAKE_SOURCE_DIR}/unittests/' - --exclude='${PROJECT_BINARY_DIR}/' - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - COMMENT "Running gcovr to produce Cobertura code coverage report.") - add_custom_command ( - TARGET ${module}-ccov-report - POST_BUILD - COMMENT - "Open ${COV_OUTPUT_PATH}/index.html in your browser to view the coverage report." - ) - else () - message (FATAL_ERROR "Complier not support yet") - endif () -endfunction () diff --git a/CMakeLists.txt b/CMakeLists.txt index 71bf7833..60984977 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,12 @@ include (CMake/Ccache.cmake) include (CheckCXXCompilerFlag) include (CMake/ClangTidy.cmake) +# Set coverage build options +if (tests AND coverage) + include (CMake/CodeCoverage.cmake) + append_coverage_compiler_flags() +endif() + if (verbose) set (CMAKE_VERBOSE_MAKEFILE TRUE) endif () @@ -267,12 +273,31 @@ if (tests) target_include_directories (${TEST_TARGET} PRIVATE unittests) target_link_libraries (${TEST_TARGET} PUBLIC clio gtest::gtest) - # Generate `clio_tests-ccov` if coverage is enabled - # Note: use `make clio_tests-ccov` to generate report + # Generate `coverage_report` target if coverage is enabled if (coverage) - target_compile_definitions(${TEST_TARGET} PRIVATE COVERAGE_ENABLED) - include (CMake/Coverage.cmake) - add_coverage (${TEST_TARGET}) + if (DEFINED CODE_COVERAGE_REPORT_FORMAT) + set(CODE_COVERAGE_FORMAT ${CODE_COVERAGE_REPORT_FORMAT}) + else() + set(CODE_COVERAGE_FORMAT html-details) + endif() + + if (DEFINED CODE_COVERAGE_TESTS_ARGS) + set(TESTS_ADDITIONAL_ARGS ${CODE_COVERAGE_TESTS_ARGS}) + separate_arguments(TESTS_ADDITIONAL_ARGS) + else() + set(TESTS_ADDITIONAL_ARGS "") + endif() + + set (GCOVR_ADDITIONAL_ARGS --exclude-throw-branches -s) + + setup_target_for_coverage_gcovr( + NAME coverage_report + FORMAT ${CODE_COVERAGE_FORMAT} + EXECUTABLE clio_tests + EXECUTABLE_ARGS --gtest_brief=1 ${TESTS_ADDITIONAL_ARGS} + EXCLUDE "unittests" + DEPENDENCIES clio_tests + ) endif () endif () diff --git a/README.md b/README.md index 61f5b070..02ace194 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,55 @@ E.g.: export CLIO_CLANG_TIDY_BIN=/opt/homebrew/opt/llvm@17/bin/clang-tidy ``` +## Coverage report + +Coverage report is intended for the developers using compilers GCC +or Clang (including Apple Clang). It is generated by the build target `coverage_report`, +which is only enabled when both `tests` and `coverage` options are set, e.g. with +`-o coverage=True -o tests=True` in `conan` + +Prerequisites for the coverage report: + +- [gcovr tool](https://gcovr.com/en/stable/getting-started.html) (can be installed e.g. with `pip install gcovr`) +- `gcov` for GCC (installed with the compiler by default) or +- `llvm-cov` for Clang (installed with the compiler by default, also on Apple) +- `Debug` build type + +Coverage report is created when the following steps are completed, in order: + +1. `clio_tests` binary built with the instrumentation data, enabled by the `coverage` + option mentioned above +2. completed run of unit tests, which populates coverage capture data +3. completed run of `gcovr` tool (which internally invokes either `gcov` or `llvm-cov`) + to assemble both instrumentation data and coverage capture data into a coverage report + +Above steps are automated into a single target `coverage_report`. The instrumented +`clio_tests` binary can be also used for running regular unit tests. In case of a +spurious failure of unit tests, it is possile to re-run `coverage_report` target without +rebuilding the `clio_tests` binary (since it is simply a dependency of the coverage report target). + +The default coverage report format is `html-details`, but the developers +can override it to any of the formats listed in `CMake/CodeCoverage.cmake` +by setting `CODE_COVERAGE_REPORT_FORMAT` variable in `cmake`. For example, CI +is setting this parameter to `xml` for the [codecov](codecov.io) integration. + +In case if some unit tests predictably fail e.g. due to absence of a Cassandra database, it is possible +to set unit tests options in `CODE_COVERAGE_TESTS_ARGS` cmake variable, as demonstrated below: + +``` +cd .build +conan install .. --output-folder . --build missing --settings build_type=Debug -o tests=True -o coverage=True +cmake -DCODE_COVERAGE_REPORT_FORMAT=json-details -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE_TESTS_ARGS="--gtest_filter=-BackendCassandra*" -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake .. +cmake --build . --target coverage_report +``` + +After `coverage_report` target is completed, the generated coverage report will be +stored inside the build directory, as either of: + +- file named `coverage_report.*`, with a suitable extension for the report format, or +- directory named `coverage_report`, with `index.html` and other files inside, for `html-details` or `html-nested` report formats. + + ## Developing against `rippled` in standalone mode If you wish you develop against a `rippled` instance running in standalone