diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 332e177..d04030d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,6 +137,7 @@ jobs: # Add $version-available directives for the modules echo "\$version-available dm_sw_ring $VERSIONS" >> versions.dmm + echo "\$version-available test_dm_sw_ring $VERSIONS" >> versions.dmm echo "Generated versions.dmm:" cat versions.dmm diff --git a/CMakeLists.txt b/CMakeLists.txt index 231de13..8e26aa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,5 +87,4 @@ target_include_directories(${DMOD_MODULE_NAME} PRIVATE # ====================================================================== # test_dm_sw_ring Application # ====================================================================== -# Add test_dm_sw_ring application subdirectory -# add_subdirectory(apps/test_dm_sw_ring) +add_subdirectory(tests) diff --git a/manifest.dmm b/manifest.dmm index 4009c3f..fb32aa9 100644 --- a/manifest.dmm +++ b/manifest.dmm @@ -5,4 +5,4 @@ $include https://github.com/choco-technologies/dm_sw_ring/releases/download/vlat dm_sw_ring https://github.com/choco-technologies/dm_sw_ring/releases/download/v/dm_sw_ring-v-.zip # Test application -# test_dm_sw_ring https://github.com/choco-technologies/dm_sw_ring/releases/download/v/dm_sw_ring-v-.zip +test_dm_sw_ring https://github.com/choco-technologies/dm_sw_ring/releases/download/v/dm_sw_ring-v-.zip diff --git a/src/dm_sw_ring.c b/src/dm_sw_ring.c index 5a8ed9a..9416a8d 100644 --- a/src/dm_sw_ring.c +++ b/src/dm_sw_ring.c @@ -1,3 +1,4 @@ +#define DMOD_ENABLE_REGISTRATION ON #include "dmod.h" #include "dm_sw_ring.h" @@ -16,6 +17,7 @@ struct dm_sw_ring dm_sw_ring_capacity_t capacity; // Capacity of the ring buffer (number of elements) dm_sw_ring_capacity_t head; // Index of the head (next element to read) dm_sw_ring_capacity_t tail; // Index of the tail (next element to write) + dm_sw_ring_capacity_t count; // Number of elements currently stored in the ring buffer uint8_t* buffer; // Pointer to the buffer memory dm_sw_ring_flags_t flags; // Flags for ring buffer behavior void* mutex; // Mutex for synchronization (if enabled) @@ -59,21 +61,17 @@ void dmod_preinit(void) // Nothing to do } -/** - * @brief Module initialization - */ + int dmod_init(const Dmod_Config_t *Config) { - (void)Config; + DMOD_LOG_INFO("DM Software Ring module initialized\n"); return 0; } -/** - * @brief Module deinitialization - */ -void dmod_deinit(void) +int dmod_deinit(void) { - // Nothing to do + DMOD_LOG_INFO("DM Software Ring module deinitialized\n"); + return 0; } // ============================================================================ @@ -109,6 +107,7 @@ dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_t, _create, (dm_sw_ring_capacity ring->capacity = capacity; ring->head = 0; ring->tail = 0; + ring->count = 0; ring->flags = flags; ring->mutex = NULL; ring->space_semaphore = NULL; @@ -156,16 +155,21 @@ dmod_dm_sw_ring_api_declaration(1.0, void, _destroy, (dm_sw_ring_t ring)) if (lock_ring(ring)) { ring->magic = 0; + void* mutex = ring->mutex; + ring->mutex = NULL; - if (ring->mutex != NULL) + if (mutex != NULL) + { + Dmod_Mutex_Unlock(mutex); + Dmod_Mutex_Delete(mutex); + } + else { - Dmod_Mutex_Delete(ring->mutex); + Dmod_ExitCritical(); } Dmod_Free(ring->buffer); Dmod_Free(ring); - - // No need to unlock since the instance is being destroyed } } @@ -246,14 +250,7 @@ dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _size, (dm_sw_ring_t dm_sw_ring_capacity_t size = 0; if(lock_ring(ring)) { - if (ring->tail >= ring->head) - { - size = ring->tail - ring->head; - } - else - { - size = ring->capacity - (ring->head - ring->tail); - } + size = ring->count; unlock_ring(ring); } return size; @@ -315,6 +312,7 @@ dmod_dm_sw_ring_api_declaration(1.0, int32_t, _clear, (dm_sw_ring_t ring)) { ring->head = 0; ring->tail = 0; + ring->count = 0; result = 0; // Success unlock_ring(ring); } @@ -436,14 +434,7 @@ static bool validate_ring(dm_sw_ring_t ring) */ static dm_sw_ring_capacity_t available_space(dm_sw_ring_t ring) { - if (ring->tail >= ring->head) - { - return ring->capacity - (ring->tail - ring->head); - } - else - { - return ring->head - ring->tail; - } + return ring->capacity - ring->count; } /** @@ -453,14 +444,7 @@ static dm_sw_ring_capacity_t available_space(dm_sw_ring_t ring) */ static dm_sw_ring_capacity_t available_data(dm_sw_ring_t ring) { - if (ring->tail >= ring->head) - { - return ring->tail - ring->head; - } - else - { - return ring->capacity - (ring->head - ring->tail); - } + return ring->count; } /** @@ -470,7 +454,7 @@ static dm_sw_ring_capacity_t available_data(dm_sw_ring_t ring) */ static bool is_full(dm_sw_ring_t ring) { - return available_space(ring) == 0; + return ring->count == ring->capacity; } /** @@ -480,7 +464,7 @@ static bool is_full(dm_sw_ring_t ring) */ static bool is_empty(dm_sw_ring_t ring) { - return ring->head == ring->tail; + return ring->count == 0; } /** @@ -492,6 +476,7 @@ static void put_byte(dm_sw_ring_t ring, uint8_t data) { ring->buffer[ring->tail] = data; ring->tail = (ring->tail + 1) % ring->capacity; + ring->count++; } /** @@ -503,6 +488,7 @@ static uint8_t get_byte(dm_sw_ring_t ring) { uint8_t data = ring->buffer[ring->head]; ring->head = (ring->head + 1) % ring->capacity; + ring->count--; return data; } @@ -523,6 +509,7 @@ static void discard(dm_sw_ring_t ring, dm_sw_ring_capacity_t length) wait_for_data(ring, length); ring->head = (ring->head + length) % ring->capacity; + ring->count -= length; } @@ -634,13 +621,10 @@ static dm_sw_ring_capacity_t peek_data(dm_sw_ring_t ring, uint8_t* buffer, dm_sw { dm_sw_ring_capacity_t peeked = 0; dm_sw_ring_capacity_t index = ring->head; + dm_sw_ring_capacity_t available = ring->count; - for (peeked = 0; peeked < length; peeked++) + for (peeked = 0; peeked < length && peeked < available; peeked++) { - if (index == ring->tail) - { - break; // Buffer is empty - } buffer[peeked] = ring->buffer[index]; index = (index + 1) % ring->capacity; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..7984a89 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +set(DMOD_MODULE_NAME test_dm_sw_ring) +set(DMOD_MODULE_VERSION ${PROJECT_VERSION}) +set(DMOD_AUTHOR_NAME "Patryk Kubiak") +set(DMOD_STACK_SIZE 1024) +set(DMOD_PRIORITY 0) + +dmod_add_executable(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + test_dm_sw_ring.c +) + +target_include_directories(${DMOD_MODULE_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) + +target_link_libraries(${DMOD_MODULE_NAME} dm_sw_ring_if) diff --git a/tests/test_dm_sw_ring.c b/tests/test_dm_sw_ring.c new file mode 100644 index 0000000..9458cd6 --- /dev/null +++ b/tests/test_dm_sw_ring.c @@ -0,0 +1,257 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmod.h" +#include "dm_sw_ring.h" +#include +#include +#include + +static int g_failures = 0; + +#define EXPECT_TRUE(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + DMOD_LOG_ERROR("Expectation failed: %s at %s:%d\n", #expr, __FILE__, __LINE__); \ + g_failures++; \ + } \ + } while (0) + +#define EXPECT_EQ_U32(actual, expected) \ + do \ + { \ + uint32_t _actual = (uint32_t)(actual); \ + uint32_t _expected = (uint32_t)(expected); \ + if (_actual != _expected) \ + { \ + DMOD_LOG_ERROR("Expectation failed: %s == %s (actual=%u expected=%u) at %s:%d\n", \ + #actual, #expected, _actual, _expected, __FILE__, __LINE__); \ + g_failures++; \ + } \ + } while (0) +#define EXPECT_EQ_I32(actual, expected) \ + do \ + { \ + int32_t _actual = (int32_t)(actual); \ + int32_t _expected = (int32_t)(expected); \ + if (_actual != _expected) \ + { \ + DMOD_LOG_ERROR("Expectation failed: %s == %s (actual=%d expected=%d) at %s:%d\n", \ + #actual, #expected, _actual, _expected, __FILE__, __LINE__); \ + g_failures++; \ + } \ + } while (0) +#define EXPECT_EQ_BOOL(actual, expected) \ + do \ + { \ + bool _actual = ((actual) ? true : false); \ + bool _expected = ((expected) ? true : false); \ + if (_actual != _expected) \ + { \ + DMOD_LOG_ERROR("Expectation failed: %s == %s (actual=%d expected=%d) at %s:%d\n", \ + #actual, #expected, (int)_actual, (int)_expected, __FILE__, __LINE__); \ + g_failures++; \ + } \ + } while (0) +#define EXPECT_EQ_U8(actual, expected) \ + do \ + { \ + uint8_t _actual = (uint8_t)(actual); \ + uint8_t _expected = (uint8_t)(expected); \ + if (_actual != _expected) \ + { \ + DMOD_LOG_ERROR("Expectation failed: %s == %s (actual=%u expected=%u) at %s:%d\n", \ + #actual, #expected, _actual, _expected, __FILE__, __LINE__); \ + g_failures++; \ + } \ + } while (0) + +typedef struct test_context +{ + dm_sw_ring_t ring; + dm_sw_ring_flags_t ring_flags; + uint8_t io_buffer[8]; +} test_context_t; + +typedef void (*test_setup_fn)(test_context_t* context); +typedef void (*test_step_fn)(test_context_t* context); +typedef void (*test_teardown_fn)(test_context_t* context); + +static void setup_ring_capacity_4(test_context_t* context) +{ + context->ring = dm_sw_ring_create(4, context->ring_flags); + EXPECT_TRUE(context->ring != NULL); +} + +static void teardown_ring(test_context_t* context) +{ + if (context->ring != NULL) + { + dm_sw_ring_destroy(context->ring); + context->ring = NULL; + } +} + +static void run_test_step(const char* name, test_context_t* context, test_setup_fn setup, test_step_fn step, test_teardown_fn teardown) +{ + int failures_before = g_failures; + DMOD_LOG_STEP_BEGIN("%s\n", name); + + if (setup != NULL) + { + setup(context); + } + if (step != NULL && context->ring != NULL) + { + step(context); + } + if (teardown != NULL) + { + teardown(context); + } + + DMOD_LOG_STEP((g_failures > failures_before) ? 1 : 0, "%s\n", name); +} + +static void test_create_step(test_context_t* context) +{ + dm_sw_ring_t ring = dm_sw_ring_create(4, context->ring_flags); + EXPECT_TRUE(ring != NULL); + EXPECT_TRUE(dm_sw_ring_create(0, context->ring_flags) == NULL); + if (ring != NULL) + { + dm_sw_ring_destroy(ring); + } +} + +static void test_destroy_step(test_context_t* context) +{ + EXPECT_TRUE(context->ring != NULL); + dm_sw_ring_destroy(context->ring); + context->ring = NULL; +} + +static void test_write_step(test_context_t* context) +{ + const uint8_t data[] = {10, 11, 12, 13, 14}; + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, NULL, 1), 0); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 0); + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 5), 5); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 4); +} + +static void test_read_step(test_context_t* context) +{ + const uint8_t data[] = {1, 2, 3}; + memset(context->io_buffer, 0, sizeof(context->io_buffer)); + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 3), 3); + EXPECT_EQ_U32(dm_sw_ring_read(context->ring, NULL, 1), 0); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 3); + EXPECT_EQ_U32(dm_sw_ring_read(context->ring, context->io_buffer, 2), 2); + EXPECT_EQ_U8(context->io_buffer[0], 1); + EXPECT_EQ_U8(context->io_buffer[1], 2); +} + +static void test_capacity_step(test_context_t* context) +{ + EXPECT_EQ_U32(dm_sw_ring_capacity(context->ring), 4); +} + +static void test_size_step(test_context_t* context) +{ + const uint8_t data[] = {1, 2, 3}; + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 0); + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 3), 3); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 3); + EXPECT_EQ_U32(dm_sw_ring_discard(context->ring, 1), 1); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 2); +} + +static void test_available_space_step(test_context_t* context) +{ + const uint8_t data[] = {1, 2, 3}; + EXPECT_EQ_U32(dm_sw_ring_available_space(context->ring), 4); + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 3), 3); + EXPECT_EQ_U32(dm_sw_ring_available_space(context->ring), 1); +} + +static void test_peek_step(test_context_t* context) +{ + const uint8_t data[] = {1, 2, 3, 4}; + memset(context->io_buffer, 0, sizeof(context->io_buffer)); + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 4), 4); + EXPECT_EQ_I32(dm_sw_ring_peek(context->ring, NULL, 1), -1); + EXPECT_EQ_I32(dm_sw_ring_peek(context->ring, context->io_buffer, 4), 4); + EXPECT_EQ_U8(context->io_buffer[0], 1); + EXPECT_EQ_U8(context->io_buffer[3], 4); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 4); +} + +static void test_discard_step(test_context_t* context) +{ + const uint8_t data[] = {4, 5, 6}; + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 3), 3); + EXPECT_EQ_I32(dm_sw_ring_discard(context->ring, 0), 0); + EXPECT_EQ_I32(dm_sw_ring_discard(context->ring, 1), 1); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 2); +} + +static void test_clear_step(test_context_t* context) +{ + const uint8_t data[] = {1, 2, 3, 4}; + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 4), 4); + EXPECT_EQ_I32(dm_sw_ring_clear(context->ring), 0); + EXPECT_EQ_U32(dm_sw_ring_size(context->ring), 0); +} + +static void test_is_full_step(test_context_t* context) +{ + const uint8_t data[] = {1, 2, 3, 4}; + EXPECT_EQ_BOOL(dm_sw_ring_is_full(context->ring), false); + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 4), 4); + EXPECT_EQ_BOOL(dm_sw_ring_is_full(context->ring), true); +} + +static void test_is_empty_step(test_context_t* context) +{ + const uint8_t data[] = {1}; + EXPECT_EQ_BOOL(dm_sw_ring_is_empty(context->ring), true); + EXPECT_EQ_U32(dm_sw_ring_write(context->ring, data, 1), 1); + EXPECT_EQ_BOOL(dm_sw_ring_is_empty(context->ring), false); + EXPECT_EQ_U32(dm_sw_ring_read(context->ring, context->io_buffer, 1), 1); + EXPECT_EQ_BOOL(dm_sw_ring_is_empty(context->ring), true); +} + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + + test_context_t context = { + .ring = NULL, + .ring_flags = dm_sw_ring_flags_drop_old_data | dm_sw_ring_flags_mutex_sync, + .io_buffer = {0} + }; + + run_test_step("dm_sw_ring_create", &context, NULL, test_create_step, NULL); + run_test_step("dm_sw_ring_destroy", &context, setup_ring_capacity_4, test_destroy_step, teardown_ring); + run_test_step("dm_sw_ring_write", &context, setup_ring_capacity_4, test_write_step, teardown_ring); + run_test_step("dm_sw_ring_read", &context, setup_ring_capacity_4, test_read_step, teardown_ring); + run_test_step("dm_sw_ring_capacity", &context, setup_ring_capacity_4, test_capacity_step, teardown_ring); + run_test_step("dm_sw_ring_size", &context, setup_ring_capacity_4, test_size_step, teardown_ring); + run_test_step("dm_sw_ring_available_space", &context, setup_ring_capacity_4, test_available_space_step, teardown_ring); + run_test_step("dm_sw_ring_peek", &context, setup_ring_capacity_4, test_peek_step, teardown_ring); + run_test_step("dm_sw_ring_discard", &context, setup_ring_capacity_4, test_discard_step, teardown_ring); + run_test_step("dm_sw_ring_clear", &context, setup_ring_capacity_4, test_clear_step, teardown_ring); + run_test_step("dm_sw_ring_is_full", &context, setup_ring_capacity_4, test_is_full_step, teardown_ring); + run_test_step("dm_sw_ring_is_empty", &context, setup_ring_capacity_4, test_is_empty_step, teardown_ring); + + if (g_failures == 0) + { + DMOD_LOG_INFO("All dm_sw_ring API checks passed\n"); + return 0; + } + + DMOD_LOG_ERROR("dm_sw_ring API checks failed: %d\n", g_failures); + return 1; +}