Verified Commit 27c83d14 authored by Alberto Miranda's avatar Alberto Miranda ♨️
Browse files

docs: Add documentation for coverage facilities

parent 8d992d17
Loading
Loading
Loading
Loading
+219 −0
Original line number Original line Diff line number Diff line
# Coverage

This guide describes how to generate coverage reports for the GekkoFS 
source code. Please note that the GekkoFS CI pipelines already generate 
coverage reports automatically for newer commits. Thus, the procedures 
described here are focused towards developers willing to generate a coverage 
report in their local workstations to check whether their work is properly 
covered by tests.

## TLDR

```shell
# 1. configure appropriate coverage flags, etc.
$ cmake --preset coverage

# 2. build GekkoFS and co.
$ cmake --build builds/coverage --parallel 8

# 3. generate zero coverage information (builds/coverage/zerocount.info)
$ cmake --build builds/coverage --target coverage-zerocount

# 4. run ALL TESTS to generate coverage data files
$ ctest --test-dir builds/user-gcc-coverage --parallel 8

# 5. generate a unified LCOV trace file (builds/coverage/coverage.info)
$ cmake --build builds/coverage --target coverage-unified

# 6. (optional) print summary stats
$ cmake --build builds/coverage --target coverage-summary

# 7. (optional) generate a HTML report (builds/coverage/coverage_html)
$ cmake --build builds/coverage --target coverage-html

# 8. (optional) generate a Cobertura XML report (builds/coverage/coverage-cobertura.xml)
$ cmake --build builds/coverage --target coverage-cobertura
```

## Configuring the build

In order to generate coverage information, the source code needs to be 
compiled and linked in debug mode with (at least) the `--coverage` flags. 
While the flags can be set manually by overriding several 
variables such as `CMAKE_CXX_FLAGS`, since v3.19 supports 
[configuration presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html)
that greatly simplify this process. Thus, GekkoFS provides a CMake 
`default-coverage` preset that can be used to configure a coverage build:

```shell
$ cmake --preset=default-coverage
Preset CMake variables:

  CLIENT_LOG_MESSAGE_SIZE="512"
  CMAKE_BUILD_TYPE="Coverage"
  CMAKE_CXX_COMPILER="/usr/bin/g++"
  CMAKE_CXX_FLAGS="-Wall -Wextra -fdiagnostics-color=always --pedantic -Wno-unused-parameter -Wno-missing-field-initializers -DGKFS_DEBUG_BUILD -DHERMES_DEBUG_BUILD"
  CMAKE_CXX_FLAGS_COVERAGE="-Og -g --coverage -fkeep-static-functions"
  CMAKE_C_COMPILER="/usr/bin/gcc"
  CMAKE_C_FLAGS_COVERAGE="-Og -g --coverage -fkeep-static-functions"
  CMAKE_EXE_LINKER_FLAGS_COVERAGE="--coverage"
  CMAKE_MAP_IMPORTED_CONFIG_COVERAGE="Coverage;RelWithDebInfo;Release;Debug;"
  CMAKE_SHARED_LINKER_FLAGS_COVERAGE="--coverage"
  ENABLE_CLIENT_LOG:BOOL="TRUE"
  GKFS_BUILD_DOCUMENTATION:BOOL="TRUE"
  GKFS_BUILD_TESTS:BOOL="TRUE"
  GKFS_CHUNK_STATS:BOOL="TRUE"
  GKFS_ENABLE_PARALLAX:BOOL="FALSE"
  GKFS_ENABLE_PROMETHEUS:BOOL="TRUE"
  GKFS_ENABLE_ROCKSDB:BOOL="TRUE"
  GKFS_GENERATE_COVERAGE_REPORTS:BOOL="TRUE"
  GKFS_INSTALL_TESTS:BOOL="TRUE"
  SYMLINK_SUPPORT:BOOL="TRUE"

-- The C compiler identification is GNU 11.1.0
-- The CXX compiler identification is GNU 11.1.0
   [...]
```

## Generating a coverage report

