Skip to content

Commit 9c7da56

Browse files
committed
feat(esp_cli): move linux test to host_test and add test_apps
1 parent c0be11a commit 9c7da56

File tree

14 files changed

+575
-139
lines changed

14 files changed

+575
-139
lines changed

README.md

Lines changed: 0 additions & 30 deletions
This file was deleted.

esp_cli/.build-test-rules.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
esp_cli/test_apps:
1+
esp_cli/host_test:
22
enable:
33
- if: IDF_TARGET == "linux"
44
reason: "Sufficient to test on Linux target"
55
disable:
66
- if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 4
7-
reason: "those versions of esp-idf do not support eventfd for linux target"
7+
reason: "those versions of esp-idf do not support eventfd for linux target"
8+
9+
esp_cli/test_apps:
10+
enable:
11+
- if: IDF_TARGET == "esp32s3"
12+
reason: "Need support for USB Serial JTAG"
13+
disable:
14+
- if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR <= 3
15+
reason: "esp_vfs_fs_ops_t not available"

esp_cli/host_test/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cmake_minimum_required(VERSION 3.22)
2+
3+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
4+
set(COMPONENTS main)
5+
project(esp_cli_host_test)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
idf_component_register(SRCS "test_esp_cli.c" "test_main.c"
3+
PRIV_INCLUDE_DIRS "."
4+
PRIV_REQUIRES unity
5+
WHOLE_ARCHIVE)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dependencies:
2+
espressif/esp_cli:
3+
version: "*"
4+
override_path: "../.."
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <string.h>
8+
#include <stdio.h>
9+
#include <fcntl.h>
10+
#include <sys/time.h>
11+
#include "freertos/FreeRTOS.h"
12+
#include "freertos/task.h"
13+
#include "freertos/semphr.h"
14+
#include "unity.h"
15+
#include "esp_cli.h"
16+
#include "esp_linenoise.h"
17+
#include "esp_cli_commands.h"
18+
19+
#include <sys/socket.h>
20+
21+
inline __attribute__((always_inline))
22+
uint32_t get_millis(void)
23+
{
24+
struct timeval tv = { 0 };
25+
gettimeofday(&tv, NULL);
26+
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
27+
}
28+
29+
inline __attribute__((always_inline))
30+
void wait_ms(int ms)
31+
{
32+
vTaskDelay(pdMS_TO_TICKS(ms));
33+
}
34+
35+
static size_t s_on_enter_nb_of_calls = 0;
36+
static size_t s_pre_executor_nb_of_calls = 0;
37+
static size_t s_post_executor_nb_of_calls = 0;
38+
static size_t s_on_stop_nb_of_calls = 0;
39+
static size_t s_on_exit_nb_of_calls = 0;
40+
41+
void test_on_enter(void *ctx, esp_cli_handle_t handle)
42+
{
43+
s_on_enter_nb_of_calls++;
44+
return;
45+
}
46+
47+
esp_err_t test_pre_executor(void *ctx, const char *buf, esp_err_t reader_ret_val)
48+
{
49+
s_pre_executor_nb_of_calls++;
50+
return ESP_OK;
51+
}
52+
53+
esp_err_t test_post_executor(void *ctx, const char *buf, esp_err_t executor_ret_val, int cmd_ret_val)
54+
{
55+
s_post_executor_nb_of_calls++;
56+
return ESP_OK;
57+
}
58+
59+
void test_on_stop(void *ctx, esp_cli_handle_t handle)
60+
{
61+
s_on_stop_nb_of_calls++;
62+
return;
63+
}
64+
65+
void test_on_exit(void *ctx, esp_cli_handle_t handle)
66+
{
67+
s_on_exit_nb_of_calls++;
68+
return;
69+
}
70+
71+
/* Pass two semaphores:
72+
* - start_sem: child gives it when it reached esp_cli() (so main knows child started)
73+
* - done_sem: child gives it just before deleting itself (so main can "join")
74+
*/
75+
typedef struct task_args {
76+
SemaphoreHandle_t start_sem;
77+
SemaphoreHandle_t done_sem;
78+
esp_cli_handle_t hdl;
79+
} task_args_t;
80+
81+
static void esp_cli_task(void *args)
82+
{
83+
task_args_t *task_args = (task_args_t *)args;
84+
85+
/* signal to main that task started and esp_cli() will run */
86+
xSemaphoreGive(task_args->start_sem);
87+
88+
/* run the esp_cli REPL loop (will return when stopped) */
89+
esp_cli(task_args->hdl);
90+
91+
/* signal completion (emulates pthread_join notification) */
92+
xSemaphoreGive(task_args->done_sem);
93+
94+
/* self-delete */
95+
vTaskDelete(NULL);
96+
}
97+
98+
static int s_socket_fd[2];
99+
100+
static void test_socket_setup(int socket_fd[2])
101+
{
102+
TEST_ASSERT_EQUAL(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fd));
103+
104+
/* ensure reads are blocking */
105+
int flags = fcntl(socket_fd[0], F_GETFL, 0);
106+
flags &= ~O_NONBLOCK;
107+
fcntl(socket_fd[0], F_SETFL, flags);
108+
109+
flags = fcntl(socket_fd[1], F_GETFL, 0);
110+
flags &= ~O_NONBLOCK;
111+
fcntl(socket_fd[1], F_SETFL, flags);
112+
}
113+
114+
static void test_socket_teardown(int socket_fd[2])
115+
{
116+
close(socket_fd[0]);
117+
close(socket_fd[1]);
118+
}
119+
120+
static void test_send_characters(int socket_fd, const char *msg)
121+
{
122+
wait_ms(100);
123+
124+
const size_t msg_len = strlen(msg);
125+
const int nwrite = write(socket_fd, msg, msg_len);
126+
TEST_ASSERT_EQUAL(msg_len, nwrite);
127+
}
128+
129+
static void esp_cli_teardown(SemaphoreHandle_t *start_sem, SemaphoreHandle_t *done_sem, int socket_fd[2],
130+
esp_linenoise_handle_t *linenoise_hdl, esp_cli_handle_t *cli_hdl)
131+
{
132+
/* destroy the instance of esp_cli */
133+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_destroy(*cli_hdl));
134+
135+
/* delete the linenoise instance */
136+
TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_delete_instance(*linenoise_hdl));
137+
138+
/* cleanup semaphores */
139+
vSemaphoreDelete(*start_sem);
140+
vSemaphoreDelete(*done_sem);
141+
142+
/* close the socketpair */
143+
test_socket_teardown(socket_fd);
144+
145+
s_on_stop_nb_of_calls = 0;
146+
s_on_exit_nb_of_calls = 0;
147+
s_on_enter_nb_of_calls = 0;
148+
s_pre_executor_nb_of_calls = 0;
149+
s_post_executor_nb_of_calls = 0;
150+
}
151+
152+
static void esp_cli_setup(SemaphoreHandle_t *start_sem, SemaphoreHandle_t *done_sem, int socket_fd[2],
153+
esp_linenoise_handle_t *linenoise_hdl, esp_cli_handle_t *cli_hdl)
154+
{
155+
/* create semaphores */
156+
*start_sem = xSemaphoreCreateBinary();
157+
TEST_ASSERT_NOT_NULL(start_sem);
158+
*done_sem = xSemaphoreCreateBinary();
159+
TEST_ASSERT_NOT_NULL(done_sem);
160+
161+
/* create the socket_pair */
162+
test_socket_setup(socket_fd);
163+
164+
/* ensure both semaphores are in the "taken/empty" state:
165+
taking with 0 timeout guarantees they are empty afterwards
166+
regardless of the create semantics on this FreeRTOS build. */
167+
xSemaphoreTake(*start_sem, 0);
168+
xSemaphoreTake(*done_sem, 0);
169+
170+
esp_linenoise_config_t linenoise_config;
171+
esp_linenoise_get_instance_config_default(&linenoise_config);
172+
linenoise_config.in_fd = socket_fd[0];
173+
linenoise_config.out_fd = socket_fd[0];
174+
TEST_ASSERT_EQUAL(ESP_OK, esp_linenoise_create_instance(&linenoise_config, linenoise_hdl));
175+
TEST_ASSERT_NOT_NULL(*linenoise_hdl);
176+
177+
esp_cli_config_t cli_config = {
178+
.linenoise_handle = *linenoise_hdl,
179+
.command_set_handle = NULL,
180+
.max_cmd_line_size = 256,
181+
.history_save_path = NULL,
182+
.on_enter = { .func = test_on_enter, .ctx = NULL },
183+
.pre_executor = { .func = test_pre_executor, .ctx = NULL },
184+
.post_executor = { .func = test_post_executor, .ctx = NULL },
185+
.on_stop = { .func = test_on_stop, .ctx = NULL },
186+
.on_exit = { .func = test_on_exit, .ctx = NULL }
187+
};
188+
189+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_create(&cli_config, cli_hdl));
190+
TEST_ASSERT_NOT_NULL(*cli_hdl);
191+
192+
s_on_stop_nb_of_calls = 0;
193+
s_on_exit_nb_of_calls = 0;
194+
s_on_enter_nb_of_calls = 0;
195+
s_pre_executor_nb_of_calls = 0;
196+
s_post_executor_nb_of_calls = 0;
197+
}
198+
199+
TEST_CASE("esp_cli() loop calls all callbacks and exit on call to esp_cli_stop", "[esp_cli]")
200+
{
201+
SemaphoreHandle_t start_sem, done_sem;
202+
esp_linenoise_handle_t linenoise_hdl;
203+
esp_cli_handle_t cli_hdl;
204+
esp_cli_setup(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl);
205+
206+
/* create the esp_cli instance task */
207+
task_args_t args = {.start_sem = start_sem, .done_sem = done_sem, .hdl = cli_hdl};
208+
BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task", 4096, &args, 5, NULL);
209+
TEST_ASSERT_EQUAL(pdPASS, rc);
210+
211+
/* should fail before esp_cli instance is started */
212+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl));
213+
214+
/* start esp_cli instance */
215+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_start(NULL));
216+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl));
217+
218+
wait_ms(100);
219+
220+
/* wait for the esp_cli task to signal it started */
221+
TEST_ASSERT_TRUE(xSemaphoreTake(start_sem, pdMS_TO_TICKS(2000)));
222+
223+
/* send a dummy string new line terminated to trigger linenoise to return */
224+
const char *input_line = "dummy_message\n";
225+
test_send_characters(s_socket_fd[1], input_line);
226+
227+
/* wait for a bit so esp_cli() has time to loop back into esp_linenoise_get_line */
228+
wait_ms(500);
229+
230+
/* check that pre-executor, post-executor callbacks are called */
231+
TEST_ASSERT_EQUAL(1, s_pre_executor_nb_of_calls);
232+
TEST_ASSERT_EQUAL(1, s_post_executor_nb_of_calls);
233+
234+
/* stop esp_cli and wait for task to finish (emulate pthread_join) */
235+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(NULL));
236+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl));
237+
238+
/* wait for the esp_cli task to signal completion */
239+
TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, pdMS_TO_TICKS(2000)));
240+
241+
/* check that all callbacks were called the right number of times */
242+
TEST_ASSERT_EQUAL(1, s_on_stop_nb_of_calls);
243+
TEST_ASSERT_EQUAL(1, s_on_enter_nb_of_calls);
244+
TEST_ASSERT_EQUAL(1, s_on_exit_nb_of_calls);
245+
TEST_ASSERT_EQUAL(2, s_pre_executor_nb_of_calls);
246+
TEST_ASSERT_EQUAL(2, s_post_executor_nb_of_calls);
247+
248+
/* make sure calling stop fails because the esp_cli instance is no longer running */
249+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl));
250+
251+
/* destroy the esp_cli instance */
252+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_cli_destroy(NULL));
253+
esp_cli_teardown(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl);
254+
}
255+
256+
TEST_CASE("esp_cli() exits when esp_cli_stop() called from the task running esp_cli()", "[esp_cli]")
257+
{
258+
SemaphoreHandle_t start_sem, done_sem;
259+
esp_linenoise_handle_t linenoise_hdl;
260+
esp_cli_handle_t cli_hdl;
261+
esp_cli_setup(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl);
262+
263+
/* create the esp_cli instance task */
264+
task_args_t args = {.start_sem = start_sem, .done_sem = done_sem, .hdl = cli_hdl};
265+
BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task", 4096, &args, 5, NULL);
266+
TEST_ASSERT_EQUAL(pdPASS, rc);
267+
268+
/* start esp_cli instance */
269+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl));
270+
271+
wait_ms(100);
272+
273+
/* wait for the esp_cli instance task to signal it started */
274+
TEST_ASSERT_TRUE(xSemaphoreTake(start_sem, pdMS_TO_TICKS(2000)));
275+
276+
/* send the quit command */
277+
const char *quit_cmd_line = "quit \n";
278+
test_send_characters(s_socket_fd[1], quit_cmd_line);
279+
280+
/* wait for the esp_cli instance task to signal completion */
281+
TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, pdMS_TO_TICKS(2000)));
282+
283+
esp_cli_teardown(&start_sem, &done_sem, s_socket_fd, &linenoise_hdl, &cli_hdl);
284+
}
285+
286+
TEST_CASE("create and destroy several instances of esp_cli", "[esp_cli]")
287+
{
288+
/* create semaphores */
289+
SemaphoreHandle_t start_sem_a, start_sem_b;
290+
SemaphoreHandle_t done_sem_a, done_sem_b;
291+
esp_cli_handle_t cli_hdl_a, cli_hdl_b;
292+
esp_linenoise_handle_t linenoise_hdl_a, linenoise_hdl_b;
293+
int socket_fd_a[2], socket_fd_b[2];
294+
295+
/* create 2 instances of esp_cli*/
296+
esp_cli_setup(&start_sem_a, &done_sem_a, socket_fd_a, &linenoise_hdl_a, &cli_hdl_a);
297+
esp_cli_setup(&start_sem_b, &done_sem_b, socket_fd_b, &linenoise_hdl_b, &cli_hdl_b);
298+
299+
/* create the esp_cli instance task A */
300+
task_args_t args_a = {.start_sem = start_sem_a, .done_sem = done_sem_a, .hdl = cli_hdl_a};
301+
BaseType_t rc = xTaskCreate(esp_cli_task, "esp_cli_task_a", 4096, &args_a, 5, NULL);
302+
TEST_ASSERT_EQUAL(pdPASS, rc);
303+
304+
/* create the esp_cli instance task B */
305+
task_args_t args_b = {.start_sem = start_sem_b, .done_sem = done_sem_b, .hdl = cli_hdl_b};
306+
rc = xTaskCreate(esp_cli_task, "esp_cli_task_b", 4096, &args_b, 5, NULL);
307+
TEST_ASSERT_EQUAL(pdPASS, rc);
308+
309+
/* start esp_cli instance */
310+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl_a));
311+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_start(cli_hdl_b));
312+
wait_ms(500);
313+
314+
/* wait for the esp_cli instance tasks to signal it started */
315+
TEST_ASSERT_TRUE(xSemaphoreTake(start_sem_a, pdMS_TO_TICKS(2000)));
316+
TEST_ASSERT_TRUE(xSemaphoreTake(start_sem_b, pdMS_TO_TICKS(2000)));
317+
318+
/* terminate instance A */
319+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl_a));
320+
321+
/* wait for the esp_cli instance task to signal completion */
322+
TEST_ASSERT_TRUE(xSemaphoreTake(done_sem_a, pdMS_TO_TICKS(2000)));
323+
324+
/* terminate instance B */
325+
TEST_ASSERT_EQUAL(ESP_OK, esp_cli_stop(cli_hdl_b));
326+
327+
/* wait for the esp_cli instance task to signal completion */
328+
TEST_ASSERT_TRUE(xSemaphoreTake(done_sem_b, pdMS_TO_TICKS(2000)));
329+
330+
esp_cli_teardown(&start_sem_a, &done_sem_a, socket_fd_a, &linenoise_hdl_a, &cli_hdl_a);
331+
esp_cli_teardown(&start_sem_b, &done_sem_b, socket_fd_b, &linenoise_hdl_b, &cli_hdl_b);
332+
}

0 commit comments

Comments
 (0)