Skip to content

Commit

Permalink
Merge #191: cmake: Add Coverage script
Browse files Browse the repository at this point in the history
7669f29 cmake: Add `Coverage` and `CoverageFuzz` scripts (Hennadii Stepanov)

Pull request description:

  Using separated scripts provides additional flexibility as there is no need to specify coverage report specific options during the build configuration stage.

  Examples of usage on Ubuntu 24.04 with [LCOV v2.0](https://packages.ubuntu.com/noble/lcov):

  - total coverage:
  ```
  $ cmake -B build -DCMAKE_BUILD_TYPE=Coverage -DCMAKE_CXX_FLAGS="-fprofile-update=atomic"
  $ cmake --build build
  $ cmake -DLCOV_OPTS="--ignore-errors mismatch" -P build/Coverage.cmake
  # OR
  $ cmake \
      -DJOBS=$(nproc) \
      -DEXTENDED_FUNCTIONAL_TESTS=ON \
      -DLCOV_OPTS="--rc branch_coverage=1 --ignore-errors mismatch" \
      -P build/Coverage.cmake
  ```

  - fuzz coverage:
  ```
  $ cmake -B build -DCMAKE_BUILD_TYPE=Coverage -DCMAKE_CXX_FLAGS="-fprofile-update=atomic" -DBUILD_FUZZ_BINARY=ON
  $ cmake --build build
  $ cmake \
      -DFUZZ_SEED_CORPUS_DIR=<path/to/seed/corpus> \
      -DLCOV_OPTS="--rc branch_coverage=1 --ignore-errors mismatch" \
      -P build/CoverageFuzz.cmake
  ```

  This PR supports both LCOV v1 and LCOV v2. Hence, it is possible to test it on Ubuntu 24.04.

Top commit has no ACKs.

Tree-SHA512: a57be6afe75771925b0524fd2333dbfc2bbc8a5cec2cc6959f34152223b2be8a2531363e29805c944687ad137224cec831138adc6fcf2d9544ffbdeb3219a6c3
  • Loading branch information
hebasto committed Jun 8, 2024
2 parents 29d3b52 + 7669f29 commit 2677998
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 7 deletions.
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,22 @@ else()
unset(c_flags_debug_overridden)
endif()

set(CMAKE_CXX_FLAGS_COVERAGE "-Og --coverage")
set(CMAKE_OBJCXX_FLAGS_COVERAGE "-Og --coverage")
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "--coverage")
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "--coverage")
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
if(NOT "Coverage" IN_LIST CMAKE_CONFIGURATION_TYPES)
list(APPEND CMAKE_CONFIGURATION_TYPES Coverage)
endif()
endif()

configure_file(cmake/script/Coverage.cmake Coverage.cmake COPYONLY)
configure_file(cmake/script/CoverageFuzz.cmake CoverageFuzz.cmake COPYONLY)
configure_file(cmake/script/CoverageInclude.cmake.in CoverageInclude.cmake @ONLY)
configure_file(contrib/filter-lcov.py filter-lcov.py COPYONLY)

include(cmake/optional.cmake)

# Don't allow extended (non-ASCII) symbols in identifiers. This is easier for code review.
Expand Down
5 changes: 5 additions & 0 deletions cmake/cov_tool_wrapper.sh.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (c) 2024-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.

exec @COV_TOOL@ "$@"
77 changes: 77 additions & 0 deletions cmake/script/Coverage.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright (c) 2024-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.

include(${CMAKE_CURRENT_LIST_DIR}/CoverageInclude.cmake)

set(functional_test_runner test/functional/test_runner.py)
if(EXTENDED_FUNCTIONAL_TESTS)
list(APPEND functional_test_runner --extended)
endif()
if(DEFINED JOBS)
list(APPEND CMAKE_CTEST_COMMAND -j ${JOBS})
list(APPEND functional_test_runner -j ${JOBS})
endif()

execute_process(
COMMAND ${CMAKE_CTEST_COMMAND} --build-config Coverage
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${LCOV_COMMAND} --capture --directory src --test-name test_bitcoin --output-file test_bitcoin.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --zerocounters --directory src
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_FILTER_COMMAND} test_bitcoin.info test_bitcoin_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --add-tracefile test_bitcoin_filtered.info --output-file test_bitcoin_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --add-tracefile baseline_filtered.info --add-tracefile test_bitcoin_filtered.info --output-file test_bitcoin_coverage.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${GENHTML_COMMAND} test_bitcoin_coverage.info --output-directory test_bitcoin.coverage
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)

execute_process(
COMMAND ${functional_test_runner}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${LCOV_COMMAND} --capture --directory src --test-name functional-tests --output-file functional_test.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --zerocounters --directory src
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_FILTER_COMMAND} functional_test.info functional_test_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --add-tracefile functional_test_filtered.info --output-file functional_test_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --add-tracefile baseline_filtered.info --add-tracefile test_bitcoin_filtered.info --add-tracefile functional_test_filtered.info --output-file total_coverage.info
COMMAND ${GREP_EXECUTABLE} "%"
COMMAND ${AWK_EXECUTABLE} "{ print substr($3,2,50) \"/\" $5 }"
OUTPUT_FILE coverage_percent.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${GENHTML_COMMAND} total_coverage.info --output-directory total.coverage
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
42 changes: 42 additions & 0 deletions cmake/script/CoverageFuzz.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright (c) 2024-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.

