- 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
167 lines
7.5 KiB
C
167 lines
7.5 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 for each channel", "[relay_chn][notify]")
|
|
{
|
|
// 1. Register the listener and reset info
|
|
reset_listener_info(&listener1_info);
|
|
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration to be processed
|
|
|
|
// Loop through each channel
|
|
for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) {
|
|
// 2. Trigger a state change on the current channel.
|
|
// tearDown() ensures each channel starts as IDLE.
|
|
relay_chn_run_forward(ch);
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow event to be processed
|
|
|
|
// 3. Verify the listener was called with correct parameters for this channel.
|
|
// The listener_info struct is overwritten each time, but we check it before the next iteration.
|
|
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
|
}
|
|
|
|
// 4. Verify the total call count after the loop
|
|
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
|
|
|
// 5. Unregister to clean up
|
|
relay_chn_unregister_listener(test_listener_1);
|
|
}
|
|
|
|
TEST_CASE("Unregistered listener is not called for any channel", "[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);
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow commands to process
|
|
|
|
// 2. Trigger a state change on all channels
|
|
relay_chn_run_forward_all();
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS * CONFIG_RELAY_CHN_COUNT)); // Allow all events to be processed
|
|
|
|
// 3. Verify the listener was NOT called
|
|
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
|
|
}
|
|
|
|
TEST_CASE("Multiple listeners are called on state change for each channel", "[relay_chn][notify]")
|
|
{
|
|
// 1. Register listeners and reset info
|
|
reset_listener_info(&listener1_info);
|
|
reset_listener_info(&listener2_info);
|
|
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
|
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration commands to be processed
|
|
|
|
// Loop through each channel
|
|
for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) {
|
|
// 2. Trigger a state change on the current channel
|
|
relay_chn_run_forward(ch);
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
|
|
|
// 3. Verify listener 1 was called correctly for this channel
|
|
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
|
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 for this channel
|
|
TEST_ASSERT_EQUAL(ch, listener2_info.chn_id);
|
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
|
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
|
|
}
|
|
|
|
// 5. Verify total call counts
|
|
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
|
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener2_info.call_count);
|
|
|
|
// 6. 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);
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow commands to process
|
|
|
|
// 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
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration commands to be processed
|
|
|
|
// 4. Trigger a state change on all channels and verify the listener is only called ONCE per channel
|
|
relay_chn_run_forward_all();
|
|
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS * CONFIG_RELAY_CHN_COUNT)); // Allow all events to be processed
|
|
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, 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.
|
|
// Use different channel IDs to make the test more robust.
|
|
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN; i++) {
|
|
uint8_t ch = i % CONFIG_RELAY_CHN_COUNT;
|
|
TEST_ESP_OK(relay_chn_notify_state_change(ch, 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)); // Use any valid channel
|
|
|
|
// 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(1 % CONFIG_RELAY_CHN_COUNT, 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;
|
|
}
|