Commit 5081bc925c7d7e228a389eb2c7fd4e60d1ad475a
Committed by
GitHub
1 parent
5d0b9e40
feat: add regression tests and coverage report (#18)
Showing
12 changed files
with
1152 additions
and
0 deletions
.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,6 +62,8 @@ jobs: | ||
| 62 | - name: Install conan | 62 | - name: Install conan |
| 63 | run: | | 63 | run: | |
| 64 | pip install conan | 64 | pip install conan |
| 65 | + conan profile new default --detect | ||
| 66 | + conan profile update settings.compiler.libcxx=libstdc++11 default | ||
| 65 | 67 | ||
| 66 | - name: Compile | 68 | - name: Compile |
| 67 | run: | | 69 | run: | |
CMakeLists.txt
| @@ -13,7 +13,10 @@ set(CMAKE_CXX_STANDARD 17) | @@ -13,7 +13,10 @@ set(CMAKE_CXX_STANDARD 17) | ||
| 13 | set(CMAKE_CXX_STANDARD_REQUIRED True) | 13 | set(CMAKE_CXX_STANDARD_REQUIRED True) |
| 14 | set(THREADS_PREFER_PTHREAD_FLAG ON) | 14 | set(THREADS_PREFER_PTHREAD_FLAG ON) |
| 15 | 15 | ||
| 16 | +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/vendor/cmake-modules") | ||
| 17 | + | ||
| 16 | set(MIN_LOGGER_LEVEL "INFO" CACHE STRING "Set minimal logger level (TRACE, DEBUG, INFO, WARNING, ERROR). No logs below this level will be omitted.") | 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 | include(GNUInstallDirs) | 21 | include(GNUInstallDirs) |
| 19 | 22 | ||
| @@ -51,6 +54,25 @@ endif() | @@ -51,6 +54,25 @@ endif() | ||
| 51 | 54 | ||
| 52 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror) | 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 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) | 76 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) |
| 55 | install(FILES ${CMAKE_BINARY_DIR}/truemqtt.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) | 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,6 +28,13 @@ make -j$(nproc) | ||
| 28 | example/pubsub/truemqtt_pubsub | 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 | ## Design choices | 38 | ## Design choices |
| 32 | 39 | ||
| 33 | ### MQTT v3 only | 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
regression/configs/nats/nats.conf
0 → 100644
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,6 +87,11 @@ void TrueMQTT::Client::Impl::Connection::runRead() | ||
| 87 | 87 | ||
| 88 | std::this_thread::sleep_for(m_backoff); | 88 | std::this_thread::sleep_for(m_backoff); |
| 89 | 89 | ||
| 90 | + if (m_state == State::STOP) | ||
| 91 | + { | ||
| 92 | + break; | ||
| 93 | + } | ||
| 94 | + | ||
| 90 | // Calculate the next backoff time, slowly reducing how often we retry. | 95 | // Calculate the next backoff time, slowly reducing how often we retry. |
| 91 | m_backoff *= 2; | 96 | m_backoff *= 2; |
| 92 | if (m_backoff > m_impl.m_connection_backoff_max) | 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() |