From edab12e5521f2beb97db9ed6aea1b48dff2dba62 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 2 Mar 2023 10:44:01 +0100 Subject: [PATCH 01/17] Add support for CMake Presets Makes project configuration simpler. Reference: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html --- .gitignore | 4 + .gitlab-ci.yml | 48 ++--------- CMakePresets.json | 199 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 42 deletions(-) create mode 100644 CMakePresets.json diff --git a/.gitignore b/.gitignore index e07ffaac9..6fb38b1ce 100644 --- a/.gitignore +++ b/.gitignore @@ -86,4 +86,8 @@ playground .hidden_playground/ test/build/ build/ +builds/ .run/ + +# Allow users to provide their own CMake presets +CMakeUserPresets.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8bd60cb4..de0a60093 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,23 +55,8 @@ gkfs: - sed -i 's/constexpr auto use_mtime = false;/constexpr auto use_mtime = true;/g' "${CI_PROJECT_DIR}/include/config.hpp" - sed -i 's/constexpr auto use_link_cnt = false;/constexpr auto use_link_cnt = true;/g' "${CI_PROJECT_DIR}/include/config.hpp" - sed -i 's/constexpr auto use_blocks = false;/constexpr auto use_blocks = true;/g' "${CI_PROJECT_DIR}/include/config.hpp" - - mkdir -p ${BUILD_PATH} && cd ${BUILD_PATH} - - cmake - -Wdev - -Wdeprecate - -DCMAKE_BUILD_TYPE=Debug - -DGKFS_ENABLE_CODE_COVERAGE:BOOL=ON - -DGKFS_BUILD_TESTS:BOOL=ON - -DGKFS_INSTALL_TESTS:BOOL=ON - -DCMAKE_INSTALL_PREFIX=${INSTALL_PATH} - -DGKFS_USE_GUIDED_DISTRIBUTION:BOOL=ON - -DGKFS_ENABLE_PARALLAX:BOOL=ON - -DGKFS_ENABLE_ROCKSDB:BOOL=ON - -DGKFS_CHUNK_STATS:BOOL=ON - -DGKFS_ENABLE_PROMETHEUS:BOOL=ON - -DGKFS_RENAME_SUPPORT:BOOL=ON - ${CI_PROJECT_DIR} - - make -j$(nproc) install + - cmake --preset ci-coverage + - cmake --build ${BUILD_PATH} -j $(nproc) --target install # reduce artifacts size - ${CI_SCRIPTS_DIR}/trim_build_artifacts.sh ${BUILD_PATH} artifacts: @@ -86,21 +71,8 @@ gkfwd: interruptible: true needs: [] script: - - mkdir -p ${BUILD_PATH} && cd ${BUILD_PATH} - - cmake - -Wdev - -Wdeprecate - -DCMAKE_BUILD_TYPE=Debug - -DGKFS_ENABLE_CODE_COVERAGE:BOOL=ON - -DGKFS_BUILD_TESTS:BOOL=ON - -DGKFS_INSTALL_TESTS:BOOL=ON - -DGKFS_ENABLE_FORWARDING:BOOL=ON - -DGKFS_ENABLE_AGIOS:BOOL=ON - -DGKFS_ENABLE_PARALLAX:BOOL=OFF - -DGKFS_ENABLE_ROCKSDB:BOOL=ON - -DCMAKE_INSTALL_PREFIX=${INSTALL_PATH} - ${CI_PROJECT_DIR} - - make -j$(nproc) install + - cmake --preset ci-forwarding-debug + - cmake --build ${BUILD_PATH} -j $(nproc) --target install # reduce artifacts size - ${CI_SCRIPTS_DIR}/trim_build_artifacts.sh ${BUILD_PATH} artifacts: @@ -270,16 +242,8 @@ documentation: when: always script: - - mkdir -p ${BUILD_PATH} && cd ${BUILD_PATH} - - cmake - -Wdev - -Wdeprecate - -DCMAKE_BUILD_TYPE=Debug - -DGKFS_BUILD_DOCUMENTATION:BOOL=ON - -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_PATH} - -DCMAKE_INSTALL_PREFIX=${INSTALL_PATH} - ${CI_PROJECT_DIR} - - make docs + - cmake --preset ci-docs + - cmake --build ${BUILD_PATH} --target docs artifacts: paths: - ${BUILD_PATH}/docs diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..6f59bbe17 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,199 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 19, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "displayName": "default", + "description": "Sets prefix, build, and install directories as well as common options", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/builds/${presetName}", + "cacheVariables": { + "GKFS_ENABLE_ROCKSDB": true, + "GKFS_ENABLE_PARALLAX": false, + "GKFS_BUILD_TESTS": true, + "GKFS_INSTALL_TESTS": true, + "ENABLE_CLIENT_LOG": true, + "CLIENT_LOG_MESSAGE_SIZE": "512", + "SYMLINK_SUPPORT": false + }, + "warnings": { + "dev": true, + "deprecated": true + } + }, + { + "name": "forwarding", + "inherits": "default", + "hidden": true, + "cacheVariables": { + "GKFS_ENABLE_FORWARDING": true + } + }, + { + "name": "release", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CXX_FLAGS": "-DNDEBUG -O3" + } + }, + { + "name": "debug", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CXX_FLAGS": "-Wall -Wextra -fdiagnostics-color=always --pedantic -Wno-unused-parameter -Wno-missing-field-initializers -DGKFS_DEBUG_BUILD -DHERMES_DEBUG_BUILD" + } + }, + { + "name": "coverage", + "hidden": true, + "inherits": "debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Coverage", + "CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-static-functions", + "CMAKE_C_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-static-functions", + "CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage", + "CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage", + "CMAKE_MAP_IMPORTED_CONFIG_COVERAGE": "Coverage;RelWithDebInfo;Release;Debug;", + "GKFS_GENERATE_COVERAGE_REPORTS": true + } + }, + { + "name": "docs", + "inherits": "debug", + "cacheVariables": { + "GKFS_BUILD_DOCUMENTATION": true + } + }, + { + "name": "ci", + "hidden": true, + "binaryDir": "${sourceDir}/gkfs/build", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/gkfs/install", + "GKFS_USE_GUIDED_DISTRIBUTION": true, + "GKFS_ENABLE_PARALLAX": true, + "GKFS_CHUNK_STATS": true, + "GKFS_ENABLE_PROMETHEUS": true, + "GKFS_RENAME_SUPPORT": true + } + }, + { + "name": "default-debug", + "displayName": "Default gekkofs (debug)", + "inherits": [ + "default", + "debug" + ] + }, + { + "name": "default-coverage", + "displayName": "Default gekkofs (coverage)", + "inherits": [ + "default", + "coverage" + ] + }, + { + "name": "default-release", + "displayName": "Default gekkofs (release)", + "inherits": [ + "default", + "release" + ] + }, + { + "name": "forwarding-debug", + "displayName": "Forwarding gekkofs (debug)", + "inherits": [ + "forwarding", + "debug" + ] + }, + { + "name": "forwarding-coverage", + "displayName": "Forwarding gekkofs (coverage)", + "inherits": [ + "forwarding", + "coverage" + ] + }, + { + "name": "forwarding-release", + "displayName": "Forwarding gekkofs (release)", + "inherits": [ + "forwarding", + "release" + ] + }, + { + "name": "ci-debug", + "displayName": "Default gekkofs (debug, CI flags)", + "inherits": [ + "ci", + "default", + "debug" + ] + }, + { + "name": "ci-coverage", + "displayName": "Default gekkofs (coverage, CI flags)", + "inherits": [ + "ci", + "default", + "coverage" + ] + }, + { + "name": "ci-docs", + "displayName": "Documentation (CI flags)", + "inherits": "ci-debug", + "cacheVariables": { + "GKFS_BUILD_DOCUMENTATION": true + } + }, + { + "name": "ci-release", + "displayName": "Default gekkofs (release, CI flags)", + "inherits": [ + "ci", + "default", + "release" + ] + }, + { + "name": "ci-forwarding-debug", + "displayName": "Forwarding gekkofs (debug, CI flags)", + "inherits": [ + "ci", + "forwarding", + "debug" + ] + }, + { + "name": "ci-forwarding-coverage", + "displayName": "Forwarding gekkofs (coverage, CI flags)", + "inherits": [ + "ci", + "forwarding", + "coverage" + ] + }, + { + "name": "ci-forwarding-release", + "displayName": "Forwarding gekkofs (release, CI flags)", + "inherits": [ + "ci", + "forwarding", + "release" + ] + } + ] +} -- GitLab From e7988741a78bc2fe66cd01e76cbcd78b77ebe625 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 2 Mar 2023 14:50:37 +0100 Subject: [PATCH 02/17] Docker: Update `core:0.9.2` image - Update cmake to v3.25.2 - Add ninja-build --- docker/0.9.2/core/Dockerfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/0.9.2/core/Dockerfile b/docker/0.9.2/core/Dockerfile index bf1b2327e..a55434924 100644 --- a/docker/0.9.2/core/Dockerfile +++ b/docker/0.9.2/core/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get update && \ automake \ gcc \ g++ \ + ninja-build \ procps \ # AGIOS dependencies libconfig-dev \ @@ -28,12 +29,12 @@ RUN apt-get update && \ # GekkoFS dependencies libboost-program-options-dev \ uuid-dev && \ - # install cmake 3.14 since it's needed for some dependencies - curl -OL https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5-Linux-x86_64.sh && \ - chmod u+x ./cmake-3.14.5-Linux-x86_64.sh && \ - ./cmake-3.14.5-Linux-x86_64.sh --skip-license --prefix=/usr && \ + # install cmake 3.14+ since it's needed for some dependencies + curl -OL https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-Linux-x86_64.sh && \ + chmod u+x ./cmake-3.25.2-Linux-x86_64.sh && \ + ./cmake-3.25.2-Linux-x86_64.sh --skip-license --prefix=/usr && \ # Clean apt cache to reduce image layer size rm -rf /var/lib/apt/lists/* && \ # Clean apt caches of packages apt-get clean && apt-get autoclean && \ - rm ./cmake-3.14.5-Linux-x86_64.sh + rm ./cmake-3.25.2-Linux-x86_64.sh -- GitLab From 8e96e0239fd70bc301fda70f894e70b59a81af17 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 9 Mar 2023 20:55:11 +0100 Subject: [PATCH 03/17] Replace ci/coverage.sh with dev/coverage.py The new script is written in Python and is therefore more robust and easier to modify if needed. --- scripts/ci/coverage.sh | 310 ------------------------ scripts/dev/coverage.py | 517 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 517 insertions(+), 310 deletions(-) delete mode 100755 scripts/ci/coverage.sh create mode 100755 scripts/dev/coverage.py diff --git a/scripts/ci/coverage.sh b/scripts/ci/coverage.sh deleted file mode 100755 index ce7ef5ae8..000000000 --- a/scripts/ci/coverage.sh +++ /dev/null @@ -1,310 +0,0 @@ -#!/usr/bin/env bash -################################################################################ -# Copyright 2018-2022, Barcelona Supercomputing Center (BSC), Spain # -# Copyright 2015-2022, Johannes Gutenberg Universitaet Mainz, Germany # -# # -# This software was partially supported by the # -# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). # -# # -# This software was partially supported by the # -# ADA-FS project under the SPPEXA project funded by the DFG. # -# # -# This file is part of GekkoFS. # -# # -# GekkoFS is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# GekkoFS is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with GekkoFS. If not, see . # -# # -# SPDX-License-Identifier: GPL-3.0-or-later # -################################################################################ - -# default values -export CCOV_ROOT_DIR="${PWD}" -export CCOV_BUILD_DIR="${PWD}" -export CCOV_MODE="" -export CCOV_CAPTURE_NAME="" -export CCOV_EXCLUSIONS_FILE=".coverage-exclusions" -export CCOV_LOG_FILE="/dev/stdout" -export CCOV_VERBOSE=false -export GCOVR_EXTRA_OPTIONS=() - -usage() { - -# `cat << EOF` This means that cat should stop reading when EOF is detected -cat << EOF -Usage: coverage.sh MODE [options] -- [extra_gcovr_options] - -A helper script to capture coverage information and generate reports. - -Mode: - -c, --capture NAME Capture coverage data and generate a JSON report for it - in $PWD/.coverage/partial//. - -m, --merge Combine coverage data from several JSON reports and - produce a Cobertura XML report. - -Options: - -h, --help Show this help message, then exit. - -r, --root-dir ROOT_DIR - The root directory of the target source files. - Defaults to '$PWD', the current directory. - -b, --build-dir BUILD_DIR - The build directory for the project. - Defaults to '$PWD', the current directory. - -e, --exclusions EXCLUSIONS_FILE - Exclude any source files that match the filters - contained in EXCLUSIONS_FILE. Each filter must be in a - line of its own and may include optional [[ROOT_DIR]] - and/or [[BUILD_DIR]] tags that will be expanded with the - appropriate values. - Defaults to .coverage-exclusions. - -l, --log-file LOG_FILE - Redirect output to LOG_FILE. - -v, --verbose - Increase verbosity. -EOF -# EOF is found above and hence cat command stops reading. This is equivalent to -# echo but much neater when printing out. -} - -parse_args() { - - # $@ is all command line parameters passed to the script. - # -o is for short options like -v - # -l is for long options with double dash like --version - # the comma separates different long options - options=$(getopt -l \ - "capture:,merge,help,root-dir:,build-dir:,exclusions:,output:,log-file:,verbose" \ - -o "cmhr:b:e:o:l:v" -- "$@") - - # set --: - # If no arguments follow this option, then the positional parameters are - # unset. Otherwise, the positional parameters are set to the arguments, - # even if some of them begin with a ‘-’. - eval set -- "${options}" - - while true; - do - opt=$1 - case $opt in - -c|--capture) - shift - - if [[ -z $1 ]]; then - echo "Missing mandatory argument for '${opt}'." - exit 1 - fi - - if [[ $1 =~ ^--.* ]]; then - echo "Invalid argument '${1}' for '${opt}'." - exit 1 - fi - - export CCOV_MODE="capture" - export CCOV_CAPTURE_NAME=$1 - ;; - - -m|--merge) - CCOV_MODE="merge" - ;; - - -r|--root-dir) - shift - - if [[ -z $1 ]]; then - echo "Missing mandatory argument for '${opt}'." - exit 1 - fi - - if ! [[ -d $1 ]]; then - echo "directory '${1}' does not exist." - exit 1 - fi - - export CCOV_ROOT_DIR=$1 - ;; - - -b|--build-dir) - shift - if [[ -z $1 ]]; then - echo "Missing mandatory argument for '${opt}'." - exit 1 - fi - - if ! [[ -d $1 ]]; then - echo "directory '${1}' does not exist." - exit 1 - fi - - export CCOV_BUILD_DIR=$1 - ;; - - -e|--exclusions) - shift - - if [[ -z $1 ]]; then - echo "Missing mandatory argument for '${opt}'." - exit 1 - fi - - if [[ $1 =~ ^--.* ]]; then - echo "Invalid argument '${1}' for '${opt}'." - exit 1 - fi - - if ! [[ -n $1 && -f $1 && -r $1 ]]; then - echo "file '${1}' does not exist or cannot be read." - exit 1 - fi - - export CCOV_EXCLUSIONS_FILE=$1 - ;; - - -l|--log-file) - shift - - if [[ -z $1 ]]; then - echo "Missing mandatory argument for '${opt}'." - exit 1 - fi - - if [[ $1 =~ ^--.* ]]; then - echo "Invalid argument '${1}' for '${opt}'." - exit 1 - fi - - export CCOV_LOG_FILE=$1 - ;; - - -v|--verbose) - CCOV_VERBOSE=true - ;; - - --) - shift - GCOVR_EXTRA_OPTIONS="$@" - break;; - - -h|--help|*) - usage - exit 0 - ;; - esac - shift - done - - if [[ -z "${CCOV_MODE}" ]]; then - echo -e "ERROR: working mode is mandatory.\n" - usage - exit 1 - fi -} - -parse_exclusions_file() { - - if [[ -n ${CCOV_EXCLUSIONS_FILE} ]]; then - mapfile -t tmp < "${CCOV_EXCLUSIONS_FILE}" - fi - - export CCOV_EXCLUSIONS=() - - for exc in "${tmp[@]}"; - do - # expand [[ROOT_DIR]] - exc="${exc/\[\[ROOT_DIR\]\]/${CCOV_ROOT_DIR}}" - - # expand [[BUILD_DIR]] - CCOV_EXCLUSIONS+=( "${exc/\[\[BUILD_DIR\]\]/${CCOV_BUILD_DIR}}" ) - done -} - -capture() { - - COVERAGE_OUTPUT_DIR="${PWD}/.coverage/partial/${CCOV_CAPTURE_NAME}" - - ! [[ -d "${COVERAGE_OUTPUT_DIR}" ]] && mkdir -p "${COVERAGE_OUTPUT_DIR}" - - if [ "$CCOV_VERBOSE" = true ]; then - printf "Executing capture command:" - printf " gcovr" - printf " --root ${CCOV_ROOT_DIR}" - printf " %s\n" "${CCOV_EXCLUSIONS[@]/#/--exclude=}" - printf " --json" - printf " --output ${COVERAGE_OUTPUT_DIR}/coverage.json" - printf " --verbose" - printf " %s\n" "${GCOVR_EXTRA_OPTIONS[@]}" - fi - - gcovr \ - --root "${CCOV_ROOT_DIR}" \ - "${CCOV_EXCLUSIONS[@]/#/--exclude=}" \ - --json \ - --output "${COVERAGE_OUTPUT_DIR}/coverage.json" \ - --verbose \ - ${GCOVR_EXTRA_OPTIONS[@]} > "${CCOV_LOG_FILE}" 2>&1 - - echo "Coverage report written to ${COVERAGE_OUTPUT_DIR}/coverage.json" -} - -merge() { - - COVERAGE_OUTPUT_DIR="${PWD}/.coverage" - - ! [[ -d "${COVERAGE_OUTPUT_DIR}" ]] && mkdir -p "${COVERAGE_OUTPUT_DIR}" - - tracefiles=() - - mapfile -d $'\0' tracefiles < \ - <(find "${PWD}/.coverage/partial" -name coverage.json -print0) - - - if [ "$CCOV_VERBOSE" = true ]; then - printf "Executing merge command:" - printf " gcovr" - printf " --root ${CCOV_ROOT_DIR}" - printf " %s\n" "${tracefiles[@]/#/--add-tracefile=}" - printf " --html-details ${COVERAGE_OUTPUT_DIR}/coverage.html" - printf " --xml" - printf " --output ${COVERAGE_OUTPUT_DIR}/coverage-cobertura.xml" - printf " --print-summary" - printf " --verbose" - printf " %s\n" "${GCOVR_EXTRA_OPTIONS[@]}" - fi - - gcovr \ - --root "${CCOV_ROOT_DIR}" \ - "${tracefiles[@]/#/--add-tracefile=}" \ - --html-details "${COVERAGE_OUTPUT_DIR}/coverage.html" \ - --xml \ - --output "${COVERAGE_OUTPUT_DIR}/coverage-cobertura.xml" \ - --print-summary \ - --verbose \ - ${GCOVR_EXTRA_OPTIONS[@]} > "${CCOV_LOG_FILE}" 2>&1 - - echo "Cobertura XML report written to ${COVERAGE_OUTPUT_DIR}/coverage-cobertura.xml" - echo "HTML report written to ${COVERAGE_OUTPUT_DIR}/coverage.html" - - exit 0 -} - -################################################################################ -## MAIN -################################################################################ -parse_args "$@" - -if [[ x"$CCOV_MODE" == x"capture" ]]; then - parse_exclusions_file - capture -else - merge -fi - -exit 0 diff --git a/scripts/dev/coverage.py b/scripts/dev/coverage.py new file mode 100755 index 000000000..6fcf8af39 --- /dev/null +++ b/scripts/dev/coverage.py @@ -0,0 +1,517 @@ +#!/usr/bin/env python3 + +import argparse +import shutil +import string +import subprocess +import sys +from collections import namedtuple +from tempfile import NamedTemporaryFile, TemporaryDirectory +from pathlib import Path +from typing import Optional, List, Union +from loguru import logger + + +class Command: + def __init__(self, cmdline: List[str]): + self._cmdline = list(filter(None, cmdline)) + self.result = None + + def run(self): + command = self._cmdline + self.result = subprocess.run( + command, + timeout=5 * 60, + capture_output=True, + encoding="utf-8") + + try: + self.result.check_returncode() + except FileNotFoundError as exc: + logger.error( + f"Command {command} failed because the process could not be " + f"found.\n{exc}") + sys.exit(1) + except subprocess.CalledProcessError as exc: + logger.error(f"Command {command} failed because the process " + f"did not return a successful return code.\n{exc}") + logger.error(f" STDOUT:\n{self.result.stdout}") + logger.error(f" STDERR:\n{self.result.stderr}") + sys.exit(1) + except subprocess.TimeoutExpired as exc: + logger.error(f"Command {command} timed out.\n {exc}") + logger.error(f" STDOUT:\n{self.result.stdout}") + logger.error(f" STDERR:\n{self.result.stderr}") + sys.exit(1) + else: + logger.trace("\n".join( + filter(None, [" STDOUT:", self.result.stdout]))) + logger.trace("\n".join( + filter(None, [" STDERR:", self.result.stderr]))) + + def stdout(self): + return self.result.stdout if self.result else "" + + def stderr(self): + return self.result.stderr if self.result else "" + + def __str__(self): + return ' '.join(self._cmdline) + + +class CommandTemplate: + def __init__(self, cmdline: List[str]): + self._cmdline = cmdline + + def substitute(self, **kwargs): + return Command(list(map( + lambda s: string.Template(s).substitute(**kwargs), + self._cmdline))) + + +class Stage: + def __init__(self, template: CommandTemplate, + input: Optional[Path] = None, + output: Union[NamedTemporaryFile, TemporaryDirectory] = + NamedTemporaryFile(suffix=".info")): + self._input = input + self._output = output + self._command = template.substitute( + INPUT=self.input_path, + OUTPUT=self.output_path) + + @property + def input_path(self) -> Optional[Path]: + return self._input + + @property + def output_path(self) -> Path: + return Path(self._output.name) + + def run(self): + self._command.run() + + def save_output(self, output_name: Path): + if self.output_path.is_dir(): + shutil.copytree(self.output_path, output_name, dirs_exist_ok=True) + else: + shutil.copy(self.output_path, output_name) + + def __str__(self): + return str(self._command) + + +class CommandPipeline: + class NoOp(Stage): + + def __init__(self): + super().__init__(CommandTemplate([])) + + @Stage.input_path.getter + def input_path(self) -> Optional[Path]: + return None + + @Stage.output_path.getter + def output_path(self) -> Optional[Path]: + return None + + def run(self): + pass + + def save_output(self, output_name: Path): + pass + + def __str__(self): + return 'noop' + + def __init__(self): + self._stages: List[Stage] = [CommandPipeline.NoOp()] + + def last(self) -> Stage: + return self._stages[-1] + + def append(self, cmdline: List[str]): + self._stages.append( + Stage( + CommandTemplate(cmdline), + input=self.last().output_path, + output=NamedTemporaryFile())) + + def run(self): + for s in self._stages[1:]: + logger.info(f"running stage: '{s}'") + s.run() + + def save_output(self, output_path: Path): + self.last().save_output(output_path) + + +def configure_logging(verbosity): + logger.remove() + + if verbosity == 0: + log_level = "SUCCESS" + elif verbosity == 1: + log_level = "INFO" + elif verbosity == 2: + log_level = "DEBUG" + else: + log_level = "TRACE" + + logger.add(sys.stderr, level=log_level) + + +def capture(args): + root_directory = args.root_directory.resolve() + sources_directory = args.sources_directory.resolve() + output_file = args.output_file.resolve() + + pipeline = CommandPipeline() + + pipeline.append([ + "lcov", + "--capture", + "--quiet" if args.verbosity > 3 else "", + "--initial" if args.initial else "", + f"--directory={root_directory}", + f"--include={sources_directory}/*", + "--output-file=$OUTPUT"]) + + if args.exclusion_patterns: + pipeline.append([ + "lcov", + "--quiet" if args.verbosity > 3 else "", + "--remove=$INPUT", + *args.exclusion_patterns, + "--output-file=$OUTPUT"]) + + logger.info("Executing command pipeline") + pipeline.run() + pipeline.save_output(output_file) + logger.success(f"Output written to '{output_file}'") + + +def merge(args): + output_file = args.output_file.resolve() + tracefiles = args.tracefiles or [] + + if args.search_pattern: + basedir = args.search_pattern.parent.resolve() or Path.cwd() + pattern = args.search_pattern.name + if not pattern: + logger.warning("Pathname pattern expansion is empty. Ignored.") + else: + tracefiles.extend(basedir.rglob(pattern)) + + if not tracefiles: + logger.error("No tracefiles were found that can be included in the " + "unified trace. Exiting.") + sys.exit(1) + + logger.trace("The following traces will be included in the unified trace:") + + for t in tracefiles: + logger.trace(f" {t}") + + pipeline = CommandPipeline() + + pipeline.append([ + "lcov", + "--quiet" if args.verbosity > 3 else "", + *(f"--add-tracefile={t}" for t in tracefiles), + "--output-file=$OUTPUT"]) + + logger.info("Executing command pipeline") + pipeline.run() + pipeline.save_output(output_file) + logger.success(f"Output written to '{output_file}'") + + +def summary(args): + input_file = args.input_tracefile + + cmd = Command( + cmdline=[ + "lcov", + "--summary", + f"{input_file}" + ]) + + logger.info("Generating coverage summary...") + cmd.run() + print(cmd.stdout()) + + +def html_report(args): + output_dir = args.output_directory.resolve() + + cmd = Command( + cmdline=[ + "genhtml", + "--quiet" if args.verbosity > 3 else "", + "--legend", + "--frames", + f"{args.input_tracefile}", + f"--prefix={args.prefix}" if args.prefix else "", + f"--output-dir={output_dir}"]) + + logger.info("Generating HTML report...") + cmd.run() + logger.success(f"HTML report written to '{output_dir}'") + + +def cobertura_report(args): + output_file = args.output_file.resolve() + + cmd = Command( + cmdline=[ + "lcov_cobertura", + f"{args.input_tracefile}", + "--base-dir={args.base_dir}", + f"--output={output_file}"]) + + logger.info("Generating Cobertura report...") + cmd.run() + logger.success(f"Cobertura report written to '{output_file}'") + + +def define_capture_mode_args(parser): + parser.add_argument( + "--initial", + help="capture initial zero coverage data", + action='store_true') + + parser.add_argument( + "-o", + "--output-file", + help="write the generated coverage trace to FILENAME", + required=True, + type=Path, + metavar="FILENAME") + + parser.add_argument( + "-r", + "--root-directory", + help="directory where .gcda files should be searched for (typically " + "${CMAKE_BINARY_DIR})", + required=True, + type=Path, + metavar="DIR") + + parser.add_argument( + "-s", + "--sources-directory", + help="directory where source files should be searched for " + "(typically ${CMAKE_SOURCE_DIR})", + required=True, + type=Path, + metavar="DIR") + + parser.add_argument( + "-e", + "--exclude-pattern", + help="exclude source files that match this pattern (can be specified " + "multiple times)", + type=str, + action='append', + dest='exclusion_patterns', + metavar="PATTERN") + + parser.add_argument( + "-v", + "--verbose", + help="enable verbose output (additional flags increase verbosity)", + action="count", + dest='verbosity') + + parser.set_defaults( + func=capture, + verbosity=0 + ) + + +def define_merge_mode_args(parser): + parser.add_argument( + "-o", + "--output-file", + help="write the unified trace to FILENAME", + required=True, + type=Path, + metavar="FILENAME") + + parser.add_argument( + "-a", + "--add-tracefile", + help="add the contents of FILENAME to the unified trace", + type=Path, + metavar="FILENAME", + action="append", + dest="tracefiles" + ) + + parser.add_argument( + "-p", + "--search-pattern", + help="include any traces matching PATTERN (e.g. '/home/user/*.info')", + type=Path, + metavar="PATTERN", + ) + + parser.add_argument( + "-v", + "--verbose", + help="enable verbose output (additional flags increase verbosity)", + action="count", + dest='verbosity') + + parser.set_defaults( + func=merge, + verbosity=0) + + +def define_summary_args(parser): + parser.add_argument( + "-i", + "--input-tracefile", + help="include coverage data found in TRACEFILE in the generated report", + required=True, + type=Path, + metavar="TRACEFILE") + + parser.add_argument( + "-v", + "--verbose", + help="enable verbose output (additional flags increase verbosity)", + action="count", + dest='verbosity') + + parser.set_defaults( + func=summary, + verbosity=0) + + +def define_html_report_args(parser): + parser.add_argument( + "-i", + "--input-tracefile", + help="include coverage data found in TRACEFILE in the generated report", + required=True, + type=Path, + metavar="TRACEFILE") + + parser.add_argument( + "-p", + "--prefix", + help="remove PREFIX from all directory names", + type=Path, + metavar="PREFIX") + + parser.add_argument( + "--output-directory", + help="write a HTML report for coverage data to OUTPUT_DIR", + required=True, + metavar="OUTPUT_DIR", + type=Path) + + parser.add_argument( + "-v", + "--verbose", + help="enable verbose output (additional flags increase verbosity)", + action="count", + dest='verbosity') + + parser.set_defaults( + func=html_report, + verbosity=0) + + +def define_cobertura_report_args(parser): + parser.add_argument( + "-i", + "--input-tracefile", + help="Include coverage data found in TRACEFILE in the generated report", + required=True, + type=Path, + metavar="TRACEFILE") + + parser.add_argument( + "--output-file", + help="write the Cobertura XML report to OUTPUT_FILE", + metavar="OUTPUT_FILE", + type=Path) + + parser.add_argument( + "-b", + "--base-dir", + help="directory where source files are located", + type=Path, + metavar="DIR") + + parser.add_argument( + "-v", + "--verbose", + help="enable verbose output (additional flags increase verbosity)", + action="count", + dest='verbosity') + + parser.set_defaults( + func=cobertura_report, + verbosity=0) + + +def define_command_line_args(): + Mode = namedtuple("Mode", ["name", "help", "func"]) + + modes = [ + Mode(name="capture", + help="generate a coverage trace file from existing .gcda files", + func=define_capture_mode_args), + Mode(name="merge", + help="merge existing coverage traces into a unified trace", + func=define_merge_mode_args), + Mode(name="summary", + help="show summary coverage data for tracefiles", + func=define_summary_args), + Mode(name="html_report", + help="generate reports from coverage trace files", + func=define_html_report_args), + Mode(name="cobertura", + help="generate a Cobertura report from coverage trace files" + "(requires `lcov_cobertura` to be installed)", + func=define_cobertura_report_args) + ] + + parser = argparse.ArgumentParser( + description="A utility script to run `lcov` and simplify the " + "generation of coverage reports based on multiple traces") + + subparsers = parser.add_subparsers( + title="commands", + help='operating modes') + + for m in modes: + subparser = subparsers.add_parser(m.name, help=m.help) + m.func(subparser) + + return parser + + +def parse_command_line(): + parser = define_command_line_args() + args = parser.parse_args() + + if 'func' not in args: + parser.print_help() + sys.exit(1) + + return args + + +def main(): + args = parse_command_line() + configure_logging(args.verbosity) + args.func(args) + + +if __name__ == "__main__": + main() -- GitLab From b6361ff5547db9d463be4163769177639a7fc6a9 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Fri, 10 Mar 2023 08:21:42 +0100 Subject: [PATCH 04/17] CMake: Simplify generation of coverage reports There are now explicit targets for generating coverage reports from CMake itself: - `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}`. --- CMake/gkfs-code-coverage.cmake | 218 +++++++++++++++++---------- CMake/gkfs-testing.cmake | 30 ++++ CMakeLists.txt | 11 +- src/client/CMakeLists.txt | 8 - src/common/CMakeLists.txt | 18 --- src/common/arithmetic/CMakeLists.txt | 2 - src/daemon/CMakeLists.txt | 8 - tests/CMakeLists.txt | 7 +- tests/unit/helpers/CMakeLists.txt | 2 - 9 files changed, 179 insertions(+), 125 deletions(-) create mode 100644 CMake/gkfs-testing.cmake diff --git a/CMake/gkfs-code-coverage.cmake b/CMake/gkfs-code-coverage.cmake index d8d23c4fd..e752de52f 100644 --- a/CMake/gkfs-code-coverage.cmake +++ b/CMake/gkfs-code-coverage.cmake @@ -26,85 +26,151 @@ # SPDX-License-Identifier: GPL-3.0-or-later # ################################################################################ -# Variables -option(GKFS_ENABLE_CODE_COVERAGE - "Builds GekkoFS targets with code coverage instrumentation." - OFF +option(GKFS_GENERATE_COVERAGE_REPORTS "Generate coverage reports" ON) + +macro(gkfs_enable_coverage_reports) + + set(OPTIONS) + set(SINGLE_VALUE) + set(MULTI_VALUE EXCLUDE_DIRECTORIES) + cmake_parse_arguments( + ARGS "${OPTIONS}" "${SINGLE_VALUE}" "${MULTI_VALUE}" ${ARGN} ) -# Common initialization/checks -if(GKFS_ENABLE_CODE_COVERAGE AND NOT GKFS_CODE_COVERAGE_ADDED) - - set(GKFS_CODE_COVERAGE_ADDED ON) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - message(STATUS "[gekkofs] Building with LLVM Code Coverage Tools") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - - message(STATUS "[gekkofs] Building with GCC Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + find_program(COVERAGE_PY + coverage.py + PATHS ${CMAKE_SOURCE_DIR}/scripts/dev + REQUIRED) + + if(NOT COVERAGE_OUTPUT_DIR) + set(COVERAGE_OUTPUT_DIR "${CMAKE_BINARY_DIR}/coverage") + endif() + + file(MAKE_DIRECTORY ${COVERAGE_OUTPUT_DIR}) + + if(NOT COVERAGE_ZEROCOUNT_TRACEFILE) + set(COVERAGE_ZEROCOUNT_TRACEFILE "${COVERAGE_OUTPUT_DIR}/zerocount.info") endif() -endif() - -# Adds code coverage instrumentation to libraries and executable targets. -# ~~~ -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC -# instead of the default of PRIVATE. -# PRIVATE - Sets the visibility for added compile options to targets to -# INTERFACE instead of the default of PRIVATE. -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options PUBLIC INTERFACE) - cmake_parse_arguments(target_code_coverage "${options}" "" "" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - else() - set(TARGET_VISIBILITY PRIVATE) + + if(NOT COVERAGE_CAPTURE_TRACEFILE) + set(COVERAGE_CAPTURE_TRACEFILE "${COVERAGE_OUTPUT_DIR}/capture.info") endif() - if(GKFS_ENABLE_CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_VISIBILITY} gcov) - endif() + if(NOT COVERAGE_UNIFIED_TRACEFILE) + set(COVERAGE_UNIFIED_TRACEFILE "${COVERAGE_OUTPUT_DIR}/coverage.info") endif() -endfunction() + + if(NOT COVERAGE_HTML_REPORT_DIRECTORY) + set(COVERAGE_HTML_REPORT_DIRECTORY "${COVERAGE_OUTPUT_DIR}/coverage_html") + endif() + + if(NOT COVERAGE_XML_REPORT) + set(COVERAGE_XML_REPORT "${COVERAGE_OUTPUT_DIR}/coverage-cobertura.xml") + endif() + + # add a `coverage-zerocount` target for the initial baseline gathering of + # coverage information + add_custom_command( + OUTPUT "${COVERAGE_ZEROCOUNT_TRACEFILE}" + COMMAND + ${COVERAGE_PY} + capture + --initial + --root-directory "${CMAKE_BINARY_DIR}" + --output-file "${COVERAGE_ZEROCOUNT_TRACEFILE}" + --sources-directory "${CMAKE_SOURCE_DIR}" + "$<$:--exclude;$>" + COMMAND_EXPAND_LISTS VERBATIM + COMMENT "Generating zerocount coverage tracefile" + ) + + add_custom_target(coverage-zerocount + DEPENDS ${COVERAGE_ZEROCOUNT_TRACEFILE}) + + # add a `coverage-capture` target to capture coverage data from any + # of the existing .gcda files + add_custom_command( + OUTPUT "${COVERAGE_CAPTURE_TRACEFILE}" + COMMAND + ${COVERAGE_PY} + capture + --root-directory "${CMAKE_BINARY_DIR}" + --output-file "${COVERAGE_CAPTURE_TRACEFILE}" + --sources-directory "${CMAKE_SOURCE_DIR}" + "$<$:--exclude;$>" + COMMAND_EXPAND_LISTS VERBATIM + COMMENT "Generating capture coverage tracefile" + ) + + add_custom_target(coverage-capture DEPENDS ${COVERAGE_CAPTURE_TRACEFILE}) + + # add a `coverage-unified` target to merge all coverage data available in + # ${COVERAGE_OUTPUT_DIR} into a unified coverage trace + add_custom_command( + OUTPUT ${COVERAGE_UNIFIED_TRACEFILE} + DEPENDS ${COVERAGE_CAPTURE_TRACEFILE} + COMMAND + ${COVERAGE_PY} + merge + --search-pattern "${COVERAGE_OUTPUT_DIR}/*.info" + --output-file "${COVERAGE_UNIFIED_TRACEFILE}" + VERBATIM + COMMENT "Generating unified coverage tracefile" + ) + + add_custom_target( + coverage-unified + DEPENDS "${COVERAGE_UNIFIED_TRACEFILE}" + ) + + # add a `coverage-summary` target to print a summary of coverage data + add_custom_target( + coverage-summary + COMMAND + ${COVERAGE_PY} + summary + --input-tracefile ${COVERAGE_UNIFIED_TRACEFILE} + DEPENDS ${COVERAGE_UNIFIED_TRACEFILE} + COMMENT "Gathering coverage information" + ) + + # add a `coverage-html` target to generate a coverage HTML report + add_custom_command(OUTPUT + "${COVERAGE_HTML_REPORT_DIRECTORY}" + COMMAND + ${COVERAGE_PY} + html_report + --input-tracefile "${COVERAGE_UNIFIED_TRACEFILE}" + --prefix "${CMAKE_SOURCE_DIR}" + --output-directory "${COVERAGE_HTML_REPORT_DIRECTORY}" + DEPENDS ${COVERAGE_UNIFIED_TRACEFILE} + VERBATIM + COMMENT "Generating HTML report" + ) + + add_custom_target( + coverage-html + DEPENDS "${COVERAGE_HTML_REPORT_DIRECTORY}") + + # add a `coverage-cobertura` target to generate a Cobertura XML report + add_custom_command(OUTPUT + "${COVERAGE_XML_REPORT}" + COMMAND + ${COVERAGE_PY} + cobertura + --input-tracefile "${COVERAGE_UNIFIED_TRACEFILE}" + --base-dir "${CMAKE_SOURCE_DIR}" + --output-file "${COVERAGE_XML_REPORT}" + DEPENDS ${COVERAGE_UNIFIED_TRACEFILE} + VERBATIM + COMMENT "Generating Cobertura report" + ) + + add_custom_target( + coverage-cobertura + DEPENDS "${COVERAGE_XML_REPORT}" + ) + set_target_properties( + coverage-cobertura PROPERTIES ADDITIONAL_CLEAN_FILES ${COVERAGE_XML_REPORT} + ) +endmacro() diff --git a/CMake/gkfs-testing.cmake b/CMake/gkfs-testing.cmake new file mode 100644 index 000000000..1f9efe22f --- /dev/null +++ b/CMake/gkfs-testing.cmake @@ -0,0 +1,30 @@ +################################################################################ +# Copyright 2018-2023, Barcelona Supercomputing Center (BSC), Spain # +# Copyright 2015-2023, Johannes Gutenberg Universitaet Mainz, Germany # +# # +# This software was partially supported by the # +# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). # +# # +# This software was partially supported by the # +# ADA-FS project under the SPPEXA project funded by the DFG. # +# # +# This file is part of GekkoFS. # +# # +# GekkoFS is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# GekkoFS is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with GekkoFS. If not, see . # +# # +# SPDX-License-Identifier: GPL-3.0-or-later # +################################################################################ + +include(gkfs-code-coverage) +include(GkfsPythonTesting) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d26697861..50c839b51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,16 +107,6 @@ configure_file(include/version.hpp.in include/version.hpp) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH}) - -################################################################################ -## Coverage generation support: -## ============================ -## -## The `gkfs-code-coverage' module enables the GKFS_ENABLE_CODE_COVERAGE option -## as well as the target_code_coverage() function. -################################################################################ -include(gkfs-code-coverage) - set(CMAKE_EXPORT_COMPILE_COMMANDS 0) @@ -301,3 +291,4 @@ if (GKFS_BUILD_TESTS) else() unset(GKFS_TESTS_INTERFACE CACHE) endif() + diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 681c264f1..464238ab6 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -101,10 +101,6 @@ target_sources(gkfs_intercept ${PRELOAD_SOURCES}) target_link_libraries(gkfs_intercept PRIVATE ${PRELOAD_LINK_LIBRARIES}) target_include_directories(gkfs_intercept PRIVATE ${PRELOAD_INCLUDE_DIRS}) -if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(gkfs_intercept AUTO) -endif() - install(TARGETS gkfs_intercept LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -133,10 +129,6 @@ if (GKFS_ENABLE_FORWARDING) target_link_libraries(gkfwd_intercept PRIVATE ${PRELOAD_LINK_LIBRARIES}) target_include_directories(gkfwd_intercept PRIVATE ${PRELOAD_INCLUDE_DIRS}) - if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(gkfwd_intercept AUTO) - endif() - install(TARGETS gkfwd_intercept LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index edd2b4f31..c53c3168d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -65,12 +65,6 @@ endif() ${PROMETHEUS_LINK_LIBRARIES} ) - -if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(distributor AUTO) - target_code_coverage(statistics AUTO) -endif() - # get spdlog set(FETCHCONTENT_QUIET ON) @@ -103,10 +97,6 @@ target_link_libraries(log_util spdlog ) -if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(log_util AUTO) -endif() - add_library(env_util STATIC) set_property(TARGET env_util PROPERTY POSITION_INDEPENDENT_CODE ON) target_sources(env_util @@ -117,10 +107,6 @@ target_sources(env_util ${CMAKE_CURRENT_LIST_DIR}/env_util.cpp ) -if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(env_util AUTO) -endif() - add_library(metadata STATIC) set_property(TARGET metadata PROPERTY POSITION_INDEPENDENT_CODE ON) target_sources(metadata @@ -134,10 +120,6 @@ target_link_libraries(metadata fmt::fmt ) -if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(metadata AUTO) -endif() - add_library(path_util STATIC ) diff --git a/src/common/arithmetic/CMakeLists.txt b/src/common/arithmetic/CMakeLists.txt index dd39211ca..bdf88f657 100644 --- a/src/common/arithmetic/CMakeLists.txt +++ b/src/common/arithmetic/CMakeLists.txt @@ -36,5 +36,3 @@ target_include_directories(arithmetic INTERFACE ${INCLUDE_DIR}/common/arithmetic/ ) - -target_code_coverage(arithmetic INTERFACE) diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index f91415eee..7e45be535 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -88,10 +88,6 @@ target_sources(gkfs_daemon ${DAEMON_SOURCES}) target_link_libraries(gkfs_daemon ${DAEMON_LINK_LIBRARIES}) target_include_directories(gkfs_daemon ${DAEMON_INCLUDE_DIRS}) -if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(gkfs_daemon AUTO) -endif() - install(TARGETS gkfs_daemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) @@ -134,10 +130,6 @@ if (GKFS_ENABLE_FORWARDING) ${AGIOS_INCLUDE_DIRS} ) - if(GKFS_ENABLE_CODE_COVERAGE) - target_code_coverage(gkfwd_daemon AUTO) - endif() - install(TARGETS gkfwd_daemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1333a019d..f831f5995 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,7 +26,12 @@ # SPDX-License-Identifier: GPL-3.0-or-later # ################################################################################ -include(GkfsPythonTesting) +include(gkfs-testing) + +if(GKFS_GENERATE_COVERAGE_REPORTS) + gkfs_enable_coverage_reports(EXCLUDE_DIRECTORIES + "${FETCHCONTENT_BASE_DIR}/*" "${CMAKE_SOURCE_DIR}/external/*" ) +endif() add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} diff --git a/tests/unit/helpers/CMakeLists.txt b/tests/unit/helpers/CMakeLists.txt index 36f77843b..e0a936ab4 100644 --- a/tests/unit/helpers/CMakeLists.txt +++ b/tests/unit/helpers/CMakeLists.txt @@ -36,8 +36,6 @@ target_sources(helpers temporary_file.cpp ) -target_code_coverage(helpers AUTO) - target_link_libraries(helpers PUBLIC fmt::fmt -- GitLab From 6c7136bde3611836d08eae390b8fa90bbc514754 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Fri, 10 Mar 2023 18:46:51 +0100 Subject: [PATCH 05/17] CMake: Rename GkfsPythonTesting.cmake to gkfs-python-testing.cmake --- CMake/{GkfsPythonTesting.cmake => gkfs-python-testing.cmake} | 0 CMake/gkfs-testing.cmake | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename CMake/{GkfsPythonTesting.cmake => gkfs-python-testing.cmake} (100%) diff --git a/CMake/GkfsPythonTesting.cmake b/CMake/gkfs-python-testing.cmake similarity index 100% rename from CMake/GkfsPythonTesting.cmake rename to CMake/gkfs-python-testing.cmake diff --git a/CMake/gkfs-testing.cmake b/CMake/gkfs-testing.cmake index 1f9efe22f..d61f0b2a1 100644 --- a/CMake/gkfs-testing.cmake +++ b/CMake/gkfs-testing.cmake @@ -27,4 +27,4 @@ ################################################################################ include(gkfs-code-coverage) -include(GkfsPythonTesting) \ No newline at end of file +include(gkfs-python-testing) -- GitLab From babbce8ade51d3b1160731b44494fb0c23f80b75 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 13 Mar 2023 10:57:08 +0100 Subject: [PATCH 06/17] Upgrade nlohmann_json to v3.11.2 Fixes #256 --- tests/integration/harness/CMakeLists.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/integration/harness/CMakeLists.txt b/tests/integration/harness/CMakeLists.txt index 25440c4a4..28e0b3608 100644 --- a/tests/integration/harness/CMakeLists.txt +++ b/tests/integration/harness/CMakeLists.txt @@ -75,11 +75,8 @@ include(FetchContent) set(FETCHCONTENT_QUIET OFF) FetchContent_Declare(nlohmann_json - GIT_REPOSITORY https://github.com/nlohmann/json - GIT_TAG e7b3b40b5a95bc74b9a7f662830a27c49ffc01b4 # v3.7.3 - GIT_SHALLOW ON - GIT_PROGRESS ON -) + URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz + DOWNLOAD_EXTRACT_TIMESTAMP ON) FetchContent_GetProperties(nlohmann_json) -- GitLab From 82434b867ad269bcc4fb3af45a01cab22e5c251d Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 13 Mar 2023 11:05:22 +0100 Subject: [PATCH 07/17] Don't remove sources for automatically downloaded dependencies Fixes #257 --- scripts/ci/trim_build_artifacts.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/ci/trim_build_artifacts.sh b/scripts/ci/trim_build_artifacts.sh index e6284778b..25d87f518 100755 --- a/scripts/ci/trim_build_artifacts.sh +++ b/scripts/ci/trim_build_artifacts.sh @@ -40,8 +40,7 @@ if [[ -z "${BUILDDIR}" ]]; then usage fi -echo "Cleaning up ${BUILDDIR}:" - +echo "Cleaning up ${BUILDDIR} (size: $(du -sh ${BUILDDIR} | cut -d ' ' -f 1)):" echo " * Removing object files" find ${BUILDDIR} \ @@ -61,6 +60,4 @@ find ${BUILDDIR} \ \) \ -delete -echo " * Removing sources automatically-downloaded by CMake" - -find ${BUILDDIR}/_deps -type d -name "*-src" -prune -exec rm -rf {} \; +echo "Finished (${BUILDDIR} size: $(du -sh ${BUILDDIR} | cut -d ' ' -f 1))" -- GitLab From 6def0f979a962042be6b55036ca7de7103ce38ec Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 13 Mar 2023 12:16:45 +0100 Subject: [PATCH 08/17] Add explicit CMAKE_BUILD_TYPE to prometheus-cpp install script Fixes #258 --- scripts/profiles/0.9.2/install/prometheus-cpp.install | 7 +++++-- src/common/CMakeLists.txt | 8 ++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/profiles/0.9.2/install/prometheus-cpp.install b/scripts/profiles/0.9.2/install/prometheus-cpp.install index 9373dbcf3..62e420217 100644 --- a/scripts/profiles/0.9.2/install/prometheus-cpp.install +++ b/scripts/profiles/0.9.2/install/prometheus-cpp.install @@ -48,8 +48,11 @@ pkg_install() { CURR="${SOURCE_DIR}/${ID}" prepare_build_dir "${CURR}" cd "${CURR}/build" - ${CMAKE} -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ - -DBUILD_SHARED_LIBS:BOOL=ON .. + ${CMAKE} \ + -DCMAKE_BUILD_TYPE:STRING=Release \ + -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + .. make -j"${CORES}" install } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c53c3168d..ba768d364 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -52,7 +52,8 @@ target_sources(statistics if(GKFS_ENABLE_PROMETHEUS) find_package(CURL REQUIRED) find_package(prometheus-cpp REQUIRED) - set(PROMETHEUS_LINK_LIBRARIES + target_link_libraries(statistics + PRIVATE prometheus-cpp::pull prometheus-cpp::push prometheus-cpp::core @@ -60,11 +61,6 @@ if(GKFS_ENABLE_PROMETHEUS) target_include_directories(statistics PRIVATE ${prometheus-cpp_INCLUDE_DIR}) endif() - target_link_libraries(statistics - PRIVATE - ${PROMETHEUS_LINK_LIBRARIES} - ) - # get spdlog set(FETCHCONTENT_QUIET ON) -- GitLab From 1e237d0c7ac7c17f384fa8e04f5aa0091b562bcc Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 13 Mar 2023 11:37:29 +0100 Subject: [PATCH 09/17] Docker: Update `testing:0.9.2` image - Upgrade CMake to 3.25.2 for consistency with other images - Remove gcovr and install lcov - Install loguru - Install lcov_cobertura - Install libgd-perl --- docker/0.9.2/testing/Dockerfile | 34 +++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/docker/0.9.2/testing/Dockerfile b/docker/0.9.2/testing/Dockerfile index c5513b248..9cb489374 100644 --- a/docker/0.9.2/testing/Dockerfile +++ b/docker/0.9.2/testing/Dockerfile @@ -3,12 +3,30 @@ FROM gekkofs/deps:0.9.2 LABEL Description="Debian-based environment to test GekkoFS" RUN \ - # install cmake 3.21 since we need to produce JUnit XML files - curl -OL https://github.com/Kitware/CMake/releases/download/v3.21.1/cmake-3.21.1-linux-x86_64.sh && \ - chmod u+x ./cmake-3.21.1-linux-x86_64.sh && \ - ./cmake-3.21.1-linux-x86_64.sh --skip-license --prefix=/usr && \ - # install gcovr - # (required for partial coverage reports in parallel runs) - pip3 install gcovr && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + # required by lcov's genhtml + libgd-perl && \ + # install cmake 3.21+ since we need to produce JUnit XML files + curl -OL https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-Linux-x86_64.sh && \ + chmod u+x ./cmake-3.25.2-Linux-x86_64.sh && \ + ./cmake-3.25.2-Linux-x86_64.sh --skip-license --prefix=/usr && \ + # install loguru + # (required by several of our scripts) + pip3 install loguru && \ + # install lcov_cobertura + # (required to produce Cobertura XML reports) + pip3 install lcov_cobertura && \ + # install lcov + # (required to produce partial coverage reports in parallel runs) + curl -OL https://github.com/linux-test-project/lcov/releases/download/v1.16/lcov-1.16.tar.gz && \ + tar xfz lcov-1.16.tar.gz && \ + cd lcov-1.16 && \ + make install && \ + cd .. && \ # cleanup - rm ./cmake-3.21.1-linux-x86_64.sh + rm -rf /var/lib/apt/lists/* && \ + apt-get clean && \ + apt-get autoclean && \ + rm ./cmake-3.25.2-Linux-x86_64.sh && \ + rm -rf ./lcov-1.16.* -- GitLab From bbd55f624d3594c3580f43a83d1ff5117cb1b838 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 13 Mar 2023 19:16:08 +0100 Subject: [PATCH 10/17] Docker: Remove `coverage:0.9.2` image Image `testing:0.9.2` is now used for coverage generation. --- docker/0.9.2/coverage/Dockerfile | 20 -------------------- docker/0.9.2/coverage/Makefile | 10 ---------- 2 files changed, 30 deletions(-) delete mode 100644 docker/0.9.2/coverage/Dockerfile delete mode 100644 docker/0.9.2/coverage/Makefile diff --git a/docker/0.9.2/coverage/Dockerfile b/docker/0.9.2/coverage/Dockerfile deleted file mode 100644 index 651480713..000000000 --- a/docker/0.9.2/coverage/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM debian:bullseye-slim - -LABEL Description="Environment to generate coverage reports in GekkoFS" - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - wget \ - git \ - cmake \ - gcc \ - g++ \ - lcov \ - python3 \ - python3-pip \ - python3-setuptools && \ - rm -rf /var/lib/apt/lists/* && \ - apt-get clean && \ - apt-get autoclean && \ - python3 -m pip install --upgrade pip && \ - pip3 install gcovr diff --git a/docker/0.9.2/coverage/Makefile b/docker/0.9.2/coverage/Makefile deleted file mode 100644 index aacfbbff8..000000000 --- a/docker/0.9.2/coverage/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -.PHONY: all - -amd64: - docker build --platform amd64 -t gekkofs/coverage:0.9.2 . - -aarch64: - docker build --platform aarch64 -t gekkofs/coverage:0.9.2 . - -all: - docker build -t gekkofs/coverage:0.9.2 . \ No newline at end of file -- GitLab From 8d992d175427567e744375751a3f1f13123bd1e5 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Fri, 10 Mar 2023 19:26:01 +0100 Subject: [PATCH 11/17] CI: Update .gitlab-ci.yml --- .gitlab-ci.yml | 135 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 44 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index de0a60093..9013dd912 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ variables: BUILD_PATH: "${CI_PROJECT_DIR}/gkfs/build" INSTALL_PATH: "${CI_PROJECT_DIR}/gkfs/install" INTEGRATION_TESTS_BIN_PATH: "${CI_PROJECT_DIR}/gkfs/install/share/gkfs/tests/integration" - COVERAGE_PATH: "${CI_PROJECT_DIR}/gkfs/build/.coverage" + COVERAGE_PATH: "${CI_PROJECT_DIR}/gkfs/build/coverage" PYTEST: "${CI_PROJECT_DIR}/gkfs/install/share/gkfs/tests/integration/pytest-venv/bin/py.test" BATS: "${CI_PROJECT_DIR}/tests/scripts/bats/bin/bats" LD_LIBRARY_PATH: "${CI_PROJECT_DIR}/deps/install/lib:${CI_PROJECT_DIR}/deps/install/lib64" @@ -55,7 +55,7 @@ gkfs: - sed -i 's/constexpr auto use_mtime = false;/constexpr auto use_mtime = true;/g' "${CI_PROJECT_DIR}/include/config.hpp" - sed -i 's/constexpr auto use_link_cnt = false;/constexpr auto use_link_cnt = true;/g' "${CI_PROJECT_DIR}/include/config.hpp" - sed -i 's/constexpr auto use_blocks = false;/constexpr auto use_blocks = true;/g' "${CI_PROJECT_DIR}/include/config.hpp" - - cmake --preset ci-coverage + - cmake --preset ci-coverage -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} - cmake --build ${BUILD_PATH} -j $(nproc) --target install # reduce artifacts size - ${CI_SCRIPTS_DIR}/trim_build_artifacts.sh ${BUILD_PATH} @@ -71,7 +71,7 @@ gkfwd: interruptible: true needs: [] script: - - cmake --preset ci-forwarding-debug + - cmake --preset ci-forwarding-coverage - cmake --build ${BUILD_PATH} -j $(nproc) --target install # reduce artifacts size - ${CI_SCRIPTS_DIR}/trim_build_artifacts.sh ${BUILD_PATH} @@ -102,6 +102,33 @@ scripts: junit: ${BUILD_PATH}/tests/scripts/report.xml +## == coverage baseline ==================== +coverage:zerocount: + stage: test + image: gekkofs/testing:0.9.2 + interruptible: true + needs: ['gkfs', 'gkfwd'] + + script: + ## capture initial coverage information to establish a baseline + ## and write it to $COVERAGE_PATH/zerocount.info + - cd ${CI_PROJECT_DIR} + - cmake --preset ci-coverage + -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} + -DCOVERAGE_ZEROCOUNT_TRACEFILE=${COVERAGE_PATH}/zerocount.info + ## Since the pipeline recreates the source tree, the access times for .gcno + ## files are newer than those of .gcda files. This makes gcov emit a + ## warning for each file which slows it down. Updating the timestamps + ## avoids this + - find ${BUILD_PATH} -name "*.gcno" -exec touch {} \; + - cmake --build ${BUILD_PATH} --target coverage-zerocount + + artifacts: + expire_in: 1 week + when: always + paths: + - ${COVERAGE_PATH}/zerocount.info + ## == integration tests for gkfs =========== gkfs:integration: stage: test @@ -122,15 +149,18 @@ gkfs:integration: --basetemp=${BUILD_PATH}/tests/run/${SUBTEST} --junit-xml=report.xml - ## capture coverage information - - cd ${BUILD_PATH} - - ${CI_SCRIPTS_DIR}/coverage.sh - --verbose - --capture integration_${SUBTEST} - --root-dir ${CI_PROJECT_DIR} - --build-dir ${BUILD_PATH} - --exclusions "${CI_SCRIPTS_DIR}/.coverage-exclusions" - --log-file "${COVERAGE_PATH}/partial/integration_${SUBTEST}/capture.log" + ## capture coverage information for this test and write it to + ## $COVERAGE_PATH/$SUBTEST.info + - cd ${CI_PROJECT_DIR} + - cmake --preset ci-coverage + -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} + -DCOVERAGE_CAPTURE_TRACEFILE=${COVERAGE_PATH}/${SUBTEST}.info + ## Since the pipeline recreates the source tree, the access times for .gcno + ## files are newer than those of .gcda files. This makes gcov emit a + ## warning for each file which slows it down. Updating the timestamps + ## avoids this + - find ${BUILD_PATH} -name "*.gcno" -exec touch {} \; + - cmake --build ${BUILD_PATH} --target coverage-capture # fix relative paths so that GitLab can find the correct files after_script: @@ -167,15 +197,18 @@ gkfwd:integration: --basetemp=${BUILD_PATH}/tests/run/${SUBTEST} --junit-xml=report.xml - ## capture coverage information - - cd ${BUILD_PATH} - - ${CI_SCRIPTS_DIR}/coverage.sh - --verbose - --capture integration_${SUBTEST} - --root-dir ${CI_PROJECT_DIR} - --build-dir ${BUILD_PATH} - --exclusions "${CI_SCRIPTS_DIR}/.coverage-exclusions" - --log-file "${COVERAGE_PATH}/partial/integration_${SUBTEST}/capture.log" + ## capture coverage information for this test and write it to + ## $COVERAGE_PATH/$SUBTEST.info + - cd ${CI_PROJECT_DIR} + - cmake --preset ci-coverage + -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} + -DCOVERAGE_CAPTURE_TRACEFILE=${COVERAGE_PATH}/${SUBTEST}.info + ## Since the pipeline recreates the source tree, the access times for .gcno + ## files are newer than those of .gcda files. This makes gcov emit a + ## warning for each file which slows it down. Updating the timestamps + ## avoids this + - find ${BUILD_PATH} -name "*.gcno" -exec touch {} \; + - cmake --build ${BUILD_PATH} --target coverage-capture # fix relative paths so that GitLab can find the correct files after_script: @@ -202,18 +235,25 @@ gkfs:unit: ## Add path to mkfs.kreon - export PATH=${PATH}:/usr/local/bin ## run actual tests - - cd ${BUILD_PATH}/tests/unit - - ctest -j $(nproc) -L unit::all --output-junit report.xml - - ## capture coverage information - - cd ${BUILD_PATH} - - ${CI_SCRIPTS_DIR}/coverage.sh - --verbose - --capture unit - --root-dir ${CI_PROJECT_DIR} - --build-dir ${BUILD_PATH} - --exclusions "${CI_SCRIPTS_DIR}/.coverage-exclusions" - --log-file "${COVERAGE_PATH}/partial/unit/capture.log" + - cd ${CI_PROJECT_DIR} + - ctest --test-dir ${BUILD_PATH} + -j $(nproc) + -L unit::all + --output-junit ${BUILD_PATH}/tests/unit/report.xml + + ## capture coverage information for this test and write it to + ## $COVERAGE_PATH/unit.info + - cd ${CI_PROJECT_DIR} + - cmake --preset ci-coverage + -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} + -DCOVERAGE_CAPTURE_TRACEFILE=${COVERAGE_PATH}/unit.info + ## Since the pipeline recreates the source tree, the access times for .gcno + ## files are newer than those of .gcda files. This makes gcov emit a + ## warning for each file which slows it down. Updating the timestamps + ## avoids this + - find ${BUILD_PATH} -name "*.gcno" -exec touch {} \; + - cmake --build ${BUILD_PATH} --target coverage-capture + artifacts: expire_in: 1 week paths: @@ -255,23 +295,30 @@ documentation: ################################################################################ coverage: stage: report - image: gekkofs/coverage:0.9.2 - needs: [ 'gkfs:integration', 'gkfwd:integration', 'gkfs:unit' ] + image: gekkofs/testing:0.9.2 + needs: [ 'coverage:zerocount', 'gkfs:integration', 'gkfwd:integration', + 'gkfs:unit' ] script: - - cd ${BUILD_PATH} - ## merge the partial coverage files from each test in the pipeline - - ${CI_SCRIPTS_DIR}/coverage.sh - --verbose - --merge - --root-dir ${CI_PROJECT_DIR} - --build-dir ${BUILD_PATH} + - cd ${CI_PROJECT_DIR} + - cmake + --preset ci-coverage + -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} + -DCOVERAGE_UNIFIED_TRACEFILE=${COVERAGE_PATH}/coverage.info + -DCOVERAGE_HTML_REPORT_DIRECTORY=${COVERAGE_PATH}/coverage_html + -DCOVERAGE_XML_REPORT=${COVERAGE_PATH}/coverage-cobertura.xml + - cmake + --build ${BUILD_PATH} + --target coverage-html + --target coverage-cobertura + --target coverage-summary + coverage: '/lines[\.]+\: (\d+\.\d+)\%/' artifacts: reports: coverage_report: coverage_format: cobertura - path: ${BUILD_PATH}/.coverage/coverage-cobertura.xml + path: ${COVERAGE_PATH}/coverage-cobertura.xml paths: - - ${BUILD_PATH}/.coverage + - ${COVERAGE_PATH} expire_in: 2 weeks -- GitLab From 27c83d1486ca17a76c20dd72f5d6068bd2cdb962 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Wed, 15 Mar 2023 10:33:08 +0100 Subject: [PATCH 12/17] docs: Add documentation for coverage facilities --- docs/sphinx/devs/coverage.md | 219 +++++++++++++++++++++++++++++++++++ docs/sphinx/index.rst | 1 + 2 files changed, 220 insertions(+) create mode 100644 docs/sphinx/devs/coverage.md diff --git a/docs/sphinx/devs/coverage.md b/docs/sphinx/devs/coverage.md new file mode 100644 index 000000000..7bd9932a7 --- /dev/null +++ b/docs/sphinx/devs/coverage.md @@ -0,0 +1,219 @@ +# 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) +``` diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst index 283ca401a..4841c175b 100644 --- a/docs/sphinx/index.rst +++ b/docs/sphinx/index.rst @@ -56,6 +56,7 @@ Architecture :caption: Developer documentation devs/testing + devs/coverage api/reference Indices and tables -- GitLab From aa6633292050b3869f9e50e9a48877eea04ff3e4 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Wed, 15 Mar 2023 11:09:25 +0100 Subject: [PATCH 13/17] Docker: Update `docs:0.9.2` image Add 'attr' python module required by Sphinx --- docker/0.9.2/docs/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/0.9.2/docs/Dockerfile b/docker/0.9.2/docs/Dockerfile index 5ef55125a..628212942 100644 --- a/docker/0.9.2/docs/Dockerfile +++ b/docker/0.9.2/docs/Dockerfile @@ -24,7 +24,8 @@ RUN apt-get update && \ 'exhale==0.3.1' \ 'sphinx-copybutton==0.5.0' \ 'sphinx-multiversion==0.2.4' \ - 'myst_parser==0.17.0' && \ + 'myst_parser==0.17.0' \ + attrs && \ # Clean apt cache to reduce image layer size rm -rf /var/lib/apt/lists/* && \ rm -rf /tmp/doxygen-1.9.2 && \ -- GitLab From 64d1aea421e32669e200fa6351214b86f3d361fd Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Wed, 15 Mar 2023 13:13:17 +0100 Subject: [PATCH 14/17] WIP: Fix symlink tests --- CMakePresets.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 6f59bbe17..de7c7c3df 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -19,8 +19,8 @@ "GKFS_BUILD_TESTS": true, "GKFS_INSTALL_TESTS": true, "ENABLE_CLIENT_LOG": true, - "CLIENT_LOG_MESSAGE_SIZE": "512", - "SYMLINK_SUPPORT": false + "CLIENT_LOG_MESSAGE_SIZE": "1024", + "GKFS_SYMLINK_SUPPORT": false }, "warnings": { "dev": true, -- GitLab From 26df04bb3bb17c02c2817ebda8c3d7c7dd48ef4b Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 16 Mar 2023 08:27:07 +0100 Subject: [PATCH 15/17] CMakePresets: Modify `default` profile GKFS_BUILD_TESTS and GKFS_INSTALL_TESTS are now set to `false` by default. --- CMakePresets.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index de7c7c3df..d222b830a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -16,8 +16,8 @@ "cacheVariables": { "GKFS_ENABLE_ROCKSDB": true, "GKFS_ENABLE_PARALLAX": false, - "GKFS_BUILD_TESTS": true, - "GKFS_INSTALL_TESTS": true, + "GKFS_BUILD_TESTS": false, + "GKFS_INSTALL_TESTS": false, "ENABLE_CLIENT_LOG": true, "CLIENT_LOG_MESSAGE_SIZE": "1024", "GKFS_SYMLINK_SUPPORT": false @@ -80,6 +80,8 @@ "CMAKE_INSTALL_PREFIX": "${sourceDir}/gkfs/install", "GKFS_USE_GUIDED_DISTRIBUTION": true, "GKFS_ENABLE_PARALLAX": true, + "GKFS_BUILD_TESTS": true, + "GKFS_INSTALL_TESTS": true, "GKFS_CHUNK_STATS": true, "GKFS_ENABLE_PROMETHEUS": true, "GKFS_RENAME_SUPPORT": true -- GitLab From f2feceb621c80b330396cf7b31fe4aed720fcaa5 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 16 Mar 2023 08:35:54 +0100 Subject: [PATCH 16/17] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a0debe6f..c1f3aeadf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added PowerPC support ([!151](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/151)). - GKFS_RENAME_SUPPORT adds support for renaming files. It includes the use case of renaming opened files using the fd - FLOCK and fcntl functions for locks, are not supported, but they are available. +- Added support for [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) to simplify build + configurations ([!163](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/163#note_8179)). ### Changed @@ -26,6 +28,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). tests ([!145](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/145)). - Support parallelism for symlink tests ([!147](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/147)). - Update Parallax release (PARALLAX-exp) ([!158](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/158) +- Improved and simplified coverage generation procedures for developers with + specific CMake targets ([!163](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/163#note_8179)). ### Removed -- GitLab From bc235cf5127690789f091bed6864fb838954945c Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 16 Mar 2023 08:55:21 +0100 Subject: [PATCH 17/17] CI: Reorganize jobs - Rename job `coverage:zerocount` to `coverage:baseline` - Move `coverage:baseline` job to `report` stage --- .gitlab-ci.yml | 57 +++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9013dd912..d44225ce4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -102,33 +102,6 @@ scripts: junit: ${BUILD_PATH}/tests/scripts/report.xml -## == coverage baseline ==================== -coverage:zerocount: - stage: test - image: gekkofs/testing:0.9.2 - interruptible: true - needs: ['gkfs', 'gkfwd'] - - script: - ## capture initial coverage information to establish a baseline - ## and write it to $COVERAGE_PATH/zerocount.info - - cd ${CI_PROJECT_DIR} - - cmake --preset ci-coverage - -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} - -DCOVERAGE_ZEROCOUNT_TRACEFILE=${COVERAGE_PATH}/zerocount.info - ## Since the pipeline recreates the source tree, the access times for .gcno - ## files are newer than those of .gcda files. This makes gcov emit a - ## warning for each file which slows it down. Updating the timestamps - ## avoids this - - find ${BUILD_PATH} -name "*.gcno" -exec touch {} \; - - cmake --build ${BUILD_PATH} --target coverage-zerocount - - artifacts: - expire_in: 1 week - when: always - paths: - - ${COVERAGE_PATH}/zerocount.info - ## == integration tests for gkfs =========== gkfs:integration: stage: test @@ -293,10 +266,38 @@ documentation: ################################################################################ ## Generation of code coverage reports ################################################################################ + +## == coverage baseline ==================== +coverage:baseline: + stage: report + image: gekkofs/testing:0.9.2 + interruptible: true + needs: ['gkfs', 'gkfwd'] + + script: + ## capture initial coverage information to establish a baseline + ## and write it to $COVERAGE_PATH/zerocount.info + - cd ${CI_PROJECT_DIR} + - cmake --preset ci-coverage + -DCOVERAGE_OUTPUT_DIR=${COVERAGE_PATH} + -DCOVERAGE_ZEROCOUNT_TRACEFILE=${COVERAGE_PATH}/zerocount.info + ## Since the pipeline recreates the source tree, the access times for .gcno + ## files are newer than those of .gcda files. This makes gcov emit a + ## warning for each file which slows it down. Updating the timestamps + ## avoids this + - find ${BUILD_PATH} -name "*.gcno" -exec touch {} \; + - cmake --build ${BUILD_PATH} --target coverage-zerocount + + artifacts: + expire_in: 1 week + when: always + paths: + - ${COVERAGE_PATH}/zerocount.info + coverage: stage: report image: gekkofs/testing:0.9.2 - needs: [ 'coverage:zerocount', 'gkfs:integration', 'gkfwd:integration', + needs: [ 'coverage:baseline', 'gkfs:integration', 'gkfwd:integration', 'gkfs:unit' ] script: - cd ${CI_PROJECT_DIR} -- GitLab