Commit 5081bc925c7d7e228a389eb2c7fd4e60d1ad475a

Authored by Patric Stout
Committed by GitHub
1 parent 5d0b9e40

feat: add regression tests and coverage report (#18)

.github/workflows/regression.yaml 0 → 100644
  1 +name: Regression
  2 +
  3 +on:
  4 + push:
  5 + branches:
  6 + - main
  7 + pull_request:
  8 + branches:
  9 + - main
  10 +
  11 +jobs:
  12 + regression:
  13 + name: Regression
  14 + runs-on: ubuntu-latest
  15 +
  16 + strategy:
  17 + fail-fast: false
  18 + matrix:
  19 + include:
  20 + - broker: Mosquitto
  21 + command: docker run --rm -d -p 1883:1883 -v $(pwd)/regression/configs/mosquitto:/mosquitto/config eclipse-mosquitto
  22 + # NATS doesn't deduplicate subscriptions, and this library doesn't either; in result, the regression fails
  23 + # - broker: NATS
  24 + # command: docker run --rm -d -v $(pwd)/regression/configs/nats:/config -p 1883:1883 nats -c /config/nats.conf
  25 + # FlashMQ doesn't deduplicate subscriptions, and this library doesn't either; in result, the regression fails
  26 + # - broker: FlashMQ
  27 + # command: docker run --rm -d -v $(pwd)/regression/configs/flashmq:/etc/flashmq -p 1883:1883 ghcr.io/truebrain/containers/flashmq
  28 +
  29 + steps:
  30 + - name: Checkout
  31 + uses: actions/checkout@v3
  32 +
  33 + - name: Install conan & gcovr
  34 + run: |
  35 + pip install conan gcovr
  36 + conan profile new default --detect
  37 + conan profile update settings.compiler.libcxx=libstdc++11 default
  38 +
  39 + - name: Start ${{ matrix.broker }}
  40 + run: |
  41 + ${{ matrix.command }}
  42 +
  43 + - name: Build library
  44 + run: |
  45 + mkdir build
  46 + cd build
  47 + conan install ..
  48 + cmake .. -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
  49 + make coverage
  50 + make coverage-html
  51 +
  52 + - uses: actions/upload-artifact@v3
  53 + with:
  54 + name: coverage-report-${{ matrix.broker }}
  55 + path: build/coverage-html
... ...
.github/workflows/sonarcloud.yaml
... ... @@ -62,6 +62,8 @@ jobs:
62 62 - name: Install conan
63 63 run: |
64 64 pip install conan
  65 + conan profile new default --detect
  66 + conan profile update settings.compiler.libcxx=libstdc++11 default
65 67  
66 68 - name: Compile
67 69 run: |
... ...
CMakeLists.txt
... ... @@ -13,7 +13,10 @@ set(CMAKE_CXX_STANDARD 17)
13 13 set(CMAKE_CXX_STANDARD_REQUIRED True)
14 14 set(THREADS_PREFER_PTHREAD_FLAG ON)
15 15  
  16 +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/vendor/cmake-modules")
  17 +
16 18 set(MIN_LOGGER_LEVEL "INFO" CACHE STRING "Set minimal logger level (TRACE, DEBUG, INFO, WARNING, ERROR). No logs below this level will be omitted.")
  19 +set(CODE_COVERAGE "OFF" CACHE STRING "Enable code coverage.")
17 20  
18 21 include(GNUInstallDirs)
19 22  
... ... @@ -51,6 +54,25 @@ endif()
51 54  
52 55 target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror)
53 56  
  57 +if(CODE_COVERAGE)
  58 + include(CodeCoverage)
  59 +
  60 + add_subdirectory(regression)
  61 +
  62 + target_compile_options(${PROJECT_NAME} PRIVATE -Og -g --coverage)
  63 + target_link_libraries(${PROJECT_NAME} PRIVATE gcov)
  64 +
  65 + setup_target_for_coverage_gcovr_xml(
  66 + NAME coverage
  67 + EXECUTABLE truemqtt_regression
  68 + BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src")
  69 +
  70 + setup_target_for_coverage_gcovr_html(
  71 + NAME coverage-html
  72 + EXECUTABLE truemqtt_regression
  73 + BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src")
  74 +endif()
  75 +
54 76 install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
55 77 install(FILES ${CMAKE_BINARY_DIR}/truemqtt.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
56 78  
... ...
README.md
... ... @@ -28,6 +28,13 @@ make -j$(nproc)
28 28 example/pubsub/truemqtt_pubsub
29 29 ```
30 30  
  31 +Or in case you want to run the regression / code coverage:
  32 +
  33 +```bash
  34 +cmake .. -DBUILD_SHARED_LIBS=ON -DMIN_LOGGER_LEVEL=INFO -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON
  35 +make -j$(nproc) coverage-html
  36 +```
  37 +
31 38 ## Design choices
32 39  
33 40 ### MQTT v3 only
... ...
regression/CMakeLists.txt 0 → 100644
  1 +#
  2 +# Copyright (c) TrueBrain
  3 +#
  4 +# This source code is licensed under the MIT license found in the
  5 +# LICENSE file in the root directory of this source tree.
  6 +#
  7 +
  8 +cmake_minimum_required(VERSION 3.16)
  9 +
  10 +project(truemqtt_regression)
  11 +
  12 +set(CMAKE_CXX_STANDARD 17)
  13 +set(CMAKE_CXX_STANDARD_REQUIRED ON)
  14 +
  15 +include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/../include)
  16 +
  17 +add_executable(${PROJECT_NAME} main.cpp)
  18 +target_link_libraries(${PROJECT_NAME} truemqtt)
... ...
regression/configs/flashmq/flashmq.conf 0 → 100644
  1 +allow_anonymous true
... ...
regression/configs/mosquitto/mosquitto.conf 0 → 100644
  1 +allow_anonymous true
  2 +bind_address 0.0.0.0
  3 +
... ...
regression/configs/nats/nats.conf 0 → 100644
  1 +jetstream: enabled
  2 +mqtt {
  3 + port: 1883
  4 +}
... ...
regression/main.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) TrueBrain
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#include <TrueMQTT.h>
  9 +
  10 +#include <catch2/catch_test_macros.hpp>
  11 +#include <mutex>
  12 +#include <thread>
  13 +
  14 +#include <iostream>
  15 +
  16 +TEST_CASE("regression", "[regression]")
  17 +{
  18 + // last_message / logger_mutex needs to outlive client1, as it is captured in a lambda.
  19 + std::string last_message = {};
  20 + std::mutex logger_mutex;
  21 +
  22 + TrueMQTT::Client client1("localhost", 1883, "client-1");
  23 + TrueMQTT::Client client2("localhost", 1883, "client-2");
  24 +
  25 + client1.setLogger(
  26 + TrueMQTT::Client::LogLevel::INFO,
  27 + [&last_message, &logger_mutex](TrueMQTT::Client::LogLevel level, const std::string_view message)
  28 + {
  29 + std::scoped_lock lock(logger_mutex);
  30 + last_message = message;
  31 + });
  32 +
  33 + client2.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 10);
  34 +
  35 + uint8_t connected = 0x0;
  36 + client1.setStateChangeCallback(
  37 + [&connected](TrueMQTT::Client::State state)
  38 + {
  39 + if (state == TrueMQTT::Client::State::CONNECTED)
  40 + {
  41 + connected |= 0x1;
  42 + }
  43 + });
  44 + client2.setStateChangeCallback(
  45 + [&connected](TrueMQTT::Client::State state)
  46 + {
  47 + if (state == TrueMQTT::Client::State::CONNECTED)
  48 + {
  49 + connected |= 0x2;
  50 + }
  51 + });
  52 +
  53 + // TC: Cannot publish/subscribe/unsubscribe when disconnected
  54 + {
  55 + CHECK(client1.publish("regression/topic1", "Hello World!", false) == false);
  56 + CHECK(last_message == "Cannot publish when disconnected");
  57 +
  58 + client1.subscribe("regression/topic1", [](const std::string_view topic, const std::string_view message) { /* empty */ });
  59 + CHECK(last_message == "Cannot subscribe when disconnected");
  60 +
  61 + client1.unsubscribe("regression/topic1");
  62 + CHECK(last_message == "Cannot unsubscribe when disconnected");
  63 + }
  64 +
  65 + uint8_t received = 0;
  66 + uint8_t received2 = 0;
  67 +
  68 + // TC: Connect the subscribing client first and instantly make a subscription; subscription should be created after connect.
  69 + {
  70 + client1.connect();
  71 + CHECK(last_message == "Connecting to localhost:1883");
  72 + client1.subscribe(
  73 + "regression/topic1",
  74 + [&received](const std::string_view topic, const std::string_view payload)
  75 + {
  76 + CHECK(std::string(topic) == "regression/topic1");
  77 + received++;
  78 + });
  79 +
  80 + auto start = std::chrono::steady_clock::now();
  81 + while ((connected & 0x1) == 0)
  82 + {
  83 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  84 + if (std::chrono::steady_clock::now() - start > std::chrono::seconds(5))
  85 + {
  86 + FAIL("Timeout waiting for client to connect");
  87 + }
  88 + }
  89 +
  90 + // Give some time for the subscription to actually be made.
  91 + std::this_thread::sleep_for(std::chrono::milliseconds(50));
  92 + }
  93 +
  94 + // TC: Connect the publishing client next and instantly publish a message; message should be published after connect.
  95 + {
  96 + client2.connect();
  97 + CHECK(client2.publish("regression/topic1", "Hello World!", false) == true);
  98 +
  99 + auto start = std::chrono::steady_clock::now();
  100 + while ((connected & 0x2) == 0)
  101 + {
  102 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  103 + if (std::chrono::steady_clock::now() - start > std::chrono::seconds(1))
  104 + {
  105 + FAIL("Timeout waiting for client to connect");
  106 + }
  107 + }
  108 + }
  109 +
  110 + // TC: Publish before connect happened and check message arrives after connection is made
  111 + {
  112 + // Wait for the pending publish to arrive.
  113 + auto start = std::chrono::steady_clock::now();
  114 + while (received != 1)
  115 + {
  116 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  117 + if (std::chrono::steady_clock::now() - start > std::chrono::seconds(1))
  118 + {
  119 + FAIL("Timeout waiting for message to arrive");
  120 + }
  121 + }
  122 + received = 0;
  123 + }
  124 +
  125 + // TC: Cannot connect when already connected
  126 + {
  127 + client1.connect();
  128 + CHECK(last_message == "Can't connect when already connecting / connected");
  129 + }
  130 +
  131 + // TC: Publish to delayed subscription must work
  132 + {
  133 + CHECK(client2.publish("regression/topic1", "Hello World!", false) == true);
  134 + CHECK(client2.publish("regression/topic1", "Hello World!", false) == true);
  135 + CHECK(client2.publish("regression/topic1", "Hello World!", false) == true);
  136 + CHECK(client2.publish("regression/topic1", "Hello World!", false) == true);
  137 +
  138 + auto start = std::chrono::steady_clock::now();
  139 + while (received != 4)
  140 + {
  141 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  142 + if (std::chrono::steady_clock::now() - start > std::chrono::seconds(1))
  143 + {
  144 + FAIL("Timeout waiting for message to arrive");
  145 + }
  146 + }
  147 + received = 0;
  148 + }
  149 +
  150 + // TC: Subscribe to new topic and publish on it
  151 + {
  152 + client1.subscribe(
  153 + "regression/topic2",
  154 + [&received2](const std::string_view topic, const std::string_view payload)
  155 + {
  156 + CHECK(std::string(topic) == "regression/topic2");
  157 + received2++;
  158 + });
  159 +
  160 + // Give some time for the subscription to actually be made.
  161 + std::this_thread::sleep_for(std::chrono::milliseconds(50));
  162 +
  163 + CHECK(client2.publish("regression/topic2", "Hello World!", false) == true);
  164 +
  165 + // Wait for the pending publish to arrive.
  166 + auto start = std::chrono::steady_clock::now();
  167 + while (received2 != 1)
  168 + {
  169 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  170 + if (std::chrono::steady_clock::now() - start > std::chrono::seconds(1))
  171 + {
  172 + FAIL("Timeout waiting for message to arrive");
  173 + }
  174 + }
  175 + received2 = 0;
  176 + }
  177 +
  178 + // TC: Unsubscribe and check a publish to the topic no longer arrives
  179 + {
  180 + client1.unsubscribe("regression/topic1");
  181 + client1.unsubscribe("regression/topic2");
  182 +
  183 + CHECK(client2.publish("regression/topic1", "Hello World!", false) == true);
  184 + CHECK(client2.publish("regression/topic2", "Hello World!", false) == true);
  185 +
  186 + std::this_thread::sleep_for(std::chrono::milliseconds(50));
  187 +
  188 + REQUIRE(received == 0);
  189 + REQUIRE(received2 == 0);
  190 + }
  191 +
  192 + // TC: Check wildcard subscriptions
  193 + {
  194 + client1.subscribe(
  195 + "regression/topic3/+",
  196 + [&received](const std::string_view topic, const std::string_view payload)
  197 + {
  198 + CHECK(std::string(topic) == "regression/topic3/1");
  199 + received++;
  200 + });
  201 + client1.subscribe(
  202 + "regression/#",
  203 + [&received2](const std::string_view topic, const std::string_view payload)
  204 + {
  205 + if (received2 == 0)
  206 + {
  207 + CHECK(std::string(topic) == "regression/topic3/1");
  208 + }
  209 + else
  210 + {
  211 + CHECK(std::string(topic) == "regression/topic4/2");
  212 + }
  213 + received2++;
  214 + });
  215 +
  216 + std::this_thread::sleep_for(std::chrono::milliseconds(50));
  217 +
  218 + CHECK(client2.publish("regression/topic3/1", "Hello World!", false) == true);
  219 + CHECK(client2.publish("regression/topic4/2", "Hello World!", false) == true);
  220 +
  221 + auto start = std::chrono::steady_clock::now();
  222 + while (received != 1 || received2 != 2)
  223 + {
  224 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  225 + if (std::chrono::steady_clock::now() - start > std::chrono::seconds(1))
  226 + {
  227 + FAIL("Timeout waiting for message to arrive");
  228 + }
  229 + }
  230 +
  231 + received = 0;
  232 + received2 = 0;
  233 + }
  234 +
  235 + // TC: Cannot unsubscribe from topic not subscribed to
  236 + {
  237 + client1.unsubscribe("regression/topic1");
  238 + CHECK(last_message == "Cannot unsubscribe from topic 'regression/topic1' because we are not subscribed to it");
  239 + }
  240 +
  241 + // TC: Validate disconnect is seen
  242 + {
  243 + client1.disconnect();
  244 + CHECK(last_message == "Disconnecting from broker");
  245 + client2.disconnect();
  246 + }
  247 +
  248 + // TC: Cannot disconnect when not connected
  249 + {
  250 + client1.disconnect();
  251 + CHECK(last_message == "Can't disconnect when already disconnected");
  252 + }
  253 +
  254 + // TC: Connect to a non-existing broker
  255 + {
  256 + TrueMQTT::Client client3("localhost", 1884, "client-3");
  257 +
  258 + client3.setLogger(
  259 + TrueMQTT::Client::LogLevel::INFO,
  260 + [&last_message, &logger_mutex](TrueMQTT::Client::LogLevel level, const std::string_view message)
  261 + {
  262 + std::scoped_lock lock(logger_mutex);
  263 + last_message = message;
  264 + });
  265 + client3.setStateChangeCallback(
  266 + [&connected](TrueMQTT::Client::State state)
  267 + {
  268 + if (state == TrueMQTT::Client::State::CONNECTED)
  269 + {
  270 + connected |= 0x4;
  271 + }
  272 + });
  273 +
  274 + client3.connect();
  275 +
  276 + CHECK(last_message == "Connecting to localhost:1884");
  277 +
  278 + bool failed = false;
  279 + auto start = std::chrono::steady_clock::now();
  280 + while ((connected & 0x4) == 0)
  281 + {
  282 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  283 + if (std::chrono::steady_clock::now() - start > std::chrono::milliseconds(100))
  284 + {
  285 + failed = true;
  286 + break;
  287 + }
  288 + }
  289 + REQUIRE(failed == true);
  290 +
  291 + client3.disconnect();
  292 + CHECK(last_message == "Disconnecting from broker");
  293 + }
  294 +}
... ...
src/Connection.cpp
... ... @@ -87,6 +87,11 @@ void TrueMQTT::Client::Impl::Connection::runRead()
87 87  
88 88 std::this_thread::sleep_for(m_backoff);
89 89  
  90 + if (m_state == State::STOP)
  91 + {
  92 + break;
  93 + }
  94 +
90 95 // Calculate the next backoff time, slowly reducing how often we retry.
91 96 m_backoff *= 2;
92 97 if (m_backoff > m_impl.m_connection_backoff_max)
... ...
vendor/cmake-modules/CodeCoverage.cmake 0 → 100644
  1 +# Copyright (c) 2012 - 2017, Lars Bilke
  2 +# All rights reserved.
  3 +#
  4 +# Redistribution and use in source and binary forms, with or without modification,
  5 +# are permitted provided that the following conditions are met:
  6 +#
  7 +# 1. Redistributions of source code must retain the above copyright notice, this
  8 +# list of conditions and the following disclaimer.
  9 +#
  10 +# 2. Redistributions in binary form must reproduce the above copyright notice,
  11 +# this list of conditions and the following disclaimer in the documentation
  12 +# and/or other materials provided with the distribution.
  13 +#
  14 +# 3. Neither the name of the copyright holder nor the names of its contributors
  15 +# may be used to endorse or promote products derived from this software without
  16 +# specific prior written permission.
  17 +#
  18 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19 +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20 +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21 +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  22 +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23 +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24 +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  25 +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27 +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28 +#
  29 +# CHANGES:
  30 +#
  31 +# 2012-01-31, Lars Bilke
  32 +# - Enable Code Coverage
  33 +#
  34 +# 2013-09-17, Joakim Söderberg
  35 +# - Added support for Clang.
  36 +# - Some additional usage instructions.
  37 +#
  38 +# 2016-02-03, Lars Bilke
  39 +# - Refactored functions to use named parameters
  40 +#
  41 +# 2017-06-02, Lars Bilke
  42 +# - Merged with modified version from github.com/ufz/ogs
  43 +#
  44 +# 2019-05-06, Anatolii Kurotych
  45 +# - Remove unnecessary --coverage flag
  46 +#
  47 +# 2019-12-13, FeRD (Frank Dana)
  48 +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor
  49 +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments.
  50 +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY
  51 +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list
  52 +# - Set lcov basedir with -b argument
  53 +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be
  54 +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().)
  55 +# - Delete output dir, .info file on 'make clean'
  56 +# - Remove Python detection, since version mismatches will break gcovr
  57 +# - Minor cleanup (lowercase function names, update examples...)
  58 +#
  59 +# 2019-12-19, FeRD (Frank Dana)
  60 +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets
  61 +#
  62 +# 2020-01-19, Bob Apthorpe
  63 +# - Added gfortran support
  64 +#
  65 +# 2020-02-17, FeRD (Frank Dana)
  66 +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters
  67 +# in EXCLUDEs, and remove manual escaping from gcovr targets
  68 +#
  69 +# 2021-01-19, Robin Mueller
  70 +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run
  71 +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional
  72 +# flags to the gcovr command
  73 +#
  74 +# 2020-05-04, Mihchael Davis
  75 +# - Add -fprofile-abs-path to make gcno files contain absolute paths
  76 +# - Fix BASE_DIRECTORY not working when defined
  77 +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines
  78 +#
  79 +# 2021-05-10, Martin Stump
  80 +# - Check if the generator is multi-config before warning about non-Debug builds
  81 +#
  82 +# 2022-02-22, Marko Wehle
  83 +# - Change gcovr output from -o <filename> for --xml <filename> and --html <filename> output respectively.
  84 +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt".
  85 +#
  86 +# USAGE:
  87 +#
  88 +# 1. Copy this file into your cmake modules path.
  89 +#
  90 +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition
  91 +# using a CMake option() to enable it just optionally):
  92 +# include(CodeCoverage)
  93 +#
  94 +# 3. Append necessary compiler flags for all supported source files:
  95 +# append_coverage_compiler_flags()
  96 +# Or for specific target:
  97 +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME)
  98 +#
  99 +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og
  100 +#
  101 +# 4. If you need to exclude additional directories from the report, specify them
  102 +# using full paths in the COVERAGE_EXCLUDES variable before calling
  103 +# setup_target_for_coverage_*().
  104 +# Example:
  105 +# set(COVERAGE_EXCLUDES
  106 +# '${PROJECT_SOURCE_DIR}/src/dir1/*'
  107 +# '/path/to/my/src/dir2/*')
  108 +# Or, use the EXCLUDE argument to setup_target_for_coverage_*().
  109 +# Example:
  110 +# setup_target_for_coverage_lcov(
  111 +# NAME coverage
  112 +# EXECUTABLE testrunner
  113 +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*")
  114 +#
  115 +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set
  116 +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR)
  117 +# Example:
  118 +# set(COVERAGE_EXCLUDES "dir1/*")
  119 +# setup_target_for_coverage_gcovr_html(
  120 +# NAME coverage
  121 +# EXECUTABLE testrunner
  122 +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src"
  123 +# EXCLUDE "dir2/*")
  124 +#
  125 +# 5. Use the functions described below to create a custom make target which
  126 +# runs your test executable and produces a code coverage report.
  127 +#
  128 +# 6. Build a Debug build:
  129 +# cmake -DCMAKE_BUILD_TYPE=Debug ..
  130 +# make
  131 +# make my_coverage_target
  132 +#
  133 +
  134 +include(CMakeParseArguments)
  135 +
  136 +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE)
  137 +
  138 +# Check prereqs
  139 +find_program( GCOV_PATH gcov )
  140 +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl)
  141 +find_program( FASTCOV_PATH NAMES fastcov fastcov.py )
  142 +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat )
  143 +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
  144 +find_program( CPPFILT_PATH NAMES c++filt )
  145 +
  146 +if(NOT GCOV_PATH)
  147 + message(FATAL_ERROR "gcov not found! Aborting...")
  148 +endif() # NOT GCOV_PATH
  149 +
  150 +# Check supported compiler (Clang, GNU and Flang)
  151 +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
  152 +foreach(LANG ${LANGUAGES})
  153 + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
  154 + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3)
  155 + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
  156 + endif()
  157 + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU"
  158 + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang")
  159 + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...")
  160 + endif()
  161 +endforeach()
  162 +
  163 +set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage"
  164 + CACHE INTERNAL "")
  165 +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
  166 + include(CheckCXXCompilerFlag)
  167 + check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path)
  168 + if(HAVE_fprofile_abs_path)
  169 + set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
  170 + endif()
  171 +endif()
  172 +
  173 +set(CMAKE_Fortran_FLAGS_COVERAGE
  174 + ${COVERAGE_COMPILER_FLAGS}
  175 + CACHE STRING "Flags used by the Fortran compiler during coverage builds."
  176 + FORCE )
  177 +set(CMAKE_CXX_FLAGS_COVERAGE
  178 + ${COVERAGE_COMPILER_FLAGS}
  179 + CACHE STRING "Flags used by the C++ compiler during coverage builds."
  180 + FORCE )
  181 +set(CMAKE_C_FLAGS_COVERAGE
  182 + ${COVERAGE_COMPILER_FLAGS}
  183 + CACHE STRING "Flags used by the C compiler during coverage builds."
  184 + FORCE )
  185 +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
  186 + ""
  187 + CACHE STRING "Flags used for linking binaries during coverage builds."
  188 + FORCE )
  189 +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
  190 + ""
  191 + CACHE STRING "Flags used by the shared libraries linker during coverage builds."
  192 + FORCE )
  193 +mark_as_advanced(
  194 + CMAKE_Fortran_FLAGS_COVERAGE
  195 + CMAKE_CXX_FLAGS_COVERAGE
  196 + CMAKE_C_FLAGS_COVERAGE
  197 + CMAKE_EXE_LINKER_FLAGS_COVERAGE
  198 + CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
  199 +
  200 +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
  201 +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG))
  202 + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
  203 +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)
  204 +
  205 +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
  206 + link_libraries(gcov)
  207 +endif()
  208 +
  209 +# Defines a target for running and collection code coverage information
  210 +# Builds dependencies, runs the given executable and outputs reports.
  211 +# NOTE! The executable should always have a ZERO as exit code otherwise
  212 +# the coverage generation will not complete.
  213 +#
  214 +# setup_target_for_coverage_lcov(
  215 +# NAME testrunner_coverage # New target name
  216 +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
  217 +# DEPENDENCIES testrunner # Dependencies to build first
  218 +# BASE_DIRECTORY "../" # Base directory for report
  219 +# # (defaults to PROJECT_SOURCE_DIR)
  220 +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
  221 +# # to BASE_DIRECTORY, with CMake 3.4+)
  222 +# NO_DEMANGLE # Don't demangle C++ symbols
  223 +# # even if c++filt is found
  224 +# )
  225 +function(setup_target_for_coverage_lcov)
  226 +
  227 + set(options NO_DEMANGLE SONARQUBE)
  228 + set(oneValueArgs BASE_DIRECTORY NAME)
  229 + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS)
  230 + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  231 +
  232 + if(NOT LCOV_PATH)
  233 + message(FATAL_ERROR "lcov not found! Aborting...")
  234 + endif() # NOT LCOV_PATH
  235 +
  236 + if(NOT GENHTML_PATH)
  237 + message(FATAL_ERROR "genhtml not found! Aborting...")
  238 + endif() # NOT GENHTML_PATH
  239 +
  240 + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
  241 + if(DEFINED Coverage_BASE_DIRECTORY)
  242 + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
  243 + else()
  244 + set(BASEDIR ${PROJECT_SOURCE_DIR})
  245 + endif()
  246 +
  247 + # Collect excludes (CMake 3.4+: Also compute absolute paths)
  248 + set(LCOV_EXCLUDES "")
  249 + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES})
  250 + if(CMAKE_VERSION VERSION_GREATER 3.4)
  251 + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
  252 + endif()
  253 + list(APPEND LCOV_EXCLUDES "${EXCLUDE}")
  254 + endforeach()
  255 + list(REMOVE_DUPLICATES LCOV_EXCLUDES)
  256 +
  257 + # Conditional arguments
  258 + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
  259 + set(GENHTML_EXTRA_ARGS "--demangle-cpp")
  260 + endif()
  261 +
  262 + # Setting up commands which will be run to generate coverage data.
  263 + # Cleanup lcov
  264 + set(LCOV_CLEAN_CMD
  265 + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory .
  266 + -b ${BASEDIR} --zerocounters
  267 + )
  268 + # Create baseline to make sure untouched files show up in the report
  269 + set(LCOV_BASELINE_CMD
  270 + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b
  271 + ${BASEDIR} -o ${Coverage_NAME}.base
  272 + )
  273 + # Run tests
  274 + set(LCOV_EXEC_TESTS_CMD
  275 + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
  276 + )
  277 + # Capturing lcov counters and generating report
  278 + set(LCOV_CAPTURE_CMD
  279 + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b
  280 + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture
  281 + )
  282 + # add baseline counters
  283 + set(LCOV_BASELINE_COUNT_CMD
  284 + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base
  285 + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total
  286 + )
  287 + # filter collected data to final coverage report
  288 + set(LCOV_FILTER_CMD
  289 + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove
  290 + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info
  291 + )
  292 + # Generate HTML output
  293 + set(LCOV_GEN_HTML_CMD
  294 + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o
  295 + ${Coverage_NAME} ${Coverage_NAME}.info
  296 + )
  297 + if(${Coverage_SONARQUBE})
  298 + # Generate SonarQube output
  299 + set(GCOVR_XML_CMD
  300 + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
  301 + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR}
  302 + )
  303 + set(GCOVR_XML_CMD_COMMAND
  304 + COMMAND ${GCOVR_XML_CMD}
  305 + )
  306 + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml)
  307 + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.")
  308 + endif()
  309 +
  310 +
  311 + if(CODE_COVERAGE_VERBOSE)
  312 + message(STATUS "Executed command report")
  313 + message(STATUS "Command to clean up lcov: ")
  314 + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}")
  315 + message(STATUS "${LCOV_CLEAN_CMD_SPACED}")
  316 +
  317 + message(STATUS "Command to create baseline: ")
  318 + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}")
  319 + message(STATUS "${LCOV_BASELINE_CMD_SPACED}")
  320 +
  321 + message(STATUS "Command to run the tests: ")
  322 + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}")
  323 + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}")
  324 +
  325 + message(STATUS "Command to capture counters and generate report: ")
  326 + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}")
  327 + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}")
  328 +
  329 + message(STATUS "Command to add baseline counters: ")
  330 + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}")
  331 + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}")
  332 +
  333 + message(STATUS "Command to filter collected data: ")
  334 + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}")
  335 + message(STATUS "${LCOV_FILTER_CMD_SPACED}")
  336 +
  337 + message(STATUS "Command to generate lcov HTML output: ")
  338 + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}")
  339 + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}")
  340 +
  341 + if(${Coverage_SONARQUBE})
  342 + message(STATUS "Command to generate SonarQube XML output: ")
  343 + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}")
  344 + message(STATUS "${GCOVR_XML_CMD_SPACED}")
  345 + endif()
  346 + endif()
  347 +
  348 + # Setup target
  349 + add_custom_target(${Coverage_NAME}
  350 + COMMAND ${LCOV_CLEAN_CMD}
  351 + COMMAND ${LCOV_BASELINE_CMD}
  352 + COMMAND ${LCOV_EXEC_TESTS_CMD}
  353 + COMMAND ${LCOV_CAPTURE_CMD}
  354 + COMMAND ${LCOV_BASELINE_COUNT_CMD}
  355 + COMMAND ${LCOV_FILTER_CMD}
  356 + COMMAND ${LCOV_GEN_HTML_CMD}
  357 + ${GCOVR_XML_CMD_COMMAND}
  358 +
  359 + # Set output files as GENERATED (will be removed on 'make clean')
  360 + BYPRODUCTS
  361 + ${Coverage_NAME}.base
  362 + ${Coverage_NAME}.capture
  363 + ${Coverage_NAME}.total
  364 + ${Coverage_NAME}.info
  365 + ${GCOVR_XML_CMD_BYPRODUCTS}
  366 + ${Coverage_NAME}/index.html
  367 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
  368 + DEPENDS ${Coverage_DEPENDENCIES}
  369 + VERBATIM # Protect arguments to commands
  370 + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
  371 + )
  372 +
  373 + # Show where to find the lcov info report
  374 + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
  375 + COMMAND ;
  376 + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info."
  377 + ${GCOVR_XML_CMD_COMMENT}
  378 + )
  379 +
  380 + # Show info where to find the report
  381 + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
  382 + COMMAND ;
  383 + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
  384 + )
  385 +
  386 +endfunction() # setup_target_for_coverage_lcov
  387 +
  388 +# Defines a target for running and collection code coverage information
  389 +# Builds dependencies, runs the given executable and outputs reports.
  390 +# NOTE! The executable should always have a ZERO as exit code otherwise
  391 +# the coverage generation will not complete.
  392 +#
  393 +# setup_target_for_coverage_gcovr_xml(
  394 +# NAME ctest_coverage # New target name
  395 +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
  396 +# DEPENDENCIES executable_target # Dependencies to build first
  397 +# BASE_DIRECTORY "../" # Base directory for report
  398 +# # (defaults to PROJECT_SOURCE_DIR)
  399 +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
  400 +# # to BASE_DIRECTORY, with CMake 3.4+)
  401 +# )
  402 +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
  403 +# GCVOR command.
  404 +function(setup_target_for_coverage_gcovr_xml)
  405 +
  406 + set(options NONE)
  407 + set(oneValueArgs BASE_DIRECTORY NAME)
  408 + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
  409 + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  410 +
  411 + if(NOT GCOVR_PATH)
  412 + message(FATAL_ERROR "gcovr not found! Aborting...")
  413 + endif() # NOT GCOVR_PATH
  414 +
  415 + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
  416 + if(DEFINED Coverage_BASE_DIRECTORY)
  417 + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
  418 + else()
  419 + set(BASEDIR ${PROJECT_SOURCE_DIR})
  420 + endif()
  421 +
  422 + # Collect excludes (CMake 3.4+: Also compute absolute paths)
  423 + set(GCOVR_EXCLUDES "")
  424 + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
  425 + if(CMAKE_VERSION VERSION_GREATER 3.4)
  426 + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
  427 + endif()
  428 + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
  429 + endforeach()
  430 + list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
  431 +
  432 + # Combine excludes to several -e arguments
  433 + set(GCOVR_EXCLUDE_ARGS "")
  434 + foreach(EXCLUDE ${GCOVR_EXCLUDES})
  435 + list(APPEND GCOVR_EXCLUDE_ARGS "-e")
  436 + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
  437 + endforeach()
  438 +
  439 + # Set up commands which will be run to generate coverage data
  440 + # Run tests
  441 + set(GCOVR_XML_EXEC_TESTS_CMD
  442 + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
  443 + )
  444 + # Running gcovr
  445 + set(GCOVR_XML_CMD
  446 + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
  447 + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR}
  448 + )
  449 +
  450 + if(CODE_COVERAGE_VERBOSE)
  451 + message(STATUS "Executed command report")
  452 +
  453 + message(STATUS "Command to run tests: ")
  454 + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}")
  455 + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}")
  456 +
  457 + message(STATUS "Command to generate gcovr XML coverage data: ")
  458 + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}")
  459 + message(STATUS "${GCOVR_XML_CMD_SPACED}")
  460 + endif()
  461 +
  462 + add_custom_target(${Coverage_NAME}
  463 + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD}
  464 + COMMAND ${GCOVR_XML_CMD}
  465 +
  466 + BYPRODUCTS ${Coverage_NAME}.xml
  467 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
  468 + DEPENDS ${Coverage_DEPENDENCIES}
  469 + VERBATIM # Protect arguments to commands
  470 + COMMENT "Running gcovr to produce Cobertura code coverage report."
  471 + )
  472 +
  473 + # Show info where to find the report
  474 + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
  475 + COMMAND ;
  476 + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
  477 + )
  478 +endfunction() # setup_target_for_coverage_gcovr_xml
  479 +
  480 +# Defines a target for running and collection code coverage information
  481 +# Builds dependencies, runs the given executable and outputs reports.
  482 +# NOTE! The executable should always have a ZERO as exit code otherwise
  483 +# the coverage generation will not complete.
  484 +#
  485 +# setup_target_for_coverage_gcovr_html(
  486 +# NAME ctest_coverage # New target name
  487 +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
  488 +# DEPENDENCIES executable_target # Dependencies to build first
  489 +# BASE_DIRECTORY "../" # Base directory for report
  490 +# # (defaults to PROJECT_SOURCE_DIR)
  491 +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
  492 +# # to BASE_DIRECTORY, with CMake 3.4+)
  493 +# )
  494 +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
  495 +# GCVOR command.
  496 +function(setup_target_for_coverage_gcovr_html)
  497 +
  498 + set(options NONE)
  499 + set(oneValueArgs BASE_DIRECTORY NAME)
  500 + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
  501 + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  502 +
  503 + if(NOT GCOVR_PATH)
  504 + message(FATAL_ERROR "gcovr not found! Aborting...")
  505 + endif() # NOT GCOVR_PATH
  506 +
  507 + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
  508 + if(DEFINED Coverage_BASE_DIRECTORY)
  509 + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
  510 + else()
  511 + set(BASEDIR ${PROJECT_SOURCE_DIR})
  512 + endif()
  513 +
  514 + # Collect excludes (CMake 3.4+: Also compute absolute paths)
  515 + set(GCOVR_EXCLUDES "")
  516 + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
  517 + if(CMAKE_VERSION VERSION_GREATER 3.4)
  518 + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
  519 + endif()
  520 + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
  521 + endforeach()
  522 + list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
  523 +
  524 + # Combine excludes to several -e arguments
  525 + set(GCOVR_EXCLUDE_ARGS "")
  526 + foreach(EXCLUDE ${GCOVR_EXCLUDES})
  527 + list(APPEND GCOVR_EXCLUDE_ARGS "-e")
  528 + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
  529 + endforeach()
  530 +
  531 + # Set up commands which will be run to generate coverage data
  532 + # Run tests
  533 + set(GCOVR_HTML_EXEC_TESTS_CMD
  534 + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
  535 + )
  536 + # Create folder
  537 + set(GCOVR_HTML_FOLDER_CMD
  538 + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME}
  539 + )
  540 + # Running gcovr
  541 + set(GCOVR_HTML_CMD
  542 + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
  543 + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR}
  544 + )
  545 +
  546 + if(CODE_COVERAGE_VERBOSE)
  547 + message(STATUS "Executed command report")
  548 +
  549 + message(STATUS "Command to run tests: ")
  550 + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}")
  551 + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}")
  552 +
  553 + message(STATUS "Command to create a folder: ")
  554 + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}")
  555 + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}")
  556 +
  557 + message(STATUS "Command to generate gcovr HTML coverage data: ")
  558 + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}")
  559 + message(STATUS "${GCOVR_HTML_CMD_SPACED}")
  560 + endif()
  561 +
  562 + add_custom_target(${Coverage_NAME}
  563 + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD}
  564 + COMMAND ${GCOVR_HTML_FOLDER_CMD}
  565 + COMMAND ${GCOVR_HTML_CMD}
  566 +
  567 + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory
  568 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
  569 + DEPENDS ${Coverage_DEPENDENCIES}
  570 + VERBATIM # Protect arguments to commands
  571 + COMMENT "Running gcovr to produce HTML code coverage report."
  572 + )
  573 +
  574 + # Show info where to find the report
  575 + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
  576 + COMMAND ;
  577 + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
  578 + )
  579 +
  580 +endfunction() # setup_target_for_coverage_gcovr_html
  581 +
  582 +# Defines a target for running and collection code coverage information
  583 +# Builds dependencies, runs the given executable and outputs reports.
  584 +# NOTE! The executable should always have a ZERO as exit code otherwise
  585 +# the coverage generation will not complete.
  586 +#
  587 +# setup_target_for_coverage_fastcov(
  588 +# NAME testrunner_coverage # New target name
  589 +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
  590 +# DEPENDENCIES testrunner # Dependencies to build first
  591 +# BASE_DIRECTORY "../" # Base directory for report
  592 +# # (defaults to PROJECT_SOURCE_DIR)
  593 +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude.
  594 +# NO_DEMANGLE # Don't demangle C++ symbols
  595 +# # even if c++filt is found
  596 +# SKIP_HTML # Don't create html report
  597 +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths
  598 +# )
  599 +function(setup_target_for_coverage_fastcov)
  600 +
  601 + set(options NO_DEMANGLE SKIP_HTML)
  602 + set(oneValueArgs BASE_DIRECTORY NAME)
  603 + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD)
  604 + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  605 +
  606 + if(NOT FASTCOV_PATH)
  607 + message(FATAL_ERROR "fastcov not found! Aborting...")
  608 + endif()
  609 +
  610 + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH)
  611 + message(FATAL_ERROR "genhtml not found! Aborting...")
  612 + endif()
  613 +
  614 + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
  615 + if(Coverage_BASE_DIRECTORY)
  616 + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
  617 + else()
  618 + set(BASEDIR ${PROJECT_SOURCE_DIR})
  619 + endif()
  620 +
  621 + # Collect excludes (Patterns, not paths, for fastcov)
  622 + set(FASTCOV_EXCLUDES "")
  623 + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES})
  624 + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}")
  625 + endforeach()
  626 + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES)
  627 +
  628 + # Conditional arguments
  629 + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
  630 + set(GENHTML_EXTRA_ARGS "--demangle-cpp")
  631 + endif()
  632 +
  633 + # Set up commands which will be run to generate coverage data
  634 + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS})
  635 +
  636 + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH}
  637 + --search-directory ${BASEDIR}
  638 + --process-gcno
  639 + --output ${Coverage_NAME}.json
  640 + --exclude ${FASTCOV_EXCLUDES}
  641 + --exclude ${FASTCOV_EXCLUDES}
  642 + )
  643 +
  644 + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH}
  645 + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info
  646 + )
  647 +
  648 + if(Coverage_SKIP_HTML)
  649 + set(FASTCOV_HTML_CMD ";")
  650 + else()
  651 + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS}
  652 + -o ${Coverage_NAME} ${Coverage_NAME}.info
  653 + )
  654 + endif()
  655 +
  656 + set(FASTCOV_POST_CMD ";")
  657 + if(Coverage_POST_CMD)
  658 + set(FASTCOV_POST_CMD ${Coverage_POST_CMD})
  659 + endif()
  660 +
  661 + if(CODE_COVERAGE_VERBOSE)
  662 + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):")
  663 +
  664 + message(" Running tests:")
  665 + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}")
  666 + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}")
  667 +
  668 + message(" Capturing fastcov counters and generating report:")
  669 + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}")
  670 + message(" ${FASTCOV_CAPTURE_CMD_SPACED}")
  671 +
  672 + message(" Converting fastcov .json to lcov .info:")
  673 + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}")
  674 + message(" ${FASTCOV_CONVERT_CMD_SPACED}")
  675 +
  676 + if(NOT Coverage_SKIP_HTML)
  677 + message(" Generating HTML report: ")
  678 + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}")
  679 + message(" ${FASTCOV_HTML_CMD_SPACED}")
  680 + endif()
  681 + if(Coverage_POST_CMD)
  682 + message(" Running post command: ")
  683 + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}")
  684 + message(" ${FASTCOV_POST_CMD_SPACED}")
  685 + endif()
  686 + endif()
  687 +
  688 + # Setup target
  689 + add_custom_target(${Coverage_NAME}
  690 +
  691 + # Cleanup fastcov
  692 + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH}
  693 + --search-directory ${BASEDIR}
  694 + --zerocounters
  695 +
  696 + COMMAND ${FASTCOV_EXEC_TESTS_CMD}
  697 + COMMAND ${FASTCOV_CAPTURE_CMD}
  698 + COMMAND ${FASTCOV_CONVERT_CMD}
  699 + COMMAND ${FASTCOV_HTML_CMD}
  700 + COMMAND ${FASTCOV_POST_CMD}
  701 +
  702 + # Set output files as GENERATED (will be removed on 'make clean')
  703 + BYPRODUCTS
  704 + ${Coverage_NAME}.info
  705 + ${Coverage_NAME}.json
  706 + ${Coverage_NAME}/index.html # report directory
  707 +
  708 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
  709 + DEPENDS ${Coverage_DEPENDENCIES}
  710 + VERBATIM # Protect arguments to commands
  711 + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report."
  712 + )
  713 +
  714 + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.")
  715 + if(NOT Coverage_SKIP_HTML)
  716 + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.")
  717 + endif()
  718 + # Show where to find the fastcov info report
  719 + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
  720 + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG}
  721 + )
  722 +
  723 +endfunction() # setup_target_for_coverage_fastcov
  724 +
  725 +function(append_coverage_compiler_flags)
  726 + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
  727 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
  728 + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
  729 + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
  730 +endfunction() # append_coverage_compiler_flags
  731 +
  732 +# Setup coverage for specific library
  733 +function(append_coverage_compiler_flags_to_target name)
  734 + target_compile_options(${name}
  735 + PRIVATE ${COVERAGE_COMPILER_FLAGS})
  736 +endfunction()
... ...
vendor/cmake-modules/README.md 0 → 100644
  1 +# CMake Modules
  2 +
  3 +This is part of the master of [cmake-modules](https://github.com/bilke/cmake-modules) on 2022-10-22.
  4 +
  5 +See upstream for more details about cmake-modules.
... ...