diff --git a/.codecov.yml b/.codecov.yml index b65be24..d0e6b47 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -22,13 +22,7 @@ ignore: - "build/_deps/**/*" - "test/**/*" -# Strip the GitHub Actions container-mount prefix (/__w///) -# from paths emitted by gcovr --root "$GITHUB_WORKSPACE". -# Without this, Codecov cannot match coverage lines to repo files. -fixes: - - "/__w/git-wip/git-wip/::" - comment: layout: "reach,diff,flags,files,footer" behavior: default - require_changes: no \ No newline at end of file + require_changes: no diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00805cc..01df302 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -192,7 +192,7 @@ jobs: retention-days: 7 coverage: - name: Coverage (debian:stable / clang / Debug) + name: Coverage (debian:stable / gcc / Debug) runs-on: ubuntu-latest @@ -206,7 +206,7 @@ jobs: - name: Install dependencies env: DEBIAN_FRONTEND: noninteractive - run: bash dependencies.sh --compiler=clang + run: bash dependencies.sh --compiler=gcc --coverage - name: Cache CMake FetchContent (spdlog, clipp, fmt) uses: actions/cache@v5 @@ -222,39 +222,19 @@ jobs: git config --global user.name "GitHub Actions" git config --global init.defaultBranch master - - name: Build with coverage + - name: Build, test, and collect coverage env: - CC: clang - CXX: clang++ + CC: gcc + CXX: g++ CI: "1" - COVERAGE: "1" - run: make TYPE=Debug test - - - name: Collect coverage data - run: | - gcovr --cobertura coverage.xml \ - --gcov-executable 'llvm-cov gcov' \ - --gcov-ignore-errors=no_working_dir_found \ - --exclude 'build/_deps/' \ - --exclude 'test/' \ - --root "$GITHUB_WORKSPACE" - - - name: Verify coverage file - run: | - ls -la coverage.xml - head -50 coverage.xml - - - name: Install codecov dependencies - env: - DEBIAN_FRONTEND: noninteractive - run: apt-get update && apt-get install -y curl gpg + run: make CC=gcc CXX=g++ TYPE=Debug coverage REBUILD=1 - name: Upload to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage.xml + files: ./coverage.info flags: unittests - name: debian-stable-clang-debug + name: debian-stable-gcc-debug verbose: true fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index 0fc9d21..00ab863 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build/ coverage-report/ coverage.xml +coverage.info ### C++ # Prerequisites diff --git a/CMakeLists.txt b/CMakeLists.txt index f913582..6e929db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Enable generation of compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# Code coverage options option(WIP_COVERAGE "Enable code coverage instrumentation" OFF) -if(WIP_COVERAGE) - add_compile_options(-fprofile-arcs -ftest-coverage) - add_link_options(-fprofile-arcs -ftest-coverage) -endif() include(FetchContent) include(CheckCXXSourceCompiles) @@ -26,6 +21,15 @@ FetchContent_Declare( FetchContent_MakeAvailable(spdlog) +# Code coverage options — applied AFTER FetchContent so that third-party +# dependencies (spdlog, fmt, …) are NOT instrumented. Mixing gcov versions +# between the host compiler and a pre-built dep causes "version mismatch" +# errors at runtime that pollute test output. +if(WIP_COVERAGE) + add_compile_options(-fprofile-arcs -ftest-coverage) + add_link_options(-fprofile-arcs -ftest-coverage) +endif() + # Detect whether the compiler's standard library ships (C++23 P2093). # GCC < 14 and some older clangs lack it even with -std=c++23. check_cxx_source_compiles(" diff --git a/Makefile b/Makefile index 82a300e..03daccc 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,12 @@ NPROC ?= $(shell nproc || echo 1) CC ?= $(shell which clang gcc cc | head -n1) CXX ?= $(shell which clang g++ c++ | head -n1) COVERAGE ?= false +# Locate the right gcov-compatible tool to match the compiler: +# - if CC is clang, use llvm-cov (prefer plain symlink, fall back to versioned) +# - otherwise use plain gcov +# Use = (recursive) not := (immediate) so CC override on the command line is respected. +_IS_CLANG = $(shell $(CC) --version 2>/dev/null | grep -c clang) +GCOV_TOOL = $(if $(filter 1,$(_IS_CLANG)),$(shell which llvm-cov 2>/dev/null || ls /usr/bin/llvm-cov-* 2>/dev/null | sort -V | tail -1) gcov,gcov) $(info ## TYPE=${TYPE} CC=${CC} CXX=${CXX} COVERAGE=${COVERAGE}) # Coverage flag for CMake @@ -61,13 +67,34 @@ test: ## run unit tests (with ctest, uses REBUILD={true,false}, COVERAGE={true,f ${Q}cd "${BUILD}"/ && ctest -C "${TYPE}" $(if ${CI},--output-on-failure -VV) ${Q}echo " ✅ Unit tests complete." -coverage: ## check code coverage (with gcovr, uses REBUILD={true,false}) +coverage: ## check code coverage (with lcov, uses REBUILD={true,false}) ${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/) - ${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" -DWIP_COVERAGE=ON + ${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" -DWIP_COVERAGE=ON -DCMAKE_C_COMPILER="${CC}" -DCMAKE_CXX_COMPILER="${CXX}" ${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}" - ${Q}cd "${BUILD}"/ && ctest -C "${TYPE}" -VV + ${Q}cd "${BUILD}"/ && ctest -C "${TYPE}" $(if ${CI},--output-on-failure -VV) + ${Q}printf '#!/bin/sh\nexec $(GCOV_TOOL) "$$@"\n' > "${BUILD}/gcov-tool.sh" && chmod +x "${BUILD}/gcov-tool.sh" + ${Q}lcov --capture --directory "${BUILD}/src" \ + --directory "${BUILD}/test" \ + --gcov-tool "${BUILD}/gcov-tool.sh" \ + --ignore-errors inconsistent \ + --ignore-errors inconsistent \ + --ignore-errors format \ + --ignore-errors unsupported \ + --output-file coverage.info + ${Q}lcov --remove coverage.info '/usr/*' '*/test/*' \ + --ignore-errors inconsistent \ + --ignore-errors inconsistent \ + --ignore-errors format \ + --ignore-errors unsupported \ + --ignore-errors corrupt \ + --output-file coverage.info ${Q}mkdir -p coverage-report - ${Q}gcovr --html coverage-report/index.html --root . "${BUILD}" + ${Q}genhtml coverage.info --output-directory coverage-report \ + --ignore-errors inconsistent \ + --ignore-errors inconsistent \ + --ignore-errors corrupt \ + --ignore-errors unsupported \ + --ignore-errors category ${Q}echo " ✅ Coverage report generated in coverage-report/" install: ## install the package (to the `PREFIX`, uses REBUILD={true,false}) diff --git a/dependencies.sh b/dependencies.sh index 795ebc9..03b1c73 100755 --- a/dependencies.sh +++ b/dependencies.sh @@ -113,6 +113,7 @@ function want_one_of() { # --------------------------------------------------------------------------- compiler="" # empty → auto-select via must_have_one_of +coverage=0 # --coverage → install lcov, curl, gpg for arg in "$@" ; do case "$arg" in @@ -122,9 +123,11 @@ for arg in "$@" ; do --compiler=clang) compiler=clang ;; --compiler=*) die "unknown --compiler value '${arg#--compiler=}' (expected gcc or clang)" ;; + --coverage) + coverage=1 ;; -h|--help) cat <<'EOF' -Usage: dependencies.sh [--compiler=] [-h|--help] +Usage: dependencies.sh [--compiler=] [--coverage] [-h|--help] Install build dependencies for git-wip. @@ -133,6 +136,8 @@ Options: --compiler=clang Install clang (no flag) Install whichever of clang/gcc is available (auto-select) + --coverage Also install coverage tools (lcov, curl, gpg) + -h, --help Show this help and exit EOF exit 0 @@ -205,7 +210,6 @@ case "$pkg_mgr" in libgmock-dev libgtest-dev libgit2-dev - gcovr ) ;; dnf) @@ -213,14 +217,12 @@ case "$pkg_mgr" in gtest-devel gmock-devel libgit2-devel - gcovr ) ;; pacman) # Arch uses different package names packages+=( libgit2 - gcovr ) # Replace base packages with Arch equivalents packages=( "${packages[@]/ninja-build/ninja}" ) @@ -233,6 +235,21 @@ case "$pkg_mgr" in ;; esac +# Coverage tools (only when --coverage is requested) +if [ "$coverage" = 1 ]; then + case "$pkg_mgr" in + apt) + packages+=( lcov curl gpg ) + ;; + dnf) + packages+=( lcov curl gnupg2 ) + ;; + pacman) + packages+=( lcov curl gnupg ) + ;; + esac +fi + set -e -x case "$pkg_mgr" in