fix(esp_wifi): Redesigned eloop code and extend UTs

This commit is contained in:
Kapil Gupta
2026-05-01 12:38:45 +05:30
parent d574eca661
commit 3368b271f5
2 changed files with 1243 additions and 345 deletions
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,13 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <time.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_system.h"
@@ -19,29 +21,110 @@
#include "memory_checks.h"
#include "test_wpa_supplicant_common.h"
static uint32_t timeouts_usec[6] = { 10000, 1000, 10000, 5000, 15000, 1000 };
static uint32_t timeouts_sec[6] = { 10, 1, 10, 5, 15, 1 };
bool current_task_is_wifi_task(void);
static void eloop_test_reset_hooks(void)
{
}
static const uint32_t timeouts_usec[6] = { 300000, 100000, 300000, 200000, 400000, 100000 };
static const uint32_t timeouts_sec[6] = { 0, 0, 0, 0, 0, 0 };
static int executed_order[6];
static int t;
static struct os_reltime ts;
struct timeout_test_ctx {
void *sem;
int count;
int last_id;
bool ran_in_wifi_task;
struct os_reltime armed_at;
struct os_reltime fired_at;
};
struct chain_test_ctx {
void *sem;
struct timeout_test_ctx *followup_ctx;
struct timeout_test_ctx *cancel_ctx;
int followup_id;
int cancel_id;
int count;
bool ran_in_wifi_task;
};
struct blocking_waiter_ctx {
int idx;
eloop_blocking_timeout_handler handler;
void *done_sem;
};
static void *blocking_gate;
static void *blocking_done_sem;
static int blocking_results[2];
static bool blocking_in_wifi_task[2];
static int blocking_ids[2] = { 11, 22 };
static int blocking_task_index[2] = { 0, 1 };
static uint32_t reltime_diff_ms(const struct os_reltime *start,
const struct os_reltime *end)
{
struct os_reltime diff;
os_reltime_sub(end, start, &diff);
return diff.sec * 1000U + diff.usec / 1000U;
}
static void wait_sem_or_fail(void *sem, uint32_t timeout_ms)
{
TEST_ASSERT_EQUAL(pdTRUE,
os_semphr_take(sem, os_task_ms_to_tick(timeout_ms)));
}
static void expect_sem_timeout(void *sem, uint32_t timeout_ms)
{
TEST_ASSERT_EQUAL(pdFALSE,
os_semphr_take(sem, os_task_ms_to_tick(timeout_ms)));
}
static void eloop_test_wifi_start(void)
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
cfg.nvs_enable = false;
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
}
static void eloop_test_wifi_stop(void)
{
TEST_ESP_OK(esp_wifi_stop());
TEST_ESP_OK(esp_wifi_deinit());
}
static void reset_timeout_ctx(struct timeout_test_ctx *ctx, void *sem)
{
memset(ctx, 0, sizeof(*ctx));
ctx->sem = sem;
ctx->last_id = -1;
os_get_reltime(&ctx->armed_at);
}
/* there is only single instance of esp_timer so no need of protection */
static void callback(void *a, void *b)
static void ordered_timeout_callback(void *a, void *b)
{
int *i = a;
struct os_time age, now;
int32_t ms_diff;
os_get_reltime(&now);
os_time_sub(&now, &ts, &age);
int32_t ms_diff = (age.sec - timeouts_sec[*i]) * 1000 +
(age.usec - timeouts_usec[*i]) / 1000;
ms_diff = (age.sec - timeouts_sec[*i]) * 1000 +
(age.usec - timeouts_usec[*i]) / 1000;
if (t > 5) {
TEST_ASSERT(0);
}
/* let's give 50 ms offset for this small block */
/* Allow some jitter from scheduling into pptask context. */
if (ms_diff > 50) {
executed_order[t] = -1;
} else {
@@ -51,49 +134,432 @@ static void callback(void *a, void *b)
ESP_LOGI("Eloop Test", "timer[%d] ran after %" PRId32 " msec of scheduled time",
*i, ms_diff);
}
static void generic_timeout_callback(void *eloop_data, void *user_data)
{
struct timeout_test_ctx *ctx = eloop_data;
int *id = user_data;
ctx->count++;
ctx->last_id = id ? *id : -1;
ctx->ran_in_wifi_task = current_task_is_wifi_task();
os_get_reltime(&ctx->fired_at);
if (ctx->sem) {
os_semphr_give(ctx->sem);
}
}
static void chain_timeout_callback(void *eloop_data, void *user_data)
{
struct chain_test_ctx *ctx = eloop_data;
(void) user_data;
ctx->count++;
ctx->ran_in_wifi_task = current_task_is_wifi_task();
eloop_cancel_timeout(generic_timeout_callback, ctx->cancel_ctx,
&ctx->cancel_id);
os_get_reltime(&ctx->followup_ctx->armed_at);
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 0, generic_timeout_callback,
ctx->followup_ctx,
&ctx->followup_id));
if (ctx->sem) {
os_semphr_give(ctx->sem);
}
}
static int blocking_callback(void *a, void *b)
{
int idx = *(int *) a;
int *id = b;
blocking_in_wifi_task[idx] = current_task_is_wifi_task();
os_semphr_take(blocking_gate, OS_BLOCK);
return *id;
}
static int immediate_blocking_callback(void *a, void *b)
{
int idx = *(int *) a;
int *id = b;
blocking_in_wifi_task[idx] = current_task_is_wifi_task();
return *id;
}
static void blocking_waiter_task(void *arg)
{
struct blocking_waiter_ctx *ctx = arg;
int idx = ctx->idx;
blocking_results[idx] = eloop_register_timeout_blocking(ctx->handler,
&blocking_task_index[idx],
&blocking_ids[idx]);
os_semphr_give(ctx->done_sem ? ctx->done_sem : blocking_done_sem);
vTaskDelete(NULL);
}
static void assert_eloop_inactive_api_behavior(void)
{
struct timeout_test_ctx ctx;
struct os_reltime remaining = { .sec = 1, .usec = 1 };
int id = 91;
memset(&ctx, 0, sizeof(ctx));
TEST_ASSERT_EQUAL(-1, eloop_register_timeout(0, 1000,
generic_timeout_callback,
&ctx, &id));
TEST_ASSERT_EQUAL(-1, eloop_register_timeout_blocking(immediate_blocking_callback,
&blocking_task_index[0],
&blocking_ids[0]));
TEST_ASSERT_EQUAL(0, eloop_cancel_timeout(generic_timeout_callback,
&ctx, &id));
TEST_ASSERT_EQUAL(0, eloop_cancel_timeout_one(generic_timeout_callback,
&ctx, &id, &remaining));
TEST_ASSERT_EQUAL(0, remaining.sec);
TEST_ASSERT_EQUAL(0, remaining.usec);
TEST_ASSERT_EQUAL(0, eloop_is_timeout_registered(generic_timeout_callback,
&ctx, &id));
TEST_ASSERT_EQUAL(-1, eloop_deplete_timeout(0, 1000,
generic_timeout_callback,
&ctx, &id));
TEST_ASSERT_EQUAL(-1, eloop_replenish_timeout(0, 1000,
generic_timeout_callback,
&ctx, &id));
}
extern const wifi_osi_funcs_t *wifi_funcs;
/* Check if eloop runs its timers correctly & in correct order */
TEST_CASE("Test eloop timers run", "[eloop]")
{
int execution_order[6] = { 1, 5, 3, 0, 2, 4 };
int index[6] = { 0, 1, 2, 3, 4, 5 };
int fired_before_stop;
eloop_test_reset_hooks();
set_leak_threshold(1000);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
cfg.nvs_enable = false;
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
TEST_ESP_OK(esp_wifi_stop());
TEST_ESP_OK(esp_wifi_deinit());
eloop_test_wifi_start();
eloop_test_wifi_stop();
/* Reset memory stats since some is leaked during the first initialization */
test_utils_record_free_mem();
int execution_order[6] = {1, 5, 3, 0, 2, 4};
int index[6] = {0, 1, 2, 3, 4, 5};
t = 0;
/* We need pptask to run eloop, wifi init will do that */
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
eloop_test_wifi_start();
os_get_reltime(&ts);
for (int i = 0; i < 6; i++) {
eloop_register_timeout(timeouts_sec[i], timeouts_usec[i],
callback, &index[i], NULL);
TEST_ASSERT_EQUAL(0, eloop_register_timeout(timeouts_sec[i], timeouts_usec[i],
ordered_timeout_callback,
&index[i], NULL));
}
/* wait for all timers to run */
os_sleep(20, 0);
/* check the execution order, this will also check whether they were fired at correct time */
os_sleep(1, 0);
TEST_ASSERT(memcmp(execution_order, executed_order, 6 * sizeof(int)) == 0);
t = 0;
/* Add timers to check deinit happens gracefully */
for (int i = 0; i < 6; i++) {
eloop_register_timeout(timeouts_sec[i], timeouts_usec[i],
callback, &index[i], NULL);
TEST_ASSERT_EQUAL(0, eloop_register_timeout(timeouts_sec[i], timeouts_usec[i],
ordered_timeout_callback,
&index[i], NULL));
}
/* Stop wifi before all the timers have run */
os_sleep(2, 0);
TEST_ESP_OK(esp_wifi_stop());
TEST_ESP_OK(esp_wifi_deinit());
os_sleep(3, 0);
/* Pending async timers are dropped during eloop destroy; only callbacks
* already dispatched before stop should run. */
vTaskDelay(250 / portTICK_PERIOD_MS);
fired_before_stop = t;
eloop_test_wifi_stop();
vTaskDelay(300 / portTICK_PERIOD_MS);
TEST_ASSERT_EQUAL(fired_before_stop, t);
TEST_ASSERT_TRUE(t >= 1);
TEST_ASSERT_TRUE(t < 6);
}
TEST_CASE("Test eloop rejects APIs before init and after destroy", "[eloop]")
{
eloop_test_reset_hooks();
assert_eloop_inactive_api_behavior();
eloop_test_wifi_start();
eloop_test_wifi_stop();
assert_eloop_inactive_api_behavior();
}
TEST_CASE("Test eloop can reinitialize repeatedly", "[eloop]")
{
for (int i = 0; i < 3; i++) {
struct timeout_test_ctx ctx;
void *sem = os_semphr_create(1, 0);
int user_id = 100 + i;
TEST_ASSERT_NOT_NULL(sem);
eloop_test_reset_hooks();
reset_timeout_ctx(&ctx, sem);
eloop_test_wifi_start();
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 50000,
generic_timeout_callback,
&ctx, &user_id));
wait_sem_or_fail(sem, 1000);
TEST_ASSERT_EQUAL(1, ctx.count);
TEST_ASSERT_TRUE(ctx.ran_in_wifi_task);
eloop_test_wifi_stop();
os_semphr_delete(sem);
}
}
TEST_CASE("Test eloop cancel and query APIs", "[eloop]")
{
struct timeout_test_ctx ctx_a, ctx_b, ctx_c;
void *sem_a = os_semphr_create(1, 0);
void *sem_b = os_semphr_create(1, 0);
void *sem_c = os_semphr_create(1, 0);
int user_a = 1;
int user_b = 2;
TEST_ASSERT_NOT_NULL(sem_a);
TEST_ASSERT_NOT_NULL(sem_b);
TEST_ASSERT_NOT_NULL(sem_c);
eloop_test_reset_hooks();
reset_timeout_ctx(&ctx_a, sem_a);
reset_timeout_ctx(&ctx_b, sem_b);
reset_timeout_ctx(&ctx_c, sem_c);
eloop_test_wifi_start();
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 400000, generic_timeout_callback,
&ctx_a, &user_a));
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 400000, generic_timeout_callback,
&ctx_b, &user_b));
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 400000, generic_timeout_callback,
&ctx_c, &user_a));
TEST_ASSERT_EQUAL(1, eloop_is_timeout_registered(generic_timeout_callback,
&ctx_a, &user_a));
TEST_ASSERT_EQUAL(1, eloop_is_timeout_registered(generic_timeout_callback,
&ctx_b, &user_b));
TEST_ASSERT_EQUAL(1, eloop_is_timeout_registered(generic_timeout_callback,
&ctx_c, &user_a));
TEST_ASSERT_EQUAL(2, eloop_cancel_timeout(generic_timeout_callback,
ELOOP_ALL_CTX, &user_a));
TEST_ASSERT_EQUAL(0, eloop_is_timeout_registered(generic_timeout_callback,
&ctx_a, &user_a));
TEST_ASSERT_EQUAL(0, eloop_is_timeout_registered(generic_timeout_callback,
&ctx_c, &user_a));
TEST_ASSERT_EQUAL(1, eloop_is_timeout_registered(generic_timeout_callback,
&ctx_b, &user_b));
wait_sem_or_fail(sem_b, 1000);
expect_sem_timeout(sem_a, 50);
expect_sem_timeout(sem_c, 50);
TEST_ASSERT_EQUAL(0, eloop_is_timeout_registered(generic_timeout_callback,
&ctx_b, &user_b));
TEST_ASSERT_EQUAL(0, ctx_a.count);
TEST_ASSERT_EQUAL(1, ctx_b.count);
TEST_ASSERT_EQUAL(0, ctx_c.count);
TEST_ASSERT_TRUE(ctx_b.ran_in_wifi_task);
eloop_test_wifi_stop();
os_semphr_delete(sem_a);
os_semphr_delete(sem_b);
os_semphr_delete(sem_c);
}
TEST_CASE("Test eloop cancel_timeout_one returns remaining", "[eloop]")
{
struct timeout_test_ctx ctx;
struct os_reltime remaining;
uint32_t remaining_ms;
void *sem = os_semphr_create(1, 0);
int user_id = 7;
TEST_ASSERT_NOT_NULL(sem);
eloop_test_reset_hooks();
reset_timeout_ctx(&ctx, sem);
eloop_test_wifi_start();
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 600000, generic_timeout_callback,
&ctx, &user_id));
TEST_ASSERT_EQUAL(1, eloop_is_timeout_registered(generic_timeout_callback,
&ctx, &user_id));
vTaskDelay(150 / portTICK_PERIOD_MS);
TEST_ASSERT_EQUAL(1, eloop_cancel_timeout_one(generic_timeout_callback,
&ctx, &user_id, &remaining));
remaining_ms = remaining.sec * 1000U + remaining.usec / 1000U;
TEST_ASSERT_TRUE(remaining_ms >= 250 && remaining_ms <= 550);
TEST_ASSERT_EQUAL(0, eloop_is_timeout_registered(generic_timeout_callback,
&ctx, &user_id));
TEST_ASSERT_EQUAL(0, eloop_cancel_timeout_one(generic_timeout_callback,
&ctx, &user_id, &remaining));
expect_sem_timeout(sem, 700);
TEST_ASSERT_EQUAL(0, ctx.count);
eloop_test_wifi_stop();
os_semphr_delete(sem);
}
TEST_CASE("Test eloop deplete and replenish APIs", "[eloop]")
{
struct timeout_test_ctx deplete_ctx, replenish_ctx, nochange_ctx;
void *deplete_sem = os_semphr_create(1, 0);
void *replenish_sem = os_semphr_create(1, 0);
int deplete_id = 31;
int replenish_id = 32;
int nochange_id = 33;
uint32_t elapsed_ms;
TEST_ASSERT_NOT_NULL(deplete_sem);
TEST_ASSERT_NOT_NULL(replenish_sem);
eloop_test_reset_hooks();
eloop_test_wifi_start();
reset_timeout_ctx(&deplete_ctx, deplete_sem);
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 800000, generic_timeout_callback,
&deplete_ctx, &deplete_id));
TEST_ASSERT_EQUAL(1, eloop_deplete_timeout(0, 150000, generic_timeout_callback,
&deplete_ctx, &deplete_id));
wait_sem_or_fail(deplete_sem, 600);
elapsed_ms = reltime_diff_ms(&deplete_ctx.armed_at, &deplete_ctx.fired_at);
TEST_ASSERT_TRUE(elapsed_ms < 600);
TEST_ASSERT_TRUE(deplete_ctx.ran_in_wifi_task);
reset_timeout_ctx(&replenish_ctx, replenish_sem);
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 150000, generic_timeout_callback,
&replenish_ctx, &replenish_id));
TEST_ASSERT_EQUAL(1, eloop_replenish_timeout(0, 600000, generic_timeout_callback,
&replenish_ctx, &replenish_id));
expect_sem_timeout(replenish_sem, 250);
wait_sem_or_fail(replenish_sem, 1000);
elapsed_ms = reltime_diff_ms(&replenish_ctx.armed_at, &replenish_ctx.fired_at);
TEST_ASSERT_TRUE(elapsed_ms >= 450 && elapsed_ms <= 1100);
TEST_ASSERT_TRUE(replenish_ctx.ran_in_wifi_task);
reset_timeout_ctx(&nochange_ctx, NULL);
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 200000, generic_timeout_callback,
&nochange_ctx, &nochange_id));
TEST_ASSERT_EQUAL(0, eloop_deplete_timeout(0, 500000, generic_timeout_callback,
&nochange_ctx, &nochange_id));
TEST_ASSERT_EQUAL(1, eloop_cancel_timeout(generic_timeout_callback,
&nochange_ctx, &nochange_id));
reset_timeout_ctx(&nochange_ctx, NULL);
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 600000, generic_timeout_callback,
&nochange_ctx, &nochange_id));
TEST_ASSERT_EQUAL(0, eloop_replenish_timeout(0, 100000, generic_timeout_callback,
&nochange_ctx, &nochange_id));
TEST_ASSERT_EQUAL(1, eloop_cancel_timeout(generic_timeout_callback,
&nochange_ctx, &nochange_id));
TEST_ASSERT_EQUAL(-1, eloop_deplete_timeout(0, 100000, generic_timeout_callback,
&nochange_ctx, &nochange_id));
TEST_ASSERT_EQUAL(-1, eloop_replenish_timeout(0, 100000, generic_timeout_callback,
&nochange_ctx, &nochange_id));
eloop_test_wifi_stop();
os_semphr_delete(deplete_sem);
os_semphr_delete(replenish_sem);
}
TEST_CASE("Test eloop callbacks can schedule and cancel timers", "[eloop]")
{
struct timeout_test_ctx followup_ctx, cancel_ctx;
struct chain_test_ctx chain_ctx;
void *chain_sem = os_semphr_create(1, 0);
void *followup_sem = os_semphr_create(1, 0);
void *cancel_sem = os_semphr_create(1, 0);
TEST_ASSERT_NOT_NULL(chain_sem);
TEST_ASSERT_NOT_NULL(followup_sem);
TEST_ASSERT_NOT_NULL(cancel_sem);
eloop_test_reset_hooks();
reset_timeout_ctx(&followup_ctx, followup_sem);
reset_timeout_ctx(&cancel_ctx, cancel_sem);
memset(&chain_ctx, 0, sizeof(chain_ctx));
chain_ctx.sem = chain_sem;
chain_ctx.followup_ctx = &followup_ctx;
chain_ctx.cancel_ctx = &cancel_ctx;
chain_ctx.followup_id = 41;
chain_ctx.cancel_id = 42;
eloop_test_wifi_start();
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 400000, generic_timeout_callback,
&cancel_ctx, &chain_ctx.cancel_id));
TEST_ASSERT_EQUAL(0, eloop_register_timeout(0, 100000, chain_timeout_callback,
&chain_ctx, NULL));
wait_sem_or_fail(chain_sem, 500);
wait_sem_or_fail(followup_sem, 500);
expect_sem_timeout(cancel_sem, 500);
TEST_ASSERT_EQUAL(1, chain_ctx.count);
TEST_ASSERT_TRUE(chain_ctx.ran_in_wifi_task);
TEST_ASSERT_EQUAL(1, followup_ctx.count);
TEST_ASSERT_TRUE(followup_ctx.ran_in_wifi_task);
TEST_ASSERT_EQUAL(0, cancel_ctx.count);
TEST_ASSERT_EQUAL(0, eloop_is_timeout_registered(generic_timeout_callback,
&cancel_ctx, &chain_ctx.cancel_id));
eloop_test_wifi_stop();
os_semphr_delete(chain_sem);
os_semphr_delete(followup_sem);
os_semphr_delete(cancel_sem);
}
TEST_CASE("Test eloop blocking waiters stay paired", "[eloop]")
{
struct blocking_waiter_ctx waiter_ctx_a = {
.idx = 0,
.handler = blocking_callback,
};
struct blocking_waiter_ctx waiter_ctx_b = {
.idx = 1,
.handler = blocking_callback,
};
blocking_results[0] = 0;
blocking_results[1] = 0;
blocking_in_wifi_task[0] = false;
blocking_in_wifi_task[1] = false;
eloop_test_reset_hooks();
eloop_test_wifi_start();
blocking_gate = os_semphr_create(2, 0);
blocking_done_sem = os_semphr_create(2, 0);
TEST_ASSERT_NOT_NULL(blocking_gate);
TEST_ASSERT_NOT_NULL(blocking_done_sem);
waiter_ctx_a.done_sem = blocking_done_sem;
waiter_ctx_b.done_sem = blocking_done_sem;
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(blocking_waiter_task, "eloop_blk_a",
4096, &waiter_ctx_a, 3, NULL));
vTaskDelay(50 / portTICK_PERIOD_MS);
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(blocking_waiter_task, "eloop_blk_b",
4096, &waiter_ctx_b, 4, NULL));
vTaskDelay(50 / portTICK_PERIOD_MS);
os_semphr_give(blocking_gate);
os_semphr_give(blocking_gate);
wait_sem_or_fail(blocking_done_sem, 2000);
wait_sem_or_fail(blocking_done_sem, 2000);
TEST_ASSERT_EQUAL(11, blocking_results[0]);
TEST_ASSERT_EQUAL(22, blocking_results[1]);
TEST_ASSERT_TRUE(blocking_in_wifi_task[0]);
TEST_ASSERT_TRUE(blocking_in_wifi_task[1]);
os_semphr_delete(blocking_done_sem);
os_semphr_delete(blocking_gate);
blocking_done_sem = NULL;
blocking_gate = NULL;
eloop_test_wifi_stop();
}