include(${CMAKE_CURRENT_LIST_DIR}/CoverageInclude.cmake)

if(NOT DEFINED FUZZ_SEED_CORPUS_DIR)
set(FUZZ_SEED_CORPUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/qa-assets/fuzz_seed_corpus)
endif()

execute_process(
COMMAND test/fuzz/test_runner.py ${FUZZ_SEED_CORPUS_DIR} --loglevel DEBUG
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${LCOV_COMMAND} --capture --directory src --test-name fuzz-tests --output-file fuzz.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --zerocounters --directory src
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_FILTER_COMMAND} fuzz.info fuzz_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --add-tracefile fuzz_filtered.info --output-file fuzz_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --add-tracefile baseline_filtered.info --add-tracefile fuzz_filtered.info --output-file fuzz_coverage.info
COMMAND ${GREP_EXECUTABLE} "%"
COMMAND ${AWK_EXECUTABLE} "{ print substr($3,2,50) \"/\" $5 }"
OUTPUT_FILE coverage_percent.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${GENHTML_COMMAND} fuzz_coverage.info --output-directory fuzz.coverage
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
56 changes: 56 additions & 0 deletions cmake/script/CoverageInclude.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (c) 2024-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.

if("@CMAKE_CXX_COMPILER_ID@" STREQUAL "Clang")
find_program(LLVM_COV_EXECUTABLE llvm-cov REQUIRED)
set(COV_TOOL "${LLVM_COV_EXECUTABLE} gcov")
else()
find_program(GCOV_EXECUTABLE gcov REQUIRED)
set(COV_TOOL "${GCOV_EXECUTABLE}")
endif()

# COV_TOOL is used to replace a placeholder.
configure_file(
cmake/cov_tool_wrapper.sh.in ${CMAKE_CURRENT_LIST_DIR}/cov_tool_wrapper.sh
FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ
@ONLY
)

find_program(LCOV_EXECUTABLE lcov REQUIRED)
separate_arguments(LCOV_OPTS)
set(LCOV_COMMAND ${LCOV_EXECUTABLE} --gcov-tool ${CMAKE_CURRENT_LIST_DIR}/cov_tool_wrapper.sh ${LCOV_OPTS})

find_program(GENHTML_EXECUTABLE genhtml REQUIRED)
set(GENHTML_COMMAND ${GENHTML_EXECUTABLE} --show-details ${LCOV_OPTS})

find_program(GREP_EXECUTABLE grep REQUIRED)
find_program(AWK_EXECUTABLE awk REQUIRED)

set(LCOV_FILTER_COMMAND ./filter-lcov.py)
list(APPEND LCOV_FILTER_COMMAND -p "/usr/local/")
list(APPEND LCOV_FILTER_COMMAND -p "/usr/include/")
list(APPEND LCOV_FILTER_COMMAND -p "/usr/lib/")
list(APPEND LCOV_FILTER_COMMAND -p "/usr/lib64/")
list(APPEND LCOV_FILTER_COMMAND -p "src/leveldb/")
list(APPEND LCOV_FILTER_COMMAND -p "src/crc32c/")
list(APPEND LCOV_FILTER_COMMAND -p "src/bench/")
list(APPEND LCOV_FILTER_COMMAND -p "src/crypto/ctaes")
list(APPEND LCOV_FILTER_COMMAND -p "src/minisketch")
list(APPEND LCOV_FILTER_COMMAND -p "src/secp256k1")
list(APPEND LCOV_FILTER_COMMAND -p "depends")

execute_process(
COMMAND ${LCOV_COMMAND} --capture --initial --directory src --output-file baseline.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_FILTER_COMMAND} baseline.info baseline_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
execute_process(
COMMAND ${LCOV_COMMAND} --add-tracefile baseline_filtered.info --output-file baseline_filtered.info
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
23 changes: 16 additions & 7 deletions doc/developer-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,25 +481,34 @@ $ ./test/functional/test_runner.py --valgrind

### Compiling for test coverage

LCOV can be used to generate a test coverage report based upon `make check`
LCOV can be used to generate a test coverage report based upon `ctest`
execution. LCOV must be installed on your system (e.g. the `lcov` package
on Debian/Ubuntu).

To enable LCOV report generation during test runs:

```shell
./configure --enable-lcov
make
make cov
cmake -B build -DCMAKE_BUILD_TYPE=Coverage
cmake --build build
cmake -P build/Coverage.cmake

# A coverage report will now be accessible at `./test_bitcoin.coverage/index.html`,
# which covers unit tests, and `./total.coverage/index.html`, which covers
# A coverage report will now be accessible at `./build/test_bitcoin.coverage/index.html`,
# which covers unit tests, and `./build/total.coverage/index.html`, which covers
# unit and functional tests.
```

Additional LCOV options can be specified using `LCOV_OPTS`, but may be dependant
on the version of LCOV. For example, when using LCOV `2.x`, branch coverage can be
enabled by setting `LCOV_OPTS="--rc branch_coverage=1"`, when configuring.
enabled by setting `LCOV_OPTS="--rc branch_coverage=1"`:

```
cmake -DLCOV_OPTS="--rc branch_coverage=1" -P build/Coverage.cmake
```

To enable test parallelism:
```
cmake -DJOBS=$(nproc) -P build/Coverage.cmake
```

### Performance profiling with perf

Expand Down

0 comments on commit 2677998

Please sign in to comment.