2 Commits

Author SHA1 Message Date
a587036093 Add tests for init error handling.
Added tests for covering initialization error handling cases like; NULL pointer, invalid GPIO count etc.

This changed implied removing the `relay_chn_create` from the Unity's `setUp` function and place it in each testcase.

Refs #1050, #1030.
2025-07-15 12:23:21 +03:00
82312ba7c3 Add NULL handling for the gpio_num pointer.
Fixes #1050.
2025-07-15 12:17:46 +03:00
2 changed files with 131 additions and 26 deletions

View File

@@ -298,6 +298,8 @@ static esp_err_t relay_chn_create_event_loop()
esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
{
ESP_RETURN_ON_FALSE(gpio_map, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL");
// Check if the device's GPIOs are enough for the number of channels
if (RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) {
ESP_LOGE(TAG, "Not enough GPIOs for the number of channels!");

View File

@@ -8,6 +8,9 @@
#include "sdkconfig.h" // For accessing CONFIG_* values
#include <string.h>
const char *TAG = "RELAY_CHN_TEST";
// Test GPIOs and channel IDs
// Please ensure these GPIOs are correct and suitable for your board.
// Two channels (4 GPIOs) are used as an example.
@@ -21,28 +24,56 @@ const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS;
// Tolerant delay margin to ensure operations complete, especially after inertia.
const uint32_t test_delay_margin_ms = 50;
static bool g_is_component_initialized = false;
// --- Test Setup/Teardown Functions ---
void setUp(void) {
// Re-create the component before each test. relay_chn_create returns esp_err_t.
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
// Reset state before each test. Initialization is now done inside each test case
// to allow for testing of initialization failures.
g_is_component_initialized = false;
}
void tearDown(void) {
relay_chn_destroy(); // Clean up after each test
// Conditionally destroy the component to avoid crashing if creation failed.
if (g_is_component_initialized) {
relay_chn_destroy();
}
}
// --- Initialization Tests ---
TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][init]")
{
// 1. Test with NULL gpio_map
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(NULL, gpio_count));
// 2. Test with incorrect gpio_count (must be RELAY_CHN_COUNT * 2)
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, gpio_count - 1));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0));
// 3. Test with invalid GPIO numbers (GPIO_NUM_MAX is an invalid GPIO for output)
gpio_num_t invalid_gpio_map[] = {GPIO_NUM_4, GPIO_NUM_MAX, GPIO_NUM_18, GPIO_NUM_19};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(invalid_gpio_map, gpio_count));
}
// --- Basic Functionality Tests ---
// TEST_CASE 1: Test that relay channels initialize correctly to RELAY_CHN_STATE_FREE
// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_FREE
TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
for (uint8_t i = 0; i < relay_chn_count; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
}
}
// TEST_CASE 1: Test that relays do nothing when an invlid channel id given
// TEST_CASE: Test that relays do nothing when an invlid channel id given
TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
for (uint8_t i = 0; i < relay_chn_count; i++) {
int invalid_id = relay_chn_count * 2 + i;
relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void
@@ -52,8 +83,11 @@ TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn]") {
}
}
// TEST_CASE 2: Test that relays run in the forward direction and update their state
// TEST_CASE: Test that relays run in the forward direction and update their state
TEST_CASE("Relay channels run forward and update state", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
for (uint8_t i = 0; i < relay_chn_count; i++) {
relay_chn_run_forward(i); // relay_chn_run_forward returns void
// Short delay for state to update
@@ -62,19 +96,26 @@ TEST_CASE("Relay channels run forward and update state", "[relay_chn]") {
}
}
// TEST_CASE 3: Test that relays do nothing when an invlid channel id given
// TEST_CASE: Test that relays do nothing when an invlid channel id given
TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Verify that no valid channels were affected
for (uint8_t i = 0; i < relay_chn_count; i++) {
int invalid_id = relay_chn_count * 2 + i;
relay_chn_run_reverse(invalid_id); // relay_chn_run_forward returns void
// Short delay for state to update
// Call run_reverse with an invalid ID
relay_chn_run_reverse(invalid_id);
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
}
}
// TEST_CASE 4: Test that relays run in the reverse direction and update their state
// TEST_CASE: Test that relays run in the reverse direction and update their state
TEST_CASE("Relay channels run reverse and update state", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
for (uint8_t i = 0; i < relay_chn_count; i++) {
relay_chn_run_reverse(i); // relay_chn_run_reverse returns void
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
@@ -82,9 +123,12 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn]") {
}
}
// TEST_CASE 5: Test that relays stop and transition to RELAY_CHN_STATE_FREE
// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_FREE
// This test also verifies the transition to FREE state after a STOP command.
TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
for (uint8_t i = 0; i < relay_chn_count; i++) {
// First, run forward to test stopping and transitioning to FREE state
relay_chn_run_forward(i); // relay_chn_run_forward returns void
@@ -103,8 +147,11 @@ TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn]") {
}
}
// TEST_CASE 6: Get state should return UNDEFINED when id is not valid
// TEST_CASE: Get state should return UNDEFINED when id is not valid
TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
for (uint8_t i = 0; i < relay_chn_count; i++) {
int invalid_id = relay_chn_count * 2 + i;
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id));
@@ -118,8 +165,11 @@ TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn]") {
}
}
// TEST_CASE 7: Get state string should return "UNKNOWN" when id is not valid
// TEST_CASE: Get state string should return "UNKNOWN" when id is not valid
TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
for (uint8_t i = 0; i < relay_chn_count; i++) {
int invalid_id = relay_chn_count * 2 + i;
TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id));
@@ -133,8 +183,11 @@ TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn]")
}
}
// TEST_CASE 8: Test independent operation of multiple relay channels
// TEST_CASE: Test independent operation of multiple relay channels
TEST_CASE("Multiple channels can operate independently", "[relay_chn]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
if (relay_chn_count >= 2) {
// Start Channel 0 in forward direction
relay_chn_run_forward(0); // relay_chn_run_forward returns void
@@ -169,11 +222,14 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn]") {
// This section specifically targets the inertia periods and complex state transitions as per the component's logic.
// TEST_CASE 7: Test transition from forward to reverse with inertia and state checks
// TEST_CASE: Test transition from forward to reverse with inertia and state checks
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][inertia]") {
uint8_t ch = 0; // Channel to test
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// 1. Start in forward direction
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization
@@ -190,11 +246,14 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][in
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // Should now be in reverse state
}
// TEST_CASE 8: Test transition from reverse to forward with inertia and state checks
// TEST_CASE: Test transition from reverse to forward with inertia and state checks
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// 1. Start in reverse direction
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
@@ -210,11 +269,14 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][in
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
}
// TEST_CASE 9: Test issuing the same run command while already running (no inertia expected)
// TEST_CASE: Test issuing the same run command while already running (no inertia expected)
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// 1. Start in forward direction
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
@@ -228,11 +290,14 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][inert
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
}
// TEST_CASE 10: Test transition from FREE state to running (no inertia expected)
// TEST_CASE: Test transition from FREE state to running (no inertia expected)
// Scenario: RELAY_CHN_STATE_FREE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("FREE to Running transition without inertia", "[relay_chn][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// setUp() should have already brought the channel to FREE state
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
@@ -279,6 +344,9 @@ static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_c
TEST_CASE("Listener is called on state change", "[relay_chn][listener]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
reset_listener_info(&listener1_info);
// 1. Register the listener
@@ -300,6 +368,9 @@ TEST_CASE("Listener is called on state change", "[relay_chn][listener]") {
TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
reset_listener_info(&listener1_info);
// 1. Register and then immediately unregister the listener
@@ -316,6 +387,9 @@ TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") {
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
reset_listener_info(&listener1_info);
reset_listener_info(&listener2_info);
@@ -343,6 +417,9 @@ TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener
}
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]") {
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
reset_listener_info(&listener1_info);
// 1. Registering a NULL listener should fail
@@ -388,11 +465,14 @@ void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(chn_id));
}
// TEST_CASE 11: Test transition from running forward to tilt forward
// TEST_CASE: Test transition from running forward to tilt forward
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running forward first to set last_run_cmd
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
@@ -412,11 +492,14 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
}
// TEST_CASE 12: Test transition from running reverse to tilt reverse
// TEST_CASE: Test transition from running reverse to tilt reverse
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running reverse first to set last_run_cmd
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
@@ -434,11 +517,14 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
}
// TEST_CASE 13: Test transition from FREE state to tilt forward (now with preparation)
// TEST_CASE: Test transition from FREE state to tilt forward (now with preparation)
// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running forward first to set last_run_cmd
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE
@@ -450,11 +536,14 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
}
// TEST_CASE 14: Test transition from FREE state to tilt reverse (now with preparation)
// TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation)
// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running reverse first to set last_run_cmd
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE
@@ -465,11 +554,14 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
}
// TEST_CASE 15: Test transition from tilt forward to run forward (inertia expected for run)
// TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run)
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(ch); // Go to tilt state
@@ -484,11 +576,14 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
}
// TEST_CASE 16: Test transition from tilt reverse to run reverse (no inertia expected for run)
// TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run)
// Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running reverse first to set last_run_cmd, then tilt
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
relay_chn_tilt_reverse(ch); // Go to tilt state
@@ -502,11 +597,14 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
}
// TEST_CASE 17: Test transition from tilt forward to run reverse (without inertia)
// TEST_CASE: Test transition from tilt forward to run reverse (without inertia)
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(ch); // Go to tilt state
@@ -519,11 +617,14 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn]
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
}
// TEST_CASE 18: Test stopping from a tilt state (no inertia for stop command itself)
// TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself)
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FREE
TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") {
uint8_t ch = 0;
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
g_is_component_initialized = true;
// Prepare channel by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(ch); // Go to tilt state
@@ -553,6 +654,8 @@ void app_main(void) {
// Run the Unity test runner
unity_run_all_tests();
ESP_LOGI(TAG, "============================== END OF TESTS ==============================");
// After tests complete, instead of restarting, the device will halt.
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000)); // Wait with low power consumption