iceoryx2
C++ Language Bindings
Loading...
Searching...
No Matches
testing_error_handler.hpp
Go to the documentation of this file.
1// Copyright (c) 2023 by Apex.AI Inc. All rights reserved.
2// Copyright (c) 2024 by ekxide IO GmbH. All rights reserved.
3// Copyright (c) 2025 Contributors to the Eclipse Foundation
4//
5// See the NOTICE file(s) distributed with this work for additional
6// information regarding copyright ownership.
7//
8// This program and the accompanying materials are made available under the
9// terms of the Apache Software License 2.0 which is available at
10// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
11// which is available at https://opensource.org/licenses/MIT.
12//
13// SPDX-License-Identifier: Apache-2.0 OR MIT
14
15#ifndef IOX2_BB_TESTING_ERROR_REPORTING_TESTING_ERROR_HANDLER_HPP
16#define IOX2_BB_TESTING_ERROR_REPORTING_TESTING_ERROR_HANDLER_HPP
17
24
25#include <gmock/gmock.h>
26#include <gtest/gtest.h>
27
28#include <vector>
29
30// we can use this for test code
31#include <mutex>
32
33// NOLINTNEXTLINE(hicpp-deprecated-headers, modernize-deprecated-headers) required to work on some platforms
34#include <setjmp.h>
35
36namespace iox2 {
37namespace bb {
38namespace testing {
39
42 public:
43 TestingErrorHandler() noexcept = default;
44 ~TestingErrorHandler() noexcept override = default;
45 TestingErrorHandler(const TestingErrorHandler&) noexcept = delete;
47 auto operator=(const TestingErrorHandler&) noexcept -> TestingErrorHandler& = delete;
48 auto operator=(TestingErrorHandler&&) noexcept -> TestingErrorHandler = delete;
49
64 static auto init() noexcept -> void;
65
67 auto on_panic() -> void override {
68 m_panicked = true;
69 jump();
70 }
71
74 auto on_report_error(legacy::er::ErrorDescriptor desc) -> void override {
75 const std::lock_guard<std::mutex> guard(m_mutex);
76 m_errors.push_back(desc);
77 }
78
82 const std::lock_guard<std::mutex> guard(m_mutex);
83 m_violations.push_back(desc);
84 }
85
88 auto has_panicked() const noexcept -> bool {
89 return m_panicked.load(std::memory_order_relaxed);
90 }
91
93 auto reset() noexcept -> void {
94 const std::lock_guard<std::mutex> guard(m_mutex);
95 m_panicked = false;
96 m_errors.clear();
97 m_violations.clear();
98 m_jumpState.store(JumpState::Obtainable);
99 }
100
102 auto has_error() const noexcept -> bool {
103 const std::lock_guard<std::mutex> guard(m_mutex);
104 return !m_errors.empty();
105 }
106
109 -> bool {
110 constexpr legacy::er::ModuleId ANY_MODULE { legacy::er::ModuleId::ANY };
111 const std::lock_guard<std::mutex> guard(m_mutex);
112 for (auto desc : m_errors) {
113 if (desc.code == code) {
114 if (module == ANY_MODULE) {
115 return true;
116 }
117 return desc.module == module;
118 }
119 }
120 return false;
121 }
122
125 auto has_violation(legacy::er::ErrorCode code) const noexcept -> bool {
126 const std::lock_guard<std::mutex> guard(m_mutex);
127 // NOLINTNEXTLINE(readability-use-anyofallof) readability of any_of vs range-based for loops is debatable
128 for (auto desc : m_violations) {
129 if (desc.code == code) {
130 return true;
131 }
132 }
133 return false;
134 }
135
139 auto fatal_failure_test_context(const bb::StaticFunction<void()>& test_function) -> bool {
140 // if there are multiple threads trying to perform a test, only the winner can proceed with the jump
141 if (m_jumpState.exchange(JumpState::Pending) == JumpState::Pending) {
142 return false;
143 };
144
145 // setjmp must be called in a stackframe that still exists when longjmp is called
146 // Therefore there cannot be a convenient abstraction that does not also
147 // know the test function that is being called.
148 // NOLINTNEXTLINE(cert-err52-cpp,modernize-avoid-setjmp-longjmp) exception cannot be used, required for testing to jump in case of failure
149 if (setjmp(&(m_jumpBuffer)[0]) != JUMPED_INDICATOR) {
150 test_function();
151 }
152
153 return true;
154 }
155
156 private:
157 void jump() noexcept {
158 if (m_jumpState.load(std::memory_order_relaxed) == JumpState::Pending) {
159 // NOLINTNEXTLINE(cert-err52-cpp,modernize-avoid-setjmp-longjmp) exception handling is not used by design
160 longjmp(&m_jumpBuffer[0], JUMPED_INDICATOR);
161 }
162 }
163
164 //
165 // private members
166 //
167
168 static constexpr int JUMPED_INDICATOR { 1 };
169
170 mutable std::mutex m_mutex;
171 legacy::concurrent::Atomic<bool> m_panicked { false };
172 std::vector<legacy::er::ErrorDescriptor> m_errors;
173
174 // we track violations separately (leads to simple search)
175 std::vector<legacy::er::ErrorDescriptor> m_violations;
176
177 // if we would like to support concurrent jumps it gets very tricky
178 // and we would need multiple jump buffers
179 jmp_buf m_jumpBuffer {};
180
181 enum class JumpState : uint8_t {
182 Obtainable,
183 Pending,
184 };
185 // Actually not needed to be atomic since it is not supposed to be used from multiple threads
186 // (longjmp does not support this)
187 // We need to ensure though that only one jump buffer is considered by panic and controlling
188 // ownership of the buffer is one way to accomplish that.
189 legacy::concurrent::Atomic<JumpState> m_jumpState { JumpState::Obtainable };
190};
191
193class ErrorHandlerSetup : public ::testing::EmptyTestEventListener {
194 public:
195 void OnTestStart(const ::testing::TestInfo& test_info) override;
196};
197
199
200
201inline auto TestingErrorHandler::init() noexcept -> void {
202 const ErrorHandler handler;
204
205 auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
206 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) required by the callee
207 listeners.Append(new (std::nothrow) ErrorHandlerSetup);
208}
209
210
211inline void ErrorHandlerSetup::OnTestStart(const ::testing::TestInfo& test_info IOX2_MAYBE_UNUSED) {
212 ErrorHandler::instance().reset();
213}
214
215} // namespace testing
216} // namespace bb
217} // namespace iox2
218
219#endif
#define IOX2_MAYBE_UNUSED
IOX2_MAYBE_UNUSED adds the [[gnu::unused]] attribute when it is available for the current compiler or...
This class hooks into gTest to automatically resets the error handler on the start of a test.
void OnTestStart(const ::testing::TestInfo &test_info) override
Defines the test reaction of dynamic error handling.
auto has_violation(legacy::er::ErrorCode code) const noexcept -> bool
Indicates whether a assumption violation occurred previously.
auto on_panic() -> void override
Defines the reaction on panic.
auto reset() noexcept -> void
Reset panic state and clears all errors that occurred previously.
auto has_error(legacy::er::ErrorCode code, legacy::er::ModuleId module=legacy::er::ModuleId()) const noexcept -> bool
Indicates whether a specific error occurred previously.
auto fatal_failure_test_context(const bb::StaticFunction< void()> &test_function) -> bool
runs test_function in a test context that can detect fatal failures; runs in the same thread
auto has_error() const noexcept -> bool
Indicates whether any error occurred previously.
auto on_report_violation(legacy::er::ErrorDescriptor desc) -> void override
Defines the reaction on violation.
auto on_report_error(legacy::er::ErrorDescriptor desc) -> void override
Defines the reaction on error.
static auto init() noexcept -> void
Initialized the error handler. This should be called in the main function of the test binary.
auto has_panicked() const noexcept -> bool
Indicates whether there was a panic call previously.
static bool set(StaticLifetimeGuard< Handler > handlerGuard) noexcept
set the current singleton instance
Manages a static instance of type T in a way so that each existing StaticLifetimeGuard prevents the d...
static T & instance(Args &&... args) noexcept
Construct the instance to be guarded with constructor arguments.
T load(std::memory_order order=std::memory_order_seq_cst) const noexcept
Atomically loads and returns the stored value.
Definition atomic.hpp:129
void store(T value, std::memory_order order=std::memory_order_seq_cst) noexcept
Atomically stores the given value with the given memory order.
Definition atomic.hpp:119
T exchange(T value, std::memory_order order=std::memory_order_seq_cst) noexcept
Atomically exchanges the given value with the stored value using the given memory order and returns t...
Definition atomic.hpp:140
Defines the dynamic error handling interface (i.e. changeable at runtime).
Contains all required information about the error. Can be extended as needed without breaking the int...
static constexpr type ANY
Definition types.hpp:53