From 2ffedd17d481774e4f00875997552c24b4df5fb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:18:28 +0000 Subject: [PATCH 01/11] Initial plan From 5e6d1b9f8cb29071a6a888d5600645bb256c00d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:25:38 +0000 Subject: [PATCH 02/11] Add complete dmenv implementation with tests Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- CMakeLists.txt | 84 ++++++++ README.md | 468 +++++++++++++++++++++++++++++++++++++++- include/dmenv.h | 78 +++++++ src/dmenv.c | 292 +++++++++++++++++++++++++ tests/CMakeLists.txt | 34 +++ tests/test_common.h | 29 +++ tests/test_dmenv_unit.c | 174 +++++++++++++++ tests/test_minimal.c | 64 ++++++ tests/test_simple.c | 110 ++++++++++ 9 files changed, 1331 insertions(+), 2 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 include/dmenv.h create mode 100644 src/dmenv.c create mode 100644 tests/CMakeLists.txt create mode 100644 tests/test_common.h create mode 100644 tests/test_dmenv_unit.c create mode 100644 tests/test_minimal.c create mode 100644 tests/test_simple.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..71b2c40 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,84 @@ +# ===================================================================== +# DMOD ENV CMake Configuration +# ===================================================================== +cmake_minimum_required(VERSION 3.10) + +# ====================================================================== +# DMOD ENV +# ====================================================================== +project(dmenv + VERSION 1.0 + DESCRIPTION "DMOD Environment Variables Manager" + LANGUAGES C CXX) + +set(DMENV_DONT_IMPLEMENT_DMOD_API OFF CACHE BOOL "Do not implement DMOD API in dmenv library") + + +# ====================================================================== +# Fetch DMOD repository +# ====================================================================== +include(FetchContent) + +# Only fetch and build dmod if it's not already available as a target +if(NOT TARGET dmod_inc) + FetchContent_Declare( + dmod + GIT_REPOSITORY https://github.com/choco-technologies/dmod.git + GIT_TAG develop + ) + + # ====================================================================== + # DMOD Configuration + # ====================================================================== + set(DMOD_MODE "DMOD_SYSTEM" CACHE STRING "DMOD build mode") + set(DMOD_BUILD_TESTS OFF CACHE BOOL "Build tests") + set(DMOD_BUILD_EXAMPLES OFF CACHE BOOL "Build examples") + set(DMOD_BUILD_TOOLS OFF CACHE BOOL "Build tools") + set(DMOD_BUILD_TEMPLATES OFF CACHE BOOL "Build templates") + + # Pass coverage flags to DMOD if enabled + if(ENABLE_COVERAGE) + set(DMOD_ENABLE_COVERAGE ON CACHE BOOL "Enable DMOD coverage") + 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() + +# ====================================================================== +# DMOD ENV Library +# ====================================================================== +set(MODULE_NAME dmenv) +add_library(${MODULE_NAME} STATIC + src/dmenv.c +) + +target_compile_definitions(${MODULE_NAME} + PRIVATE + $<$:DMENV_DONT_IMPLEMENT_DMOD_API> + DMENV_VERSION="${PROJECT_VERSION}" +) + +target_include_directories(${MODULE_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(${MODULE_NAME} + PRIVATE + dmod_inc + ) + +create_library_makefile(${MODULE_NAME}) + +# ====================================================================== +# Tests +# ====================================================================== +option(DMENV_BUILD_TESTS "Build tests" OFF) + +if(DMENV_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/README.md b/README.md index a8f2149..e181b8a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,466 @@ -# dmenv -DMOD Environment Variables +# dmenv - DMOD Environment Variables Manager + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +A lightweight environment variables manager designed specifically for the **DMOD (Dynamic Modules)** framework. dmenv provides a simple and efficient way to manage environment variables in embedded systems with limited resources. + +## Table of Contents + +- [Overview](#overview) +- [What is DMOD?](#what-is-dmod) +- [What is dmenv?](#what-is-dmenv) +- [Features](#features) +- [Building](#building) +- [Testing](#testing) +- [Usage](#usage) +- [API Reference](#api-reference) +- [Contributing](#contributing) +- [License](#license) + +## Overview + +dmenv is a custom environment variables manager that integrates seamlessly with the DMOD dynamic module system. It provides a static-buffer-based solution for storing and retrieving configuration parameters, making it ideal for embedded systems where dynamic memory allocation may be limited or undesirable. + +## 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 dmenv? + +**dmenv** is an environment variables manager specifically designed to work with DMOD. It provides: + +- **Simple key-value storage**: Store configuration parameters as name-value pairs +- **Static buffer management**: Operates on a pre-allocated buffer (no reliance on system malloc) +- **Thread-safe operations**: Uses DMOD's critical section mechanisms +- **Prefix-based search**: Find all variables matching a specific prefix +- **Efficient lookup**: Fast variable retrieval using linked list structure +- **Module integration**: Seamless integration with DMOD logging and error handling + +dmenv is ideal for storing application configuration, runtime parameters, and inter-module communication data in embedded DMOD-based systems. + +## Features + +- ✅ **Key-value storage**: Store and retrieve environment variables by name +- ✅ **Variable update**: Update existing variables without creating duplicates +- ✅ **Variable removal**: Remove individual variables or clear all at once +- ✅ **Prefix search**: Find all variables matching a given prefix +- ✅ **Variable counting**: Track the number of stored variables +- ✅ **Static buffer operation**: No dependency on system malloc/free +- ✅ **Thread-safe**: Uses DMOD critical sections for synchronization +- ✅ **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/dmenv.git +cd dmenv + +# 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 -DDMENV_BUILD_TESTS=ON .. + +# Disable DMOD API implementation +cmake -DDMENV_DONT_IMPLEMENT_DMOD_API=ON .. + +# Change DMOD mode +cmake -DDMOD_MODE=DMOD_EMBEDDED .. +``` + +### Using Makefile + +dmenv also supports traditional Makefile builds: + +```bash +# Build the library +make + +# The library will be created as libdmenv.a +``` + +## Testing + +dmenv includes comprehensive test suites: + +### Test Suites + +1. **test_minimal**: Minimal smoke tests + - Initialization + - Basic set/get operations + - Count and remove operations + +2. **test_simple**: Simple functional tests + - Set and get variables + - Remove variables + - Clear all variables + - Find variables by prefix + +3. **test_dmenv_unit**: Comprehensive unit tests using Unity framework + - Initialization with various conditions + - Set/get operations + - Update existing variables + - Remove operations + - Clear all variables + - Count operations + - Find operations with prefixes + - Multiple variable stress tests + +### Running Tests + +```bash +# Run all tests +cd build +ctest + +# Run with verbose output +ctest --verbose + +# Run specific test +./tests/test_minimal +./tests/test_simple +./tests/test_dmenv_unit +``` + +## Usage + +### Basic Usage + +```c +#include "dmenv.h" +#include + +// 1. Define your buffer +#define ENV_BUFFER_SIZE (8 * 1024) // 8KB +static char env_buffer[ENV_BUFFER_SIZE]; + +int main(void) { + // 2. Initialize the environment manager + bool success = dmenv_init(env_buffer, ENV_BUFFER_SIZE); + if (!success) { + // Handle initialization failure + return -1; + } + + // 3. Set environment variables + dmenv_set("APP_NAME", "MyApplication"); + dmenv_set("APP_VERSION", "1.0.0"); + dmenv_set("DEBUG_MODE", "true"); + + // 4. Get environment variables + const char* app_name = dmenv_get("APP_NAME"); + if (app_name != NULL) { + printf("Application: %s\n", app_name); + } + + return 0; +} +``` + +### Update Variables + +```c +#include "dmenv.h" + +void config_example(void) { + // Set initial value + dmenv_set("LOG_LEVEL", "INFO"); + + // Update the value (no duplicate created) + dmenv_set("LOG_LEVEL", "DEBUG"); + + // Get updated value + const char* level = dmenv_get("LOG_LEVEL"); + // level now contains "DEBUG" +} +``` + +### Remove Variables + +```c +#include "dmenv.h" + +void cleanup_example(void) { + // Set a temporary variable + dmenv_set("TEMP_DATA", "temporary_value"); + + // Remove it when done + bool removed = dmenv_remove("TEMP_DATA"); + if (removed) { + printf("Temporary data removed\n"); + } + + // Variable no longer exists + const char* value = dmenv_get("TEMP_DATA"); + // value is NULL +} +``` + +### Find Variables by Prefix + +```c +#include "dmenv.h" + +void print_var(const char* name, const char* value, void* user_data) { + printf("%s = %s\n", name, value); +} + +void find_example(void) { + // Set related variables with common prefix + dmenv_set("DB_HOST", "localhost"); + dmenv_set("DB_PORT", "5432"); + dmenv_set("DB_NAME", "mydb"); + dmenv_set("APP_NAME", "myapp"); + + // Find all database-related variables + printf("Database configuration:\n"); + size_t found = dmenv_find("DB_", print_var, NULL); + printf("Found %zu database variables\n", found); +} +``` + +### Clear All Variables + +```c +#include "dmenv.h" + +void reset_example(void) { + // Clear all environment variables + dmenv_clear(); + + // Verify count is 0 + size_t count = dmenv_count(); + printf("Variables remaining: %zu\n", count); // Prints: 0 +} +``` + +### Integration Example + +```c +#include "dmenv.h" +#include "dmod.h" +#include + +#define ENV_SIZE (16 * 1024) +static char env_buffer[ENV_SIZE]; + +void load_configuration(void) { + // Load configuration from persistent storage or hardcode + dmenv_set("SYSTEM_NAME", "EmbeddedDevice"); + dmenv_set("FIRMWARE_VERSION", "2.1.3"); + dmenv_set("NETWORK_ENABLED", "true"); + dmenv_set("NETWORK_IP", "192.168.1.100"); + dmenv_set("NETWORK_PORT", "8080"); + dmenv_set("SENSOR_SAMPLE_RATE", "1000"); + dmenv_set("SENSOR_THRESHOLD", "75.5"); +} + +void print_all_network_settings(const char* name, const char* value, void* user_data) { + printf(" %s = %s\n", name, value); +} + +int main(void) { + printf("=== Embedded System Configuration ===\n\n"); + + // Initialize environment manager + if (!dmenv_init(env_buffer, ENV_SIZE)) { + printf("ERROR: Failed to initialize environment manager\n"); + return -1; + } + + // Load configuration + load_configuration(); + + printf("Configuration loaded: %zu variables\n\n", dmenv_count()); + + // Display system info + printf("System: %s\n", dmenv_get("SYSTEM_NAME")); + printf("Firmware: %s\n\n", dmenv_get("FIRMWARE_VERSION")); + + // Display network settings + printf("Network Configuration:\n"); + dmenv_find("NETWORK_", print_all_network_settings, NULL); + + // Display sensor settings + printf("\nSensor Configuration:\n"); + dmenv_find("SENSOR_", print_all_network_settings, NULL); + + return 0; +} +``` + +## API Reference + +### Initialization + +#### `dmenv_init` + +```c +bool dmenv_init(void* buffer, size_t size); +``` + +Initialize the environment variables manager with a buffer. + +- **Parameters:** + - `buffer`: Pointer to the memory buffer to be used for storage + - `size`: Size of the memory buffer in bytes +- **Returns:** `true` if initialization is successful, `false` otherwise +- **Thread-safe:** Yes + +#### `dmenv_is_initialized` + +```c +bool dmenv_is_initialized(void); +``` + +Check if the environment variables manager is initialized. + +- **Returns:** `true` if initialized, `false` otherwise +- **Thread-safe:** Yes + +### Variable Operations + +#### `dmenv_set` + +```c +bool dmenv_set(const char* name, const char* value); +``` + +Set an environment variable. If the variable already exists, its value is updated. + +- **Parameters:** + - `name`: Name of the environment variable (max 63 characters) + - `value`: Value to set (max 255 characters) +- **Returns:** `true` if the variable was set successfully, `false` otherwise +- **Thread-safe:** Yes + +#### `dmenv_get` + +```c +const char* dmenv_get(const char* name); +``` + +Get an environment variable value. + +- **Parameters:** + - `name`: Name of the environment variable +- **Returns:** Pointer to the value string, or NULL if not found +- **Thread-safe:** Yes +- **Note:** The returned pointer is valid until the variable is removed or modified + +#### `dmenv_remove` + +```c +bool dmenv_remove(const char* name); +``` + +Remove an environment variable. + +- **Parameters:** + - `name`: Name of the environment variable to remove +- **Returns:** `true` if the variable was removed successfully, `false` if not found +- **Thread-safe:** Yes + +#### `dmenv_clear` + +```c +bool dmenv_clear(void); +``` + +Clear all environment variables. + +- **Returns:** `true` if all variables were cleared successfully, `false` otherwise +- **Thread-safe:** Yes + +### Query Operations + +#### `dmenv_find` + +```c +size_t dmenv_find(const char* prefix, + void (*callback)(const char* name, const char* value, void* user_data), + void* user_data); +``` + +Find environment variables matching a prefix. + +- **Parameters:** + - `prefix`: Prefix to match against variable names + - `callback`: Callback function to call for each matching variable + - `user_data`: User data to pass to the callback +- **Returns:** Number of matching variables found +- **Thread-safe:** Yes + +#### `dmenv_count` + +```c +size_t dmenv_count(void); +``` + +Get the number of environment variables currently stored. + +- **Returns:** Number of environment variables +- **Thread-safe:** Yes + +## 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/dmenv.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 + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## 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 System + +--- + +**For more information and support, please visit the [dmenv repository](https://github.com/choco-technologies/dmenv) or contact the Choco-Technologies team.** diff --git a/include/dmenv.h b/include/dmenv.h new file mode 100644 index 0000000..20935f7 --- /dev/null +++ b/include/dmenv.h @@ -0,0 +1,78 @@ +#ifndef DMENV_H +#define DMENV_H + +#include "dmod.h" +#include +#include + +/** + * @brief Initialize the environment variables manager with a buffer. + * + * @param buffer Pointer to the memory buffer to be used for environment variables. + * @param size Size of the memory buffer. + * + * @return true if initialization is successful, false otherwise. + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool , _init, ( void* buffer, size_t size ) ); + +/** + * @brief Check if the environment variables manager is initialized. + * + * @return true if initialized, false otherwise. + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool , _is_initialized, ( void ) ); + +/** + * @brief Set an environment variable. + * + * @param name Name of the environment variable. + * @param value Value to set for the environment variable. + * + * @return true if the variable was set successfully, false otherwise. + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool , _set, ( const char* name, const char* value ) ); + +/** + * @brief Get an environment variable value. + * + * @param name Name of the environment variable. + * + * @return Pointer to the value string, or NULL if not found. + */ +DMOD_BUILTIN_API( dmenv, 1.0, const char* , _get, ( const char* name ) ); + +/** + * @brief Find environment variables matching a prefix. + * + * @param prefix Prefix to match against variable names. + * @param callback Callback function to call for each matching variable. + * @param user_data User data to pass to the callback. + * + * @return Number of matching variables found. + */ +DMOD_BUILTIN_API( dmenv, 1.0, size_t , _find, ( const char* prefix, void (*callback)(const char* name, const char* value, void* user_data), void* user_data ) ); + +/** + * @brief Remove an environment variable. + * + * @param name Name of the environment variable to remove. + * + * @return true if the variable was removed successfully, false if not found. + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool , _remove, ( const char* name ) ); + +/** + * @brief Clear all environment variables. + * + * @return true if all variables were cleared successfully, false otherwise. + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool , _clear, ( void ) ); + +/** + * @brief Get the number of environment variables currently stored. + * + * @return Number of environment variables. + */ +DMOD_BUILTIN_API( dmenv, 1.0, size_t , _count, ( void ) ); + +#endif // DMENV_H diff --git a/src/dmenv.c b/src/dmenv.c new file mode 100644 index 0000000..cecfc38 --- /dev/null +++ b/src/dmenv.c @@ -0,0 +1,292 @@ +#include "dmenv.h" +#include +#include + +#ifndef DMENV_MAX_NAME_LENGTH +#define DMENV_MAX_NAME_LENGTH 64 +#endif + +#ifndef DMENV_MAX_VALUE_LENGTH +#define DMENV_MAX_VALUE_LENGTH 256 +#endif + +/** + * @brief Structure to hold an environment variable entry + */ +typedef struct env_entry { + char name[DMENV_MAX_NAME_LENGTH]; + char value[DMENV_MAX_VALUE_LENGTH]; + struct env_entry* next; +} env_entry_t; + +/** + * @brief Context structure for the environment variables manager + */ +typedef struct { + void* buffer; + size_t buffer_size; + env_entry_t* head; + bool initialized; + size_t entry_count; + size_t used_size; +} dmenv_context_t; + +static dmenv_context_t g_dmenv_context = {0}; + +/** + * @brief Helper function to allocate an entry from the buffer + */ +static env_entry_t* allocate_entry(void) { + if (g_dmenv_context.used_size + sizeof(env_entry_t) > g_dmenv_context.buffer_size) { + DMOD_LOG_ERROR("dmenv", "Buffer full, cannot allocate new entry"); + return NULL; + } + + env_entry_t* entry = (env_entry_t*)((char*)g_dmenv_context.buffer + g_dmenv_context.used_size); + g_dmenv_context.used_size += sizeof(env_entry_t); + memset(entry, 0, sizeof(env_entry_t)); + return entry; +} + +/** + * @brief Helper function to find an entry by name + */ +static env_entry_t* find_entry(const char* name) { + if (!name || !g_dmenv_context.initialized) { + return NULL; + } + + env_entry_t* current = g_dmenv_context.head; + while (current != NULL) { + if (strncmp(current->name, name, DMENV_MAX_NAME_LENGTH) == 0) { + return current; + } + current = current->next; + } + return NULL; +} + +// ============================================================================ +// DMOD API Implementations +// ============================================================================ + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _init, ( void* buffer, size_t size ) ) +{ + if (!buffer || size < sizeof(env_entry_t)) { + DMOD_LOG_ERROR("dmenv", "Invalid buffer or size for initialization"); + return false; + } + + DMOD_ENTER_CRITICAL_SECTION(); + + g_dmenv_context.buffer = buffer; + g_dmenv_context.buffer_size = size; + g_dmenv_context.head = NULL; + g_dmenv_context.initialized = true; + g_dmenv_context.entry_count = 0; + g_dmenv_context.used_size = 0; + + DMOD_LOG_INFO("dmenv", "Initialized with buffer %p of size %zu", buffer, size); + + DMOD_EXIT_CRITICAL_SECTION(); + + return true; +} + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _is_initialized, ( void ) ) +{ + return g_dmenv_context.initialized; +} + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const char* value ) ) +{ + if (!g_dmenv_context.initialized) { + DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + return false; + } + + if (!name || !value) { + DMOD_LOG_ERROR("dmenv", "Invalid name or value"); + return false; + } + + if (strlen(name) >= DMENV_MAX_NAME_LENGTH) { + DMOD_LOG_ERROR("dmenv", "Name too long: %s", name); + return false; + } + + if (strlen(value) >= DMENV_MAX_VALUE_LENGTH) { + DMOD_LOG_ERROR("dmenv", "Value too long for name: %s", name); + return false; + } + + DMOD_ENTER_CRITICAL_SECTION(); + + // Check if variable already exists + env_entry_t* existing = find_entry(name); + if (existing != NULL) { + // Update existing entry + strncpy(existing->value, value, DMENV_MAX_VALUE_LENGTH - 1); + existing->value[DMENV_MAX_VALUE_LENGTH - 1] = '\0'; + DMOD_LOG_INFO("dmenv", "Updated variable %s = %s", name, value); + DMOD_EXIT_CRITICAL_SECTION(); + return true; + } + + // Create new entry + env_entry_t* entry = allocate_entry(); + if (entry == NULL) { + DMOD_EXIT_CRITICAL_SECTION(); + return false; + } + + strncpy(entry->name, name, DMENV_MAX_NAME_LENGTH - 1); + entry->name[DMENV_MAX_NAME_LENGTH - 1] = '\0'; + strncpy(entry->value, value, DMENV_MAX_VALUE_LENGTH - 1); + entry->value[DMENV_MAX_VALUE_LENGTH - 1] = '\0'; + + // Add to linked list + entry->next = g_dmenv_context.head; + g_dmenv_context.head = entry; + g_dmenv_context.entry_count++; + + DMOD_LOG_INFO("dmenv", "Set variable %s = %s", name, value); + + DMOD_EXIT_CRITICAL_SECTION(); + + return true; +} + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, const char*, _get, ( const char* name ) ) +{ + if (!g_dmenv_context.initialized) { + DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + return NULL; + } + + if (!name) { + DMOD_LOG_ERROR("dmenv", "Invalid name"); + return NULL; + } + + DMOD_ENTER_CRITICAL_SECTION(); + + env_entry_t* entry = find_entry(name); + + DMOD_EXIT_CRITICAL_SECTION(); + + if (entry != NULL) { + DMOD_LOG_DEBUG("dmenv", "Get variable %s = %s", name, entry->value); + return entry->value; + } + + DMOD_LOG_DEBUG("dmenv", "Variable %s not found", name); + return NULL; +} + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, size_t, _find, ( const char* prefix, void (*callback)(const char* name, const char* value, void* user_data), void* user_data ) ) +{ + if (!g_dmenv_context.initialized) { + DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + return 0; + } + + if (!prefix || !callback) { + DMOD_LOG_ERROR("dmenv", "Invalid prefix or callback"); + return 0; + } + + size_t count = 0; + size_t prefix_len = strlen(prefix); + + DMOD_ENTER_CRITICAL_SECTION(); + + env_entry_t* current = g_dmenv_context.head; + while (current != NULL) { + if (strncmp(current->name, prefix, prefix_len) == 0) { + callback(current->name, current->value, user_data); + count++; + } + current = current->next; + } + + DMOD_EXIT_CRITICAL_SECTION(); + + DMOD_LOG_DEBUG("dmenv", "Found %zu variables with prefix '%s'", count, prefix); + + return count; +} + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) +{ + if (!g_dmenv_context.initialized) { + DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + return false; + } + + if (!name) { + DMOD_LOG_ERROR("dmenv", "Invalid name"); + return false; + } + + DMOD_ENTER_CRITICAL_SECTION(); + + env_entry_t* current = g_dmenv_context.head; + env_entry_t* prev = NULL; + + while (current != NULL) { + if (strncmp(current->name, name, DMENV_MAX_NAME_LENGTH) == 0) { + // Found the entry to remove + if (prev == NULL) { + // Removing the head + g_dmenv_context.head = current->next; + } else { + // Removing from middle or end + prev->next = current->next; + } + + g_dmenv_context.entry_count--; + + DMOD_LOG_INFO("dmenv", "Removed variable %s", name); + + DMOD_EXIT_CRITICAL_SECTION(); + return true; + } + prev = current; + current = current->next; + } + + DMOD_EXIT_CRITICAL_SECTION(); + + DMOD_LOG_DEBUG("dmenv", "Variable %s not found for removal", name); + return false; +} + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _clear, ( void ) ) +{ + if (!g_dmenv_context.initialized) { + DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + return false; + } + + DMOD_ENTER_CRITICAL_SECTION(); + + g_dmenv_context.head = NULL; + g_dmenv_context.entry_count = 0; + g_dmenv_context.used_size = 0; + + DMOD_LOG_INFO("dmenv", "Cleared all environment variables"); + + DMOD_EXIT_CRITICAL_SECTION(); + + return true; +} + +DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, size_t, _count, ( void ) ) +{ + if (!g_dmenv_context.initialized) { + return 0; + } + + return g_dmenv_context.entry_count; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..aa9c467 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,34 @@ +# ===================================================================== +# DMOD ENV Tests +# ===================================================================== + +# Find or fetch Unity test framework +include(FetchContent) + +if(NOT TARGET unity) + FetchContent_Declare( + unity + GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git + GIT_TAG v2.5.2 + ) + FetchContent_MakeAvailable(unity) +endif() + +# Common test utilities +add_library(test_common INTERFACE) +target_include_directories(test_common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +# Test: Simple test +add_executable(test_simple test_simple.c) +target_link_libraries(test_simple PRIVATE dmenv unity test_common) +add_test(NAME test_simple COMMAND test_simple) + +# Test: Unit tests +add_executable(test_dmenv_unit test_dmenv_unit.c) +target_link_libraries(test_dmenv_unit PRIVATE dmenv unity test_common) +add_test(NAME test_dmenv_unit COMMAND test_dmenv_unit) + +# Test: Minimal test +add_executable(test_minimal test_minimal.c) +target_link_libraries(test_minimal PRIVATE dmenv unity test_common) +add_test(NAME test_minimal COMMAND test_minimal) diff --git a/tests/test_common.h b/tests/test_common.h new file mode 100644 index 0000000..b1a70bc --- /dev/null +++ b/tests/test_common.h @@ -0,0 +1,29 @@ +#ifndef TEST_COMMON_H +#define TEST_COMMON_H + +#include "dmenv.h" +#include + +/** + * @brief Common test buffer size + */ +#define TEST_BUFFER_SIZE (16 * 1024) + +/** + * @brief Test helper to print results + */ +#define TEST_PRINT_RESULT(name, result) \ + printf("%s: %s\n", name, (result) ? "PASS" : "FAIL") + +/** + * @brief Test helper to verify condition + */ +#define TEST_ASSERT(condition, message) \ + do { \ + if (!(condition)) { \ + printf("ASSERT FAILED: %s at %s:%d\n", message, __FILE__, __LINE__); \ + return false; \ + } \ + } while(0) + +#endif // TEST_COMMON_H diff --git a/tests/test_dmenv_unit.c b/tests/test_dmenv_unit.c new file mode 100644 index 0000000..d98ea9f --- /dev/null +++ b/tests/test_dmenv_unit.c @@ -0,0 +1,174 @@ +#include "test_common.h" +#include "dmenv.h" +#include "unity.h" +#include + +static char test_buffer[TEST_BUFFER_SIZE]; + +void setUp(void) { + memset(test_buffer, 0, TEST_BUFFER_SIZE); + dmenv_init(test_buffer, TEST_BUFFER_SIZE); +} + +void tearDown(void) { + if (dmenv_is_initialized()) { + dmenv_clear(); + } +} + +void test_init_success(void) { + char buffer[1024]; + TEST_ASSERT_TRUE(dmenv_init(buffer, 1024)); + TEST_ASSERT_TRUE(dmenv_is_initialized()); +} + +void test_init_invalid_buffer(void) { + TEST_ASSERT_FALSE(dmenv_init(NULL, 1024)); +} + +void test_init_too_small(void) { + char buffer[10]; + TEST_ASSERT_FALSE(dmenv_init(buffer, 10)); +} + +void test_set_and_get(void) { + TEST_ASSERT_TRUE(dmenv_set("MY_VAR", "my_value")); + const char* value = dmenv_get("MY_VAR"); + TEST_ASSERT_NOT_NULL(value); + TEST_ASSERT_EQUAL_STRING("my_value", value); +} + +void test_set_update_existing(void) { + TEST_ASSERT_TRUE(dmenv_set("VAR", "value1")); + TEST_ASSERT_TRUE(dmenv_set("VAR", "value2")); + const char* value = dmenv_get("VAR"); + TEST_ASSERT_EQUAL_STRING("value2", value); +} + +void test_get_nonexistent(void) { + const char* value = dmenv_get("NONEXISTENT"); + TEST_ASSERT_NULL(value); +} + +void test_set_null_name(void) { + TEST_ASSERT_FALSE(dmenv_set(NULL, "value")); +} + +void test_set_null_value(void) { + TEST_ASSERT_FALSE(dmenv_set("VAR", NULL)); +} + +void test_remove_existing(void) { + TEST_ASSERT_TRUE(dmenv_set("TEMP", "temp_value")); + TEST_ASSERT_TRUE(dmenv_remove("TEMP")); + TEST_ASSERT_NULL(dmenv_get("TEMP")); +} + +void test_remove_nonexistent(void) { + TEST_ASSERT_FALSE(dmenv_remove("NONEXISTENT")); +} + +void test_clear(void) { + dmenv_set("VAR1", "value1"); + dmenv_set("VAR2", "value2"); + dmenv_set("VAR3", "value3"); + + TEST_ASSERT_TRUE(dmenv_clear()); + TEST_ASSERT_EQUAL_size_t(0, dmenv_count()); +} + +void test_count(void) { + TEST_ASSERT_EQUAL_size_t(0, dmenv_count()); + + dmenv_set("VAR1", "value1"); + TEST_ASSERT_EQUAL_size_t(1, dmenv_count()); + + dmenv_set("VAR2", "value2"); + TEST_ASSERT_EQUAL_size_t(2, dmenv_count()); + + dmenv_remove("VAR1"); + TEST_ASSERT_EQUAL_size_t(1, dmenv_count()); +} + +typedef struct { + size_t count; + char names[10][64]; +} find_test_context_t; + +void find_test_callback(const char* name, const char* value, void* user_data) { + find_test_context_t* ctx = (find_test_context_t*)user_data; + if (ctx->count < 10) { + strncpy(ctx->names[ctx->count], name, 63); + ctx->names[ctx->count][63] = '\0'; + ctx->count++; + } +} + +void test_find_with_prefix(void) { + find_test_context_t ctx = {0}; + + dmenv_set("PREFIX_VAR1", "value1"); + dmenv_set("PREFIX_VAR2", "value2"); + dmenv_set("OTHER_VAR", "value3"); + + size_t found = dmenv_find("PREFIX_", find_test_callback, &ctx); + TEST_ASSERT_EQUAL_size_t(2, found); + TEST_ASSERT_EQUAL_size_t(2, ctx.count); +} + +void test_find_no_matches(void) { + find_test_context_t ctx = {0}; + + dmenv_set("VAR1", "value1"); + dmenv_set("VAR2", "value2"); + + size_t found = dmenv_find("NOMATCH_", find_test_callback, &ctx); + TEST_ASSERT_EQUAL_size_t(0, found); +} + +void test_multiple_variables(void) { + // Test setting multiple variables + for (int i = 0; i < 10; i++) { + char name[32]; + char value[32]; + snprintf(name, sizeof(name), "VAR_%d", i); + snprintf(value, sizeof(value), "value_%d", i); + TEST_ASSERT_TRUE(dmenv_set(name, value)); + } + + TEST_ASSERT_EQUAL_size_t(10, dmenv_count()); + + // Verify all values + for (int i = 0; i < 10; i++) { + char name[32]; + char expected[32]; + snprintf(name, sizeof(name), "VAR_%d", i); + snprintf(expected, sizeof(expected), "value_%d", i); + + const char* value = dmenv_get(name); + TEST_ASSERT_NOT_NULL(value); + TEST_ASSERT_EQUAL_STRING(expected, value); + } +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_init_success); + RUN_TEST(test_init_invalid_buffer); + RUN_TEST(test_init_too_small); + RUN_TEST(test_set_and_get); + RUN_TEST(test_set_update_existing); + RUN_TEST(test_get_nonexistent); + RUN_TEST(test_set_null_name); + RUN_TEST(test_set_null_value); + RUN_TEST(test_remove_existing); + RUN_TEST(test_remove_nonexistent); + RUN_TEST(test_clear); + RUN_TEST(test_count); + RUN_TEST(test_find_with_prefix); + RUN_TEST(test_find_no_matches); + RUN_TEST(test_multiple_variables); + + return UNITY_END(); +} diff --git a/tests/test_minimal.c b/tests/test_minimal.c new file mode 100644 index 0000000..83d94fc --- /dev/null +++ b/tests/test_minimal.c @@ -0,0 +1,64 @@ +#include "test_common.h" +#include "dmenv.h" +#include +#include + +static char test_buffer[TEST_BUFFER_SIZE]; + +int main(void) { + printf("=== Minimal DMENV Test ===\n"); + + // Test initialization + if (!dmenv_init(test_buffer, TEST_BUFFER_SIZE)) { + printf("Init: FAIL\n"); + return 1; + } + printf("Init: PASS\n"); + + // Test is_initialized + if (!dmenv_is_initialized()) { + printf("Is Initialized: FAIL\n"); + return 1; + } + printf("Is Initialized: PASS\n"); + + // Test set + if (!dmenv_set("TEST", "value")) { + printf("Set: FAIL\n"); + return 1; + } + printf("Set: PASS\n"); + + // Test get + const char* value = dmenv_get("TEST"); + if (value == NULL || strcmp(value, "value") != 0) { + printf("Get: FAIL\n"); + return 1; + } + printf("Get: PASS\n"); + + // Test count + if (dmenv_count() != 1) { + printf("Count: FAIL (expected 1, got %zu)\n", dmenv_count()); + return 1; + } + printf("Count: PASS\n"); + + // Test remove + if (!dmenv_remove("TEST")) { + printf("Remove: FAIL\n"); + return 1; + } + printf("Remove: PASS\n"); + + // Test count after remove + if (dmenv_count() != 0) { + printf("Count After Remove: FAIL (expected 0, got %zu)\n", dmenv_count()); + return 1; + } + printf("Count After Remove: PASS\n"); + + printf("\nAll minimal tests passed!\n"); + + return 0; +} diff --git a/tests/test_simple.c b/tests/test_simple.c new file mode 100644 index 0000000..b96716c --- /dev/null +++ b/tests/test_simple.c @@ -0,0 +1,110 @@ +#include "test_common.h" +#include "dmenv.h" +#include +#include + +static char test_buffer[TEST_BUFFER_SIZE]; + +bool test_init(void) { + bool result = dmenv_init(test_buffer, TEST_BUFFER_SIZE); + TEST_ASSERT(result, "Init should succeed"); + TEST_ASSERT(dmenv_is_initialized(), "Should be initialized"); + return true; +} + +bool test_set_get(void) { + const char* value; + + // Set a variable + TEST_ASSERT(dmenv_set("TEST_VAR", "test_value"), "Set should succeed"); + + // Get the variable + value = dmenv_get("TEST_VAR"); + TEST_ASSERT(value != NULL, "Get should return a value"); + TEST_ASSERT(strcmp(value, "test_value") == 0, "Value should match"); + + return true; +} + +bool test_remove(void) { + // Set a variable + TEST_ASSERT(dmenv_set("REMOVE_ME", "temporary"), "Set should succeed"); + + // Verify it exists + TEST_ASSERT(dmenv_get("REMOVE_ME") != NULL, "Variable should exist"); + + // Remove it + TEST_ASSERT(dmenv_remove("REMOVE_ME"), "Remove should succeed"); + + // Verify it's gone + TEST_ASSERT(dmenv_get("REMOVE_ME") == NULL, "Variable should not exist"); + + return true; +} + +bool test_clear(void) { + // Set some variables + dmenv_set("VAR1", "value1"); + dmenv_set("VAR2", "value2"); + dmenv_set("VAR3", "value3"); + + TEST_ASSERT(dmenv_count() >= 3, "Should have at least 3 variables"); + + // Clear all + TEST_ASSERT(dmenv_clear(), "Clear should succeed"); + + // Verify count is 0 + TEST_ASSERT(dmenv_count() == 0, "Count should be 0 after clear"); + + return true; +} + +typedef struct { + size_t count; +} find_context_t; + +void find_callback(const char* name, const char* value, void* user_data) { + find_context_t* ctx = (find_context_t*)user_data; + ctx->count++; + printf(" Found: %s = %s\n", name, value); +} + +bool test_find(void) { + find_context_t ctx = {0}; + + // Set variables with different prefixes + dmenv_set("APP_NAME", "myapp"); + dmenv_set("APP_VERSION", "1.0"); + dmenv_set("APP_DEBUG", "true"); + dmenv_set("SYS_PATH", "/usr/bin"); + dmenv_set("SYS_USER", "admin"); + + // Find all APP_ variables + size_t found = dmenv_find("APP_", find_callback, &ctx); + TEST_ASSERT(found == 3, "Should find 3 APP_ variables"); + TEST_ASSERT(ctx.count == 3, "Callback should be called 3 times"); + + // Reset context + ctx.count = 0; + + // Find all SYS_ variables + found = dmenv_find("SYS_", find_callback, &ctx); + TEST_ASSERT(found == 2, "Should find 2 SYS_ variables"); + TEST_ASSERT(ctx.count == 2, "Callback should be called 2 times"); + + return true; +} + +int main(void) { + printf("=== DMENV Simple Test ===\n\n"); + + TEST_PRINT_RESULT("Init", test_init()); + TEST_PRINT_RESULT("Set/Get", test_set_get()); + TEST_PRINT_RESULT("Remove", test_remove()); + TEST_PRINT_RESULT("Clear", test_clear()); + TEST_PRINT_RESULT("Find", test_find()); + + printf("\nAll simple tests completed!\n"); + + return 0; +} From e9bb781d40bdf59c5ea04fe6a49c3c285606845a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:32:01 +0000 Subject: [PATCH 03/11] Fix DMOD API implementation and build issues Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- build/libdmenv.a | Bin 0 -> 9520 bytes build/tests/test_dmenv_unit | Bin 0 -> 41856 bytes build/tests/test_minimal | Bin 0 -> 21272 bytes build/tests/test_simple | Bin 0 -> 21488 bytes src/dmenv.c | 90 ++++++++++++++++++------------------ tests/CMakeLists.txt | 12 +++-- 6 files changed, 53 insertions(+), 49 deletions(-) create mode 100644 build/libdmenv.a create mode 100755 build/tests/test_dmenv_unit create mode 100755 build/tests/test_minimal create mode 100755 build/tests/test_simple diff --git a/build/libdmenv.a b/build/libdmenv.a new file mode 100644 index 0000000000000000000000000000000000000000..161211e89ac03af9310ce3f5d1ff4893182a7689 GIT binary patch literal 9520 zcmb_geQXrh5#O`v6*cJuLi-U&+=Y$i1{@~rO)3+VV z44mt3NToJ35}9nSP;usd6oCX?g8c~g)6NSwlc-O1qApn;Ua1!=&e!XxKibME1D|N; zhTW7v-6n96Oga!Z>|33E^{OOwz#;X_<4eSPC;6&iPtc>Dj#LM z*DMX`nuUz=HCs{URL0d^WOAokLfy`>Vx_gIJ-&H*dYZeUlG+%(UeenYK7Bl<*Drqu z6=<2qmvVR2V(i5mW{G?B3^JFVLAu_?A{J%I&NZvQzB;+HwWDKON0 zqrwkNUoX4|=)?sq)}L{E`wH1;GVrqus7v|~f<{3x=zz@&e7L-ua1SgO~d zUQkN%Q`UZ2zg70tD^4_l^%QZo;JXgw9ejq)F18NW0m{3jrDeUnyt5l#gmUn97G4u> zT)8%#6N#N?a%K*9OMK1^XD)6^9KwARi}LaH_-fDa=uIZk5D;5m08v&_}Du`s>{BKVy&=ZrSb(h4GFwaOjoOF z3jl_9z^_W!S2YD}IpKV+Y6;jH_+j6eg3rq3@U{SaE8vS^-9?lu)eC?<9>7lo;D^c1 z-^h;Y8Ni+;{2Jk^R{(nne&zgpH30t^**Qw7Ry_sS8-%~2o(ZUz0Q)uJg*h+)Q9T65 zcj1Teo`7ovMfDCGFT)S<6<9bB)iXFyx0rCu>f5cqm&DR3&mHtEzRT^_cR2hyLc`6; zbkFYXnRv?UwUTZUzolfoOe&V_r>`XH>x*Kwf~`zT25xk)4O$u+jC(%vKR(fb*@T!Unv;OG+y-eXWWpSS2! z>v;peo$wzTxJ~#k1de@jknr<_n|b)X!RI_&4&Z+z@C9I-+v2LE4VKN-OH3_jaG7Qp|Wz;R#YJp9zK!}rWHf-n2< zO@q(tz7xRzi@@c&Q-+<*RFB#8U}NTUKH;c_`R8@-2;eUlxa|L(0>^t0=V8#W&)3Ou z!Iyb>#^AF*F9h(vFL2q1zYM@X61eoUnl4N{Nc=X!F+Zrv`_DZAc$0zGgAU5Z0DPOk zG5;@8y>=0f2brIk!RP$+2k>(Om-%^C*ul8?dVEFT@_PKaup|9>-Qcr7Zw2r#8~isZ z-oFO$|8DTv{<=@!JkFf=TiZE z{b6P1{rXe@|D3Qd>-al?fL}cqjDnz9 zS+7L`$91`0O9?mg*l7K;W`2$AleOm!Al}tjlNs z|B8Y0zWPZ3J}q#}=W|rY`B*#f%AL%n1S!rA>vFWL=9FQbop zd6aPz(Xe`155wous1@$cX0320txsa=qp&SHJ|?H1%I5{YF-#qivk8hI6zjdlV_GZySc$f8?O7 zgCFXf>x|&V4}$(lVgLE~08H{&@~)D;#BgkKI{?G2-Sode`Zml-G5!A*OrlLwzmDqm zn6aR#k6l=<{*gQNss(*e^~aL1KG(n8+NiR3p;lZ@j$t^cYwDv-qeM#aBt9g8T+s*r V-v~#&+mzF9w6%)oYwCfi|6e82Vu1hv literal 0 HcmV?d00001 diff --git a/build/tests/test_dmenv_unit b/build/tests/test_dmenv_unit new file mode 100755 index 0000000000000000000000000000000000000000..ece188e830ff4c518a7a970ac4090cf5852561ea GIT binary patch literal 41856 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 literal 0 HcmV?d00001 diff --git a/build/tests/test_minimal b/build/tests/test_minimal new file mode 100755 index 0000000000000000000000000000000000000000..a1498ff92f7fc9a5c64ea984e57735db4e7378d6 GIT binary patch literal 21272 zcmeHPeRP!7nZF5)1dAjnC?6`XTGXsC1PCbF$OM@1VhMzgATEo;keMV?=d1Gp!4(X~ zRK}2VuokOUcUjxh>T1uyE_hhasYXdJNI_)vbxfe62WDJxL#0e?>L2cG-KV@bp^yDt`X