GekkoFS relies on the `scripts/dev/coverage.py` script to manage all its 
coverage generation and reporting needs. The script internally calls
[`lcov`](https://github.com/linux-test-project/lcov) as needed to generate 
both intermediate and unified coverage traces, and 
[`lcov_cobertura`](https://pypi.org/project/lcov-cobertura/) to generate 
Cobertura XML reports.

As described by the `lcov` man page, the recommended procedure to generate 
coverage data for a test case is the following:

1. create baseline coverage data file
2. execute some tests to gather coverage data files
3. create a coverage data file for the tests
4. combine baseline and test coverage data

Since `coverage.py` is internally based on `lcov`, the script offers the 
following working modes:

- `capture`: Generate a `lcov` tracefile from existing coverage data. This 
  working mode is intended for generating initial zero coverage data as well 
  as coverage data from one or many tests.
- `merge`: Merge existing `lcov` tracefiles into a unified tracefile. This 
  working mode is intended to be used for merging several `lcov` tracefiles 
  into a unified tracefile. For instance, it can be used to merge the 
  tracefile for initial zero coverage with the traces for captured from 
  running tests.
- `summary`: Print summary coverage information for a specified `lcov` tracefile.
- `html_report`: Generate a HTML report for a specified `lcov` tracefile.
- `cobertura`: Generate a Cobertura XML report for a specified `lcov` tracefile.

Thus, in order to generate a coverage report for tests `test_directories` and 
`test_syscalls`, we can run the following commmands from the project's 
source directory:

```shell
SOURCE_DIR="$PWD"
BUILD_DIR="$SOURCE_DIR/build"
COVERAGE_DIR="$BUILD_DIR/coverage"

# generate a zero coverage tracefile while excluding any coverage data 
# referring to source files in $BUILD_DIR/_deps and $SOURCE_DIR/external
$ scripts/dev/coverage.py \
    --initial \
    --root-directory "$BUILD_DIR" \
    --output-file \
    "$COVERAGE_DIR/zerocount.info" \
    --sources-directory "$SOURCE_DIR" \
    --exclude "$BUILD_DIR/_deps/*" \
    --exclude "$SOURCE_DIR/external/*"
    
# execute the tests we are interested in
$ ctest --test-dir $BUILD_DIR -R test_directories
$ ctest --test-dir $BUILD_DIR -R test_syscalls

# generate a coverage tracefile for the generated coverage data while excluding 
# any coverage data referring to source files in $BUILD_DIR/_deps and 
# $SOURCE_DIR/external
$ scripts/dev/coverage.py \
    --root-directory "$BUILD_DIR" \
    --output-file \
    "$COVERAGE_DIR/directories_and_syscalls_tests.info" \
    --sources-directory "$SOURCE_DIR" \
    --exclude "$BUILD_DIR/_deps/*" \
    --exclude "$SOURCE_DIR/external/*"
    
# merge the generated tracefiles (i.e. $COVERAGE_DIR/zerocount.info and 
# $COVERAGE_DIR/directories_and_syscalls_tests.info) into a unified file
$ scripts/dev/coverage.py \
    merge \
    --output-file $COVERAGE_DIR/coverage.info \
    --search-pattern "$COVERAGE_DIR/*.info"
    
# print a summary, generate a HTML report, etc.
$ scripts/dev/coverage.py \
    summary \
    --input-tracefile $COVERAGE_DIR/coverage.info
Reading tracefile build/coverage/coverage.info
Summary coverage rate:
  lines......: 75.1% (6332 of 8430 lines)
  functions..: 83.2% (957 of 1150 functions)
  branches...: 42.1% (5987 of 14224 branches)
```

Since running all these steps manually is cumbersome and error-prone, GekkoFS' 
build script provides several targets to simplify report generation by 
automatically setting directories, exclusions, and so on:

- `coverage-zerocount`: Capture initial zero coverage data and write it to
  `${COVERAGE_ZEROCOUNT_TRACEFILE}`.
- `coverage-capture`: Capture coverage data from existing `.gcda` files and
  write it to `${COVERAGE_CAPTURE_TRACEFILE}`.
- `coverage-unified`: Merge any coverage data files found in
  `COVERAGE_OUTPUT_DIR` and generate a unified coverage trace.
- `coverage-summary`: Print a summary of the coverage data found in
  `${COVERAGE_UNIFIED_TRACEFILE}`.
- `coverage-html_report`: Write a HTML report from the coverage data
  found in `${COVERAGE_UNIFIED_TRACEFILE}`.
- `coverage-cobertura`: Write a Cobertura report from the coverage data
  found in `${COVERAGE_UNIFIED_TRACEFILE}`.

```{important}
It is possible to override the default values for all the `COVERAGE_` CMake 
variables mentioned above by setting them during CMake's project 
configuration, e.g. 

    `cmake --preset=default-coverage -DCOVERAGE_OUTPUT_DIR=/tmp`
```

The steps above can then be simplified:

```shell

# write a zero coverage tracefile in $COVERAGE_DIR/zerocount.info
# (with automatic exclusions)
$ cmake --build builds/coverage --target coverage-zerocount
[0/1] Generating zerocount coverage tracefile
Output written to '/home/user/gekkofs/build/coverage/zerocount.info'

# execute the tests we are interested in
$ ctest --test-dir $BUILD_DIR -R test_directories
$ ctest --test-dir $BUILD_DIR -R test_syscalls
 
# This executes the following:
# 1. write a coverage tracefile for the generated coverage data into
#    $COVERAGE_DIR/capture.info (with automatic exclusions)
# 2. merge $COVERAGE_DIR/zerocount.info with $COVERAGE_DIR/capture.info and 
#    write the result into $COVERAGE_DIR/coverage.info
# 3. print a stats report for $COVERAGE_DIR/capture.info
$ cmake --build $BUILD_DIR -t coverage-summary
[1/3] Generating capture coverage tracefile
Output written to '/home/user/gekkofs/source/build/coverage/capture.info'
[2/3] Generating unified coverage tracefile
Output written to '/home/user/gekkofs/source/build/coverage/coverage.info'
[3/3] Gathering coverage information
Reading tracefile /home/user/gekkofs/build/coverage/coverage.info
Summary coverage rate:
  lines......: 75.1% (6332 of 8430 lines)
  functions..: 83.2% (957 of 1150 functions)
  branches...: 42.1% (5987 of 14224 branches)
```
+1 −0
Original line number Original line Diff line number Diff line
@@ -56,6 +56,7 @@ Architecture
   :caption: Developer documentation
   :caption: Developer documentation


   devs/testing
   devs/testing
   devs/coverage
   api/reference
   api/reference


Indices and tables
Indices and tables