diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c8b133..5cee08c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(RUUVI_ESP_WRAPPERS_SRC include/os_timer_sig.h include/os_wrapper_types.h include/time_units.h + include/snprintf_with_esp_err_desc.h include/str_buf.h include/wrap_esp_err_to_name_r.h src/log_dump.c @@ -34,6 +35,7 @@ set(RUUVI_ESP_WRAPPERS_SRC src/os_time.c src/os_timer.c src/os_timer_sig.c + src/snprintf_with_esp_err_desc.c src/str_buf.c src/wrap_esp_err_to_name_r.c ) diff --git a/include/snprintf_with_esp_err_desc.h b/include/snprintf_with_esp_err_desc.h new file mode 100644 index 0000000..527f33e --- /dev/null +++ b/include/snprintf_with_esp_err_desc.h @@ -0,0 +1,34 @@ +/** + * @file snprintf_with_esp_err_desc.h + * @author TheSomeMan + * @date 2023-04-09 + * @copyright Ruuvi Innovations Ltd, license BSD-3-Clause. + */ + +#ifndef RUUVI_SNPRINTF_WITH_ESP_ERR_DESC_H +#define RUUVI_SNPRINTF_WITH_ESP_ERR_DESC_H + +#include +#include "str_buf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +str_buf_t +esp_err_to_name_with_alloc_str_buf(const esp_err_t esp_err_code); + +ATTR_PRINTF(4, 5) +int +snprintf_with_esp_err_desc( + const esp_err_t esp_err_code, + char* const p_buf, + const size_t buf_size, + const char* const p_fmt, + ...); + +#ifdef __cplusplus +} +#endif + +#endif // RUUVI_SNPRINTF_WITH_ESP_ERR_DESC_H diff --git a/src/snprintf_with_esp_err_desc.c b/src/snprintf_with_esp_err_desc.c new file mode 100644 index 0000000..6f47cab --- /dev/null +++ b/src/snprintf_with_esp_err_desc.c @@ -0,0 +1,68 @@ +/** + * @file snprintf_with_esp_err_desc.c + * @author TheSomeMan + * @date 2023-04-09 + * @copyright Ruuvi Innovations Ltd, license BSD-3-Clause. + */ + +#include "snprintf_with_esp_err_desc.h" +#include +#include +#include "os_malloc.h" +#include "str_buf.h" +#include "wrap_esp_err_to_name_r.h" + +#define ERR_DESC_SIZE (120) + +str_buf_t +esp_err_to_name_with_alloc_str_buf(const esp_err_t esp_err_code) +{ + char* p_err_desc_buf = os_malloc(ERR_DESC_SIZE); + if (NULL == p_err_desc_buf) + { + return str_buf_init_null(); + } + str_buf_t str_buf = STR_BUF_INIT(p_err_desc_buf, ERR_DESC_SIZE); + wrap_esp_err_to_name_r(esp_err_code, p_err_desc_buf, ERR_DESC_SIZE); + str_buf.idx = strlen(p_err_desc_buf); + return str_buf; +} + +int +snprintf_with_esp_err_desc( + const esp_err_t esp_err_code, + char* const p_buf, + const size_t buf_size, + const char* const p_fmt, + ...) +{ + int idx = 0; + if (NULL != p_fmt) + { + va_list args; + va_start(args, p_fmt); + idx = vsnprintf(p_buf, buf_size, p_fmt, args); + va_end(args); + } + if (idx < 0) + { + return idx; + } + char* const p_extra_buf = (NULL != p_buf) ? &p_buf[idx] : NULL; + const size_t remain_len = ((size_t)idx < buf_size) ? buf_size - (size_t)idx : 0; + const char* const p_delimiter = (0 != idx) ? ", " : ""; + str_buf_t str_buf_err_desc = esp_err_to_name_with_alloc_str_buf(esp_err_code); + if (NULL != str_buf_err_desc.buf) + { + idx += snprintf(p_extra_buf, remain_len, "%serror %d (%s)", p_delimiter, esp_err_code, str_buf_err_desc.buf); + } + else + { + idx += snprintf(p_extra_buf, remain_len, "%serror %d", p_delimiter, esp_err_code); + } + if (NULL != str_buf_err_desc.buf) + { + str_buf_free_buf(&str_buf_err_desc); + } + return idx; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a439a7f..fe64a96 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,6 +45,7 @@ add_subdirectory(test_os_task) #add_subdirectory(test_os_task_freertos) add_subdirectory(test_os_timer_freertos) add_subdirectory(test_os_timer_sig_freertos) +add_subdirectory(test_snprintf_with_esp_err_desc) add_subdirectory(test_str_buf) add_subdirectory(test_time_units) @@ -113,6 +114,11 @@ add_test(NAME test_os_timer_sig_freertos --gtest_output=xml:$/gtestresults.xml ) +add_test(NAME test_snprintf_with_esp_err_desc + COMMAND ruuvi_esp_wrappers-test-snprintf_with_esp_err_desc + --gtest_output=xml:$/gtestresults.xml +) + add_test(NAME test_str_buf COMMAND ruuvi_esp_wrappers-test-str_buf --gtest_output=xml:$/gtestresults.xml diff --git a/tests/common/include/esp_err.h b/tests/common/include/esp_err.h index f38bad6..cca6bbb 100644 --- a/tests/common/include/esp_err.h +++ b/tests/common/include/esp_err.h @@ -61,6 +61,26 @@ typedef int32_t esp_err_t; const char* esp_err_to_name(esp_err_t code); +/** + * @brief Returns string for esp_err_t and system error codes + * + * This function finds the error code in a pre-generated lookup-table of + * esp_err_t errors and returns its string representation. If the error code + * is not found then it is attempted to be found among system errors. + * + * The function is generated by the Python script + * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t + * error is modified, created or removed from the IDF project. + * + * @param code esp_err_t error code + * @param[out] buf buffer where the error message should be written + * @param buflen Size of buffer buf. At most buflen bytes are written into the buf buffer (including the terminating + * null byte). + * @return buf containing the string error message + */ +const char* +esp_err_to_name_r(esp_err_t code, char* buf, size_t buflen); + static inline void strlcpy(char* dst, const char* src, const size_t len) { diff --git a/tests/test_snprintf_with_esp_err_desc/CMakeLists.txt b/tests/test_snprintf_with_esp_err_desc/CMakeLists.txt new file mode 100644 index 0000000..635abd5 --- /dev/null +++ b/tests/test_snprintf_with_esp_err_desc/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.7) + +project(ruuvi_esp_wrappers-test-snprintf_with_esp_err_desc) +set(ProjectId ruuvi_esp_wrappers-test-snprintf_with_esp_err_desc) + +add_executable(${ProjectId} + test_snprintf_with_esp_err_desc.cpp + ../../src/snprintf_with_esp_err_desc.c + ../../include/snprintf_with_esp_err_desc.h + ../../src/str_buf.c + ../../include/str_buf.h +) + +set_target_properties(${ProjectId} PROPERTIES + C_STANDARD 11 + CXX_STANDARD 14 +) + +target_include_directories(${ProjectId} PUBLIC + ${gtest_SOURCE_DIR}/include + ${gtest_SOURCE_DIR} + ../../include + include + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(${ProjectId} PUBLIC + RUUVI_TESTS_SNPRINTF_WITH_ESP_ERR_DESC=1 +) + +target_compile_options(${ProjectId} PUBLIC + -g3 + -ggdb + -fprofile-arcs + -ftest-coverage + --coverage +) + +# CMake has a target_link_options starting from version 3.13 +#target_link_options(${ProjectId} PUBLIC +# --coverage +#) + +target_link_libraries(${ProjectId} + gtest + gtest_main + gcov + ruuvi_esp_wrappers-common_test_funcs + --coverage +) diff --git a/tests/test_snprintf_with_esp_err_desc/test_snprintf_with_esp_err_desc.cpp b/tests/test_snprintf_with_esp_err_desc/test_snprintf_with_esp_err_desc.cpp new file mode 100644 index 0000000..6b58692 --- /dev/null +++ b/tests/test_snprintf_with_esp_err_desc/test_snprintf_with_esp_err_desc.cpp @@ -0,0 +1,246 @@ +/** + * @file test_snprintf_with_esp_err_desc.cpp + * @author TheSomeMan + * @date 2023-04-09 + * @copyright Ruuvi Innovations Ltd, license BSD-3-Clause. + */ + +#include "gtest/gtest.h" +#include "snprintf_with_esp_err_desc.h" +#include + +using namespace std; + +/*** Google-test class implementation *********************************************************************************/ + +class TestClass; +static TestClass* g_pTestClass; + +class MemAllocTrace +{ + vector allocated_mem; + + std::vector::iterator + find(void* p_mem) + { + for (auto iter = this->allocated_mem.begin(); iter != this->allocated_mem.end(); ++iter) + { + if (*iter == p_mem) + { + return iter; + } + } + return this->allocated_mem.end(); + } + +public: + void + add(void* p_mem) + { + auto iter = find(p_mem); + assert(iter == this->allocated_mem.end()); // p_mem was found in the list of allocated memory blocks + this->allocated_mem.push_back(p_mem); + } + void + remove(void* p_mem) + { + auto iter = find(p_mem); + assert(iter != this->allocated_mem.end()); // p_mem was not found in the list of allocated memory blocks + this->allocated_mem.erase(iter); + } + bool + is_empty() + { + return this->allocated_mem.empty(); + } +}; + +class TestClass : public ::testing::Test +{ +private: +protected: + void + SetUp() override + { + g_pTestClass = this; + m_flag_malloc_fail = false; + m_err_desc_extra = nullptr; + } + + void + TearDown() override + { + g_pTestClass = nullptr; + } + +public: + TestClass(); + + ~TestClass() override; + + bool m_flag_malloc_fail; + const char* m_err_desc_extra; + MemAllocTrace m_mem_alloc_trace; +}; + +TestClass::TestClass() + : Test() + , m_flag_malloc_fail(false) + , m_err_desc_extra(nullptr) + , m_mem_alloc_trace() +{ +} + +TestClass::~TestClass() = default; + +extern "C" { + +void* +os_malloc(size_t size) +{ + if (g_pTestClass->m_flag_malloc_fail) + { + return nullptr; + } + void* p_buf = malloc(size); + g_pTestClass->m_mem_alloc_trace.add(p_buf); + return p_buf; +} + +void +os_free_internal(void* p_buf) +{ + g_pTestClass->m_mem_alloc_trace.remove(p_buf); + free(p_buf); +} + +const char* +wrap_esp_err_to_name_r(const esp_err_t code, char* const p_buf, const size_t buf_len) +{ + (void)snprintf( + p_buf, + buf_len, + "Error description for code %d%s", + code, + (nullptr != g_pTestClass->m_err_desc_extra) ? g_pTestClass->m_err_desc_extra : ""); + return p_buf; +} + +} // extern "C" + +/*** Unit-Tests *******************************************************************************************************/ + +TEST_F(TestClass, test_esp_err_to_name_with_alloc_str_buf__ok) // NOLINT +{ + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + str_buf_t str_buf = esp_err_to_name_with_alloc_str_buf(esp_err_code); + ASSERT_NE(nullptr, str_buf.buf); + ASSERT_EQ(string("Error description for code 258"), str_buf.buf); + ASSERT_EQ(30, str_buf.idx); + str_buf_free_buf(&str_buf); +} + +TEST_F(TestClass, test_esp_err_to_name_with_alloc_str_buf__fail) // NOLINT +{ + this->m_flag_malloc_fail = true; + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + const str_buf_t str_buf = esp_err_to_name_with_alloc_str_buf(esp_err_code); + ASSERT_EQ(nullptr, str_buf.buf); + ASSERT_EQ(0, str_buf.idx); +} + +TEST_F(TestClass, test_esp_err_to_name_with_alloc_str_buf__err_desc_too_big) // NOLINT +{ + this->m_err_desc_extra + = " 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567A"; + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + str_buf_t str_buf = esp_err_to_name_with_alloc_str_buf(esp_err_code); + ASSERT_NE(nullptr, str_buf.buf); + ASSERT_EQ( + string("Error description for code 258 " + "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"), + str_buf.buf); + ASSERT_EQ(119, str_buf.idx); + str_buf_free_buf(&str_buf); +} + +TEST_F(TestClass, test_snprintf_with_esp_err_desc__ok) // NOLINT +{ + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + char buf[128]; + + const int res = snprintf_with_esp_err_desc(esp_err_code, buf, sizeof(buf), "Message %d", 123); + ASSERT_EQ(55, res); + ASSERT_EQ(string("Message 123, error 258 (Error description for code 258)"), buf); + ASSERT_TRUE(this->m_mem_alloc_trace.is_empty()); +} + +TEST_F(TestClass, test_snprintf_with_esp_err_desc__calc_size_without_printing) // NOLINT +{ + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + + const int res = snprintf_with_esp_err_desc(esp_err_code, nullptr, 0, "Message %d", 123); + ASSERT_EQ(55, res); + ASSERT_TRUE(this->m_mem_alloc_trace.is_empty()); +} + +TEST_F(TestClass, test_snprintf_with_esp_err_desc__ok_with_null_fmt) // NOLINT +{ + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + char buf[128]; + + const int res = snprintf_with_esp_err_desc(esp_err_code, buf, sizeof(buf), nullptr); + ASSERT_EQ(42, res); + ASSERT_EQ(string("error 258 (Error description for code 258)"), buf); + ASSERT_TRUE(this->m_mem_alloc_trace.is_empty()); +} + +TEST_F(TestClass, test_snprintf_with_esp_err_desc__ok_with_empty_message) // NOLINT +{ + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + char buf[128]; + + const int res = snprintf_with_esp_err_desc(esp_err_code, buf, sizeof(buf), "%s", ""); + ASSERT_EQ(42, res); + ASSERT_EQ(string("error 258 (Error description for code 258)"), buf); + ASSERT_TRUE(this->m_mem_alloc_trace.is_empty()); +} + +TEST_F(TestClass, test_snprintf_with_esp_err_desc__malloc_failed) // NOLINT +{ + this->m_flag_malloc_fail = true; + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + char buf[128]; + + const int res = snprintf_with_esp_err_desc(esp_err_code, buf, sizeof(buf), "Message %d", 123); + ASSERT_EQ(22, res); + ASSERT_EQ(string("Message 123, error 258"), buf); + ASSERT_TRUE(this->m_mem_alloc_trace.is_empty()); +} + +TEST_F(TestClass, test_snprintf_with_esp_err_desc__insufficient_buffer) // NOLINT +{ + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + char buf[55]; + + const int res = snprintf_with_esp_err_desc(esp_err_code, buf, sizeof(buf), "Message %d", 123); + ASSERT_EQ(55, res); + ASSERT_EQ(string("Message 123, error 258 (Error description for code 258"), buf); + ASSERT_TRUE(this->m_mem_alloc_trace.is_empty()); +} + +TEST_F(TestClass, test_snprintf_with_esp_err_desc__too_big_err_desc) // NOLINT +{ + this->m_err_desc_extra + = " 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567A"; + const esp_err_t esp_err_code = ESP_ERR_INVALID_ARG; + char buf[256]; + + const int res = snprintf_with_esp_err_desc(esp_err_code, buf, sizeof(buf), "Message %d", 123); + ASSERT_EQ(144, res); + ASSERT_EQ( + string("Message 123, error 258 (Error description for code 258 " + "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567)"), + buf); + ASSERT_TRUE(this->m_mem_alloc_trace.is_empty()); +}