r! 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 literal 0 HcmV?d00001 diff --git a/build/tests/test_simple b/build/tests/test_simple new file mode 100755 index 0000000000000000000000000000000000000000..0f847ba74637d2d07d39701629b93e1a37277497 GIT binary patch literal 21488 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 g_dmenv_context.buffer_size) { - DMOD_LOG_ERROR("dmenv", "Buffer full, cannot allocate new entry"); + DMOD_LOG_ERROR("Buffer full, cannot allocate new entry"); return NULL; } @@ -70,14 +70,14 @@ static env_entry_t* find_entry(const char* name) { // DMOD API Implementations // ============================================================================ -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _init, ( void* buffer, size_t size ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _init, ( void* buffer, size_t size ) ) { if (!buffer || size < sizeof(env_entry_t)) { - DMOD_LOG_ERROR("dmenv", "Invalid buffer or size for initialization"); + DMOD_LOG_ERROR("Invalid buffer or size for initialization"); return false; } - DMOD_ENTER_CRITICAL_SECTION(); + Dmod_EnterCritical(); g_dmenv_context.buffer = buffer; g_dmenv_context.buffer_size = size; @@ -86,41 +86,41 @@ DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _init, ( void* buffer, size_t si g_dmenv_context.entry_count = 0; g_dmenv_context.used_size = 0; - DMOD_LOG_INFO("dmenv", "Initialized with buffer %p of size %zu", buffer, size); + DMOD_LOG_INFO("dmenv: Initialized with buffer %p of size %zu", buffer, size); - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); return true; } -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _is_initialized, ( void ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _is_initialized, ( void ) ) { return g_dmenv_context.initialized; } -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const char* value ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const char* value ) ) { if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + DMOD_LOG_ERROR("Environment manager not initialized"); return false; } if (!name || !value) { - DMOD_LOG_ERROR("dmenv", "Invalid name or value"); + DMOD_LOG_ERROR("Invalid name or value"); return false; } if (strlen(name) >= DMENV_MAX_NAME_LENGTH) { - DMOD_LOG_ERROR("dmenv", "Name too long: %s", name); + DMOD_LOG_ERROR("Name too long: %s", name); return false; } if (strlen(value) >= DMENV_MAX_VALUE_LENGTH) { - DMOD_LOG_ERROR("dmenv", "Value too long for name: %s", name); + DMOD_LOG_ERROR("Value too long for name: %s", name); return false; } - DMOD_ENTER_CRITICAL_SECTION(); + Dmod_EnterCritical(); // Check if variable already exists env_entry_t* existing = find_entry(name); @@ -128,15 +128,15 @@ DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const // Update existing entry strncpy(existing->value, value, DMENV_MAX_VALUE_LENGTH - 1); existing->value[DMENV_MAX_VALUE_LENGTH - 1] = '\0'; - DMOD_LOG_INFO("dmenv", "Updated variable %s = %s", name, value); - DMOD_EXIT_CRITICAL_SECTION(); + DMOD_LOG_INFO("Updated variable %s = %s", name, value); + Dmod_ExitCritical(); return true; } // Create new entry env_entry_t* entry = allocate_entry(); if (entry == NULL) { - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); return false; } @@ -150,56 +150,56 @@ DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const g_dmenv_context.head = entry; g_dmenv_context.entry_count++; - DMOD_LOG_INFO("dmenv", "Set variable %s = %s", name, value); + DMOD_LOG_INFO("Set variable %s = %s", name, value); - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); return true; } -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, const char*, _get, ( const char* name ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( const char* name ) ) { if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + DMOD_LOG_ERROR("Environment manager not initialized"); return NULL; } if (!name) { - DMOD_LOG_ERROR("dmenv", "Invalid name"); + DMOD_LOG_ERROR("Invalid name"); return NULL; } - DMOD_ENTER_CRITICAL_SECTION(); + Dmod_EnterCritical(); env_entry_t* entry = find_entry(name); - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); if (entry != NULL) { - DMOD_LOG_DEBUG("dmenv", "Get variable %s = %s", name, entry->value); + DMOD_LOG_INFO("Get variable %s = %s", name, entry->value); return entry->value; } - DMOD_LOG_DEBUG("dmenv", "Variable %s not found", name); + DMOD_LOG_INFO("Variable %s not found", name); return NULL; } -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, size_t, _find, ( const char* prefix, void (*callback)(const char* name, const char* value, void* user_data), void* user_data ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _find, ( const char* prefix, void (*callback)(const char* name, const char* value, void* user_data), void* user_data ) ) { if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + DMOD_LOG_ERROR("Environment manager not initialized"); return 0; } if (!prefix || !callback) { - DMOD_LOG_ERROR("dmenv", "Invalid prefix or callback"); + DMOD_LOG_ERROR("Invalid prefix or callback"); return 0; } size_t count = 0; size_t prefix_len = strlen(prefix); - DMOD_ENTER_CRITICAL_SECTION(); + Dmod_EnterCritical(); env_entry_t* current = g_dmenv_context.head; while (current != NULL) { @@ -210,26 +210,26 @@ DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, size_t, _find, ( const char* prefix, v current = current->next; } - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); - DMOD_LOG_DEBUG("dmenv", "Found %zu variables with prefix '%s'", count, prefix); + DMOD_LOG_INFO("Found %zu variables with prefix '%s'", count, prefix); return count; } -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) { if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + DMOD_LOG_ERROR("Environment manager not initialized"); return false; } if (!name) { - DMOD_LOG_ERROR("dmenv", "Invalid name"); + DMOD_LOG_ERROR("Invalid name"); return false; } - DMOD_ENTER_CRITICAL_SECTION(); + Dmod_EnterCritical(); env_entry_t* current = g_dmenv_context.head; env_entry_t* prev = NULL; @@ -247,42 +247,42 @@ DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) g_dmenv_context.entry_count--; - DMOD_LOG_INFO("dmenv", "Removed variable %s", name); + DMOD_LOG_INFO("Removed variable %s", name); - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); return true; } prev = current; current = current->next; } - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); - DMOD_LOG_DEBUG("dmenv", "Variable %s not found for removal", name); + DMOD_LOG_INFO("Variable %s not found for removal", name); return false; } -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, bool, _clear, ( void ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _clear, ( void ) ) { if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("dmenv", "Environment manager not initialized"); + DMOD_LOG_ERROR("Environment manager not initialized"); return false; } - DMOD_ENTER_CRITICAL_SECTION(); + Dmod_EnterCritical(); g_dmenv_context.head = NULL; g_dmenv_context.entry_count = 0; g_dmenv_context.used_size = 0; - DMOD_LOG_INFO("dmenv", "Cleared all environment variables"); + DMOD_LOG_INFO("Cleared all environment variables"); - DMOD_EXIT_CRITICAL_SECTION(); + Dmod_ExitCritical(); return true; } -DMOD_BUILTIN_API_DECLARATION( dmenv, 1.0, size_t, _count, ( void ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _count, ( void ) ) { if (!g_dmenv_context.initialized) { return 0; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aa9c467..3a85b5d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,19 +16,23 @@ endif() # Common test utilities add_library(test_common INTERFACE) -target_include_directories(test_common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(test_common INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) +target_link_libraries(test_common INTERFACE dmod_inc) # Test: Simple test add_executable(test_simple test_simple.c) -target_link_libraries(test_simple PRIVATE dmenv unity test_common) +target_link_libraries(test_simple PRIVATE dmenv unity test_common dmod_system) add_test(NAME test_simple COMMAND test_simple) # Test: Unit tests add_executable(test_dmenv_unit test_dmenv_unit.c) -target_link_libraries(test_dmenv_unit PRIVATE dmenv unity test_common) +target_link_libraries(test_dmenv_unit PRIVATE dmenv unity test_common dmod_system) add_test(NAME test_dmenv_unit COMMAND test_dmenv_unit) # Test: Minimal test add_executable(test_minimal test_minimal.c) -target_link_libraries(test_minimal PRIVATE dmenv unity test_common) +target_link_libraries(test_minimal PRIVATE dmenv unity test_common dmod_system) add_test(NAME test_minimal COMMAND test_minimal) From ed8f9106eec8fdaf7178c4b27ff44086df2b21fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:32:53 +0000 Subject: [PATCH 04/11] Update .gitignore to exclude build directory Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- .gitignore | 1 + build/libdmenv.a | Bin 9520 -> 0 bytes build/tests/test_dmenv_unit | Bin 41856 -> 0 bytes build/tests/test_minimal | Bin 21272 -> 0 bytes build/tests/test_simple | Bin 21488 -> 0 bytes 5 files changed, 1 insertion(+) delete mode 100644 build/libdmenv.a delete mode 100755 build/tests/test_dmenv_unit delete mode 100755 build/tests/test_minimal delete mode 100755 build/tests/test_simple diff --git a/.gitignore b/.gitignore index 1f99f9d..6fdb7cd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ compile_commands.json CTestTestfile.cmake _deps CMakeUserPresets.json +build/ # CLion # JetBrains specific template is maintained in a separate JetBrains.gitignore that can diff --git a/build/libdmenv.a b/build/libdmenv.a deleted file mode 100644 index 161211e89ac03af9310ce3f5d1ff4893182a7689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9520 zcmb_geQXrh5#O`v6*cJuLi-U&+=Y$i1{@~rO)3+VV z44mt3NToJ35}9nSP;usd6oCX?g8c~g)6NSwlc-O1qApn;Ua1!=&e!XxKibME1D|N; zhTW7v-6n96Oga!Z>|33E^{OOwz#;X_<4eSPC;6&iPtc>Dj#LM z*DMX`nuUz=HCs{URL0d^WOAokLfy`>Vx_gIJ-&H*dYZeUlG+%(UeenYK7Bl<*Drqu z6=<2qmvVR2V(i5mW{G?B3^JFVLAu_?A{J%I&NZvQzB;+HwWDKON0 zqrwkNUoX4|=)?sq)}L{E`wH1;GVrqus7v|~f<{3x=zz@&e7L-ua1SgO~d zUQkN%Q`UZ2zg70tD^4_l^%QZo;JXgw9ejq)F18NW0m{3jrDeUnyt5l#gmUn97G4u> zT)8%#6N#N?a%K*9OMK1^XD)6^9KwARi}LaH_-fDa=uIZk5D;5m08v&_}Du`s>{BKVy&=ZrSb(h4GFwaOjoOF z3jl_9z^_W!S2YD}IpKV+Y6;jH_+j6eg3rq3@U{SaE8vS^-9?lu)eC?<9>7lo;D^c1 z-^h;Y8Ni+;{2Jk^R{(nne&zgpH30t^**Qw7Ry_sS8-%~2o(ZUz0Q)uJg*h+)Q9T65 zcj1Teo`7ovMfDCGFT)S<6<9bB)iXFyx0rCu>f5cqm&DR3&mHtEzRT^_cR2hyLc`6; zbkFYXnRv?UwUTZUzolfoOe&V_r>`XH>x*Kwf~`zT25xk)4O$u+jC(%vKR(fb*@T!Unv;OG+y-eXWWpSS2! z>v;peo$wzTxJ~#k1de@jknr<_n|b)X!RI_&4&Z+z@C9I-+v2LE4VKN-OH3_jaG7Qp|Wz;R#YJp9zK!}rWHf-n2< zO@q(tz7xRzi@@c&Q-+<*RFB#8U}NTUKH;c_`R8@-2;eUlxa|L(0>^t0=V8#W&)3Ou z!Iyb>#^AF*F9h(vFL2q1zYM@X61eoUnl4N{Nc=X!F+Zrv`_DZAc$0zGgAU5Z0DPOk zG5;@8y>=0f2brIk!RP$+2k>(Om-%^C*ul8?dVEFT@_PKaup|9>-Qcr7Zw2r#8~isZ z-oFO$|8DTv{<=@!JkFf=TiZE z{b6P1{rXe@|D3Qd>-al?fL}cqjDnz9 zS+7L`$91`0O9?mg*l7K;W`2$AleOm!Al}tjlNs z|B8Y0zWPZ3J}q#}=W|rY`B*#f%AL%n1S!rA>vFWL=9FQbop zd6aPz(Xe`155wous1@$cX0320txsa=qp&SHJ|?H1%I5{YF-#qivk8hI6zjdlV_GZySc$f8?O7 zgCFXf>x|&V4}$(lVgLE~08H{&@~)D;#BgkKI{?G2-Sode`Zml-G5!A*OrlLwzmDqm zn6aR#k6l=<{*gQNss(*e^~aL1KG(n8+NiR3p;lZ@j$t^cYwDv-qeM#aBt9g8T+s*r V-v~#&+mzF9w6%)oYwCfi|6e82Vu1hv diff --git a/build/tests/test_dmenv_unit b/build/tests/test_dmenv_unit deleted file mode 100755 index ece188e830ff4c518a7a970ac4090cf5852561ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41856 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 diff --git a/build/tests/test_minimal b/build/tests/test_minimal deleted file mode 100755 index a1498ff92f7fc9a5c64ea984e57735db4e7378d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21272 zcmeHPeRP!7nZF5)1dAjnC?6`XTGXsC1PCbF$OM@1VhMzgATEo;keMV?=d1Gp!4(X~ zRK}2VuokOUcUjxh>T1uyE_hhasYXdJNI_)vbxfe62WDJxL#0e?>L2cG-KV@bp^yDt`X

r! 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 diff --git a/build/tests/test_simple b/build/tests/test_simple deleted file mode 100755 index 0f847ba74637d2d07d39701629b93e1a37277497..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21488 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 Date: Fri, 7 Nov 2025 09:35:56 +0000 Subject: [PATCH 05/11] Fix code review issues: thread safety and string comparison Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- include/dmenv.h | 3 +++ src/dmenv.c | 10 +++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/dmenv.h b/include/dmenv.h index 20935f7..85629af 100644 --- a/include/dmenv.h +++ b/include/dmenv.h @@ -46,6 +46,9 @@ DMOD_BUILTIN_API( dmenv, 1.0, const char* , _get, ( const char* name ) ); * * @param prefix Prefix to match against variable names. * @param callback Callback function to call for each matching variable. + * WARNING: The callback is executed while holding the internal + * lock. Do not call other dmenv functions from within the callback + * as this will cause a deadlock. Keep callback execution short. * @param user_data User data to pass to the callback. * * @return Number of matching variables found. diff --git a/src/dmenv.c b/src/dmenv.c index 5150823..f220ded 100644 --- a/src/dmenv.c +++ b/src/dmenv.c @@ -58,7 +58,7 @@ static env_entry_t* find_entry(const char* name) { env_entry_t* current = g_dmenv_context.head; while (current != NULL) { - if (strncmp(current->name, name, DMENV_MAX_NAME_LENGTH) == 0) { + if (strcmp(current->name, name) == 0) { return current; } current = current->next; @@ -235,7 +235,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) env_entry_t* prev = NULL; while (current != NULL) { - if (strncmp(current->name, name, DMENV_MAX_NAME_LENGTH) == 0) { + if (strcmp(current->name, name) == 0) { // Found the entry to remove if (prev == NULL) { // Removing the head @@ -288,5 +288,9 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _count, ( void ) ) return 0; } - return g_dmenv_context.entry_count; + Dmod_EnterCritical(); + size_t count = g_dmenv_context.entry_count; + Dmod_ExitCritical(); + + return count; } From db0e14ac3fd42a6a59ecdcc3f132152331b2b90e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:37:17 +0000 Subject: [PATCH 06/11] Complete dmenv implementation with security verification Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- _codeql_build_dir/libdmenv.a | Bin 0 -> 6272 bytes _codeql_detected_source_root | 1 + 2 files changed, 1 insertion(+) create mode 100644 _codeql_build_dir/libdmenv.a create mode 120000 _codeql_detected_source_root diff --git a/_codeql_build_dir/libdmenv.a b/_codeql_build_dir/libdmenv.a new file mode 100644 index 0000000000000000000000000000000000000000..4da42ac80ed071fa1d933bfb31169fe5ef9f86d4 GIT binary patch literal 6272 zcmbtYeQcA}8b9rZ^ltceqQ)urLQRMnQA!I7nMS4ER(KnyQE-6F-BG%hRkwDwZwX)F z0`1_wfhkcFV^=2`iD=_ zo^yWZIluFKozt^jd3`J!AGon$fp4k=*wE?oZ=Wx?XtB~*+aQGSz~}8h`t%Pa(i{3x z>69f@Hz!A_*kEdNqF;fz1Wm6m_mZhJO=lBBnGFf08y`%>veeJy(`uzZ?$0P^YWc9Z zwN=`_I1p%aoLJlNuSnNYx61`CIN}mFipq#kAto-a^h2^65{9@?ob%59se6T4_|{{N zOjOr4@(kI56N-3JAAuiW2g79+&5A@<1M}f z>ZbiG*zGMo1O2G|e{Nl|&<@{1@ivme9+{*JXbx!8Zy^(9x+bDoc)e zI_=Xvz2Q5<7>1Ch6bYUgA5;4|HjEZb-1b@-3GdFwk+;s7vLyx?0dq#Tcuf04^F$rY ziLuG%+ZdJtiA0R zlNw602+Q@hD!j5%bLA3w4nwNy4f7GTe~|U|uIOW}Wp`)EC2O^5A7y^oubX>h$&9?0|K#S8&#kpl zyEs=KB+8FjkSAHYDfl7T4*P5Qf^*_n0+$L`4MAdLsg6I&yrX=s@E(>Yr#NP{$>n&X zl>>AB9VT_;mwmDauO_?)$ZieC_^k+zQQ2N3r&aAbedIt-s>`gNz>pBjIy$cNwREq8 zUxPgSMEMu_1M?T><#6G)g{}TT>pTS$OFeX&;DcT-yQN#Wg(x8=F9K*Ufe(sn^K#+Z z*yNfuW4dP_Tmu2X{w`;P*o|$(Xxilt8_k8fPQ&N!X)v0X8ckut)7iM(Xzq|*+~-!c zW`=6JpzT-v|KNQCt47`JM)Tgfu;JTPA2wPF(>jfS`;`Wx1(;!ncB84?@I)Hj$291s zM#ruf6yJVqr`k0Db^!u*nT@N+E)&#bjG_Gu*MJT8p$+%ewHvkIC! z4Tfh~<86jV&OfH+uPV>4g_%DVTv9L2;?K@`QdjktA8P_DW2*cOhAxI5gHJGL?OV>plJmKr=enLg)vfazmA=fVf&poUYOJgebk|BD(0>zO z*~)m(PC74pnLe+#`)la$*KqRpDC6Pv_BO-0J^y7q+^>(AKKJV^!#U4H4W3D+&w1ve z^B_3Q<8X_@F)#d_u4FimPp`&9aoEiCF9I&A&jSqS_CLvZcpRQ(`rQ7{YUrP-p+6HH z0wI6tzG_i8Hr%fTnm+lpoayuY=9oUWe=Eb$G^+odjEDQRo8de@2W#Mm7(d_ld8W_# z-(vb`5BYUk!zn&z84r)oFHE2BI}HaQ6lePVnxWxn6UwCuM>=lj6%6NbxSH``m+ERE z)8{-yT2V^&P~*X44;pUvO^ z)r0<^@W(Sl@UKO9*QGO7!ViDK&Cgk}_}yx}KArauXEVczthEW2B4J{nFPV)EC6r@- zvGKurbnOC-C#1$nl{cE!-@JGpF+?F!Q0*c7uPR8BDpdSi2=`G#(TtNI_})caT{{R# zc|zNlaFqrciff>Uocj8bS|2EDIvt-j4#F5Y7iu`I*Ug~)sST<1`aZ|iLbQ(yt)n&y zlho&WXZ4NMq^^4h;CTM{*+JC_wcdvVCKP{y?tu<+>g$_TL6o!&_4R|Wwp#v=%yf1r zIgP;lcgbbVCt literal 0 HcmV?d00001 diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000..945c9b4 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file From 14d33c15e083b897cf1ff166ca08938805725602 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:43:19 +0000 Subject: [PATCH 07/11] Remove CodeQL artifacts and update .gitignore Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- .gitignore | 2 ++ _codeql_build_dir/libdmenv.a | Bin 6272 -> 0 bytes _codeql_detected_source_root | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 _codeql_build_dir/libdmenv.a delete mode 120000 _codeql_detected_source_root diff --git a/.gitignore b/.gitignore index 6fdb7cd..6b11200 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ CTestTestfile.cmake _deps CMakeUserPresets.json build/ +_codeql_build_dir/ +_codeql_detected_source_root # CLion # JetBrains specific template is maintained in a separate JetBrains.gitignore that can diff --git a/_codeql_build_dir/libdmenv.a b/_codeql_build_dir/libdmenv.a deleted file mode 100644 index 4da42ac80ed071fa1d933bfb31169fe5ef9f86d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6272 zcmbtYeQcA}8b9rZ^ltceqQ)urLQRMnQA!I7nMS4ER(KnyQE-6F-BG%hRkwDwZwX)F z0`1_wfhkcFV^=2`iD=_ zo^yWZIluFKozt^jd3`J!AGon$fp4k=*wE?oZ=Wx?XtB~*+aQGSz~}8h`t%Pa(i{3x z>69f@Hz!A_*kEdNqF;fz1Wm6m_mZhJO=lBBnGFf08y`%>veeJy(`uzZ?$0P^YWc9Z zwN=`_I1p%aoLJlNuSnNYx61`CIN}mFipq#kAto-a^h2^65{9@?ob%59se6T4_|{{N zOjOr4@(kI56N-3JAAuiW2g79+&5A@<1M}f z>ZbiG*zGMo1O2G|e{Nl|&<@{1@ivme9+{*JXbx!8Zy^(9x+bDoc)e zI_=Xvz2Q5<7>1Ch6bYUgA5;4|HjEZb-1b@-3GdFwk+;s7vLyx?0dq#Tcuf04^F$rY ziLuG%+ZdJtiA0R zlNw602+Q@hD!j5%bLA3w4nwNy4f7GTe~|U|uIOW}Wp`)EC2O^5A7y^oubX>h$&9?0|K#S8&#kpl zyEs=KB+8FjkSAHYDfl7T4*P5Qf^*_n0+$L`4MAdLsg6I&yrX=s@E(>Yr#NP{$>n&X zl>>AB9VT_;mwmDauO_?)$ZieC_^k+zQQ2N3r&aAbedIt-s>`gNz>pBjIy$cNwREq8 zUxPgSMEMu_1M?T><#6G)g{}TT>pTS$OFeX&;DcT-yQN#Wg(x8=F9K*Ufe(sn^K#+Z z*yNfuW4dP_Tmu2X{w`;P*o|$(Xxilt8_k8fPQ&N!X)v0X8ckut)7iM(Xzq|*+~-!c zW`=6JpzT-v|KNQCt47`JM)Tgfu;JTPA2wPF(>jfS`;`Wx1(;!ncB84?@I)Hj$291s zM#ruf6yJVqr`k0Db^!u*nT@N+E)&#bjG_Gu*MJT8p$+%ewHvkIC! z4Tfh~<86jV&OfH+uPV>4g_%DVTv9L2;?K@`QdjktA8P_DW2*cOhAxI5gHJGL?OV>plJmKr=enLg)vfazmA=fVf&poUYOJgebk|BD(0>zO z*~)m(PC74pnLe+#`)la$*KqRpDC6Pv_BO-0J^y7q+^>(AKKJV^!#U4H4W3D+&w1ve z^B_3Q<8X_@F)#d_u4FimPp`&9aoEiCF9I&A&jSqS_CLvZcpRQ(`rQ7{YUrP-p+6HH z0wI6tzG_i8Hr%fTnm+lpoayuY=9oUWe=Eb$G^+odjEDQRo8de@2W#Mm7(d_ld8W_# z-(vb`5BYUk!zn&z84r)oFHE2BI}HaQ6lePVnxWxn6UwCuM>=lj6%6NbxSH``m+ERE z)8{-yT2V^&P~*X44;pUvO^ z)r0<^@W(Sl@UKO9*QGO7!ViDK&Cgk}_}yx}KArauXEVczthEW2B4J{nFPV)EC6r@- zvGKurbnOC-C#1$nl{cE!-@JGpF+?F!Q0*c7uPR8BDpdSi2=`G#(TtNI_})caT{{R# zc|zNlaFqrciff>Uocj8bS|2EDIvt-j4#F5Y7iu`I*Ug~)sST<1`aZ|iLbQ(yt)n&y zlho&WXZ4NMq^^4h;CTM{*+JC_wcdvVCKP{y?tu<+>g$_TL6o!&_4R|Wwp#v=%yf1r zIgP;lcgbbVCt diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b4..0000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From 43801982d94f9dd3565033b78f303495a9fb8a92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:48:17 +0000 Subject: [PATCH 08/11] Implement context-based API with parent inheritance and integer functions Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- .github/workflows/ci.yml | 107 ++++++++++++++ include/dmenv.h | 157 +++++++++++++++----- src/dmenv.c | 299 ++++++++++++++++++++++++++------------- tests/test_dmenv_unit.c | 181 +++++++++++++++++------- tests/test_minimal.c | 51 ++++--- tests/test_simple.c | 117 +++++++++++---- 6 files changed, 680 insertions(+), 232 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f2e43a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +name: CI + +on: + push: + branches: [ main, develop, copilot/*, feature/* ] + pull_request: + branches: [ main, develop, feature/* ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + 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 -DDMENV_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 + 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 -DDMENV_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/include/dmenv.h b/include/dmenv.h index 85629af..260e459 100644 --- a/include/dmenv.h +++ b/include/dmenv.h @@ -4,78 +4,163 @@ #include "dmod.h" #include #include +#include /** - * @brief Initialize the environment variables manager with a buffer. + * @brief Opaque context type for environment variables manager + */ +typedef struct dmenv_ctx* dmenv_ctx_t; + +/** + * @brief Callback function type for dmenv_find operation + * + * This callback is invoked for each environment variable that matches the + * search prefix. The callback is executed while holding an internal lock, + * so it should not call other dmenv functions and should complete quickly. + * + * @param name Name of the matched environment variable + * @param value Value of the matched environment variable + * @param user_data User-provided data pointer passed to dmenv_find + */ +typedef void (*dmenv_find_callback_t)(const char* name, const char* value, void* user_data); + +/** + * @brief Create a new environment variables context * - * @param buffer Pointer to the memory buffer to be used for environment variables. - * @param size Size of the memory buffer. + * @param parent Optional parent context for variable inheritance. If a variable + * is not found in the current context, it will be searched in the + * parent context. Pass NULL for no inheritance. + * + * @return Pointer to the created context, or NULL on failure + */ +DMOD_BUILTIN_API( dmenv, 1.0, dmenv_ctx_t, _create, ( dmenv_ctx_t parent ) ); + +/** + * @brief Destroy an environment variables context * - * @return true if initialization is successful, false otherwise. + * @param ctx Context to destroy */ -DMOD_BUILTIN_API( dmenv, 1.0, bool , _init, ( void* buffer, size_t size ) ); +DMOD_BUILTIN_API( dmenv, 1.0, void, _destroy, ( dmenv_ctx_t ctx ) ); /** - * @brief Check if the environment variables manager is initialized. + * @brief Check if a context is valid * - * @return true if initialized, false otherwise. + * @param ctx Context to check + * @return true if valid, false otherwise */ -DMOD_BUILTIN_API( dmenv, 1.0, bool , _is_initialized, ( void ) ); +DMOD_BUILTIN_API( dmenv, 1.0, bool, _is_valid, ( dmenv_ctx_t ctx ) ); /** - * @brief Set an environment variable. + * @brief Set the default context * - * @param name Name of the environment variable. - * @param value Value to set for the environment variable. + * @param ctx Context to set as default + */ +DMOD_BUILTIN_API( dmenv, 1.0, void, _set_as_default, ( dmenv_ctx_t ctx ) ); + +/** + * @brief Get the default context * - * @return true if the variable was set successfully, false otherwise. + * @return Pointer to the default context, or NULL if not set */ -DMOD_BUILTIN_API( dmenv, 1.0, bool , _set, ( const char* name, const char* value ) ); +DMOD_BUILTIN_API( dmenv, 1.0, dmenv_ctx_t, _get_default, ( void ) ); /** - * @brief Get an environment variable value. + * @brief Set an environment variable (string value) * - * @param name Name of the environment variable. + * @param ctx Context to set the variable in + * @param name Name of the environment variable + * @param value Value to set for the environment variable * - * @return Pointer to the value string, or NULL if not found. + * @return true if the variable was set successfully, false otherwise */ -DMOD_BUILTIN_API( dmenv, 1.0, const char* , _get, ( const char* name ) ); +DMOD_BUILTIN_API( dmenv, 1.0, bool, _set, ( dmenv_ctx_t ctx, const char* name, const char* value ) ); /** - * @brief Find environment variables matching a prefix. + * @brief Get an environment variable value (string) * - * @param prefix Prefix to match against variable names. - * @param callback Callback function to call for each matching variable. - * WARNING: The callback is executed while holding the internal - * lock. Do not call other dmenv functions from within the callback - * as this will cause a deadlock. Keep callback execution short. - * @param user_data User data to pass to the callback. + * If the variable is not found in the context and the context has a parent, + * the parent will be searched recursively. * - * @return Number of matching variables found. + * @param ctx Context to get the variable from + * @param name Name of the environment variable + * + * @return Pointer to the value string, or NULL if not found */ -DMOD_BUILTIN_API( dmenv, 1.0, size_t , _find, ( const char* prefix, void (*callback)(const char* name, const char* value, void* user_data), void* user_data ) ); +DMOD_BUILTIN_API( dmenv, 1.0, const char*, _get, ( dmenv_ctx_t ctx, const char* name ) ); /** - * @brief Remove an environment variable. + * @brief Set an environment variable (unsigned integer value in hex) + * + * The value is stored internally as a hexadecimal string (e.g., "0x2000"). * - * @param name Name of the environment variable to remove. + * @param ctx Context to set the variable in + * @param name Name of the environment variable + * @param value Unsigned integer value to set * - * @return true if the variable was removed successfully, false if not found. + * @return true if the variable was set successfully, false otherwise */ -DMOD_BUILTIN_API( dmenv, 1.0, bool , _remove, ( const char* name ) ); +DMOD_BUILTIN_API( dmenv, 1.0, bool, _seti, ( dmenv_ctx_t ctx, const char* name, uint32_t value ) ); /** - * @brief Clear all environment variables. + * @brief Get an environment variable value (unsigned integer) + * + * Parses the value as a hexadecimal or decimal number. If the variable is not + * found in the context and the context has a parent, the parent will be searched. * - * @return true if all variables were cleared successfully, false otherwise. + * @param ctx Context to get the variable from + * @param name Name of the environment variable + * @param out_value Pointer to store the parsed value + * + * @return true if the variable was found and parsed successfully, false otherwise */ -DMOD_BUILTIN_API( dmenv, 1.0, bool , _clear, ( void ) ); +DMOD_BUILTIN_API( dmenv, 1.0, bool, _geti, ( dmenv_ctx_t ctx, const char* name, uint32_t* out_value ) ); /** - * @brief Get the number of environment variables currently stored. + * @brief Find environment variables matching a prefix + * + * Only searches in the current context, not in parent contexts. + * + * @param ctx Context to search in + * @param prefix Prefix to match against variable names + * @param callback Callback function to call for each matching variable + * @param user_data User data to pass to the callback + * + * @return Number of matching variables found + */ +DMOD_BUILTIN_API( dmenv, 1.0, size_t, _find, ( dmenv_ctx_t ctx, const char* prefix, dmenv_find_callback_t callback, void* user_data ) ); + +/** + * @brief Remove an environment variable + * + * Only removes from the current context, not from parent contexts. + * + * @param ctx Context to remove the variable from + * @param name Name of the environment variable to remove + * + * @return true if the variable was removed successfully, false if not found + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool, _remove, ( dmenv_ctx_t ctx, const char* name ) ); + +/** + * @brief Clear all environment variables in a context + * + * Only clears variables in the current context, not in parent contexts. + * + * @param ctx Context to clear + * + * @return true if all variables were cleared successfully, false otherwise + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool, _clear, ( dmenv_ctx_t ctx ) ); + +/** + * @brief Get the number of environment variables in a context + * + * Only counts variables in the current context, not in parent contexts. + * + * @param ctx Context to count variables in * - * @return Number of environment variables. + * @return Number of environment variables */ -DMOD_BUILTIN_API( dmenv, 1.0, size_t , _count, ( void ) ); +DMOD_BUILTIN_API( dmenv, 1.0, size_t, _count, ( dmenv_ctx_t ctx ) ); #endif // DMENV_H diff --git a/src/dmenv.c b/src/dmenv.c index f220ded..ba71016 100644 --- a/src/dmenv.c +++ b/src/dmenv.c @@ -1,68 +1,65 @@ #include "dmenv.h" #include #include - -#ifndef DMENV_MAX_NAME_LENGTH -#define DMENV_MAX_NAME_LENGTH 64 -#endif - -#ifndef DMENV_MAX_VALUE_LENGTH -#define DMENV_MAX_VALUE_LENGTH 256 -#endif +#include /** * @brief Structure to hold an environment variable entry */ typedef struct env_entry { - char name[DMENV_MAX_NAME_LENGTH]; - char value[DMENV_MAX_VALUE_LENGTH]; + char* name; + char* value; struct env_entry* next; } env_entry_t; /** * @brief Context structure for the environment variables manager */ -typedef struct { - void* buffer; - size_t buffer_size; +typedef struct dmenv_ctx { env_entry_t* head; - bool initialized; + dmenv_ctx_t parent; size_t entry_count; - size_t used_size; -} dmenv_context_t; +} dmenv_ctx_internal_t; -static dmenv_context_t g_dmenv_context = {0}; +static dmenv_ctx_t g_default_context = NULL; /** - * @brief Helper function to allocate an entry from the buffer + * @brief Helper function to find an entry by name in a context */ -static env_entry_t* allocate_entry(void) { - if (g_dmenv_context.used_size + sizeof(env_entry_t) > g_dmenv_context.buffer_size) { - DMOD_LOG_ERROR("Buffer full, cannot allocate new entry"); +static env_entry_t* find_entry(dmenv_ctx_t ctx, const char* name) { + if (!ctx || !name) { return NULL; } - env_entry_t* entry = (env_entry_t*)((char*)g_dmenv_context.buffer + g_dmenv_context.used_size); - g_dmenv_context.used_size += sizeof(env_entry_t); - memset(entry, 0, sizeof(env_entry_t)); - return entry; + env_entry_t* current = ((dmenv_ctx_internal_t*)ctx)->head; + while (current != NULL) { + if (strcmp(current->name, name) == 0) { + return current; + } + current = current->next; + } + return NULL; } /** - * @brief Helper function to find an entry by name + * @brief Helper function to recursively search in parent contexts */ -static env_entry_t* find_entry(const char* name) { - if (!name || !g_dmenv_context.initialized) { +static env_entry_t* find_entry_with_inheritance(dmenv_ctx_t ctx, const char* name) { + if (!ctx || !name) { return NULL; } - env_entry_t* current = g_dmenv_context.head; - while (current != NULL) { - if (strcmp(current->name, name) == 0) { - return current; - } - current = current->next; + env_entry_t* entry = find_entry(ctx, name); + if (entry != NULL) { + return entry; + } + + // Search in parent context + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + if (internal->parent != NULL) { + return find_entry_with_inheritance(internal->parent, name); } + return NULL; } @@ -70,38 +67,83 @@ static env_entry_t* find_entry(const char* name) { // DMOD API Implementations // ============================================================================ -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _init, ( void* buffer, size_t size ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, dmenv_ctx_t, _create, ( dmenv_ctx_t parent ) ) { - if (!buffer || size < sizeof(env_entry_t)) { - DMOD_LOG_ERROR("Invalid buffer or size for initialization"); - return false; + Dmod_EnterCritical(); + + dmenv_ctx_internal_t* ctx = (dmenv_ctx_internal_t*)Dmod_MallocEx(sizeof(dmenv_ctx_internal_t), "dmenv"); + if (ctx == NULL) { + DMOD_LOG_ERROR("Failed to allocate memory for context"); + Dmod_ExitCritical(); + return NULL; + } + + ctx->head = NULL; + ctx->parent = parent; + ctx->entry_count = 0; + + DMOD_LOG_INFO("== dmenv ver. %s ==", DMENV_VERSION); + DMOD_LOG_INFO("Created context %p with parent %p", ctx, parent); + + Dmod_ExitCritical(); + + return (dmenv_ctx_t)ctx; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, void, _destroy, ( dmenv_ctx_t ctx ) ) +{ + if (!ctx) { + return; } Dmod_EnterCritical(); - g_dmenv_context.buffer = buffer; - g_dmenv_context.buffer_size = size; - g_dmenv_context.head = NULL; - g_dmenv_context.initialized = true; - g_dmenv_context.entry_count = 0; - g_dmenv_context.used_size = 0; + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + + // Free all entries + env_entry_t* current = internal->head; + while (current != NULL) { + env_entry_t* next = current->next; + Dmod_FreeEx(current->name, false); + Dmod_FreeEx(current->value, false); + Dmod_FreeEx(current, false); + current = next; + } + + // If this is the default context, clear it + if (g_default_context == ctx) { + g_default_context = NULL; + } - DMOD_LOG_INFO("dmenv: Initialized with buffer %p of size %zu", buffer, size); + Dmod_FreeEx(ctx, false); - Dmod_ExitCritical(); + DMOD_LOG_INFO("Destroyed context %p", ctx); - return true; + Dmod_ExitCritical(); } -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _is_initialized, ( void ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _is_valid, ( dmenv_ctx_t ctx ) ) { - return g_dmenv_context.initialized; + return ctx != NULL; } -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const char* value ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, void, _set_as_default, ( dmenv_ctx_t ctx ) ) { - if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("Environment manager not initialized"); + Dmod_EnterCritical(); + g_default_context = ctx; + DMOD_LOG_INFO("Set context %p as default", ctx); + Dmod_ExitCritical(); +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, dmenv_ctx_t, _get_default, ( void ) ) +{ + return g_default_context; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _set, ( dmenv_ctx_t ctx, const char* name, const char* value ) ) +{ + if (!ctx) { + DMOD_LOG_ERROR("Invalid context"); return false; } @@ -110,45 +152,60 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const ch return false; } - if (strlen(name) >= DMENV_MAX_NAME_LENGTH) { - DMOD_LOG_ERROR("Name too long: %s", name); - return false; - } - - if (strlen(value) >= DMENV_MAX_VALUE_LENGTH) { - DMOD_LOG_ERROR("Value too long for name: %s", name); - return false; - } - Dmod_EnterCritical(); + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + // Check if variable already exists - env_entry_t* existing = find_entry(name); + env_entry_t* existing = find_entry(ctx, name); if (existing != NULL) { // Update existing entry - strncpy(existing->value, value, DMENV_MAX_VALUE_LENGTH - 1); - existing->value[DMENV_MAX_VALUE_LENGTH - 1] = '\0'; + char* new_value = (char*)Dmod_MallocEx(strlen(value) + 1, "dmenv"); + if (new_value == NULL) { + DMOD_LOG_ERROR("Failed to allocate memory for value"); + Dmod_ExitCritical(); + return false; + } + strcpy(new_value, value); + Dmod_FreeEx(existing->value, false); + existing->value = new_value; DMOD_LOG_INFO("Updated variable %s = %s", name, value); Dmod_ExitCritical(); return true; } // Create new entry - env_entry_t* entry = allocate_entry(); + env_entry_t* entry = (env_entry_t*)Dmod_MallocEx(sizeof(env_entry_t), "dmenv"); if (entry == NULL) { + DMOD_LOG_ERROR("Failed to allocate memory for entry"); Dmod_ExitCritical(); return false; } - strncpy(entry->name, name, DMENV_MAX_NAME_LENGTH - 1); - entry->name[DMENV_MAX_NAME_LENGTH - 1] = '\0'; - strncpy(entry->value, value, DMENV_MAX_VALUE_LENGTH - 1); - entry->value[DMENV_MAX_VALUE_LENGTH - 1] = '\0'; + entry->name = (char*)Dmod_MallocEx(strlen(name) + 1, "dmenv"); + if (entry->name == NULL) { + DMOD_LOG_ERROR("Failed to allocate memory for name"); + Dmod_FreeEx(entry, false); + Dmod_ExitCritical(); + return false; + } + + entry->value = (char*)Dmod_MallocEx(strlen(value) + 1, "dmenv"); + if (entry->value == NULL) { + DMOD_LOG_ERROR("Failed to allocate memory for value"); + Dmod_FreeEx(entry->name, false); + Dmod_FreeEx(entry, false); + Dmod_ExitCritical(); + return false; + } + + strcpy(entry->name, name); + strcpy(entry->value, value); // Add to linked list - entry->next = g_dmenv_context.head; - g_dmenv_context.head = entry; - g_dmenv_context.entry_count++; + entry->next = internal->head; + internal->head = entry; + internal->entry_count++; DMOD_LOG_INFO("Set variable %s = %s", name, value); @@ -157,10 +214,10 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _set, ( const char* name, const ch return true; } -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( const char* name ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( dmenv_ctx_t ctx, const char* name ) ) { - if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("Environment manager not initialized"); + if (!ctx) { + DMOD_LOG_ERROR("Invalid context"); return NULL; } @@ -171,7 +228,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( const char* name ) Dmod_EnterCritical(); - env_entry_t* entry = find_entry(name); + env_entry_t* entry = find_entry_with_inheritance(ctx, name); Dmod_ExitCritical(); @@ -184,10 +241,42 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( const char* name ) return NULL; } -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _find, ( const char* prefix, void (*callback)(const char* name, const char* value, void* user_data), void* user_data ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _seti, ( dmenv_ctx_t ctx, const char* name, uint32_t value ) ) { - if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("Environment manager not initialized"); + char buffer[32]; + snprintf(buffer, sizeof(buffer), "0x%X", value); + return dmenv_set(ctx, name, buffer); +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _geti, ( dmenv_ctx_t ctx, const char* name, uint32_t* out_value ) ) +{ + if (!out_value) { + DMOD_LOG_ERROR("Invalid output pointer"); + return false; + } + + const char* str_value = dmenv_get(ctx, name); + if (str_value == NULL) { + return false; + } + + // Try to parse as hex or decimal + char* endptr; + unsigned long parsed = strtoul(str_value, &endptr, 0); // 0 = auto-detect base + + if (endptr == str_value || *endptr != '\0') { + DMOD_LOG_ERROR("Failed to parse value '%s' as integer", str_value); + return false; + } + + *out_value = (uint32_t)parsed; + return true; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _find, ( dmenv_ctx_t ctx, const char* prefix, dmenv_find_callback_t callback, void* user_data ) ) +{ + if (!ctx) { + DMOD_LOG_ERROR("Invalid context"); return 0; } @@ -201,7 +290,8 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _find, ( const char* prefix, voi Dmod_EnterCritical(); - env_entry_t* current = g_dmenv_context.head; + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + env_entry_t* current = internal->head; while (current != NULL) { if (strncmp(current->name, prefix, prefix_len) == 0) { callback(current->name, current->value, user_data); @@ -217,10 +307,10 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _find, ( const char* prefix, voi return count; } -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( dmenv_ctx_t ctx, const char* name ) ) { - if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("Environment manager not initialized"); + if (!ctx) { + DMOD_LOG_ERROR("Invalid context"); return false; } @@ -231,7 +321,8 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) Dmod_EnterCritical(); - env_entry_t* current = g_dmenv_context.head; + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + env_entry_t* current = internal->head; env_entry_t* prev = NULL; while (current != NULL) { @@ -239,13 +330,17 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) // Found the entry to remove if (prev == NULL) { // Removing the head - g_dmenv_context.head = current->next; + internal->head = current->next; } else { // Removing from middle or end prev->next = current->next; } - g_dmenv_context.entry_count--; + Dmod_FreeEx(current->name, false); + Dmod_FreeEx(current->value, false); + Dmod_FreeEx(current, false); + + internal->entry_count--; DMOD_LOG_INFO("Removed variable %s", name); @@ -262,18 +357,29 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( const char* name ) ) return false; } -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _clear, ( void ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _clear, ( dmenv_ctx_t ctx ) ) { - if (!g_dmenv_context.initialized) { - DMOD_LOG_ERROR("Environment manager not initialized"); + if (!ctx) { + DMOD_LOG_ERROR("Invalid context"); return false; } Dmod_EnterCritical(); - g_dmenv_context.head = NULL; - g_dmenv_context.entry_count = 0; - g_dmenv_context.used_size = 0; + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + + // Free all entries + env_entry_t* current = internal->head; + while (current != NULL) { + env_entry_t* next = current->next; + Dmod_FreeEx(current->name, false); + Dmod_FreeEx(current->value, false); + Dmod_FreeEx(current, false); + current = next; + } + + internal->head = NULL; + internal->entry_count = 0; DMOD_LOG_INFO("Cleared all environment variables"); @@ -282,14 +388,15 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _clear, ( void ) ) return true; } -DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _count, ( void ) ) +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _count, ( dmenv_ctx_t ctx ) ) { - if (!g_dmenv_context.initialized) { + if (!ctx) { return 0; } Dmod_EnterCritical(); - size_t count = g_dmenv_context.entry_count; + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + size_t count = internal->entry_count; Dmod_ExitCritical(); return count; diff --git a/tests/test_dmenv_unit.c b/tests/test_dmenv_unit.c index d98ea9f..7d3ff2b 100644 --- a/tests/test_dmenv_unit.c +++ b/tests/test_dmenv_unit.c @@ -3,91 +3,93 @@ #include "unity.h" #include -static char test_buffer[TEST_BUFFER_SIZE]; +static dmenv_ctx_t test_ctx; void setUp(void) { - memset(test_buffer, 0, TEST_BUFFER_SIZE); - dmenv_init(test_buffer, TEST_BUFFER_SIZE); + test_ctx = dmenv_create(NULL); } void tearDown(void) { - if (dmenv_is_initialized()) { - dmenv_clear(); + if (dmenv_is_valid(test_ctx)) { + dmenv_destroy(test_ctx); } } -void test_init_success(void) { - char buffer[1024]; - TEST_ASSERT_TRUE(dmenv_init(buffer, 1024)); - TEST_ASSERT_TRUE(dmenv_is_initialized()); +void test_create_success(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + TEST_ASSERT_TRUE(dmenv_is_valid(ctx)); + dmenv_destroy(ctx); } -void test_init_invalid_buffer(void) { - TEST_ASSERT_FALSE(dmenv_init(NULL, 1024)); -} - -void test_init_too_small(void) { - char buffer[10]; - TEST_ASSERT_FALSE(dmenv_init(buffer, 10)); +void test_create_with_parent(void) { + dmenv_ctx_t parent = dmenv_create(NULL); + dmenv_ctx_t child = dmenv_create(parent); + TEST_ASSERT_TRUE(dmenv_is_valid(child)); + dmenv_destroy(child); + dmenv_destroy(parent); } void test_set_and_get(void) { - TEST_ASSERT_TRUE(dmenv_set("MY_VAR", "my_value")); - const char* value = dmenv_get("MY_VAR"); + TEST_ASSERT_TRUE(dmenv_set(test_ctx, "MY_VAR", "my_value")); + const char* value = dmenv_get(test_ctx, "MY_VAR"); TEST_ASSERT_NOT_NULL(value); TEST_ASSERT_EQUAL_STRING("my_value", value); } void test_set_update_existing(void) { - TEST_ASSERT_TRUE(dmenv_set("VAR", "value1")); - TEST_ASSERT_TRUE(dmenv_set("VAR", "value2")); - const char* value = dmenv_get("VAR"); + TEST_ASSERT_TRUE(dmenv_set(test_ctx, "VAR", "value1")); + TEST_ASSERT_TRUE(dmenv_set(test_ctx, "VAR", "value2")); + const char* value = dmenv_get(test_ctx, "VAR"); TEST_ASSERT_EQUAL_STRING("value2", value); } void test_get_nonexistent(void) { - const char* value = dmenv_get("NONEXISTENT"); + const char* value = dmenv_get(test_ctx, "NONEXISTENT"); TEST_ASSERT_NULL(value); } +void test_set_null_ctx(void) { + TEST_ASSERT_FALSE(dmenv_set(NULL, "VAR", "value")); +} + void test_set_null_name(void) { - TEST_ASSERT_FALSE(dmenv_set(NULL, "value")); + TEST_ASSERT_FALSE(dmenv_set(test_ctx, NULL, "value")); } void test_set_null_value(void) { - TEST_ASSERT_FALSE(dmenv_set("VAR", NULL)); + TEST_ASSERT_FALSE(dmenv_set(test_ctx, "VAR", NULL)); } void test_remove_existing(void) { - TEST_ASSERT_TRUE(dmenv_set("TEMP", "temp_value")); - TEST_ASSERT_TRUE(dmenv_remove("TEMP")); - TEST_ASSERT_NULL(dmenv_get("TEMP")); + TEST_ASSERT_TRUE(dmenv_set(test_ctx, "TEMP", "temp_value")); + TEST_ASSERT_TRUE(dmenv_remove(test_ctx, "TEMP")); + TEST_ASSERT_NULL(dmenv_get(test_ctx, "TEMP")); } void test_remove_nonexistent(void) { - TEST_ASSERT_FALSE(dmenv_remove("NONEXISTENT")); + TEST_ASSERT_FALSE(dmenv_remove(test_ctx, "NONEXISTENT")); } void test_clear(void) { - dmenv_set("VAR1", "value1"); - dmenv_set("VAR2", "value2"); - dmenv_set("VAR3", "value3"); + dmenv_set(test_ctx, "VAR1", "value1"); + dmenv_set(test_ctx, "VAR2", "value2"); + dmenv_set(test_ctx, "VAR3", "value3"); - TEST_ASSERT_TRUE(dmenv_clear()); - TEST_ASSERT_EQUAL_size_t(0, dmenv_count()); + TEST_ASSERT_TRUE(dmenv_clear(test_ctx)); + TEST_ASSERT_EQUAL_size_t(0, dmenv_count(test_ctx)); } void test_count(void) { - TEST_ASSERT_EQUAL_size_t(0, dmenv_count()); + TEST_ASSERT_EQUAL_size_t(0, dmenv_count(test_ctx)); - dmenv_set("VAR1", "value1"); - TEST_ASSERT_EQUAL_size_t(1, dmenv_count()); + dmenv_set(test_ctx, "VAR1", "value1"); + TEST_ASSERT_EQUAL_size_t(1, dmenv_count(test_ctx)); - dmenv_set("VAR2", "value2"); - TEST_ASSERT_EQUAL_size_t(2, dmenv_count()); + dmenv_set(test_ctx, "VAR2", "value2"); + TEST_ASSERT_EQUAL_size_t(2, dmenv_count(test_ctx)); - dmenv_remove("VAR1"); - TEST_ASSERT_EQUAL_size_t(1, dmenv_count()); + dmenv_remove(test_ctx, "VAR1"); + TEST_ASSERT_EQUAL_size_t(1, dmenv_count(test_ctx)); } typedef struct { @@ -107,11 +109,11 @@ void find_test_callback(const char* name, const char* value, void* user_data) { void test_find_with_prefix(void) { find_test_context_t ctx = {0}; - dmenv_set("PREFIX_VAR1", "value1"); - dmenv_set("PREFIX_VAR2", "value2"); - dmenv_set("OTHER_VAR", "value3"); + dmenv_set(test_ctx, "PREFIX_VAR1", "value1"); + dmenv_set(test_ctx, "PREFIX_VAR2", "value2"); + dmenv_set(test_ctx, "OTHER_VAR", "value3"); - size_t found = dmenv_find("PREFIX_", find_test_callback, &ctx); + size_t found = dmenv_find(test_ctx, "PREFIX_", find_test_callback, &ctx); TEST_ASSERT_EQUAL_size_t(2, found); TEST_ASSERT_EQUAL_size_t(2, ctx.count); } @@ -119,10 +121,10 @@ void test_find_with_prefix(void) { void test_find_no_matches(void) { find_test_context_t ctx = {0}; - dmenv_set("VAR1", "value1"); - dmenv_set("VAR2", "value2"); + dmenv_set(test_ctx, "VAR1", "value1"); + dmenv_set(test_ctx, "VAR2", "value2"); - size_t found = dmenv_find("NOMATCH_", find_test_callback, &ctx); + size_t found = dmenv_find(test_ctx, "NOMATCH_", find_test_callback, &ctx); TEST_ASSERT_EQUAL_size_t(0, found); } @@ -133,10 +135,10 @@ void test_multiple_variables(void) { char value[32]; snprintf(name, sizeof(name), "VAR_%d", i); snprintf(value, sizeof(value), "value_%d", i); - TEST_ASSERT_TRUE(dmenv_set(name, value)); + TEST_ASSERT_TRUE(dmenv_set(test_ctx, name, value)); } - TEST_ASSERT_EQUAL_size_t(10, dmenv_count()); + TEST_ASSERT_EQUAL_size_t(10, dmenv_count(test_ctx)); // Verify all values for (int i = 0; i < 10; i++) { @@ -145,21 +147,89 @@ void test_multiple_variables(void) { snprintf(name, sizeof(name), "VAR_%d", i); snprintf(expected, sizeof(expected), "value_%d", i); - const char* value = dmenv_get(name); + const char* value = dmenv_get(test_ctx, name); TEST_ASSERT_NOT_NULL(value); TEST_ASSERT_EQUAL_STRING(expected, value); } } +void test_seti_and_geti(void) { + uint32_t value; + + TEST_ASSERT_TRUE(dmenv_seti(test_ctx, "NUM", 0x2000)); + TEST_ASSERT_TRUE(dmenv_geti(test_ctx, "NUM", &value)); + TEST_ASSERT_EQUAL_UINT32(0x2000, value); +} + +void test_geti_decimal(void) { + uint32_t value; + + // Set as string with decimal value + TEST_ASSERT_TRUE(dmenv_set(test_ctx, "DEC_NUM", "12345")); + TEST_ASSERT_TRUE(dmenv_geti(test_ctx, "DEC_NUM", &value)); + TEST_ASSERT_EQUAL_UINT32(12345, value); +} + +void test_inheritance(void) { + dmenv_ctx_t parent = dmenv_create(NULL); + dmenv_ctx_t child = dmenv_create(parent); + + dmenv_set(parent, "PARENT_VAR", "parent_value"); + dmenv_set(child, "CHILD_VAR", "child_value"); + + // Child should find parent variable + const char* parent_val = dmenv_get(child, "PARENT_VAR"); + TEST_ASSERT_NOT_NULL(parent_val); + TEST_ASSERT_EQUAL_STRING("parent_value", parent_val); + + // Child should find its own variable + const char* child_val = dmenv_get(child, "CHILD_VAR"); + TEST_ASSERT_NOT_NULL(child_val); + TEST_ASSERT_EQUAL_STRING("child_value", child_val); + + // Parent should not find child variable + const char* not_found = dmenv_get(parent, "CHILD_VAR"); + TEST_ASSERT_NULL(not_found); + + dmenv_destroy(child); + dmenv_destroy(parent); +} + +void test_inheritance_override(void) { + dmenv_ctx_t parent = dmenv_create(NULL); + dmenv_ctx_t child = dmenv_create(parent); + + dmenv_set(parent, "VAR", "parent_value"); + dmenv_set(child, "VAR", "child_value"); + + // Child should get its own value, not parent's + const char* val = dmenv_get(child, "VAR"); + TEST_ASSERT_NOT_NULL(val); + TEST_ASSERT_EQUAL_STRING("child_value", val); + + dmenv_destroy(child); + dmenv_destroy(parent); +} + +void test_default_context(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + dmenv_set_as_default(ctx); + + dmenv_ctx_t retrieved = dmenv_get_default(); + TEST_ASSERT_EQUAL_PTR(ctx, retrieved); + + dmenv_destroy(ctx); +} + int main(void) { UNITY_BEGIN(); - RUN_TEST(test_init_success); - RUN_TEST(test_init_invalid_buffer); - RUN_TEST(test_init_too_small); + RUN_TEST(test_create_success); + RUN_TEST(test_create_with_parent); RUN_TEST(test_set_and_get); RUN_TEST(test_set_update_existing); RUN_TEST(test_get_nonexistent); + RUN_TEST(test_set_null_ctx); RUN_TEST(test_set_null_name); RUN_TEST(test_set_null_value); RUN_TEST(test_remove_existing); @@ -169,6 +239,11 @@ int main(void) { RUN_TEST(test_find_with_prefix); RUN_TEST(test_find_no_matches); RUN_TEST(test_multiple_variables); + RUN_TEST(test_seti_and_geti); + RUN_TEST(test_geti_decimal); + RUN_TEST(test_inheritance); + RUN_TEST(test_inheritance_override); + RUN_TEST(test_default_context); return UNITY_END(); } diff --git a/tests/test_minimal.c b/tests/test_minimal.c index 83d94fc..9c64128 100644 --- a/tests/test_minimal.c +++ b/tests/test_minimal.c @@ -3,34 +3,33 @@ #include #include -static char test_buffer[TEST_BUFFER_SIZE]; - int main(void) { printf("=== Minimal DMENV Test ===\n"); - // Test initialization - if (!dmenv_init(test_buffer, TEST_BUFFER_SIZE)) { - printf("Init: FAIL\n"); + // Test create context + dmenv_ctx_t ctx = dmenv_create(NULL); + if (!ctx) { + printf("Create: FAIL\n"); return 1; } - printf("Init: PASS\n"); + printf("Create: PASS\n"); - // Test is_initialized - if (!dmenv_is_initialized()) { - printf("Is Initialized: FAIL\n"); + // Test is_valid + if (!dmenv_is_valid(ctx)) { + printf("Is Valid: FAIL\n"); return 1; } - printf("Is Initialized: PASS\n"); + printf("Is Valid: PASS\n"); // Test set - if (!dmenv_set("TEST", "value")) { + if (!dmenv_set(ctx, "TEST", "value")) { printf("Set: FAIL\n"); return 1; } printf("Set: PASS\n"); // Test get - const char* value = dmenv_get("TEST"); + const char* value = dmenv_get(ctx, "TEST"); if (value == NULL || strcmp(value, "value") != 0) { printf("Get: FAIL\n"); return 1; @@ -38,26 +37,44 @@ int main(void) { printf("Get: PASS\n"); // Test count - if (dmenv_count() != 1) { - printf("Count: FAIL (expected 1, got %zu)\n", dmenv_count()); + if (dmenv_count(ctx) != 1) { + printf("Count: FAIL (expected 1, got %zu)\n", dmenv_count(ctx)); return 1; } printf("Count: PASS\n"); // Test remove - if (!dmenv_remove("TEST")) { + if (!dmenv_remove(ctx, "TEST")) { printf("Remove: FAIL\n"); return 1; } printf("Remove: PASS\n"); // Test count after remove - if (dmenv_count() != 0) { - printf("Count After Remove: FAIL (expected 0, got %zu)\n", dmenv_count()); + if (dmenv_count(ctx) != 0) { + printf("Count After Remove: FAIL (expected 0, got %zu)\n", dmenv_count(ctx)); return 1; } printf("Count After Remove: PASS\n"); + // Test integer set/get + if (!dmenv_seti(ctx, "NUM", 0x2000)) { + printf("Set Integer: FAIL\n"); + return 1; + } + printf("Set Integer: PASS\n"); + + uint32_t num_value; + if (!dmenv_geti(ctx, "NUM", &num_value) || num_value != 0x2000) { + printf("Get Integer: FAIL (got 0x%X)\n", num_value); + return 1; + } + printf("Get Integer: PASS\n"); + + // Clean up + dmenv_destroy(ctx); + printf("Destroy: PASS\n"); + printf("\nAll minimal tests passed!\n"); return 0; diff --git a/tests/test_simple.c b/tests/test_simple.c index b96716c..6e5c5c5 100644 --- a/tests/test_simple.c +++ b/tests/test_simple.c @@ -3,59 +3,66 @@ #include #include -static char test_buffer[TEST_BUFFER_SIZE]; - -bool test_init(void) { - bool result = dmenv_init(test_buffer, TEST_BUFFER_SIZE); - TEST_ASSERT(result, "Init should succeed"); - TEST_ASSERT(dmenv_is_initialized(), "Should be initialized"); +bool test_create(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + TEST_ASSERT(ctx != NULL, "Create should succeed"); + TEST_ASSERT(dmenv_is_valid(ctx), "Context should be valid"); + dmenv_destroy(ctx); return true; } bool test_set_get(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); const char* value; // Set a variable - TEST_ASSERT(dmenv_set("TEST_VAR", "test_value"), "Set should succeed"); + TEST_ASSERT(dmenv_set(ctx, "TEST_VAR", "test_value"), "Set should succeed"); // Get the variable - value = dmenv_get("TEST_VAR"); + value = dmenv_get(ctx, "TEST_VAR"); TEST_ASSERT(value != NULL, "Get should return a value"); TEST_ASSERT(strcmp(value, "test_value") == 0, "Value should match"); + dmenv_destroy(ctx); return true; } bool test_remove(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + // Set a variable - TEST_ASSERT(dmenv_set("REMOVE_ME", "temporary"), "Set should succeed"); + TEST_ASSERT(dmenv_set(ctx, "REMOVE_ME", "temporary"), "Set should succeed"); // Verify it exists - TEST_ASSERT(dmenv_get("REMOVE_ME") != NULL, "Variable should exist"); + TEST_ASSERT(dmenv_get(ctx, "REMOVE_ME") != NULL, "Variable should exist"); // Remove it - TEST_ASSERT(dmenv_remove("REMOVE_ME"), "Remove should succeed"); + TEST_ASSERT(dmenv_remove(ctx, "REMOVE_ME"), "Remove should succeed"); // Verify it's gone - TEST_ASSERT(dmenv_get("REMOVE_ME") == NULL, "Variable should not exist"); + TEST_ASSERT(dmenv_get(ctx, "REMOVE_ME") == NULL, "Variable should not exist"); + dmenv_destroy(ctx); return true; } bool test_clear(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + // Set some variables - dmenv_set("VAR1", "value1"); - dmenv_set("VAR2", "value2"); - dmenv_set("VAR3", "value3"); + dmenv_set(ctx, "VAR1", "value1"); + dmenv_set(ctx, "VAR2", "value2"); + dmenv_set(ctx, "VAR3", "value3"); - TEST_ASSERT(dmenv_count() >= 3, "Should have at least 3 variables"); + TEST_ASSERT(dmenv_count(ctx) >= 3, "Should have at least 3 variables"); // Clear all - TEST_ASSERT(dmenv_clear(), "Clear should succeed"); + TEST_ASSERT(dmenv_clear(ctx), "Clear should succeed"); // Verify count is 0 - TEST_ASSERT(dmenv_count() == 0, "Count should be 0 after clear"); + TEST_ASSERT(dmenv_count(ctx) == 0, "Count should be 0 after clear"); + dmenv_destroy(ctx); return true; } @@ -70,39 +77,89 @@ void find_callback(const char* name, const char* value, void* user_data) { } bool test_find(void) { - find_context_t ctx = {0}; + dmenv_ctx_t ctx = dmenv_create(NULL); + find_context_t find_ctx = {0}; // Set variables with different prefixes - dmenv_set("APP_NAME", "myapp"); - dmenv_set("APP_VERSION", "1.0"); - dmenv_set("APP_DEBUG", "true"); - dmenv_set("SYS_PATH", "/usr/bin"); - dmenv_set("SYS_USER", "admin"); + dmenv_set(ctx, "APP_NAME", "myapp"); + dmenv_set(ctx, "APP_VERSION", "1.0"); + dmenv_set(ctx, "APP_DEBUG", "true"); + dmenv_set(ctx, "SYS_PATH", "/usr/bin"); + dmenv_set(ctx, "SYS_USER", "admin"); // Find all APP_ variables - size_t found = dmenv_find("APP_", find_callback, &ctx); + size_t found = dmenv_find(ctx, "APP_", find_callback, &find_ctx); TEST_ASSERT(found == 3, "Should find 3 APP_ variables"); - TEST_ASSERT(ctx.count == 3, "Callback should be called 3 times"); + TEST_ASSERT(find_ctx.count == 3, "Callback should be called 3 times"); // Reset context - ctx.count = 0; + find_ctx.count = 0; // Find all SYS_ variables - found = dmenv_find("SYS_", find_callback, &ctx); + found = dmenv_find(ctx, "SYS_", find_callback, &find_ctx); TEST_ASSERT(found == 2, "Should find 2 SYS_ variables"); - TEST_ASSERT(ctx.count == 2, "Callback should be called 2 times"); + TEST_ASSERT(find_ctx.count == 2, "Callback should be called 2 times"); + + dmenv_destroy(ctx); + return true; +} + +bool test_integer(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + uint32_t value; + + // Set integer value + TEST_ASSERT(dmenv_seti(ctx, "NUM", 0x2000), "Set integer should succeed"); + + // Get integer value + TEST_ASSERT(dmenv_geti(ctx, "NUM", &value), "Get integer should succeed"); + TEST_ASSERT(value == 0x2000, "Integer value should match"); + dmenv_destroy(ctx); + return true; +} + +bool test_inheritance(void) { + // Create parent context + dmenv_ctx_t parent = dmenv_create(NULL); + dmenv_set(parent, "PARENT_VAR", "from_parent"); + dmenv_set(parent, "OVERRIDE_VAR", "parent_value"); + + // Create child context with parent + dmenv_ctx_t child = dmenv_create(parent); + dmenv_set(child, "CHILD_VAR", "from_child"); + dmenv_set(child, "OVERRIDE_VAR", "child_value"); + + // Test inheritance + const char* parent_var = dmenv_get(child, "PARENT_VAR"); + TEST_ASSERT(parent_var != NULL, "Should find parent variable"); + TEST_ASSERT(strcmp(parent_var, "from_parent") == 0, "Parent value should match"); + + // Test child variable + const char* child_var = dmenv_get(child, "CHILD_VAR"); + TEST_ASSERT(child_var != NULL, "Should find child variable"); + TEST_ASSERT(strcmp(child_var, "from_child") == 0, "Child value should match"); + + // Test override + const char* override_var = dmenv_get(child, "OVERRIDE_VAR"); + TEST_ASSERT(override_var != NULL, "Should find overridden variable"); + TEST_ASSERT(strcmp(override_var, "child_value") == 0, "Should get child value"); + + dmenv_destroy(child); + dmenv_destroy(parent); return true; } int main(void) { printf("=== DMENV Simple Test ===\n\n"); - TEST_PRINT_RESULT("Init", test_init()); + TEST_PRINT_RESULT("Create", test_create()); TEST_PRINT_RESULT("Set/Get", test_set_get()); TEST_PRINT_RESULT("Remove", test_remove()); TEST_PRINT_RESULT("Clear", test_clear()); TEST_PRINT_RESULT("Find", test_find()); + TEST_PRINT_RESULT("Integer", test_integer()); + TEST_PRINT_RESULT("Inheritance", test_inheritance()); printf("\nAll simple tests completed!\n"); From 82e51ef6b8a1231eb60bffefab582a8ce895447e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:51:01 +0000 Subject: [PATCH 09/11] Fix GitHub Actions permissions for security Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2e43a8..9d6bac1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main, develop, feature/* ] +permissions: + contents: read + jobs: build-and-test: runs-on: ubuntu-latest From ef3bb177a2490c4b2266403bbe5ff80938520664 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:04:44 +0000 Subject: [PATCH 10/11] Add magic number validation, fix snprintf for embedded, implement DMOD API Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- src/dmenv.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/src/dmenv.c b/src/dmenv.c index ba71016..22be07e 100644 --- a/src/dmenv.c +++ b/src/dmenv.c @@ -3,6 +3,10 @@ #include #include +#ifndef DMENV_MAGIC_NUMBER +#define DMENV_MAGIC_NUMBER 0x444D454E // "DMEN" in hex +#endif + /** * @brief Structure to hold an environment variable entry */ @@ -16,6 +20,7 @@ typedef struct env_entry { * @brief Context structure for the environment variables manager */ typedef struct dmenv_ctx { + uint32_t magic; env_entry_t* head; dmenv_ctx_t parent; size_t entry_count; @@ -78,6 +83,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, dmenv_ctx_t, _create, ( dmenv_ctx_t pare return NULL; } + ctx->magic = DMENV_MAGIC_NUMBER; ctx->head = NULL; ctx->parent = parent; ctx->entry_count = 0; @@ -92,7 +98,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, dmenv_ctx_t, _create, ( dmenv_ctx_t pare DMOD_INPUT_API_DECLARATION( dmenv, 1.0, void, _destroy, ( dmenv_ctx_t ctx ) ) { - if (!ctx) { + if (!dmenv_is_valid(ctx)) { return; } @@ -115,6 +121,9 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, void, _destroy, ( dmenv_ctx_t ctx ) ) g_default_context = NULL; } + // Invalidate magic number + internal->magic = 0; + Dmod_FreeEx(ctx, false); DMOD_LOG_INFO("Destroyed context %p", ctx); @@ -124,11 +133,19 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, void, _destroy, ( dmenv_ctx_t ctx ) ) DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _is_valid, ( dmenv_ctx_t ctx ) ) { - return ctx != NULL; + Dmod_EnterCritical(); + bool result = ctx != NULL && ((dmenv_ctx_internal_t*)ctx)->magic == DMENV_MAGIC_NUMBER; + Dmod_ExitCritical(); + return result; } DMOD_INPUT_API_DECLARATION( dmenv, 1.0, void, _set_as_default, ( dmenv_ctx_t ctx ) ) { + if (!dmenv_is_valid(ctx)) { + DMOD_LOG_ERROR("Invalid context"); + return; + } + Dmod_EnterCritical(); g_default_context = ctx; DMOD_LOG_INFO("Set context %p as default", ctx); @@ -142,7 +159,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, dmenv_ctx_t, _get_default, ( void ) ) DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _set, ( dmenv_ctx_t ctx, const char* name, const char* value ) ) { - if (!ctx) { + if (!dmenv_is_valid(ctx)) { DMOD_LOG_ERROR("Invalid context"); return false; } @@ -216,7 +233,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _set, ( dmenv_ctx_t ctx, const cha DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( dmenv_ctx_t ctx, const char* name ) ) { - if (!ctx) { + if (!dmenv_is_valid(ctx)) { DMOD_LOG_ERROR("Invalid context"); return NULL; } @@ -244,7 +261,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( dmenv_ctx_t ctx, co DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _seti, ( dmenv_ctx_t ctx, const char* name, uint32_t value ) ) { char buffer[32]; - snprintf(buffer, sizeof(buffer), "0x%X", value); + Dmod_SnPrintf(buffer, sizeof(buffer), "0x%X", value); return dmenv_set(ctx, name, buffer); } @@ -275,7 +292,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _geti, ( dmenv_ctx_t ctx, const ch DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _find, ( dmenv_ctx_t ctx, const char* prefix, dmenv_find_callback_t callback, void* user_data ) ) { - if (!ctx) { + if (!dmenv_is_valid(ctx)) { DMOD_LOG_ERROR("Invalid context"); return 0; } @@ -309,7 +326,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _find, ( dmenv_ctx_t ctx, const DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( dmenv_ctx_t ctx, const char* name ) ) { - if (!ctx) { + if (!dmenv_is_valid(ctx)) { DMOD_LOG_ERROR("Invalid context"); return false; } @@ -359,7 +376,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( dmenv_ctx_t ctx, const DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _clear, ( dmenv_ctx_t ctx ) ) { - if (!ctx) { + if (!dmenv_is_valid(ctx)) { DMOD_LOG_ERROR("Invalid context"); return false; } @@ -390,7 +407,7 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _clear, ( dmenv_ctx_t ctx ) ) DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _count, ( dmenv_ctx_t ctx ) ) { - if (!ctx) { + if (!dmenv_is_valid(ctx)) { return 0; } @@ -401,3 +418,38 @@ DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _count, ( dmenv_ctx_t ctx ) ) return count; } + +#ifndef DMENV_DONT_IMPLEMENT_DMOD_API +/** + * @brief Set an environment variable in the default context (DMOD API) + * + * @param Name Name of the environment variable + * @param Value Value to set + * @return bool true if successful, false otherwise + */ +DMOD_INPUT_API_DECLARATION( Dmod, 1.0, bool, _SetEnv, ( const char* Name, const char* Value ) ) +{ + dmenv_ctx_t ctx = dmenv_get_default(); + if (ctx == NULL) { + DMOD_LOG_ERROR("No default context set for Dmod_SetEnv"); + return false; + } + return dmenv_set(ctx, Name, Value); +} + +/** + * @brief Get an environment variable from the default context (DMOD API) + * + * @param Name Name of the environment variable + * @return const char* Value if found, NULL otherwise + */ +DMOD_INPUT_API_DECLARATION( Dmod, 1.0, const char*, _GetEnv, ( const char* Name ) ) +{ + dmenv_ctx_t ctx = dmenv_get_default(); + if (ctx == NULL) { + DMOD_LOG_ERROR("No default context set for Dmod_GetEnv"); + return NULL; + } + return dmenv_get(ctx, Name); +} +#endif // DMENV_DONT_IMPLEMENT_DMOD_API From 5f65f9aa9671a3aab44805e7178cfcb4de73689d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:19:34 +0000 Subject: [PATCH 11/11] Fix CI coverage by adding --coverage flags to CMake Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- CMakeLists.txt | 6 ++++++ tests/CMakeLists.txt | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71b2c40..fabbfcf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,12 @@ target_link_libraries(${MODULE_NAME} dmod_inc ) +# Enable coverage for dmenv library if requested +if(ENABLE_COVERAGE) + target_compile_options(${MODULE_NAME} PRIVATE --coverage) + target_link_options(${MODULE_NAME} PRIVATE --coverage) +endif() + create_library_makefile(${MODULE_NAME}) # ====================================================================== diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3a85b5d..6bdb479 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,3 +36,13 @@ add_test(NAME test_dmenv_unit COMMAND test_dmenv_unit) add_executable(test_minimal test_minimal.c) target_link_libraries(test_minimal PRIVATE dmenv unity test_common dmod_system) add_test(NAME test_minimal COMMAND test_minimal) + +# Enable coverage for test executables if requested +if(ENABLE_COVERAGE) + target_compile_options(test_simple PRIVATE --coverage) + target_link_options(test_simple PRIVATE --coverage) + target_compile_options(test_dmenv_unit PRIVATE --coverage) + target_link_options(test_dmenv_unit PRIVATE --coverage) + target_compile_options(test_minimal PRIVATE --coverage) + target_link_options(test_minimal PRIVATE --coverage) +endif()