From 1455a9da01655d232ae855c7d9e4b86a111b284e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:56:41 +0000 Subject: [PATCH 1/8] Initial plan From d713873faf257f956461a837554f0df1b046e605 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:01:06 +0000 Subject: [PATCH 2/8] chore: fix test CMakeLists.txt to include linker script Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- tests/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6bdb479..8f96df3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,16 +25,19 @@ 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) +target_link_options(test_simple PRIVATE -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) 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) +target_link_options(test_dmenv_unit PRIVATE -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) 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) +target_link_options(test_minimal PRIVATE -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) add_test(NAME test_minimal COMMAND test_minimal) # Enable coverage for test executables if requested From 381514da860dc4efcaa636e9ca7fe96ed1628b41 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:04:37 +0000 Subject: [PATCH 3/8] feat: add current context support with push/pop mechanism Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- include/dmenv.h | 57 +++++++++++++++- src/dmenv.c | 138 +++++++++++++++++++++++++++++++++++---- tests/test_dmenv_unit.c | 139 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 318 insertions(+), 16 deletions(-) diff --git a/include/dmenv.h b/include/dmenv.h index 16af747..992ac48 100644 --- a/include/dmenv.h +++ b/include/dmenv.h @@ -51,16 +51,69 @@ DMOD_BUILTIN_API(dmenv, 1.0, void, _destroy, (dmenv_ctx_t ctx)); DMOD_BUILTIN_API(dmenv, 1.0, bool, _is_valid, (dmenv_ctx_t ctx)); /** - * @brief Set the default context + * @brief Set the root context + * + * Sets the global root context. The root context serves as the base context + * when no other contexts have been pushed onto the context stack. + * + * @param ctx Context to set as root context + */ +DMOD_BUILTIN_API(dmenv, 1.0, void, _set_root_context, (dmenv_ctx_t ctx)); + +/** + * @brief Get the root context + * + * @return Pointer to the root context, or NULL if not set + */ +DMOD_BUILTIN_API(dmenv, 1.0, dmenv_ctx_t, _get_root_context, (void)); + +/** + * @brief Push a context onto the context stack + * + * Pushes the specified context onto the context stack, making it the current + * context. When accessing variables through the current context, this context + * will be used instead of the root context. + * + * @param ctx Context to push onto the stack + * + * @return true if the context was pushed successfully, false otherwise (e.g., stack overflow or invalid context) + */ +DMOD_BUILTIN_API(dmenv, 1.0, bool, _push_context, (dmenv_ctx_t ctx)); + +/** + * @brief Pop the current context from the context stack + * + * Removes the top context from the context stack. After popping, the previous + * context on the stack becomes the current context. If the stack is empty, + * the root context becomes the current context. + * + * @return Pointer to the popped context, or NULL if the stack was empty + */ +DMOD_BUILTIN_API(dmenv, 1.0, dmenv_ctx_t, _pop_context, (void)); + +/** + * @brief Get the current context + * + * Returns the current active context. If contexts have been pushed onto the + * stack, returns the top context. Otherwise, returns the root context. + * + * @return Pointer to the current context, or NULL if no context is set + */ +DMOD_BUILTIN_API(dmenv, 1.0, dmenv_ctx_t, _get_current_context, (void)); + +/** + * @brief Set the default context (deprecated, use dmenv_set_root_context instead) * * @param ctx Context to set as default + * @deprecated Use dmenv_set_root_context instead */ DMOD_BUILTIN_API(dmenv, 1.0, void, _set_as_default, (dmenv_ctx_t ctx)); /** - * @brief Get the default context + * @brief Get the default context (deprecated, use dmenv_get_root_context instead) * * @return Pointer to the default context, or NULL if not set + * @deprecated Use dmenv_get_root_context instead */ DMOD_BUILTIN_API(dmenv, 1.0, dmenv_ctx_t, _get_default, (void)); diff --git a/src/dmenv.c b/src/dmenv.c index b86698f..30d9e9c 100644 --- a/src/dmenv.c +++ b/src/dmenv.c @@ -7,6 +7,10 @@ #define DMENV_MAGIC_NUMBER 0x444D454E // "DMEN" in hex #endif +#ifndef DMENV_CONTEXT_STACK_SIZE +#define DMENV_CONTEXT_STACK_SIZE 16 +#endif + /** * @brief Structure to hold an environment variable entry */ @@ -28,7 +32,16 @@ typedef struct dmenv_ctx size_t entry_count; } dmenv_ctx_internal_t; -static dmenv_ctx_t g_default_context = NULL; +/** + * @brief Global root context (previously named default context) + */ +static dmenv_ctx_t g_root_context = NULL; + +/** + * @brief Context stack for push/pop functionality + */ +static dmenv_ctx_t g_context_stack[DMENV_CONTEXT_STACK_SIZE]; +static size_t g_context_stack_top = 0; /** * @brief Helper function to find an entry by name in a context @@ -129,10 +142,25 @@ DMOD_INPUT_API_DECLARATION(dmenv, 1.0, void, _destroy, (dmenv_ctx_t ctx)) current = next; } - // If this is the default context, clear it - if (g_default_context == ctx) + // If this is the root context, clear it + if (g_root_context == ctx) { - g_default_context = NULL; + g_root_context = NULL; + } + + // Remove from context stack if present + for (size_t i = 0; i < g_context_stack_top; i++) + { + if (g_context_stack[i] == ctx) + { + // Shift remaining contexts down + for (size_t j = i; j < g_context_stack_top - 1; j++) + { + g_context_stack[j] = g_context_stack[j + 1]; + } + g_context_stack_top--; + break; + } } // Invalidate magic number @@ -153,7 +181,7 @@ DMOD_INPUT_API_DECLARATION(dmenv, 1.0, bool, _is_valid, (dmenv_ctx_t ctx)) return result; } -DMOD_INPUT_API_DECLARATION(dmenv, 1.0, void, _set_as_default, (dmenv_ctx_t ctx)) +DMOD_INPUT_API_DECLARATION(dmenv, 1.0, void, _set_root_context, (dmenv_ctx_t ctx)) { if (!dmenv_is_valid(ctx)) { @@ -162,14 +190,90 @@ DMOD_INPUT_API_DECLARATION(dmenv, 1.0, void, _set_as_default, (dmenv_ctx_t ctx)) } Dmod_EnterCritical(); - g_default_context = ctx; - DMOD_LOG_INFO("Set context %p as default\n", ctx); + g_root_context = ctx; + DMOD_LOG_INFO("Set context %p as root context\n", ctx); + Dmod_ExitCritical(); +} + +DMOD_INPUT_API_DECLARATION(dmenv, 1.0, dmenv_ctx_t, _get_root_context, (void)) +{ + return g_root_context; +} + +DMOD_INPUT_API_DECLARATION(dmenv, 1.0, bool, _push_context, (dmenv_ctx_t ctx)) +{ + if (!dmenv_is_valid(ctx)) + { + DMOD_LOG_ERROR("Invalid context\n"); + return false; + } + + Dmod_EnterCritical(); + + if (g_context_stack_top >= DMENV_CONTEXT_STACK_SIZE) + { + DMOD_LOG_ERROR("Context stack overflow\n"); + Dmod_ExitCritical(); + return false; + } + + g_context_stack[g_context_stack_top] = ctx; + g_context_stack_top++; + + DMOD_LOG_INFO("Pushed context %p onto stack (depth: %zu)\n", ctx, g_context_stack_top); + + Dmod_ExitCritical(); + return true; +} + +DMOD_INPUT_API_DECLARATION(dmenv, 1.0, dmenv_ctx_t, _pop_context, (void)) +{ + Dmod_EnterCritical(); + + if (g_context_stack_top == 0) + { + DMOD_LOG_INFO("Context stack is empty, nothing to pop\n"); + Dmod_ExitCritical(); + return NULL; + } + + g_context_stack_top--; + dmenv_ctx_t ctx = g_context_stack[g_context_stack_top]; + + DMOD_LOG_INFO("Popped context %p from stack (depth: %zu)\n", ctx, g_context_stack_top); + + Dmod_ExitCritical(); + return ctx; +} + +DMOD_INPUT_API_DECLARATION(dmenv, 1.0, dmenv_ctx_t, _get_current_context, (void)) +{ + Dmod_EnterCritical(); + + dmenv_ctx_t ctx; + if (g_context_stack_top > 0) + { + ctx = g_context_stack[g_context_stack_top - 1]; + } + else + { + ctx = g_root_context; + } + Dmod_ExitCritical(); + return ctx; +} + +DMOD_INPUT_API_DECLARATION(dmenv, 1.0, void, _set_as_default, (dmenv_ctx_t ctx)) +{ + // Deprecated: delegates to set_root_context for backward compatibility + dmenv_set_root_context(ctx); } DMOD_INPUT_API_DECLARATION(dmenv, 1.0, dmenv_ctx_t, _get_default, (void)) { - return g_default_context; + // Deprecated: delegates to get_root_context for backward compatibility + return dmenv_get_root_context(); } DMOD_INPUT_API_DECLARATION(dmenv, 1.0, bool, _set, (dmenv_ctx_t ctx, const char *name, const char *value)) @@ -463,7 +567,10 @@ DMOD_INPUT_API_DECLARATION(dmenv, 1.0, size_t, _count, (dmenv_ctx_t ctx)) #ifndef DMENV_DONT_IMPLEMENT_DMOD_API /** - * @brief Set an environment variable in the default context (DMOD API) + * @brief Set an environment variable in the current context (DMOD API) + * + * Uses the current context (top of stack if any contexts are pushed, + * otherwise the root context). * * @param Name Name of the environment variable * @param Value Value to set @@ -472,10 +579,10 @@ DMOD_INPUT_API_DECLARATION(dmenv, 1.0, size_t, _count, (dmenv_ctx_t ctx)) */ DMOD_INPUT_API_DECLARATION(Dmod, 1.0, int, _SetEnv, (const char *Name, const char *Value, int Overwrite)) { - dmenv_ctx_t ctx = dmenv_get_default(); + dmenv_ctx_t ctx = dmenv_get_current_context(); if (ctx == NULL) { - DMOD_LOG_ERROR("No default context set for Dmod_SetEnv\n"); + DMOD_LOG_ERROR("No context available for Dmod_SetEnv\n"); return -1; } @@ -496,17 +603,20 @@ DMOD_INPUT_API_DECLARATION(Dmod, 1.0, int, _SetEnv, (const char *Name, const cha } /** - * @brief Get an environment variable from the default context (DMOD API) + * @brief Get an environment variable from the current context (DMOD API) + * + * Uses the current context (top of stack if any contexts are pushed, + * otherwise the root context). * * @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(); + dmenv_ctx_t ctx = dmenv_get_current_context(); if (ctx == NULL) { - DMOD_LOG_ERROR("No default context set for Dmod_GetEnv\n"); + DMOD_LOG_ERROR("No context available for Dmod_GetEnv\n"); return NULL; } return dmenv_get(ctx, Name); diff --git a/tests/test_dmenv_unit.c b/tests/test_dmenv_unit.c index 7d3ff2b..2b67ad6 100644 --- a/tests/test_dmenv_unit.c +++ b/tests/test_dmenv_unit.c @@ -221,6 +221,139 @@ void test_default_context(void) { dmenv_destroy(ctx); } +void test_root_context(void) { + dmenv_ctx_t ctx = dmenv_create(NULL); + dmenv_set_root_context(ctx); + + dmenv_ctx_t retrieved = dmenv_get_root_context(); + TEST_ASSERT_EQUAL_PTR(ctx, retrieved); + + // Verify backward compatibility: get_default should return root context + dmenv_ctx_t default_ctx = dmenv_get_default(); + TEST_ASSERT_EQUAL_PTR(ctx, default_ctx); + + dmenv_destroy(ctx); +} + +void test_push_pop_context(void) { + dmenv_ctx_t root = dmenv_create(NULL); + dmenv_ctx_t ctx1 = dmenv_create(NULL); + dmenv_ctx_t ctx2 = dmenv_create(NULL); + + // Set root context + dmenv_set_root_context(root); + + // Initially, current context should be root + TEST_ASSERT_EQUAL_PTR(root, dmenv_get_current_context()); + + // Push ctx1 + TEST_ASSERT_TRUE(dmenv_push_context(ctx1)); + TEST_ASSERT_EQUAL_PTR(ctx1, dmenv_get_current_context()); + + // Push ctx2 + TEST_ASSERT_TRUE(dmenv_push_context(ctx2)); + TEST_ASSERT_EQUAL_PTR(ctx2, dmenv_get_current_context()); + + // Pop ctx2 + dmenv_ctx_t popped = dmenv_pop_context(); + TEST_ASSERT_EQUAL_PTR(ctx2, popped); + TEST_ASSERT_EQUAL_PTR(ctx1, dmenv_get_current_context()); + + // Pop ctx1 + popped = dmenv_pop_context(); + TEST_ASSERT_EQUAL_PTR(ctx1, popped); + TEST_ASSERT_EQUAL_PTR(root, dmenv_get_current_context()); + + // Pop from empty stack should return NULL + popped = dmenv_pop_context(); + TEST_ASSERT_NULL(popped); + + // Current context should still be root + TEST_ASSERT_EQUAL_PTR(root, dmenv_get_current_context()); + + dmenv_destroy(ctx2); + dmenv_destroy(ctx1); + dmenv_destroy(root); +} + +void test_push_invalid_context(void) { + TEST_ASSERT_FALSE(dmenv_push_context(NULL)); +} + +void test_current_context_without_root(void) { + // Clear root context first + dmenv_set_root_context(NULL); + + // Without root context and empty stack, get_current_context should return NULL + TEST_ASSERT_NULL(dmenv_get_current_context()); + + // Create and push a context + dmenv_ctx_t ctx = dmenv_create(NULL); + TEST_ASSERT_TRUE(dmenv_push_context(ctx)); + TEST_ASSERT_EQUAL_PTR(ctx, dmenv_get_current_context()); + + // Pop it + dmenv_pop_context(); + TEST_ASSERT_NULL(dmenv_get_current_context()); + + dmenv_destroy(ctx); +} + +void test_context_stack_variables(void) { + dmenv_ctx_t root = dmenv_create(NULL); + dmenv_ctx_t child = dmenv_create(NULL); + + dmenv_set_root_context(root); + + // Set variable in root context + dmenv_set(root, "ROOT_VAR", "root_value"); + dmenv_set(root, "SHARED", "root_shared"); + + // Set variable in child context + dmenv_set(child, "CHILD_VAR", "child_value"); + dmenv_set(child, "SHARED", "child_shared"); + + // Initially current is root + TEST_ASSERT_EQUAL_STRING("root_value", dmenv_get(dmenv_get_current_context(), "ROOT_VAR")); + TEST_ASSERT_EQUAL_STRING("root_shared", dmenv_get(dmenv_get_current_context(), "SHARED")); + + // Push child context + dmenv_push_context(child); + + // Now current is child + TEST_ASSERT_EQUAL_STRING("child_value", dmenv_get(dmenv_get_current_context(), "CHILD_VAR")); + TEST_ASSERT_EQUAL_STRING("child_shared", dmenv_get(dmenv_get_current_context(), "SHARED")); + TEST_ASSERT_NULL(dmenv_get(dmenv_get_current_context(), "ROOT_VAR")); // Not in child's direct context + + // Pop child + dmenv_pop_context(); + + // Back to root + TEST_ASSERT_EQUAL_STRING("root_value", dmenv_get(dmenv_get_current_context(), "ROOT_VAR")); + TEST_ASSERT_EQUAL_STRING("root_shared", dmenv_get(dmenv_get_current_context(), "SHARED")); + + dmenv_destroy(child); + dmenv_destroy(root); +} + +void test_destroy_removes_from_stack(void) { + dmenv_ctx_t root = dmenv_create(NULL); + dmenv_ctx_t ctx = dmenv_create(NULL); + + dmenv_set_root_context(root); + dmenv_push_context(ctx); + + TEST_ASSERT_EQUAL_PTR(ctx, dmenv_get_current_context()); + + // Destroy the pushed context + dmenv_destroy(ctx); + + // Current should now be root since ctx was removed from stack + TEST_ASSERT_EQUAL_PTR(root, dmenv_get_current_context()); + + dmenv_destroy(root); +} + int main(void) { UNITY_BEGIN(); @@ -244,6 +377,12 @@ int main(void) { RUN_TEST(test_inheritance); RUN_TEST(test_inheritance_override); RUN_TEST(test_default_context); + RUN_TEST(test_root_context); + RUN_TEST(test_push_pop_context); + RUN_TEST(test_push_invalid_context); + RUN_TEST(test_current_context_without_root); + RUN_TEST(test_context_stack_variables); + RUN_TEST(test_destroy_removes_from_stack); return UNITY_END(); } From 0f2dc537ade131d340e46ffc5e37bbaaf68278da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:06:20 +0000 Subject: [PATCH 4/8] refactor: extract common linker options in tests CMakeLists.txt Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- tests/CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8f96df3..3c0a3fc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,22 +22,25 @@ target_include_directories(test_common INTERFACE ) target_link_libraries(test_common INTERFACE dmod_inc) +# Common linker options for all tests +set(TEST_LINK_OPTIONS -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) + # Test: Simple test add_executable(test_simple test_simple.c) target_link_libraries(test_simple PRIVATE dmenv unity test_common dmod_system) -target_link_options(test_simple PRIVATE -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) +target_link_options(test_simple PRIVATE ${TEST_LINK_OPTIONS}) 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) -target_link_options(test_dmenv_unit PRIVATE -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) +target_link_options(test_dmenv_unit PRIVATE ${TEST_LINK_OPTIONS}) 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) -target_link_options(test_minimal PRIVATE -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) +target_link_options(test_minimal PRIVATE ${TEST_LINK_OPTIONS}) add_test(NAME test_minimal COMMAND test_minimal) # Enable coverage for test executables if requested From 31eb0550adb5dd41798babba5a87c60ab82afabb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:18:07 +0000 Subject: [PATCH 5/8] refactor: remove deprecated set_as_default and get_default functions Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- include/dmenv.h | 16 ---------------- src/dmenv.c | 12 ------------ tests/test_dmenv_unit.c | 15 --------------- 3 files changed, 43 deletions(-) diff --git a/include/dmenv.h b/include/dmenv.h index 992ac48..c014f1a 100644 --- a/include/dmenv.h +++ b/include/dmenv.h @@ -101,22 +101,6 @@ DMOD_BUILTIN_API(dmenv, 1.0, dmenv_ctx_t, _pop_context, (void)); */ DMOD_BUILTIN_API(dmenv, 1.0, dmenv_ctx_t, _get_current_context, (void)); -/** - * @brief Set the default context (deprecated, use dmenv_set_root_context instead) - * - * @param ctx Context to set as default - * @deprecated Use dmenv_set_root_context instead - */ -DMOD_BUILTIN_API(dmenv, 1.0, void, _set_as_default, (dmenv_ctx_t ctx)); - -/** - * @brief Get the default context (deprecated, use dmenv_get_root_context instead) - * - * @return Pointer to the default context, or NULL if not set - * @deprecated Use dmenv_get_root_context instead - */ -DMOD_BUILTIN_API(dmenv, 1.0, dmenv_ctx_t, _get_default, (void)); - /** * @brief Set an environment variable (string value) * diff --git a/src/dmenv.c b/src/dmenv.c index 30d9e9c..8ce9f87 100644 --- a/src/dmenv.c +++ b/src/dmenv.c @@ -264,18 +264,6 @@ DMOD_INPUT_API_DECLARATION(dmenv, 1.0, dmenv_ctx_t, _get_current_context, (void) return ctx; } -DMOD_INPUT_API_DECLARATION(dmenv, 1.0, void, _set_as_default, (dmenv_ctx_t ctx)) -{ - // Deprecated: delegates to set_root_context for backward compatibility - dmenv_set_root_context(ctx); -} - -DMOD_INPUT_API_DECLARATION(dmenv, 1.0, dmenv_ctx_t, _get_default, (void)) -{ - // Deprecated: delegates to get_root_context for backward compatibility - return dmenv_get_root_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)) diff --git a/tests/test_dmenv_unit.c b/tests/test_dmenv_unit.c index 2b67ad6..ed7b5da 100644 --- a/tests/test_dmenv_unit.c +++ b/tests/test_dmenv_unit.c @@ -211,16 +211,6 @@ void test_inheritance_override(void) { 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); -} - void test_root_context(void) { dmenv_ctx_t ctx = dmenv_create(NULL); dmenv_set_root_context(ctx); @@ -228,10 +218,6 @@ void test_root_context(void) { dmenv_ctx_t retrieved = dmenv_get_root_context(); TEST_ASSERT_EQUAL_PTR(ctx, retrieved); - // Verify backward compatibility: get_default should return root context - dmenv_ctx_t default_ctx = dmenv_get_default(); - TEST_ASSERT_EQUAL_PTR(ctx, default_ctx); - dmenv_destroy(ctx); } @@ -376,7 +362,6 @@ int main(void) { RUN_TEST(test_geti_decimal); RUN_TEST(test_inheritance); RUN_TEST(test_inheritance_override); - RUN_TEST(test_default_context); RUN_TEST(test_root_context); RUN_TEST(test_push_pop_context); RUN_TEST(test_push_invalid_context); From d8be37ac98bcb9b31ff377e8a60c7f9a6c03280e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:27:48 +0000 Subject: [PATCH 6/8] docs: update README with new context stack API Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- README.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d56c679..7f1c4a9 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ DMOD provides a modular architecture that makes embedded systems more flexible, - **Simple key-value storage**: Store configuration parameters as name-value pairs - **Context-based management**: Create multiple isolated environment contexts - **Context inheritance**: Child contexts can inherit variables from parent contexts +- **Context stack**: Push and pop contexts for temporary context switching - **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 @@ -52,6 +53,8 @@ dmenv is ideal for storing application configuration, runtime parameters, and in - ✅ **Context-based management**: Create multiple isolated environment contexts - ✅ **Context inheritance**: Child contexts can inherit variables from parent contexts +- ✅ **Context stack**: Push and pop contexts for temporary context switching +- ✅ **Root context**: Set a global root context as the base fallback - ✅ **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 @@ -149,7 +152,8 @@ dmenv includes comprehensive test suites: - Integer operations (seti/geti) - Context inheritance - Context override behavior - - Default context management + - Root context management + - Context stack push/pop operations - Multiple variable stress tests ### Running Tests @@ -295,6 +299,44 @@ void reset_example(void) { } ``` +### Context Stack Example + +```c +#include "dmenv.h" + +void context_stack_example(void) { + // Create root and temporary contexts + dmenv_ctx_t root = dmenv_create(NULL); + dmenv_ctx_t temp = dmenv_create(NULL); + + // Set root context as the base + dmenv_set_root_context(root); + dmenv_set(root, "MODE", "production"); + + // Get current context (returns root) + dmenv_ctx_t current = dmenv_get_current_context(); + printf("Mode: %s\n", dmenv_get(current, "MODE")); // Prints: production + + // Push temporary context for testing + dmenv_push_context(temp); + dmenv_set(temp, "MODE", "testing"); + + // Get current context (returns temp) + current = dmenv_get_current_context(); + printf("Mode: %s\n", dmenv_get(current, "MODE")); // Prints: testing + + // Pop temporary context + dmenv_pop_context(); + + // Get current context (returns root again) + current = dmenv_get_current_context(); + printf("Mode: %s\n", dmenv_get(current, "MODE")); // Prints: production + + dmenv_destroy(temp); + dmenv_destroy(root); +} +``` + ### Integration Example ```c @@ -393,27 +435,62 @@ Check if a context is valid. - **Returns:** `true` if valid, `false` otherwise - **Thread-safe:** Yes -#### `dmenv_set_as_default` +#### `dmenv_set_root_context` + +```c +void dmenv_set_root_context(dmenv_ctx_t ctx); +``` + +Set the root context. The root context serves as the base context when no other contexts have been pushed onto the context stack. + +- **Parameters:** + - `ctx`: Context to set as root context +- **Thread-safe:** Yes + +#### `dmenv_get_root_context` + +```c +dmenv_ctx_t dmenv_get_root_context(void); +``` + +Get the root context. + +- **Returns:** Pointer to the root context, or NULL if not set +- **Thread-safe:** Yes + +#### `dmenv_push_context` ```c -void dmenv_set_as_default(dmenv_ctx_t ctx); +bool dmenv_push_context(dmenv_ctx_t ctx); ``` -Set the default context. +Push a context onto the context stack. The pushed context becomes the current context. - **Parameters:** - - `ctx`: Context to set as default + - `ctx`: Context to push onto the stack +- **Returns:** `true` if the context was pushed successfully, `false` otherwise (e.g., stack overflow or invalid context) +- **Thread-safe:** Yes + +#### `dmenv_pop_context` + +```c +dmenv_ctx_t dmenv_pop_context(void); +``` + +Pop the current context from the context stack. After popping, the previous context on the stack becomes the current context. If the stack is empty, the root context becomes the current context. + +- **Returns:** Pointer to the popped context, or NULL if the stack was empty - **Thread-safe:** Yes -#### `dmenv_get_default` +#### `dmenv_get_current_context` ```c -dmenv_ctx_t dmenv_get_default(void); +dmenv_ctx_t dmenv_get_current_context(void); ``` -Get the default context. +Get the current context. Returns the top context from the stack if any contexts have been pushed, otherwise returns the root context. -- **Returns:** Pointer to the default context, or NULL if not set +- **Returns:** Pointer to the current context, or NULL if no context is set - **Thread-safe:** Yes ### Variable Operations From f29b3b433bf9ad6b7a201eb97d7905473077c57c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:39:40 +0000 Subject: [PATCH 7/8] fix: skip create_library_makefile in CI environment Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fabbfcf..796359f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,7 +77,11 @@ if(ENABLE_COVERAGE) target_link_options(${MODULE_NAME} PRIVATE --coverage) endif() -create_library_makefile(${MODULE_NAME}) +# Only run create_library_makefile if not in CI environment +# The memory_analysis.cmake script path has issues in FetchContent context +if(NOT DEFINED ENV{CI} AND COMMAND create_library_makefile) + create_library_makefile(${MODULE_NAME}) +endif() # ====================================================================== # Tests From a3b53bade5a4bc440be8363861902b82e6d2f2ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:58:53 +0000 Subject: [PATCH 8/8] fix: enable coverage by skipping linker script and adding stubs Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- tests/CMakeLists.txt | 30 ++++++++++++++++++++++-------- tests/coverage_stubs.c | 15 +++++++++++++++ 2 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 tests/coverage_stubs.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3c0a3fc..5eb665c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,25 +22,39 @@ target_include_directories(test_common INTERFACE ) target_link_libraries(test_common INTERFACE dmod_inc) -# Common linker options for all tests -set(TEST_LINK_OPTIONS -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) +# Common linker options for all tests (only when not using coverage) +# The custom linker script is for embedded targets and interferes with coverage data generation +if(NOT ENABLE_COVERAGE) + set(TEST_LINK_OPTIONS -L ${DMOD_DIR}/scripts -T ${DMOD_DIR}/scripts/main.ld) +endif() + +# Coverage stubs - provides symbols normally defined by the linker script +if(ENABLE_COVERAGE) + set(COVERAGE_STUBS coverage_stubs.c) +endif() # Test: Simple test -add_executable(test_simple test_simple.c) +add_executable(test_simple test_simple.c ${COVERAGE_STUBS}) target_link_libraries(test_simple PRIVATE dmenv unity test_common dmod_system) -target_link_options(test_simple PRIVATE ${TEST_LINK_OPTIONS}) +if(TEST_LINK_OPTIONS) + target_link_options(test_simple PRIVATE ${TEST_LINK_OPTIONS}) +endif() add_test(NAME test_simple COMMAND test_simple) # Test: Unit tests -add_executable(test_dmenv_unit test_dmenv_unit.c) +add_executable(test_dmenv_unit test_dmenv_unit.c ${COVERAGE_STUBS}) target_link_libraries(test_dmenv_unit PRIVATE dmenv unity test_common dmod_system) -target_link_options(test_dmenv_unit PRIVATE ${TEST_LINK_OPTIONS}) +if(TEST_LINK_OPTIONS) + target_link_options(test_dmenv_unit PRIVATE ${TEST_LINK_OPTIONS}) +endif() add_test(NAME test_dmenv_unit COMMAND test_dmenv_unit) # Test: Minimal test -add_executable(test_minimal test_minimal.c) +add_executable(test_minimal test_minimal.c ${COVERAGE_STUBS}) target_link_libraries(test_minimal PRIVATE dmenv unity test_common dmod_system) -target_link_options(test_minimal PRIVATE ${TEST_LINK_OPTIONS}) +if(TEST_LINK_OPTIONS) + target_link_options(test_minimal PRIVATE ${TEST_LINK_OPTIONS}) +endif() add_test(NAME test_minimal COMMAND test_minimal) # Enable coverage for test executables if requested diff --git a/tests/coverage_stubs.c b/tests/coverage_stubs.c new file mode 100644 index 0000000..e9ad6a1 --- /dev/null +++ b/tests/coverage_stubs.c @@ -0,0 +1,15 @@ +/** + * @file coverage_stubs.c + * @brief Stub symbols for coverage builds without the embedded linker script. + * + * When building with coverage enabled, we skip the custom linker script that + * defines these symbols. This file provides stub definitions to satisfy the linker. + */ + +/* Stub sections for DMOD input/output APIs */ +char __dmod_inputs_start = 0; +char __dmod_inputs_end = 0; +char __dmod_inputs_size = 0; +char __dmod_outputs_start = 0; +char __dmod_outputs_end = 0; +char __dmod_outputs_size = 0;