diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9d6bac1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,110 @@ +name: CI + +on: + push: + branches: [ main, develop, copilot/*, feature/* ] + pull_request: + branches: [ main, develop, feature/* ] + +permissions: + contents: read + +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/.gitignore b/.gitignore index 1f99f9d..6b11200 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ compile_commands.json 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/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fabbfcf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,90 @@ +# ===================================================================== +# 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 + ) + +# 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}) + +# ====================================================================== +# 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..260e459 --- /dev/null +++ b/include/dmenv.h @@ -0,0 +1,166 @@ +#ifndef DMENV_H +#define DMENV_H + +#include "dmod.h" +#include +#include +#include + +/** + * @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 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 + * + * @param ctx Context to destroy + */ +DMOD_BUILTIN_API( dmenv, 1.0, void, _destroy, ( dmenv_ctx_t ctx ) ); + +/** + * @brief Check if a context is valid + * + * @param ctx Context to check + * @return true if valid, false otherwise + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool, _is_valid, ( dmenv_ctx_t ctx ) ); + +/** + * @brief Set the default context + * + * @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 Pointer to the default context, or NULL if not set + */ +DMOD_BUILTIN_API( dmenv, 1.0, dmenv_ctx_t, _get_default, ( void ) ); + +/** + * @brief Set an environment variable (string value) + * + * @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 true if the variable was set successfully, false otherwise + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool, _set, ( dmenv_ctx_t ctx, const char* name, const char* value ) ); + +/** + * @brief Get an environment variable value (string) + * + * If the variable is not found in the context and the context has a parent, + * the parent will be searched recursively. + * + * @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, const char*, _get, ( dmenv_ctx_t ctx, const char* name ) ); + +/** + * @brief Set an environment variable (unsigned integer value in hex) + * + * The value is stored internally as a hexadecimal string (e.g., "0x2000"). + * + * @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 set successfully, false otherwise + */ +DMOD_BUILTIN_API( dmenv, 1.0, bool, _seti, ( dmenv_ctx_t ctx, const char* name, uint32_t value ) ); + +/** + * @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. + * + * @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, _geti, ( dmenv_ctx_t ctx, const char* name, uint32_t* out_value ) ); + +/** + * @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 + */ +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 new file mode 100644 index 0000000..22be07e --- /dev/null +++ b/src/dmenv.c @@ -0,0 +1,455 @@ +#include "dmenv.h" +#include +#include +#include + +#ifndef DMENV_MAGIC_NUMBER +#define DMENV_MAGIC_NUMBER 0x444D454E // "DMEN" in hex +#endif + +/** + * @brief Structure to hold an environment variable entry + */ +typedef struct env_entry { + char* name; + char* value; + struct env_entry* next; +} env_entry_t; + +/** + * @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; +} dmenv_ctx_internal_t; + +static dmenv_ctx_t g_default_context = NULL; + +/** + * @brief Helper function to find an entry by name in a context + */ +static env_entry_t* find_entry(dmenv_ctx_t ctx, const char* name) { + if (!ctx || !name) { + return NULL; + } + + 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 recursively search in parent contexts + */ +static env_entry_t* find_entry_with_inheritance(dmenv_ctx_t ctx, const char* name) { + if (!ctx || !name) { + return NULL; + } + + 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; +} + +// ============================================================================ +// DMOD API Implementations +// ============================================================================ + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, dmenv_ctx_t, _create, ( dmenv_ctx_t parent ) ) +{ + 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->magic = DMENV_MAGIC_NUMBER; + 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 (!dmenv_is_valid(ctx)) { + return; + } + + Dmod_EnterCritical(); + + 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; + } + + // Invalidate magic number + internal->magic = 0; + + Dmod_FreeEx(ctx, false); + + DMOD_LOG_INFO("Destroyed context %p", ctx); + + Dmod_ExitCritical(); +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _is_valid, ( dmenv_ctx_t ctx ) ) +{ + 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); + 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 (!dmenv_is_valid(ctx)) { + DMOD_LOG_ERROR("Invalid context"); + return false; + } + + if (!name || !value) { + DMOD_LOG_ERROR("Invalid name or value"); + 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(ctx, name); + if (existing != NULL) { + // Update existing entry + 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 = (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; + } + + 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 = internal->head; + internal->head = entry; + internal->entry_count++; + + DMOD_LOG_INFO("Set variable %s = %s", name, value); + + Dmod_ExitCritical(); + + return true; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, const char*, _get, ( dmenv_ctx_t ctx, const char* name ) ) +{ + if (!dmenv_is_valid(ctx)) { + DMOD_LOG_ERROR("Invalid context"); + return NULL; + } + + if (!name) { + DMOD_LOG_ERROR("Invalid name"); + return NULL; + } + + Dmod_EnterCritical(); + + env_entry_t* entry = find_entry_with_inheritance(ctx, name); + + Dmod_ExitCritical(); + + if (entry != NULL) { + DMOD_LOG_INFO("Get variable %s = %s", name, entry->value); + return entry->value; + } + + DMOD_LOG_INFO("Variable %s not found", name); + return NULL; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _seti, ( dmenv_ctx_t ctx, const char* name, uint32_t value ) ) +{ + char buffer[32]; + Dmod_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 (!dmenv_is_valid(ctx)) { + DMOD_LOG_ERROR("Invalid context"); + return 0; + } + + if (!prefix || !callback) { + DMOD_LOG_ERROR("Invalid prefix or callback"); + return 0; + } + + size_t count = 0; + size_t prefix_len = strlen(prefix); + + Dmod_EnterCritical(); + + 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); + count++; + } + current = current->next; + } + + Dmod_ExitCritical(); + + DMOD_LOG_INFO("Found %zu variables with prefix '%s'", count, prefix); + + return count; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _remove, ( dmenv_ctx_t ctx, const char* name ) ) +{ + if (!dmenv_is_valid(ctx)) { + DMOD_LOG_ERROR("Invalid context"); + return false; + } + + if (!name) { + DMOD_LOG_ERROR("Invalid name"); + return false; + } + + Dmod_EnterCritical(); + + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + env_entry_t* current = internal->head; + env_entry_t* prev = NULL; + + while (current != NULL) { + if (strcmp(current->name, name) == 0) { + // Found the entry to remove + if (prev == NULL) { + // Removing the head + internal->head = current->next; + } else { + // Removing from middle or end + prev->next = current->next; + } + + Dmod_FreeEx(current->name, false); + Dmod_FreeEx(current->value, false); + Dmod_FreeEx(current, false); + + internal->entry_count--; + + DMOD_LOG_INFO("Removed variable %s", name); + + Dmod_ExitCritical(); + return true; + } + prev = current; + current = current->next; + } + + Dmod_ExitCritical(); + + DMOD_LOG_INFO("Variable %s not found for removal", name); + return false; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, bool, _clear, ( dmenv_ctx_t ctx ) ) +{ + if (!dmenv_is_valid(ctx)) { + DMOD_LOG_ERROR("Invalid context"); + return false; + } + + Dmod_EnterCritical(); + + 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"); + + Dmod_ExitCritical(); + + return true; +} + +DMOD_INPUT_API_DECLARATION( dmenv, 1.0, size_t, _count, ( dmenv_ctx_t ctx ) ) +{ + if (!dmenv_is_valid(ctx)) { + return 0; + } + + Dmod_EnterCritical(); + dmenv_ctx_internal_t* internal = (dmenv_ctx_internal_t*)ctx; + size_t count = internal->entry_count; + Dmod_ExitCritical(); + + 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..6bdb479 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,48 @@ +# ===================================================================== +# 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} + ${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 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 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 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() 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..7d3ff2b --- /dev/null +++ b/tests/test_dmenv_unit.c @@ -0,0 +1,249 @@ +#include "test_common.h" +#include "dmenv.h" +#include "unity.h" +#include + +static dmenv_ctx_t test_ctx; + +void setUp(void) { + test_ctx = dmenv_create(NULL); +} + +void tearDown(void) { + if (dmenv_is_valid(test_ctx)) { + dmenv_destroy(test_ctx); + } +} + +void test_create_success(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + TEST_ASSERT_TRUE(dmenv_is_valid(ctx)); + dmenv_destroy(ctx); +} + +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(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(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(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(test_ctx, NULL, "value")); +} + +void test_set_null_value(void) { + TEST_ASSERT_FALSE(dmenv_set(test_ctx, "VAR", NULL)); +} + +void test_remove_existing(void) { + 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(test_ctx, "NONEXISTENT")); +} + +void test_clear(void) { + dmenv_set(test_ctx, "VAR1", "value1"); + dmenv_set(test_ctx, "VAR2", "value2"); + dmenv_set(test_ctx, "VAR3", "value3"); + + 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_ctx)); + + dmenv_set(test_ctx, "VAR1", "value1"); + TEST_ASSERT_EQUAL_size_t(1, dmenv_count(test_ctx)); + + dmenv_set(test_ctx, "VAR2", "value2"); + TEST_ASSERT_EQUAL_size_t(2, dmenv_count(test_ctx)); + + dmenv_remove(test_ctx, "VAR1"); + TEST_ASSERT_EQUAL_size_t(1, dmenv_count(test_ctx)); +} + +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(test_ctx, "PREFIX_VAR1", "value1"); + dmenv_set(test_ctx, "PREFIX_VAR2", "value2"); + dmenv_set(test_ctx, "OTHER_VAR", "value3"); + + 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); +} + +void test_find_no_matches(void) { + find_test_context_t ctx = {0}; + + dmenv_set(test_ctx, "VAR1", "value1"); + dmenv_set(test_ctx, "VAR2", "value2"); + + size_t found = dmenv_find(test_ctx, "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(test_ctx, name, value)); + } + + TEST_ASSERT_EQUAL_size_t(10, dmenv_count(test_ctx)); + + // 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(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_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); + 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); + 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 new file mode 100644 index 0000000..9c64128 --- /dev/null +++ b/tests/test_minimal.c @@ -0,0 +1,81 @@ +#include "test_common.h" +#include "dmenv.h" +#include +#include + +int main(void) { + printf("=== Minimal DMENV Test ===\n"); + + // Test create context + dmenv_ctx_t ctx = dmenv_create(NULL); + if (!ctx) { + printf("Create: FAIL\n"); + return 1; + } + printf("Create: PASS\n"); + + // Test is_valid + if (!dmenv_is_valid(ctx)) { + printf("Is Valid: FAIL\n"); + return 1; + } + printf("Is Valid: PASS\n"); + + // Test set + if (!dmenv_set(ctx, "TEST", "value")) { + printf("Set: FAIL\n"); + return 1; + } + printf("Set: PASS\n"); + + // Test get + const char* value = dmenv_get(ctx, "TEST"); + if (value == NULL || strcmp(value, "value") != 0) { + printf("Get: FAIL\n"); + return 1; + } + printf("Get: PASS\n"); + + // Test 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(ctx, "TEST")) { + printf("Remove: FAIL\n"); + return 1; + } + printf("Remove: PASS\n"); + + // Test count after remove + 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 new file mode 100644 index 0000000..6e5c5c5 --- /dev/null +++ b/tests/test_simple.c @@ -0,0 +1,167 @@ +#include "test_common.h" +#include "dmenv.h" +#include +#include + +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(ctx, "TEST_VAR", "test_value"), "Set should succeed"); + + // Get the variable + 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(ctx, "REMOVE_ME", "temporary"), "Set should succeed"); + + // Verify it exists + TEST_ASSERT(dmenv_get(ctx, "REMOVE_ME") != NULL, "Variable should exist"); + + // Remove it + TEST_ASSERT(dmenv_remove(ctx, "REMOVE_ME"), "Remove should succeed"); + + // Verify it's gone + 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(ctx, "VAR1", "value1"); + dmenv_set(ctx, "VAR2", "value2"); + dmenv_set(ctx, "VAR3", "value3"); + + TEST_ASSERT(dmenv_count(ctx) >= 3, "Should have at least 3 variables"); + + // Clear all + TEST_ASSERT(dmenv_clear(ctx), "Clear should succeed"); + + // Verify count is 0 + TEST_ASSERT(dmenv_count(ctx) == 0, "Count should be 0 after clear"); + + dmenv_destroy(ctx); + 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) { + dmenv_ctx_t ctx = dmenv_create(NULL); + find_context_t find_ctx = {0}; + + // Set variables with different prefixes + 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(ctx, "APP_", find_callback, &find_ctx); + TEST_ASSERT(found == 3, "Should find 3 APP_ variables"); + TEST_ASSERT(find_ctx.count == 3, "Callback should be called 3 times"); + + // Reset context + find_ctx.count = 0; + + // Find all SYS_ variables + found = dmenv_find(ctx, "SYS_", find_callback, &find_ctx); + TEST_ASSERT(found == 2, "Should find 2 SYS_ variables"); + 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("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"); + + return 0; +}