Skip to content

Commit

Permalink
iox-#590 Use a typed and untyped HandlerFunction to avoid problems in…
Browse files Browse the repository at this point in the history
… tests

Signed-off-by: Simon Hoinkis <[email protected]>
  • Loading branch information
mossmaurice committed Feb 7, 2022
1 parent 7e3f82d commit 467b9f7
Show file tree
Hide file tree
Showing 26 changed files with 225 additions and 220 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ TEST(c2cpp_enum_translation_test, SubscriberState)
// the clang sanitizer detects this successfully and this leads to termination, and with this the test fails
#if !defined(__clang__)
iox::Error errorValue = iox::Error::kNO_ERROR;
auto errorHandlerGuard = iox::ErrorHandler<iox::Error>::setTemporaryErrorHandler(
[&](const iox::Error e, const std::function<void()>, const iox::ErrorLevel) { errorValue = e; });
auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler<iox::Error>(
[&](const iox::Error e, const iox::ErrorLevel) { errorValue = e; });
EXPECT_EQ(c2cpp::queueFullPolicy(static_cast<iox_QueueFullPolicy>(-1)),
iox::popo::QueueFullPolicy::DISCARD_OLDEST_DATA);
EXPECT_THAT(errorValue, Eq(iox::Error::kBINDING_C__UNDEFINED_STATE_IN_IOX_QUEUE_FULL_POLICY));
Expand Down Expand Up @@ -95,8 +95,8 @@ TEST(c2cpp_enum_translation_test, SubscriberEvent)
// the clang sanitizer detects this successfully and this leads to termination, and with this the test fails
#if !defined(__clang__)
iox::Error errorValue = iox::Error::kNO_ERROR;
auto errorHandlerGuard = iox::ErrorHandler<iox::Error>::setTemporaryErrorHandler(
[&](const iox::Error e, const std::function<void()>, const iox::ErrorLevel) { errorValue = e; });
auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler<iox::Error>(
[&](const iox::Error e, const iox::ErrorLevel) { errorValue = e; });
EXPECT_EQ(c2cpp::subscriberTooSlowPolicy(static_cast<iox_SubscriberTooSlowPolicy>(-1)),
iox::popo::SubscriberTooSlowPolicy::DISCARD_OLDEST_DATA);
EXPECT_THAT(errorValue, Eq(iox::Error::kBINDING_C__UNDEFINED_STATE_IN_IOX_SUBSCRIBER_TOO_SLOW_POLICY));
Expand Down
1 change: 1 addition & 0 deletions iceoryx_hoofs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ if(GTest_FOUND) # only GTest_FOUND, just in case someone want's to use iceoryx_h
testing/mocks/time_mock.cpp
testing/timing_test.cpp
testing/compile_test.cpp
testing/test.cpp
)

add_library(iceoryx_hoofs_testing::iceoryx_hoofs_testing ALIAS iceoryx_hoofs_testing)
Expand Down
156 changes: 69 additions & 87 deletions iceoryx_hoofs/include/iceoryx_hoofs/error_handling/error_handling.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
/// @todo #590 rename this file to error_handler.hpp

#include "iceoryx_hoofs/cxx/generic_raii.hpp"
#include "iceoryx_hoofs/cxx/type_traits.hpp"
#include "iceoryx_hoofs/log/logger.hpp"
#include "iceoryx_hoofs/log/logging.hpp"
#include "iceoryx_hoofs/log/logmanager.hpp"
Expand All @@ -29,8 +30,13 @@
#include <iostream>
#include <mutex>
#include <sstream>


namespace iox
{
// Forward declaration
void outputToGTestFail(const char* error);

// clang-format off
#define ICEORYX_ERRORS(error) \
error(NO_ERROR)\
Expand Down Expand Up @@ -173,12 +179,6 @@ enum class Error : uint32_t
ICEORYX_ERRORS(CREATE_ICEORYX_ERROR_ENUM)
};

