From 69dcba71d4e3731eef6b40f093bc26f470ea857a Mon Sep 17 00:00:00 2001 From: Alexis Placet Date: Sun, 8 Dec 2024 22:39:59 +0100 Subject: [PATCH] Add coverage option --- .github/workflows/windows.yml | 25 ++++++++- CMakeLists.txt | 20 +++++-- cmake/code_coverage.cmake | 98 +++++++++++++++++++++++++++++++++++ doxide.yaml | 9 ++++ test/CMakeLists.txt | 7 ++- 5 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 cmake/code_coverage.cmake create mode 100644 doxide.yaml diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 01a0f5143..f2b920325 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -146,6 +146,12 @@ jobs: if: matrix.target-arch.name == 'Win32' # Note: this is not a mandatory limitation in any context, we add this just to have at least one configuration testing that feature run: echo "SPARROW_CHECKS='$SPARROW_CHECKS -DSPARROW_ENABLE_32BIT_SIZE_LIMIT=ON'" >> $GITHUB_ENV + - name: Enable tests coverage + if: matrix.config.name == 'Debug' + run: | + choco install opencppcoverage + echo "TEST_COVERAGE_ACTIVATION=' -DENABLE_COVERAGE=ON'" >> $GITHUB_ENV + - name: Set CMake generator environment variable run: | echo "CMAKE_GENERATOR=${{ matrix.build-system.name }}" >> $GITHUB_ENV @@ -182,7 +188,8 @@ jobs: $SPARROW_ADDITIONAL_OPTIONS \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL + -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL \ + $TEST_COVERAGE_ACTIVATION - name: Build library working-directory: build @@ -207,7 +214,21 @@ jobs: name: test_sparrow_lib_report_Windows_${{ matrix.toolchain.compiler }}_${{ matrix.build-system.name }}_${{ matrix.config.name }}_${{ matrix.target-arch.name }}_date-polyfill_${{ matrix.toolchain.date-polyfill}} path: '**/test_sparrow_lib_report.xml' + - name: Tests coverage + if: matrix.config.name == 'Debug' + working-directory: build + run: | + cmake --build . --config Debug --target generate_cobertura + + - name: Produce the coverage report + if: matrix.config.name == 'Debug' + uses: dennisjensen95/coverage-scope@v0.5.0 + with: + coverage-filepath: 'build/coverage_reports/cobertura.xml' + branch: main + threshold-total: 50 + threshold-change: 50 + - name: Run all examples working-directory: build run: cmake --build . --config ${{matrix.config.name}} --target run_examples - diff --git a/CMakeLists.txt b/CMakeLists.txt index 68a3f663c..c4bdf7a0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.22) +cmake_minimum_required(VERSION 3.10) # This is better specified per target, but cmake keeps ignoring these language version # specification when building this project by itself, in particular the gnu extensions, @@ -47,6 +47,9 @@ if(CMAKE_CXX_COMPILER_LAUNCHER) message(STATUS "Using C++ compiler launcher: ${CMAKE_CXX_COMPILER_LAUNCHER}") endif() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +message(DEBUG "CMake module path: ${CMAKE_MODULE_PATH}") + # Versionning # =========== @@ -90,6 +93,12 @@ OPTION(USE_LARGE_INT_PLACEHOLDERS "use types without api for big integers" OFF) OPTION(SPARROW_TARGET_32BIT "Test 32bit support" OFF) +OPTION(ENABLE_COVERAGE "Enable test coverage" OFF) + +if(ENABLE_COVERAGE) + include(code_coverage) +endif() + include(CheckCXXSymbolExists) if(cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) @@ -113,13 +122,13 @@ OPTION(ACTIVATE_LINTER "Create targets to run clang-format" OFF) cmake_dependent_option(ACTIVATE_LINTER_DURING_COMPILATION "Run linter during the compilation" ON "ACTIVATE_LINTER" OFF) if(ACTIVATE_LINTER) - include(cmake/clang-format.cmake) - include(cmake/clang-tidy.cmake) + include(clang-format) + include(clang-tidy) endif() # Sanitizers # ========== -include(cmake/sanitizers.cmake) +include(sanitizers) # Dependencies # ============ @@ -272,6 +281,9 @@ target_include_directories(sparrow PUBLIC set_target_properties(sparrow PROPERTIES CMAKE_CXX_EXTENSIONS OFF) target_compile_features(sparrow PUBLIC cxx_std_20) target_link_libraries(sparrow PUBLIC ${SPARROW_INTERFACE_DEPENDENCIES}) +if(ENABLE_COVERAGE) + enable_coverage(sparrow) +endif() if (BUILD_TESTS) enable_testing() diff --git a/cmake/code_coverage.cmake b/cmake/code_coverage.cmake new file mode 100644 index 000000000..3be172839 --- /dev/null +++ b/cmake/code_coverage.cmake @@ -0,0 +1,98 @@ +if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() + +set(COVERAGE_REPORT_PATH "${CMAKE_BINARY_DIR}/coverage_reports" CACHE PATH "Path to store coverage reports") +set(COBERTURA_REPORT_PATH "${COVERAGE_REPORT_PATH}/cobertura.xml" CACHE PATH "Path to store cobertura report") +set(COVERAGE_TARGETS_FOLDER "Tests utilities/Code Coverage") + +if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + find_program(OpenCPPCoverage OpenCppCoverage.exe opencppcoverage.exe REQUIRED + PATHS "C:/Program Files/OpenCppCoverage" "C:/Program Files (x86)/OpenCppCoverage") + + cmake_path(CONVERT ${CMAKE_SOURCE_DIR} TO_NATIVE_PATH_LIST OPENCPPCOVERAGE_SOURCES) + set(OPENCPPCOVERAGE_COMMON_ARGS --sources=${OPENCPPCOVERAGE_SOURCES} --modules=${OPENCPPCOVERAGE_SOURCES} --excluded_sources=test*) + + add_custom_target(generate_cobertura + COMMAND ${OpenCPPCoverage} + ${OPENCPPCOVERAGE_COMMON_ARGS} + --export_type=cobertura:${COBERTURA_REPORT_PATH} + -- $ + DEPENDS test_sparrow_lib + COMMENT "Generating coverage cobertura report with OpenCppCoverage: ${COBERTURA_REPORT_PATH}" + ) + set(TARGET_PROPERTIES generate_cobertura PROPERTIES FOLDER ${COVERAGE_TARGETS_FOLDER}) + + add_custom_target(generate_html_coverage_report + COMMAND ${OpenCPPCoverage} + ${OPENCPPCOVERAGE_COMMON_ARGS} + --export_type=html:${COVERAGE_REPORT_PATH} + -- $ + DEPENDS test_sparrow_lib + COMMENT "Generating coverage report with OpenCppCoverage: ${COVERAGE_REPORT_PATH}" + ) + set(TARGET_PROPERTIES generate_cobertura PROPERTIES FOLDER "Tests utilities/Code Coverage") +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_program(llvm-cov llvm-cov REQUIRED) + find_program(gcovr gcovr REQUIRED) + find_program(doxide doxide REQUIRED PATHS ${doxide_ROOT}) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(gcov_executable llvm-cov gcov) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(gcov_executable gcov) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_custom_target(merge_gcov + COMMAND ${CMAKE_COMMAND} -E rm -f "${CMAKE_BINARY_DIR}/coverage.gcov" + COMMAND for /r %f in (*.gcda) do ${gcov_executable} --stdout "%f" >> ${CMAKE_BINARY_DIR}/coverage.gcov + DEPENDS run_tests) + else() + add_custom_target(merge_gcov + COMMAND ${CMAKE_COMMAND} -E rm -f "${CMAKE_BINARY_DIR}/coverage.gcov" + COMMAND find . -name '*.gcda' | xargs ${gcov_executable} --stdout > ${CMAKE_BINARY_DIR}/coverage.gcov + DEPENDS run_tests) + endif() + set_target_properties(merge_gcov PROPERTIES FOLDER ${COVERAGE_TARGETS_FOLDER}) + + add_custom_target(doxide_generate_json + COMMAND ${doxide} cover --coverage ${CMAKE_BINARY_DIR}/coverage.gcov > ${CMAKE_BINARY_DIR}/doxide_coverage.json + DEPENDS merge_gcov + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating coverage report with doxide: ${CMAKE_BINARY_DIR}/doxide_coverage.json" + ) + set_target_properties(doxide_generate_json PROPERTIES FOLDER ${COVERAGE_TARGETS_FOLDER}) + + add_custom_target(generate_html_coverage_report + COMMAND ${gcovr} --html-details ${COVERAGE_REPORT_PATH}/coverage.html + --exclude-function-lines + --exclude-noncode-lines + --json-add-tracefile doxide_coverage.json + -r ${CMAKE_SOURCE_DIR} + DEPENDS doxide_generate_json + COMMENT "Generating coverage report with gcovr: ${COVERAGE_REPORT_PATH}/coverage.html") + set_target_properties(generate_html_coverage_report PROPERTIES FOLDER ${COVERAGE_TARGETS_FOLDER}) + + add_custom_target(generate_cobertura + COMMAND ${gcovr} --xml -o ${COBERTURA_REPORT_PATH} + --exclude-function-lines + --exclude-noncode-lines + --json-add-tracefile doxide_coverage.json + -r ${CMAKE_SOURCE_DIR} + DEPENDS doxide_generate_json + COMMENT "Generating coverage report with gcovr: ${CMAKE_BINARY_DIR}/cobertura.xml") + set_target_properties(generate_cobertura PROPERTIES FOLDER ${COVERAGE_TARGETS_FOLDER}) +endif() + +function(enable_coverage target) + set(CLANG_COVERAGE_FLAGS --coverage -fprofile-instr-generate -fcoverage-mapping -fno-inline -fno-elide-constructors) + set(GCC_COVERAGE_FLAGS --coverage -fno-inline -fno-inline-small-functions -fno-default-inline) + + target_compile_options(${target} PRIVATE + $<$:${CLANG_COVERAGE_FLAGS}> + $<$:${GCC_COVERAGE_FLAGS}>) + target_link_options(${target} PRIVATE + $<$:${CLANG_COVERAGE_FLAGS}> + $<$:${GCC_COVERAGE_FLAGS}>) +endfunction(enable_coverage target) diff --git a/doxide.yaml b/doxide.yaml new file mode 100644 index 000000000..254614f79 --- /dev/null +++ b/doxide.yaml @@ -0,0 +1,9 @@ +title: Sparrow +description: +files: + - "include/*.hpp" + - "include/**/*.hpp" + - "include/*.h" + - "include/**/*.h" + - "src/*.cpp" + - "src/**/*.cpp" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a708c77c0..3950e428f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.10) enable_testing() @@ -199,6 +199,11 @@ target_compile_options(${test_target} target_link_options(${test_target} PRIVATE $<$:${SANITIZER_LINK_OPTIONS}>) + +if(ENABLE_COVERAGE) + enable_coverage(${test_target}) +endif() + # We do not use non-standard C++ set_target_properties(${test_target} PROPERTIES CMAKE_CXX_EXTENSIONS OFF) target_compile_features(${test_target} PRIVATE cxx_std_20)