Files
relay_chn/test_apps/main/test_relay_chn_notify_single.c
ismail 5e8e5a4cab Add notification system for relay channel state changes
- Introduced a new notification module to handle state change listeners.
- Added functions to register and unregister listeners for relay channel state changes.
- Implemented a queue-based system to manage notifications and listener callbacks.
- Updated core relay channel logic to utilize the new notification system.
- Removed old listener management code from relay channel core.
- Refactored the former listener tests to notify tests and added tests for the notification system, including handling of multiple listeners and queue overflow scenarios.
- Updated CMakeLists.txt to include new source files and headers for the notification module.
- Revised README.md to include warnings about callback execution context and performance considerations.

Refs #1096, #1085 and closes #1097
2025-09-02 15:46:48 +03:00

147 lines
5.8 KiB
C

#include "test_relay_chn_notify_common.h"
// This is a private header, but we need it for direct notification calls and queue length.
// It's included conditionally in the build via CMakeLists.txt when NVS is enabled.
#include "relay_chn_notify.h"
// ### Listener Functionality Tests
TEST_CASE("Listener is called on state change", "[relay_chn][notify]")
{
reset_listener_info(&listener1_info);
// 1. Register the listener
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
// 2. Trigger a state change
relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow event to be processed
// 3. Verify the listener was called with correct parameters
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
// 4. Unregister to clean up
relay_chn_unregister_listener(test_listener_1);
}
TEST_CASE("Unregistered listener is not called", "[relay_chn][notify]")
{
reset_listener_info(&listener1_info);
// 1. Register and then immediately unregister the listener
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
relay_chn_unregister_listener(test_listener_1);
// 2. Trigger a state change
relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
// 3. Verify the listener was NOT called
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
}
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][notify]")
{
reset_listener_info(&listener1_info);
reset_listener_info(&listener2_info);
// 1. Register two different listeners
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
// 2. Trigger a state change
relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
// 3. Verify listener 1 was called correctly
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
// 4. Verify listener 2 was also called correctly
TEST_ASSERT_EQUAL(1, listener2_info.call_count);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
// 5. Clean up
relay_chn_unregister_listener(test_listener_1);
relay_chn_unregister_listener(test_listener_2);
}
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][notify]")
{
reset_listener_info(&listener1_info);
// 1. Registering a NULL listener should fail
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_register_listener(NULL));
// 2. Unregistering a NULL listener should not crash
relay_chn_unregister_listener(NULL);
// 3. Registering the same listener twice should be handled gracefully
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
TEST_ESP_OK(relay_chn_register_listener(test_listener_1)); // Second call should be a no-op
// 4. Trigger a state change and verify the listener is only called ONCE
relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
// 5. Clean up
relay_chn_unregister_listener(test_listener_1);
}
TEST_CASE("Notify queue full scenario is handled gracefully", "[relay_chn][notify]")
{
// 1. Setup
blocking_listener_sem = xSemaphoreCreateBinary();
log_check_sem = xSemaphoreCreateBinary();
blocking_listener_call_count = 0;
// Intercept logs to check for the "queue full" warning
original_vprintf = esp_log_set_vprintf(log_check_vprintf);
// 2. Register a listener that will block, allowing the queue to fill up
TEST_ESP_OK(relay_chn_register_listener(blocking_listener));
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow task to start
// 3. Fill the queue. The first event will be consumed immediately by the dispatcher,
// which will then call the blocking_listener and block. The remaining (LEN - 1)
// events will sit in the queue, leaving one empty slot.
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN; i++) {
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
}
// 4. Send one more event to fill the last slot in the queue. This should succeed.
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
// 5. Now the queue is full. Trigger one more event to cause an overflow.
// This call should fail and log the warning.
TEST_ASSERT_EQUAL(ESP_FAIL, relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
// 6. Wait for the "queue full" log message to be captured by our vprintf hook
TEST_ASSERT_TRUE_MESSAGE(xSemaphoreTake(log_check_sem, pdMS_TO_TICKS(1000)) == pdTRUE, "Did not receive 'queue full' log message");
// 7. Unblock the listener so it can process all queued items.
// There was 1 initial event + QUEUE_LEN events that were successfully queued.
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1; i++) {
xSemaphoreGive(blocking_listener_sem);
// Give the dispatcher task a moment to process one item from the queue
vTaskDelay(pdMS_TO_TICKS(10));
}
// 8. Verify the listener was called exactly QUEUE_LEN + 1 times
TEST_ASSERT_EQUAL_INT(TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1, blocking_listener_call_count);
// 9. Cleanup
esp_log_set_vprintf(original_vprintf);
relay_chn_unregister_listener(blocking_listener);
vSemaphoreDelete(blocking_listener_sem);
vSemaphoreDelete(log_check_sem);
blocking_listener_sem = NULL;
log_check_sem = NULL;
original_vprintf = NULL;
}