/// @brief Convenience stream operator to easily use the Error enum with std::ostream
/// @param[in] stream sink to write the message to
/// @param[in] value to convert to a string literal
/// @return the reference to `stream` which was provided as input parameter
std::ostream& operator<<(std::ostream& stream, Error value) noexcept;

/// @brief the available error levels
/// FATAL
/// - Log message with FATAL
Expand All @@ -205,37 +205,6 @@ enum class ErrorLevel : uint32_t
MODERATE
};

template <typename Error>
using HandlerFunction = std::function<void(const Error error, const std::function<void()>, const ErrorLevel)>;

/// @brief This handler is needed for unit testing, special debugging cases and
/// other corner cases where we'd like to explicitly suppress the
/// error handling.
/// @tparam[in] Error type of the enum which is used to report the error
template <typename Error>
class ErrorHandler
{
template <typename ErrorType>
friend void
errorHandler(const ErrorType error, const std::function<void()>& errorCallBack, const ErrorLevel level) noexcept;

public:
static cxx::GenericRAII setTemporaryErrorHandler(const HandlerFunction<Error>& newHandler) noexcept;

protected:
static void reactOnErrorLevel(const ErrorLevel level, const char* errorText) noexcept;

private:
static void defaultHandler(const Error error,
const std::function<void()>& errorCallBack,
const ErrorLevel level = ErrorLevel::FATAL) noexcept;

static iox::HandlerFunction<Error> handler;
/// Needed, if you want to exchange the handler. Remember the old one and call it if it is not your error. The error
/// mock needs to be the last one exchanging the handler in tests.
static std::mutex handler_mutex;
};

/// @brief Howto use the error handler correctly
/// 1.) Use the macro ICEORYX_ERRORS(error)\ to create the enum for your component and
/// add new errors like:
Expand All @@ -245,9 +214,6 @@ class ErrorHandler
/// And a long name is alright!
///
/// 2.) Specialize the following methods for your NewEnumErrorType:
/// - defaultHandler(const NewEnumErrorType error, const std::function<void()>& errorCallBack, const
/// ErrorLevel level)
/// - std::ostream& operator<<(std::ostream& stream, NewEnumErrorType value)
/// - const char* toString(const NewEnumErrorType error)
///
/// 3.) Call errorHandler(Error::kMODULE_NAME__MY_FUNKY_ERROR);
Expand All @@ -271,7 +237,7 @@ class ErrorHandler
///
/// @code
/// bool called = false;
/// auto temporaryErrorHandler = ErrorHandler<ErrorType>::setTemporaryErrorHandler(
/// auto temporaryErrorHandler = ErrorHandler::setTemporaryErrorHandler(
/// [&](const Error e, std::function<void()>, const ErrorLevel) {
/// called = true;
/// });
Expand All @@ -280,78 +246,94 @@ class ErrorHandler
/// ASSERT_TRUE(called);
/// @endcode
/// @tparam[in] Error type of the enum which is used to report the error
/// @todo use enable_if with is_enum
/// @todo #590 use enable_if with is_enum
template <typename Error>
inline void errorHandler(const Error error,
const std::function<void()>& errorCallBack = std::function<void()>(),
const ErrorLevel level = ErrorLevel::FATAL) noexcept
{
ErrorHandler<Error>::handler(error, errorCallBack, level);
}
void errorHandler(const Error error,
const std::function<void()>& errorCallBack = std::function<void()>(),
const ErrorLevel level = ErrorLevel::FATAL) noexcept;

template <typename Error>
using TypedHandlerFunction = std::function<void(const Error, const ErrorLevel)>;

using HandlerFunction = std::function<void(const uint32_t, const char*, const ErrorLevel)>;

/// @todo #590 specialise for posh and c binding error handling
template <>
inline void ErrorHandler<Error>::defaultHandler(const Error error,
const std::function<void()>& errorCallBack,
const ErrorLevel level) noexcept
/// @brief This handler is needed for unit testing, special debugging cases and
/// other corner cases where we'd like to explicitly suppress the
/// error handling.
class ErrorHandler
{
if (errorCallBack)
{
errorCallBack();
}
else
{
std::stringstream ss;
ss << "iceoryx_posh error! " << error;
template <typename Error>
friend void
errorHandler(const Error error, const std::function<void()>& errorCallBack, const ErrorLevel level) noexcept;

reactOnErrorLevel(level, ss.str().c_str());
}
}
public:
template <typename Error>
static cxx::GenericRAII setTemporaryErrorHandler(const TypedHandlerFunction<Error>& newHandler) noexcept;

