Compare commits
2 Commits
a9a8169710
...
a447e568d6
| Author | SHA1 | Date | |
|---|---|---|---|
|
a447e568d6
|
|||
|
a143484748
|
156
scripts/run_tests.sh
Executable file
156
scripts/run_tests.sh
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# ==== 1. Check ESP-IDF environment ====
|
||||
if [[ -z "$IDF_PATH" ]]; then
|
||||
echo "❌ ESP-IDF environment not found. Please source the export.sh file first:"
|
||||
echo "'. $HOME/esp/esp-idf/export.sh' or wherever the ESP-IDF is installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ==== 2. Valid Modes and Defaults ====
|
||||
valid_test_tags=("core" "tilt" "listener" "all" "relay_chn")
|
||||
arg_tag="all" # Default to 'all' if no tag specified
|
||||
arg_clean=false
|
||||
arg_log=false
|
||||
arg_dry_run=false
|
||||
arg_sdkconfig_file=""
|
||||
flag_file=false
|
||||
|
||||
print_help() {
|
||||
echo "Usage: $0 -t <tags> [OPTIONS]"
|
||||
echo ""
|
||||
echo "This script builds and runs tests for the relay_chn component using QEMU."
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " -t, --tag [relay_chn|core|tilt|listener|all] Specify which test tag to run."
|
||||
echo ""
|
||||
echo " If no tag is specified, it defaults to 'all'."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -f, --file <path> Specify a custom sdkconfig file to use for the build."
|
||||
echo " Defaults to 'sdkconfig.defaults' if not provided."
|
||||
echo " -c, --clean Perform a 'fullclean' before building the tests."
|
||||
echo " -l, --log Log the test output to a timestamped file."
|
||||
echo " -n, --dry-run Build the project without running qemu."
|
||||
echo " -h, --help Show this help message and exit."
|
||||
}
|
||||
|
||||
help() {
|
||||
print_help
|
||||
exit 0
|
||||
}
|
||||
|
||||
usage() {
|
||||
print_help
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ==== 3. Argument Parsing ====
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--tag|-t)
|
||||
arg_tag="$2"
|
||||
shift 2
|
||||
;;
|
||||
--file|-f)
|
||||
arg_sdkconfig_file="$2"
|
||||
flag_file=true
|
||||
shift 2
|
||||
;;
|
||||
--clean|-c)
|
||||
arg_clean=true
|
||||
shift
|
||||
;;
|
||||
--log|-l)
|
||||
arg_log=true
|
||||
shift
|
||||
;;
|
||||
--dry-run|-n)
|
||||
arg_dry_run=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
help
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ==== 4. Validity Check ====
|
||||
if [[ ! " ${valid_test_tags[*]} " =~ " $arg_tag " ]]; then
|
||||
echo "❌ Invalid mode: '$arg_tag'"
|
||||
usage
|
||||
fi
|
||||
|
||||
# ==== 5. Resolve Paths and Switch to Working Directory ====
|
||||
script_dir=$(dirname "$(readlink -f "$0")")
|
||||
project_root=$(dirname "$script_dir")
|
||||
|
||||
echo "🔍 Searching for 'test_apps' directory in '$project_root'..."
|
||||
test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1)
|
||||
|
||||
if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then
|
||||
echo "❌ 'test_apps' directory not found within the project root: '$project_root'"
|
||||
echo " Please ensure the script is in a 'scripts' directory and 'test_apps' is a sibling."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Found 'test_apps' at: $test_apps_dir"
|
||||
|
||||
if $flag_file; then
|
||||
if [[ -z "$arg_sdkconfig_file" || ! -f "$arg_sdkconfig_file" ]]; then
|
||||
echo "❌ Invalid or missing file: '$arg_sdkconfig_file'"
|
||||
usage
|
||||
fi
|
||||
# Resolve to an absolute path to work correctly after changing directory
|
||||
arg_sdkconfig_file=$(readlink -f "$arg_sdkconfig_file")
|
||||
else
|
||||
echo "⚠️ No SDK configuration file provided. Using default sdkconfig."
|
||||
arg_sdkconfig_file="$test_apps_dir/sdkconfig.defaults"
|
||||
fi
|
||||
|
||||
echo "🧪 Test mode: $arg_tag"
|
||||
echo "🧹 Clean: $arg_clean | 📄 Log: $arg_log"
|
||||
|
||||
echo "📂 Changing to working directory: $test_apps_dir"
|
||||
cd "$test_apps_dir" || exit 1
|
||||
|
||||
# ==== 6. Clean if requested ====
|
||||
if $arg_clean; then
|
||||
echo "🧹 Doing Fullclean..."
|
||||
idf.py fullclean
|
||||
rm sdkconfig
|
||||
fi
|
||||
|
||||
# ==== 7. Building and Running Tests ====
|
||||
# In some locales, we can get errors like: "Error: unknown opcode or format name 'wsr.IBREAKA1'"
|
||||
# The 'LC_ALL=C' env variable is set to ensure consistent locale settings.
|
||||
LC_ALL=C \
|
||||
SDKCONFIG_DEFAULTS="$arg_sdkconfig_file" \
|
||||
RELAY_CHN_UNITY_TEST_GROUP_TAG="$arg_tag" \
|
||||
idf.py reconfigure build
|
||||
|
||||
echo "🚀 Running test with QEMU..."
|
||||
|
||||
if $arg_log; then
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
LOGFILE="test_log_${arg_tag}_$TIMESTAMP.txt"
|
||||
if $arg_dry_run; then
|
||||
echo "🔍 Dry run mode: Logging to $LOGFILE but not executing." | tee "$LOGFILE"
|
||||
echo "Command: idf.py qemu" | tee "$LOGFILE"
|
||||
else
|
||||
echo "📜 Logging test output to: $LOGFILE"
|
||||
idf.py qemu | tee "$LOGFILE"
|
||||
fi
|
||||
else
|
||||
if $arg_dry_run; then
|
||||
echo "🔍 Dry run mode: Not executing idf.py qemu."
|
||||
echo "Command: idf.py qemu"
|
||||
else
|
||||
echo "🚀 Running idf.py qemu..."
|
||||
idf.py qemu
|
||||
fi
|
||||
fi
|
||||
@@ -13,5 +13,8 @@ set(COMPONENTS main)
|
||||
# Include ESP-IDF project build system
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
# Inject the test tag into the build
|
||||
add_compile_definitions(RELAY_CHN_UNITY_TEST_GROUP_TAG=\"$ENV{RELAY_CHN_UNITY_TEST_GROUP_TAG}\")
|
||||
|
||||
# Define the name of this project
|
||||
project(relay_chn_test)
|
||||
@@ -1,3 +1,20 @@
|
||||
idf_component_register(SRCS "test_relay_chn.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES unity relay_chn)
|
||||
# === These files must be included in any case ===
|
||||
set(srcs "test_common.c"
|
||||
"test_app_main.c"
|
||||
"test_relay_chn_core.c"
|
||||
"test_relay_chn_listener.c")
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_TILTING)
|
||||
list(APPEND srcs "test_relay_chn_tilt.c")
|
||||
endif()
|
||||
|
||||
message(STATUS "srcs=${srcs}")
|
||||
|
||||
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
|
||||
# the component can be registered as WHOLE_ARCHIVE
|
||||
idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES unity relay_chn
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
|
||||
47
test_apps/main/test_app_main.c
Normal file
47
test_apps/main/test_app_main.c
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "test_common.h"
|
||||
#include "unity.h"
|
||||
#include "unity_internals.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
#ifndef RELAY_CHN_UNITY_TEST_GROUP_TAG
|
||||
#warning "RELAY_CHN_UNITY_TEST_GROUP_TAG is not defined, using default 'relay_chn'"
|
||||
#define RELAY_CHN_UNITY_TEST_GROUP_TAG "relay_chn"
|
||||
#endif
|
||||
|
||||
void setUp()
|
||||
{
|
||||
g_is_component_initialized = false;
|
||||
}
|
||||
|
||||
void tearDown()
|
||||
{
|
||||
// Clean up after each test
|
||||
if (g_is_component_initialized) {
|
||||
relay_chn_destroy();
|
||||
g_is_component_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
// Log general test information
|
||||
ESP_LOGI(TEST_TAG, "Available test count: %d", unity_get_test_count());
|
||||
ESP_LOGI(TEST_TAG, "Running tests for tag: %s", RELAY_CHN_UNITY_TEST_GROUP_TAG);
|
||||
|
||||
if (strncmp(RELAY_CHN_UNITY_TEST_GROUP_TAG, "all", strlen("all")) == 0) {
|
||||
unity_run_all_tests();
|
||||
}
|
||||
else {
|
||||
unity_run_tests_by_tag(RELAY_CHN_UNITY_TEST_GROUP_TAG, false);
|
||||
}
|
||||
|
||||
UNITY_END();
|
||||
ESP_LOGI(TEST_TAG, "All tests complete.");
|
||||
}
|
||||
17
test_apps/main/test_common.c
Normal file
17
test_apps/main/test_common.c
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "test_common.h"
|
||||
|
||||
const char *TEST_TAG = "RELAY_CHN_TEST";
|
||||
|
||||
// GPIO eşlemesi (örn: GPIO_NUM_4 vs GPIO_NUM_5)
|
||||
const gpio_num_t gpio_map[] = {
|
||||
GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_18, GPIO_NUM_19
|
||||
};
|
||||
|
||||
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
||||
const uint8_t relay_chn_count = gpio_count / 2;
|
||||
|
||||
// Konfigürasyon tabanlı inertia süresi
|
||||
const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS;
|
||||
const uint32_t test_delay_margin_ms = 50; // ms toleransı
|
||||
|
||||
bool g_is_component_initialized = false;
|
||||
25
test_apps/main/test_common.h
Normal file
25
test_apps/main/test_common.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string.h> // For memset
|
||||
#include "unity.h"
|
||||
#include "relay_chn.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
// Test log tag
|
||||
extern const char *TEST_TAG;
|
||||
|
||||
// GPIO konfigürasyonları
|
||||
extern const gpio_num_t gpio_map[];
|
||||
extern const uint8_t gpio_count;
|
||||
extern const uint8_t relay_chn_count;
|
||||
|
||||
// Config parametreleri
|
||||
extern const uint32_t opposite_inertia_ms;
|
||||
extern const uint32_t test_delay_margin_ms;
|
||||
|
||||
// Init durumu
|
||||
extern bool g_is_component_initialized;
|
||||
@@ -1,883 +0,0 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "unity.h"
|
||||
#include "unity_test_utils.h"
|
||||
#include "relay_chn.h" // Main header file for the relay_chn component
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#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.
|
||||
const gpio_num_t gpio_map[] = {GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_18, GPIO_NUM_19};
|
||||
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
||||
// Assuming 2 GPIOs are used per channel
|
||||
const uint8_t relay_chn_count = gpio_count / 2;
|
||||
|
||||
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) {
|
||||
// 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) {
|
||||
// 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: 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: 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
|
||||
// Short delay for state to update
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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: 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));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ### Broadcast Command (RELAY_CHN_ID_ALL) Tests
|
||||
|
||||
TEST_CASE("run_forward with ID_ALL sets all channels to FORWARD", "[relay_chn][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("run_reverse with ID_ALL sets all channels to REVERSE", "[relay_chn][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
relay_chn_run_reverse(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("stop with ID_ALL stops all running channels", "[relay_chn][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Start all channels forward to ensure they are in a known running state
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 2. Stop all channels using the broadcast command
|
||||
relay_chn_stop(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels have transitioned to the FREE state
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
|
||||
// Now, issue the stop command
|
||||
relay_chn_stop(i); // relay_chn_stop returns void
|
||||
// Immediately after stop, state should be STOPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
||||
|
||||
// Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_FREE
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1)); // Other channel should not be affected
|
||||
|
||||
// Start Channel 1 in reverse direction
|
||||
relay_chn_run_reverse(1); // relay_chn_run_reverse returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1));
|
||||
|
||||
// Stop Channel 0 and wait for it to become FREE
|
||||
relay_chn_stop(0); // relay_chn_stop returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running
|
||||
|
||||
// Stop Channel 1 and wait for it to become FREE
|
||||
relay_chn_stop(1); // relay_chn_stop returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1));
|
||||
} else {
|
||||
ESP_LOGW("TEST", "Skipping 'Multiple channels can operate independently' test: Not enough channels available.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ### Inertia and State Transition Tests
|
||||
|
||||
// This section specifically targets the inertia periods and complex state transitions as per the component's logic.
|
||||
|
||||
// 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
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue reverse command
|
||||
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void
|
||||
// Immediately after the command, the motor should be stopped
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // Should now be in reverse state
|
||||
}
|
||||
|
||||
// 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));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue forward command
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for inertia
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue the same forward command again
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia.
|
||||
// Just a short delay to check state remains the same.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
// Start in forward direction
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
// No inertia is expected when starting from FREE state.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
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;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
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;
|
||||
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
|
||||
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;
|
||||
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);
|
||||
|
||||
// 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]") {
|
||||
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
|
||||
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)
|
||||
|
||||
// This section will only be compiled if **`CONFIG_RELAY_CHN_ENABLE_TILTING`** is defined as **`1`** in `sdkconfig`.
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||
|
||||
#define RELAY_CHN_CMD_FORWARD 1
|
||||
#define RELAY_CHN_CMD_REVERSE 2
|
||||
|
||||
// Helper function to prepare channel for tilt tests
|
||||
void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) {
|
||||
// Ensure the channel has had a 'last_run_cmd'
|
||||
if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
relay_chn_run_forward(chn_id);
|
||||
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||
relay_chn_run_reverse(chn_id);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process
|
||||
relay_chn_stop(chn_id); // Stop it to set last_run_cmd but return to FREE for next test
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(chn_id));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue tilt forward command
|
||||
relay_chn_tilt_forward(ch);
|
||||
// After tilt command, it should immediately stop and then trigger inertia.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for the inertia period (after which the tilt command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue tilt reverse command
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Issue tilt forward command
|
||||
relay_chn_tilt_forward(ch);
|
||||
// From FREE state, tilt command should still incur the inertia due to the internal timer logic
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Issue tilt reverse command
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run forward command
|
||||
relay_chn_run_forward(ch);
|
||||
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected.
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run reverse command
|
||||
relay_chn_run_reverse(ch);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run reverse command (opposite direction)
|
||||
relay_chn_run_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue stop command
|
||||
relay_chn_stop(ch);
|
||||
// Stop command should apply immediately, setting state to FREE since last state was tilt.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// ### Tilt Broadcast Command (RELAY_CHN_ID_ALL) Tests
|
||||
|
||||
TEST_CASE("tilt_forward with ID_ALL sets all channels to TILT_FORWARD", "[relay_chn][tilt][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare all channels.
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
// 2. Issue tilt forward to all channels
|
||||
relay_chn_tilt_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE doesn't have stop-inertia
|
||||
|
||||
// 3. Verify all channels are tilting forward
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_chn][tilt][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare all channels.
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
// 2. Issue tilt reverse to all channels
|
||||
relay_chn_tilt_reverse(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels are tilting reverse
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_stop with ID_ALL stops all tilting channels", "[relay_chn][tilt][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare and start all channels tilting forward
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
relay_chn_tilt_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 2. Stop tilting on all channels
|
||||
relay_chn_tilt_stop(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels are free
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_auto with ID_ALL tilts channels based on last run direction", "[relay_chn][tilt][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// This test requires at least 2 channels to demonstrate different behaviors
|
||||
TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, relay_chn_count, "Test requires at least 2 channels");
|
||||
|
||||
// 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE
|
||||
prepare_channel_for_tilt(0, RELAY_CHN_CMD_FORWARD);
|
||||
prepare_channel_for_tilt(1, RELAY_CHN_CMD_REVERSE);
|
||||
|
||||
// 2. Issue auto tilt command to all channels
|
||||
relay_chn_tilt_auto(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE state is dispatched immediately
|
||||
|
||||
// 3. Verify channel 0 tilts forward (last run was forward) and channel 1 tilts reverse (last run was reverse)
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(1));
|
||||
}
|
||||
|
||||
#else // CONFIG_RELAY_CHN_ENABLE_TILTING == 0
|
||||
// If tilt functionality is disabled, these tests are skipped.
|
||||
// A dummy test case is added to indicate this in the test output.
|
||||
TEST_CASE("Tilt functionality is disabled, skipping tilt tests", "[relay_chn][tilt_disabled]") {
|
||||
TEST_ASSERT_TRUE(true); // Just to ensure at least one test passes for visibility
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
|
||||
// ### Direction Flipping Tests
|
||||
|
||||
TEST_CASE("Single channel direction can be flipped", "[relay_chn][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t ch = 0;
|
||||
|
||||
// 1. Initial direction should be default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch));
|
||||
|
||||
// 2. Flip the direction
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia
|
||||
|
||||
// 3. Verify direction is flipped
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch));
|
||||
|
||||
// 4. Flip back
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia
|
||||
|
||||
// 5. Verify direction is back to default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch));
|
||||
}
|
||||
|
||||
TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][direction][all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Flip all channels
|
||||
relay_chn_flip_direction(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 2. Verify all channels are flipped
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i));
|
||||
}
|
||||
|
||||
// 3. Flip all back
|
||||
relay_chn_flip_direction(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 4. Verify all channels are back to default
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t ch = 0;
|
||||
|
||||
// 1. Start channel running and verify state
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Flip the direction while running
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Give time for events to process
|
||||
|
||||
// 3. The channel should stop as part of the flip process
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
// 4. Wait for the flip inertia to pass, after which it should be FREE and FLIPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch));
|
||||
}
|
||||
|
||||
TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t invalid_ch = relay_chn_count + 5;
|
||||
|
||||
relay_chn_flip_direction(invalid_ch); // Call with an invalid ID
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch));
|
||||
}
|
||||
|
||||
|
||||
// ### `app_main` Function
|
||||
|
||||
// --- app_main function ---
|
||||
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
|
||||
}
|
||||
}
|
||||
403
test_apps/main/test_relay_chn_core.c
Normal file
403
test_apps/main/test_relay_chn_core.c
Normal file
@@ -0,0 +1,403 @@
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// --- Initialization Tests ---
|
||||
|
||||
TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]")
|
||||
{
|
||||
// 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: Test that relay channels initialize correctly to RELAY_CHN_STATE_FREE
|
||||
TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") {
|
||||
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: Test that relays do nothing when an invlid channel id given
|
||||
TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]") {
|
||||
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
|
||||
// Short delay for state to update
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// 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][core]") {
|
||||
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
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// 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][core]") {
|
||||
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;
|
||||
// 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: Test that relays run in the reverse direction and update their state
|
||||
TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") {
|
||||
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));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ### Broadcast Command (RELAY_CHN_ID_ALL) Tests
|
||||
|
||||
TEST_CASE("run_forward with ID_ALL sets all channels to FORWARD", "[relay_chn][core][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("run_reverse with ID_ALL sets all channels to REVERSE", "[relay_chn][core][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
relay_chn_run_reverse(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("stop with ID_ALL stops all running channels", "[relay_chn][core][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Start all channels forward to ensure they are in a known running state
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 2. Stop all channels using the broadcast command
|
||||
relay_chn_stop(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels have transitioned to the FREE state
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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][core]") {
|
||||
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
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
|
||||
// Now, issue the stop command
|
||||
relay_chn_stop(i); // relay_chn_stop returns void
|
||||
// Immediately after stop, state should be STOPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
||||
|
||||
// Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_FREE
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Get state should return UNDEFINED when id is not valid
|
||||
TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") {
|
||||
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));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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][core]") {
|
||||
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));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test independent operation of multiple relay channels
|
||||
TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") {
|
||||
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
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1)); // Other channel should not be affected
|
||||
|
||||
// Start Channel 1 in reverse direction
|
||||
relay_chn_run_reverse(1); // relay_chn_run_reverse returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1));
|
||||
|
||||
// Stop Channel 0 and wait for it to become FREE
|
||||
relay_chn_stop(0); // relay_chn_stop returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running
|
||||
|
||||
// Stop Channel 1 and wait for it to become FREE
|
||||
relay_chn_stop(1); // relay_chn_stop returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1));
|
||||
} else {
|
||||
ESP_LOGW("TEST", "Skipping 'Multiple channels can operate independently' test: Not enough channels available.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ### Inertia and State Transition Tests
|
||||
|
||||
// This section specifically targets the inertia periods and complex state transitions as per the component's logic.
|
||||
|
||||
// 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][core][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
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue reverse command
|
||||
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void
|
||||
// Immediately after the command, the motor should be stopped
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // Should now be in reverse state
|
||||
}
|
||||
|
||||
// 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][core][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));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue forward command
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for inertia
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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][core][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));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue the same forward command again
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia.
|
||||
// Just a short delay to check state remains the same.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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][core][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));
|
||||
|
||||
// Start in forward direction
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
// No inertia is expected when starting from FREE state.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// ### Direction Flipping Tests
|
||||
|
||||
TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t ch = 0;
|
||||
|
||||
// 1. Initial direction should be default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch));
|
||||
|
||||
// 2. Flip the direction
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia
|
||||
|
||||
// 3. Verify direction is flipped
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch));
|
||||
|
||||
// 4. Flip back
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia
|
||||
|
||||
// 5. Verify direction is back to default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch));
|
||||
}
|
||||
|
||||
TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Flip all channels
|
||||
relay_chn_flip_direction(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 2. Verify all channels are flipped
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i));
|
||||
}
|
||||
|
||||
// 3. Flip all back
|
||||
relay_chn_flip_direction(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 4. Verify all channels are back to default
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t ch = 0;
|
||||
|
||||
// 1. Start channel running and verify state
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Flip the direction while running
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Give time for events to process
|
||||
|
||||
// 3. The channel should stop as part of the flip process
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
// 4. Wait for the flip inertia to pass, after which it should be FREE and FLIPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch));
|
||||
}
|
||||
|
||||
TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t invalid_ch = relay_chn_count + 5;
|
||||
|
||||
relay_chn_flip_direction(invalid_ch); // Call with an invalid ID
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch));
|
||||
}
|
||||
135
test_apps/main/test_relay_chn_listener.c
Normal file
135
test_apps/main/test_relay_chn_listener.c
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// --- 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;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
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;
|
||||
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
|
||||
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;
|
||||
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);
|
||||
|
||||
// 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]") {
|
||||
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
|
||||
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);
|
||||
}
|
||||
285
test_apps/main/test_relay_chn_tilt.c
Normal file
285
test_apps/main/test_relay_chn_tilt.c
Normal file
@@ -0,0 +1,285 @@
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// ### Tilt Functionality Tests (Conditional)
|
||||
|
||||
// This section will only be compiled if **`CONFIG_RELAY_CHN_ENABLE_TILTING`** is defined as **`1`** in `sdkconfig`.
|
||||
|
||||
#ifndef CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#error "This test requires CONFIG_RELAY_CHN_ENABLE_TILTING"
|
||||
#endif
|
||||
|
||||
#define RELAY_CHN_CMD_FORWARD 1
|
||||
#define RELAY_CHN_CMD_REVERSE 2
|
||||
|
||||
// Helper function to prepare channel for tilt tests
|
||||
void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) {
|
||||
// Ensure the channel has had a 'last_run_cmd'
|
||||
if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
relay_chn_run_forward(chn_id);
|
||||
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||
relay_chn_run_reverse(chn_id);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process
|
||||
relay_chn_stop(chn_id); // Stop it to set last_run_cmd but return to FREE for next test
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(chn_id));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue tilt forward command
|
||||
relay_chn_tilt_forward(ch);
|
||||
// After tilt command, it should immediately stop and then trigger inertia.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for the inertia period (after which the tilt command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue tilt reverse command
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Issue tilt forward command
|
||||
relay_chn_tilt_forward(ch);
|
||||
// From FREE state, tilt command should still incur the inertia due to the internal timer logic
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Issue tilt reverse command
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run forward command
|
||||
relay_chn_run_forward(ch);
|
||||
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected.
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run reverse command
|
||||
relay_chn_run_reverse(ch);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run reverse command (opposite direction)
|
||||
relay_chn_run_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// 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
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue stop command
|
||||
relay_chn_stop(ch);
|
||||
// Stop command should apply immediately, setting state to FREE since last state was tilt.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// ### Tilt Broadcast Command (RELAY_CHN_ID_ALL) Tests
|
||||
|
||||
TEST_CASE("tilt_forward with ID_ALL sets all channels to TILT_FORWARD", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare all channels.
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
// 2. Issue tilt forward to all channels
|
||||
relay_chn_tilt_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE doesn't have stop-inertia
|
||||
|
||||
// 3. Verify all channels are tilting forward
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare all channels.
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
// 2. Issue tilt reverse to all channels
|
||||
relay_chn_tilt_reverse(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels are tilting reverse
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_stop with ID_ALL stops all tilting channels", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare and start all channels tilting forward
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
relay_chn_tilt_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 2. Stop tilting on all channels
|
||||
relay_chn_tilt_stop(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels are free
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_auto with ID_ALL tilts channels based on last run direction", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// This test requires at least 2 channels to demonstrate different behaviors
|
||||
TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, relay_chn_count, "Test requires at least 2 channels");
|
||||
|
||||
// 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE
|
||||
prepare_channel_for_tilt(0, RELAY_CHN_CMD_FORWARD);
|
||||
prepare_channel_for_tilt(1, RELAY_CHN_CMD_REVERSE);
|
||||
|
||||
// 2. Issue auto tilt command to all channels
|
||||
relay_chn_tilt_auto(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE state is dispatched immediately
|
||||
|
||||
// 3. Verify channel 0 tilts forward (last run was forward) and channel 1 tilts reverse (last run was reverse)
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(1));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,8 @@
|
||||
# Disable task WDT for tests
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
# Relay Channel Driver Default Configuration for Testing
|
||||
# Keep this as short as possible for tests
|
||||
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
|
||||
CONFIG_RELAY_CHN_COUNT=2
|
||||
CONFIG_RELAY_CHN_ENABLE_TILTING=y
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user