diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce6114ad..0eb9d867 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,12 @@ jobs: ./examples/system/dif_test/dif_test ./examples/system/dif_test/test_direct_access + - name: Test DMOD_LOG_STEP_* macros via log_step module + working-directory: build + run: | + ./examples/system/dmod_loader/dmod_loader ./dmf/log_step.dmf + echo "log_step module loaded and executed successfully" + - name: Run manifest library tests working-directory: build run: ./tests/tests_dmod_manifest diff --git a/examples/module/CMakeLists.txt b/examples/module/CMakeLists.txt index a76d3a60..c880568d 100644 --- a/examples/module/CMakeLists.txt +++ b/examples/module/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(library) -add_subdirectory(application) \ No newline at end of file +add_subdirectory(application) +add_subdirectory(log_step) \ No newline at end of file diff --git a/examples/module/Makefile b/examples/module/Makefile index 6b42ee4d..bf813676 100644 --- a/examples/module/Makefile +++ b/examples/module/Makefile @@ -7,7 +7,8 @@ DMOD_DIR=../.. # Subdirectories # ----------------------------------------------------------------------------- SUBDIRS := library \ - application + application \ + log_step # ----------------------------------------------------------------------------- # Initialization of paths diff --git a/examples/module/log_step/CMakeLists.txt b/examples/module/log_step/CMakeLists.txt new file mode 100644 index 00000000..4e5cc065 --- /dev/null +++ b/examples/module/log_step/CMakeLists.txt @@ -0,0 +1,9 @@ +set(DMOD_MODULE_NAME log_step) +set(DMOD_MODULE_VERSION "0.1") +set(DMOD_AUTHOR_NAME "Patryk Kubiak") +set(DMOD_STACK_SIZE 1024) +set(DMOD_PRIORITY 0) + +dmod_add_executable(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + main.c +) diff --git a/examples/module/log_step/Makefile b/examples/module/log_step/Makefile new file mode 100644 index 00000000..4712ff37 --- /dev/null +++ b/examples/module/log_step/Makefile @@ -0,0 +1,28 @@ +# ############################################################################# +# +# Example application module that exercises DMOD_LOG_STEP_* macros. +# +# ############################################################################# +DMOD_DIR=../../.. + +# ----------------------------------------------------------------------------- +# Paths initialization +# ----------------------------------------------------------------------------- +include $(DMOD_DIR)/paths.mk + +# ----------------------------------------------------------------------------- +# Module configuration +# ----------------------------------------------------------------------------- +DMOD_MODULE_NAME=log_step +DMOD_MODULE_VERSION=0.1 +DMOD_AUTHOR_NAME=Patryk Kubiak +DMOD_CSOURCES=main.c +DMOD_CXXSOURCES= +DMOD_INC_DIRS= +DMOD_LIBS= +DMOD_DEFINITIONS= + +# ----------------------------------------------------------------------------- +# Include the dmod app makefile +# ----------------------------------------------------------------------------- +include $(DMOD_DMF_APP_FILE_PATH) diff --git a/examples/module/log_step/main.c b/examples/module/log_step/main.c new file mode 100644 index 00000000..f08a5918 --- /dev/null +++ b/examples/module/log_step/main.c @@ -0,0 +1,37 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmod.h" + +/** + * @brief Example module demonstrating DMOD_LOG_STEP_* macros. + * + * This module exercises: + * - DMOD_LOG_STEP_BEGIN : show an empty progress bar at the start of an operation + * - DMOD_LOG_STEP_PROGRESS : update the progress bar with a percentage + * - DMOD_LOG_STEP : conclude with [ OK ] or [ FAIL ] depending on result + * + * The DMOD_LOG_STEP_* macros concatenate their argument with a colour-escape + * prefix at compile time, so string literals must be used. + */ +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + + /* --------------------------------------------------------------- + * Simulate a successful multi-step operation + * --------------------------------------------------------------- */ + DMOD_LOG_STEP_BEGIN("Initializing subsystem\n"); + DMOD_LOG_STEP_PROGRESS(25, "Initializing subsystem\n"); + DMOD_LOG_STEP_PROGRESS(50, "Initializing subsystem\n"); + DMOD_LOG_STEP_PROGRESS(75, "Initializing subsystem\n"); + DMOD_LOG_STEP(0, "Initializing subsystem\n"); + + /* --------------------------------------------------------------- + * Simulate a failing step (non-zero result → [ FAIL ]) + * --------------------------------------------------------------- */ + DMOD_LOG_STEP_BEGIN("Running diagnostics\n"); + DMOD_LOG_STEP_PROGRESS(50, "Running diagnostics\n"); + DMOD_LOG_STEP(1, "Running diagnostics\n"); + + return 0; +} diff --git a/tests/system/if/CMakeLists.txt b/tests/system/if/CMakeLists.txt index 6054bf92..ce863864 100644 --- a/tests/system/if/CMakeLists.txt +++ b/tests/system/if/CMakeLists.txt @@ -12,5 +12,6 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_uptime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_module_log_level.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_log_step.cpp PARENT_SCOPE ) diff --git a/tests/system/if/tests_dmod_log_step.cpp b/tests/system/if/tests_dmod_log_step.cpp new file mode 100644 index 00000000..9ad993cc --- /dev/null +++ b/tests/system/if/tests_dmod_log_step.cpp @@ -0,0 +1,161 @@ +#include +#include "dmod.h" + +// =============================================================== +// Tests for DMOD_LOG_STEP_* macros and Dmod_GetStepBar helper +// =============================================================== + +class DmodLogStepTest : public ::testing::Test +{ +}; + +/** + * @brief DMOD_LOG_STEP_BEGIN does not crash + */ +TEST_F(DmodLogStepTest, LogStepBegin_NoCrash) +{ + DMOD_LOG_STEP_BEGIN("starting operation\n"); +} + +/** + * @brief DMOD_LOG_STEP_PROGRESS does not crash at 0% + */ +TEST_F(DmodLogStepTest, LogStepProgress_Zero_NoCrash) +{ + DMOD_LOG_STEP_PROGRESS(0, "operation in progress\n"); +} + +/** + * @brief DMOD_LOG_STEP_PROGRESS does not crash at 50% + */ +TEST_F(DmodLogStepTest, LogStepProgress_Mid_NoCrash) +{ + DMOD_LOG_STEP_PROGRESS(50, "operation in progress\n"); +} + +/** + * @brief DMOD_LOG_STEP_PROGRESS does not crash at 100% + */ +TEST_F(DmodLogStepTest, LogStepProgress_Full_NoCrash) +{ + DMOD_LOG_STEP_PROGRESS(100, "operation in progress\n"); +} + +/** + * @brief DMOD_LOG_STEP with result 0 (success) does not crash + */ +TEST_F(DmodLogStepTest, LogStep_Success_NoCrash) +{ + DMOD_LOG_STEP(0, "operation result\n"); +} + +/** + * @brief DMOD_LOG_STEP with non-zero result (failure) does not crash + */ +TEST_F(DmodLogStepTest, LogStep_Failure_NoCrash) +{ + DMOD_LOG_STEP(1, "operation result\n"); +} + +/** + * @brief Full sequence (begin, progress, step) does not crash + */ +TEST_F(DmodLogStepTest, FullSequence_NoCrash) +{ + DMOD_LOG_STEP_BEGIN("loading module\n"); + DMOD_LOG_STEP_PROGRESS(25, "loading module\n"); + DMOD_LOG_STEP_PROGRESS(75, "loading module\n"); + DMOD_LOG_STEP(0, "loading module\n"); +} + +/** + * @brief Dmod_GetStepBar returns non-NULL for 0% + */ +TEST_F(DmodLogStepTest, GetStepBar_Zero_NotNull) +{ + EXPECT_NE(Dmod_GetStepBar(0), nullptr); +} + +/** + * @brief Dmod_GetStepBar returns non-NULL for 50% + */ +TEST_F(DmodLogStepTest, GetStepBar_Mid_NotNull) +{ + EXPECT_NE(Dmod_GetStepBar(50), nullptr); +} + +/** + * @brief Dmod_GetStepBar returns non-NULL for 100% + */ +TEST_F(DmodLogStepTest, GetStepBar_Full_NotNull) +{ + EXPECT_NE(Dmod_GetStepBar(100), nullptr); +} + +/** + * @brief Dmod_GetStepBar clamps negative percentages without crashing + */ +TEST_F(DmodLogStepTest, GetStepBar_Negative_NoCrash) +{ + const char* bar = Dmod_GetStepBar(-10); + EXPECT_NE(bar, nullptr); + /* negative should clamp to the same bar as 0% */ + EXPECT_STREQ(bar, Dmod_GetStepBar(0)); +} + +/** + * @brief Dmod_GetStepBar clamps percentages above 100 without crashing + */ +TEST_F(DmodLogStepTest, GetStepBar_Over100_NoCrash) +{ + const char* bar = Dmod_GetStepBar(110); + EXPECT_NE(bar, nullptr); + /* values > 100 should clamp to the same bar as 100% */ + EXPECT_STREQ(bar, Dmod_GetStepBar(100)); +} + +/* Count the number of filled-block UTF-8 sequences (U+2588, encoded as + * \xe2\x96\x88) in a bar string. Each filled segment adds one such sequence. + */ +static int CountFilledBlocks(const char* bar) +{ + int count = 0; + while (bar && *bar) + { + if ((unsigned char)bar[0] == 0xe2 && + (unsigned char)bar[1] == 0x96 && + (unsigned char)bar[2] == 0x88) + { + count++; + bar += 3; + } + else + { + bar++; + } + } + return count; +} + +/** + * @brief Dmod_GetStepBar returns bars with non-decreasing fill as percentage rises + * + * The seven discrete states (index 0..6, one per ~17%) should never regress: + * each representative percentage must have at least as many filled blocks as + * the previous one. + */ +TEST_F(DmodLogStepTest, GetStepBar_Monotone) +{ + int prevFill = CountFilledBlocks(Dmod_GetStepBar(0)); + + /* Walk through representative percentages and ensure fill never decreases */ + int percentages[] = { 17, 34, 50, 67, 84, 100 }; + for (int i = 0; i < (int)(sizeof(percentages) / sizeof(percentages[0])); i++) + { + const char* cur = Dmod_GetStepBar(percentages[i]); + ASSERT_NE(cur, nullptr); + int curFill = CountFilledBlocks(cur); + EXPECT_GE(curFill, prevFill); + prevFill = curFill; + } +}