/// @todo #590 move the below to .inl
protected:
static void reactOnErrorLevel(const ErrorLevel level, const char* errorText) noexcept;

template <typename Error>
std::mutex ErrorHandler<Error>::handler_mutex;
private:
static void
defaultHandler(const uint32_t error, const char* errorName, const ErrorLevel level = ErrorLevel::FATAL) noexcept;

static iox::HandlerFunction handler;
/// Needed, if you want to exchange the handler. Remember the old one and call it if it is not your error. The error
/// mock needs to be the last one exchanging the handler in tests.
static std::mutex handler_mutex;
};

const char* toString(const Error error) noexcept;

// NOLINTNEXTLINE(cert-err58-cpp) ErrorHander only used in tests
template <typename Error>
HandlerFunction<Error> ErrorHandler<Error>::handler = {ErrorHandler::defaultHandler};
cxx::optional<iox::TypedHandlerFunction<Error>> typedHandler;


/// @todo #590 move the implementations below to .inl
template <typename Error>
inline void ErrorHandler<Error>::reactOnErrorLevel(const ErrorLevel level, const char* errorText) noexcept
inline void errorHandler(const Error error,
const std::function<void()>& errorCallBack IOX_MAYBE_UNUSED,
const ErrorLevel level) noexcept
{
static auto& logger = createLogger("", "", log::LogManager::GetLogManager().DefaultLogLevel());
switch (level)
ErrorHandler::handler(static_cast<typename std::underlying_type<Error>::type>(error), toString(error), level);
}

template <typename ErrorEnumType>
inline void
errorHandlerForTest(const uint32_t error, const char* errorName IOX_MAYBE_UNUSED, const ErrorLevel level) noexcept
{
uint32_t errorEnumType = error >> 16;
uint32_t expectedErrorEnumType =
static_cast<typename std::underlying_type<ErrorEnumType>::type>(ErrorEnumType::kNO_ERROR) >> 16;

if (errorEnumType == expectedErrorEnumType)
{
case ErrorLevel::FATAL:
logger.LogError() << errorText;
assert(false);
std::terminate();
break;
case ErrorLevel::SEVERE:
logger.LogWarn() << errorText;
assert(false);
break;
case ErrorLevel::MODERATE:
logger.LogWarn() << errorText;
break;
// We undo the type erasure
auto typedError = static_cast<ErrorEnumType>(error);
typedHandler<iox::Error>.and_then(
[&](TypedHandlerFunction<Error> storedHandler) { storedHandler(typedError, level); });
}
else
{
outputToGTestFail(errorName);
}
}

template <typename Error>
inline cxx::GenericRAII ErrorHandler<Error>::setTemporaryErrorHandler(const HandlerFunction<Error>& newHandler) noexcept
inline cxx::GenericRAII ErrorHandler::setTemporaryErrorHandler(const TypedHandlerFunction<Error>& newHandler) noexcept
{
return cxx::GenericRAII(
[&newHandler] {
std::lock_guard<std::mutex> lock(handler_mutex);
handler = newHandler;
typedHandler<iox::Error>.emplace(newHandler);
handler = errorHandlerForTest<Error>;
},
[] {
std::lock_guard<std::mutex> lock(handler_mutex);
typedHandler<iox::Error>.reset();
handler = defaultHandler;
});
}

} // namespace iox

