feat(ci): add ci test for auto mode connect passive chnnel hidden ap

This commit is contained in:
muhaidong
2026-05-14 15:54:24 +08:00
parent 315b6547ff
commit 1c640fb30e
6 changed files with 298 additions and 6 deletions
@@ -2,6 +2,7 @@ idf_component_register(SRCS
"app_main.c"
"test_wifi_conn.c"
"test_bss_max_idle.c"
"test_connect_passive_channel_hidden_ap.c"
PRIV_INCLUDE_DIRS . ${CMAKE_CURRENT_BINARY_DIR}
PRIV_REQUIRES cmock test_utils nvs_flash esp_common esp_wifi esp_event
@@ -1,9 +1,11 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_err.h"
@@ -12,8 +14,7 @@
#include "esp_heap_caps.h"
// Some resources are lazy allocated in wifi and lwip
#define TEST_MEMORY_LEAK_THRESHOLD (-1896)
#define TEST_MEMORY_LEAK_THRESHOLD (-2100)
static size_t before_free_8bit;
static size_t before_free_32bit;
@@ -21,7 +22,7 @@ static size_t before_free_32bit;
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, (unsigned)before_free, (unsigned)after_free, (int)delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
@@ -36,8 +37,10 @@ void setUp(void)
void tearDown(void)
{
ESP_ERROR_CHECK(esp_wifi_stop());
vTaskDelay(pdMS_TO_TICKS(100));
ESP_ERROR_CHECK(esp_wifi_deinit());
vTaskDelay(500 / portTICK_PERIOD_MS);
vTaskDelay(pdMS_TO_TICKS(500));
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
@@ -0,0 +1,253 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* Tests station association (L2 only; no DHCP / GOT_IP) to a hidden SoftAP when passive-hidden-AP support is enabled.
*
* Not in default wifi_connect builds: enable CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT only via
* sdkconfig.ci.passive_hidden_ap (pytest config "passive_hidden_ap"), or merge that file in
* SDKCONFIG_DEFAULTS for local builds.
*
* Wi-Fi remote: CONFIG_WIFI_RMT_PASSIVE_HIDDEN_AP_SUPPORT maps to the same option.
*/
#include "sdkconfig.h"
#if CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT
#include <string.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_wifi_default.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "test_utils.h"
#include "freertos/event_groups.h"
#include "soc/soc_caps.h"
#ifndef TEST_SUFFIX_STR
#define TEST_SUFFIX_STR "_0000"
#endif
#define TEST_DEFAULT_SSID "SSID_" CONFIG_IDF_TARGET TEST_SUFFIX_STR
#define TEST_DEFAULT_PWD "PASS_" CONFIG_IDF_TARGET TEST_SUFFIX_STR
#if CONFIG_SOC_WIFI_SUPPORT_5G
/* DFS channel: passive scan is required by regulatory rules on many domains. */
#define TEST_PASSIVE_HIDDEN_AP_CHANNEL (52)
#else
/* 2.4 GHz passive scan scenario: channel 12 with CN regulatory (see apply_country_auto). */
#define TEST_PASSIVE_HIDDEN_AP_CHANNEL (12)
#endif
#define CONNECT_TIMEOUT_MS (30000)
#define MAXIMUM_RETRY (5)
#define WIFI_DISCONNECT_EVENT (1 << 1)
#define WIFI_STA_CONNECTED (1 << 2)
#define WIFI_AP_STA_CONNECTED (1 << 3)
static const char *TAG = "test_passive_hidden_ap";
static EventGroupHandle_t s_wifi_events;
static esp_netif_t *s_sta_netif;
static esp_netif_t *s_ap_netif;
static int s_retry_num;
static bool s_sta_conn_first;
static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
switch (event_id) {
case WIFI_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "WIFI_EVENT_AP_STACONNECTED");
if (s_wifi_events) {
xEventGroupSetBits(s_wifi_events, WIFI_AP_STA_CONNECTED);
}
break;
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI(TAG, "WIFI_EVENT_STA_CONNECTED");
if (s_wifi_events) {
xEventGroupSetBits(s_wifi_events, WIFI_STA_CONNECTED);
s_sta_conn_first = true;
}
break;
case WIFI_EVENT_STA_DISCONNECTED:
ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED");
if (s_wifi_events) {
if ((s_retry_num < MAXIMUM_RETRY) && !s_sta_conn_first) {
s_retry_num++;
ESP_LOGI(TAG, "retry connect to hidden passive-channel AP (%d/%d)", s_retry_num, MAXIMUM_RETRY);
esp_wifi_connect();
} else {
xEventGroupSetBits(s_wifi_events, WIFI_DISCONNECT_EVENT);
}
}
break;
default:
break;
}
}
static esp_err_t event_init_sta_side(void)
{
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
s_sta_netif = esp_netif_create_default_wifi_sta();
return ESP_OK;
}
static esp_err_t event_deinit_sta_side(void)
{
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler));
esp_netif_destroy_default_wifi(s_sta_netif);
s_sta_netif = NULL;
ESP_ERROR_CHECK(esp_event_loop_delete_default());
return ESP_OK;
}
static esp_err_t event_init_ap_side(void)
{
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
s_ap_netif = esp_netif_create_default_wifi_ap();
return ESP_OK;
}
static esp_err_t event_deinit_ap_side(void)
{
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler));
esp_netif_destroy_default_wifi(s_ap_netif);
s_ap_netif = NULL;
ESP_ERROR_CHECK(esp_event_loop_delete_default());
return ESP_OK;
}
static void apply_country_auto(void)
{
wifi_country_t country = {0};
memcpy(country.cc, "CN", 2);
country.schan = 1;
country.nchan = 13;
country.policy = WIFI_COUNTRY_POLICY_AUTO;
#if CONFIG_SOC_WIFI_SUPPORT_5G
country.wifi_5g_channel_mask = 0;
#endif
TEST_ESP_OK(esp_wifi_set_country(&country));
}
static void start_hidden_softap_on_passive_channel(void)
{
wifi_config_t w_config = {
.ap.ssid = TEST_DEFAULT_SSID,
.ap.password = TEST_DEFAULT_PWD,
.ap.ssid_len = strlen(TEST_DEFAULT_SSID),
.ap.channel = TEST_PASSIVE_HIDDEN_AP_CHANNEL,
.ap.authmode = WIFI_AUTH_WPA2_PSK,
.ap.ssid_hidden = true,
.ap.max_connection = 4,
.ap.beacon_interval = 100,
};
event_init_ap_side();
if (s_wifi_events == NULL) {
s_wifi_events = xEventGroupCreate();
}
xEventGroupClearBits(s_wifi_events, 0x00ffffff);
apply_country_auto();
TEST_ESP_OK(esp_wifi_set_mode(WIFI_MODE_AP));
TEST_ESP_OK(esp_wifi_set_config(WIFI_IF_AP, &w_config));
TEST_ESP_OK(esp_wifi_start());
ESP_LOGI(TAG, "hidden softap on channel %u, ssid (hidden): %s", (unsigned)TEST_PASSIVE_HIDDEN_AP_CHANNEL, TEST_DEFAULT_SSID);
}
static void start_sta(void)
{
event_init_sta_side();
if (s_wifi_events == NULL) {
s_wifi_events = xEventGroupCreate();
}
xEventGroupClearBits(s_wifi_events, 0x00ffffff);
apply_country_auto();
TEST_ESP_OK(esp_wifi_set_mode(WIFI_MODE_STA));
TEST_ESP_OK(esp_wifi_start());
}
static void stop_wifi_sta_side(void)
{
TEST_ESP_OK(esp_wifi_stop());
event_deinit_sta_side();
if (s_wifi_events) {
vEventGroupDelete(s_wifi_events);
s_wifi_events = NULL;
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
static void stop_wifi_ap_side(void)
{
TEST_ESP_OK(esp_wifi_stop());
event_deinit_ap_side();
if (s_wifi_events) {
vEventGroupDelete(s_wifi_events);
s_wifi_events = NULL;
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
static void sta_connect_hidden_on_passive_channel(void)
{
wifi_config_t w_config = { 0 };
strncpy((char *)w_config.sta.ssid, TEST_DEFAULT_SSID, sizeof(w_config.sta.ssid));
strncpy((char *)w_config.sta.password, TEST_DEFAULT_PWD, sizeof(w_config.sta.password));
w_config.sta.channel = TEST_PASSIVE_HIDDEN_AP_CHANNEL;
TEST_ESP_OK(esp_wifi_set_config(WIFI_IF_STA, &w_config));
s_retry_num = 0;
s_sta_conn_first = false;
TEST_ESP_OK(esp_wifi_connect());
ESP_LOGI(TAG, "esp_wifi_connect hidden AP, channel hint %u", (unsigned)TEST_PASSIVE_HIDDEN_AP_CHANNEL);
EventBits_t bits = xEventGroupWaitBits(s_wifi_events,
WIFI_STA_CONNECTED | WIFI_DISCONNECT_EVENT,
pdTRUE,
pdFALSE,
pdMS_TO_TICKS(CONNECT_TIMEOUT_MS));
TEST_ASSERT_MESSAGE(bits & WIFI_STA_CONNECTED, "STA did not connect (hidden passive-channel path, L2 only)");
}
static void test_passive_hidden_channel_sta(void)
{
start_sta();
vTaskDelay(pdMS_TO_TICKS(1000));
sta_connect_hidden_on_passive_channel();
stop_wifi_sta_side();
}
static void test_passive_hidden_channel_softap(void)
{
start_hidden_softap_on_passive_channel();
EventBits_t bits = xEventGroupWaitBits(s_wifi_events,
WIFI_AP_STA_CONNECTED,
pdFALSE,
pdFALSE,
pdMS_TO_TICKS(CONNECT_TIMEOUT_MS));
TEST_ASSERT_MESSAGE(bits & WIFI_AP_STA_CONNECTED, "No station connected to hidden softap in time");
stop_wifi_ap_side();
}
TEST_CASE_MULTIPLE_DEVICES("connect passive channel hidden softap",
"[wifi][passive_hidden_ap]",
test_passive_hidden_channel_sta,
test_passive_hidden_channel_softap);
#endif /* CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT */
@@ -1,4 +1,4 @@
# 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
import pytest
from pytest_embedded_idf.unity_tester import CaseTester
@@ -55,3 +55,32 @@ def test_wifi_connect_cases_esp32c2_rev2_xtal26m(case_tester: CaseTester) -> Non
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
def test_wifi_connect_cases_esp32c3_rev1(case_tester: CaseTester) -> None:
case_tester.run_all_cases()
# Must match TEST_CASE_MULTIPLE_DEVICES name string in test_connect_passive_channel_hidden_ap.c (menu line quoted text).
_PASSIVE_HIDDEN_AP_CASE = 'connect passive channel hidden softap'
@pytest.mark.two_duts
@pytest.mark.parametrize(
'count, config',
[
(2, 'passive_hidden_ap'),
],
indirect=True,
)
@idf_parametrize(
'target',
['esp32', 'esp32c3', 'esp32s2', 'esp32s3', 'esp32c5', 'esp32c6', 'esp32c61'],
indirect=['target'],
)
def test_wifi_connect_passive_hidden_ap(case_tester: CaseTester) -> None:
"""Firmware uses sdkconfig.ci.passive_hidden_ap; runs only the passive hidden AP multi-DUT case."""
for case in case_tester.test_menu:
if case.name == _PASSIVE_HIDDEN_AP_CASE:
case_tester.run_multi_dev_case(case=case, reset=True)
return
pytest.fail(
f'Case {_PASSIVE_HIDDEN_AP_CASE!r} not in menu; build must merge sdkconfig.ci.passive_hidden_ap '
'(CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT).'
)
@@ -0,0 +1,3 @@
# Merged when pytest uses config "passive_hidden_ap" (or set SDKCONFIG_DEFAULTS to include this file).
# Enables the hidden passive-channel AP test and WIFI_INIT passive-hidden feature bit only for that build.
CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT=y
@@ -1,3 +1,6 @@
# Passive hidden AP test: off by default. Merge sdkconfig.ci.passive_hidden_ap when building/running that case only
# (see pytest test_wifi_connect_passive_hidden_ap). Example:
# idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.passive_hidden_ap" set-target esp32c3 build
# ignore task watchdog triggered by unity_run_menu
CONFIG_ESP_TASK_WDT_EN=n