mirror of
https://github.com/espressif/esp-idf.git
synced 2025-11-26 20:53:11 +00:00
ci: Migrate unit test for tcp_transport to unit test app
This commit is contained in:
7
components/tcp_transport/test_apps/CMakeLists.txt
Normal file
7
components/tcp_transport/test_apps/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
#This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp_tcp_transport_test)
|
||||
2
components/tcp_transport/test_apps/README.md
Normal file
2
components/tcp_transport/test_apps/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
5
components/tcp_transport/test_apps/main/CMakeLists.txt
Normal file
5
components/tcp_transport/test_apps/main/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
set(srcs "test_app_main.c" "test_transport_basic.c" "test_transport_connect" "test_transport_fixtures.c")
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_INCLUDE_DIRS "../../private_include" "."
|
||||
PRIV_REQUIRES cmock test_utils tcp_transport unity
|
||||
WHOLE_ARCHIVE)
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#ifndef _TCP_TRANSPORT_FIXTURES_H_
|
||||
#define _TCP_TRANSPORT_FIXTURES_H_
|
||||
|
||||
/**
|
||||
* @brief Structures and types for passing socket options
|
||||
*/
|
||||
enum expected_sock_option_types {
|
||||
SOCK_OPT_TYPE_BOOL,
|
||||
SOCK_OPT_TYPE_INT,
|
||||
};
|
||||
struct expected_sock_option {
|
||||
int level;
|
||||
int optname;
|
||||
int optval;
|
||||
enum expected_sock_option_types opttype;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper test functions for timeout connection tests
|
||||
*
|
||||
* This case simulates connection timeout running tcp connect asynchronously with other socket connection
|
||||
* consuming entire socket listener backlog.
|
||||
* Important: Both tasks must run on the same core, with listener's prio higher to make sure that
|
||||
* 1) first the localhost_listener() creates and connects all sockets until the last one blocks
|
||||
* 2) before the tcp_connect_task() attempts to connect and thus fails with connection timeout
|
||||
*/
|
||||
void tcp_transport_test_connection_timeout(esp_transport_handle_t transport_under_test);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper test function to check socket options configured separately by transports
|
||||
*
|
||||
* This sets up the connection test to start two tasks, but unlike tcp_transport_test_connection_timeout,
|
||||
* here we just let the connection to happen or at least open on TCP layer so we get the internal socket
|
||||
* descriptor. While the connection is in progress or connected, we can check the socket options configured
|
||||
* by the tcp_transport API.
|
||||
*/
|
||||
void tcp_transport_test_socket_options(esp_transport_handle_t transport_under_test, bool async,
|
||||
const struct expected_sock_option *expected_opts, size_t sock_options_len);
|
||||
|
||||
#endif //_TCP_TRANSPORT_FIXTURES_H_
|
||||
18
components/tcp_transport/test_apps/main/test_app_main.c
Normal file
18
components/tcp_transport/test_apps/main/test_app_main.c
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity_fixture.h"
|
||||
#include "unity_fixture_extras.h"
|
||||
|
||||
static void run_all_tests(void)
|
||||
{
|
||||
RUN_TEST_GROUP(transport_basic);
|
||||
RUN_TEST_GROUP(transport_connect);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
UNITY_MAIN_FUNC(run_all_tests);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity_fixture.h"
|
||||
#include "memory_checks.h"
|
||||
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_tcp.h"
|
||||
#include "esp_transport_ssl.h"
|
||||
#include "esp_transport_ws.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
||||
TEST_GROUP(transport_basic);
|
||||
|
||||
TEST_SETUP(transport_basic)
|
||||
{
|
||||
test_utils_record_free_mem();
|
||||
TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL));
|
||||
}
|
||||
|
||||
TEST_TEAR_DOWN(transport_basic)
|
||||
{
|
||||
test_utils_finish_and_evaluate_leaks(test_utils_get_leak_level(ESP_LEAK_TYPE_WARNING, ESP_COMP_LEAK_ALL),
|
||||
test_utils_get_leak_level(ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_ALL));
|
||||
}
|
||||
|
||||
TEST(transport_basic, transport_list_init_and_destroy)
|
||||
{
|
||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
esp_transport_list_add(transport_list, tcp, "tcp");
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_list_destroy(transport_list));
|
||||
}
|
||||
|
||||
TEST(transport_basic, transport_ssl_init_destroy_no_list)
|
||||
{
|
||||
esp_transport_handle_t h = esp_transport_ssl_init();
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(h));
|
||||
}
|
||||
|
||||
TEST(transport_basic, transport_ws_init_destroy_no_list)
|
||||
{
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(ws));
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(tcp));
|
||||
}
|
||||
|
||||
TEST(transport_basic, transport_list_init_multiple_and_destroy)
|
||||
{
|
||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
esp_transport_list_add(transport_list, tcp, "tcp");
|
||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||
esp_transport_list_add(transport_list, ssl, "ssl");
|
||||
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
||||
esp_transport_list_add(transport_list, ws, "ws");
|
||||
esp_transport_handle_t wss = esp_transport_ws_init(ssl);
|
||||
esp_transport_list_add(transport_list, wss, "wss");
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_list_destroy(transport_list));
|
||||
}
|
||||
|
||||
TEST_GROUP_RUNNER(transport_basic)
|
||||
{
|
||||
RUN_TEST_CASE(transport_basic, transport_list_init_and_destroy);
|
||||
RUN_TEST_CASE(transport_basic, transport_ssl_init_destroy_no_list);
|
||||
RUN_TEST_CASE(transport_basic, transport_ws_init_destroy_no_list);
|
||||
RUN_TEST_CASE(transport_basic, transport_list_init_multiple_and_destroy);
|
||||
}
|
||||
213
components/tcp_transport/test_apps/main/test_transport_connect.c
Normal file
213
components/tcp_transport/test_apps/main/test_transport_connect.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity_fixture.h"
|
||||
#include "memory_checks.h"
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_tcp.h"
|
||||
#include "esp_transport_ssl.h"
|
||||
#include "esp_transport_ws.h"
|
||||
#include "esp_log.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "tcp_transport_fixtures.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
TEST_GROUP(transport_connect);
|
||||
|
||||
TEST_SETUP(transport_connect)
|
||||
{
|
||||
test_utils_record_free_mem();
|
||||
TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL));
|
||||
}
|
||||
|
||||
TEST_TEAR_DOWN(transport_connect)
|
||||
{
|
||||
test_utils_finish_and_evaluate_leaks(test_utils_get_leak_level(ESP_LEAK_TYPE_WARNING, ESP_COMP_LEAK_ALL),
|
||||
test_utils_get_leak_level(ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_ALL));
|
||||
}
|
||||
|
||||
#define TEST_TRANSPORT_BIND_IFNAME() \
|
||||
struct ifreq ifr; \
|
||||
ifr.ifr_name[0] = 'l'; \
|
||||
ifr.ifr_name[1] = 'o'; \
|
||||
ifr.ifr_name[2] = '0'; \
|
||||
ifr.ifr_name[3] = '\0';
|
||||
|
||||
|
||||
static void tcp_transport_keepalive_test(esp_transport_handle_t transport_under_test, bool async, esp_transport_keep_alive_t *config)
|
||||
{
|
||||
static struct expected_sock_option expected_opts[4] = {
|
||||
{ .level = SOL_SOCKET, .optname = SO_KEEPALIVE, .optval = 1, .opttype = SOCK_OPT_TYPE_BOOL },
|
||||
{ .level = IPPROTO_TCP },
|
||||
{ .level = IPPROTO_TCP },
|
||||
{ .level = IPPROTO_TCP }
|
||||
};
|
||||
|
||||
expected_opts[1].optname = TCP_KEEPIDLE;
|
||||
expected_opts[1].optval = config->keep_alive_idle;
|
||||
expected_opts[2].optname = TCP_KEEPINTVL;
|
||||
expected_opts[2].optval = config->keep_alive_interval;
|
||||
expected_opts[3].optname = TCP_KEEPCNT;
|
||||
expected_opts[3].optval = config->keep_alive_count;
|
||||
|
||||
tcp_transport_test_socket_options(transport_under_test, async, expected_opts,
|
||||
sizeof(expected_opts) / sizeof(struct expected_sock_option));
|
||||
}
|
||||
|
||||
TEST(transport_connect, tcp_connect_timeout)
|
||||
{
|
||||
// Init the transport under test
|
||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
esp_transport_list_add(transport_list, tcp, "tcp");
|
||||
|
||||
tcp_transport_test_connection_timeout(tcp);
|
||||
esp_transport_close(tcp);
|
||||
esp_transport_list_destroy(transport_list);
|
||||
}
|
||||
|
||||
TEST(transport_connect, ssl_connect_timeout)
|
||||
{
|
||||
// Init the transport under test
|
||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
esp_transport_list_add(transport_list, tcp, "tcp");
|
||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||
esp_transport_list_add(transport_list, ssl, "ssl");
|
||||
|
||||
tcp_transport_test_connection_timeout(ssl);
|
||||
esp_transport_close(tcp);
|
||||
esp_transport_close(ssl);
|
||||
esp_transport_list_destroy(transport_list);
|
||||
}
|
||||
|
||||
TEST(transport_connect, tcp_keep_alive)
|
||||
{
|
||||
// Init the transport under test
|
||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
esp_transport_list_add(transport_list, tcp, "tcp");
|
||||
|
||||
// Perform the test
|
||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||
.keep_alive_interval = 5,
|
||||
.keep_alive_idle = 4,
|
||||
.keep_alive_enable = true,
|
||||
.keep_alive_count = 3
|
||||
};
|
||||
esp_transport_tcp_set_keep_alive(tcp, &keep_alive_cfg);
|
||||
|
||||
// Bind device interface to loopback
|
||||
TEST_TRANSPORT_BIND_IFNAME();
|
||||
esp_transport_tcp_set_interface_name(tcp, &ifr);
|
||||
|
||||
// Run the test for both sync and async_connect
|
||||
tcp_transport_keepalive_test(tcp, true, &keep_alive_cfg);
|
||||
tcp_transport_keepalive_test(tcp, false, &keep_alive_cfg);
|
||||
|
||||
// Cleanup
|
||||
esp_transport_close(tcp);
|
||||
esp_transport_list_destroy(transport_list);
|
||||
}
|
||||
|
||||
TEST(transport_connect, ssl_keep_alive)
|
||||
{
|
||||
// Init the transport under test
|
||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||
esp_transport_list_add(transport_list, ssl, "ssl");
|
||||
esp_tls_init_global_ca_store();
|
||||
esp_transport_ssl_enable_global_ca_store(ssl);
|
||||
|
||||
// Perform the test
|
||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||
.keep_alive_interval = 2,
|
||||
.keep_alive_idle = 3,
|
||||
.keep_alive_enable = true,
|
||||
.keep_alive_count = 4
|
||||
};
|
||||
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
|
||||
|
||||
// Bind device interface to loopback
|
||||
TEST_TRANSPORT_BIND_IFNAME();
|
||||
esp_transport_ssl_set_interface_name(ssl, &ifr);
|
||||
|
||||
// Run the test for async_connect only
|
||||
// - TLS connection would connect on socket level only, returning tls-handshake in progress
|
||||
tcp_transport_keepalive_test(ssl, true, &keep_alive_cfg);
|
||||
|
||||
// Cleanup
|
||||
esp_transport_close(ssl);
|
||||
esp_transport_list_destroy(transport_list);
|
||||
}
|
||||
|
||||
TEST(transport_connect, ws_keep_alive)
|
||||
{
|
||||
// Init the transport under test
|
||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
esp_transport_list_add(transport_list, tcp, "tcp");
|
||||
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
||||
esp_transport_list_add(transport_list, ws, "ws");
|
||||
|
||||
// Perform the test
|
||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||
.keep_alive_interval = 11,
|
||||
.keep_alive_idle = 22,
|
||||
.keep_alive_enable = true,
|
||||
.keep_alive_count = 33
|
||||
};
|
||||
esp_transport_tcp_set_keep_alive(tcp, &keep_alive_cfg);
|
||||
|
||||
// Bind device interface to loopback
|
||||
TEST_TRANSPORT_BIND_IFNAME();
|
||||
esp_transport_ssl_set_interface_name(tcp, &ifr);
|
||||
|
||||
// Run the test for sync_connect only (ws doesn't support async)
|
||||
tcp_transport_keepalive_test(ws, false, &keep_alive_cfg);
|
||||
|
||||
// Cleanup
|
||||
esp_transport_close(tcp);
|
||||
esp_transport_list_destroy(transport_list);
|
||||
}
|
||||
|
||||
// Note: This functionality is tested and kept only for compatibility reasons with IDF <= 4.x
|
||||
// It is strongly encouraged to use transport within lists only
|
||||
TEST(transport_connect, ssl_set_parameter_independently)
|
||||
{
|
||||
// Init the transport under test
|
||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||
esp_tls_init_global_ca_store();
|
||||
esp_transport_ssl_enable_global_ca_store(ssl);
|
||||
|
||||
// Perform the test
|
||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||
.keep_alive_interval = 2,
|
||||
.keep_alive_idle = 4,
|
||||
.keep_alive_enable = true,
|
||||
.keep_alive_count = 3
|
||||
};
|
||||
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
|
||||
|
||||
// Bind device interface to loopback
|
||||
TEST_TRANSPORT_BIND_IFNAME();
|
||||
esp_transport_ssl_set_interface_name(ssl, &ifr);
|
||||
|
||||
tcp_transport_keepalive_test(ssl, true, &keep_alive_cfg);
|
||||
|
||||
// Cleanup
|
||||
esp_transport_close(ssl);
|
||||
esp_transport_destroy(ssl);
|
||||
}
|
||||
|
||||
TEST_GROUP_RUNNER(transport_connect)
|
||||
{
|
||||
RUN_TEST_CASE(transport_connect, tcp_connect_timeout);
|
||||
RUN_TEST_CASE(transport_connect, ssl_connect_timeout);
|
||||
RUN_TEST_CASE(transport_connect, tcp_keep_alive);
|
||||
RUN_TEST_CASE(transport_connect, ssl_keep_alive);
|
||||
RUN_TEST_CASE(transport_connect, ws_keep_alive);
|
||||
RUN_TEST_CASE(transport_connect, ssl_set_parameter_independently);
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity.h"
|
||||
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_tcp.h"
|
||||
#include "test_utils.h"
|
||||
#include "esp_log.h"
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "tcp_transport_fixtures.h"
|
||||
#include "unity_test_utils.h"
|
||||
|
||||
#define LISTENER_TASK_PRIORITY (UNITY_FREERTOS_PRIORITY+1)
|
||||
#define CONNECT_TASK_PRIORITY (LISTENER_TASK_PRIORITY+1)
|
||||
#define CONNECT_TASK_PRIORITY_LOWER (LISTENER_TASK_PRIORITY-2)
|
||||
|
||||
// This is a private API of the tcp transport, but needed for socket operation tests
|
||||
int esp_transport_get_socket(esp_transport_handle_t t);
|
||||
|
||||
/**
|
||||
* @brief Event flags for synchronization between the listener task, the connection task and the test task
|
||||
*/
|
||||
enum {
|
||||
TCP_CONNECT_DONE = 1 << 0, /*!< Indicates that the connection task has finished, so the transport_connect() exited */
|
||||
TCP_LISTENER_DONE = 1 << 1, /*!< Indicates that the listener task has finished either with success for failure */
|
||||
TCP_TEST_DONE = 1 << 2, /*!< Indicates that the test case finished, test tear-down() called */
|
||||
TCP_LISTENER_READY = 1 << 3, /*!< Indicates that the listener task is ready to accept connections */
|
||||
TCP_LISTENER_ACCEPTED = 1 << 4, /*!< Indicates that the listener task has accepted a connection (from transport_connect()) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Connection test configuration parameters
|
||||
*/
|
||||
struct tcp_connect_test_config {
|
||||
esp_transport_handle_t transport_under_test;
|
||||
bool accept_connection;
|
||||
bool consume_sock_backlog;
|
||||
bool connect_async;
|
||||
int timeout_ms;
|
||||
int port;
|
||||
bool connect_task_prio_lower;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Test setup structure containing all the info needed for the connection tests
|
||||
*/
|
||||
struct tcp_connect_test_storage {
|
||||
struct tcp_connect_test_config config;
|
||||
TickType_t max_wait;
|
||||
EventGroupHandle_t tcp_connect_done;
|
||||
int connect_return_value;
|
||||
int listen_sock;
|
||||
int accepted_sock;
|
||||
int last_connect_sock;
|
||||
bool tcp_listener_failed;
|
||||
TaskHandle_t listener_task;
|
||||
TaskHandle_t tcp_connect_task;
|
||||
};
|
||||
|
||||
typedef struct tcp_connect_test_storage *tcp_connect_test_t;
|
||||
|
||||
/**
|
||||
* @brief Recursively connects with a new socket to loopback interface until the last one blocks.
|
||||
* The last socket is closed upon test teardown, that initiates recursive cleanup (close) for all
|
||||
* active/connected sockets.
|
||||
*/
|
||||
static void connect_once(struct tcp_connect_test_storage *storage)
|
||||
{
|
||||
struct sockaddr_in dest_addr_ip4 = { .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(storage->config.port) };
|
||||
int connect_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||
if (connect_sock < 0) {
|
||||
storage->tcp_listener_failed = true;
|
||||
return;
|
||||
}
|
||||
storage->last_connect_sock = connect_sock;
|
||||
int err = connect(connect_sock, (struct sockaddr *)&dest_addr_ip4, sizeof(dest_addr_ip4));
|
||||
if (err != 0) {
|
||||
// The last connection is expected to fail here, since the both sockets get closed on test cleanup
|
||||
return;
|
||||
}
|
||||
connect_once(storage);
|
||||
close(connect_sock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief creates a listener (and an acceptor if configured)
|
||||
*
|
||||
* if consume_sock_backlog set: connect as many times as possible to prepare an endpoint which
|
||||
* would make the client block but not complete TCP handshake
|
||||
*
|
||||
* if accept_connection set: waiting normally for connection creating an acceptor to mimic tcp-transport endpoint
|
||||
*/
|
||||
static void localhost_listener(void *pvParameters)
|
||||
{
|
||||
const char* TAG = "tcp_transport_test";
|
||||
struct tcp_connect_test_storage *storage = pvParameters;
|
||||
struct sockaddr_in dest_addr_ip4 = { .sin_addr.s_addr = htonl(INADDR_ANY),
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(storage->config.port) };
|
||||
// Create listener socket and bind it to ANY address
|
||||
storage->listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||
int opt = 1;
|
||||
setsockopt(storage->listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
if (storage->listen_sock < 0) {
|
||||
ESP_LOGE(TAG, "Unable to create socket");
|
||||
storage->tcp_listener_failed = true;
|
||||
goto failed;
|
||||
}
|
||||
int err = bind(storage->listen_sock, (struct sockaddr *)&dest_addr_ip4, sizeof(dest_addr_ip4));
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
storage->tcp_listener_failed = true;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Listen with backlog set to a low number
|
||||
err = listen(storage->listen_sock, 4);
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
|
||||
storage->tcp_listener_failed = true;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Listener is ready at this point
|
||||
xEventGroupSetBits(storage->tcp_connect_done, TCP_LISTENER_READY);
|
||||
|
||||
if (storage->config.consume_sock_backlog) {
|
||||
// Ideally we would set backlog to 0, but since this is an implementation specific recommendation parameter,
|
||||
// we recursively create sockets and try to connect to this listener in order to consume the backlog. After
|
||||
// the backlog is consumed, the last connection blocks (waiting for accept), but at that point we are sure
|
||||
// that any other connection would also block
|
||||
connect_once(storage);
|
||||
} else if (storage->config.accept_connection) {
|
||||
struct sockaddr_storage source_addr;
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
storage->accepted_sock = accept(storage->listen_sock, (struct sockaddr *)&source_addr, &addr_len);
|
||||
if (storage->accepted_sock < 0) {
|
||||
ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
|
||||
goto failed;
|
||||
}
|
||||
xEventGroupSetBits(storage->tcp_connect_done, TCP_LISTENER_ACCEPTED); // Mark the socket as accepted
|
||||
// ...and wait for the "acceptor" tests to finish
|
||||
xEventGroupWaitBits(storage->tcp_connect_done, TCP_TEST_DONE, true, true, storage->config.timeout_ms * 10);
|
||||
}
|
||||
|
||||
failed:
|
||||
xEventGroupSetBits(storage->tcp_connect_done, TCP_LISTENER_DONE);
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This task simply tries to connect to localhost (server provided by listner's task) using tcp_transport
|
||||
*/
|
||||
static void tcp_connect_task(void *pvParameters)
|
||||
{
|
||||
struct tcp_connect_test_storage *storage = pvParameters;
|
||||
|
||||
int (*connect_fn)(esp_transport_handle_t, const char *, int, int) =
|
||||
storage->config.connect_async ? esp_transport_connect_async : esp_transport_connect;
|
||||
|
||||
storage->connect_return_value = connect_fn(storage->config.transport_under_test, "localhost", storage->config.port, storage->config.timeout_ms);
|
||||
|
||||
if (storage->config.accept_connection) {
|
||||
// If we test the accepted connection, need to wait until the test completes
|
||||
xEventGroupWaitBits(storage->tcp_connect_done, TCP_TEST_DONE, true, true, storage->config.timeout_ms * 10);
|
||||
}
|
||||
xEventGroupSetBits(storage->tcp_connect_done, TCP_CONNECT_DONE);
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
static inline void close_if_valid(int *s)
|
||||
{
|
||||
if (*s >= 0) {
|
||||
close(*s);
|
||||
*s = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect test setup function
|
||||
*
|
||||
* Creates the Test storage, configures it accordingly and starts two tasks
|
||||
* * localhost_listener -- to provide a simple server endpoint for the transport layers to connect to
|
||||
* * tcp_connect_task -- to perform the connection
|
||||
*/
|
||||
static tcp_connect_test_t connect_test_setup(struct tcp_connect_test_config *config)
|
||||
{
|
||||
tcp_connect_test_t t = calloc(1, sizeof(struct tcp_connect_test_storage));
|
||||
if (!t) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(&t->config, config, sizeof(struct tcp_connect_test_config));
|
||||
t->tcp_connect_done = xEventGroupCreate();
|
||||
if (!t->tcp_connect_done) {
|
||||
return NULL;
|
||||
}
|
||||
t->max_wait = pdMS_TO_TICKS(config->timeout_ms * 10);
|
||||
|
||||
t->listen_sock = t->last_connect_sock = t->accepted_sock = -1; // mark all sockets invalid
|
||||
|
||||
test_case_uses_tcpip();
|
||||
|
||||
// Create listener task
|
||||
xTaskCreatePinnedToCore(localhost_listener, "localhost_listener", 4096, t, LISTENER_TASK_PRIORITY, &t->listener_task, 0);
|
||||
xEventGroupWaitBits(t->tcp_connect_done, TCP_LISTENER_READY, true, true, t->max_wait);
|
||||
|
||||
// Perform tcp-connect in a separate task to check asynchronously for the timeout or to connect (depends on the test config)
|
||||
xTaskCreatePinnedToCore(tcp_connect_task, "tcp_connect_task", 4096, t,
|
||||
config->connect_task_prio_lower? CONNECT_TASK_PRIORITY_LOWER : CONNECT_TASK_PRIORITY, &t->tcp_connect_task, 0);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys and cleans out the test environment
|
||||
*/
|
||||
static void connect_test_teardown(tcp_connect_test_t t)
|
||||
{
|
||||
// Mark the test done and wait for the listener to check if finished with no issues
|
||||
xEventGroupSetBits(t->tcp_connect_done, TCP_TEST_DONE);
|
||||
xEventGroupWaitBits(t->tcp_connect_done, TCP_LISTENER_DONE, true, true, t->max_wait);
|
||||
TEST_ASSERT_EQUAL(false, t->tcp_listener_failed);
|
||||
|
||||
// Closing both parties of the last "blocking" connection to unwind localhost_listener() and let other connected sockets closed
|
||||
close_if_valid(&t->listen_sock);
|
||||
close_if_valid(&t->last_connect_sock);
|
||||
close_if_valid(&t->accepted_sock);
|
||||
|
||||
// Cleanup
|
||||
vTaskSuspend(t->tcp_connect_task);
|
||||
vTaskSuspend(t->listener_task);
|
||||
vEventGroupDelete(t->tcp_connect_done);
|
||||
unity_utils_task_delete(t->tcp_connect_task);
|
||||
unity_utils_task_delete(t->listener_task);
|
||||
free(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function for testing timeouts for different transports
|
||||
*/
|
||||
void tcp_transport_test_connection_timeout(esp_transport_handle_t transport_under_test)
|
||||
{
|
||||
|
||||
struct tcp_connect_test_config params = {
|
||||
.timeout_ms = 200,
|
||||
.port = 80,
|
||||
.consume_sock_backlog = true,
|
||||
.connect_async = false,
|
||||
.transport_under_test = transport_under_test,
|
||||
.connect_task_prio_lower = true
|
||||
};
|
||||
|
||||
tcp_connect_test_t test = connect_test_setup(¶ms);
|
||||
TEST_ASSERT_NOT_NULL(test);
|
||||
|
||||
// Roughly measure tick-time spent while trying to connect
|
||||
#if !CONFIG_FREERTOS_SMP // IDF-5225 - timeout is several times shorter than expected, probably not measured correctly
|
||||
TickType_t start = xTaskGetTickCount();
|
||||
#endif
|
||||
EventBits_t bits = xEventGroupWaitBits(test->tcp_connect_done, TCP_CONNECT_DONE, true, true, test->max_wait);
|
||||
#if !CONFIG_FREERTOS_SMP // IDF-5225 - timeout is several times shorter than expected, probably not measured correctly
|
||||
TickType_t end = xTaskGetTickCount();
|
||||
#endif
|
||||
|
||||
TEST_ASSERT_EQUAL(TCP_CONNECT_DONE, TCP_CONNECT_DONE & bits); // Connection has finished
|
||||
|
||||
#if !CONFIG_FREERTOS_SMP // IDF-5225 - timeout is several times shorter than expected, probably not measured correctly
|
||||
TEST_ASSERT_EQUAL(-1, test->connect_return_value); // Connection failed with -1
|
||||
// Test connection attempt took expected timeout value
|
||||
TEST_ASSERT_INT_WITHIN(pdMS_TO_TICKS(params.timeout_ms/5), pdMS_TO_TICKS(params.timeout_ms), end-start);
|
||||
#endif
|
||||
|
||||
// Close the last bound connection, to recursively unwind the consumed backlog
|
||||
close_if_valid(&test->last_connect_sock);
|
||||
|
||||
connect_test_teardown(test);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function for testing timeouts for different transports, options and both sync and async connection
|
||||
*/
|
||||
void tcp_transport_test_socket_options(esp_transport_handle_t transport_under_test, bool async,
|
||||
const struct expected_sock_option *expected_opts, size_t sock_options_len)
|
||||
{
|
||||
struct tcp_connect_test_config params = {
|
||||
.timeout_ms = 200,
|
||||
.port = 80,
|
||||
.accept_connection = true,
|
||||
.consume_sock_backlog = false,
|
||||
.transport_under_test = transport_under_test,
|
||||
.connect_async = async,
|
||||
.connect_task_prio_lower = false
|
||||
};
|
||||
|
||||
tcp_connect_test_t test = connect_test_setup(¶ms);
|
||||
TEST_ASSERT_NOT_NULL(test);
|
||||
|
||||
// Wait till the connection gets accepted to get the client's socket
|
||||
xEventGroupWaitBits(test->tcp_connect_done, TCP_LISTENER_ACCEPTED, true, true, test->max_wait);
|
||||
int sock = esp_transport_get_socket(params.transport_under_test);
|
||||
for (int i=0; i<sock_options_len; ++i) {
|
||||
int value = -1;
|
||||
socklen_t optlen = (socklen_t)sizeof(value);
|
||||
TEST_ASSERT_EQUAL(getsockopt(sock, expected_opts[i].level, expected_opts[i].optname,
|
||||
(void*)&value, &optlen), 0);
|
||||
if (expected_opts[i].opttype == SOCK_OPT_TYPE_BOOL) {
|
||||
TEST_ASSERT_EQUAL((bool)value, (bool) expected_opts[i].optval);
|
||||
} else if (expected_opts[i].opttype == SOCK_OPT_TYPE_INT) {
|
||||
TEST_ASSERT_EQUAL(value, expected_opts[i].optval);
|
||||
} else {
|
||||
TEST_FAIL_MESSAGE("Unsupported socket option type");
|
||||
}
|
||||
}
|
||||
|
||||
// close the tcp_transport's socket so we don't have to wait for connection timeout
|
||||
close(sock);
|
||||
|
||||
connect_test_teardown(test);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32c3
|
||||
@pytest.mark.generic
|
||||
def test_tcp_transport_client(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output()
|
||||
1
components/tcp_transport/test_apps/sdkconfig.ci.default
Normal file
1
components/tcp_transport/test_apps/sdkconfig.ci.default
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
10
components/tcp_transport/test_apps/sdkconfig.ci.psram
Normal file
10
components/tcp_transport/test_apps/sdkconfig.ci.psram
Normal file
@@ -0,0 +1,10 @@
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_ESP_INT_WDT_TIMEOUT_MS=800
|
||||
CONFIG_SPIRAM_OCCUPY_NO_HOST=y
|
||||
CONFIG_ESP_WIFI_RX_IRAM_OPT=n
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=n
|
||||
# Disable encrypted flash reads/writes to save IRAM in this build configuration
|
||||
CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=n
|
||||
CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY=y
|
||||
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
|
||||
Reference in New Issue
Block a user