#endif // IOX_HOOFS_ERROR_HANDLING_ERROR_HANDLING_HPP
38 changes: 34 additions & 4 deletions iceoryx_hoofs/source/error_handling/error_handling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,44 @@ namespace iox
{
const char* ERROR_NAMES[] = {ICEORYX_ERRORS(CREATE_ICEORYX_ERROR_STRING)};

std::mutex ErrorHandler::handler_mutex;

// NOLINTNEXTLINE(cert-err58-cpp) ErrorHander only used in tests
iox::HandlerFunction ErrorHandler::handler = {ErrorHandler::defaultHandler};

const char* toString(const Error error) noexcept
{
return ERROR_NAMES[static_cast<uint32_t>(error)];
return ERROR_NAMES[static_cast<typename std::underlying_type<Error>::type>(error)];
}

std::ostream& operator<<(std::ostream& stream, Error value) noexcept
void ErrorHandler::defaultHandler(const uint32_t error IOX_MAYBE_UNUSED,
const char* errorName,
const ErrorLevel level) noexcept
{
stream << toString(value);
return stream;
std::stringstream ss;
ss << "ICEORYX error! " << errorName;

reactOnErrorLevel(level, ss.str().c_str());
}

void ErrorHandler::reactOnErrorLevel(const ErrorLevel level, const char* errorText) noexcept
{
static auto& logger = createLogger("", "", log::LogManager::GetLogManager().DefaultLogLevel());
switch (level)
{
case ErrorLevel::FATAL:
logger.LogError() << errorText;
assert(false);
std::terminate();
break;
case ErrorLevel::SEVERE:
logger.LogWarn() << errorText;
assert(false);
break;
case ErrorLevel::MODERATE:
logger.LogWarn() << errorText;
break;
}
}

} // namespace iox
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ void ExpectDoesNotCallTerminateWhenObjectIsValid(const ExpectCall& callExpect)
bool wasErrorHandlerCalled = false;
SutType sut = FactoryType::createValidObject();
{
auto handle = iox::ErrorHandler<iox::Error>::setTemporaryErrorHandler(
[&](auto, auto, auto) { wasErrorHandlerCalled = true; });
auto handle = iox::ErrorHandler::setTemporaryErrorHandler<iox::Error>(
[&](auto, auto) { wasErrorHandlerCalled = true; });
callExpect(sut);
}

Expand Down Expand Up @@ -75,8 +75,8 @@ void ExpectDoesCallTerminateWhenObjectIsInvalid(const ExpectCall& callExpect)
bool wasErrorHandlerCalled = true;
SutType sut = FactoryType::createInvalidObject();
{
auto handle = iox::ErrorHandler<iox::Error>::setTemporaryErrorHandler(
[&](auto, auto, auto) { wasErrorHandlerCalled = true; });
auto handle = iox::ErrorHandler::setTemporaryErrorHandler<iox::Error>(
[&](auto, auto) { wasErrorHandlerCalled = true; });
callExpect(sut);
}

Expand Down
8 changes: 4 additions & 4 deletions iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,8 @@ TEST_F(Mepoo_IntegrationTest, WrongSampleSize)
constexpr uint32_t SAMPLE_SIZE = 2048U;
constexpr uint32_t REPETITION = 1U;
iox::cxx::optional<iox::Error> receivedError;
auto errorHandlerGuard = iox::ErrorHandler<iox::Error>::setTemporaryErrorHandler(
[&receivedError](const iox::Error error, const std::function<void()>, const iox::ErrorLevel) {
auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler<iox::Error>(
[&receivedError](const iox::Error error, const iox::ErrorLevel) {
receivedError.emplace(error);
});

Expand All @@ -436,8 +436,8 @@ TEST_F(Mepoo_IntegrationTest, SampleOverflow)
constexpr uint32_t SAMPLE_SIZE_1 = 200U;
constexpr uint32_t REPETITION = 1U;
iox::cxx::optional<iox::Error> receivedError;
auto errorHandlerGuard = iox::ErrorHandler<iox::Error>::setTemporaryErrorHandler(
[&receivedError](const iox::Error error, const std::function<void()>, const iox::ErrorLevel) {
auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler<iox::Error>(
[&receivedError](const iox::Error error, const iox::ErrorLevel) {
receivedError.emplace(error);
});

Expand Down
Loading

0 comments on commit 467b9f7

Please sign in to comment.