diff --git a/include/libhal/callback_recorder.hpp b/include/libhal/callback_recorder.hpp new file mode 100644 index 000000000..e20f37865 --- /dev/null +++ b/include/libhal/callback_recorder.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include + +namespace hal::mock { +/** + * @addtogroup mock + * @{ + */ + +/** + * @brief General class which will be used to allow for signature to be used and + * then split by the below class. + * + * @tparam signature function signature to be split up in the callback_recorder + * specialization + */ +template +class callback_recorder; + +/** + * @brief Specialization of callback_recorder with the return type and arguments + * split up. + * + * @tparam return_t function's return type + * @tparam args_t function's set of arguments + */ +template +class callback_recorder +{ +public: + using callback_function = std::function; + + /** + * @brief Construct a new callback_recorder without providing a callback + * + * @tparam U - return type of the callback function + * @tparam V - type of the callback function + */ + template>*> + callback_recorder() noexcept + { + m_callback = [this]([[maybe_unused]] args_t... p_args) { m_call_count++; }; + } + + /** + * @brief Construct a new callback_recorder that returns a default value + * + * @tparam U - return type of the callback function + * @tparam V - type of the callback function + * @param p_default - default value to return from callback function + * @param p_callback - when the static callback function is called, it will + * call this callback + */ + template>*> + callback_recorder(U p_default = U{}, V p_callback = 0) noexcept + { + m_callback = + [this, p_default, p_callback]([[maybe_unused]] args_t... p_args) -> U { + m_call_count++; + return p_default; + }; + } + + /** + * @brief Construct a new static callable object + * + * @param p_callback - when the static callback function is called, it will + * call this callback + */ + explicit callback_recorder(callback_function p_callback) noexcept + { + m_callback = [this, + p_callback]([[maybe_unused]] args_t... p_args) -> return_t { + m_call_count++; + return p_callback(p_args...); + }; + } + + /** + * @brief Get the static function's address + * + * @return auto* - static function's address + */ + [[nodiscard]] auto& callback() noexcept + { + return m_callback; + } + + /** + * @brief Returns true if the callback was ever called + * + * @return true - was called + * @return false - has not been called + */ + [[nodiscard]] bool was_called() noexcept + { + return m_call_count > 0; + } + + /** + * @brief Returns true if the callback was called exactly once + * + * @return true - called exactly once + * @return false - called more or less than once + */ + [[nodiscard]] bool was_called_once() noexcept + { + return m_call_count == 1; + } + + /** + * @brief Returns true if the callback was called exactly n times + * + * @return true - called exactly n times + * @return false - called more or less than n times + */ + [[nodiscard]] bool was_called_n_times(std::uint32_t p_times_called) noexcept + { + return m_call_count == p_times_called; + } + + /** + * @brief Get the number of calls to the handler + * + * @return auto - number of calls to the callback + */ + [[nodiscard]] auto call_count() noexcept + { + return m_call_count; + } + + /** + * @brief Clear call count + * + */ + void clear_call_count() noexcept + { + m_call_count = 0; + } + +private: + std::function m_callback; + std::uint32_t m_call_count = 0; +}; +/** @} */ +} // namespace hal::mock diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 41118d3cd..38a67e95c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -77,7 +77,8 @@ add_executable(${PROJECT_NAME} math.test.cpp error.test.cpp map.test.cpp - units.test.cpp) + units.test.cpp + callback_recorder.test.cpp) target_include_directories(${PROJECT_NAME} PUBLIC . ../include) target_compile_options(${PROJECT_NAME} PRIVATE -Werror -Wall -Wextra diff --git a/tests/callback_recorder.test.cpp b/tests/callback_recorder.test.cpp new file mode 100644 index 000000000..44e860a52 --- /dev/null +++ b/tests/callback_recorder.test.cpp @@ -0,0 +1,94 @@ +#include + +#include + +namespace hal { +boost::ut::suite callback_recorder_test = []() { + using namespace boost::ut; + + "callback_recorder::ctor(callback_function)"_test = []() { + // Setup + int counter = 0; + const std::function expected_callback = [&counter](void) { + counter++; + }; + hal::mock::callback_recorder recorder(expected_callback); + + // Exercise + Verify + auto recorded_callback = recorder.callback(); + expect(that % 0 == counter); + expect(that % 0 == recorder.call_count()); + expect(that % false == recorder.was_called()); + expect(that % false == recorder.was_called_once()); + + recorded_callback(); + expect(that % 1 == counter); + expect(that % 1 == recorder.call_count()); + expect(that % true == recorder.was_called()); + expect(that % true == recorder.was_called_once()); + expect(that % true == recorder.was_called_n_times(1)); + recorded_callback(); + expect(that % false == recorder.was_called_once()); + expect(that % true == recorder.was_called_n_times(2)); + + recorder.clear_call_count(); + expect(that % 0 == recorder.call_count()); + expect(that % false == recorder.was_called()); + expect(that % false == recorder.was_called_once()); + + recorded_callback(); + expect(that % 3 == counter); + expect(that % 1 == recorder.call_count()); + expect(that % true == recorder.was_called()); + expect(that % true == recorder.was_called_once()); + }; + + "callback_recorder::ctor(void)"_test = []() { + // Setup + hal::mock::callback_recorder recorder; + + // Exercise + Verify + auto recorded_callback = recorder.callback(); + expect(that % 0 == recorder.call_count()); + expect(that % false == recorder.was_called()); + expect(that % false == recorder.was_called_once()); + + recorded_callback(); + expect(that % 1 == recorder.call_count()); + expect(that % true == recorder.was_called()); + expect(that % true == recorder.was_called_once()); + expect(that % true == recorder.was_called_n_times(1)); + recorded_callback(); + expect(that % false == recorder.was_called_once()); + expect(that % true == recorder.was_called_n_times(2)); + + recorder.clear_call_count(); + expect(that % 0 == recorder.call_count()); + expect(that % false == recorder.was_called()); + expect(that % false == recorder.was_called_once()); + }; + + "callback_recorder::ctor(default)"_test = []() { + // Setup + hal::mock::callback_recorder recorder(5); + + // Exercise + Verify + auto recorded_callback = recorder.callback(); + expect(that % 0 == recorder.call_count()); + expect(that % false == recorder.was_called()); + expect(that % false == recorder.was_called_once()); + + auto result = recorded_callback(); + expect(that % 5 == result); + expect(that % 1 == recorder.call_count()); + expect(that % true == recorder.was_called()); + expect(that % true == recorder.was_called_once()); + expect(that % true == recorder.was_called_n_times(1)); + + recorder.clear_call_count(); + expect(that % 0 == recorder.call_count()); + expect(that % false == recorder.was_called()); + expect(that % false == recorder.was_called_once()); + }; +}; +} // namespace hal