Fix listener memory allocation bug.

- Replaced the buggy, oldschool, plain pointer based list approach with more robust FreeRTOS linked list implementation for the listener API. Fixes #1049.

- Added relevant test cases. Refs #1030.
This commit is contained in:
2025-07-14 14:24:26 +03:00
parent 1ee70be715
commit db62a7b5b2
2 changed files with 206 additions and 69 deletions

View File

@@ -6,6 +6,7 @@
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "sdkconfig.h" // For accessing CONFIG_* values
#include <string.h>
// Test GPIOs and channel IDs
// Please ensure these GPIOs are correct and suitable for your board.
@@ -242,6 +243,127 @@ TEST_CASE("FREE to Running transition without inertia", "[relay_chn][inertia]")
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
}
// --- Listener Test Globals ---
typedef struct {
uint8_t chn_id;
relay_chn_state_t old_state;
relay_chn_state_t new_state;
int call_count;
} listener_callback_info_t;
static listener_callback_info_t listener1_info;
static listener_callback_info_t listener2_info;
// --- Listener Test Helper Functions ---
// Clear the memory from possible garbage values
static void reset_listener_info(listener_callback_info_t* info) {
memset(info, 0, sizeof(listener_callback_info_t));
}
static void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
listener1_info.chn_id = chn_id;
listener1_info.old_state = old_state;
listener1_info.new_state = new_state;
listener1_info.call_count++;
}
static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
listener2_info.chn_id = chn_id;
listener2_info.old_state = old_state;
listener2_info.new_state = new_state;
listener2_info.call_count++;
}
// ### Listener Functionality Tests
TEST_CASE("Listener is called on state change", "[relay_chn][listener]") {
uint8_t ch = 0;
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(ch);
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(ch, listener1_info.chn_id);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, 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][listener]") {
uint8_t ch = 0;
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(ch);
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][listener]") {
uint8_t ch = 0;
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(ch);
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_FREE, 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_FREE, 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][listener]") {
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(0);
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);
}
// ### Tilt Functionality Tests (Conditional)