From c1277ba1d0a0b4051be66e36e08306e1dbe22276 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Sat, 9 May 2026 09:40:53 -0300 Subject: [PATCH 1/7] ci: surface code coverage on pull requests Three small infra changes; no production code touched. - .github/workflows/build_debug.yml: add `pull_request:` trigger so the existing code-coverage job runs on PRs targeting develop. Gate the Docker `build` and `package` jobs with `if: github.event_name != 'pull_request'` so they remain push-only (we don't want PR runs pushing debug images to Docker Hub). - .codecov.yml: new file. Configure both project and patch status as informational so the Codecov bot posts a PR comment with the diff but never blocks merge. Once project coverage hits 85% we flip both to `informational: false`. Ignores 3rd-party, build artefacts, generated protobuf, and test sources themselves. - CMakeLists.txt: drop `cmd/*` from the lcov EXCLUDE list so the pktvisord/pktvisor-reader entry-point logic shows up in the report (per discussion). `3rd/*`, `libs/*`, and the build dir stay excluded. The first PR run will give us the baseline coverage number, which informs the Phase 2 push toward 85%. Co-Authored-By: Claude Opus 4.7 (1M context) --- .codecov.yml | 30 ++++++++++++++++++++++++++++++ .github/workflows/build_debug.yml | 4 ++++ CMakeLists.txt | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..0bd6645d1 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,30 @@ +coverage: + # Both gates are informational — they show up in PR comments and Codecov + # status checks but never block merge. Flip `informational: false` later + # when we want to enforce a floor. + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + informational: true + +comment: + layout: "diff, flags, files" + behavior: default + require_changes: false + +ignore: + # Vendored third-party code we don't own + - "3rd/**" + # Build artifacts and generated headers + - "build/**" + - "**/*.pb.h" + - "**/*.pb.cc" + # Test sources themselves don't need to be coverage targets + - "**/tests/**" + - "**/test_*.cpp" diff --git a/.github/workflows/build_debug.yml b/.github/workflows/build_debug.yml index f9941dbe5..8187d7f5e 100644 --- a/.github/workflows/build_debug.yml +++ b/.github/workflows/build_debug.yml @@ -3,6 +3,8 @@ name: Debug Builds on: push: branches: [ develop, master, release/** ] + pull_request: + branches: [ develop ] env: CTEST_OUTPUT_ON_FAILURE: 1 @@ -69,6 +71,7 @@ jobs: build: runs-on: ubuntu-latest + if: github.event_name != 'pull_request' steps: - uses: actions/checkout@v6 @@ -113,6 +116,7 @@ jobs: package: needs: build runs-on: ubuntu-latest + if: github.event_name != 'pull_request' steps: - name: Download to workspace uses: actions/download-artifact@v8 diff --git a/CMakeLists.txt b/CMakeLists.txt index 91284558a..947ff421c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,7 @@ if(CODE_COVERAGE) setup_target_for_coverage_lcov( NAME coverage EXECUTABLE ctest - EXCLUDE "3rd/*" "cmd/*" "libs/*" "${PROJECT_BINARY_DIR}/*") + EXCLUDE "3rd/*" "libs/*" "${PROJECT_BINARY_DIR}/*") endif() set(VISOR_STATIC_PLUGINS) From 2dd0c05d7a2e0f5f04aee7870392e8188d12da9b Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Sat, 9 May 2026 09:49:47 -0300 Subject: [PATCH 2/7] ci(coverage): post a per-PR coverage comment via lcov-reporter-action Mirror the comment-on-PR pattern used in orb-discovery's device-discovery-lint-tests workflow. Codecov continues to receive uploads (long-term dashboard) but romeovs/lcov-reporter-action now also posts a self-contained markdown summary directly into the PR, showing per-file coverage and diff for changed files. Pinned to v0.4.0 (commit 87a815f) per repo convention. Adds pull-requests:write to the code-coverage job's permissions so the action can post/update the comment with the default GITHUB_TOKEN. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build_debug.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build_debug.yml b/.github/workflows/build_debug.yml index 8187d7f5e..7d5ace0a2 100644 --- a/.github/workflows/build_debug.yml +++ b/.github/workflows/build_debug.yml @@ -14,6 +14,9 @@ env: jobs: code-coverage: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write outputs: version_number: ${{ steps.build.outputs.version }} steps: @@ -69,6 +72,13 @@ jobs: name: pktvisor verbose: true + - name: Post coverage summary to PR + if: github.event_name == 'pull_request' + uses: romeovs/lcov-reporter-action@87a815f34ec27a5826abba44ce09bbc688da58fd #v0.4.0 + with: + lcov-file: build/coverage.info + github-token: ${{ secrets.GITHUB_TOKEN }} + build: runs-on: ubuntu-latest if: github.event_name != 'pull_request' From 002f7f2b33645f39346957d2d7a8570b6159c0c8 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Sat, 9 May 2026 09:50:53 -0300 Subject: [PATCH 3/7] ci(coverage): grant issues:write for the comment-posting action Belt-and-suspenders: pull-requests:write should cover PR comment posting per GitHub's docs, but some comment-posting actions hit the issues.createComment endpoint directly (since PR conversation comments share the issue API path) and only check the issues:write scope. Granting both avoids a 403 surprise. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build_debug.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build_debug.yml b/.github/workflows/build_debug.yml index 7d5ace0a2..9081d9ba1 100644 --- a/.github/workflows/build_debug.yml +++ b/.github/workflows/build_debug.yml @@ -17,6 +17,11 @@ jobs: permissions: contents: read pull-requests: write + # Some comment-posting actions hit the issues.createComment API path + # (PR conversation comments share the issue endpoint). Granting + # issues:write avoids a 403 if the action doesn't honour the PR-only + # scope mapping. + issues: write outputs: version_number: ${{ steps.build.outputs.version }} steps: From f063c98a9340fb545b6d5cc762f18878993528f2 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Sat, 9 May 2026 11:29:45 -0300 Subject: [PATCH 4/7] ci(coverage): exclude system + STL headers from lcov report The previous run leaked /usr/include/c++/13/* and similar system paths into the coverage report, making the comment dominated by STL noise. Add /usr/*, /Library/*, */c++/*, and */.conan2/* to the lcov EXCLUDE list so the report only covers our own source tree. Also strip explanatory comments from the workflow file. --- .github/workflows/build_debug.yml | 4 ---- CMakeLists.txt | 9 ++++++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_debug.yml b/.github/workflows/build_debug.yml index 9081d9ba1..73793a956 100644 --- a/.github/workflows/build_debug.yml +++ b/.github/workflows/build_debug.yml @@ -17,10 +17,6 @@ jobs: permissions: contents: read pull-requests: write - # Some comment-posting actions hit the issues.createComment API path - # (PR conversation comments share the issue endpoint). Granting - # issues:write avoids a 403 if the action doesn't honour the PR-only - # scope mapping. issues: write outputs: version_number: ${{ steps.build.outputs.version }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 947ff421c..ee40e51e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,14 @@ if(CODE_COVERAGE) setup_target_for_coverage_lcov( NAME coverage EXECUTABLE ctest - EXCLUDE "3rd/*" "libs/*" "${PROJECT_BINARY_DIR}/*") + EXCLUDE + "3rd/*" + "libs/*" + "${PROJECT_BINARY_DIR}/*" + "/usr/*" + "/Library/*" + "*/c++/*" + "*/.conan2/*") endif() set(VISOR_STATIC_PLUGINS) From 0e0e12fc061e8101b98b6286067dd2ddb3d46a05 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Sat, 9 May 2026 11:58:56 -0300 Subject: [PATCH 5/7] ci(coverage): tolerate unused exclude patterns + drop non-Linux ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous run hit lcov v2's strict-by-default behaviour on unused EXCLUDE patterns: '/Library/*' and '*/.conan2/*' don't match any file on the Ubuntu runner, so lcov bailed with a non-zero exit at the filter step. - Drop the two non-Linux patterns from setup_target_for_coverage_lcov. - Pass `LCOV_ARGS --ignore-errors unused` so any future pattern drift becomes a warning rather than a hard failure. - Picks up the user's CodeCoverage.cmake refresh (Lars Bilke's Jan 2026 version) which adds -fprofile-update=atomic for concurrent ctest runs and proper lcov-version detection — drop-in compatible with our existing call site. --- CMakeLists.txt | 5 +- cmake/CodeCoverage.cmake | 406 ++++++++++++++++++++++++--------------- 2 files changed, 249 insertions(+), 162 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee40e51e3..d3886568d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,14 +92,13 @@ if(CODE_COVERAGE) setup_target_for_coverage_lcov( NAME coverage EXECUTABLE ctest + LCOV_ARGS --ignore-errors unused EXCLUDE "3rd/*" "libs/*" "${PROJECT_BINARY_DIR}/*" "/usr/*" - "/Library/*" - "*/c++/*" - "*/.conan2/*") + "*/c++/*") endif() set(VISOR_STATIC_PLUGINS) diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index 885425350..6c8d6f474 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -83,6 +83,13 @@ # - 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) +# +# 2026-01-20, Michał Sawicz +# - add `-fprofile-update=atomic` flag, if supported, for concurrent test runs +# # USAGE: # # 1. Copy this file into your cmake modules path. @@ -126,7 +133,7 @@ # runs your test executable and produces a code coverage report. # # 6. Build a Debug build: -# cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./cmake/conan_provider.cmake .. +# cmake -DCMAKE_BUILD_TYPE=Debug .. # make # make my_coverage_target # @@ -136,76 +143,142 @@ include(CMakeParseArguments) option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) # Check prereqs -find_program( GCOV_PATH gcov ) -find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( GCOV_PATH NAMES gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl ) find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) -find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( GCOVR_PATH NAMES gcovr ) find_program( CPPFILT_PATH NAMES c++filt ) if(NOT GCOV_PATH) message(FATAL_ERROR "gcov not found! Aborting...") endif() # NOT GCOV_PATH +if(LCOV_PATH) + set(lcov_message "Found Lcov: ${LCOV_PATH}") + execute_process(COMMAND ${LCOV_PATH} "--version" OUTPUT_VARIABLE LCOV_VERSION_OUTPUT) + if("${LCOV_VERSION_OUTPUT}" MATCHES "lcov: LCOV version ([0-9]+\\.[0-9]+(\\.[0-9]+)?)(-[0-9])?\n") + set(LCOV_VERSION "${CMAKE_MATCH_1}") + string(APPEND lcov_message " (found version ${LCOV_VERSION})") + endif() + message(STATUS "${lcov_message}") +endif() + # Check supported compiler (Clang, GNU and Flang) get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +list(REMOVE_ITEM LANGUAGES NONE) 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...") + 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 -fprofile-arcs -ftest-coverage" - CACHE INTERNAL "") +set(COVERAGE_COMPILER_FLAGS "-g --coverage" + CACHE INTERNAL "") + +# This is necessary because older versions of ccache don't yet have these: +# https://github.com/ccache/ccache/commit/2737d79e282de72d1b29128f437173e4892ced5e +# https://github.com/ccache/ccache/pull/1408 +function(_coverage_atomic_update_flag out_var lang) + set(_atomic_update_flag "-fprofile-update=atomic") + set(_coverage_uses_ccache OFF) + foreach(_launcher IN LISTS CMAKE_${lang}_COMPILER_LAUNCHER) + if(_launcher MATCHES "ccache(\\.exe)?$") + # Check that ccache version is less than 4.10. + execute_process(COMMAND ${_launcher} --version OUTPUT_VARIABLE CACHE_VERSION_OUTPUT) + if("${CACHE_VERSION_OUTPUT}" MATCHES "ccache version ([0-9]+\\.[0-9]+\\.[0-9]+)") + if("${CMAKE_MATCH_1}" VERSION_LESS "4.10.0") + set(_coverage_uses_ccache ON) + break() + endif() + endif() + endif() + endforeach() + if(_coverage_uses_ccache) + set(_atomic_update_flag "--ccache-skip -fprofile-update=atomic") + endif() + set(${out_var} "${_atomic_update_flag}" PARENT_SCOPE) +endfunction() + +set(COVERAGE_CXX_COMPILER_FLAGS ${COVERAGE_COMPILER_FLAGS}) if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) - if(HAVE_fprofile_abs_path) - set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_CXX_COMPILER_FLAGS} -fprofile-abs-path") + endif() + check_cxx_compiler_flag(-fprofile-update=atomic HAVE_cxx_fprofile_update_atomic) + if(HAVE_cxx_fprofile_update_atomic) + _coverage_atomic_update_flag(COVERAGE_ATOMIC_UPDATE_FLAG CXX) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_CXX_COMPILER_FLAGS} ${COVERAGE_ATOMIC_UPDATE_FLAG}") + endif() +endif() + +set(COVERAGE_C_COMPILER_FLAGS ${COVERAGE_COMPILER_FLAGS}) +if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") + 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_C_COMPILER_FLAGS} -fprofile-abs-path") + endif() + check_c_compiler_flag(-fprofile-update=atomic HAVE_c_fprofile_update_atomic) + if(HAVE_c_fprofile_update_atomic) + _coverage_atomic_update_flag(COVERAGE_ATOMIC_UPDATE_FLAG C) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_C_COMPILER_FLAGS} ${COVERAGE_ATOMIC_UPDATE_FLAG}") + endif() +endif() + +set(COVERAGE_Fortran_COMPILER_FLAGS ${COVERAGE_COMPILER_FLAGS}) +if(CMAKE_Fortran_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckFortranCompilerFlag) + check_fortran_compiler_flag(-fprofile-abs-path HAVE_fortran_fprofile_abs_path) + if(HAVE_fortran_fprofile_abs_path) + set(COVERAGE_Fortran_COMPILER_FLAGS "${COVERAGE_Fortran_COMPILER_FLAGS} -fprofile-abs-path") + endif() + check_fortran_compiler_flag(-fprofile-update=atomic HAVE_fortran_fprofile_update_atomic) + if(HAVE_fortran_fprofile_update_atomic) + _coverage_atomic_update_flag(COVERAGE_ATOMIC_UPDATE_FLAG Fortran) + set(COVERAGE_Fortran_COMPILER_FLAGS "${COVERAGE_Fortran_COMPILER_FLAGS} ${COVERAGE_ATOMIC_UPDATE_FLAG}") endif() endif() set(CMAKE_Fortran_FLAGS_COVERAGE - ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the Fortran compiler during coverage builds." - FORCE ) + ${COVERAGE_Fortran_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 ) + ${COVERAGE_CXX_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 ) + ${COVERAGE_C_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 ) + "" + 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 ) + "" + 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 ) + 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 @@ -217,6 +290,8 @@ endif() # DEPENDENCIES testrunner # Dependencies to build first # BASE_DIRECTORY "../" # Base directory for report # # (defaults to PROJECT_SOURCE_DIR) +# DATA_DIRECTORY "./" # Data directory +# # (defaults to PROJECT_BINARY_DIR) # EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative # # to BASE_DIRECTORY, with CMake 3.4+) # NO_DEMANGLE # Don't demangle C++ symbols @@ -225,7 +300,7 @@ endif() function(setup_target_for_coverage_lcov) set(options NO_DEMANGLE SONARQUBE) - set(oneValueArgs BASE_DIRECTORY NAME) + set(oneValueArgs BASE_DIRECTORY DATA_DIRECTORY NAME) set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -259,50 +334,56 @@ function(setup_target_for_coverage_lcov) set(GENHTML_EXTRA_ARGS "--demangle-cpp") endif() + if(DEFINED Coverage_DATA_DIRECTORY) + set(LCOV_DATA_DIRECTORY ${Coverage_DATA_DIRECTORY}) + else() + set(LCOV_DATA_DIRECTORY ${PROJECT_BINARY_DIR}) + endif() + # Setting up commands which will be run to generate coverage data. # Cleanup lcov set(LCOV_CLEAN_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . - -b ${BASEDIR} --zerocounters - ) + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory ${LCOV_DATA_DIRECTORY} + -b ${BASEDIR} --zerocounters + ) # Create baseline to make sure untouched files show up in the report set(LCOV_BASELINE_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b - ${BASEDIR} -o ${Coverage_NAME}.base - ) + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d ${LCOV_DATA_DIRECTORY} -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) # Run tests set(LCOV_EXEC_TESTS_CMD - ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} - ) + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) # Capturing lcov counters and generating report set(LCOV_CAPTURE_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b - ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture - ) + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory ${LCOV_DATA_DIRECTORY} -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) # add baseline counters set(LCOV_BASELINE_COUNT_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base - -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total - ) + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) # filter collected data to final coverage report set(LCOV_FILTER_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove - ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info - ) + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) # Generate HTML output set(LCOV_GEN_HTML_CMD - ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o - ${Coverage_NAME} ${Coverage_NAME}.info - ) + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) if(${Coverage_SONARQUBE}) # Generate SonarQube output set(GCOVR_XML_CMD - ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} - ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} - ) + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) set(GCOVR_XML_CMD_COMMAND - COMMAND ${GCOVR_XML_CMD} - ) + COMMAND ${GCOVR_XML_CMD} + ) set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") endif() @@ -347,41 +428,41 @@ function(setup_target_for_coverage_lcov) # Setup target add_custom_target(${Coverage_NAME} - COMMAND ${LCOV_CLEAN_CMD} - COMMAND ${LCOV_BASELINE_CMD} - COMMAND ${LCOV_EXEC_TESTS_CMD} - COMMAND ${LCOV_CAPTURE_CMD} - COMMAND ${LCOV_BASELINE_COUNT_CMD} - COMMAND ${LCOV_FILTER_CMD} - COMMAND ${LCOV_GEN_HTML_CMD} - ${GCOVR_XML_CMD_COMMAND} - - # Set output files as GENERATED (will be removed on 'make clean') - BYPRODUCTS + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS ${Coverage_NAME}.base ${Coverage_NAME}.capture ${Coverage_NAME}.total ${Coverage_NAME}.info ${GCOVR_XML_CMD_BYPRODUCTS} ${Coverage_NAME}/index.html - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) # Show where to find the lcov info report add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." - ${GCOVR_XML_CMD_COMMENT} - ) + COMMAND true + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) # 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." - ) + COMMAND true + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) endfunction() # setup_target_for_coverage_lcov @@ -439,13 +520,13 @@ function(setup_target_for_coverage_gcovr_xml) # Set up commands which will be run to generate coverage data # Run tests set(GCOVR_XML_EXEC_TESTS_CMD - ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} - ) + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) # Running gcovr set(GCOVR_XML_CMD - ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} - ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} - ) + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) if(CODE_COVERAGE_VERBOSE) message(STATUS "Executed command report") @@ -460,21 +541,21 @@ function(setup_target_for_coverage_gcovr_xml) endif() add_custom_target(${Coverage_NAME} - COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} - COMMAND ${GCOVR_XML_CMD} + 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." - ) + 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." - ) + COMMAND true + 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 @@ -531,17 +612,17 @@ 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 - ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} - ) + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) # Create folder set(GCOVR_HTML_FOLDER_CMD - ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} - ) + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) # Running gcovr set(GCOVR_HTML_CMD - ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} - ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} - ) + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) if(CODE_COVERAGE_VERBOSE) message(STATUS "Executed command report") @@ -560,22 +641,22 @@ function(setup_target_for_coverage_gcovr_html) endif() add_custom_target(${Coverage_NAME} - COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} - COMMAND ${GCOVR_HTML_FOLDER_CMD} - COMMAND ${GCOVR_HTML_CMD} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + 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." - ) + 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." - ) + COMMAND true + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) endfunction() # setup_target_for_coverage_gcovr_html @@ -634,23 +715,22 @@ function(setup_target_for_coverage_fastcov) set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} - --search-directory ${BASEDIR} - --process-gcno - --output ${Coverage_NAME}.json - --exclude ${FASTCOV_EXCLUDES} - --exclude ${FASTCOV_EXCLUDES} - ) + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} - -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info - ) + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) if(Coverage_SKIP_HTML) set(FASTCOV_HTML_CMD ";") else() set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} - -o ${Coverage_NAME} ${Coverage_NAME}.info - ) + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) endif() set(FASTCOV_POST_CMD ";") @@ -688,28 +768,28 @@ function(setup_target_for_coverage_fastcov) # Setup target add_custom_target(${Coverage_NAME} - # Cleanup fastcov - COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} --search-directory ${BASEDIR} --zerocounters - COMMAND ${FASTCOV_EXEC_TESTS_CMD} - COMMAND ${FASTCOV_CAPTURE_CMD} - COMMAND ${FASTCOV_CONVERT_CMD} - COMMAND ${FASTCOV_HTML_CMD} - COMMAND ${FASTCOV_POST_CMD} + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} - # Set output files as GENERATED (will be removed on 'make clean') - BYPRODUCTS - ${Coverage_NAME}.info - ${Coverage_NAME}.json - ${Coverage_NAME}/index.html # report directory + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." - ) + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") if(NOT Coverage_SKIP_HTML) @@ -717,20 +797,28 @@ function(setup_target_for_coverage_fastcov) endif() # Show where to find the fastcov info report add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} - ) + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) endfunction() # setup_target_for_coverage_fastcov 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}") + foreach(LANG ${LANGUAGES}) + set(CMAKE_${LANG}_FLAGS "${CMAKE_${LANG}_FLAGS} ${CMAKE_${LANG}_FLAGS_COVERAGE}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags for ${LANG}: ${CMAKE_${LANG}_FLAGS_COVERAGE}") + endforeach() + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) + endif() endfunction() # append_coverage_compiler_flags # Setup coverage for specific library function(append_coverage_compiler_flags_to_target name) - target_compile_options(${name} - PRIVATE ${COVERAGE_COMPILER_FLAGS}) + foreach(LANG ${LANGUAGES}) + separate_arguments(_flag_list NATIVE_COMMAND "${CMAKE_${LANG}_FLAGS_COVERAGE}") + target_compile_options(${name} PRIVATE $<$:${_flag_list}>) + endforeach() + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() endfunction() \ No newline at end of file From 96b660803b8070a7b53f7593be6156d01b2bf5e4 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Sat, 9 May 2026 12:30:47 -0300 Subject: [PATCH 6/7] ci(coverage): render PR comment from lcov --list to avoid truncation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit romeovs/lcov-reporter-action emits an HTML table row per source file. With ~99 .gcda files in pktvisor (every handler/input compilation unit plus their tests), the rendered comment hit ~65402 bytes — right at GitHub's 65536-char limit. Files alphabetically past src/Metrics.h (which is exactly where src/handlers/ and src/inputs/ start) got silently truncated. Replace the action with a two-step approach: 1. `lcov --summary` + `lcov --list` produce a compact text table (~80 chars/file flat) that holds all 99 files comfortably. 2. marocchino/sticky-pull-request-comment posts/updates a single comment per PR keyed on the `code-coverage` header. Codecov upload remains for the dashboard. --- .github/workflows/build_debug.yml | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_debug.yml b/.github/workflows/build_debug.yml index 73793a956..27b685ecf 100644 --- a/.github/workflows/build_debug.yml +++ b/.github/workflows/build_debug.yml @@ -73,12 +73,31 @@ jobs: name: pktvisor verbose: true - - name: Post coverage summary to PR + - name: Render coverage summary if: github.event_name == 'pull_request' - uses: romeovs/lcov-reporter-action@87a815f34ec27a5826abba44ce09bbc688da58fd #v0.4.0 + working-directory: ${{github.workspace}}/build + run: | + { + echo "## Code Coverage" + echo "" + echo '```' + lcov --summary coverage.info 2>/dev/null + echo '```' + echo "" + echo "
Per-file breakdown" + echo "" + echo '```' + lcov --list coverage.info 2>/dev/null + echo '```' + echo "
" + } > coverage-comment.md + + - name: Post coverage comment + if: github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 #v3.0.4 with: - lcov-file: build/coverage.info - github-token: ${{ secrets.GITHUB_TOKEN }} + header: code-coverage + path: build/coverage-comment.md build: runs-on: ubuntu-latest From 70a573fff2820ecfbdd7aceb2b56763fee0851b4 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Sat, 9 May 2026 12:36:09 -0300 Subject: [PATCH 7/7] ci(coverage): switch to zgosalvez/github-actions-report-lcov Better fit than the lcov-list+sticky-comment combo: - Single step instead of two (render + post) - Uploads a browsable HTML report as a PR artifact, so the full per-directory drill-down is one click away even if the inline comment truncates anything - update-comment: true keeps PR clean across re-runs --- .github/workflows/build_debug.yml | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build_debug.yml b/.github/workflows/build_debug.yml index 27b685ecf..5ec4248a5 100644 --- a/.github/workflows/build_debug.yml +++ b/.github/workflows/build_debug.yml @@ -73,31 +73,14 @@ jobs: name: pktvisor verbose: true - - name: Render coverage summary + - name: Report coverage to PR if: github.event_name == 'pull_request' - working-directory: ${{github.workspace}}/build - run: | - { - echo "## Code Coverage" - echo "" - echo '```' - lcov --summary coverage.info 2>/dev/null - echo '```' - echo "" - echo "
Per-file breakdown" - echo "" - echo '```' - lcov --list coverage.info 2>/dev/null - echo '```' - echo "
" - } > coverage-comment.md - - - name: Post coverage comment - if: github.event_name == 'pull_request' - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 #v3.0.4 + uses: zgosalvez/github-actions-report-lcov@7d72c57ce4bc101a4a0bf9d726b6c435abde8439 #v7.0.10 with: - header: code-coverage - path: build/coverage-comment.md + coverage-files: build/coverage.info + artifact-name: code-coverage-report + github-token: ${{ secrets.GITHUB_TOKEN }} + update-comment: true build: runs-on: ubuntu-latest