From 49f091b9f24d26f3a0b922de426fd6c20c524bbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:30:01 +0000 Subject: [PATCH 1/5] Initial plan From 221b1deb185f1922d189803fa1bf712d75270305 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:35:49 +0000 Subject: [PATCH 2/5] Add Google Test for C API with comprehensive test coverage Co-authored-by: draugvar <12036000+draugvar@users.noreply.github.com> --- CMakeLists.txt | 18 ++- test/test_c_api_gtest.cpp | 302 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 test/test_c_api_gtest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4341c0b..a7929a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ set_target_properties(gtest_main PROPERTIES MSVC_RUNTIME_LIBRARY MultiThreaded$< # 'Unit_Tests_run' is the target name # 'UNIT_SOURCE' are source files with tests -file(GLOB UNIT_SOURCE test/*.cpp test/*.h src/*.cpp include/*.h) +file(GLOB UNIT_SOURCE test/unit_test.cpp test/unit_test.h src/*.cpp include/*.h) set(UNIT_TEST notifly_unit_test) add_executable(${UNIT_TEST} ${UNIT_SOURCE}) @@ -90,6 +90,22 @@ set_target_properties(${C_EXAMPLE} LINKER_LANGUAGE C ) +# C Interface Google Test +set(C_GTEST notifly_c_gtest) +add_executable(${C_GTEST} test/test_c_api_gtest.cpp) +target_link_libraries(${C_GTEST} PRIVATE + ${PROJECT_NAME}_c + ${CMAKE_THREAD_LIBS_INIT} + $<$:dl> + gtest + gtest_main) +target_include_directories(${C_GTEST} PRIVATE include) +set_target_properties(${C_GTEST} + PROPERTIES + OUTPUT_NAME ${C_GTEST} + MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug> + ) + # Function to copy a file only if it does not exist or is different function(move_file source_file destination_dir) # Check if the source file exists diff --git a/test/test_c_api_gtest.cpp b/test/test_c_api_gtest.cpp new file mode 100644 index 0000000..a783f32 --- /dev/null +++ b/test/test_c_api_gtest.cpp @@ -0,0 +1,302 @@ +/* + * test_c_api_gtest.cpp + * Google Test based tests for the notifly C interface + * + * This file tests the C API using Google Test framework to provide + * structured test reporting and better integration with CI/CD systems. + */ + +#include +extern "C" { +#include "notifly_c.h" +} +#include // for usleep +#include + +// Test data structure +typedef struct { + int value; + char message[100]; +} test_data_t; + +// Global variables to track callback invocations +static int g_callback_count = 0; +static int g_last_notification_id = -1; +static test_data_t g_last_received_data = {0, ""}; +static void* g_last_user_data = nullptr; + +// Test callback function +void test_callback(int notification_id, void* data, void* user_data) { + g_callback_count++; + g_last_notification_id = notification_id; + g_last_user_data = user_data; + + if (data) { + test_data_t* test_data = (test_data_t*)data; + g_last_received_data = *test_data; + } +} + +void simple_callback(int notification_id, void* data, void* user_data) { + g_callback_count++; + g_last_notification_id = notification_id; + g_last_user_data = user_data; +} + +// Test fixture class for C API tests +class NotiflyCAI : public ::testing::Test { +protected: + void SetUp() override { + // Reset global state before each test + g_callback_count = 0; + g_last_notification_id = -1; + g_last_received_data = {0, ""}; + g_last_user_data = nullptr; + } + + void TearDown() override { + // Clean up any remaining observers after each test + // This ensures tests don't interfere with each other + } +}; + +// Test version information +TEST_F(NotiflyCAI, VersionConstants) { + // Test that version constants are defined + EXPECT_EQ(NOTIFLY_C_VERSION_MAJOR, 1); + EXPECT_EQ(NOTIFLY_C_VERSION_MINOR, 0); + EXPECT_EQ(NOTIFLY_C_VERSION_PATCH, 0); +} + +// Test basic functionality +TEST_F(NotiflyCAI, BasicFunctionality) { + // Get default handle + notifly_handle handle = notifly_default(); + ASSERT_NE(handle, nullptr) << "Could not get default handle"; + + // Add observer + const char* user_data = "test user data"; + int observer_id = notifly_add_observer(handle, 1001, test_callback, (void*)user_data); + ASSERT_GT(observer_id, 0) << "Could not add observer, result=" << observer_id + << " (" << notifly_result_to_string(observer_id) << ")"; + + // Post notification with data + test_data_t test_data = {42, "Hello from Google Test!"}; + int result = notifly_post_notification(handle, 1001, &test_data); + ASSERT_GT(result, 0) << "Could not post notification, result=" << result + << " (" << notifly_result_to_string(result) << ")"; + EXPECT_EQ(result, 1) << "Expected 1 observer notified"; + + // Verify callback was called correctly + EXPECT_EQ(g_callback_count, 1) << "Callback should have been called once"; + EXPECT_EQ(g_last_notification_id, 1001) << "Wrong notification ID in callback"; + EXPECT_EQ(g_last_received_data.value, 42) << "Wrong data value received"; + EXPECT_STREQ(g_last_received_data.message, "Hello from Google Test!") << "Wrong message received"; + EXPECT_EQ(g_last_user_data, (void*)user_data) << "Wrong user data received"; + + // Remove observer + result = notifly_remove_observer(handle, observer_id); + EXPECT_EQ(result, NOTIFLY_SUCCESS) << "Could not remove observer, result=" << result + << " (" << notifly_result_to_string(result) << ")"; +} + +// Test multiple observers +TEST_F(NotiflyCAI, MultipleObservers) { + notifly_handle handle = notifly_default(); + ASSERT_NE(handle, nullptr); + + // Add multiple observers + int observer1 = notifly_add_observer(handle, 1002, simple_callback, nullptr); + int observer2 = notifly_add_observer(handle, 1002, simple_callback, nullptr); + int observer3 = notifly_add_observer(handle, 1002, simple_callback, nullptr); + + ASSERT_GT(observer1, 0) << "Could not add observer1"; + ASSERT_GT(observer2, 0) << "Could not add observer2"; + ASSERT_GT(observer3, 0) << "Could not add observer3"; + + // Verify observers have different IDs + EXPECT_NE(observer1, observer2); + EXPECT_NE(observer1, observer3); + EXPECT_NE(observer2, observer3); + + // Post notification + int result = notifly_post_notification(handle, 1002, nullptr); + EXPECT_EQ(result, 3) << "Expected 3 observers notified"; + EXPECT_EQ(g_callback_count, 3) << "Expected 3 callbacks"; + + // Remove all observers + result = notifly_remove_all_observers(handle, 1002); + EXPECT_EQ(result, 3) << "Expected 3 observers removed"; +} + +// Test asynchronous notification +TEST_F(NotiflyCAI, AsyncNotification) { + notifly_handle handle = notifly_default(); + ASSERT_NE(handle, nullptr); + + int observer_id = notifly_add_observer(handle, 1003, simple_callback, nullptr); + ASSERT_GT(observer_id, 0) << "Could not add observer"; + + // Post async notification + int result = notifly_post_notification_async(handle, 1003, nullptr); + EXPECT_EQ(result, 1) << "Expected 1 observer notified async"; + + // Wait for async callback + usleep(100000); // 100ms + + EXPECT_EQ(g_callback_count, 1) << "Expected 1 async callback"; + EXPECT_EQ(g_last_notification_id, 1003) << "Wrong notification ID in async callback"; + + // Cleanup + notifly_remove_observer(handle, observer_id); +} + +// Test instance creation and destruction +TEST_F(NotiflyCAI, InstanceCreation) { + // Create custom instance + notifly_handle handle = notifly_create(); + ASSERT_NE(handle, nullptr) << "Could not create instance"; + + int observer_id = notifly_add_observer(handle, 1004, simple_callback, nullptr); + ASSERT_GT(observer_id, 0) << "Could not add observer to custom instance"; + + int result = notifly_post_notification(handle, 1004, nullptr); + EXPECT_EQ(result, 1) << "Expected 1 observer notified"; + EXPECT_EQ(g_callback_count, 1) << "Expected 1 callback"; + + // Destroy instance (this should clean up observers automatically) + notifly_destroy(handle); +} + +// Test error handling +TEST_F(NotiflyCAI, ErrorHandling) { + // Test invalid handle + int result = notifly_add_observer(nullptr, 1005, simple_callback, nullptr); + EXPECT_EQ(result, NOTIFLY_INVALID_HANDLE) << "NULL handle should be rejected"; + + // Test NULL callback + notifly_handle handle = notifly_default(); + result = notifly_add_observer(handle, 1005, nullptr, nullptr); + EXPECT_EQ(result, NOTIFLY_INVALID_HANDLE) << "NULL callback should be rejected"; + + // Test removing non-existent observer + result = notifly_remove_observer(handle, 99999); + EXPECT_EQ(result, NOTIFLY_OBSERVER_NOT_FOUND) << "Non-existent observer should return error"; + + // Test posting to non-existent notification + result = notifly_post_notification(handle, 99999, nullptr); + EXPECT_EQ(result, NOTIFLY_NOTIFICATION_NOT_FOUND) << "Non-existent notification should return error"; + + // Test other invalid handle cases + result = notifly_remove_observer(nullptr, 1); + EXPECT_EQ(result, NOTIFLY_INVALID_HANDLE) << "NULL handle should be rejected for remove_observer"; + + result = notifly_post_notification(nullptr, 1005, nullptr); + EXPECT_EQ(result, NOTIFLY_INVALID_HANDLE) << "NULL handle should be rejected for post_notification"; + + result = notifly_post_notification_async(nullptr, 1005, nullptr); + EXPECT_EQ(result, NOTIFLY_INVALID_HANDLE) << "NULL handle should be rejected for post_notification_async"; + + result = notifly_remove_all_observers(nullptr, 1005); + EXPECT_EQ(result, NOTIFLY_INVALID_HANDLE) << "NULL handle should be rejected for remove_all_observers"; +} + +// Test result string conversion +TEST_F(NotiflyCAI, ResultToString) { + EXPECT_STREQ(notifly_result_to_string(NOTIFLY_SUCCESS), "Success"); + EXPECT_STREQ(notifly_result_to_string(NOTIFLY_OBSERVER_NOT_FOUND), "Observer not found"); + EXPECT_STREQ(notifly_result_to_string(NOTIFLY_NOTIFICATION_NOT_FOUND), "Notification not found"); + EXPECT_STREQ(notifly_result_to_string(NOTIFLY_PAYLOAD_TYPE_NOT_MATCH), "Payload type mismatch"); + EXPECT_STREQ(notifly_result_to_string(NOTIFLY_NO_MORE_OBSERVER_IDS), "No more observer IDs available"); + EXPECT_STREQ(notifly_result_to_string(NOTIFLY_INVALID_HANDLE), "Invalid handle"); + EXPECT_STREQ(notifly_result_to_string(999), "Unknown error"); +} + +// Test data passing with different structures +TEST_F(NotiflyCAI, DataPassingVariousTypes) { + notifly_handle handle = notifly_default(); + ASSERT_NE(handle, nullptr); + + // Test with integer + int observer_id = notifly_add_observer(handle, 2001, test_callback, nullptr); + ASSERT_GT(observer_id, 0); + + int int_data = 123; + int result = notifly_post_notification(handle, 2001, &int_data); + EXPECT_EQ(result, 1); + EXPECT_EQ(g_callback_count, 1); + + notifly_remove_observer(handle, observer_id); + + // Test with string + SetUp(); // Reset callback counters + observer_id = notifly_add_observer(handle, 2002, test_callback, nullptr); + ASSERT_GT(observer_id, 0); + + const char* str_data = "Test String"; + result = notifly_post_notification(handle, 2002, (void*)str_data); + EXPECT_EQ(result, 1); + EXPECT_EQ(g_callback_count, 1); + + notifly_remove_observer(handle, observer_id); +} + +// Test observer ID reuse +TEST_F(NotiflyCAI, ObserverIDReuse) { + notifly_handle handle = notifly_default(); + ASSERT_NE(handle, nullptr); + + // Add and remove observer multiple times + for (int i = 0; i < 5; i++) { + int observer_id = notifly_add_observer(handle, 3000 + i, simple_callback, nullptr); + ASSERT_GT(observer_id, 0) << "Iteration " << i; + + int result = notifly_remove_observer(handle, observer_id); + EXPECT_EQ(result, NOTIFLY_SUCCESS) << "Iteration " << i; + } +} + +// Test concurrent notifications (basic test) +TEST_F(NotiflyCAI, MultipleNotifications) { + notifly_handle handle = notifly_default(); + ASSERT_NE(handle, nullptr); + + int observer1 = notifly_add_observer(handle, 4001, simple_callback, nullptr); + int observer2 = notifly_add_observer(handle, 4002, simple_callback, nullptr); + + ASSERT_GT(observer1, 0); + ASSERT_GT(observer2, 0); + + // Post to different notifications + int result1 = notifly_post_notification(handle, 4001, nullptr); + int result2 = notifly_post_notification(handle, 4002, nullptr); + + EXPECT_EQ(result1, 1); + EXPECT_EQ(result2, 1); + EXPECT_EQ(g_callback_count, 2); + + // Cleanup + notifly_remove_observer(handle, observer1); + notifly_remove_observer(handle, observer2); +} + +// Test default instance behavior +TEST_F(NotiflyCAI, DefaultInstanceBehavior) { + notifly_handle handle1 = notifly_default(); + notifly_handle handle2 = notifly_default(); + + ASSERT_NE(handle1, nullptr); + ASSERT_NE(handle2, nullptr); + // Both should return the same default instance + EXPECT_EQ(handle1, handle2) << "Default instance should be singleton"; + + // Test that observers added to one handle are visible to the other + int observer_id = notifly_add_observer(handle1, 5001, simple_callback, nullptr); + ASSERT_GT(observer_id, 0); + + int result = notifly_post_notification(handle2, 5001, nullptr); + EXPECT_EQ(result, 1) << "Notification should work across default handles"; + + // Cleanup + notifly_remove_observer(handle1, observer_id); +} \ No newline at end of file From d95b30aafdb48faa6d7e5d5d85bf0633a5224923 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:44:00 +0000 Subject: [PATCH 3/5] Fix Windows compilation issues in C API Google Test - replaced Unix-specific headers with cross-platform alternatives Co-authored-by: draugvar <12036000+draugvar@users.noreply.github.com> --- .github/workflows/build_and_test.yml | 8 ++++++-- test/test_c_api_gtest.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 7661ca0..5e52164 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -41,10 +41,14 @@ jobs: if: runner.os == 'Linux' working-directory: build shell: bash - run: ./notifly_unit_test + run: | + ./notifly_unit_test + ./notifly_c_gtest - name: Test on Windows if: runner.os == 'Windows' working-directory: build shell: pwsh - run: .\Release\notifly_unit_test.exe + run: | + .\Release\notifly_unit_test.exe + .\Release\notifly_c_gtest.exe diff --git a/test/test_c_api_gtest.cpp b/test/test_c_api_gtest.cpp index a783f32..a0784c8 100644 --- a/test/test_c_api_gtest.cpp +++ b/test/test_c_api_gtest.cpp @@ -10,8 +10,13 @@ extern "C" { #include "notifly_c.h" } -#include // for usleep #include +#ifdef _WIN32 +#include +#define usleep(x) Sleep((x)/1000) +#else +#include +#endif // Test data structure typedef struct { From bc2c6e3efd02b3179b83a6a035e4500e5d65d8e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:51:31 +0000 Subject: [PATCH 4/5] Fix remaining Windows compilation issues in C files - replace unistd.h with cross-platform headers Co-authored-by: draugvar <12036000+draugvar@users.noreply.github.com> --- example/c_example.c | 7 ++++++- test/test_c_interface.c | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/example/c_example.c b/example/c_example.c index 7433b5c..5ed1236 100644 --- a/example/c_example.c +++ b/example/c_example.c @@ -7,7 +7,12 @@ #include #include #include -#include // for sleep +#ifdef _WIN32 +#include +#define usleep(x) Sleep((x)/1000) +#else +#include +#endif // Message IDs #define MSG_STARTUP 1001 diff --git a/test/test_c_interface.c b/test/test_c_interface.c index 54e2a7e..511f601 100644 --- a/test/test_c_interface.c +++ b/test/test_c_interface.c @@ -7,7 +7,12 @@ #include #include #include -#include // for sleep +#ifdef _WIN32 +#include +#define usleep(x) Sleep((x)/1000) +#else +#include +#endif // Test data structure typedef struct { From 44f57a46a90b646f0cfc44438d6d3dd9b6da56a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 22:03:17 +0000 Subject: [PATCH 5/5] Fix Windows DLL export/import issues - add proper NOTIFLY_C_API macros for cross-platform compatibility Co-authored-by: draugvar <12036000+draugvar@users.noreply.github.com> --- CMakeLists.txt | 1 + include/notifly_c.h | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7929a7..4dcf1a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ set_target_properties(${PROJECT_NAME}_c PROPERTIES OUTPUT_NAME "notifly_c" VERSION 1.0.0 SOVERSION 1 + DEFINE_SYMBOL "NOTIFLY_C_EXPORTS" ) target_compile_features(${PROJECT_NAME}_c PUBLIC cxx_std_20) diff --git a/include/notifly_c.h b/include/notifly_c.h index 8a9e9e7..76f8d99 100644 --- a/include/notifly_c.h +++ b/include/notifly_c.h @@ -26,6 +26,16 @@ #ifndef NOTIFLY_C_H #define NOTIFLY_C_H +#ifdef _WIN32 + #ifdef NOTIFLY_C_EXPORTS + #define NOTIFLY_C_API __declspec(dllexport) + #else + #define NOTIFLY_C_API __declspec(dllimport) + #endif +#else + #define NOTIFLY_C_API +#endif + #ifdef __cplusplus extern "C" { #endif @@ -52,21 +62,21 @@ typedef enum { #define NOTIFLY_C_VERSION_PATCH 0 /* Instance management */ -notifly_handle notifly_create(void); -void notifly_destroy(notifly_handle handle); -notifly_handle notifly_default(void); +NOTIFLY_C_API notifly_handle notifly_create(void); +NOTIFLY_C_API void notifly_destroy(notifly_handle handle); +NOTIFLY_C_API notifly_handle notifly_default(void); /* Observer management */ -int notifly_add_observer(notifly_handle handle, int notification_id, notifly_callback callback, void* user_data); -int notifly_remove_observer(notifly_handle handle, int observer_id); -int notifly_remove_all_observers(notifly_handle handle, int notification_id); +NOTIFLY_C_API int notifly_add_observer(notifly_handle handle, int notification_id, notifly_callback callback, void* user_data); +NOTIFLY_C_API int notifly_remove_observer(notifly_handle handle, int observer_id); +NOTIFLY_C_API int notifly_remove_all_observers(notifly_handle handle, int notification_id); /* Notification posting */ -int notifly_post_notification(notifly_handle handle, int notification_id, void* data); -int notifly_post_notification_async(notifly_handle handle, int notification_id, void* data); +NOTIFLY_C_API int notifly_post_notification(notifly_handle handle, int notification_id, void* data); +NOTIFLY_C_API int notifly_post_notification_async(notifly_handle handle, int notification_id, void* data); /* Utility functions */ -const char* notifly_result_to_string(int result); +NOTIFLY_C_API const char* notifly_result_to_string(int result); #ifdef __cplusplus }