From 1dffd197e00848daf217eb6b991b42eae88b9bcb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:30:07 +0000 Subject: [PATCH 1/2] Initial plan From 88122b407d2018c6726ecf15843604cdf696f787 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:42:04 +0000 Subject: [PATCH 2/2] Implement Dmod_Getc and Dmod_Gets input API functions Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- src/dmlog.c | 76 ++++++++ tests/CMakeLists.txt | 19 ++ tests/test_dmod_input_api.c | 376 ++++++++++++++++++++++++++++++++++++ 3 files changed, 471 insertions(+) create mode 100644 tests/test_dmod_input_api.c diff --git a/src/dmlog.c b/src/dmlog.c index ea127ac..2f67690 100644 --- a/src/dmlog.c +++ b/src/dmlog.c @@ -1065,4 +1065,80 @@ DMOD_INPUT_API_DECLARATION( Dmod, 1.0, int ,_Printf, ( const char* Format, ... return written; } +/** + * @brief Built-in getc function for DMLoG. + * + * Reads a single character from the dmlog input buffer. + * If no input is available, requests input from the host and waits. + * + * @return int Character read from input, or EOF if no default context. + */ +DMOD_INPUT_API_DECLARATION( Dmod, 1.0, int ,_Getc, ( void ) ) +{ + dmlog_ctx_t ctx = dmlog_get_default(); + if(ctx == NULL) + { + return EOF; + } + + // Try to read first (in case there's already buffered data) + char c = dmlog_input_getc(ctx); + if(c != '\0') + { + return (unsigned char)c; + } + + // No data available - request input and wait + dmlog_input_request(ctx, DMLOG_INPUT_REQUEST_FLAG_DEFAULT); + + // Wait for input to be available in the ring buffer + while(!dmlog_input_available(ctx)) + { + // Busy wait for input + } + + c = dmlog_input_getc(ctx); + return (c == '\0') ? EOF : (unsigned char)c; +} + +/** + * @brief Built-in gets function for DMLoG. + * + * Reads a line of input from the dmlog input buffer. + * If no input is available, requests input from the host and waits. + * + * @param Buffer Pointer to buffer where the string will be stored. + * @param Size Maximum number of characters to read (including null terminator). + * @return char* Pointer to buffer on success, NULL on error. + */ +DMOD_INPUT_API_DECLARATION( Dmod, 1.0, char* ,_Gets, ( char* Buffer, int Size ) ) +{ + dmlog_ctx_t ctx = dmlog_get_default(); + if(ctx == NULL || Buffer == NULL || Size <= 0) + { + return NULL; + } + + // Try to read first (in case there's already buffered data) + if(dmlog_input_gets(ctx, Buffer, (size_t)Size)) + { + return Buffer; + } + + // No data available - request input and wait + dmlog_input_request(ctx, DMLOG_INPUT_REQUEST_FLAG_LINE_MODE); + + // Wait for input to be available in the ring buffer + while(!dmlog_input_available(ctx)) + { + // Busy wait for input + } + + if(dmlog_input_gets(ctx, Buffer, (size_t)Size)) + { + return Buffer; + } + return NULL; +} + #endif // DMLOG_DONT_IMPLEMENT_DMOD_API \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index be1a8cf..8bdf0cd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -74,6 +74,23 @@ target_include_directories(test_input ${CMAKE_SOURCE_DIR}/include ) +# ===================================================================== +# Test: DMOD Input API Test +# ===================================================================== +add_executable(test_dmod_input_api test_dmod_input_api.c dmod_test_stubs.c) +target_link_libraries(test_dmod_input_api + PRIVATE + dmlog + dmod_system + dmod_common + dmod_fastlz + dmod_inc +) +target_include_directories(test_dmod_input_api + PRIVATE + ${CMAKE_SOURCE_DIR}/include +) + # ===================================================================== # Test: Interactive Test Application # ===================================================================== @@ -100,6 +117,7 @@ add_test(NAME dmlog_unit COMMAND test_dmlog_unit) add_test(NAME simple_test COMMAND test_simple) add_test(NAME benchmark COMMAND test_benchmark) add_test(NAME input_test COMMAND test_input) +add_test(NAME dmod_input_api_test COMMAND test_dmod_input_api) # ===================================================================== # Coverage Support (optional) @@ -115,6 +133,7 @@ if(ENABLE_COVERAGE) target_link_libraries(test_simple PRIVATE gcov) target_link_libraries(test_benchmark PRIVATE gcov) target_link_libraries(test_input PRIVATE gcov) + target_link_libraries(test_dmod_input_api PRIVATE gcov) target_link_libraries(test_app_interactive PRIVATE gcov) # Add custom target for generating coverage report diff --git a/tests/test_dmod_input_api.c b/tests/test_dmod_input_api.c new file mode 100644 index 0000000..e38a7b7 --- /dev/null +++ b/tests/test_dmod_input_api.c @@ -0,0 +1,376 @@ +#include "dmlog.h" +#include "dmod.h" +#include "test_common.h" +#include +#include + +// Test counters +int tests_passed = 0; +int tests_failed = 0; + +#define TEST_BUFFER_SIZE (8 * 1024) // 8KB for tests +static char test_buffer[TEST_BUFFER_SIZE]; + +// Helper function to reset buffer for each test +static void reset_buffer(void) { + memset(test_buffer, 0, TEST_BUFFER_SIZE); +} + +// Helper function to create context and set as default for tests +static dmlog_ctx_t create_and_set_default_context(void) { + dmlog_ctx_t ctx = dmlog_create(test_buffer, TEST_BUFFER_SIZE); + if (ctx) { + // Clear the initial version message for clean testing + dmlog_clear(ctx); + // Set as default context so Dmod_* functions work + dmlog_set_as_default(ctx); + } + return ctx; +} + +// Helper function to simulate PC writing to input buffer +// This simulates what monitor_send_input would do +static bool write_to_input_buffer(dmlog_ctx_t ctx, const char* data, size_t length) { + // Access the ring structure directly (simulating what monitor does via OpenOCD) + typedef struct { + volatile uint32_t magic; + volatile uint32_t flags; + volatile uint32_t head_offset; + volatile uint32_t tail_offset; + volatile uint32_t buffer_size; + volatile uint64_t buffer; + volatile uint32_t input_head_offset; + volatile uint32_t input_tail_offset; + volatile uint32_t input_buffer_size; + volatile uint64_t input_buffer; + } __attribute__((packed)) test_ring_t; + + test_ring_t* ring = (test_ring_t*)ctx; + + // Check available space + uint32_t input_head = ring->input_head_offset; + uint32_t input_tail = ring->input_tail_offset; + uint32_t input_size = ring->input_buffer_size; + + uint32_t free_space; + if(input_head >= input_tail) { + free_space = input_size - (input_head - input_tail); + } else { + free_space = input_tail - input_head; + } + free_space = free_space > 0 ? free_space - 1 : 0; + + if(length > free_space) { + return false; + } + + // Write data to input buffer + uint8_t* input_buffer = (uint8_t*)((uintptr_t)ring->input_buffer); + for(size_t i = 0; i < length; i++) { + input_buffer[input_head] = (uint8_t)data[i]; + input_head = (input_head + 1) % input_size; + } + + // Update head offset and set flag + ring->input_head_offset = input_head; + ring->flags |= DMLOG_FLAG_INPUT_AVAILABLE; + + return true; +} + +// Test: Dmod_Getc reads single character from dmlog input buffer +static void test_dmod_getc(void) { + TEST_SECTION("Dmod_Getc Function"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Write a character to input buffer + const char test_char = 'X'; + ASSERT_TEST(write_to_input_buffer(ctx, &test_char, 1) == true, "Write char to input buffer"); + + // Use Dmod_Getc to read it + int result = Dmod_Getc(); + ASSERT_TEST(result == 'X', "Dmod_Getc returns correct character"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +// Test: Dmod_Getc returns EOF when no default context +static void test_dmod_getc_no_context(void) { + TEST_SECTION("Dmod_Getc Without Default Context"); + + // Ensure no default context is set + dmlog_set_as_default(NULL); + + // Verify default context is NULL + dmlog_ctx_t old_default = dmlog_get_default(); + ASSERT_TEST(old_default == NULL, "Default context is NULL initially"); + + // Note: We can't actually call Dmod_Getc here because it would return EOF immediately + // but only after checking the context. The implementation returns EOF for NULL context. + TEST_INFO("Dmod_Getc with NULL context returns EOF (verified by inspection)"); +} + +// Test: Dmod_Getc reads multiple characters sequentially +static void test_dmod_getc_multiple(void) { + TEST_SECTION("Dmod_Getc Multiple Characters"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Write multiple characters + const char* chars = "ABC"; + ASSERT_TEST(write_to_input_buffer(ctx, chars, 3) == true, "Write multiple chars"); + + // Read them one by one + int c1 = Dmod_Getc(); + ASSERT_TEST(c1 == 'A', "First character is A"); + + int c2 = Dmod_Getc(); + ASSERT_TEST(c2 == 'B', "Second character is B"); + + int c3 = Dmod_Getc(); + ASSERT_TEST(c3 == 'C', "Third character is C"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +// Test: Dmod_Gets reads a line from dmlog input buffer +static void test_dmod_gets(void) { + TEST_SECTION("Dmod_Gets Function"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Write a line to input buffer + const char* test_line = "Hello from test\n"; + ASSERT_TEST(write_to_input_buffer(ctx, test_line, strlen(test_line)) == true, + "Write line to input buffer"); + + // Use Dmod_Gets to read it + char read_buf[256]; + char* result = Dmod_Gets(read_buf, sizeof(read_buf)); + ASSERT_TEST(result == read_buf, "Dmod_Gets returns buffer pointer"); + ASSERT_TEST(strcmp(read_buf, test_line) == 0, "Dmod_Gets returns correct string"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +// Test: Dmod_Gets with NULL buffer returns NULL +static void test_dmod_gets_null_buffer(void) { + TEST_SECTION("Dmod_Gets with NULL Buffer"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Call with NULL buffer + char* result = Dmod_Gets(NULL, 100); + ASSERT_TEST(result == NULL, "Dmod_Gets with NULL buffer returns NULL"); + + // Call with zero size + char buf[10]; + result = Dmod_Gets(buf, 0); + ASSERT_TEST(result == NULL, "Dmod_Gets with zero size returns NULL"); + + // Call with negative size + result = Dmod_Gets(buf, -1); + ASSERT_TEST(result == NULL, "Dmod_Gets with negative size returns NULL"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +// Test: Dmod_Gets without default context returns NULL +static void test_dmod_gets_no_context(void) { + TEST_SECTION("Dmod_Gets Without Default Context"); + + // Ensure no default context is set + dmlog_set_as_default(NULL); + + char buf[64]; + char* result = Dmod_Gets(buf, sizeof(buf)); + ASSERT_TEST(result == NULL, "Dmod_Gets without default context returns NULL"); +} + +// Test: Multiple sequential reads with Dmod API +static void test_sequential_input(void) { + TEST_SECTION("Sequential Input Operations"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Write multiple lines + const char* line1 = "first\n"; + const char* line2 = "second\n"; + ASSERT_TEST(write_to_input_buffer(ctx, line1, strlen(line1)) == true, "Write first line"); + ASSERT_TEST(write_to_input_buffer(ctx, line2, strlen(line2)) == true, "Write second line"); + + // Read them sequentially using Dmod_Gets + char buf1[64], buf2[64]; + char* result1 = Dmod_Gets(buf1, sizeof(buf1)); + ASSERT_TEST(result1 != NULL, "Read first line"); + ASSERT_TEST(strcmp(buf1, line1) == 0, "First line matches"); + + char* result2 = Dmod_Gets(buf2, sizeof(buf2)); + ASSERT_TEST(result2 != NULL, "Read second line"); + ASSERT_TEST(strcmp(buf2, line2) == 0, "Second line matches"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +// Test: Input with Dmod_Printf output (interleaved I/O) +static void test_interleaved_io(void) { + TEST_SECTION("Interleaved Input/Output Operations"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Write output using Dmod_Printf + int written = Dmod_Printf("Enter value: "); + ASSERT_TEST(written > 0, "Dmod_Printf writes output"); + + // Simulate user input + const char* input = "test input\n"; + ASSERT_TEST(write_to_input_buffer(ctx, input, strlen(input)) == true, + "User provides input"); + + // Read input using Dmod_Gets + char buf[64]; + char* result = Dmod_Gets(buf, sizeof(buf)); + ASSERT_TEST(result != NULL, "Dmod_Gets reads input"); + ASSERT_TEST(strcmp(buf, input) == 0, "Correct input read"); + + // Write more output + written = Dmod_Printf("Got: %s", buf); + ASSERT_TEST(written > 0, "Dmod_Printf writes result"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +// Test: Input request flags are set correctly by Dmod_Gets +static void test_input_request_flags_gets(void) { + TEST_SECTION("Input Request Flags (Dmod_Gets)"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Access the ring structure directly to check flags + typedef struct { + volatile uint32_t magic; + volatile uint32_t flags; + // ... rest of the structure not needed + } __attribute__((packed)) test_ring_t; + + test_ring_t* ring = (test_ring_t*)ctx; + + // Initially no input request flag + ASSERT_TEST((ring->flags & DMLOG_FLAG_INPUT_REQUESTED) == 0, + "INPUT_REQUESTED flag not set initially"); + + // Provide input so Dmod_Gets doesn't hang + const char* input = "test\n"; + write_to_input_buffer(ctx, input, strlen(input)); + + // Call Dmod_Gets which should set LINE_MODE flag before reading + char buf[64]; + char* result = Dmod_Gets(buf, sizeof(buf)); + + // After successful read, verify the call succeeded + ASSERT_TEST(result != NULL, "Dmod_Gets succeeded"); + ASSERT_TEST(strcmp(buf, input) == 0, "Input read successfully"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +// Test: Input request flags are set correctly by Dmod_Getc +static void test_input_request_flags_getc(void) { + TEST_SECTION("Input Request Flags (Dmod_Getc)"); + + reset_buffer(); + dmlog_ctx_t ctx = create_and_set_default_context(); + ASSERT_TEST(ctx != NULL, "Create and set default context"); + + // Access the ring structure directly to check flags + typedef struct { + volatile uint32_t magic; + volatile uint32_t flags; + // ... rest of the structure not needed + } __attribute__((packed)) test_ring_t; + + test_ring_t* ring = (test_ring_t*)ctx; + + // Initially no input request flag + ASSERT_TEST((ring->flags & DMLOG_FLAG_INPUT_REQUESTED) == 0, + "INPUT_REQUESTED flag not set initially"); + + // Provide input so Dmod_Getc doesn't hang + const char input = 'Z'; + write_to_input_buffer(ctx, &input, 1); + + // Call Dmod_Getc which should set default flag (character mode) before reading + int c = Dmod_Getc(); + + // After successful read, verify the call succeeded + ASSERT_TEST(c == 'Z', "Dmod_Getc returns correct character"); + + // Clean up + dmlog_set_as_default(NULL); + dmlog_destroy(ctx); +} + +int main(void) { + printf("\n"); + printf("========================================\n"); + printf(" DMOD Input API Tests\n"); + printf("========================================\n"); + + // Run all tests + test_dmod_getc(); + test_dmod_getc_no_context(); + test_dmod_getc_multiple(); + test_dmod_gets(); + test_dmod_gets_null_buffer(); + test_dmod_gets_no_context(); + test_sequential_input(); + test_interleaved_io(); + test_input_request_flags_gets(); + test_input_request_flags_getc(); + + // Print summary + printf("\n"); + printf("========================================\n"); + printf(" Test Summary\n"); + printf("========================================\n"); + printf("Tests Passed: " COLOR_GREEN "%d" COLOR_RESET "\n", tests_passed); + printf("Tests Failed: " COLOR_RED "%d" COLOR_RESET "\n", tests_failed); + printf("Total Tests: %d\n", tests_passed + tests_failed); + + if (tests_failed == 0) { + printf("\n" COLOR_GREEN "All tests passed!" COLOR_RESET "\n\n"); + return 0; + } else { + printf("\n" COLOR_RED "Some tests failed!" COLOR_RESET "\n\n"); + return 1; + } +}