diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4f1a8ec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,111 @@ +name: CI + +on: + push: + branches: [ main, develop, copilot/*, feature/* ] + pull_request: + branches: [ main, develop, feature/* ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: chocotechnologies/dmod:1.0.2 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: | + apt-get update + apt-get install -y lcov + + - name: Configure CMake + run: | + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -DDMLIST_BUILD_TESTS=ON + + - name: Build + run: | + cd build + make -j$(nproc) + + - name: Run tests + run: | + cd build + ctest --output-on-failure --verbose + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: build/Testing/ + retention-days: 30 + + coverage: + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: chocotechnologies/dmod:1.0.2 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: | + apt-get update + apt-get install -y lcov + + - name: Configure CMake with coverage + run: | + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON -DDMLIST_BUILD_TESTS=ON + + - name: Build with coverage + run: | + cd build + make -j$(nproc) + + - name: Run tests + run: | + cd build + ctest --output-on-failure + + - name: Generate coverage report + run: | + cd build + lcov --directory . --capture --output-file coverage.info + lcov --remove coverage.info '/usr/*' '*/build/_deps/*' --output-file coverage_filtered.info --ignore-errors unused + lcov --list coverage_filtered.info + + - name: Generate HTML coverage report + run: | + cd build + mkdir -p coverage_html + genhtml coverage_filtered.info --output-directory coverage_html + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: build/coverage_html/ + retention-days: 30 + + - name: Coverage summary + run: | + cd build + echo "## Coverage Summary" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + lcov --summary coverage_filtered.info 2>&1 | tee -a $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4426ade --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,103 @@ +# ===================================================================== +# DMOD Linked List Library +# ===================================================================== +cmake_minimum_required(VERSION 3.10) + +# ====================================================================== +# DMOD List +# ====================================================================== +project(dmlist + VERSION 1.0 + DESCRIPTION "DMOD Linked List Library" + LANGUAGES C CXX) + +# ====================================================================== +# Coverage Configuration +# ====================================================================== +option(ENABLE_COVERAGE "Enable code coverage" OFF) + +if(ENABLE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + message(STATUS "Code coverage enabled") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") + else() + message(WARNING "Code coverage is only supported with GCC or Clang") + set(ENABLE_COVERAGE OFF) + endif() +endif() + +# ====================================================================== +# Fetch DMOD repository +# ====================================================================== +include(FetchContent) + +# Only fetch and build dmod if it's not already available as a target +if(NOT TARGET dmod_inc) + # ====================================================================== + # DMOD Configuration + # ====================================================================== + set(DMOD_MODE "DMOD_SYSTEM" CACHE STRING "DMOD build mode" FORCE) + set(DMOD_BUILD_TESTS OFF CACHE BOOL "Build tests" FORCE) + set(DMOD_BUILD_EXAMPLES OFF CACHE BOOL "Build examples" FORCE) + set(DMOD_BUILD_TOOLS OFF CACHE BOOL "Build tools" FORCE) + set(DMOD_BUILD_TEMPLATES OFF CACHE BOOL "Build templates" FORCE) + + FetchContent_Declare( + dmod + GIT_REPOSITORY https://github.com/choco-technologies/dmod.git + GIT_TAG develop + ) + + # Pass coverage flags to DMOD if enabled - set before FetchContent_MakeAvailable + if(ENABLE_COVERAGE) + set(DMOD_ENABLE_COVERAGE ON CACHE BOOL "Enable DMOD coverage") + # Ensure DMOD uses the same coverage flags + set(CMAKE_C_FLAGS_INIT "${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS}") + endif() + + FetchContent_MakeAvailable(dmod) + set(DMOD_DIR ${dmod_SOURCE_DIR} CACHE PATH "DMOD source directory" FORCE) +else() + message(STATUS "dmod target already exists, skipping FetchContent") +endif() + +include(${DMOD_DIR}/paths.cmake) + +# ====================================================================== +# DMOD List Library +# ====================================================================== +set(MODULE_NAME dmlist) +add_library(${MODULE_NAME} STATIC + src/dmlist.c +) + +target_compile_definitions(${MODULE_NAME} + PRIVATE + DMLIST_VERSION_STRING="== dmlist ver. ${PROJECT_VERSION} ==\\n" +) + +target_include_directories(${MODULE_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(${MODULE_NAME} + PUBLIC + dmod_inc + ) + +create_library_makefile(${MODULE_NAME}) + +# ====================================================================== +# Tests +# ====================================================================== +option(DMLIST_BUILD_TESTS "Build tests" OFF) + +if(DMLIST_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bac1901 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +# ############################################################################## +# Makefile for the dmlist module +# +# This Makefile is used to build the dmlist module library +# +# DONT EDIT THIS FILE - it is automatically generated. +# Edit the scripts/Makefile-lib.in file instead. +# +# ############################################################################## +ifeq ($(DMOD_DIR),) + DMOD_DIR = _codeql_build_dir/_deps/dmod-src +endif + +# +# Name of the module +# +DMOD_LIB_NAME=libdmlist.a +DMOD_SOURCES=src/dmlist.c +DMOD_INC_DIRS = include\ + $(DMOD_DIR)/inc\ + _codeql_build_dir/_deps/dmod-build +DMOD_LIBS = dmod_inc +DMOD_GEN_HEADERS_IN = +DMOD_DEFINITIONS = DMLIST_VERSION_STRING="== dmlist ver. 1.0 ==\n" + +# ----------------------------------------------------------------------------- +# Initialization of paths +# ----------------------------------------------------------------------------- +include $(DMOD_DIR)/paths.mk + +# ----------------------------------------------------------------------------- +# Including the template for the library +# ----------------------------------------------------------------------------- +DMOD_LIB_OBJS_DIR = $(DMOD_OBJS_DIR)/dmlist +include $(DMOD_SLIB_FILE_PATH) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab26f0a --- /dev/null +++ b/README.md @@ -0,0 +1,901 @@ +# dmlist - DMOD Linked List Library + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +A doubly-linked list implementation designed specifically for the **DMOD (Dynamic Modules)** framework. dmlist provides a simple and efficient linked list data structure with module-aware memory management for embedded systems. + +## Table of Contents + +- [Overview](#overview) +- [What is DMOD?](#what-is-dmod) +- [What is dmlist?](#what-is-dmlist) +- [Architecture](#architecture) +- [Features](#features) +- [Building](#building) +- [Testing](#testing) +- [Usage](#usage) +- [API Reference](#api-reference) +- [Examples](#examples) +- [Contributing](#contributing) +- [License](#license) + +## Overview + +dmlist is a doubly-linked list library that integrates seamlessly with the DMOD dynamic module system. It provides module-aware memory management through DMOD's allocation API, making it ideal for use in embedded systems where memory tracking and module lifecycle management are critical. + +## What is DMOD? + +**DMOD (Dynamic Modules)** is a library that enables dynamic loading and unloading of modules in embedded systems at runtime. It allows you to: + +- **Dynamically load modules**: Load functionality from `.dmf` files without recompiling +- **Manage dependencies**: Automatically handle module dependencies +- **Inter-module communication**: Modules can communicate via a common API +- **Resource management**: Efficiently manage system resources +- **Safe updates**: Update individual modules without affecting the entire system + +DMOD provides a modular architecture that makes embedded systems more flexible, maintainable, and easier to extend. For more information, visit the [DMOD repository](https://github.com/choco-technologies/dmod). + +## What is dmlist? + +**dmlist** is a doubly-linked list implementation specifically designed to work with DMOD. It provides: + +- **Module-aware allocation**: All memory allocations are tracked by module name through DMOD's memory API +- **Doubly-linked structure**: Supports efficient forward and backward traversal +- **Generic data storage**: Stores void pointers, allowing any data type to be stored +- **Rich API**: Supports common operations like push, pop, insert, remove, find, and iteration +- **Automatic cleanup**: When used with DMOD's memory management, lists can be cleaned up automatically when modules are unloaded +- **Zero external dependencies**: Only requires the DMOD framework + +## Architecture + +### List Structure + +dmlist uses a doubly-linked list structure with head and tail pointers for efficient operations: + +``` +┌─────────────────────────────────────────────────┐ +│ dmlist Context │ +├─────────────────────────────────────────────────┤ +│ - head: dmlist_node_t* ───────┐ │ +│ - tail: dmlist_node_t* ───┐ │ │ +│ - size: size_t │ │ │ +│ - module_name: char[] │ │ │ +└──────────────────────────────┼───┼──────────────┘ + │ │ + ┌───────────┘ └───────────┐ + ▼ ▼ + ┌────────────┐ ┌────────────┐ + │ Node 1 │◄───────►│ Node 2 │◄───►... + ├────────────┤ ├────────────┤ + │ data: void*│ │ data: void*│ + │ next: * │────────►│ next: * │ + │ prev: * │◄────────│ prev: * │ + └────────────┘ └────────────┘ +``` + +Each node contains: +- **data**: Pointer to the user data (void* for generic storage) +- **next**: Pointer to the next node in the list +- **prev**: Pointer to the previous node in the list + +### Memory Management + +All memory allocations in dmlist go through DMOD's memory API (`Dmod_Malloc` and `Dmod_Free`). This allows: + +1. **Module tracking**: Every allocation is associated with the module that created the list +2. **Automatic cleanup**: When a module is unloaded, all its list allocations can be freed automatically +3. **Memory profiling**: Track memory usage per module +4. **Consistent allocation**: Use the same allocator as the rest of the DMOD ecosystem + +## Features + +- ✅ **Doubly-linked list**: Efficient forward and backward traversal +- ✅ **Module-aware allocations**: All allocations tracked by DMOD module system +- ✅ **Generic storage**: Stores void pointers for any data type +- ✅ **Push/Pop operations**: Add/remove from front or back in O(1) time +- ✅ **Insert/Remove at position**: Support for arbitrary position operations +- ✅ **Find operation**: Search for elements with custom comparison function +- ✅ **Iteration**: Foreach with callback function support +- ✅ **Size tracking**: Constant time size queries +- ✅ **Clear operation**: Remove all elements efficiently +- ✅ **Comprehensive logging**: Integration with DMOD logging system +- ✅ **Zero external dependencies**: Only requires DMOD framework + +## Building + +### Prerequisites + +- **CMake**: Version 3.10 or higher +- **C Compiler**: GCC or compatible +- **Make**: For Makefile-based builds (optional) + +### Using CMake + +```bash +# Clone the repository +git clone https://github.com/choco-technologies/dmlist.git +cd dmlist + +# Create build directory +mkdir build +cd build + +# Configure +cmake .. + +# Build +make + +# Run tests +ctest --verbose +``` + +### Build Options + +You can customize the build with these CMake options: + +```bash +# Enable tests +cmake -DDMLIST_BUILD_TESTS=ON .. + +# Enable code coverage +cmake -DENABLE_COVERAGE=ON .. + +# Change DMOD mode +cmake -DDMOD_MODE=DMOD_EMBEDDED .. +``` + +### Using Makefile + +dmlist also supports traditional Makefile builds: + +```bash +# Build the library +make + +# The library will be created as libdmlist.a +``` + +The Makefile is automatically generated by CMake and includes necessary DMOD paths. + +## Testing + +dmlist includes basic test suites: + +### Test Suites + +1. **test_simple**: Basic smoke tests + - Initialization + - Push/Pop operations + - Size queries + - Clear operation + - Destruction + +### Running Tests + +```bash +# Run all tests +cd build +ctest + +# Run with verbose output +ctest --verbose + +# Run specific test +./tests/test_simple +``` + +### Example Test Output + +``` +=== Simple DMLIST Test === +Init: PASS +Is Empty: PASS +Push Back: PASS +Size: PASS +Front: PASS +Back: PASS +Pop Front: PASS +Pop Back: PASS +Clear: PASS +Destroy: PASS + +All simple tests completed! +``` + +## Usage + +### Basic Usage + +```c +#include "dmlist.h" +#include + +int main(void) { + // 1. Create a list + dmlist_context_t* list = dmlist_create("main"); + if (list == NULL) { + return -1; + } + + // 2. Add elements + int value1 = 10; + int value2 = 20; + dmlist_push_back(list, &value1); + dmlist_push_back(list, &value2); + + // 3. Access elements + int* front = (int*)dmlist_front(list); + printf("Front: %d\n", *front); // Output: Front: 10 + + // 4. Get list size + printf("Size: %zu\n", dmlist_size(list)); // Output: Size: 2 + + // 5. Remove elements + int* popped = (int*)dmlist_pop_front(list); + printf("Popped: %d\n", *popped); // Output: Popped: 10 + + // 6. Clean up + dmlist_destroy(list); + + return 0; +} +``` + +### Working with Strings + +```c +#include "dmlist.h" +#include +#include + +void string_list_example(void) { + dmlist_context_t* list = dmlist_create("string_module"); + + // Add strings to the list + dmlist_push_back(list, "Hello"); + dmlist_push_back(list, "World"); + dmlist_push_back(list, "!"); + + // Print all strings + for (size_t i = 0; i < dmlist_size(list); i++) { + const char* str = (const char*)dmlist_get(list, i); + printf("%s ", str); + } + printf("\n"); // Output: Hello World ! + + dmlist_destroy(list); +} +``` + +### Finding Elements + +```c +#include "dmlist.h" +#include + +// Comparison function for strings +int compare_strings(const void* a, const void* b) { + return strcmp((const char*)a, (const char*)b); +} + +// Comparison function for integers +int compare_ints(const void* a, const void* b) { + int ia = *(const int*)a; + int ib = *(const int*)b; + return ia - ib; +} + +void find_example(void) { + dmlist_context_t* list = dmlist_create("search_module"); + + // Add some values + int values[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < 5; i++) { + dmlist_push_back(list, &values[i]); + } + + // Find a specific value + int search_val = 30; + int* found = (int*)dmlist_find(list, &search_val, compare_ints); + if (found != NULL) { + printf("Found: %d\n", *found); // Output: Found: 30 + } + + dmlist_destroy(list); +} +``` + +### Iteration with Foreach + +```c +#include "dmlist.h" +#include + +// Iterator callback function +bool print_int(void* data, void* user_data) { + int* value = (int*)data; + printf("%d ", *value); + return true; // Continue iteration +} + +// Iterator with sum accumulator +bool sum_accumulator(void* data, void* user_data) { + int* value = (int*)data; + int* sum = (int*)user_data; + *sum += *value; + return true; +} + +void iteration_example(void) { + dmlist_context_t* list = dmlist_create("iter_module"); + + // Add values + int values[] = {1, 2, 3, 4, 5}; + for (int i = 0; i < 5; i++) { + dmlist_push_back(list, &values[i]); + } + + // Print all values + printf("Values: "); + dmlist_foreach(list, print_int, NULL); + printf("\n"); // Output: Values: 1 2 3 4 5 + + // Calculate sum + int sum = 0; + dmlist_foreach(list, sum_accumulator, &sum); + printf("Sum: %d\n", sum); // Output: Sum: 15 + + dmlist_destroy(list); +} +``` + +### Inserting at Specific Position + +```c +#include "dmlist.h" +#include + +void insert_example(void) { + dmlist_context_t* list = dmlist_create("insert_module"); + + int values[] = {1, 2, 4, 5}; + + // Build initial list: 1, 2, 4, 5 + for (int i = 0; i < 4; i++) { + dmlist_push_back(list, &values[i]); + } + + // Insert 3 at position 2 (between 2 and 4) + int val = 3; + dmlist_insert(list, 2, &val); + + // Now list is: 1, 2, 3, 4, 5 + printf("List after insert: "); + for (size_t i = 0; i < dmlist_size(list); i++) { + int* v = (int*)dmlist_get(list, i); + printf("%d ", *v); + } + printf("\n"); // Output: List after insert: 1 2 3 4 5 + + dmlist_destroy(list); +} +``` + +### Removing Elements + +```c +#include "dmlist.h" +#include + +int compare_strings(const void* a, const void* b) { + return strcmp((const char*)a, (const char*)b); +} + +void remove_example(void) { + dmlist_context_t* list = dmlist_create("remove_module"); + + // Add elements + dmlist_push_back(list, "apple"); + dmlist_push_back(list, "banana"); + dmlist_push_back(list, "cherry"); + dmlist_push_back(list, "date"); + + // Remove "banana" + const char* to_remove = "banana"; + bool removed = dmlist_remove(list, to_remove, compare_strings); + printf("Removed: %s\n", removed ? "yes" : "no"); // Output: Removed: yes + + // Remove element at position 1 + const char* removed_val = (const char*)dmlist_remove_at(list, 1); + printf("Removed at pos 1: %s\n", removed_val); // Output: Removed at pos 1: cherry + + dmlist_destroy(list); +} +``` + +### Working with Structures + +```c +#include "dmlist.h" +#include +#include + +typedef struct { + char name[50]; + int age; + float score; +} Student; + +int compare_students_by_age(const void* a, const void* b) { + const Student* sa = (const Student*)a; + const Student* sb = (const Student*)b; + return sa->age - sb->age; +} + +bool print_student(void* data, void* user_data) { + Student* s = (Student*)data; + printf(" %s (age: %d, score: %.1f)\n", s->name, s->age, s->score); + return true; +} + +void struct_example(void) { + dmlist_context_t* list = dmlist_create("student_module"); + + // Create students + Student s1 = {"Alice", 20, 85.5}; + Student s2 = {"Bob", 22, 90.0}; + Student s3 = {"Charlie", 19, 88.5}; + + // Add to list + dmlist_push_back(list, &s1); + dmlist_push_back(list, &s2); + dmlist_push_back(list, &s3); + + // Print all students + printf("All students:\n"); + dmlist_foreach(list, print_student, NULL); + + // Find a specific student + Student search = {"", 22, 0}; + Student* found = (Student*)dmlist_find(list, &search, compare_students_by_age); + if (found != NULL) { + printf("\nFound student aged 22: %s\n", found->name); + } + + dmlist_destroy(list); +} +``` + +## API Reference + +### Initialization and Cleanup + +#### `dmlist_create` + +```c +dmlist_context_t* dmlist_create(const char* module_name); +``` + +Create a linked list context. + +- **Parameters:** + - `module_name`: Name of the module using the list (for memory tracking) +- **Returns:** Pointer to the list context, or `NULL` if creation fails +- **Note:** All memory allocations for this list will be tracked under `module_name` + +#### `dmlist_destroy` + +```c +void dmlist_destroy(dmlist_context_t* ctx); +``` + +Destroy a linked list and free all its nodes. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Note:** This frees all node structures, but does NOT free the data pointers stored in the nodes + +### Size and Status + +#### `dmlist_size` + +```c +size_t dmlist_size(dmlist_context_t* ctx); +``` + +Get the number of elements in the list. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Returns:** Number of elements in the list +- **Time Complexity:** O(1) + +#### `dmlist_is_empty` + +```c +bool dmlist_is_empty(dmlist_context_t* ctx); +``` + +Check if the list is empty. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Returns:** `true` if the list is empty, `false` otherwise +- **Time Complexity:** O(1) + +### Adding Elements + +#### `dmlist_push_front` + +```c +bool dmlist_push_front(dmlist_context_t* ctx, void* data); +``` + +Add an element to the front of the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `data`: Pointer to the data to add +- **Returns:** `true` if successful, `false` otherwise +- **Time Complexity:** O(1) + +#### `dmlist_push_back` + +```c +bool dmlist_push_back(dmlist_context_t* ctx, void* data); +``` + +Add an element to the back of the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `data`: Pointer to the data to add +- **Returns:** `true` if successful, `false` otherwise +- **Time Complexity:** O(1) + +#### `dmlist_insert` + +```c +bool dmlist_insert(dmlist_context_t* ctx, size_t position, void* data); +``` + +Insert an element at a specific position in the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `position`: Position to insert at (0 = front) + - `data`: Pointer to the data to insert +- **Returns:** `true` if successful, `false` otherwise +- **Time Complexity:** O(n) +- **Note:** If position >= size, the element is added to the back + +### Removing Elements + +#### `dmlist_pop_front` + +```c +void* dmlist_pop_front(dmlist_context_t* ctx); +``` + +Remove and return the element at the front of the list. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Returns:** Pointer to the data, or `NULL` if the list is empty +- **Time Complexity:** O(1) + +#### `dmlist_pop_back` + +```c +void* dmlist_pop_back(dmlist_context_t* ctx); +``` + +Remove and return the element at the back of the list. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Returns:** Pointer to the data, or `NULL` if the list is empty +- **Time Complexity:** O(1) + +#### `dmlist_remove_at` + +```c +void* dmlist_remove_at(dmlist_context_t* ctx, size_t position); +``` + +Remove an element at a specific position in the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `position`: Position to remove (0 = front) +- **Returns:** Pointer to the removed data, or `NULL` if position is out of bounds +- **Time Complexity:** O(n) + +#### `dmlist_remove` + +```c +bool dmlist_remove(dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func); +``` + +Remove an element from the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `data`: Pointer to the data to remove + - `compare_func`: Comparison function to use +- **Returns:** `true` if the element was found and removed, `false` otherwise +- **Time Complexity:** O(n) + +#### `dmlist_clear` + +```c +void dmlist_clear(dmlist_context_t* ctx); +``` + +Remove all elements from the list. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Time Complexity:** O(n) +- **Note:** This frees all node structures, but does NOT free the data pointers + +### Accessing Elements + +#### `dmlist_front` + +```c +void* dmlist_front(dmlist_context_t* ctx); +``` + +Get the element at the front of the list without removing it. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Returns:** Pointer to the data, or `NULL` if the list is empty +- **Time Complexity:** O(1) + +#### `dmlist_back` + +```c +void* dmlist_back(dmlist_context_t* ctx); +``` + +Get the element at the back of the list without removing it. + +- **Parameters:** + - `ctx`: Pointer to the list context +- **Returns:** Pointer to the data, or `NULL` if the list is empty +- **Time Complexity:** O(1) + +#### `dmlist_get` + +```c +void* dmlist_get(dmlist_context_t* ctx, size_t position); +``` + +Get an element at a specific position in the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `position`: Position to get (0 = front) +- **Returns:** Pointer to the data, or `NULL` if position is out of bounds +- **Time Complexity:** O(n) + +### Searching + +#### `dmlist_find` + +```c +void* dmlist_find(dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func); +``` + +Find an element in the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `data`: Pointer to the data to find + - `compare_func`: Comparison function to use (returns 0 if equal) +- **Returns:** Pointer to the found data, or `NULL` if not found +- **Time Complexity:** O(n) + +### Iteration + +#### `dmlist_foreach` + +```c +void dmlist_foreach(dmlist_context_t* ctx, dmlist_iterator_func_t iterator_func, void* user_data); +``` + +Iterate over all elements in the list. + +- **Parameters:** + - `ctx`: Pointer to the list context + - `iterator_func`: Callback function to call for each element (returns `true` to continue) + - `user_data`: User-provided data passed to the iterator function +- **Time Complexity:** O(n) + +### Callback Types + +#### `dmlist_iterator_func_t` + +```c +typedef bool (*dmlist_iterator_func_t)(void* data, void* user_data); +``` + +Callback function type for iterating over list elements. + +- **Parameters:** + - `data`: Pointer to the data stored in the node + - `user_data`: User-provided data +- **Returns:** `true` to continue iteration, `false` to stop + +#### `dmlist_compare_func_t` + +```c +typedef int (*dmlist_compare_func_t)(const void* data1, const void* data2); +``` + +Callback function type for comparing list elements. + +- **Parameters:** + - `data1`: Pointer to the first data element + - `data2`: Pointer to the second data element +- **Returns:** `0` if equal, `<0` if data1 < data2, `>0` if data1 > data2 + +## Examples + +### Example 1: Task Queue + +```c +#include "dmlist.h" +#include +#include + +typedef struct { + int priority; + char description[100]; +} Task; + +int compare_priority(const void* a, const void* b) { + const Task* ta = (const Task*)a; + const Task* tb = (const Task*)b; + return ta->priority - tb->priority; +} + +void task_queue_example(void) { + dmlist_context_t* queue = dmlist_create("task_queue"); + + // Add tasks + Task t1 = {1, "Low priority task"}; + Task t2 = {5, "High priority task"}; + Task t3 = {3, "Medium priority task"}; + + dmlist_push_back(queue, &t1); + dmlist_push_back(queue, &t2); + dmlist_push_back(queue, &t3); + + // Process tasks + while (!dmlist_is_empty(queue)) { + Task* task = (Task*)dmlist_pop_front(queue); + printf("Processing: %s (priority: %d)\n", task->description, task->priority); + } + + dmlist_destroy(queue); +} +``` + +### Example 2: History Buffer + +```c +#include "dmlist.h" +#include +#include + +#define MAX_HISTORY 10 + +typedef struct { + dmlist_context_t* list; + size_t max_size; +} History; + +History* history_create(void) { + History* hist = (History*)Dmod_Malloc(sizeof(History), "history"); + hist->list = dmlist_create("history"); + hist->max_size = MAX_HISTORY; + return hist; +} + +void history_add(History* hist, const char* command) { + // If at max size, remove oldest + if (dmlist_size(hist->list) >= hist->max_size) { + dmlist_pop_front(hist->list); + } + + // Add new command + dmlist_push_back(hist->list, (void*)command); +} + +bool print_history_item(void* data, void* user_data) { + int* index = (int*)user_data; + const char* cmd = (const char*)data; + printf("%d: %s\n", (*index)++, cmd); + return true; +} + +void history_print(History* hist) { + int index = 0; + dmlist_foreach(hist->list, print_history_item, &index); +} + +void history_destroy(History* hist) { + dmlist_destroy(hist->list); + Dmod_Free(hist); +} +``` + +## Contributing + +Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests. + +### Development Setup + +1. Fork the repository +2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/dmlist.git` +3. Create a feature branch: `git checkout -b feature/my-new-feature` +4. Make your changes and add tests +5. Run tests: `cd build && ctest` +6. Commit your changes: `git commit -am 'Add some feature'` +7. Push to the branch: `git push origin feature/my-new-feature` +8. Submit a pull request + +### Code Style + +- Follow the existing code style +- Use meaningful variable and function names +- Add comments for complex logic +- Update documentation for API changes + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +``` +MIT License + +Copyright (c) 2025 Choco-Technologies + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## Acknowledgments + +- [DMOD (Dynamic Modules)](https://github.com/choco-technologies/dmod) - The dynamic module loading framework +- Choco-Technologies team for creating and maintaining this project + +## Related Projects + +- [DMOD](https://github.com/choco-technologies/dmod) - Dynamic Module Loading Framework +- [dmheap](https://github.com/choco-technologies/dmheap) - DMOD Heap Memory Manager +- [dmlog](https://github.com/choco-technologies/dmlog) - DMOD Logging Library +- [dmvfs](https://github.com/choco-technologies/dmvfs) - DMOD Virtual File System + +--- + +**For more information and support, please visit the [dmlist repository](https://github.com/choco-technologies/dmlist) or contact the Choco-Technologies team.** diff --git a/include/dmlist.h b/include/dmlist.h new file mode 100644 index 0000000..80aeff6 --- /dev/null +++ b/include/dmlist.h @@ -0,0 +1,197 @@ +#ifndef DMLIST_H +#define DMLIST_H + +#include "dmod.h" +#include +#include + +/** + * @brief Opaque type for linked list node. + */ +typedef struct dmlist_node_t dmlist_node_t; + +/** + * @brief Opaque type for linked list context. + */ +typedef struct dmlist_context_t dmlist_context_t; + +/** + * @brief Callback function type for iterating over list elements. + * + * @param data Pointer to the data stored in the node. + * @param user_data User-provided data passed to the iteration function. + * + * @return true to continue iteration, false to stop. + */ +typedef bool (*dmlist_iterator_func_t)(void* data, void* user_data); + +/** + * @brief Callback function type for comparing list elements. + * + * @param data1 Pointer to the first data element. + * @param data2 Pointer to the second data element. + * + * @return 0 if equal, <0 if data1 < data2, >0 if data1 > data2. + */ +typedef int (*dmlist_compare_func_t)(const void* data1, const void* data2); + +/** + * @brief Create a linked list context. + * + * @param module_name Name of the module using the list (for memory tracking). + * + * @return Pointer to the list context, or NULL if creation fails. + */ +DMOD_BUILTIN_API( dmlist, 1.0, dmlist_context_t*, _create, ( const char* module_name ) ); + +/** + * @brief Destroy a linked list and free all its nodes. + * + * @param ctx Pointer to the list context. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void, _destroy, ( dmlist_context_t* ctx ) ); + +/** + * @brief Get the number of elements in the list. + * + * @param ctx Pointer to the list context. + * + * @return Number of elements in the list. + */ +DMOD_BUILTIN_API( dmlist, 1.0, size_t, _size, ( dmlist_context_t* ctx ) ); + +/** + * @brief Check if the list is empty. + * + * @param ctx Pointer to the list context. + * + * @return true if the list is empty, false otherwise. + */ +DMOD_BUILTIN_API( dmlist, 1.0, bool, _is_empty, ( dmlist_context_t* ctx ) ); + +/** + * @brief Add an element to the front of the list. + * + * @param ctx Pointer to the list context. + * @param data Pointer to the data to add. + * + * @return true if successful, false otherwise. + */ +DMOD_BUILTIN_API( dmlist, 1.0, bool, _push_front, ( dmlist_context_t* ctx, void* data ) ); + +/** + * @brief Add an element to the back of the list. + * + * @param ctx Pointer to the list context. + * @param data Pointer to the data to add. + * + * @return true if successful, false otherwise. + */ +DMOD_BUILTIN_API( dmlist, 1.0, bool, _push_back, ( dmlist_context_t* ctx, void* data ) ); + +/** + * @brief Remove and return the element at the front of the list. + * + * @param ctx Pointer to the list context. + * + * @return Pointer to the data, or NULL if the list is empty. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void*, _pop_front, ( dmlist_context_t* ctx ) ); + +/** + * @brief Remove and return the element at the back of the list. + * + * @param ctx Pointer to the list context. + * + * @return Pointer to the data, or NULL if the list is empty. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void*, _pop_back, ( dmlist_context_t* ctx ) ); + +/** + * @brief Get the element at the front of the list without removing it. + * + * @param ctx Pointer to the list context. + * + * @return Pointer to the data, or NULL if the list is empty. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void*, _front, ( dmlist_context_t* ctx ) ); + +/** + * @brief Get the element at the back of the list without removing it. + * + * @param ctx Pointer to the list context. + * + * @return Pointer to the data, or NULL if the list is empty. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void*, _back, ( dmlist_context_t* ctx ) ); + +/** + * @brief Remove all elements from the list. + * + * @param ctx Pointer to the list context. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void, _clear, ( dmlist_context_t* ctx ) ); + +/** + * @brief Find an element in the list. + * + * @param ctx Pointer to the list context. + * @param data Pointer to the data to find. + * @param compare_func Comparison function to use. + * + * @return Pointer to the found data, or NULL if not found. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void*, _find, ( dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func ) ); + +/** + * @brief Remove an element from the list. + * + * @param ctx Pointer to the list context. + * @param data Pointer to the data to remove. + * @param compare_func Comparison function to use. + * + * @return true if the element was found and removed, false otherwise. + */ +DMOD_BUILTIN_API( dmlist, 1.0, bool, _remove, ( dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func ) ); + +/** + * @brief Iterate over all elements in the list. + * + * @param ctx Pointer to the list context. + * @param iterator_func Callback function to call for each element. + * @param user_data User-provided data passed to the iterator function. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void, _foreach, ( dmlist_context_t* ctx, dmlist_iterator_func_t iterator_func, void* user_data ) ); + +/** + * @brief Insert an element at a specific position in the list. + * + * @param ctx Pointer to the list context. + * @param position Position to insert at (0 = front). + * @param data Pointer to the data to insert. + * + * @return true if successful, false otherwise. + */ +DMOD_BUILTIN_API( dmlist, 1.0, bool, _insert, ( dmlist_context_t* ctx, size_t position, void* data ) ); + +/** + * @brief Get an element at a specific position in the list. + * + * @param ctx Pointer to the list context. + * @param position Position to get (0 = front). + * + * @return Pointer to the data, or NULL if position is out of bounds. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void*, _get, ( dmlist_context_t* ctx, size_t position ) ); + +/** + * @brief Remove an element at a specific position in the list. + * + * @param ctx Pointer to the list context. + * @param position Position to remove (0 = front). + * + * @return Pointer to the removed data, or NULL if position is out of bounds. + */ +DMOD_BUILTIN_API( dmlist, 1.0, void*, _remove_at, ( dmlist_context_t* ctx, size_t position ) ); + +#endif // DMLIST_H diff --git a/src/dmlist.c b/src/dmlist.c new file mode 100644 index 0000000..6a135cb --- /dev/null +++ b/src/dmlist.c @@ -0,0 +1,460 @@ +#include "dmlist.h" +#include + +/** + * @brief Structure to represent a node in the linked list. + */ +struct dmlist_node_t +{ + void* data; //!< Pointer to the data stored in the node. + struct dmlist_node_t* next; //!< Pointer to the next node. + struct dmlist_node_t* prev; //!< Pointer to the previous node. +}; + +/** + * @brief Structure to hold the context of the linked list. + */ +struct dmlist_context_t +{ + dmlist_node_t* head; //!< Pointer to the first node. + dmlist_node_t* tail; //!< Pointer to the last node. + size_t size; //!< Number of elements in the list. + char module_name[DMOD_MAX_MODULE_NAME_LENGTH]; //!< Name of the module using the list. +}; + +/** + * @brief Create a new node. + * + * @param data Pointer to the data to store in the node. + * @param module_name Name of the module creating the node. + * + * @return Pointer to the created node, or NULL if allocation fails. + */ +static dmlist_node_t* create_node( void* data, const char* module_name ) +{ + dmlist_node_t* node = (dmlist_node_t*)Dmod_MallocEx( sizeof(dmlist_node_t), module_name ); + if( node == NULL ) + { + DMOD_LOG_ERROR("dmlist: Failed to allocate memory for node.\n"); + return NULL; + } + + node->data = data; + node->next = NULL; + node->prev = NULL; + + return node; +} + +/** + * @brief Free a node. + * + * @param node Pointer to the node to free. + */ +static void free_node( dmlist_node_t* node ) +{ + if( node != NULL ) + { + Dmod_Free( node ); + } +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, dmlist_context_t*, _create, ( const char* module_name ) ) +{ + if( module_name == NULL ) + { + DMOD_LOG_ERROR("dmlist: _create called with NULL module_name.\n"); + return NULL; + } + + dmlist_context_t* ctx = (dmlist_context_t*)Dmod_MallocEx( sizeof(dmlist_context_t), module_name ); + if( ctx == NULL ) + { + DMOD_LOG_ERROR("dmlist: Failed to allocate memory for list context.\n"); + return NULL; + } + + ctx->head = NULL; + ctx->tail = NULL; + ctx->size = 0; + strncpy( ctx->module_name, module_name, DMOD_MAX_MODULE_NAME_LENGTH - 1 ); + ctx->module_name[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + + DMOD_LOG_INFO("dmlist: Created list for module %s.\n", module_name); + + return ctx; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void, _destroy, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL ) + { + return; + } + + dmlist_clear( ctx ); + DMOD_LOG_INFO("dmlist: Destroyed list for module %s.\n", ctx->module_name); + Dmod_Free( ctx ); +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, size_t, _size, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL ) + { + return 0; + } + + return ctx->size; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, bool, _is_empty, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL ) + { + return true; + } + + return ctx->size == 0; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, bool, _push_front, ( dmlist_context_t* ctx, void* data ) ) +{ + if( ctx == NULL ) + { + DMOD_LOG_ERROR("dmlist: _push_front called with NULL context.\n"); + return false; + } + + dmlist_node_t* node = create_node( data, ctx->module_name ); + if( node == NULL ) + { + return false; + } + + if( ctx->head == NULL ) + { + // List is empty + ctx->head = node; + ctx->tail = node; + } + else + { + // Add to front + node->next = ctx->head; + ctx->head->prev = node; + ctx->head = node; + } + + ctx->size++; + + return true; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, bool, _push_back, ( dmlist_context_t* ctx, void* data ) ) +{ + if( ctx == NULL ) + { + DMOD_LOG_ERROR("dmlist: _push_back called with NULL context.\n"); + return false; + } + + dmlist_node_t* node = create_node( data, ctx->module_name ); + if( node == NULL ) + { + return false; + } + + if( ctx->tail == NULL ) + { + // List is empty + ctx->head = node; + ctx->tail = node; + } + else + { + // Add to back + node->prev = ctx->tail; + ctx->tail->next = node; + ctx->tail = node; + } + + ctx->size++; + + return true; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void*, _pop_front, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL || ctx->head == NULL ) + { + return NULL; + } + + dmlist_node_t* node = ctx->head; + void* data = node->data; + + if( ctx->head == ctx->tail ) + { + // Only one element + ctx->head = NULL; + ctx->tail = NULL; + } + else + { + // Multiple elements + ctx->head = node->next; + ctx->head->prev = NULL; + } + + free_node( node ); + ctx->size--; + + return data; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void*, _pop_back, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL || ctx->tail == NULL ) + { + return NULL; + } + + dmlist_node_t* node = ctx->tail; + void* data = node->data; + + if( ctx->head == ctx->tail ) + { + // Only one element + ctx->head = NULL; + ctx->tail = NULL; + } + else + { + // Multiple elements + ctx->tail = node->prev; + ctx->tail->next = NULL; + } + + free_node( node ); + ctx->size--; + + return data; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void*, _front, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL || ctx->head == NULL ) + { + return NULL; + } + + return ctx->head->data; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void*, _back, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL || ctx->tail == NULL ) + { + return NULL; + } + + return ctx->tail->data; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void, _clear, ( dmlist_context_t* ctx ) ) +{ + if( ctx == NULL ) + { + return; + } + + dmlist_node_t* current = ctx->head; + while( current != NULL ) + { + dmlist_node_t* next = current->next; + free_node( current ); + current = next; + } + + ctx->head = NULL; + ctx->tail = NULL; + ctx->size = 0; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void*, _find, ( dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func ) ) +{ + if( ctx == NULL || compare_func == NULL ) + { + return NULL; + } + + dmlist_node_t* current = ctx->head; + while( current != NULL ) + { + if( compare_func( current->data, data ) == 0 ) + { + return current->data; + } + current = current->next; + } + + return NULL; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, bool, _remove, ( dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func ) ) +{ + if( ctx == NULL || compare_func == NULL ) + { + return false; + } + + dmlist_node_t* current = ctx->head; + while( current != NULL ) + { + if( compare_func( current->data, data ) == 0 ) + { + // Found the node to remove + if( current == ctx->head && current == ctx->tail ) + { + // Only one element + ctx->head = NULL; + ctx->tail = NULL; + } + else if( current == ctx->head ) + { + // First element + ctx->head = current->next; + ctx->head->prev = NULL; + } + else if( current == ctx->tail ) + { + // Last element + ctx->tail = current->prev; + ctx->tail->next = NULL; + } + else + { + // Middle element + current->prev->next = current->next; + current->next->prev = current->prev; + } + + free_node( current ); + ctx->size--; + + return true; + } + current = current->next; + } + + return false; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void, _foreach, ( dmlist_context_t* ctx, dmlist_iterator_func_t iterator_func, void* user_data ) ) +{ + if( ctx == NULL || iterator_func == NULL ) + { + return; + } + + dmlist_node_t* current = ctx->head; + while( current != NULL ) + { + if( !iterator_func( current->data, user_data ) ) + { + break; + } + current = current->next; + } +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, bool, _insert, ( dmlist_context_t* ctx, size_t position, void* data ) ) +{ + if( ctx == NULL ) + { + DMOD_LOG_ERROR("dmlist: _insert called with NULL context.\n"); + return false; + } + + if( position == 0 ) + { + return dmlist_push_front( ctx, data ); + } + + if( position >= ctx->size ) + { + return dmlist_push_back( ctx, data ); + } + + dmlist_node_t* node = create_node( data, ctx->module_name ); + if( node == NULL ) + { + return false; + } + + dmlist_node_t* current = ctx->head; + for( size_t i = 0; i < position; i++ ) + { + current = current->next; + } + + // Insert before current + node->next = current; + node->prev = current->prev; + current->prev->next = node; + current->prev = node; + + ctx->size++; + + return true; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void*, _get, ( dmlist_context_t* ctx, size_t position ) ) +{ + if( ctx == NULL || position >= ctx->size ) + { + return NULL; + } + + dmlist_node_t* current = ctx->head; + for( size_t i = 0; i < position; i++ ) + { + current = current->next; + } + + return current->data; +} + +DMOD_INPUT_API_DECLARATION( dmlist, 1.0, void*, _remove_at, ( dmlist_context_t* ctx, size_t position ) ) +{ + if( ctx == NULL || position >= ctx->size ) + { + return NULL; + } + + if( position == 0 ) + { + return dmlist_pop_front( ctx ); + } + + if( position == ctx->size - 1 ) + { + return dmlist_pop_back( ctx ); + } + + dmlist_node_t* current = ctx->head; + for( size_t i = 0; i < position; i++ ) + { + current = current->next; + } + + void* data = current->data; + + current->prev->next = current->next; + current->next->prev = current->prev; + + free_node( current ); + ctx->size--; + + return data; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8c2153e --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +# ===================================================================== +# DMLIST Tests +# ===================================================================== + +# Create a simple test executable +add_executable(test_simple test_simple.c dmod_stubs.c) +target_link_libraries(test_simple PRIVATE dmlist dmod_system) + +# Add test +add_test(NAME test_simple COMMAND test_simple) diff --git a/tests/dmod_stubs.c b/tests/dmod_stubs.c new file mode 100644 index 0000000..a6b4839 --- /dev/null +++ b/tests/dmod_stubs.c @@ -0,0 +1,13 @@ +/** + * @file dmod_stubs.c + * @brief DMOD stubs for testing + */ + +#include "dmod.h" + +// These symbols are normally defined by the linker script +// For tests, we define them as empty pointers +void* __dmod_inputs_start __attribute__((weak)) = NULL; +size_t __dmod_inputs_size __attribute__((weak)) = 0; +void* __dmod_outputs_start __attribute__((weak)) = NULL; +size_t __dmod_outputs_size __attribute__((weak)) = 0; diff --git a/tests/test_simple.c b/tests/test_simple.c new file mode 100644 index 0000000..1eca0bf --- /dev/null +++ b/tests/test_simple.c @@ -0,0 +1,107 @@ +#include "dmlist.h" +#include +#include + +#define TEST_PASS() printf("PASS\n") +#define TEST_FAIL() printf("FAIL\n") + +int main(void) { + printf("=== Simple DMLIST Test ===\n"); + + // Test 1: Create + printf("Create: "); + dmlist_context_t* list = dmlist_create("test"); + if(list == NULL) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 2: Check if empty + printf("Is Empty: "); + if(!dmlist_is_empty(list)) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 3: Push back + printf("Push Back: "); + int val1 = 10; + int val2 = 20; + int val3 = 30; + if(!dmlist_push_back(list, &val1) || !dmlist_push_back(list, &val2) || !dmlist_push_back(list, &val3)) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 4: Check size + printf("Size: "); + if(dmlist_size(list) != 3) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 5: Front + printf("Front: "); + int* front_val = (int*)dmlist_front(list); + if(front_val == NULL || *front_val != 10) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 6: Back + printf("Back: "); + int* back_val = (int*)dmlist_back(list); + if(back_val == NULL || *back_val != 30) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 7: Pop front + printf("Pop Front: "); + int* popped = (int*)dmlist_pop_front(list); + if(popped == NULL) { + TEST_FAIL(); + return 1; + } + if(*popped != 10 || dmlist_size(list) != 2) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 8: Pop back + printf("Pop Back: "); + popped = (int*)dmlist_pop_back(list); + if(popped == NULL) { + TEST_FAIL(); + return 1; + } + if(*popped != 30 || dmlist_size(list) != 1) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 9: Clear + printf("Clear: "); + dmlist_clear(list); + if(!dmlist_is_empty(list) || dmlist_size(list) != 0) { + TEST_FAIL(); + return 1; + } + TEST_PASS(); + + // Test 10: Destroy + printf("Destroy: "); + dmlist_destroy(list); + TEST_PASS(); + + printf("\nAll simple tests completed!\n"); + return 0; +}